Bug 1374590 - Fix changing devtools toolbox dock location while using WebExtension devtools panel. r=aswan
authorLuca Greco <lgreco@mozilla.com>
Mon, 11 Sep 2017 17:32:05 +0200
changeset 430277 97b428deeefae7e859ff336ea7f21a45663925a4
parent 430276 d1dc7082f9e3c5a96f4cce794c48f9e7575f558c
child 430278 a83df3d96eafcfcfab92274fbb5c40b9e15df0fc
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)
reviewersaswan
bugs1374590
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 1374590 - Fix changing devtools toolbox dock location while using WebExtension devtools panel. r=aswan MozReview-Commit-ID: 2O1MoNZXZm0
browser/components/extensions/ext-c-devtools-panels.js
browser/components/extensions/ext-devtools-panels.js
--- a/browser/components/extensions/ext-c-devtools-panels.js
+++ b/browser/components/extensions/ext-c-devtools-panels.js
@@ -55,30 +55,29 @@ class ChildDevToolsPanel extends Extensi
         return view;
       }
     }
 
     return null;
   }
 
   receiveMessage({name, data}) {
-    // Filter out any message received while the panel context do not yet
-    // exist.
-    if (!this.panelContext || !this.panelContext.contentWindow) {
-      return;
-    }
-
     // Filter out any message that is not related to the id of this
     // toolbox panel.
     if (!data || data.toolboxPanelId !== this.id) {
       return;
     }
 
     switch (name) {
       case "Extension:DevToolsPanelShown":
+        // Filter out *Shown message received while the panel context do not yet
+        // exist.
+        if (!this.panelContext || !this.panelContext.contentWindow) {
+          return;
+        }
         this.onParentPanelShown();
         break;
       case "Extension:DevToolsPanelHidden":
         this.onParentPanelHidden();
         break;
     }
   }
 
