diff --git a/src/browser-polyfill.js b/src/browser-polyfill.js index f5cd05e..fd20d4d 100644 --- a/src/browser-polyfill.js +++ b/src/browser-polyfill.js @@ -7,6 +7,7 @@ "use strict"; if (typeof browser === "undefined") { + 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 @@ -446,7 +447,14 @@ if (typeof browser === "undefined") { const wrappedSendMessageCallback = ({reject, resolve}, reply) => { if (chrome.runtime.lastError) { - reject(chrome.runtime.lastError); + // Detect when none of the listers replied to the sendMessage call and resolve + // the promise to undefined as in Firefox. + // See https://github.com/mozilla/webextension-polyfill/issues/130 + if (chrome.runtime.lastError.message === CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE) { + resolve(); + } else { + reject(chrome.runtime.lastError); + } } else if (reply && reply.__mozWebExtensionPolyfillReject__) { // Convert back the JSON representation of the error into // an Error instance. diff --git a/test/fixtures/runtime-messaging-extension/background.js b/test/fixtures/runtime-messaging-extension/background.js index c090a8e..52a485b 100644 --- a/test/fixtures/runtime-messaging-extension/background.js +++ b/test/fixtures/runtime-messaging-extension/background.js @@ -45,6 +45,9 @@ browser.runtime.onMessage.addListener((msg, sender, sendResponse) => { case "test - sendMessage with listener callback throws": throw new Error("listener throws"); + case "test - sendMessage and no listener answers": + return undefined; + default: return Promise.resolve( `Unxpected message received by the background page: ${JSON.stringify(msg)}\n`); @@ -52,6 +55,10 @@ browser.runtime.onMessage.addListener((msg, sender, sendResponse) => { }); browser.runtime.onMessage.addListener((msg, sender, sendResponse) => { + if (msg === "test - sendMessage and no listener answers") { + return undefined; + } + setTimeout(() => { sendResponse("second listener reply"); }, 100); diff --git a/test/fixtures/runtime-messaging-extension/content.js b/test/fixtures/runtime-messaging-extension/content.js index 5773af2..490eec3 100644 --- a/test/fixtures/runtime-messaging-extension/content.js +++ b/test/fixtures/runtime-messaging-extension/content.js @@ -75,3 +75,8 @@ test("sendMessage with listener callback throws", async (t) => { t.equal(err.message, "listener throws", "Got an error with the expected message"); } }); + +test("sendMessage and no listener answers", async (t) => { + const reply = await browser.runtime.sendMessage("test - sendMessage and no listener answers"); + t.equal(reply, undefined, "Got undefined reply as expected"); +}); diff --git a/test/test-runtime-onMessage.js b/test/test-runtime-onMessage.js index 0351372..2e97407 100644 --- a/test/test-runtime-onMessage.js +++ b/test/test-runtime-onMessage.js @@ -238,5 +238,30 @@ describe("browser-polyfill", () => { }); }); }); + + it("resolves to undefined when no listeners reply", () => { + const fakeChrome = { + runtime: { + // This error message is defined as CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE + // in the polyfill sources and it is used to recognize when Chrome has detected that + // none of the listeners replied. + lastError: { + message: "The message port closed before a response was received.", + }, + sendMessage: sinon.stub(), + }, + }; + + fakeChrome.runtime.sendMessage.onFirstCall().callsArgWith(1, [undefined]); + + return setupTestDOMWindow(fakeChrome).then(window => { + const promise = window.browser.runtime.sendMessage("some_message"); + ok(fakeChrome.runtime.sendMessage.calledOnce, "sendMessage has been called once"); + + return promise.then(reply => { + deepEqual(reply, undefined, "sendMessage promise should be resolved to undefined"); + }); + }); + }); }); });