Bug 1396686 - Provide info which onMessage listener's response handle went out of scope draft
authorTomislav Jovanovic <tomica@gmail.com>
Tue, 05 Sep 2017 03:00:07 +0200
changeset 659994 da5c970bb364c0e43f7bb226f18bc0cfdd4ae916
parent 657984 a46a5879b8781ae9ea99f37b5d34a891f0f75047
child 730116 a11b811dc1b4461a7c974c2ba32af16fb981d3d5
push id78268
push userbmo:tomica@gmail.com
push dateWed, 06 Sep 2017 14:03:51 +0000
bugs1396686
milestone57.0a1
Bug 1396686 - Provide info which onMessage listener's response handle went out of scope MozReview-Commit-ID: Bu71gP8Ey3
toolkit/components/extensions/ExtensionChild.jsm
toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage.js
--- a/toolkit/components/extensions/ExtensionChild.jsm
+++ b/toolkit/components/extensions/ExtensionChild.jsm
@@ -81,30 +81,35 @@ function injectAPI(source, dest) {
  * A finalization witness helper that wraps a sendMessage response and
  * guarantees to either get the promise resolved, or rejected when the
  * wrapped promise goes out of scope.
  *
  * Holding a reference to a returned StrongPromise doesn't prevent the
  * wrapped promise from being garbage collected.
  */
 const StrongPromise = {
-  wrap(promise, id) {
+  wrap(promise, channelId, location) {
     return new Promise((resolve, reject) => {
-      const witness = finalizationService.make("extensions-sendMessage-witness", id);
+      const tag = `${channelId}|${location}`;
+      const witness = finalizationService.make("extensions-sendMessage-witness", tag);
       promise.then(value => {
         witness.forget();
         resolve(value);
       }, error => {
         witness.forget();
         reject(error);
       });
     });
   },
-  observe(subject, topic, id) {
-    MessageChannel.abortChannel(id, {message: "Response handle went out of scope"});
+  observe(subject, topic, tag) {
+    const pos = tag.indexOf("|");
+    const channel = tag.substr(0, pos);
+    const location = tag.substr(pos + 1);
+    const message = `Promised response from onMessage listener at ${location} went out of scope`;
+    MessageChannel.abortChannel(channel, {message});
   },
 };
 Services.obs.addObserver(StrongPromise, "extensions-sendMessage-witness");
 
 /**
  * Abstraction for a Port object in the extension API.
  *
  * @param {BaseContext} context The context that owns this port.
@@ -375,16 +380,18 @@ class Messenger {
 
   sendNativeMessage(messageManager, msg, recipient, responseCallback) {
     msg = NativeApp.encodeMessage(this.context, msg);
     return this.sendMessage(messageManager, msg, recipient, responseCallback);
   }
 
   _onMessage(name, filter) {
     return new EventManager(this.context, name, fire => {
+      const [location] = new this.context.cloneScope.Error().stack.split("\n", 1);
+
       let listener = {
         messageFilterPermissive: this.optionalFilter,
         messageFilterStrict: this.filter,
 
         filterMessage: (sender, recipient) => {
           // Ignore the message if it was sent by this Messenger.
           return (sender.contextId !== this.context.contextId &&
                   filter(sender, recipient));
@@ -411,19 +418,19 @@ class Messenger {
           sendResponse = Cu.exportFunction(sendResponse, this.context.cloneScope);
 
           // Note: We intentionally do not use runSafe here so that any
           // errors are propagated to the message sender.
           let result = fire.raw(message, sender, sendResponse);
           message = null;
 
           if (result instanceof this.context.cloneScope.Promise) {
-            return StrongPromise.wrap(result, channelId);
+            return StrongPromise.wrap(result, channelId, location);
           } else if (result === true) {
-            return StrongPromise.wrap(promise, channelId);
+            return StrongPromise.wrap(promise, channelId, location);
           }
           return response;
         },
       };
 
       MessageChannel.addListener(this.messageManagers, "Extension:Message", listener);
       return () => {
         MessageChannel.removeListener(this.messageManagers, "Extension:Message", listener);
--- a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage.js
@@ -150,20 +150,20 @@ add_task(async function sendMessageRespo
   function page() {
     browser.test.onMessage.addListener(msg => {
       browser.runtime.sendMessage(msg)
         .then(response => {
           if (response) {
             browser.test.log(`Got response: ${response}`);
             browser.test.sendMessage(response);
           }
-        }, error => {
-          browser.test.assertEq(error.message,
-            "Response handle went out of scope",
-            "The promise rejected with the correct error");
+        }, ({message}) => {
+          browser.test.assertTrue(
+            /at background@moz-extension:\/\/[\w-]+\/%7B[\w-]+%7D\.js:4:\d went out/.test(message),
+            `Promise rejected with the correct error message: ${message}`);
           browser.test.sendMessage("rejected");
         }
       );
     });
     browser.test.sendMessage("ready");
   }
 
   let extension = ExtensionTestUtils.loadExtension({