Bug 1454038 - 2. Finalize event callbacks on content window unload; r?esawin draft
authorJim Chen <nchen@mozilla.com>
Tue, 17 Apr 2018 16:13:39 -0400
changeset 783835 d92cf5539f8bcfa4c811d52e65b44595c7fa6567
parent 783834 5b98a8a95c6bfc7582fcb88e62c40b6f44f1895c
child 783836 a519d55d046dc638525e5ca15732eb20b115803f
push id106800
push userbmo:nchen@mozilla.com
push dateTue, 17 Apr 2018 20:15:42 +0000
reviewersesawin
bugs1454038
milestone61.0a1
Bug 1454038 - 2. Finalize event callbacks on content window unload; r?esawin When the content window unloads, we will no longer get any callbacks through the message manager, so we should finalize all active callbacks. MozReview-Commit-ID: 3VJ2bTlhUmH
mobile/android/modules/geckoview/GeckoViewContentModule.jsm
mobile/android/modules/geckoview/Messaging.jsm
--- a/mobile/android/modules/geckoview/GeckoViewContentModule.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewContentModule.jsm
@@ -24,16 +24,23 @@ class GeckoViewContentModule {
 
   constructor(aModuleName, aGlobal) {
     this.moduleName = aModuleName;
     this.messageManager = aGlobal;
 
     if (!aGlobal._gvEventDispatcher) {
       aGlobal._gvEventDispatcher =
           GeckoViewUtils.getDispatcherForWindow(aGlobal.content);
+      aGlobal.addEventListener("unload", event => {
+        if (event.target === this.messageManager) {
+          aGlobal._gvEventDispatcher.finalize();
+        }
+      }, {
+        mozSystemGroup: true,
+      });
     }
     this.eventDispatcher = aGlobal._gvEventDispatcher;
 
     this.messageManager.addMessageListener(
       "GeckoView:UpdateSettings",
       aMsg => {
         this.settings = aMsg.data;
         this.onSettingsUpdate();
--- a/mobile/android/modules/geckoview/Messaging.jsm
+++ b/mobile/android/modules/geckoview/Messaging.jsm
@@ -114,16 +114,30 @@ DispatcherDelegate.prototype = {
 
       this.dispatch(type, aMsg, {
         onSuccess: resolve,
         onError: reject,
       });
     });
   },
 
+  finalize: function() {
+    if (!this._replies) {
+      return;
+    }
+    this._replies.forEach(reply => {
+      if (typeof reply.finalizer === "function") {
+        reply.finalizer();
+      } else if (reply.finalizer) {
+        reply.finalizer.onFinalize();
+      }
+    });
+    this._replies.clear();
+  },
+
   receiveMessage: function(aMsg) {
     const {uuid, type} = aMsg.data;
     const reply = this._replies.get(uuid);
     if (!reply) {
       return;
     }
 
     if (type === "success") {
@@ -182,20 +196,29 @@ var EventDispatcher = {
     return new DispatcherDelegate(null, aMessageManager);
   },
 
   receiveMessage: function(aMsg) {
     // aMsg.data includes keys: global, event, data, uuid
     let callback;
     if (aMsg.data.uuid) {
       let reply = (type, response) => {
-        let mm = aMsg.data.global ? aMsg.target : aMsg.target.messageManager;
+        const mm = aMsg.data.global ? aMsg.target : aMsg.target.messageManager;
+        if (!mm) {
+          if (type === "finalize") {
+            // It's normal for the finalize call to come after the browser has
+            // been destroyed. We can gracefully handle that case despite
+            // having no message manager.
+            return;
+          }
+          throw Error(`No message manager for ${aMsg.data.event}:${type} reply`);
+        }
         mm.sendAsyncMessage("GeckoView:MessagingReply", {
-          type: type,
-          response: response,
+          type,
+          response,
           uuid: aMsg.data.uuid,
         });
       };
       callback = {
         onSuccess: response => reply("success", response),
         onError: error => reply("error", error),
         onFinalize: () => reply("finalize"),
       };