2019-03-14 16:51:20 +01:00
|
|
|
/* 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";
|
|
|
|
|
2019-11-28 01:26:35 +01:00
|
|
|
let scope;
|
2019-03-14 16:51:20 +01:00
|
|
|
if ((typeof exports) !== "undefined"){
|
|
|
|
scope = exports;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
scope = require.register("./extension", {});
|
|
|
|
}
|
|
|
|
|
2019-04-19 14:09:56 +02:00
|
|
|
const browserAvailable = typeof browser !== "undefined";
|
2020-01-19 01:27:26 +01:00
|
|
|
const logging = require("./logging");
|
2019-04-19 14:09:56 +02:00
|
|
|
|
|
|
|
scope.inBackgroundScript = !!(
|
|
|
|
browserAvailable &&
|
|
|
|
browser.extension.getBackgroundPage &&
|
|
|
|
browser.extension.getBackgroundPage() === window
|
|
|
|
);
|
|
|
|
|
2019-04-30 23:39:40 +02:00
|
|
|
scope.getTranslation = browserAvailable? function getTranslation(id){
|
2019-03-14 16:51:20 +01:00
|
|
|
return browser.i18n.getMessage(id);
|
2019-04-30 23:39:40 +02:00
|
|
|
}: function(id){
|
|
|
|
return id;
|
2019-03-14 16:51:20 +01:00
|
|
|
};
|
|
|
|
|
2020-01-25 00:42:16 +01:00
|
|
|
scope.parseTranslation = function parseTranslation(message, parameters = {}){
|
2020-01-23 15:23:56 +01:00
|
|
|
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;
|
|
|
|
}
|
2020-01-25 00:42:16 +01:00
|
|
|
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));
|
|
|
|
}
|
2020-01-23 15:23:56 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
container.appendChild(document.createTextNode(part));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return container;
|
|
|
|
};
|
|
|
|
|
2021-11-01 13:18:38 +01:00
|
|
|
scope.getURL = function getURL(str){
|
2023-05-30 19:04:02 +02:00
|
|
|
return browser.runtime.getURL(str);
|
2021-11-01 13:18:38 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
scope.extensionID = browserAvailable? scope.getURL(""): "extensionID";
|
2019-03-14 16:51:20 +01:00
|
|
|
|
2019-04-30 23:39:40 +02:00
|
|
|
scope.inIncognitoContext = browserAvailable? browser.extension.inIncognitoContext: false;
|
2019-03-14 16:51:20 +01:00
|
|
|
|
|
|
|
scope.message = {
|
2019-04-30 23:39:40 +02:00
|
|
|
on: browserAvailable? function(callback){
|
2019-03-14 16:51:20 +01:00
|
|
|
return browser.runtime.onMessage.addListener(callback);
|
2019-04-30 23:39:40 +02:00
|
|
|
}: function(){
|
|
|
|
return false;
|
2019-03-14 16:51:20 +01:00
|
|
|
},
|
2019-04-30 23:39:40 +02:00
|
|
|
send: browserAvailable? function(data){
|
2019-03-14 16:51:20 +01:00
|
|
|
return browser.runtime.sendMessage(data);
|
2019-04-30 23:39:40 +02:00
|
|
|
}: function(){
|
|
|
|
return false;
|
2019-03-14 16:51:20 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
Object.seal(scope.message);
|
|
|
|
|
2019-12-12 00:09:53 +01:00
|
|
|
scope.getWrapped = function getWrapped(obj){
|
|
|
|
return obj && (obj.wrappedJSObject || obj);
|
|
|
|
};
|
|
|
|
|
|
|
|
scope.exportFunctionWithName = function exportFunctionWithName(func, context, name){
|
2020-11-02 13:11:50 +01:00
|
|
|
const targetObject = scope.getWrapped(context).Object.create(null);
|
|
|
|
const exportedTry = exportFunction(func, targetObject, {allowCrossOriginArguments: true, defineAs: name});
|
2019-12-12 00:09:53 +01:00
|
|
|
if (exportedTry.name === name){
|
|
|
|
return exportedTry;
|
|
|
|
}
|
|
|
|
else {
|
2020-01-22 13:38:24 +01:00
|
|
|
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());
|
|
|
|
}
|
2019-12-12 00:09:53 +01:00
|
|
|
const wrappedContext = scope.getWrapped(context);
|
|
|
|
const options = {
|
2019-12-29 23:40:39 +01:00
|
|
|
allowCrossOriginArguments: true,
|
2019-12-12 00:09:53 +01:00
|
|
|
defineAs: name
|
|
|
|
};
|
|
|
|
const oldDescriptor = Object.getOwnPropertyDescriptor(wrappedContext, name);
|
2020-01-19 01:27:26 +01:00
|
|
|
if (oldDescriptor && !oldDescriptor.configurable){
|
|
|
|
logging.error(
|
|
|
|
"Unable to export function with the correct name", name,
|
|
|
|
"instead we have to use", exportedTry.name
|
|
|
|
);
|
|
|
|
return exportedTry;
|
|
|
|
}
|
2019-12-12 00:09:53 +01:00
|
|
|
const exported = exportFunction(func, context, options);
|
|
|
|
if (oldDescriptor){
|
|
|
|
Object.defineProperty(wrappedContext, name, oldDescriptor);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
delete wrappedContext[name];
|
|
|
|
}
|
|
|
|
return exported;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-02-21 11:00:33 +01:00
|
|
|
const proxies = new Map();
|
2024-04-07 23:59:49 +02:00
|
|
|
const changedWindowsForProxies = new WeakMap();
|
|
|
|
function setupWindowForProxies(window){
|
|
|
|
if (changedWindowsForProxies.get(window)){
|
|
|
|
return;
|
2021-02-21 11:00:33 +01:00
|
|
|
}
|
2024-04-07 23:59:49 +02:00
|
|
|
const wrappedWindow = scope.getWrapped(window);
|
|
|
|
|
|
|
|
const functionPrototype = wrappedWindow.Function.prototype;
|
|
|
|
const originalToString = functionPrototype.toString;
|
|
|
|
changedWindowsForProxies.set(window, originalToString);
|
|
|
|
const alteredToString = scope.createProxyFunction(
|
|
|
|
window,
|
|
|
|
originalToString,
|
|
|
|
function toString(){
|
|
|
|
if (proxies.has(this)){
|
|
|
|
return proxies.get(this).string;
|
|
|
|
}
|
|
|
|
return originalToString.call(scope.getWrapped(this));
|
|
|
|
}
|
|
|
|
);
|
|
|
|
scope.changeProperty(window, "toString", {
|
|
|
|
object: functionPrototype,
|
|
|
|
name: "toString",
|
|
|
|
type: "value",
|
|
|
|
changed: alteredToString
|
|
|
|
});
|
2024-04-08 00:05:50 +02:00
|
|
|
|
|
|
|
const wrappedReflect = wrappedWindow.Reflect;
|
|
|
|
const originalReflectSetPrototypeOf = wrappedReflect.setPrototypeOf;
|
|
|
|
const alteredReflectSetPrototypeOf = scope.exportFunctionWithName(
|
|
|
|
function setPrototypeOf(target, prototype){
|
|
|
|
target = scope.getWrapped(target);
|
|
|
|
if (proxies.has(target)){
|
|
|
|
target = proxies.get(target).wrappedOriginal;
|
|
|
|
}
|
|
|
|
if (proxies.has(prototype)){
|
|
|
|
prototype = proxies.get(prototype).wrappedOriginal;
|
|
|
|
}
|
2024-04-17 00:07:16 +02:00
|
|
|
if (prototype){
|
|
|
|
const grandPrototype = wrappedReflect.getPrototypeOf(prototype);
|
|
|
|
if (proxies.has(grandPrototype)){
|
|
|
|
const testPrototype = wrappedWindow.Object.create(proxies.get(grandPrototype).wrappedOriginal);
|
|
|
|
const value = originalReflectSetPrototypeOf.call(wrappedReflect, target, testPrototype);
|
|
|
|
if (!value){
|
|
|
|
return false;
|
|
|
|
}
|
2024-04-08 00:05:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
const value = originalReflectSetPrototypeOf.call(wrappedReflect, target, scope.getWrapped(prototype));
|
|
|
|
return value;
|
|
|
|
}, window, "setPrototypeOf"
|
|
|
|
);
|
|
|
|
scope.changeProperty(window, "toString", {
|
|
|
|
object: wrappedWindow.Reflect,
|
|
|
|
name: "setPrototypeOf",
|
|
|
|
type: "value",
|
|
|
|
changed: alteredReflectSetPrototypeOf
|
|
|
|
});
|
2024-04-07 23:59:49 +02:00
|
|
|
}
|
|
|
|
scope.createProxyFunction = function createProxyFunction(window, original, replacement){
|
|
|
|
setupWindowForProxies(window);
|
2024-04-08 00:05:50 +02:00
|
|
|
const wrappedObject = scope.getWrapped(window).Object;
|
|
|
|
const handler = wrappedObject.create(null);
|
|
|
|
handler.apply = scope.exportFunctionWithName(function(target, thisArg, args){
|
2021-02-21 11:00:33 +01:00
|
|
|
try {
|
|
|
|
return args.length?
|
2024-04-08 00:05:50 +02:00
|
|
|
replacement.call(thisArg, ...args):
|
|
|
|
replacement.call(thisArg);
|
2021-02-21 11:00:33 +01:00
|
|
|
}
|
|
|
|
catch (error){
|
2021-06-21 20:41:33 +02:00
|
|
|
try {
|
2024-04-08 00:05:50 +02:00
|
|
|
return original.apply(thisArg, args);
|
2021-06-21 20:41:33 +02:00
|
|
|
}
|
|
|
|
catch (error){
|
2024-04-17 00:34:19 +02:00
|
|
|
return scope.getWrapped(target).apply(thisArg, args);
|
2021-06-21 20:41:33 +02:00
|
|
|
}
|
2021-02-21 11:00:33 +01:00
|
|
|
}
|
2021-01-26 13:47:59 +01:00
|
|
|
}, window, "");
|
2024-04-08 00:05:50 +02:00
|
|
|
handler.setPrototypeOf = scope.exportFunctionWithName(function(target, prototype){
|
|
|
|
target = scope.getWrapped(target);
|
|
|
|
if (proxies.has(target)){
|
|
|
|
target = proxies.get(target).wrappedOriginal;
|
|
|
|
}
|
|
|
|
if (proxies.has(prototype)){
|
|
|
|
prototype = proxies.get(prototype).wrappedOriginal;
|
|
|
|
}
|
2024-04-17 00:07:16 +02:00
|
|
|
if (prototype){
|
|
|
|
const grandPrototype = wrappedObject.getPrototypeOf(prototype);
|
|
|
|
if (proxies.has(grandPrototype)){
|
|
|
|
const testPrototype = wrappedObject.create(proxies.get(grandPrototype).wrappedOriginal);
|
|
|
|
wrappedObject.setPrototypeOf(target, testPrototype);
|
|
|
|
}
|
2024-04-08 00:05:50 +02:00
|
|
|
}
|
|
|
|
return wrappedObject.setPrototypeOf(target, scope.getWrapped(prototype));
|
|
|
|
}, window, "");
|
2021-02-21 11:00:33 +01:00
|
|
|
const proxy = new window.Proxy(original, handler);
|
2024-04-07 23:59:49 +02:00
|
|
|
const proxyData = {
|
|
|
|
original: original,
|
|
|
|
wrappedOriginal: scope.getWrapped(original),
|
|
|
|
string: changedWindowsForProxies.get(window).call(original),
|
|
|
|
};
|
|
|
|
proxies.set(proxy, proxyData);
|
|
|
|
proxies.set(scope.getWrapped(proxy), proxyData);
|
2021-02-21 11:00:33 +01:00
|
|
|
return scope.getWrapped(proxy);
|
2021-01-26 13:47:59 +01:00
|
|
|
};
|
|
|
|
|
2020-01-23 13:56:14 +01:00
|
|
|
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){
|
2020-01-25 10:04:54 +01:00
|
|
|
const remainingProperties = changedProperties.filter(function(changedProperty){
|
|
|
|
return changedProperty.group !== group;
|
2020-01-23 13:56:14 +01:00
|
|
|
});
|
|
|
|
changedPropertiesByWindow.set(window, remainingProperties);
|
2020-01-25 10:04:54 +01:00
|
|
|
changedProperties = changedProperties.filter(function(changedProperty){
|
|
|
|
return changedProperty.group === group;
|
2020-01-23 13:56:14 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-01-26 01:11:18 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2024-03-28 14:43:53 +01:00
|
|
|
scope.displayVersion = async function displayVersion(node, displayRefresh = false){
|
|
|
|
if ("string" === typeof node){
|
|
|
|
node = document.getElementById(node);
|
|
|
|
}
|
|
|
|
if (!node){
|
|
|
|
throw "display node not found";
|
|
|
|
}
|
|
|
|
fetch(scope.getURL("manifest.json")).then(function(response){
|
|
|
|
return response.json();
|
|
|
|
}).then(function(manifest){
|
|
|
|
node.textContent = "Version " + manifest.version;
|
|
|
|
return manifest.version;
|
|
|
|
}).catch(function(error){
|
|
|
|
node.textContent = "Unable to get version: " + error;
|
|
|
|
});
|
|
|
|
|
|
|
|
if (displayRefresh){
|
|
|
|
// Workaround to hide the scroll bars
|
|
|
|
window.setTimeout(function(){
|
|
|
|
node.style.display = "none";
|
|
|
|
node.style.display = "";
|
|
|
|
}, displayRefresh);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-03-14 16:51:20 +01:00
|
|
|
Object.seal(scope);
|
|
|
|
}());
|