121 lines
4.0 KiB
JavaScript
121 lines
4.0 KiB
JavaScript
'use strict';
|
|
|
|
(() => {
|
|
/* Chrome reinjects content script when documentElement is replaced so we ignore it
|
|
by checking against a literal `1`, not just `if (truthy)`, because <html id="INJECTED">
|
|
is exposed per HTML spec as a global `window.INJECTED` */
|
|
if (window.INJECTED === 1) return;
|
|
|
|
//#region for content scripts and our extension pages
|
|
|
|
if (!((window.browser || {}).runtime || {}).sendMessage) {
|
|
/* Auto-promisifier with a fallback to direct call on signature error.
|
|
The fallback isn't used now since we call all synchronous methods via `chrome` */
|
|
const directEvents = ['addListener', 'removeListener', 'hasListener', 'hasListeners'];
|
|
// generated by tools/chrome-api-no-cb.js
|
|
const directMethods = {
|
|
alarms: ['create'],
|
|
extension: ['getBackgroundPage', 'getExtensionTabs', 'getURL', 'getViews', 'setUpdateUrlData'],
|
|
i18n: ['getMessage', 'getUILanguage'],
|
|
identity: ['getRedirectURL'],
|
|
runtime: ['connect', 'connectNative', 'getManifest', 'getURL', 'reload', 'restart'],
|
|
tabs: ['connect'],
|
|
};
|
|
const promisify = function (fn, ...args) {
|
|
let res;
|
|
try {
|
|
let resolve, reject;
|
|
/* Some callbacks have 2 parameters so we're resolving as an array in that case.
|
|
For example, chrome.runtime.requestUpdateCheck and chrome.webRequest.onAuthRequired */
|
|
args.push((...results) =>
|
|
chrome.runtime.lastError ?
|
|
reject(new Error(chrome.runtime.lastError.message)) :
|
|
resolve(results.length <= 1 ? results[0] : results));
|
|
fn.apply(this, args);
|
|
res = new Promise((...rr) => ([resolve, reject] = rr));
|
|
} catch (err) {
|
|
if (!err.message.includes('No matching signature')) {
|
|
throw err;
|
|
}
|
|
args.pop();
|
|
res = fn.apply(this, args);
|
|
}
|
|
return res;
|
|
};
|
|
const proxify = (src, srcName, target, key) => {
|
|
let res = src[key];
|
|
if (res && typeof res === 'object') {
|
|
res = createProxy(res, key); // eslint-disable-line no-use-before-define
|
|
} else if (typeof res === 'function') {
|
|
res = (directMethods[srcName] || directEvents).includes(key)
|
|
? res.bind(src)
|
|
: promisify.bind(src, res);
|
|
}
|
|
target[key] = res;
|
|
return res;
|
|
};
|
|
const createProxy = (src, srcName) =>
|
|
new Proxy({}, {
|
|
get(target, key) {
|
|
return target[key] || proxify(src, srcName, target, key);
|
|
},
|
|
});
|
|
window.browser = createProxy(chrome);
|
|
}
|
|
|
|
//#endregion
|
|
|
|
if (!chrome.tabs) return;
|
|
|
|
//#region for our extension pages
|
|
|
|
const reqPromise = {};
|
|
window.require = async function require(urls, cb) {
|
|
const promises = [];
|
|
const all = [];
|
|
const toLoad = [];
|
|
for (let url of Array.isArray(urls) ? urls : [urls]) {
|
|
const isCss = url.endsWith('.css');
|
|
const tag = isCss ? 'link' : 'script';
|
|
const attr = isCss ? 'href' : 'src';
|
|
if (!isCss && !url.endsWith('.js')) url += '.js';
|
|
if (url.startsWith('/')) url = url.slice(1);
|
|
let el = document.head.querySelector(`${tag}[${attr}$="${url}"]`);
|
|
if (!el) {
|
|
el = document.createElement(tag);
|
|
toLoad.push(el);
|
|
reqPromise[url] = new Promise((resolve, reject) => {
|
|
el.onload = resolve;
|
|
el.onerror = reject;
|
|
el[attr] = url;
|
|
if (isCss) el.rel = 'stylesheet';
|
|
}).catch(console.warn);
|
|
}
|
|
promises.push(reqPromise[url]);
|
|
all.push(el);
|
|
}
|
|
if (toLoad.length) document.head.append(...toLoad);
|
|
if (promises.length) await Promise.all(promises);
|
|
if (cb) cb(...all);
|
|
return all[0];
|
|
};
|
|
|
|
if (!(new URLSearchParams({foo: 1})).get('foo')) {
|
|
// TODO: remove when minimum_chrome_version >= 61
|
|
window.URLSearchParams = class extends URLSearchParams {
|
|
constructor(init) {
|
|
if (init && typeof init === 'object') {
|
|
super();
|
|
for (const [key, val] of Object.entries(init)) {
|
|
this.set(key, val);
|
|
}
|
|
} else {
|
|
super(...arguments);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
//#endregion
|
|
})();
|