--- a/browser/components/extensions/ext-devtools-panels.js
+++ b/browser/components/extensions/ext-devtools-panels.js
@@ -56,22 +56,29 @@ class ParentDevToolsPanel {
 
     this.panelOptions = panelOptions;
 
     this.context.callOnClose(this);
 
     this.id = this.panelOptions.id;
 
     this.onToolboxPanelSelect = this.onToolboxPanelSelect.bind(this);
+    this.onToolboxHostWillChange = this.onToolboxHostWillChange.bind(this);
+    this.onToolboxHostChanged = this.onToolboxHostChanged.bind(this);
 
     this.unwatchExtensionProxyContextLoad = null;
     this.waitTopLevelContext = new Promise(resolve => {
       this._resolveTopLevelContext = resolve;
     });
 
+    // References to the panel browser XUL element and the toolbox window global which
+    // contains the devtools panel UI.
+    this.browser = null;
+    this.browserContainerWindow = null;
+
     this.panelAdded = false;
     this.addPanel();
   }
 
   addPanel() {
     const {icon, title} = this.panelOptions;
     const extensionName = this.context.extension.name;
 
@@ -98,30 +105,76 @@ class ParentDevToolsPanel {
     this.panelAdded = true;
   }
 
   buildPanel(window) {
     const {toolbox} = this;
 
     this.createBrowserElement(window);
 
+    // Store the last panel's container element (used to restore it when the toolbox
+    // host is switched between docked and undocked).
+    this.browserContainerWindow = window;
+
     toolbox.on("select", this.onToolboxPanelSelect);
+    toolbox.on("host-will-change", this.onToolboxHostWillChange);
+    toolbox.on("host-changed", this.onToolboxHostChanged);
 
     // Return a cleanup method that is when the panel is destroyed, e.g.
     // - when addon devtool panel has been disabled by the user from the toolbox preferences,
     //   its ParentDevToolsPanel instance is still valid, but the built devtools panel is removed from
     //   the toolbox (and re-built again if the user re-enables it from the toolbox preferences panel)
     // - when the creator context has been destroyed, the ParentDevToolsPanel close method is called,
     //   it removes the tool definition from the toolbox, which will call this destroy method.
     return () => {
       this.destroyBrowserElement();
+      this.browserContainerWindow = null;
       toolbox.off("select", this.onToolboxPanelSelect);
+      toolbox.off("host-will-change", this.onToolboxHostWillChange);
+      toolbox.off("host-changed", this.onToolboxHostChanged);
     };
   }
 
+  onToolboxHostWillChange() {
+    // NOTE: Using a content iframe here breaks the devtools panel
+    // switching between docked and undocked mode,
+    // because of a swapFrameLoader exception (see bug 1075490),
+    // destroy the browser and recreate it after the toolbox host has been
+    // switched is a reasonable workaround to fix the issue on release and beta
+    // Firefox versions (at least until the underlying bug can be fixed).
+    if (this.browser) {
+      // Fires a panel.onHidden event before destroying the browser element because
+      // the toolbox hosts is changing.
+      if (this.visible) {
+        this.context.parentMessageManager.sendAsyncMessage("Extension:DevToolsPanelHidden", {
+          toolboxPanelId: this.id,
+        });
+      }
+
+      this.destroyBrowserElement();
+    }
+  }
+
+  async onToolboxHostChanged() {
+    if (this.browserContainerWindow) {
+      this.createBrowserElement(this.browserContainerWindow);
+
+      // Fires a panel.onShown event once the browser element has been recreated
+      // after the toolbox hosts has been changed (needed to provide the new window
+      // object to the extension page that has created the devtools panel).
+      if (this.visible) {
+        await this.waitTopLevelContext;
+
+        this.context.parentMessageManager.sendAsyncMessage("Extension:DevToolsPanelShown", {
+          toolboxPanelId: this.id,
+        });
+      }
+    }
+  }
+
   async onToolboxPanelSelect(what, id) {
     if (!this.waitTopLevelContext || !this.panelAdded) {
       return;
     }
 
     // Wait that the panel is fully loaded and emit show.
     await this.waitTopLevelContext;
 
@@ -145,20 +198,22 @@ class ParentDevToolsPanel {
     }
 
     // Explicitly remove the panel if it is registered and the toolbox is not
     // closing itself.
     if (this.panelAdded && toolbox.isToolRegistered(this.id)) {
       toolbox.removeAdditionalTool(this.id);
     }
 
+    this.waitTopLevelContext = null;
+    this._resolveTopLevelContext = null;
     this.context = null;
     this.toolbox = null;
-    this.waitTopLevelContext = null;
-    this._resolveTopLevelContext = null;
+    this.browser = null;
+    this.browserContainerWindow = null;
   }
 
   createBrowserElement(window) {
     const {toolbox} = this;
     const {url} = this.panelOptions;
     const {document} = window;
 
     const browser = document.createElementNS(XUL_NS, "browser");
@@ -174,22 +229,16 @@ class ParentDevToolsPanel {
 
     const {extension} = this.context;
 
     let awaitFrameLoader = Promise.resolve();
     if (extension.remote) {
       browser.setAttribute("remote", "true");
       browser.setAttribute("remoteType", E10SUtils.EXTENSION_REMOTE_TYPE);
       awaitFrameLoader = promiseEvent(browser, "XULFrameLoaderCreated");
-    } else if (!AppConstants.RELEASE_OR_BETA) {
-      // NOTE: Using a content iframe here breaks the devtools panel
-      // switching between docked and undocked mode,
-      // because of a swapFrameLoader exception (see bug 1075490).
-      browser.setAttribute("type", "chrome");
-      browser.setAttribute("forcemessagemanager", true);
     }
 
     let hasTopLevelContext = false;
 
     // Listening to new proxy contexts.
     this.unwatchExtensionProxyContextLoad = watchExtensionProxyContextLoad(this, context => {
       // Keep track of the toolbox and target associated to the context, which is
       // needed by the API methods implementation.