'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 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; let resolve, reject; // Saving the local callstack before making an async call const err = new Error(); try { args.push((...results) => { const {lastError} = chrome.runtime; if (lastError) { err.message = lastError.message; reject(err); } else { /* Some callbacks have 2 parameters so we're resolving as an array in that case. For example, chrome.runtime.requestUpdateCheck and chrome.webRequest.onAuthRequired */ 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 })();