summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/lib/UA.js8
-rw-r--r--src/lib/browser-polyfill.js1186
-rw-r--r--src/manifest.json8
-rw-r--r--src/ui/options.html2
-rw-r--r--src/ui/popup.html2
-rw-r--r--src/ui/prompt.html2
-rw-r--r--src/ui/siteInfo.html2
7 files changed, 1209 insertions, 1 deletions
diff --git a/src/lib/UA.js b/src/lib/UA.js
new file mode 100644
index 0000000..0a751a4
--- /dev/null
+++ b/src/lib/UA.js
@@ -0,0 +1,8 @@
+var UA = {
+ isMozilla: document.URL.startsWith("moz-"),
+}
+
+if (!UA.isMozilla && typeof chrome === "object" && !chrome.tabs && typeof exportFunction === "undefined") {
+ // content script shims
+ window.exportFunction = () => {};
+}
diff --git a/src/lib/browser-polyfill.js b/src/lib/browser-polyfill.js
new file mode 100644
index 0000000..b3dd82d
--- /dev/null
+++ b/src/lib/browser-polyfill.js
@@ -0,0 +1,1186 @@
+(function (global, factory) {
+ if (typeof define === "function" && define.amd) {
+ define("webextension-polyfill", ["module"], factory);
+ } else if (typeof exports !== "undefined") {
+ factory(module);
+ } else {
+ var mod = {
+ exports: {}
+ };
+ factory(mod);
+ global.browser = mod.exports;
+ }
+})(this, function (module) {
+ /* webextension-polyfill - v0.3.1 - Tue Aug 21 2018 10:09:34 */
+ /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set sts=2 sw=2 et tw=80: */
+ /* 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/. */
+ "use strict";
+
+ if (typeof browser === "undefined" || Object.getPrototypeOf(browser) !== Object.prototype) {
+ const CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE = "The message port closed before a response was received.";
+ const SEND_RESPONSE_DEPRECATION_WARNING = "Returning a Promise is the preferred way to send a reply from an onMessage/onMessageExternal listener, as the sendResponse will be removed from the specs (See https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onMessage)";
+
+ // Wrapping the bulk of this polyfill in a one-time-use function is a minor
+ // optimization for Firefox. Since Spidermonkey does not fully parse the
+ // contents of a function until the first time it's called, and since it will
+ // never actually need to be called, this allows the polyfill to be included
+ // in Firefox nearly for free.
+ const wrapAPIs = () => {
+ // NOTE: apiMetadata is associated to the content of the api-metadata.json file
+ // at build time by replacing the following "include" with the content of the
+ // JSON file.
+ const apiMetadata = {
+ "alarms": {
+ "clear": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "clearAll": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "get": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "getAll": {
+ "minArgs": 0,
+ "maxArgs": 0
+ }
+ },
+ "bookmarks": {
+ "create": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "get": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getChildren": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getRecent": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getSubTree": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getTree": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "move": {
+ "minArgs": 2,
+ "maxArgs": 2
+ },
+ "remove": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "removeTree": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "search": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "update": {
+ "minArgs": 2,
+ "maxArgs": 2
+ }
+ },
+ "browserAction": {
+ "disable": {
+ "minArgs": 0,
+ "maxArgs": 1,
+ "fallbackToNoCallback": true
+ },
+ "enable": {
+ "minArgs": 0,
+ "maxArgs": 1,
+ "fallbackToNoCallback": true
+ },
+ "getBadgeBackgroundColor": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getBadgeText": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getPopup": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getTitle": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "openPopup": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "setBadgeBackgroundColor": {
+ "minArgs": 1,
+ "maxArgs": 1,
+ "fallbackToNoCallback": true
+ },
+ "setBadgeText": {
+ "minArgs": 1,
+ "maxArgs": 1,
+ "fallbackToNoCallback": true
+ },
+ "setIcon": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "setPopup": {
+ "minArgs": 1,
+ "maxArgs": 1,
+ "fallbackToNoCallback": true
+ },
+ "setTitle": {
+ "minArgs": 1,
+ "maxArgs": 1,
+ "fallbackToNoCallback": true
+ }
+ },
+ "browsingData": {
+ "remove": {
+ "minArgs": 2,
+ "maxArgs": 2
+ },
+ "removeCache": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "removeCookies": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "removeDownloads": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "removeFormData": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "removeHistory": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "removeLocalStorage": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "removePasswords": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "removePluginData": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "settings": {
+ "minArgs": 0,
+ "maxArgs": 0
+ }
+ },
+ "commands": {
+ "getAll": {
+ "minArgs": 0,
+ "maxArgs": 0
+ }
+ },
+ "contextMenus": {
+ "remove": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "removeAll": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "update": {
+ "minArgs": 2,
+ "maxArgs": 2
+ }
+ },
+ "cookies": {
+ "get": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getAll": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getAllCookieStores": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "remove": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "set": {
+ "minArgs": 1,
+ "maxArgs": 1
+ }
+ },
+ "devtools": {
+ "inspectedWindow": {
+ "eval": {
+ "minArgs": 1,
+ "maxArgs": 2
+ }
+ },
+ "panels": {
+ "create": {
+ "minArgs": 3,
+ "maxArgs": 3,
+ "singleCallbackArg": true
+ }
+ }
+ },
+ "downloads": {
+ "cancel": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "download": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "erase": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getFileIcon": {
+ "minArgs": 1,
+ "maxArgs": 2
+ },
+ "open": {
+ "minArgs": 1,
+ "maxArgs": 1,
+ "fallbackToNoCallback": true
+ },
+ "pause": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "removeFile": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "resume": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "search": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "show": {
+ "minArgs": 1,
+ "maxArgs": 1,
+ "fallbackToNoCallback": true
+ }
+ },
+ "extension": {
+ "isAllowedFileSchemeAccess": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "isAllowedIncognitoAccess": {
+ "minArgs": 0,
+ "maxArgs": 0
+ }
+ },
+ "history": {
+ "addUrl": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "deleteAll": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "deleteRange": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "deleteUrl": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getVisits": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "search": {
+ "minArgs": 1,
+ "maxArgs": 1
+ }
+ },
+ "i18n": {
+ "detectLanguage": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getAcceptLanguages": {
+ "minArgs": 0,
+ "maxArgs": 0
+ }
+ },
+ "identity": {
+ "launchWebAuthFlow": {
+ "minArgs": 1,
+ "maxArgs": 1
+ }
+ },
+ "idle": {
+ "queryState": {
+ "minArgs": 1,
+ "maxArgs": 1
+ }
+ },
+ "management": {
+ "get": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getAll": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "getSelf": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "setEnabled": {
+ "minArgs": 2,
+ "maxArgs": 2
+ },
+ "uninstallSelf": {
+ "minArgs": 0,
+ "maxArgs": 1
+ }
+ },
+ "notifications": {
+ "clear": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "create": {
+ "minArgs": 1,
+ "maxArgs": 2
+ },
+ "getAll": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "getPermissionLevel": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "update": {
+ "minArgs": 2,
+ "maxArgs": 2
+ }
+ },
+ "pageAction": {
+ "getPopup": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getTitle": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "hide": {
+ "minArgs": 1,
+ "maxArgs": 1,
+ "fallbackToNoCallback": true
+ },
+ "setIcon": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "setPopup": {
+ "minArgs": 1,
+ "maxArgs": 1,
+ "fallbackToNoCallback": true
+ },
+ "setTitle": {
+ "minArgs": 1,
+ "maxArgs": 1,
+ "fallbackToNoCallback": true
+ },
+ "show": {
+ "minArgs": 1,
+ "maxArgs": 1,
+ "fallbackToNoCallback": true
+ }
+ },
+ "permissions": {
+ "contains": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getAll": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "remove": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "request": {
+ "minArgs": 1,
+ "maxArgs": 1
+ }
+ },
+ "runtime": {
+ "getBackgroundPage": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "getBrowserInfo": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "getPlatformInfo": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "openOptionsPage": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "requestUpdateCheck": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "sendMessage": {
+ "minArgs": 1,
+ "maxArgs": 3
+ },
+ "sendNativeMessage": {
+ "minArgs": 2,
+ "maxArgs": 2
+ },
+ "setUninstallURL": {
+ "minArgs": 1,
+ "maxArgs": 1
+ }
+ },
+ "sessions": {
+ "getDevices": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "getRecentlyClosed": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "restore": {
+ "minArgs": 0,
+ "maxArgs": 1
+ }
+ },
+ "storage": {
+ "local": {
+ "clear": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "get": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "getBytesInUse": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "remove": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "set": {
+ "minArgs": 1,
+ "maxArgs": 1
+ }
+ },
+ "managed": {
+ "get": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "getBytesInUse": {
+ "minArgs": 0,
+ "maxArgs": 1
+ }
+ },
+ "sync": {
+ "clear": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "get": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "getBytesInUse": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "remove": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "set": {
+ "minArgs": 1,
+ "maxArgs": 1
+ }
+ }
+ },
+ "tabs": {
+ "captureVisibleTab": {
+ "minArgs": 0,
+ "maxArgs": 2
+ },
+ "create": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "detectLanguage": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "discard": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "duplicate": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "executeScript": {
+ "minArgs": 1,
+ "maxArgs": 2
+ },
+ "get": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getCurrent": {
+ "minArgs": 0,
+ "maxArgs": 0
+ },
+ "getZoom": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "getZoomSettings": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "highlight": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "insertCSS": {
+ "minArgs": 1,
+ "maxArgs": 2
+ },
+ "move": {
+ "minArgs": 2,
+ "maxArgs": 2
+ },
+ "query": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "reload": {
+ "minArgs": 0,
+ "maxArgs": 2
+ },
+ "remove": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "removeCSS": {
+ "minArgs": 1,
+ "maxArgs": 2
+ },
+ "sendMessage": {
+ "minArgs": 2,
+ "maxArgs": 3
+ },
+ "setZoom": {
+ "minArgs": 1,
+ "maxArgs": 2
+ },
+ "setZoomSettings": {
+ "minArgs": 1,
+ "maxArgs": 2
+ },
+ "update": {
+ "minArgs": 1,
+ "maxArgs": 2
+ }
+ },
+ "topSites": {
+ "get": {
+ "minArgs": 0,
+ "maxArgs": 0
+ }
+ },
+ "webNavigation": {
+ "getAllFrames": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "getFrame": {
+ "minArgs": 1,
+ "maxArgs": 1
+ }
+ },
+ "webRequest": {
+ "handlerBehaviorChanged": {
+ "minArgs": 0,
+ "maxArgs": 0
+ }
+ },
+ "windows": {
+ "create": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "get": {
+ "minArgs": 1,
+ "maxArgs": 2
+ },
+ "getAll": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "getCurrent": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "getLastFocused": {
+ "minArgs": 0,
+ "maxArgs": 1
+ },
+ "remove": {
+ "minArgs": 1,
+ "maxArgs": 1
+ },
+ "update": {
+ "minArgs": 2,
+ "maxArgs": 2
+ }
+ }
+ };
+
+ if (Object.keys(apiMetadata).length === 0) {
+ throw new Error("api-metadata.json has not been included in browser-polyfill");
+ }
+
+ /**
+ * A WeakMap subclass which creates and stores a value for any key which does
+ * not exist when accessed, but behaves exactly as an ordinary WeakMap
+ * otherwise.
+ *
+ * @param {function} createItem
+ * A function which will be called in order to create the value for any
+ * key which does not exist, the first time it is accessed. The
+ * function receives, as its only argument, the key being created.
+ */
+ class DefaultWeakMap extends WeakMap {
+ constructor(createItem, items = undefined) {
+ super(items);
+ this.createItem = createItem;
+ }
+
+ get(key) {
+ if (!this.has(key)) {
+ this.set(key, this.createItem(key));
+ }
+
+ return super.get(key);
+ }
+ }
+
+ /**
+ * Returns true if the given object is an object with a `then` method, and can
+ * therefore be assumed to behave as a Promise.
+ *
+ * @param {*} value The value to test.
+ * @returns {boolean} True if the value is thenable.
+ */
+ const isThenable = value => {
+ return value && typeof value === "object" && typeof value.then === "function";
+ };
+
+ /**
+ * Creates and returns a function which, when called, will resolve or reject
+ * the given promise based on how it is called:
+ *
+ * - If, when called, `chrome.runtime.lastError` contains a non-null object,
+ * the promise is rejected with that value.
+ * - If the function is called with exactly one argument, the promise is
+ * resolved to that value.
+ * - Otherwise, the promise is resolved to an array containing all of the
+ * function's arguments.
+ *
+ * @param {object} promise
+ * An object containing the resolution and rejection functions of a
+ * promise.
+ * @param {function} promise.resolve
+ * The promise's resolution function.
+ * @param {function} promise.rejection
+ * The promise's rejection function.
+ * @param {object} metadata
+ * Metadata about the wrapped method which has created the callback.
+ * @param {integer} metadata.maxResolvedArgs
+ * The maximum number of arguments which may be passed to the
+ * callback created by the wrapped async function.
+ *
+ * @returns {function}
+ * The generated callback function.
+ */
+ const makeCallback = (promise, metadata) => {
+ return (...callbackArgs) => {
+ if (chrome.runtime.lastError) {
+ promise.reject(chrome.runtime.lastError);
+ } else if (metadata.singleCallbackArg || callbackArgs.length <= 1) {
+ promise.resolve(callbackArgs[0]);
+ } else {
+ promise.resolve(callbackArgs);
+ }
+ };
+ };
+
+ const pluralizeArguments = numArgs => numArgs == 1 ? "argument" : "arguments";
+
+ /**
+ * Creates a wrapper function for a method with the given name and metadata.
+ *
+ * @param {string} name
+ * The name of the method which is being wrapped.
+ * @param {object} metadata
+ * Metadata about the method being wrapped.
+ * @param {integer} metadata.minArgs
+ * The minimum number of arguments which must be passed to the
+ * function. If called with fewer than this number of arguments, the
+ * wrapper will raise an exception.
+ * @param {integer} metadata.maxArgs
+ * The maximum number of arguments which may be passed to the
+ * function. If called with more than this number of arguments, the
+ * wrapper will raise an exception.
+ * @param {integer} metadata.maxResolvedArgs
+ * The maximum number of arguments which may be passed to the
+ * callback created by the wrapped async function.
+ *
+ * @returns {function(object, ...*)}
+ * The generated wrapper function.
+ */
+ const wrapAsyncFunction = (name, metadata) => {
+ return function asyncFunctionWrapper(target, ...args) {
+ if (args.length < metadata.minArgs) {
+ throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`);
+ }
+
+ if (args.length > metadata.maxArgs) {
+ throw new Error(`Expected at most ${metadata.maxArgs} ${pluralizeArguments(metadata.maxArgs)} for ${name}(), got ${args.length}`);
+ }
+
+ return new Promise((resolve, reject) => {
+ if (metadata.fallbackToNoCallback) {
+ // This API method has currently no callback on Chrome, but it return a promise on Firefox,
+ // and so the polyfill will try to call it with a callback first, and it will fallback
+ // to not passing the callback if the first call fails.
+ try {
+ target[name](...args, makeCallback({ resolve, reject }, metadata));
+ } catch (cbError) {
+ console.warn(`${name} API method doesn't seem to support the callback parameter, ` + "falling back to call it without a callback: ", cbError);
+
+ target[name](...args);
+
+ // Update the API method metadata, so that the next API calls will not try to
+ // use the unsupported callback anymore.
+ metadata.fallbackToNoCallback = false;
+ metadata.noCallback = true;
+
+ resolve();
+ }
+ } else if (metadata.noCallback) {
+ target[name](...args);
+ resolve();
+ } else {
+ target[name](...args, makeCallback({ resolve, reject }, metadata));
+ }
+ });
+ };
+ };
+
+ /**
+ * Wraps an existing method of the target object, so that calls to it are
+ * intercepted by the given wrapper function. The wrapper function receives,
+ * as its first argument, the original `target` object, followed by each of
+ * the arguments passed to the original method.
+ *
+ * @param {object} target
+ * The original target object that the wrapped method belongs to.
+ * @param {function} method
+ * The method being wrapped. This is used as the target of the Proxy
+ * object which is created to wrap the method.
+ * @param {function} wrapper
+ * The wrapper function which is called in place of a direct invocation
+ * of the wrapped method.
+ *
+ * @returns {Proxy<function>}
+ * A Proxy object for the given method, which invokes the given wrapper
+ * method in its place.
+ */
+ const wrapMethod = (target, method, wrapper) => {
+ return new Proxy(method, {
+ apply(targetMethod, thisObj, args) {
+ return wrapper.call(thisObj, target, ...args);
+ }
+ });
+ };
+
+ let hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty);
+
+ /**
+ * Wraps an object in a Proxy which intercepts and wraps certain methods
+ * based on the given `wrappers` and `metadata` objects.
+ *
+ * @param {object} target
+ * The target object to wrap.
+ *
+ * @param {object} [wrappers = {}]
+ * An object tree containing wrapper functions for special cases. Any
+ * function present in this object tree is called in place of the
+ * method in the same location in the `target` object tree. These
+ * wrapper methods are invoked as described in {@see wrapMethod}.
+ *
+ * @param {object} [metadata = {}]
+ * An object tree containing metadata used to automatically generate
+ * Promise-based wrapper functions for asynchronous. Any function in
+ * the `target` object tree which has a corresponding metadata object
+ * in the same location in the `metadata` tree is replaced with an
+ * automatically-generated wrapper function, as described in
+ * {@see wrapAsyncFunction}
+ *
+ * @returns {Proxy<object>}
+ */
+ const wrapObject = (target, wrappers = {}, metadata = {}) => {
+ let cache = Object.create(null);
+ let handlers = {
+ has(proxyTarget, prop) {
+ return prop in target || prop in cache;
+ },
+
+ get(proxyTarget, prop, receiver) {
+ if (prop in cache) {
+ return cache[prop];
+ }
+
+ if (!(prop in target)) {
+ return undefined;
+ }
+
+ let value = target[prop];
+
+ if (typeof value === "function") {
+ // This is a method on the underlying object. Check if we need to do
+ // any wrapping.
+
+ if (typeof wrappers[prop] === "function") {
+ // We have a special-case wrapper for this method.
+ value = wrapMethod(target, target[prop], wrappers[prop]);
+ } else if (hasOwnProperty(metadata, prop)) {
+ // This is an async method that we have metadata for. Create a
+ // Promise wrapper for it.
+ let wrapper = wrapAsyncFunction(prop, metadata[prop]);
+ value = wrapMethod(target, target[prop], wrapper);
+ } else {
+ // This is a method that we don't know or care about. Return the
+ // original method, bound to the underlying object.
+ value = value.bind(target);
+ }
+ } else if (typeof value === "object" && value !== null && (hasOwnProperty(wrappers, prop) || hasOwnProperty(metadata, prop))) {
+ // This is an object that we need to do some wrapping for the children
+ // of. Create a sub-object wrapper for it with the appropriate child
+ // metadata.
+ value = wrapObject(value, wrappers[prop], metadata[prop]);
+ } else {
+ // We don't need to do any wrapping for this property,
+ // so just forward all access to the underlying object.
+ Object.defineProperty(cache, prop, {
+ configurable: true,
+ enumerable: true,
+ get() {
+ return target[prop];
+ },
+ set(value) {
+ target[prop] = value;
+ }
+ });
+
+ return value;
+ }
+
+ cache[prop] = value;
+ return value;
+ },
+
+ set(proxyTarget, prop, value, receiver) {
+ if (prop in cache) {
+ cache[prop] = value;
+ } else {
+ target[prop] = value;
+ }
+ return true;
+ },
+
+ defineProperty(proxyTarget, prop, desc) {
+ return Reflect.defineProperty(cache, prop, desc);
+ },
+
+ deleteProperty(proxyTarget, prop) {
+ return Reflect.deleteProperty(cache, prop);
+ }
+ };
+
+ // Per contract of the Proxy API, the "get" proxy handler must return the
+ // original value of the target if that value is declared read-only and
+ // non-configurable. For this reason, we create an object with the
+ // prototype set to `target` instead of using `target` directly.
+ // Otherwise we cannot return a custom object for APIs that
+ // are declared read-only and non-configurable, such as `chrome.devtools`.
+ //
+ // The proxy handlers themselves will still use the original `target`
+ // instead of the `proxyTarget`, so that the methods and properties are
+ // dereferenced via the original targets.
+ let proxyTarget = Object.create(target);
+ return new Proxy(proxyTarget, handlers);
+ };
+
+ /**
+ * Creates a set of wrapper functions for an event object, which handles
+ * wrapping of listener functions that those messages are passed.
+ *
+ * A single wrapper is created for each listener function, and stored in a
+ * map. Subsequent calls to `addListener`, `hasListener`, or `removeListener`
+ * retrieve the original wrapper, so that attempts to remove a
+ * previously-added listener work as expected.
+ *
+ * @param {DefaultWeakMap<function, function>} wrapperMap
+ * A DefaultWeakMap object which will create the appropriate wrapper
+ * for a given listener function when one does not exist, and retrieve
+ * an existing one when it does.
+ *
+ * @returns {object}
+ */
+ const wrapEvent = wrapperMap => ({
+ addListener(target, listener, ...args) {
+ target.addListener(wrapperMap.get(listener), ...args);
+ },
+
+ hasListener(target, listener) {
+ return target.hasListener(wrapperMap.get(listener));
+ },
+
+ removeListener(target, listener) {
+ target.removeListener(wrapperMap.get(listener));
+ }
+ });
+
+ // Keep track if the deprecation warning has been logged at least once.
+ let loggedSendResponseDeprecationWarning = false;
+
+ const onMessageWrappers = new DefaultWeakMap(listener => {
+ if (typeof listener !== "function") {
+ return listener;
+ }
+
+ /**
+ * Wraps a message listener function so that it may send responses based on
+ * its return value, rather than by returning a sentinel value and calling a
+ * callback. If the listener function returns a Promise, the response is
+ * sent when the promise either resolves or rejects.
+ *
+ * @param {*} message
+ * The message sent by the other end of the channel.
+ * @param {object} sender
+ * Details about the sender of the message.
+ * @param {function(*)} sendResponse
+ * A callback which, when called with an arbitrary argument, sends
+ * that value as a response.
+ * @returns {boolean}
+ * True if the wrapped listener returned a Promise, which will later
+ * yield a response. False otherwise.
+ */
+ return function onMessage(message, sender, sendResponse) {
+ let didCallSendResponse = false;
+
+ let wrappedSendResponse;
+ let sendResponsePromise = new Promise(resolve => {
+ wrappedSendResponse = function (response) {
+ if (!loggedSendResponseDeprecationWarning) {
+ console.warn(SEND_RESPONSE_DEPRECATION_WARNING, new Error().stack);
+ loggedSendResponseDeprecationWarning = true;
+ }
+ didCallSendResponse = true;
+ resolve(response);
+ };
+ });
+
+ let result;
+ try {
+ result = listener(message, sender, wrappedSendResponse);
+ } catch (err) {
+ result = Promise.reject(err);
+ }
+
+ const isResultThenable = result !== true && isThenable(result);
+
+ // If the listener didn't returned true or a Promise, or called
+ // wrappedSendResponse synchronously, we can exit earlier
+ // because there will be no response sent from this listener.
+ if (result !== true && !isResultThenable && !didCallSendResponse) {
+ return false;
+ }
+
+ // A small helper to send the message if the promise resolves
+ // and an error if the promise rejects (a wrapped sendMessage has
+ // to translate the message into a resolved promise or a rejected
+ // promise).
+ const sendPromisedResult = promise => {
+ promise.then(msg => {
+ // send the message value.
+ sendResponse(msg);
+ }, error => {
+ // Send a JSON representation of the error if the rejected value
+ // is an instance of error, or the object itself otherwise.
+ let message;
+ if (error && (error instanceof Error || typeof error.message === "string")) {
+ message = error.message;
+ } else {
+ message = "An unexpected error occurred";
+ }
+
+ sendResponse({
+ __mozWebExtensionPolyfillReject__: true,
+ message
+ });
+ }).catch(err => {
+ // Print an error on the console if unable to send the response.
+ console.error("Failed to send onMessage rejected reply", err);
+ });
+ };
+
+ // If the listener returned a Promise, send the resolved value as a
+ // result, otherwise wait the promise related to the wrappedSendResponse
+ // callback to resolve and send it as a response.
+ if (isResultThenable) {
+ sendPromisedResult(result);
+ } else {
+ sendPromisedResult(sendResponsePromise);
+ }
+
+ // Let Chrome know that the listener is replying.
+ return true;
+ };
+ });
+
+ const wrappedSendMessageCallback = ({ reject, resolve }, reply) => {
+ if (chrome.runtime.lastError) {
+ // Detect when none of the listeners replied to the sendMessage call and resolve
+ // the promise to undefined as in Firefox.
+ // See https://github.com/mozilla/webextension-polyfill/issues/130
+ if (chrome.runtime.lastError.message === CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE) {
+ resolve();
+ } else {
+ reject(chrome.runtime.lastError);
+ }
+ } else if (reply && reply.__mozWebExtensionPolyfillReject__) {
+ // Convert back the JSON representation of the error into
+ // an Error instance.
+ reject(new Error(reply.message));
+ } else {
+ resolve(reply);
+ }
+ };
+
+ const wrappedSendMessage = (name, metadata, apiNamespaceObj, ...args) => {
+ if (args.length < metadata.minArgs) {
+ throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`);
+ }
+
+ if (args.length > metadata.maxArgs) {
+ throw new Error(`Expected at most ${metadata.maxArgs} ${pluralizeArguments(metadata.maxArgs)} for ${name}(), got ${args.length}`);
+ }
+
+ return new Promise((resolve, reject) => {
+ const wrappedCb = wrappedSendMessageCallback.bind(null, { resolve, reject });
+ args.push(wrappedCb);
+ apiNamespaceObj.sendMessage(...args);
+ });
+ };
+
+ const staticWrappers = {
+ runtime: {
+ onMessage: wrapEvent(onMessageWrappers),
+ onMessageExternal: wrapEvent(onMessageWrappers),
+ sendMessage: wrappedSendMessage.bind(null, "sendMessage", { minArgs: 1, maxArgs: 3 })
+ },
+ tabs: {
+ sendMessage: wrappedSendMessage.bind(null, "sendMessage", { minArgs: 2, maxArgs: 3 })
+ }
+ };
+ const settingMetadata = {
+ clear: { minArgs: 1, maxArgs: 1 },
+ get: { minArgs: 1, maxArgs: 1 },
+ set: { minArgs: 1, maxArgs: 1 }
+ };
+ apiMetadata.privacy = {
+ network: {
+ networkPredictionEnabled: settingMetadata,
+ webRTCIPHandlingPolicy: settingMetadata
+ },
+ services: {
+ passwordSavingEnabled: settingMetadata
+ },
+ websites: {
+ hyperlinkAuditingEnabled: settingMetadata,
+ referrersEnabled: settingMetadata
+ }
+ };
+
+ return wrapObject(chrome, staticWrappers, apiMetadata);
+ };
+
+ // The build process adds a UMD wrapper around this file, which makes the
+ // `module` variable available.
+ module.exports = wrapAPIs(); // eslint-disable-line no-undef
+ } else {
+ module.exports = browser; // eslint-disable-line no-undef
+ }
+});
+//# sourceMappingURL=browser-polyfill.js.map
diff --git a/src/manifest.json b/src/manifest.json
index 43e47dc..f0326ab 100644
--- a/src/manifest.json
+++ b/src/manifest.json
@@ -8,7 +8,7 @@
"strict_min_version": "59.0"
}
},
- "version": "10.2.2rc3",
+ "version": "10.5",
"description": "__MSG_Description__",
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'none'",
@@ -34,6 +34,8 @@
"background": {
"persistent": true,
"scripts": [
+ "lib/UA.js",
+ "lib/browser-polyfill.js",
"lib/uuid.js",
"lib/log.js",
"lib/include.js",
@@ -76,6 +78,8 @@
"match_about_blank": true,
"all_frames": true,
"js": [
+ "lib/UA.js",
+ "lib/browser-polyfill.js",
"lib/log.js",
"lib/uuid.js",
"lib/sha256.js",
@@ -116,11 +120,13 @@
"commands": {
"_execute_browser_action": {
+ "description": "NoScript UI",
"suggested_key": {
"default": "Alt+Shift+N"
}
},
"togglePermissions": {
+ "description": "Toggle permissions",
"suggested_key": {
"default": "Ctrl+Shift+T"
}
diff --git a/src/ui/options.html b/src/ui/options.html
index 6e2ad0e..4d44d75 100644
--- a/src/ui/options.html
+++ b/src/ui/options.html
@@ -8,6 +8,8 @@
<link rel="stylesheet" href="/lib/flextabs.css" />
<link rel="stylesheet" href="options.css" />
<link rel="stylesheet" href="whirlpool.css" />
+<script src="/lib/UA.js"></script>
+<script src="/lib/browser-polyfill.js"></script>
<script src="/lib/include.js"></script>
<script src="/lib/log.js"></script>
<script src="/lib/flextabs.js"></script>
diff --git a/src/ui/popup.html b/src/ui/popup.html
index 517b233..5efc9e9 100644
--- a/src/ui/popup.html
+++ b/src/ui/popup.html
@@ -6,6 +6,8 @@
<title>NoScript Settings</title>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="popup.css" />
+<script src="/lib/UA.js"></script>
+<script src="/lib/browser-polyfill.js"></script>
<script src="/lib/include.js"></script>
<script src="/lib/log.js"></script>
<script src="/common/locale.js"></script>
diff --git a/src/ui/prompt.html b/src/ui/prompt.html
index 902b375..3c723e5 100644
--- a/src/ui/prompt.html
+++ b/src/ui/prompt.html
@@ -5,6 +5,8 @@
<title></title>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="prompt.css" />
+<script src="/lib/UA.js"></script>
+<script src="/lib/browser-polyfill.js"></script>
<script src="/lib/include.js"></script>
<script src="/lib/log.js"></script>
<script src="/common/locale.js"></script>
diff --git a/src/ui/siteInfo.html b/src/ui/siteInfo.html
index 0cb24ec..c37fb53 100644
--- a/src/ui/siteInfo.html
+++ b/src/ui/siteInfo.html
@@ -1,5 +1,7 @@
<!DOCTYPE html>
<meta charset="utf-8">
+<script src="/lib/UA.js"></script>
+<script src="/lib/browser-polyfill.js"></script>
<script src="/lib/log.js"></script>
<script src="/lib/include.js"></script>
<script src="siteInfo.js"></script>