2021-01-01 14:27:58 +00:00
|
|
|
/* global API msg */// msg.js
|
|
|
|
/* global addAPI bgReady */// common.js
|
|
|
|
/* global createWorker */// worker-util.js
|
|
|
|
/* global prefs */
|
|
|
|
/* global styleMan */
|
|
|
|
/* global syncMan */
|
|
|
|
/* global updateMan */
|
|
|
|
/* global usercssMan */
|
2021-07-30 12:44:06 +00:00
|
|
|
/* global uswApi */
|
2021-01-01 14:27:58 +00:00
|
|
|
/* global
|
|
|
|
FIREFOX
|
2022-01-27 12:21:02 +00:00
|
|
|
UA
|
2021-01-01 14:27:58 +00:00
|
|
|
URLS
|
|
|
|
activateTab
|
|
|
|
download
|
|
|
|
findExistingTab
|
|
|
|
openURL
|
|
|
|
*/ // toolbox.js
|
2021-12-02 16:49:03 +00:00
|
|
|
/* global colorScheme */ // color-scheme.js
|
2017-03-26 02:30:59 +00:00
|
|
|
'use strict';
|
2017-03-15 12:41:39 +00:00
|
|
|
|
2021-01-01 14:27:58 +00:00
|
|
|
//#region API
|
2020-04-16 10:17:12 +00:00
|
|
|
|
2021-01-01 14:27:58 +00:00
|
|
|
addAPI(/** @namespace API */ {
|
2020-04-16 10:17:12 +00:00
|
|
|
|
2021-07-30 12:44:06 +00:00
|
|
|
/** Temporary storage for data needed elsewhere e.g. in a content script */
|
|
|
|
data: ((data = {}) => ({
|
|
|
|
del: key => delete data[key],
|
|
|
|
get: key => data[key],
|
|
|
|
has: key => key in data,
|
|
|
|
pop: key => {
|
|
|
|
const val = data[key];
|
|
|
|
delete data[key];
|
|
|
|
return val;
|
|
|
|
},
|
|
|
|
set: (key, val) => {
|
|
|
|
data[key] = val;
|
|
|
|
},
|
|
|
|
}))(),
|
|
|
|
|
2021-01-01 14:27:58 +00:00
|
|
|
styles: styleMan,
|
|
|
|
sync: syncMan,
|
|
|
|
updater: updateMan,
|
|
|
|
usercss: usercssMan,
|
2021-07-30 12:44:06 +00:00
|
|
|
usw: uswApi,
|
2021-12-02 16:49:03 +00:00
|
|
|
colorScheme,
|
2021-01-01 14:27:58 +00:00
|
|
|
/** @type {BackgroundWorker} */
|
|
|
|
worker: createWorker({url: '/background/background-worker'}),
|
2018-11-07 06:09:29 +00:00
|
|
|
|
2021-01-01 14:27:58 +00:00
|
|
|
download(url, opts) {
|
|
|
|
return typeof url === 'string' && url.startsWith(URLS.uso) &&
|
|
|
|
this.sender.url.startsWith(URLS.uso) &&
|
|
|
|
download(url, opts || {});
|
|
|
|
},
|
2019-03-03 22:54:37 +00:00
|
|
|
|
2021-01-01 14:27:58 +00:00
|
|
|
/** @returns {string} */
|
2018-11-07 06:09:29 +00:00
|
|
|
getTabUrlPrefix() {
|
2021-01-01 14:27:58 +00:00
|
|
|
return this.sender.tab.url.match(/^([\w-]+:\/+[^/#]+)/)[1];
|
2018-11-07 06:09:29 +00:00
|
|
|
},
|
2018-10-02 12:22:18 +00:00
|
|
|
|
2021-01-01 14:27:58 +00:00
|
|
|
/**
|
|
|
|
* Opens the editor or activates an existing tab
|
|
|
|
* @param {{
|
|
|
|
id?: number
|
|
|
|
domain?: string
|
|
|
|
'url-prefix'?: string
|
|
|
|
}} params
|
|
|
|
* @returns {Promise<chrome.tabs.Tab>}
|
|
|
|
*/
|
|
|
|
async openEditor(params) {
|
|
|
|
const u = new URL(chrome.runtime.getURL('edit.html'));
|
|
|
|
u.search = new URLSearchParams(params);
|
|
|
|
const wnd = prefs.get('openEditInWindow');
|
|
|
|
const wndPos = wnd && prefs.get('windowPosition');
|
|
|
|
const wndBase = wnd && prefs.get('openEditInWindow.popup') ? {type: 'popup'} : {};
|
|
|
|
const ffBug = wnd && FIREFOX; // https://bugzil.la/1271047
|
2021-12-26 16:25:25 +00:00
|
|
|
if (wndPos) {
|
|
|
|
const {left, top, width, height} = wndPos;
|
|
|
|
const r = left + width;
|
|
|
|
const b = top + height;
|
|
|
|
const peek = 32;
|
|
|
|
if (isNaN(r) || r < peek || left > screen.availWidth - peek || width < 100) {
|
|
|
|
delete wndPos.left;
|
|
|
|
delete wndPos.width;
|
|
|
|
}
|
|
|
|
if (isNaN(b) || b < peek || top > screen.availHeight - peek || height < 100) {
|
|
|
|
delete wndPos.top;
|
|
|
|
delete wndPos.height;
|
|
|
|
}
|
|
|
|
}
|
2021-01-01 14:27:58 +00:00
|
|
|
const tab = await openURL({
|
|
|
|
url: `${u}`,
|
|
|
|
currentWindow: null,
|
2021-01-19 06:26:10 +00:00
|
|
|
newWindow: wnd && Object.assign(wndBase, !ffBug && wndPos),
|
2021-01-01 14:27:58 +00:00
|
|
|
});
|
|
|
|
if (ffBug) await browser.windows.update(tab.windowId, wndPos);
|
|
|
|
return tab;
|
2018-07-05 12:42:42 +00:00
|
|
|
},
|
2018-01-01 17:02:49 +00:00
|
|
|
|
2021-01-01 14:27:58 +00:00
|
|
|
/** @returns {Promise<chrome.tabs.Tab>} */
|
|
|
|
async openManage({options = false, search, searchMode} = {}) {
|
|
|
|
let url = chrome.runtime.getURL('manage.html');
|
|
|
|
if (search) {
|
|
|
|
url += `?search=${encodeURIComponent(search)}&searchMode=${searchMode}`;
|
|
|
|
}
|
|
|
|
if (options) {
|
|
|
|
url += '#stylus-options';
|
|
|
|
}
|
2021-05-27 11:34:19 +00:00
|
|
|
const tab = await findExistingTab({
|
2021-01-01 14:27:58 +00:00
|
|
|
url,
|
|
|
|
currentWindow: null,
|
|
|
|
ignoreHash: true,
|
|
|
|
ignoreSearch: true,
|
|
|
|
});
|
|
|
|
if (tab) {
|
|
|
|
await activateTab(tab);
|
|
|
|
if (url !== (tab.pendingUrl || tab.url)) {
|
|
|
|
await msg.sendTab(tab.id, {method: 'pushState', url}).catch(console.error);
|
|
|
|
}
|
|
|
|
return tab;
|
|
|
|
}
|
2021-05-27 11:34:19 +00:00
|
|
|
return openURL({url, ignoreExisting: true}).then(activateTab); // activateTab unminimizes the window
|
2021-01-01 14:27:58 +00:00
|
|
|
},
|
2018-11-07 06:09:29 +00:00
|
|
|
|
2021-01-01 14:27:58 +00:00
|
|
|
/**
|
|
|
|
* Same as openURL, the only extra prop in `opts` is `message` - it'll be sent
|
|
|
|
* when the tab is ready, which is needed in the popup, otherwise another
|
|
|
|
* extension could force the tab to open in foreground thus auto-closing the
|
|
|
|
* popup (in Chrome at least) and preventing the sendMessage code from running
|
|
|
|
* @returns {Promise<chrome.tabs.Tab>}
|
|
|
|
*/
|
2020-10-13 18:14:54 +00:00
|
|
|
async openURL(opts) {
|
|
|
|
const tab = await openURL(opts);
|
|
|
|
if (opts.message) {
|
|
|
|
await onTabReady(tab);
|
|
|
|
await msg.sendTab(tab.id, opts.message);
|
|
|
|
}
|
|
|
|
return tab;
|
2020-02-24 23:16:45 +00:00
|
|
|
function onTabReady(tab) {
|
|
|
|
return new Promise((resolve, reject) =>
|
|
|
|
setTimeout(function ping(numTries = 10, delay = 100) {
|
|
|
|
msg.sendTab(tab.id, {method: 'ping'})
|
|
|
|
.catch(() => false)
|
|
|
|
.then(pong => pong
|
|
|
|
? resolve(tab)
|
|
|
|
: numTries && setTimeout(ping, delay, numTries - 1, delay * 1.5) ||
|
|
|
|
reject('timeout'));
|
|
|
|
}));
|
|
|
|
}
|
2018-11-07 06:09:29 +00:00
|
|
|
},
|
2018-01-01 17:02:49 +00:00
|
|
|
|
2021-01-01 14:27:58 +00:00
|
|
|
prefs: {
|
|
|
|
getValues: () => prefs.__values, // will be deepCopy'd by apiHandler
|
|
|
|
set: prefs.set,
|
2019-11-05 19:30:45 +00:00
|
|
|
},
|
2020-02-23 15:43:26 +00:00
|
|
|
});
|
|
|
|
|
2021-01-01 14:27:58 +00:00
|
|
|
//#endregion
|
|
|
|
//#region Events
|
2017-04-20 14:00:43 +00:00
|
|
|
|
2021-01-01 14:27:58 +00:00
|
|
|
const browserCommands = {
|
|
|
|
openManage: () => API.openManage(),
|
|
|
|
openOptions: () => API.openManage({options: true}),
|
|
|
|
reload: () => chrome.runtime.reload(),
|
|
|
|
styleDisableAll(info) {
|
|
|
|
prefs.set('disableAll', info ? info.checked : !prefs.get('disableAll'));
|
2020-11-18 11:17:15 +00:00
|
|
|
},
|
2017-11-26 11:20:44 +00:00
|
|
|
};
|
2017-03-18 22:35:27 +00:00
|
|
|
|
2021-01-01 14:27:58 +00:00
|
|
|
if (chrome.commands) {
|
|
|
|
chrome.commands.onCommand.addListener(id => browserCommands[id]());
|
2020-02-12 12:47:24 +00:00
|
|
|
}
|
2017-12-02 13:06:57 +00:00
|
|
|
|
2021-01-01 14:27:58 +00:00
|
|
|
chrome.runtime.onInstalled.addListener(({reason, previousVersion}) => {
|
2022-01-27 12:21:02 +00:00
|
|
|
if (reason === 'install') {
|
|
|
|
if (UA.mobile) prefs.set('manage.newUI', false);
|
|
|
|
if (UA.windows) prefs.set('editor.keyMap', 'sublime');
|
|
|
|
}
|
2022-01-29 12:35:01 +00:00
|
|
|
// TODO: remove this before 1.5.23 as it's only for a few users who installed git 26b75e77
|
|
|
|
if (reason === 'update' && previousVersion === '1.5.22') {
|
|
|
|
for (const dbName of ['drafts', prefs.STORAGE_KEY]) {
|
|
|
|
try {
|
|
|
|
indexedDB.open(dbName).onsuccess = async e => {
|
|
|
|
const idb = /** @type IDBDatabase */ e.target.result;
|
|
|
|
const ta = idb.objectStoreNames[0] === 'data' && idb.transaction(['data']);
|
|
|
|
if (ta && ta.objectStore('data').autoIncrement) {
|
|
|
|
ta.abort();
|
|
|
|
idb.close();
|
|
|
|
await new Promise(setTimeout);
|
|
|
|
indexedDB.deleteDatabase(dbName);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
} catch (e) {}
|
|
|
|
}
|
|
|
|
}
|
2021-01-01 14:27:58 +00:00
|
|
|
});
|
2020-02-02 04:36:54 +00:00
|
|
|
|
2021-01-01 14:27:58 +00:00
|
|
|
msg.on((msg, sender) => {
|
|
|
|
if (msg.method === 'invokeAPI') {
|
|
|
|
let res = msg.path.reduce((res, name) => res && res[name], API);
|
|
|
|
if (!res) throw new Error(`Unknown API.${msg.path.join('.')}`);
|
|
|
|
res = res.apply({msg, sender}, msg.args);
|
|
|
|
return res === undefined ? null : res;
|
2018-11-07 06:09:29 +00:00
|
|
|
}
|
2021-01-01 14:27:58 +00:00
|
|
|
});
|
2020-02-02 04:36:54 +00:00
|
|
|
|
2021-01-01 14:27:58 +00:00
|
|
|
//#endregion
|
|
|
|
|
|
|
|
Promise.all([
|
2021-11-02 12:32:43 +00:00
|
|
|
browser.extension.isAllowedFileSchemeAccess()
|
|
|
|
.then(res => API.data.set('hasFileAccess', res)),
|
2021-01-01 14:27:58 +00:00
|
|
|
bgReady.styles,
|
|
|
|
/* These are loaded conditionally.
|
|
|
|
Each item uses `require` individually so IDE can jump to the source and track usage. */
|
|
|
|
FIREFOX &&
|
|
|
|
require(['/background/style-via-api']),
|
|
|
|
FIREFOX && ((browser.commands || {}).update) &&
|
|
|
|
require(['/background/browser-cmd-hotkeys']),
|
|
|
|
!FIREFOX &&
|
|
|
|
require(['/background/content-scripts']),
|
|
|
|
chrome.contextMenus &&
|
|
|
|
require(['/background/context-menus']),
|
|
|
|
]).then(() => {
|
|
|
|
bgReady._resolveAll();
|
|
|
|
msg.isBgReady = true;
|
|
|
|
msg.broadcast({method: 'backgroundReady'});
|
|
|
|
});
|