Bug 1396686 - Provide info which onMessage listener's response handle went out of scope r=kmag
authorTomislav Jovanovic <tomica@gmail.com>
Tue, 05 Sep 2017 03:00:07 +0200
changeset 428664 2b6ef963b47bb56b770cd7d9963dbea28f76cabe
parent 428663 6a66de2d1901d7d199f22d1cd97bbffdf895533c
child 428665 4fa9f7339c5dd4034004a25d022b17140f03e65c
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskmag
bugs1396686
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1396686 - Provide info which onMessage listener's response handle went out of scope r=kmag 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({