(function (global, factory) { if (typeof define === 'function' && define.amd) { define('webextension-polyfill', ['module'], factory) } else if (typeof exports !== 'undefined') { factory(module) } else { const 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} * 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) } }) } const 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} */ const wrapObject = (target, wrappers = {}, metadata = {}) => { const cache = Object.create(null) const 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. const 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. const 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} 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 const 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