Bug 1374590 - Fix changing devtools toolbox dock location while using WebExtension devtools panel.
MozReview-Commit-ID: 2O1MoNZXZm0
--- 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.