CanvasBlocker/lib/extension.js

235 lines
6.9 KiB
JavaScript

/* 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);
}());