1 (function (global, factory) {
2 if (typeof define === "function" && define.amd) {
3 define("webextension-polyfill", ["module"], factory);
4 } else if (typeof exports !== "undefined") {
11 global.browser = mod.exports;
13 })(typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : this, function (module) {
14 /* webextension-polyfill - v0.7.0 - Tue Nov 10 2020 20:24:04 */
16 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
18 /* vim: set sts=2 sw=2 et tw=80: */
20 /* This Source Code Form is subject to the terms of the Mozilla Public
21 * License, v. 2.0. If a copy of the MPL was not distributed with this
22 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
25 if (typeof browser === "undefined" || Object.getPrototypeOf(browser) !== Object.prototype) {
26 const CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE = "The message port closed before a response was received.";
27 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
28 // optimization for Firefox. Since Spidermonkey does not fully parse the
29 // contents of a function until the first time it's called, and since it will
30 // never actually need to be called, this allows the polyfill to be included
31 // in Firefox nearly for free.
33 const wrapAPIs = extensionAPIs => {
34 // NOTE: apiMetadata is associated to the content of the api-metadata.json file
35 // at build time by replacing the following "include" with the content of the
106 "fallbackToNoCallback": true
111 "fallbackToNoCallback": true
113 "getBadgeBackgroundColor": {
133 "setBadgeBackgroundColor": {
136 "fallbackToNoCallback": true
141 "fallbackToNoCallback": true
150 "fallbackToNoCallback": true
155 "fallbackToNoCallback": true
183 "removeLocalStorage": {
191 "removePluginData": {
229 "getAllCookieStores": {
247 "singleCallbackArg": false
254 "singleCallbackArg": true
257 "createSidebarPane": {
284 "fallbackToNoCallback": true
305 "fallbackToNoCallback": true
309 "isAllowedFileSchemeAccess": {
313 "isAllowedIncognitoAccess": {
349 "getAcceptLanguages": {
355 "launchWebAuthFlow": {
401 "getPermissionLevel": {
422 "fallbackToNoCallback": true
431 "fallbackToNoCallback": true
436 "fallbackToNoCallback": true
441 "fallbackToNoCallback": true
463 "getBackgroundPage": {
475 "requestUpdateCheck": {
483 "sendNativeMessage": {
497 "getRecentlyClosed": {
563 "captureVisibleTab": {
673 "handlerBehaviorChanged": {
710 if (Object.keys(apiMetadata).length === 0) {
711 throw new Error("api-metadata.json has not been included in browser-polyfill");
714 * A WeakMap subclass which creates and stores a value for any key which does
715 * not exist when accessed, but behaves exactly as an ordinary WeakMap
718 * @param {function} createItem
719 * A function which will be called in order to create the value for any
720 * key which does not exist, the first time it is accessed. The
721 * function receives, as its only argument, the key being created.
725 class DefaultWeakMap extends WeakMap {
726 constructor(createItem, items = undefined) {
728 this.createItem = createItem;
732 if (!this.has(key)) {
733 this.set(key, this.createItem(key));
736 return super.get(key);
741 * Returns true if the given object is an object with a `then` method, and can
742 * therefore be assumed to behave as a Promise.
744 * @param {*} value The value to test.
745 * @returns {boolean} True if the value is thenable.
749 const isThenable = value => {
750 return value && typeof value === "object" && typeof value.then === "function";
753 * Creates and returns a function which, when called, will resolve or reject
754 * the given promise based on how it is called:
756 * - If, when called, `chrome.runtime.lastError` contains a non-null object,
757 * the promise is rejected with that value.
758 * - If the function is called with exactly one argument, the promise is
759 * resolved to that value.
760 * - Otherwise, the promise is resolved to an array containing all of the
761 * function's arguments.
763 * @param {object} promise
764 * An object containing the resolution and rejection functions of a
766 * @param {function} promise.resolve
767 * The promise's resolution function.
768 * @param {function} promise.rejection
769 * The promise's rejection function.
770 * @param {object} metadata
771 * Metadata about the wrapped method which has created the callback.
772 * @param {integer} metadata.maxResolvedArgs
773 * The maximum number of arguments which may be passed to the
774 * callback created by the wrapped async function.
776 * @returns {function}
777 * The generated callback function.
781 const makeCallback = (promise, metadata) => {
782 return (...callbackArgs) => {
783 if (extensionAPIs.runtime.lastError) {
784 promise.reject(extensionAPIs.runtime.lastError);
785 } else if (metadata.singleCallbackArg || callbackArgs.length <= 1 && metadata.singleCallbackArg !== false) {
786 promise.resolve(callbackArgs[0]);
788 promise.resolve(callbackArgs);
793 const pluralizeArguments = numArgs => numArgs == 1 ? "argument" : "arguments";
795 * Creates a wrapper function for a method with the given name and metadata.
797 * @param {string} name
798 * The name of the method which is being wrapped.
799 * @param {object} metadata
800 * Metadata about the method being wrapped.
801 * @param {integer} metadata.minArgs
802 * The minimum number of arguments which must be passed to the
803 * function. If called with fewer than this number of arguments, the
804 * wrapper will raise an exception.
805 * @param {integer} metadata.maxArgs
806 * The maximum number of arguments which may be passed to the
807 * function. If called with more than this number of arguments, the
808 * wrapper will raise an exception.
809 * @param {integer} metadata.maxResolvedArgs
810 * The maximum number of arguments which may be passed to the
811 * callback created by the wrapped async function.
813 * @returns {function(object, ...*)}
814 * The generated wrapper function.
818 const wrapAsyncFunction = (name, metadata) => {
819 return function asyncFunctionWrapper(target, ...args) {
820 if (args.length < metadata.minArgs) {
821 throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`);
824 if (args.length > metadata.maxArgs) {
825 throw new Error(`Expected at most ${metadata.maxArgs} ${pluralizeArguments(metadata.maxArgs)} for ${name}(), got ${args.length}`);
828 return new Promise((resolve, reject) => {
829 if (metadata.fallbackToNoCallback) {
830 // This API method has currently no callback on Chrome, but it return a promise on Firefox,
831 // and so the polyfill will try to call it with a callback first, and it will fallback
832 // to not passing the callback if the first call fails.
834 target[name](...args, makeCallback({
839 console.warn(`${name} API method doesn't seem to support the callback parameter, ` + "falling back to call it without a callback: ", cbError);
840 target[name](...args); // Update the API method metadata, so that the next API calls will not try to
841 // use the unsupported callback anymore.
843 metadata.fallbackToNoCallback = false;
844 metadata.noCallback = true;
847 } else if (metadata.noCallback) {
848 target[name](...args);
851 target[name](...args, makeCallback({
860 * Wraps an existing method of the target object, so that calls to it are
861 * intercepted by the given wrapper function. The wrapper function receives,
862 * as its first argument, the original `target` object, followed by each of
863 * the arguments passed to the original method.
865 * @param {object} target
866 * The original target object that the wrapped method belongs to.
867 * @param {function} method
868 * The method being wrapped. This is used as the target of the Proxy
869 * object which is created to wrap the method.
870 * @param {function} wrapper
871 * The wrapper function which is called in place of a direct invocation
872 * of the wrapped method.
874 * @returns {Proxy<function>}
875 * A Proxy object for the given method, which invokes the given wrapper
876 * method in its place.
880 const wrapMethod = (target, method, wrapper) => {
881 return new Proxy(method, {
882 apply(targetMethod, thisObj, args) {
883 return wrapper.call(thisObj, target, ...args);
889 let hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty);
891 * Wraps an object in a Proxy which intercepts and wraps certain methods
892 * based on the given `wrappers` and `metadata` objects.
894 * @param {object} target
895 * The target object to wrap.
897 * @param {object} [wrappers = {}]
898 * An object tree containing wrapper functions for special cases. Any
899 * function present in this object tree is called in place of the
900 * method in the same location in the `target` object tree. These
901 * wrapper methods are invoked as described in {@see wrapMethod}.
903 * @param {object} [metadata = {}]
904 * An object tree containing metadata used to automatically generate
905 * Promise-based wrapper functions for asynchronous. Any function in
906 * the `target` object tree which has a corresponding metadata object
907 * in the same location in the `metadata` tree is replaced with an
908 * automatically-generated wrapper function, as described in
909 * {@see wrapAsyncFunction}
911 * @returns {Proxy<object>}
914 const wrapObject = (target, wrappers = {}, metadata = {}) => {
915 let cache = Object.create(null);
917 has(proxyTarget, prop) {
918 return prop in target || prop in cache;
921 get(proxyTarget, prop, receiver) {
926 if (!(prop in target)) {
930 let value = target[prop];
932 if (typeof value === "function") {
933 // This is a method on the underlying object. Check if we need to do
935 if (typeof wrappers[prop] === "function") {
936 // We have a special-case wrapper for this method.
937 value = wrapMethod(target, target[prop], wrappers[prop]);
938 } else if (hasOwnProperty(metadata, prop)) {
939 // This is an async method that we have metadata for. Create a
940 // Promise wrapper for it.
941 let wrapper = wrapAsyncFunction(prop, metadata[prop]);
942 value = wrapMethod(target, target[prop], wrapper);
944 // This is a method that we don't know or care about. Return the
945 // original method, bound to the underlying object.
946 value = value.bind(target);
948 } else if (typeof value === "object" && value !== null && (hasOwnProperty(wrappers, prop) || hasOwnProperty(metadata, prop))) {
949 // This is an object that we need to do some wrapping for the children
950 // of. Create a sub-object wrapper for it with the appropriate child
952 value = wrapObject(value, wrappers[prop], metadata[prop]);
953 } else if (hasOwnProperty(metadata, "*")) {
954 // Wrap all properties in * namespace.
955 value = wrapObject(value, wrappers[prop], metadata["*"]);
957 // We don't need to do any wrapping for this property,
958 // so just forward all access to the underlying object.
959 Object.defineProperty(cache, prop, {
968 target[prop] = value;
979 set(proxyTarget, prop, value, receiver) {
983 target[prop] = value;
989 defineProperty(proxyTarget, prop, desc) {
990 return Reflect.defineProperty(cache, prop, desc);
993 deleteProperty(proxyTarget, prop) {
994 return Reflect.deleteProperty(cache, prop);
997 }; // Per contract of the Proxy API, the "get" proxy handler must return the
998 // original value of the target if that value is declared read-only and
999 // non-configurable. For this reason, we create an object with the
1000 // prototype set to `target` instead of using `target` directly.
1001 // Otherwise we cannot return a custom object for APIs that
1002 // are declared read-only and non-configurable, such as `chrome.devtools`.
1004 // The proxy handlers themselves will still use the original `target`
1005 // instead of the `proxyTarget`, so that the methods and properties are
1006 // dereferenced via the original targets.
1008 let proxyTarget = Object.create(target);
1009 return new Proxy(proxyTarget, handlers);
1012 * Creates a set of wrapper functions for an event object, which handles
1013 * wrapping of listener functions that those messages are passed.
1015 * A single wrapper is created for each listener function, and stored in a
1016 * map. Subsequent calls to `addListener`, `hasListener`, or `removeListener`
1017 * retrieve the original wrapper, so that attempts to remove a
1018 * previously-added listener work as expected.
1020 * @param {DefaultWeakMap<function, function>} wrapperMap
1021 * A DefaultWeakMap object which will create the appropriate wrapper
1022 * for a given listener function when one does not exist, and retrieve
1023 * an existing one when it does.
1029 const wrapEvent = wrapperMap => ({
1030 addListener(target, listener, ...args) {
1031 target.addListener(wrapperMap.get(listener), ...args);
1034 hasListener(target, listener) {
1035 return target.hasListener(wrapperMap.get(listener));
1038 removeListener(target, listener) {
1039 target.removeListener(wrapperMap.get(listener));
1042 }); // Keep track if the deprecation warning has been logged at least once.
1045 let loggedSendResponseDeprecationWarning = false;
1046 const onMessageWrappers = new DefaultWeakMap(listener => {
1047 if (typeof listener !== "function") {
1051 * Wraps a message listener function so that it may send responses based on
1052 * its return value, rather than by returning a sentinel value and calling a
1053 * callback. If the listener function returns a Promise, the response is
1054 * sent when the promise either resolves or rejects.
1056 * @param {*} message
1057 * The message sent by the other end of the channel.
1058 * @param {object} sender
1059 * Details about the sender of the message.
1060 * @param {function(*)} sendResponse
1061 * A callback which, when called with an arbitrary argument, sends
1062 * that value as a response.
1063 * @returns {boolean}
1064 * True if the wrapped listener returned a Promise, which will later
1065 * yield a response. False otherwise.
1069 return function onMessage(message, sender, sendResponse) {
1070 let didCallSendResponse = false;
1071 let wrappedSendResponse;
1072 let sendResponsePromise = new Promise(resolve => {
1073 wrappedSendResponse = function (response) {
1074 if (!loggedSendResponseDeprecationWarning) {
1075 console.warn(SEND_RESPONSE_DEPRECATION_WARNING, new Error().stack);
1076 loggedSendResponseDeprecationWarning = true;
1079 didCallSendResponse = true;
1086 result = listener(message, sender, wrappedSendResponse);
1088 result = Promise.reject(err);
1091 const isResultThenable = result !== true && isThenable(result); // If the listener didn't returned true or a Promise, or called
1092 // wrappedSendResponse synchronously, we can exit earlier
1093 // because there will be no response sent from this listener.
1095 if (result !== true && !isResultThenable && !didCallSendResponse) {
1097 } // A small helper to send the message if the promise resolves
1098 // and an error if the promise rejects (a wrapped sendMessage has
1099 // to translate the message into a resolved promise or a rejected
1103 const sendPromisedResult = promise => {
1104 promise.then(msg => {
1105 // send the message value.
1108 // Send a JSON representation of the error if the rejected value
1109 // is an instance of error, or the object itself otherwise.
1112 if (error && (error instanceof Error || typeof error.message === "string")) {
1113 message = error.message;
1115 message = "An unexpected error occurred";
1119 __mozWebExtensionPolyfillReject__: true,
1123 // Print an error on the console if unable to send the response.
1124 console.error("Failed to send onMessage rejected reply", err);
1126 }; // If the listener returned a Promise, send the resolved value as a
1127 // result, otherwise wait the promise related to the wrappedSendResponse
1128 // callback to resolve and send it as a response.
1131 if (isResultThenable) {
1132 sendPromisedResult(result);
1134 sendPromisedResult(sendResponsePromise);
1135 } // Let Chrome know that the listener is replying.
1142 const wrappedSendMessageCallback = ({
1146 if (extensionAPIs.runtime.lastError) {
1147 // Detect when none of the listeners replied to the sendMessage call and resolve
1148 // the promise to undefined as in Firefox.
1149 // See https://github.com/mozilla/webextension-polyfill/issues/130
1150 if (extensionAPIs.runtime.lastError.message === CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE) {
1153 reject(extensionAPIs.runtime.lastError);
1155 } else if (reply && reply.__mozWebExtensionPolyfillReject__) {
1156 // Convert back the JSON representation of the error into
1157 // an Error instance.
1158 reject(new Error(reply.message));
1164 const wrappedSendMessage = (name, metadata, apiNamespaceObj, ...args) => {
1165 if (args.length < metadata.minArgs) {
1166 throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`);
1169 if (args.length > metadata.maxArgs) {
1170 throw new Error(`Expected at most ${metadata.maxArgs} ${pluralizeArguments(metadata.maxArgs)} for ${name}(), got ${args.length}`);
1173 return new Promise((resolve, reject) => {
1174 const wrappedCb = wrappedSendMessageCallback.bind(null, {
1178 args.push(wrappedCb);
1179 apiNamespaceObj.sendMessage(...args);
1183 const staticWrappers = {
1185 onMessage: wrapEvent(onMessageWrappers),
1186 onMessageExternal: wrapEvent(onMessageWrappers),
1187 sendMessage: wrappedSendMessage.bind(null, "sendMessage", {
1193 sendMessage: wrappedSendMessage.bind(null, "sendMessage", {
1199 const settingMetadata = {
1213 apiMetadata.privacy = {
1215 "*": settingMetadata
1218 "*": settingMetadata
1221 "*": settingMetadata
1224 return wrapObject(extensionAPIs, staticWrappers, apiMetadata);
1227 if (typeof chrome != "object" || !chrome || !chrome.runtime || !chrome.runtime.id) {
1228 throw new Error("This script should only be loaded in a browser extension.");
1229 } // The build process adds a UMD wrapper around this file, which makes the
1230 // `module` variable available.
1233 module.exports = wrapAPIs(chrome);
1235 module.exports = browser;
1238 //# sourceMappingURL=browser-polyfill.js.map