'use strict'; if (self.INJECTED !== 1) { /* 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` */ Promise.resolve().then(() => (self.INJECTED = 1)); const isContentScript = !chrome.tabs; //#region Stuff 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 //#region AMD loader for content scripts if (isContentScript && typeof define !== 'function') { /** * WARNING! * All deps needed to run the current define() must be already resolved. */ const modules = {}; const addJs = name => name.endsWith('.js') ? name : name + '.js'; const require = self.require = name => (name = addJs(name)) in modules ? modules[name] : {}; const define = self.define = fn => { const name = addJs(define.currentModule || `${fn}`.slice(0, 1000)); if (!(name in modules)) modules[name] = fn(require); define.currentModule = null; }; } //#endregion //#region AMD loader for our extension pages if (!isContentScript && typeof define !== 'function') { /** * Notes: * 'js!path' and 'path.css' resolve on load automatically. * 'resolved!path' will skip this path during dependency auto-detection so use only if you need to use a synchronous require() call in the future when this path is guaranteed to be resolved elsewhere. * js/css of the critical rendering path should still be specified in html. */ const REQ_SYM = Symbol(Math.random()); const modules = {}; const depsQueue = {}; const loadElement = ({mode, url}) => { const isCss = url.endsWith('.css'); const tag = isCss ? 'link' : 'script'; const el = document.createElement(tag); if (mode !== 'js' && !isCss) { el.src = url; return el; } const args = [url, [], () => el]; const attr = isCss ? 'href' : 'src'; const fileName = url.split('/').pop(); for (const el of document.head.querySelectorAll(`${tag}[${attr}$="${fileName}"]`)) { if (location.origin + url === (el.href || el.src || '')) { self.define(...args); return; } } el.addEventListener('load', self.define.bind(null, ...args), {once: true}); el[attr] = url; if (isCss) el.rel = 'stylesheet'; return el; }; const isEmpty = obj => { for (const k in obj) return false; return true; }; const parseDepName = (name, base) => { let mode; let url = name; if (name) { mode = name.startsWith('js!') ? 'js' : ''; url = mode ? name.slice(mode.length + 1) : name; if (!url.startsWith('/')) { url = new URL(url, location.origin + base).pathname; } if (!url.endsWith('.js') && !url.endsWith('.css')) { url += '.js'; } } return {mode, url}; }; const require = self.require = function (name) { return Array.isArray(name) ? self.define.apply([REQ_SYM, this], arguments) : modules[parseDepName(name, this).url] || {}; }; const define = self.define = async function (...args) { const isReq = this && this[0] === REQ_SYM; let base = isReq && this[1]; let i = 0; let name = typeof args[i] === 'string' && args[i++]; let deps = Array.isArray(args[i]) && args[i++]; const init = args[i]; const hasInit = i < args.length; if (isReq) { name = ''; define.currentModule = base; } else { define.currentModule = base = name = typeof name === 'string' && name || ((document.currentScript || {}).src || '').replace(location.origin, '') || parseDepName(define.currentModule, '').url || ''; } if (name in modules) { return modules[name]; } const isInitFn = typeof init === 'function'; if (!deps) { deps = isInitFn ? ['require', 'exports', 'module'] : []; let code = isInitFn ? `${init}` : ''; if (code.includes('require')) { code = code.replace(/\/\*([^*]|\*(?!\/))*(\*\/|$)|\/\/[^'"`\r\n]*?require\s*\(.*/g, ''); const rx = /[^.\w]require\s*\(\s*(['"])(?!resolved!)([^'"]+)\1\s*\)/g; for (let m; (m = rx.exec(code));) { deps.push(m[2]); } } } const exports = {}; const internals = isInitFn && { exports, module: {exports}, require: require.bind(base), }; const needs = {}; const toLoad = []; const ctx = {deps, needs, name, urls: []}; deps.forEach((depName, i) => { let int, mode, url; if ((int = internals[depName]) || ({mode, url} = parseDepName(depName, base)).url in modules) { deps[i] = int || modules[url]; } else if (needs[url] == null) { needs[url] = i; ctx.urls.push(url); const queue = depsQueue[url]; if (!queue) toLoad.push({mode, url}); (queue || (depsQueue[url] = [])).push(ctx); } }); let resolvedElsewhere; if (!isEmpty(needs)) { if (toLoad.length) document.head.append(...toLoad.map(loadElement).filter(Boolean)); if (name && !depsQueue[name]) depsQueue[name] = [ctx]; if (!isEmpty(needs)) await new Promise(resolve => (ctx.resolve = resolve)); resolvedElsewhere = name in modules; } const result = resolvedElsewhere ? modules[name] : isInitFn ? init(...deps) : hasInit ? init : deps[0]; if (name && !resolvedElsewhere) { modules[name] = result; const toClear = [name]; while (toClear.length) { const tc = toClear.pop(); const contexts = depsQueue[tc] || []; for (let i = contexts.length, qCtx; i && (qCtx = contexts[--i]);) { const {needs} = qCtx; qCtx.deps[needs[tc]] = result; delete needs[tc]; delete needs[qCtx.name]; if (isEmpty(needs)) { toClear.push(...qCtx.urls); contexts.splice(i, 1); if (qCtx.resolve) qCtx.resolve(); } } if (!contexts.length) delete depsQueue[tc]; } } define.currentModule = null; return result; }; define.amd = {modules, depsQueue}; } //#endregion //#region Stuff for our extension pages if (!isContentScript) { 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 define.currentModule = '/js/polyfill'; define(() => ({ isEmptyObj(obj) { if (obj) { for (const k in obj) { if (Object.prototype.hasOwnProperty.call(obj, k)) { return false; } } } return true; }, })); }