2019-11-18 20:40:53 +01:00
( function ( global , factory ) {
2020-12-10 10:14:29 +01:00
if ( typeof define === 'function' && define . amd ) {
define ( 'webextension-polyfill' , [ 'module' ] , factory )
} else if ( typeof exports !== 'undefined' ) {
factory ( module )
2019-11-18 20:40:53 +01:00
} else {
2020-12-10 10:14:29 +01:00
const mod = {
2019-11-18 20:40:53 +01:00
exports : { }
2020-12-10 10:14:29 +01:00
}
factory ( mod )
global . browser = mod . exports
2019-11-18 20:40:53 +01:00
}
} ) ( 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: */
/ * T h i s S o u r c e C o d e F o r m i s s u b j e c t t o t h e t e r m s o f t h e M o z i l l a P u b l i c
* 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/. */
2020-12-10 10:14:29 +01:00
'use strict'
2019-11-18 20:40:53 +01:00
2020-12-10 10:14:29 +01:00
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)'
2019-11-18 20:40:53 +01:00
// 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 = {
2020-12-10 10:14:29 +01:00
alarms : {
clear : {
minArgs : 0 ,
maxArgs : 1
} ,
clearAll : {
minArgs : 0 ,
maxArgs : 0
} ,
get : {
minArgs : 0 ,
maxArgs : 1
} ,
getAll : {
minArgs : 0 ,
maxArgs : 0
2019-11-18 20:40:53 +01:00
}
} ,
2020-12-10 10:14:29 +01:00
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
2019-11-18 20:40:53 +01:00
}
} ,
2020-12-10 10:14:29 +01:00
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
2019-11-18 20:40:53 +01:00
}
} ,
2020-12-10 10:14:29 +01:00
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
2019-11-18 20:40:53 +01:00
}
} ,
2020-12-10 10:14:29 +01:00
commands : {
getAll : {
minArgs : 0 ,
maxArgs : 0
2019-11-18 20:40:53 +01:00
}
} ,
2020-12-10 10:14:29 +01:00
contextMenus : {
remove : {
minArgs : 1 ,
maxArgs : 1
} ,
removeAll : {
minArgs : 0 ,
maxArgs : 0
} ,
update : {
minArgs : 2 ,
maxArgs : 2
2019-11-18 20:40:53 +01:00
}
} ,
2020-12-10 10:14:29 +01:00
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
2019-11-18 20:40:53 +01:00
}
} ,
2020-12-10 10:14:29 +01:00
devtools : {
inspectedWindow : {
eval : {
minArgs : 1 ,
maxArgs : 2 ,
singleCallbackArg : false
2019-11-18 20:40:53 +01:00
}
} ,
2020-12-10 10:14:29 +01:00
panels : {
create : {
minArgs : 3 ,
maxArgs : 3 ,
singleCallbackArg : true
2019-11-18 20:40:53 +01:00
}
}
} ,
2020-12-10 10:14:29 +01:00
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
2019-11-18 20:40:53 +01:00
}
} ,
2020-12-10 10:14:29 +01:00
extension : {
isAllowedFileSchemeAccess : {
minArgs : 0 ,
maxArgs : 0
} ,
isAllowedIncognitoAccess : {
minArgs : 0 ,
maxArgs : 0
2019-11-18 20:40:53 +01:00
}
} ,
2020-12-10 10:14:29 +01:00
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
2019-11-18 20:40:53 +01:00
}
} ,
2020-12-10 10:14:29 +01:00
i18n : {
detectLanguage : {
minArgs : 1 ,
maxArgs : 1
} ,
getAcceptLanguages : {
minArgs : 0 ,
maxArgs : 0
2019-11-18 20:40:53 +01:00
}
} ,
2020-12-10 10:14:29 +01:00
identity : {
launchWebAuthFlow : {
minArgs : 1 ,
maxArgs : 1
2019-11-18 20:40:53 +01:00
}
} ,
2020-12-10 10:14:29 +01:00
idle : {
queryState : {
minArgs : 1 ,
maxArgs : 1
2019-11-18 20:40:53 +01:00
}
} ,
2020-12-10 10:14:29 +01:00
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
2019-11-18 20:40:53 +01:00
}
} ,
2020-12-10 10:14:29 +01:00
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
2019-11-18 20:40:53 +01:00
}
} ,
2020-12-10 10:14:29 +01:00
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
2019-11-18 20:40:53 +01:00
}
} ,
2020-12-10 10:14:29 +01:00
permissions : {
contains : {
minArgs : 1 ,
maxArgs : 1
} ,
getAll : {
minArgs : 0 ,
maxArgs : 0
} ,
remove : {
minArgs : 1 ,
maxArgs : 1
} ,
request : {
minArgs : 1 ,
maxArgs : 1
2019-11-18 20:40:53 +01:00
}
} ,
2020-12-10 10:14:29 +01:00
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
2019-11-18 20:40:53 +01:00
}
} ,
2020-12-10 10:14:29 +01:00
sessions : {
getDevices : {
minArgs : 0 ,
maxArgs : 1
} ,
getRecentlyClosed : {
minArgs : 0 ,
maxArgs : 1
} ,
restore : {
minArgs : 0 ,
maxArgs : 1
2019-11-18 20:40:53 +01:00
}
} ,
2020-12-10 10:14:29 +01:00
storage : {
local : {
clear : {
minArgs : 0 ,
maxArgs : 0
2019-11-18 20:40:53 +01:00
} ,
2020-12-10 10:14:29 +01:00
get : {
minArgs : 0 ,
maxArgs : 1
2019-11-18 20:40:53 +01:00
} ,
2020-12-10 10:14:29 +01:00
getBytesInUse : {
minArgs : 0 ,
maxArgs : 1
2019-11-18 20:40:53 +01:00
} ,
2020-12-10 10:14:29 +01:00
remove : {
minArgs : 1 ,
maxArgs : 1
2019-11-18 20:40:53 +01:00
} ,
2020-12-10 10:14:29 +01:00
set : {
minArgs : 1 ,
maxArgs : 1
2019-11-18 20:40:53 +01:00
}
} ,
2020-12-10 10:14:29 +01:00
managed : {
get : {
minArgs : 0 ,
maxArgs : 1
2019-11-18 20:40:53 +01:00
} ,
2020-12-10 10:14:29 +01:00
getBytesInUse : {
minArgs : 0 ,
maxArgs : 1
2019-11-18 20:40:53 +01:00
}
} ,
2020-12-10 10:14:29 +01:00
sync : {
clear : {
minArgs : 0 ,
maxArgs : 0
2019-11-18 20:40:53 +01:00
} ,
2020-12-10 10:14:29 +01:00
get : {
minArgs : 0 ,
maxArgs : 1
2019-11-18 20:40:53 +01:00
} ,
2020-12-10 10:14:29 +01:00
getBytesInUse : {
minArgs : 0 ,
maxArgs : 1
2019-11-18 20:40:53 +01:00
} ,
2020-12-10 10:14:29 +01:00
remove : {
minArgs : 1 ,
maxArgs : 1
2019-11-18 20:40:53 +01:00
} ,
2020-12-10 10:14:29 +01:00
set : {
minArgs : 1 ,
maxArgs : 1
2019-11-18 20:40:53 +01:00
}
}
} ,
2020-12-10 10:14:29 +01:00
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
2019-11-18 20:40:53 +01:00
}
} ,
2020-12-10 10:14:29 +01:00
topSites : {
get : {
minArgs : 0 ,
maxArgs : 0
2019-11-18 20:40:53 +01:00
}
} ,
2020-12-10 10:14:29 +01:00
webNavigation : {
getAllFrames : {
minArgs : 1 ,
maxArgs : 1
} ,
getFrame : {
minArgs : 1 ,
maxArgs : 1
2019-11-18 20:40:53 +01:00
}
} ,
2020-12-10 10:14:29 +01:00
webRequest : {
handlerBehaviorChanged : {
minArgs : 0 ,
maxArgs : 0
2019-11-18 20:40:53 +01:00
}
} ,
2020-12-10 10:14:29 +01:00
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
2019-11-18 20:40:53 +01:00
}
}
2020-12-10 10:14:29 +01:00
}
2019-11-18 20:40:53 +01:00
if ( Object . keys ( apiMetadata ) . length === 0 ) {
2020-12-10 10:14:29 +01:00
throw new Error ( 'api-metadata.json has not been included in browser-polyfill' )
2019-11-18 20:40:53 +01:00
}
/ * *
* 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 {
2020-12-10 10:14:29 +01:00
constructor ( createItem , items = undefined ) {
super ( items )
this . createItem = createItem
2019-11-18 20:40:53 +01:00
}
2020-12-10 10:14:29 +01:00
get ( key ) {
2019-11-18 20:40:53 +01:00
if ( ! this . has ( key ) ) {
2020-12-10 10:14:29 +01:00
this . set ( key , this . createItem ( key ) )
2019-11-18 20:40:53 +01:00
}
2020-12-10 10:14:29 +01:00
return super . get ( key )
2019-11-18 20:40:53 +01:00
}
}
/ * *
* 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 => {
2020-12-10 10:14:29 +01:00
return value && typeof value === 'object' && typeof value . then === 'function'
}
2019-11-18 20:40:53 +01:00
/ * *
* 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 ) {
2020-12-10 10:14:29 +01:00
promise . reject ( extensionAPIs . runtime . lastError )
2019-11-18 20:40:53 +01:00
} else if ( metadata . singleCallbackArg || callbackArgs . length <= 1 && metadata . singleCallbackArg !== false ) {
2020-12-10 10:14:29 +01:00
promise . resolve ( callbackArgs [ 0 ] )
2019-11-18 20:40:53 +01:00
} else {
2020-12-10 10:14:29 +01:00
promise . resolve ( callbackArgs )
2019-11-18 20:40:53 +01:00
}
2020-12-10 10:14:29 +01:00
}
}
2019-11-18 20:40:53 +01:00
2020-12-10 10:14:29 +01:00
const pluralizeArguments = numArgs => numArgs == 1 ? 'argument' : 'arguments'
2019-11-18 20:40:53 +01:00
/ * *
* 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 ) => {
2020-12-10 10:14:29 +01:00
return function asyncFunctionWrapper ( target , ... args ) {
2019-11-18 20:40:53 +01:00
if ( args . length < metadata . minArgs ) {
2020-12-10 10:14:29 +01:00
throw new Error ( ` Expected at least ${ metadata . minArgs } ${ pluralizeArguments ( metadata . minArgs ) } for ${ name } (), got ${ args . length } ` )
2019-11-18 20:40:53 +01:00
}
if ( args . length > metadata . maxArgs ) {
2020-12-10 10:14:29 +01:00
throw new Error ( ` Expected at most ${ metadata . maxArgs } ${ pluralizeArguments ( metadata . maxArgs ) } for ${ name } (), got ${ args . length } ` )
2019-11-18 20:40:53 +01:00
}
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 {
2020-12-10 10:14:29 +01:00
target [ name ] ( ... args , makeCallback ( { resolve , reject } , metadata ) )
2019-11-18 20:40:53 +01:00
} catch ( cbError ) {
2020-12-10 10:14:29 +01:00
console . warn ( ` ${ name } API method doesn't seem to support the callback parameter, ` + 'falling back to call it without a callback: ' , cbError )
2019-11-18 20:40:53 +01:00
2020-12-10 10:14:29 +01:00
target [ name ] ( ... args )
2019-11-18 20:40:53 +01:00
// Update the API method metadata, so that the next API calls will not try to
// use the unsupported callback anymore.
2020-12-10 10:14:29 +01:00
metadata . fallbackToNoCallback = false
metadata . noCallback = true
2019-11-18 20:40:53 +01:00
2020-12-10 10:14:29 +01:00
resolve ( )
2019-11-18 20:40:53 +01:00
}
} else if ( metadata . noCallback ) {
2020-12-10 10:14:29 +01:00
target [ name ] ( ... args )
resolve ( )
2019-11-18 20:40:53 +01:00
} else {
2020-12-10 10:14:29 +01:00
target [ name ] ( ... args , makeCallback ( { resolve , reject } , metadata ) )
2019-11-18 20:40:53 +01:00
}
2020-12-10 10:14:29 +01:00
} )
}
}
2019-11-18 20:40:53 +01:00
/ * *
* 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 , {
2020-12-10 10:14:29 +01:00
apply ( targetMethod , thisObj , args ) {
return wrapper . call ( thisObj , target , ... args )
2019-11-18 20:40:53 +01:00
}
2020-12-10 10:14:29 +01:00
} )
}
2019-11-18 20:40:53 +01:00
2020-12-10 10:14:29 +01:00
const hasOwnProperty = Function . call . bind ( Object . prototype . hasOwnProperty )
2019-11-18 20:40:53 +01:00
/ * *
* 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 = { } ) => {
2020-12-10 10:14:29 +01:00
const cache = Object . create ( null )
const handlers = {
has ( proxyTarget , prop ) {
return prop in target || prop in cache
2019-11-18 20:40:53 +01:00
} ,
2020-12-10 10:14:29 +01:00
get ( proxyTarget , prop , receiver ) {
2019-11-18 20:40:53 +01:00
if ( prop in cache ) {
2020-12-10 10:14:29 +01:00
return cache [ prop ]
2019-11-18 20:40:53 +01:00
}
if ( ! ( prop in target ) ) {
2020-12-10 10:14:29 +01:00
return undefined
2019-11-18 20:40:53 +01:00
}
2020-12-10 10:14:29 +01:00
let value = target [ prop ]
2019-11-18 20:40:53 +01:00
2020-12-10 10:14:29 +01:00
if ( typeof value === 'function' ) {
2019-11-18 20:40:53 +01:00
// This is a method on the underlying object. Check if we need to do
// any wrapping.
2020-12-10 10:14:29 +01:00
if ( typeof wrappers [ prop ] === 'function' ) {
2019-11-18 20:40:53 +01:00
// We have a special-case wrapper for this method.
2020-12-10 10:14:29 +01:00
value = wrapMethod ( target , target [ prop ] , wrappers [ prop ] )
2019-11-18 20:40:53 +01:00
} else if ( hasOwnProperty ( metadata , prop ) ) {
// This is an async method that we have metadata for. Create a
// Promise wrapper for it.
2020-12-10 10:14:29 +01:00
const wrapper = wrapAsyncFunction ( prop , metadata [ prop ] )
value = wrapMethod ( target , target [ prop ] , wrapper )
2019-11-18 20:40:53 +01:00
} else {
// This is a method that we don't know or care about. Return the
// original method, bound to the underlying object.
2020-12-10 10:14:29 +01:00
value = value . bind ( target )
2019-11-18 20:40:53 +01:00
}
2020-12-10 10:14:29 +01:00
} else if ( typeof value === 'object' && value !== null && ( hasOwnProperty ( wrappers , prop ) || hasOwnProperty ( metadata , prop ) ) ) {
2019-11-18 20:40:53 +01:00
// 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.
2020-12-10 10:14:29 +01:00
value = wrapObject ( value , wrappers [ prop ] , metadata [ prop ] )
2019-11-18 20:40:53 +01:00
} 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 ,
2020-12-10 10:14:29 +01:00
get ( ) {
return target [ prop ]
2019-11-18 20:40:53 +01:00
} ,
2020-12-10 10:14:29 +01:00
set ( value ) {
target [ prop ] = value
2019-11-18 20:40:53 +01:00
}
2020-12-10 10:14:29 +01:00
} )
2019-11-18 20:40:53 +01:00
2020-12-10 10:14:29 +01:00
return value
2019-11-18 20:40:53 +01:00
}
2020-12-10 10:14:29 +01:00
cache [ prop ] = value
return value
2019-11-18 20:40:53 +01:00
} ,
2020-12-10 10:14:29 +01:00
set ( proxyTarget , prop , value , receiver ) {
2019-11-18 20:40:53 +01:00
if ( prop in cache ) {
2020-12-10 10:14:29 +01:00
cache [ prop ] = value
2019-11-18 20:40:53 +01:00
} else {
2020-12-10 10:14:29 +01:00
target [ prop ] = value
2019-11-18 20:40:53 +01:00
}
2020-12-10 10:14:29 +01:00
return true
2019-11-18 20:40:53 +01:00
} ,
2020-12-10 10:14:29 +01:00
defineProperty ( proxyTarget , prop , desc ) {
return Reflect . defineProperty ( cache , prop , desc )
2019-11-18 20:40:53 +01:00
} ,
2020-12-10 10:14:29 +01:00
deleteProperty ( proxyTarget , prop ) {
return Reflect . deleteProperty ( cache , prop )
2019-11-18 20:40:53 +01:00
}
2020-12-10 10:14:29 +01:00
}
2019-11-18 20:40:53 +01:00
// 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.
2020-12-10 10:14:29 +01:00
const proxyTarget = Object . create ( target )
return new Proxy ( proxyTarget , handlers )
}
2019-11-18 20:40:53 +01:00
/ * *
* 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 => ( {
2020-12-10 10:14:29 +01:00
addListener ( target , listener , ... args ) {
target . addListener ( wrapperMap . get ( listener ) , ... args )
2019-11-18 20:40:53 +01:00
} ,
2020-12-10 10:14:29 +01:00
hasListener ( target , listener ) {
return target . hasListener ( wrapperMap . get ( listener ) )
2019-11-18 20:40:53 +01:00
} ,
2020-12-10 10:14:29 +01:00
removeListener ( target , listener ) {
target . removeListener ( wrapperMap . get ( listener ) )
2019-11-18 20:40:53 +01:00
}
2020-12-10 10:14:29 +01:00
} )
2019-11-18 20:40:53 +01:00
// Keep track if the deprecation warning has been logged at least once.
2020-12-10 10:14:29 +01:00
let loggedSendResponseDeprecationWarning = false
2019-11-18 20:40:53 +01:00
const onMessageWrappers = new DefaultWeakMap ( listener => {
2020-12-10 10:14:29 +01:00
if ( typeof listener !== 'function' ) {
return listener
2019-11-18 20:40:53 +01:00
}
/ * *
* 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 .
* /
2020-12-10 10:14:29 +01:00
return function onMessage ( message , sender , sendResponse ) {
let didCallSendResponse = false
2019-11-18 20:40:53 +01:00
2020-12-10 10:14:29 +01:00
let wrappedSendResponse
const sendResponsePromise = new Promise ( resolve => {
2019-11-18 20:40:53 +01:00
wrappedSendResponse = function ( response ) {
if ( ! loggedSendResponseDeprecationWarning ) {
2020-12-10 10:14:29 +01:00
console . warn ( SEND _RESPONSE _DEPRECATION _WARNING , new Error ( ) . stack )
loggedSendResponseDeprecationWarning = true
2019-11-18 20:40:53 +01:00
}
2020-12-10 10:14:29 +01:00
didCallSendResponse = true
resolve ( response )
}
} )
2019-11-18 20:40:53 +01:00
2020-12-10 10:14:29 +01:00
let result
2019-11-18 20:40:53 +01:00
try {
2020-12-10 10:14:29 +01:00
result = listener ( message , sender , wrappedSendResponse )
2019-11-18 20:40:53 +01:00
} catch ( err ) {
2020-12-10 10:14:29 +01:00
result = Promise . reject ( err )
2019-11-18 20:40:53 +01:00
}
2020-12-10 10:14:29 +01:00
const isResultThenable = result !== true && isThenable ( result )
2019-11-18 20:40:53 +01:00
// 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 ) {
2020-12-10 10:14:29 +01:00
return false
2019-11-18 20:40:53 +01:00
}
// 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.
2020-12-10 10:14:29 +01:00
sendResponse ( msg )
2019-11-18 20:40:53 +01:00
} , error => {
// Send a JSON representation of the error if the rejected value
// is an instance of error, or the object itself otherwise.
2020-12-10 10:14:29 +01:00
let message
if ( error && ( error instanceof Error || typeof error . message === 'string' ) ) {
message = error . message
2019-11-18 20:40:53 +01:00
} else {
2020-12-10 10:14:29 +01:00
message = 'An unexpected error occurred'
2019-11-18 20:40:53 +01:00
}
sendResponse ( {
_ _mozWebExtensionPolyfillReject _ _ : true ,
message
2020-12-10 10:14:29 +01:00
} )
2019-11-18 20:40:53 +01:00
} ) . catch ( err => {
// Print an error on the console if unable to send the response.
2020-12-10 10:14:29 +01:00
console . error ( 'Failed to send onMessage rejected reply' , err )
} )
}
2019-11-18 20:40:53 +01:00
// 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 ) {
2020-12-10 10:14:29 +01:00
sendPromisedResult ( result )
2019-11-18 20:40:53 +01:00
} else {
2020-12-10 10:14:29 +01:00
sendPromisedResult ( sendResponsePromise )
2019-11-18 20:40:53 +01:00
}
// Let Chrome know that the listener is replying.
2020-12-10 10:14:29 +01:00
return true
}
} )
2019-11-18 20:40:53 +01:00
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 ) {
2020-12-10 10:14:29 +01:00
resolve ( )
2019-11-18 20:40:53 +01:00
} else {
2020-12-10 10:14:29 +01:00
reject ( extensionAPIs . runtime . lastError )
2019-11-18 20:40:53 +01:00
}
} else if ( reply && reply . _ _mozWebExtensionPolyfillReject _ _ ) {
// Convert back the JSON representation of the error into
// an Error instance.
2020-12-10 10:14:29 +01:00
reject ( new Error ( reply . message ) )
2019-11-18 20:40:53 +01:00
} else {
2020-12-10 10:14:29 +01:00
resolve ( reply )
2019-11-18 20:40:53 +01:00
}
2020-12-10 10:14:29 +01:00
}
2019-11-18 20:40:53 +01:00
const wrappedSendMessage = ( name , metadata , apiNamespaceObj , ... args ) => {
if ( args . length < metadata . minArgs ) {
2020-12-10 10:14:29 +01:00
throw new Error ( ` Expected at least ${ metadata . minArgs } ${ pluralizeArguments ( metadata . minArgs ) } for ${ name } (), got ${ args . length } ` )
2019-11-18 20:40:53 +01:00
}
if ( args . length > metadata . maxArgs ) {
2020-12-10 10:14:29 +01:00
throw new Error ( ` Expected at most ${ metadata . maxArgs } ${ pluralizeArguments ( metadata . maxArgs ) } for ${ name } (), got ${ args . length } ` )
2019-11-18 20:40:53 +01:00
}
return new Promise ( ( resolve , reject ) => {
2020-12-10 10:14:29 +01:00
const wrappedCb = wrappedSendMessageCallback . bind ( null , { resolve , reject } )
args . push ( wrappedCb )
apiNamespaceObj . sendMessage ( ... args )
} )
}
2019-11-18 20:40:53 +01:00
const staticWrappers = {
runtime : {
onMessage : wrapEvent ( onMessageWrappers ) ,
onMessageExternal : wrapEvent ( onMessageWrappers ) ,
2020-12-10 10:14:29 +01:00
sendMessage : wrappedSendMessage . bind ( null , 'sendMessage' , { minArgs : 1 , maxArgs : 3 } )
2019-11-18 20:40:53 +01:00
} ,
tabs : {
2020-12-10 10:14:29 +01:00
sendMessage : wrappedSendMessage . bind ( null , 'sendMessage' , { minArgs : 2 , maxArgs : 3 } )
2019-11-18 20:40:53 +01:00
}
2020-12-10 10:14:29 +01:00
}
2019-11-18 20:40:53 +01:00
const settingMetadata = {
clear : { minArgs : 1 , maxArgs : 1 } ,
get : { minArgs : 1 , maxArgs : 1 } ,
set : { minArgs : 1 , maxArgs : 1 }
2020-12-10 10:14:29 +01:00
}
2019-11-18 20:40:53 +01:00
apiMetadata . privacy = {
network : {
networkPredictionEnabled : settingMetadata ,
webRTCIPHandlingPolicy : settingMetadata
} ,
services : {
passwordSavingEnabled : settingMetadata
} ,
websites : {
hyperlinkAuditingEnabled : settingMetadata ,
referrersEnabled : settingMetadata
}
2020-12-10 10:14:29 +01:00
}
2019-11-18 20:40:53 +01:00
2020-12-10 10:14:29 +01:00
return wrapObject ( extensionAPIs , staticWrappers , apiMetadata )
}
2019-11-18 20:40:53 +01:00
2020-12-10 10:14:29 +01:00
if ( typeof chrome !== 'object' || ! chrome || ! chrome . runtime || ! chrome . runtime . id ) {
throw new Error ( 'This script should only be loaded in a browser extension.' )
2019-11-18 20:40:53 +01:00
}
// The build process adds a UMD wrapper around this file, which makes the
// `module` variable available.
2020-12-10 10:14:29 +01:00
module . exports = wrapAPIs ( chrome )
2019-11-18 20:40:53 +01:00
} else {
2020-12-10 10:14:29 +01:00
module . exports = browser
2019-11-18 20:40:53 +01:00
}
2020-12-10 10:14:29 +01:00
} )
// # sourceMappingURL=browser-polyfill.js.map