1188 lines
36 KiB
JavaScript
1188 lines
36 KiB
JavaScript
(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.5.0 - Thu Sep 26 2019 22:22:26 */
|
|
/* -*- 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 = extensionAPIs => {
|
|
// 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,
|
|
"singleCallbackArg": false
|
|
}
|
|
},
|
|
"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
|
|
},
|
|
"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 (extensionAPIs.runtime.lastError) {
|
|
promise.reject(extensionAPIs.runtime.lastError);
|
|
} else if (metadata.singleCallbackArg || callbackArgs.length <= 1 && metadata.singleCallbackArg !== false) {
|
|
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 (extensionAPIs.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 (extensionAPIs.runtime.lastError.message === CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE) {
|
|
resolve();
|
|
} else {
|
|
reject(extensionAPIs.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(extensionAPIs, staticWrappers, apiMetadata);
|
|
};
|
|
|
|
if (typeof chrome != "object" || !chrome || !chrome.runtime || !chrome.runtime.id) {
|
|
throw new Error("This script should only be loaded in a browser extension.");
|
|
}
|
|
|
|
// The build process adds a UMD wrapper around this file, which makes the
|
|
// `module` variable available.
|
|
module.exports = wrapAPIs(chrome);
|
|
} else {
|
|
module.exports = browser;
|
|
}
|
|
});
|
|
//# sourceMappingURL=browser-polyfill.js.map
|