Updated browser-polyfill.js.

This commit is contained in:
hackademix 2020-12-29 00:56:01 +01:00
parent cfc1688215
commit b5e26f2260
1 changed files with 122 additions and 70 deletions

View File

@ -10,10 +10,13 @@
factory(mod); factory(mod);
global.browser = mod.exports; global.browser = mod.exports;
} }
})(this, function (module) { })(typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : this, function (module) {
/* webextension-polyfill - v0.3.1 - Tue Aug 21 2018 10:09:34 */ /* webextension-polyfill - v0.7.0 - Tue Nov 10 2020 20:24:04 */
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */ /* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public /* 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 * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@ -21,14 +24,13 @@
if (typeof browser === "undefined" || Object.getPrototypeOf(browser) !== Object.prototype) { 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 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)"; 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
// 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 // 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 // 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 // never actually need to be called, this allows the polyfill to be included
// in Firefox nearly for free. // in Firefox nearly for free.
const wrapAPIs = () => {
const wrapAPIs = extensionAPIs => {
// NOTE: apiMetadata is associated to the content of the api-metadata.json file // 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 // at build time by replacing the following "include" with the content of the
// JSON file. // JSON file.
@ -241,7 +243,8 @@
"inspectedWindow": { "inspectedWindow": {
"eval": { "eval": {
"minArgs": 1, "minArgs": 1,
"maxArgs": 2 "maxArgs": 2,
"singleCallbackArg": false
} }
}, },
"panels": { "panels": {
@ -249,6 +252,12 @@
"minArgs": 3, "minArgs": 3,
"maxArgs": 3, "maxArgs": 3,
"singleCallbackArg": true "singleCallbackArg": true
},
"elements": {
"createSidebarPane": {
"minArgs": 1,
"maxArgs": 1
}
} }
} }
}, },
@ -455,10 +464,6 @@
"minArgs": 0, "minArgs": 0,
"maxArgs": 0 "maxArgs": 0
}, },
"getBrowserInfo": {
"minArgs": 0,
"maxArgs": 0
},
"getPlatformInfo": { "getPlatformInfo": {
"minArgs": 0, "minArgs": 0,
"maxArgs": 0 "maxArgs": 0
@ -595,6 +600,14 @@
"minArgs": 0, "minArgs": 0,
"maxArgs": 1 "maxArgs": 1
}, },
"goBack": {
"minArgs": 0,
"maxArgs": 1
},
"goForward": {
"minArgs": 0,
"maxArgs": 1
},
"highlight": { "highlight": {
"minArgs": 1, "minArgs": 1,
"maxArgs": 1 "maxArgs": 1
@ -697,7 +710,6 @@
if (Object.keys(apiMetadata).length === 0) { if (Object.keys(apiMetadata).length === 0) {
throw new Error("api-metadata.json has not been included in browser-polyfill"); 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 * 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 * not exist when accessed, but behaves exactly as an ordinary WeakMap
@ -708,6 +720,8 @@
* key which does not exist, the first time it is accessed. The * key which does not exist, the first time it is accessed. The
* function receives, as its only argument, the key being created. * function receives, as its only argument, the key being created.
*/ */
class DefaultWeakMap extends WeakMap { class DefaultWeakMap extends WeakMap {
constructor(createItem, items = undefined) { constructor(createItem, items = undefined) {
super(items); super(items);
@ -721,8 +735,8 @@
return super.get(key); return super.get(key);
} }
}
}
/** /**
* Returns true if the given object is an object with a `then` method, and can * Returns true if the given object is an object with a `then` method, and can
* therefore be assumed to behave as a Promise. * therefore be assumed to behave as a Promise.
@ -730,10 +744,11 @@
* @param {*} value The value to test. * @param {*} value The value to test.
* @returns {boolean} True if the value is thenable. * @returns {boolean} True if the value is thenable.
*/ */
const isThenable = value => { const isThenable = value => {
return value && typeof value === "object" && typeof value.then === "function"; return value && typeof value === "object" && typeof value.then === "function";
}; };
/** /**
* Creates and returns a function which, when called, will resolve or reject * Creates and returns a function which, when called, will resolve or reject
* the given promise based on how it is called: * the given promise based on how it is called:
@ -761,11 +776,13 @@
* @returns {function} * @returns {function}
* The generated callback function. * The generated callback function.
*/ */
const makeCallback = (promise, metadata) => { const makeCallback = (promise, metadata) => {
return (...callbackArgs) => { return (...callbackArgs) => {
if (chrome.runtime.lastError) { if (extensionAPIs.runtime.lastError) {
promise.reject(chrome.runtime.lastError); promise.reject(extensionAPIs.runtime.lastError);
} else if (metadata.singleCallbackArg || callbackArgs.length <= 1) { } else if (metadata.singleCallbackArg || callbackArgs.length <= 1 && metadata.singleCallbackArg !== false) {
promise.resolve(callbackArgs[0]); promise.resolve(callbackArgs[0]);
} else { } else {
promise.resolve(callbackArgs); promise.resolve(callbackArgs);
@ -774,7 +791,6 @@
}; };
const pluralizeArguments = numArgs => numArgs == 1 ? "argument" : "arguments"; const pluralizeArguments = numArgs => numArgs == 1 ? "argument" : "arguments";
/** /**
* Creates a wrapper function for a method with the given name and metadata. * Creates a wrapper function for a method with the given name and metadata.
* *
@ -797,6 +813,8 @@
* @returns {function(object, ...*)} * @returns {function(object, ...*)}
* The generated wrapper function. * The generated wrapper function.
*/ */
const wrapAsyncFunction = (name, metadata) => { const wrapAsyncFunction = (name, metadata) => {
return function asyncFunctionWrapper(target, ...args) { return function asyncFunctionWrapper(target, ...args) {
if (args.length < metadata.minArgs) { if (args.length < metadata.minArgs) {
@ -813,29 +831,31 @@
// and so the polyfill will try to call it with a callback first, and it will fallback // 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. // to not passing the callback if the first call fails.
try { try {
target[name](...args, makeCallback({ resolve, reject }, metadata)); target[name](...args, makeCallback({
resolve,
reject
}, metadata));
} catch (cbError) { } catch (cbError) {
console.warn(`${name} API method doesn't seem to support the callback parameter, ` + "falling back to call it without a callback: ", 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
target[name](...args);
// Update the API method metadata, so that the next API calls will not try to
// use the unsupported callback anymore. // use the unsupported callback anymore.
metadata.fallbackToNoCallback = false; metadata.fallbackToNoCallback = false;
metadata.noCallback = true; metadata.noCallback = true;
resolve(); resolve();
} }
} else if (metadata.noCallback) { } else if (metadata.noCallback) {
target[name](...args); target[name](...args);
resolve(); resolve();
} else { } else {
target[name](...args, makeCallback({ resolve, reject }, metadata)); target[name](...args, makeCallback({
resolve,
reject
}, metadata));
} }
}); });
}; };
}; };
/** /**
* Wraps an existing method of the target object, so that calls to it are * Wraps an existing method of the target object, so that calls to it are
* intercepted by the given wrapper function. The wrapper function receives, * intercepted by the given wrapper function. The wrapper function receives,
@ -855,16 +875,18 @@
* A Proxy object for the given method, which invokes the given wrapper * A Proxy object for the given method, which invokes the given wrapper
* method in its place. * method in its place.
*/ */
const wrapMethod = (target, method, wrapper) => { const wrapMethod = (target, method, wrapper) => {
return new Proxy(method, { return new Proxy(method, {
apply(targetMethod, thisObj, args) { apply(targetMethod, thisObj, args) {
return wrapper.call(thisObj, target, ...args); return wrapper.call(thisObj, target, ...args);
} }
}); });
}; };
let hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty); let hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty);
/** /**
* Wraps an object in a Proxy which intercepts and wraps certain methods * Wraps an object in a Proxy which intercepts and wraps certain methods
* based on the given `wrappers` and `metadata` objects. * based on the given `wrappers` and `metadata` objects.
@ -888,6 +910,7 @@
* *
* @returns {Proxy<object>} * @returns {Proxy<object>}
*/ */
const wrapObject = (target, wrappers = {}, metadata = {}) => { const wrapObject = (target, wrappers = {}, metadata = {}) => {
let cache = Object.create(null); let cache = Object.create(null);
let handlers = { let handlers = {
@ -909,7 +932,6 @@
if (typeof value === "function") { if (typeof value === "function") {
// This is a method on the underlying object. Check if we need to do // This is a method on the underlying object. Check if we need to do
// any wrapping. // any wrapping.
if (typeof wrappers[prop] === "function") { if (typeof wrappers[prop] === "function") {
// We have a special-case wrapper for this method. // We have a special-case wrapper for this method.
value = wrapMethod(target, target[prop], wrappers[prop]); value = wrapMethod(target, target[prop], wrappers[prop]);
@ -928,20 +950,25 @@
// of. Create a sub-object wrapper for it with the appropriate child // of. Create a sub-object wrapper for it with the appropriate child
// metadata. // metadata.
value = wrapObject(value, wrappers[prop], metadata[prop]); value = wrapObject(value, wrappers[prop], metadata[prop]);
} else if (hasOwnProperty(metadata, "*")) {
// Wrap all properties in * namespace.
value = wrapObject(value, wrappers[prop], metadata["*"]);
} else { } else {
// We don't need to do any wrapping for this property, // We don't need to do any wrapping for this property,
// so just forward all access to the underlying object. // so just forward all access to the underlying object.
Object.defineProperty(cache, prop, { Object.defineProperty(cache, prop, {
configurable: true, configurable: true,
enumerable: true, enumerable: true,
get() { get() {
return target[prop]; return target[prop];
}, },
set(value) { set(value) {
target[prop] = value; target[prop] = value;
} }
});
});
return value; return value;
} }
@ -955,6 +982,7 @@
} else { } else {
target[prop] = value; target[prop] = value;
} }
return true; return true;
}, },
@ -965,9 +993,8 @@
deleteProperty(proxyTarget, prop) { deleteProperty(proxyTarget, prop) {
return Reflect.deleteProperty(cache, prop); return Reflect.deleteProperty(cache, prop);
} }
};
// Per contract of the Proxy API, the "get" proxy handler must return the }; // 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 // original value of the target if that value is declared read-only and
// non-configurable. For this reason, we create an object with the // non-configurable. For this reason, we create an object with the
// prototype set to `target` instead of using `target` directly. // prototype set to `target` instead of using `target` directly.
@ -977,10 +1004,10 @@
// The proxy handlers themselves will still use the original `target` // The proxy handlers themselves will still use the original `target`
// instead of the `proxyTarget`, so that the methods and properties are // instead of the `proxyTarget`, so that the methods and properties are
// dereferenced via the original targets. // dereferenced via the original targets.
let proxyTarget = Object.create(target); let proxyTarget = Object.create(target);
return new Proxy(proxyTarget, handlers); return new Proxy(proxyTarget, handlers);
}; };
/** /**
* Creates a set of wrapper functions for an event object, which handles * Creates a set of wrapper functions for an event object, which handles
* wrapping of listener functions that those messages are passed. * wrapping of listener functions that those messages are passed.
@ -997,6 +1024,8 @@
* *
* @returns {object} * @returns {object}
*/ */
const wrapEvent = wrapperMap => ({ const wrapEvent = wrapperMap => ({
addListener(target, listener, ...args) { addListener(target, listener, ...args) {
target.addListener(wrapperMap.get(listener), ...args); target.addListener(wrapperMap.get(listener), ...args);
@ -1009,16 +1038,15 @@
removeListener(target, listener) { removeListener(target, listener) {
target.removeListener(wrapperMap.get(listener)); target.removeListener(wrapperMap.get(listener));
} }
});
// Keep track if the deprecation warning has been logged at least once. }); // Keep track if the deprecation warning has been logged at least once.
let loggedSendResponseDeprecationWarning = false; let loggedSendResponseDeprecationWarning = false;
const onMessageWrappers = new DefaultWeakMap(listener => { const onMessageWrappers = new DefaultWeakMap(listener => {
if (typeof listener !== "function") { if (typeof listener !== "function") {
return listener; return listener;
} }
/** /**
* Wraps a message listener function so that it may send responses based on * 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 * its return value, rather than by returning a sentinel value and calling a
@ -1036,9 +1064,10 @@
* True if the wrapped listener returned a Promise, which will later * True if the wrapped listener returned a Promise, which will later
* yield a response. False otherwise. * yield a response. False otherwise.
*/ */
return function onMessage(message, sender, sendResponse) { return function onMessage(message, sender, sendResponse) {
let didCallSendResponse = false; let didCallSendResponse = false;
let wrappedSendResponse; let wrappedSendResponse;
let sendResponsePromise = new Promise(resolve => { let sendResponsePromise = new Promise(resolve => {
wrappedSendResponse = function (response) { wrappedSendResponse = function (response) {
@ -1046,31 +1075,31 @@
console.warn(SEND_RESPONSE_DEPRECATION_WARNING, new Error().stack); console.warn(SEND_RESPONSE_DEPRECATION_WARNING, new Error().stack);
loggedSendResponseDeprecationWarning = true; loggedSendResponseDeprecationWarning = true;
} }
didCallSendResponse = true; didCallSendResponse = true;
resolve(response); resolve(response);
}; };
}); });
let result; let result;
try { try {
result = listener(message, sender, wrappedSendResponse); result = listener(message, sender, wrappedSendResponse);
} catch (err) { } catch (err) {
result = Promise.reject(err); result = Promise.reject(err);
} }
const isResultThenable = result !== true && isThenable(result); const isResultThenable = result !== true && isThenable(result); // If the listener didn't returned true or a Promise, or called
// If the listener didn't returned true or a Promise, or called
// wrappedSendResponse synchronously, we can exit earlier // wrappedSendResponse synchronously, we can exit earlier
// because there will be no response sent from this listener. // because there will be no response sent from this listener.
if (result !== true && !isResultThenable && !didCallSendResponse) { if (result !== true && !isResultThenable && !didCallSendResponse) {
return false; return false;
} } // A small helper to send the message if the promise resolves
// A small helper to send the message if the promise resolves
// and an error if the promise rejects (a wrapped sendMessage has // and an error if the promise rejects (a wrapped sendMessage has
// to translate the message into a resolved promise or a rejected // to translate the message into a resolved promise or a rejected
// promise). // promise).
const sendPromisedResult = promise => { const sendPromisedResult = promise => {
promise.then(msg => { promise.then(msg => {
// send the message value. // send the message value.
@ -1079,6 +1108,7 @@
// Send a JSON representation of the error if the rejected value // Send a JSON representation of the error if the rejected value
// is an instance of error, or the object itself otherwise. // is an instance of error, or the object itself otherwise.
let message; let message;
if (error && (error instanceof Error || typeof error.message === "string")) { if (error && (error instanceof Error || typeof error.message === "string")) {
message = error.message; message = error.message;
} else { } else {
@ -1093,31 +1123,34 @@
// Print an error on the console if unable to send the response. // Print an error on the console if unable to send the response.
console.error("Failed to send onMessage rejected reply", err); console.error("Failed to send onMessage rejected reply", err);
}); });
}; }; // If the listener returned a Promise, send the resolved value as a
// If the listener returned a Promise, send the resolved value as a
// result, otherwise wait the promise related to the wrappedSendResponse // result, otherwise wait the promise related to the wrappedSendResponse
// callback to resolve and send it as a response. // callback to resolve and send it as a response.
if (isResultThenable) { if (isResultThenable) {
sendPromisedResult(result); sendPromisedResult(result);
} else { } else {
sendPromisedResult(sendResponsePromise); sendPromisedResult(sendResponsePromise);
} } // Let Chrome know that the listener is replying.
// Let Chrome know that the listener is replying.
return true; return true;
}; };
}); });
const wrappedSendMessageCallback = ({ reject, resolve }, reply) => { const wrappedSendMessageCallback = ({
if (chrome.runtime.lastError) { reject,
resolve
}, reply) => {
if (extensionAPIs.runtime.lastError) {
// Detect when none of the listeners replied to the sendMessage call and resolve // Detect when none of the listeners replied to the sendMessage call and resolve
// the promise to undefined as in Firefox. // the promise to undefined as in Firefox.
// See https://github.com/mozilla/webextension-polyfill/issues/130 // See https://github.com/mozilla/webextension-polyfill/issues/130
if (chrome.runtime.lastError.message === CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE) { if (extensionAPIs.runtime.lastError.message === CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE) {
resolve(); resolve();
} else { } else {
reject(chrome.runtime.lastError); reject(extensionAPIs.runtime.lastError);
} }
} else if (reply && reply.__mozWebExtensionPolyfillReject__) { } else if (reply && reply.__mozWebExtensionPolyfillReject__) {
// Convert back the JSON representation of the error into // Convert back the JSON representation of the error into
@ -1138,7 +1171,10 @@
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const wrappedCb = wrappedSendMessageCallback.bind(null, { resolve, reject }); const wrappedCb = wrappedSendMessageCallback.bind(null, {
resolve,
reject
});
args.push(wrappedCb); args.push(wrappedCb);
apiNamespaceObj.sendMessage(...args); apiNamespaceObj.sendMessage(...args);
}); });
@ -1148,39 +1184,55 @@
runtime: { runtime: {
onMessage: wrapEvent(onMessageWrappers), onMessage: wrapEvent(onMessageWrappers),
onMessageExternal: wrapEvent(onMessageWrappers), onMessageExternal: wrapEvent(onMessageWrappers),
sendMessage: wrappedSendMessage.bind(null, "sendMessage", { minArgs: 1, maxArgs: 3 }) sendMessage: wrappedSendMessage.bind(null, "sendMessage", {
minArgs: 1,
maxArgs: 3
})
}, },
tabs: { tabs: {
sendMessage: wrappedSendMessage.bind(null, "sendMessage", { minArgs: 2, maxArgs: 3 }) sendMessage: wrappedSendMessage.bind(null, "sendMessage", {
minArgs: 2,
maxArgs: 3
})
} }
}; };
const settingMetadata = { const settingMetadata = {
clear: { minArgs: 1, maxArgs: 1 }, clear: {
get: { minArgs: 1, maxArgs: 1 }, minArgs: 1,
set: { minArgs: 1, maxArgs: 1 } maxArgs: 1
},
get: {
minArgs: 1,
maxArgs: 1
},
set: {
minArgs: 1,
maxArgs: 1
}
}; };
apiMetadata.privacy = { apiMetadata.privacy = {
network: { network: {
networkPredictionEnabled: settingMetadata, "*": settingMetadata
webRTCIPHandlingPolicy: settingMetadata
}, },
services: { services: {
passwordSavingEnabled: settingMetadata "*": settingMetadata
}, },
websites: { websites: {
hyperlinkAuditingEnabled: settingMetadata, "*": settingMetadata
referrersEnabled: settingMetadata
} }
}; };
return wrapObject(extensionAPIs, staticWrappers, apiMetadata);
return wrapObject(chrome, staticWrappers, apiMetadata);
}; };
// The build process adds a UMD wrapper around this file, which makes the 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` variable available.
module.exports = wrapAPIs(); // eslint-disable-line no-undef
module.exports = wrapAPIs(chrome);
} else { } else {
module.exports = browser; // eslint-disable-line no-undef module.exports = browser;
} }
}); });
// !disabled because of https://bugzilla.mozilla.org/show_bug.cgi?id=1437937 # sourceMappingURL=browser-polyfill.js.map //# sourceMappingURL=browser-polyfill.js.map