/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ (function(){ "use strict"; let scope; if ((typeof exports) !== "undefined"){ scope = exports; } else { scope = require.register("./extension", {}); } const browserAvailable = typeof browser !== "undefined"; const logging = require("./logging"); scope.inBackgroundScript = !!( browserAvailable && browser.extension.getBackgroundPage && browser.extension.getBackgroundPage() === window ); scope.getTranslation = browserAvailable? function getTranslation(id){ return browser.i18n.getMessage(id); }: function(id){ return id; }; scope.parseTranslation = function parseTranslation(message, parameters = {}){ const container = document.createDocumentFragment(); message.split(/(\{[^}]+\})/).forEach(function(part){ if (part.startsWith("{") && part.endsWith("}")){ part = part.substring(1, part.length - 1); const args = part.split(":"); switch (args[0]){ case "image": { const image = document.createElement("img"); image.className = "noticeImage"; image.src = args.slice(1).join(":"); container.appendChild(image); break; } case "link": { const link = document.createElement("a"); link.target = "_blank"; link.textContent = args[1]; link.href = args.slice(2).join(":"); container.appendChild(link); break; } default: if (parameters[args[0]]){ const parameter = parameters[args[0]]; if ((typeof parameter) === "function"){ container.appendChild(parameter(args.slice(1).join(":"))); } else { container.appendChild(document.createTextNode(parameter)); } } else { container.appendChild(document.createTextNode(part)); } } } else { container.appendChild(document.createTextNode(part)); } }); return container; }; scope.extensionID = browserAvailable? browser.extension.getURL(""): "extensionID"; scope.inIncognitoContext = browserAvailable? browser.extension.inIncognitoContext: false; scope.message = { on: browserAvailable? function(callback){ return browser.runtime.onMessage.addListener(callback); }: function(){ return false; }, send: browserAvailable? function(data){ return browser.runtime.sendMessage(data); }: function(){ return false; } }; Object.seal(scope.message); scope.getWrapped = function getWrapped(obj){ return obj && (obj.wrappedJSObject || obj); }; scope.exportFunctionWithName = function exportFunctionWithName(func, context, name){ const targetObject = scope.getWrapped(context).Object.create(null); const exportedTry = exportFunction(func, targetObject, {allowCrossOriginArguments: true, defineAs: name}); if (exportedTry.name === name){ return exportedTry; } else { if (func.name === name){ logging.message( "FireFox bug: Need to change name in exportFunction from", exportedTry.name, "(originally correct) to", name ); } else { logging.error("Wrong name specified for", name, new Error()); } const wrappedContext = scope.getWrapped(context); const options = { allowCrossOriginArguments: true, defineAs: name }; const oldDescriptor = Object.getOwnPropertyDescriptor(wrappedContext, name); if (oldDescriptor && !oldDescriptor.configurable){ logging.error( "Unable to export function with the correct name", name, "instead we have to use", exportedTry.name ); return exportedTry; } const exported = exportFunction(func, context, options); if (oldDescriptor){ Object.defineProperty(wrappedContext, name, oldDescriptor); } else { delete wrappedContext[name]; } return exported; } }; const proxies = new Map(); const changedToStrings = new WeakMap(); scope.createProxyFunction = function createProxyFunction(window, original, replacement){ if (!changedToStrings.get(window)){ changedToStrings.set(window, true); const functionPrototype = scope.getWrapped(window).Function.prototype; const toString = functionPrototype.toString; scope.changeProperty(window, "toString", { object: functionPrototype, name: "toString", type: "value", changed: scope.createProxyFunction( window, toString, function(){ return proxies.get(this) || toString.call(this); } ) }); } const handler = scope.getWrapped(window).Object.create(null); handler.apply = scope.exportFunctionWithName(function(target, thisArgs, args){ try { return args.length? replacement.call(thisArgs, ...args): replacement.call(thisArgs); } catch (error){ try { return original.apply(thisArgs, args); } catch (error){ return target.apply(thisArgs, args); } } }, window, ""); const proxy = new window.Proxy(original, handler); proxies.set(proxy, original.toString()); return scope.getWrapped(proxy); }; const changedPropertiesByWindow = new WeakMap(); scope.changeProperty = function(window, group, {object, name, type, changed}){ let changedProperties = changedPropertiesByWindow.get(scope.getWrapped(window)); if (!changedProperties){ changedProperties = []; changedPropertiesByWindow.set(scope.getWrapped(window), changedProperties); } const descriptor = Object.getOwnPropertyDescriptor(object, name); const original = descriptor[type]; descriptor[type] = changed; Object.defineProperty(object, name, descriptor); changedProperties.push({group, object, name, type, original}); }; scope.revertProperties = function(window, group){ window = scope.getWrapped(window); let changedProperties = changedPropertiesByWindow.get(window); if (!changedProperties){ return; } if (group){ const remainingProperties = changedProperties.filter(function(changedProperty){ return changedProperty.group !== group; }); changedPropertiesByWindow.set(window, remainingProperties); changedProperties = changedProperties.filter(function(changedProperty){ return changedProperty.group === group; }); } else { changedPropertiesByWindow.delete(window); } for (let i = changedProperties.length - 1; i >= 0; i -= 1){ const {object, name, type, original} = changedProperties[i]; logging.verbose("reverting", name, "on", object); const descriptor = Object.getOwnPropertyDescriptor(object, name); descriptor[type] = original; Object.defineProperty(object, name, descriptor); } }; scope.waitSync = function waitSync(reason = "for no reason"){ logging.message(`Starting synchronous request ${reason}.`); try { let xhr = new XMLHttpRequest(); xhr.open("GET", "https://[::]", false); xhr.send(); xhr = null; } catch (error){ logging.verbose("Error in XHR:", error); } }; Object.seal(scope); }());