| Index: polyfill.js |
| =================================================================== |
| --- a/polyfill.js |
| +++ b/polyfill.js |
| @@ -48,36 +48,45 @@ |
| // Since we add a callback for all messaging API calls in our wrappers, |
| // Chrome assumes we're interested in the response; when there's no response, |
| // it sets runtime.lastError |
| const portClosedBeforeResponseError = |
| // Older versions of Chrome have a typo: |
| // https://crrev.com/c33f51726eacdcc1a487b21a13611f7eab580d6d |
| /^The message port closed before a res?ponse was received\.$/; |
| - function wrapAPI(api) |
| + // This is the error Firefox throws when a message listener is not a |
| + // function. |
| + const invalidMessageListenerError = "Invalid listener for runtime.onMessage."; |
| + |
| + let messageListeners = new WeakMap(); |
| + |
| + function wrapAsyncAPI(api) |
| { |
| let object = browser; |
| let path = api.split("."); |
| let name = path.pop(); |
| for (let node of path) |
| { |
| object = object[node]; |
| if (!object) |
| return; |
| } |
| let func = object[name]; |
| if (!func) |
| return; |
| + |
| let descriptor = Object.getOwnPropertyDescriptor(object, name); |
| + |
| delete descriptor["get"]; |
| delete descriptor["set"]; |
| + |
| descriptor.value = function(...args) |
| { |
| let callStack = new Error().stack; |
| if (typeof args[args.length - 1] == "function") |
| return func.apply(object, args); |
| // If the last argument is undefined, we drop it from the list assuming |
| @@ -109,19 +118,84 @@ |
| } |
| else |
| { |
| resolve(result); |
| } |
| }); |
| }); |
| }; |
| + |
| Object.defineProperty(object, name, descriptor); |
| } |
| + function wrapRuntimeOnMessage() |
| + { |
| + let {onMessage} = browser.runtime; |
| + let {addListener, removeListener, hasListener} = onMessage; |
| + |
| + onMessage.addListener = function(listener) |
| + { |
| + if (typeof listener != "function") |
| + throw new Error(invalidMessageListenerError); |
| + |
| + // Don't add the same listener twice or we end up with multiple wrappers. |
| + if (messageListeners.has(listener)) |
| + return; |
| + |
| + let wrapper = (message, sender, sendResponse) => |
| + { |
| + let wait = listener(message, sender, sendResponse); |
| + |
| + if (wait instanceof Promise) |
| + { |
| + wait.then(sendResponse, reason => |
| + { |
| + try |
| + { |
| + sendResponse(); |
| + } |
| + finally |
| + { |
| + // sendResponse can throw if the internal port is closed; be sure |
| + // to throw the original error. |
| + throw reason; |
| + } |
| + }); |
| + } |
| + |
| + return !!wait; |
| + }; |
| + |
| + addListener.call(onMessage, wrapper); |
| + messageListeners.set(listener, wrapper); |
| + }; |
| + |
| + onMessage.removeListener = function(listener) |
| + { |
| + if (typeof listener != "function") |
| + throw new Error(invalidMessageListenerError); |
| + |
| + let wrapper = messageListeners.get(listener); |
| + if (wrapper) |
| + { |
| + removeListener.call(onMessage, wrapper); |
| + messageListeners.delete(listener); |
| + } |
| + }; |
| + |
| + onMessage.hasListener = function(listener) |
| + { |
| + if (typeof listener != "function") |
| + throw new Error(invalidMessageListenerError); |
| + |
| + return messageListeners.has(listener); |
| + }; |
| + } |
| + |
| function shouldWrapAPIs() |
| { |
| try |
| { |
| return !(browser.storage.local.get([]) instanceof Promise); |
| } |
| catch (error) |
| { |
| @@ -134,17 +208,19 @@ |
| { |
| // Unlike Firefox and Microsoft Edge, Chrome doesn't have a "browser" |
| // object, but provides the extension API through the "chrome" namespace |
| // (non-standard). |
| if (typeof browser == "undefined") |
| window.browser = chrome; |
| for (let api of asyncAPIs) |
| - wrapAPI(api); |
| + wrapAsyncAPI(api); |
| + |
| + wrapRuntimeOnMessage(); |
| } |
| // Workaround since HTMLCollection, NodeList, StyleSheetList, and CSSRuleList |
| // didn't have iterator support before Chrome 51. |
| // https://bugs.chromium.org/p/chromium/issues/detail?id=401699 |
| for (let object of [HTMLCollection, NodeList, StyleSheetList, CSSRuleList]) |
| { |
| if (!(Symbol.iterator in object.prototype)) |