--- a/devtools/client/framework/devtools.js
+++ b/devtools/client/framework/devtools.js
@@ -5,16 +5,17 @@
"use strict";
const Services = require("Services");
const promise = require("promise");
const defer = require("devtools/shared/defer");
// Load gDevToolsBrowser toolbox lazily as they need gDevTools to be fully initialized
loader.lazyRequireGetter(this, "Toolbox", "devtools/client/framework/toolbox", true);
+loader.lazyRequireGetter(this, "ToolboxWrapper", "devtools/client/framework/toolbox-wrapper", true);
loader.lazyRequireGetter(this, "gDevToolsBrowser", "devtools/client/framework/devtools-browser", true);
const {defaultTools: DefaultTools, defaultThemes: DefaultThemes} =
require("devtools/client/definitions");
const EventEmitter = require("devtools/shared/event-emitter");
const {JsonView} = require("devtools/client/jsonview/main");
const AboutDevTools = require("devtools/client/framework/about-devtools-toolbox");
const {when: unload} = require("sdk/system/unload");
@@ -414,38 +415,38 @@ DevTools.prototype = {
}
return hostPromise.then(function () {
toolbox.raise();
return toolbox;
});
}
else {
- // No toolbox for target, create one
- toolbox = new Toolbox(target, toolId, hostType, hostOptions);
-
- this.emit("toolbox-created", toolbox);
+ let wrapper = new ToolboxWrapper(target, hostType, hostOptions);
- this._toolboxes.set(target, toolbox);
+ wrapper.create(toolId)
+ .then(toolbox => {
+ this._toolboxes.set(target, toolbox);
- toolbox.once("destroy", () => {
- this.emit("toolbox-destroy", target);
- });
+ this.emit("toolbox-created", toolbox);
- toolbox.once("destroyed", () => {
- this._toolboxes.delete(target);
- this.emit("toolbox-destroyed", target);
- });
+ toolbox.once("destroy", () => {
+ this.emit("toolbox-destroy", target);
+ });
- // If toolId was passed in, it will already be selected before the
- // open promise resolves.
- toolbox.open().then(() => {
- deferred.resolve(toolbox);
- this.emit("toolbox-ready", toolbox);
- });
+ toolbox.once("destroyed", () => {
+ this._toolboxes.delete(target);
+ this.emit("toolbox-destroyed", target);
+ });
+
+ toolbox.open().then(() => {
+ deferred.resolve(toolbox);
+ this.emit("toolbox-ready", toolbox);
+ });
+ });
}
return deferred.promise;
},
/**
* Return the toolbox for a given target.
*
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/toolbox-wrapper.js
@@ -0,0 +1,194 @@
+const Services = require("Services");
+const { Ci } = require("chrome");
+const {LocalizationHelper} = require("devtools/shared/l10n");
+const L10N = new LocalizationHelper("devtools/locale/toolbox.properties");
+
+loader.lazyRequireGetter(this, "Toolbox", "devtools/client/framework/toolbox", true);
+loader.lazyRequireGetter(this, "Hosts", "devtools/client/framework/toolbox-hosts", true);
+
+/**
+ * Implement a wrapper on the chrome side to setup a Toolbox within Firefox UI.
+ *
+ * This components handles iframe creation within Firefox, in which we are loading
+ * the toolbox document. Then both the chrome and the toolbox document communicate
+ * via "message" events.
+ *
+ * Messages sent by the toolbox to the chrome:
+ * - switch-host: order to display the toolbox in another host (side, bottom or window)
+ *
+ * Messages sent by the chrome to the toolbox:
+ * - host-will-change: tells the toolbox document that the host is about to change
+ */
+
+const LAST_HOST = "devtools.toolbox.host";
+let ID_COUNTER = 1;
+
+function ToolboxWrapper(target, hostType, hostOptions) {
+ this.target = target;
+
+ this.frameId = ID_COUNTER++;
+
+ if (!hostType) {
+ hostType = Services.prefs.getCharPref(LAST_HOST);
+ }
+ this.onHostMinimized = this.onHostMinimized.bind(this);
+ this.onHostMaximized = this.onHostMaximized.bind(this);
+ this.host = this.createHost(hostType, hostOptions);
+}
+
+ToolboxWrapper.prototype = {
+ create(toolId) {
+ return this.host.create()
+ .then(() => {
+ this.host.frame.setAttribute("aria-label", L10N.getStr("toolbox.label"));
+ this.host.frame.ownerDocument.defaultView.addEventListener("message", this);
+ this.host.frame.addEventListener("unload", this);
+
+ let toolbox = new Toolbox(this.target, toolId, this.host.type, this.host.frame.contentWindow, this.frameId);
+
+ // Prevent reloading the toolbox when loading the tools in a tab (e.g. from about:debugging)
+ if (!this.host.frame.contentWindow.location.href.startsWith("about:devtools-toolbox")) {
+ this.host.frame.setAttribute("src", "about:devtools-toolbox");
+ }
+
+ return toolbox;
+ });
+ },
+
+ handleEvent(event) {
+ switch(event.type) {
+ case "message":
+ this.onMessage(event);
+ break;
+ case "unload":
+ if (event.target.location.href == "about:blank") {
+ break;
+ }
+ this.destroy();
+ break;
+ }
+ },
+
+ onMessage(event) {
+ if (!event.data) return;
+ // Toolbox document is still chrome and disallow identifying message
+ // origin via event.source as it is null. So use a custom id.
+ if (event.data.frameId != this.frameId) {
+ return;
+ }
+ switch (event.data.name) {
+ case "switch-host":
+ this.switchHost(event.data.hostType);
+ break;
+ case "maximize-host":
+ this.host.maximize();
+ break;
+ case "raise-host":
+ this.host.raise();
+ break;
+ case "toggle-minimize-mode":
+ this.host.toggleMinimizeMode(event.data.toolbarHeight);
+ break;
+ case "set-host-title":
+ this.host.setTitle(event.data.title);
+ break;
+ case "destroy-host":
+ this.destroy();
+ break;
+ }
+ },
+
+ postMessage(data) {
+ let window = this.host.frame.contentWindow;
+ window.postMessage(data, "*");
+ },
+
+ destroy() {
+ this.destroyHost();
+ this.host = null;
+ this.target = null;
+ },
+
+ /**
+ * Create a host object based on the given host type.
+ *
+ * Warning: some hosts require that the toolbox target provides a reference to
+ * the attached tab. Not all Targets have a tab property - make sure you
+ * correctly mix and match hosts and targets.
+ *
+ * @param {string} hostType
+ * The host type of the new host object
+ *
+ * @return {Host} host
+ * The created host object
+ */
+ createHost(hostType, options) {
+ if (!Hosts[hostType]) {
+ throw new Error("Unknown hostType: " + hostType);
+ }
+
+ let newHost = new Hosts[hostType](this.target.tab, options);
+ // Update the label and icon when the state changes.
+ newHost.on("minimized", this.onHostMinimized);
+ newHost.on("maximized", this.onHostMaximized);
+ return newHost;
+ },
+
+ onHostMinimized() {
+ this.postMessage({
+ name: "host-minimized"
+ });
+ },
+ onHostMaximized() {
+ this.postMessage({
+ name: "host-maximized"
+ });
+ },
+
+ switchHost(hostType) {
+ let iframe = this.host.frame;
+ let newHost = this.createHost(hostType);
+ return newHost.create().then(newIframe => {
+ // change toolbox document's parent to the new host
+ newIframe.QueryInterface(Ci.nsIFrameLoaderOwner);
+ newIframe.swapFrameLoaders(iframe);
+
+ // See bug 1022726, most probably because of swapFrameLoaders we need to
+ // first focus the window here, and then once again further from
+ // toolbox.js to make sure focus actually happens.
+ iframe.contentWindow.focus();
+
+ this.destroyHost();
+
+ this.host = newHost;
+ this.host.setTitle(this.host.frame.contentWindow.document.title);
+ this.host.frame.ownerDocument.defaultView.addEventListener("message", this);
+ this.host.frame.addEventListener("unload", this);
+
+ if (hostType != Toolbox.HostType.CUSTOM) {
+ Services.prefs.setCharPref(LAST_HOST, hostType);
+ }
+
+ // Tell the toolbox the host changed
+ this.postMessage({
+ name: "switched-host",
+ hostType
+ });
+ });
+ },
+
+ /**
+ * Destroy the current host, and remove event listeners from its frame.
+ *
+ * @return {promise} to be resolved when the host is destroyed.
+ */
+ destroyHost() {
+ this.host.frame.ownerDocument.defaultView.removeEventListener("message", this);
+ this.host.frame.removeEventListener("unload", this);
+
+ this.host.off("minimized", this.onHostMinimized);
+ this.host.off("maximized", this.onHostMaximized);
+ return this.host.destroy();
+ }
+};
+exports.ToolboxWrapper = ToolboxWrapper;
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -35,18 +35,16 @@ const { BrowserLoader } =
const {LocalizationHelper} = require("devtools/shared/l10n");
const L10N = new LocalizationHelper("devtools/locale/toolbox.properties");
loader.lazyRequireGetter(this, "CommandUtils",
"devtools/client/shared/developer-toolbar", true);
loader.lazyRequireGetter(this, "getHighlighterUtils",
"devtools/client/framework/toolbox-highlighter-utils", true);
-loader.lazyRequireGetter(this, "Hosts",
- "devtools/client/framework/toolbox-hosts", true);
loader.lazyRequireGetter(this, "Selection",
"devtools/client/framework/selection", true);
loader.lazyRequireGetter(this, "InspectorFront",
"devtools/shared/fronts/inspector", true);
loader.lazyRequireGetter(this, "flags",
"devtools/shared/flags");
loader.lazyRequireGetter(this, "showDoorhanger",
"devtools/client/shared/doorhanger", true);
@@ -98,22 +96,22 @@ const ToolboxButtons = exports.ToolboxBu
* the iframes where the tool panels will be living in.
*
* @param {object} target
* The object the toolbox is debugging.
* @param {string} selectedTool
* Tool to select initially
* @param {Toolbox.HostType} hostType
* Type of host that will host the toolbox (e.g. sidebar, window)
- * @param {object} hostOptions
- * Options for host specifically
*/
-function Toolbox(target, selectedTool, hostType, hostOptions) {
+function Toolbox(target, selectedTool, hostType, contentWindow, frameId) {
this._target = target;
- this._win = null;
+ this._win = contentWindow;
+ this.frameId = frameId;
+
this._toolPanels = new Map();
this._telemetry = new Telemetry();
if (Services.prefs.getBoolPref("devtools.sourcemap.locations.enabled")) {
this._sourceMapService = new SourceMapService(this._target);
}
this._initInspector = null;
this._inspector = null;
@@ -131,39 +129,36 @@ function Toolbox(target, selectedTool, h
this._splitConsoleOnKeypress = this._splitConsoleOnKeypress.bind(this);
this.destroy = this.destroy.bind(this);
this.highlighterUtils = getHighlighterUtils(this);
this._highlighterReady = this._highlighterReady.bind(this);
this._highlighterHidden = this._highlighterHidden.bind(this);
this._prefChanged = this._prefChanged.bind(this);
this._saveSplitConsoleHeight = this._saveSplitConsoleHeight.bind(this);
this._onFocus = this._onFocus.bind(this);
+ this._onHostMessage = this._onHostMessage.bind(this);
this._showDevEditionPromo = this._showDevEditionPromo.bind(this);
this._updateTextboxMenuItems = this._updateTextboxMenuItems.bind(this);
this._onBottomHostMinimized = this._onBottomHostMinimized.bind(this);
this._onBottomHostMaximized = this._onBottomHostMaximized.bind(this);
this._onToolSelectWhileMinimized = this._onToolSelectWhileMinimized.bind(this);
this._onPerformanceFrontEvent = this._onPerformanceFrontEvent.bind(this);
this._onBottomHostWillChange = this._onBottomHostWillChange.bind(this);
this._toggleMinimizeMode = this._toggleMinimizeMode.bind(this);
this._onTabbarFocus = this._onTabbarFocus.bind(this);
this._onTabbarArrowKeypress = this._onTabbarArrowKeypress.bind(this);
this._target.on("close", this.destroy);
- if (!hostType) {
- hostType = Services.prefs.getCharPref(this._prefs.LAST_HOST);
- }
if (!selectedTool) {
selectedTool = Services.prefs.getCharPref(this._prefs.LAST_TOOL);
}
this._defaultToolId = selectedTool;
- this._hostOptions = hostOptions;
- this._host = this._createHost(hostType, hostOptions);
+ this._hostType = hostType;
EventEmitter.decorate(this);
this._target.on("navigate", this._refreshHostTitle);
this._target.on("frame-update", this._updateFrames);
this.on("host-changed", this._refreshHostTitle);
this.on("select", this._refreshHostTitle);
@@ -185,17 +180,16 @@ Toolbox.HostType = {
WINDOW: "window",
CUSTOM: "custom"
};
Toolbox.prototype = {
_URL: "about:devtools-toolbox",
_prefs: {
- LAST_HOST: "devtools.toolbox.host",
LAST_TOOL: "devtools.toolbox.selectedTool",
SIDE_ENABLED: "devtools.toolbox.sideEnabled",
PREVIOUS_HOST: "devtools.toolbox.previousHost"
},
currentToolId: null,
lastUsedToolId: null,
@@ -265,17 +259,17 @@ Toolbox.prototype = {
return this._threadClient;
},
/**
* Get/alter the host of a Toolbox, i.e. is it in browser or in a separate
* tab. See HostType for more details.
*/
get hostType() {
- return this._host.type;
+ return this._hostType;
},
/**
* Shortcut to the window containing the toolbox UI
*/
get win() {
return this._win;
},
@@ -347,37 +341,28 @@ Toolbox.prototype = {
this.doc.querySelector("#toolbox-panel-iframe-webconsole").contentWindow;
},
/**
* Open the toolbox
*/
open: function () {
return Task.spawn(function* () {
- let iframe = yield this._host.create();
- this._win = iframe.contentWindow;
-
- let domReady = defer();
-
- // Prevent reloading the document when the toolbox is opened in a tab
- let location = iframe.contentWindow.location.href;
- if (!location.startsWith(this._URL)) {
- iframe.setAttribute("src", this._URL);
- } else {
- // Update the URL so that onceDOMReady watch for the right url.
- this._URL = location;
- }
-
this.browserRequire = BrowserLoader({
window: this.doc.defaultView,
useOnlyShared: true
}).require;
- iframe.setAttribute("aria-label", L10N.getStr("toolbox.label"));
- let domHelper = new DOMHelpers(iframe.contentWindow);
+ if (this.win.location.href.startsWith(this._URL)) {
+ // Update the URL so that onceDOMReady watch for the right url.
+ this._URL = this.win.location.href;
+ }
+
+ let domReady = defer();
+ let domHelper = new DOMHelpers(this.win);
domHelper.onceDOMReady(() => {
domReady.resolve();
}, this._URL);
// Optimization: fire up a few other things before waiting on
// the iframe being ready (makes startup faster)
// Load the toolbox-level actor fronts and utilities now
@@ -618,27 +603,49 @@ Toolbox.prototype = {
(name, event) => {
this.switchToPreviousHost();
event.preventDefault();
});
this.doc.addEventListener("keypress", this._splitConsoleOnKeypress, false);
this.doc.addEventListener("focus", this._onFocus, true);
this.win.addEventListener("unload", this.destroy);
+ this.win.addEventListener("message", this._onHostMessage);
},
_removeHostListeners: function () {
// The host iframe's contentDocument may already be gone.
if (this.doc) {
this.doc.removeEventListener("keypress", this._splitConsoleOnKeypress, false);
this.doc.removeEventListener("focus", this._onFocus, true);
this.win.removeEventListener("unload", this.destroy);
+ this.win.removeEventListener("message", this._onHostMessage);
}
},
+ // Called whenever the host, on the chrome side, send a message
+ _onHostMessage: function (event) {
+ if (!event.data) return;
+ switch (event.data.name) {
+ case "switched-host":
+ this._onSwitchedHost(event.data);
+ break;
+ case "host-minimized":
+ if (this.hostType == Toolbox.HostType.BOTTOM) {
+ this._onBottomHostMinimized();
+ }
+ break;
+ case "host-maximized":
+ if (this.hostType == Toolbox.HostType.BOTTOM) {
+ this._onBottomHostMaximized();
+ }
+ break;
+ };
+ },
+
_registerOverlays: function () {
registerHarOverlay(this);
},
_saveSplitConsoleHeight: function () {
Services.prefs.setIntPref(SPLITCONSOLE_HEIGHT_PREF,
this.webconsolePanel.height);
},
@@ -789,19 +796,16 @@ Toolbox.prototype = {
the UI for it, and until bug 1173849 is fixed too. */
minimizeBtn.setAttribute("hidden", "true");
minimizeBtn.addEventListener("click", this._toggleMinimizeMode);
dockBox.appendChild(minimizeBtn);
// Show the button in its maximized state.
this._onBottomHostMaximized();
- // Update the label and icon when the state changes.
- this._host.on("minimized", this._onBottomHostMinimized);
- this._host.on("maximized", this._onBottomHostMaximized);
// Maximize again when a tool gets selected.
this.on("before-select", this._onToolSelectWhileMinimized);
// Maximize and stop listening before the host type changes.
this.once("host-will-change", this._onBottomHostWillChange);
}
if (this.hostType == Toolbox.HostType.WINDOW) {
this.closeButton.setAttribute("hidden", "true");
@@ -852,37 +856,53 @@ Toolbox.prototype = {
btn.className = "maximized";
btn.setAttribute("title",
L10N.getStr("toolboxDockButtons.bottom.minimize") + " " +
this._getMinimizeButtonShortcutTooltip());
},
_onToolSelectWhileMinimized: function () {
- this._host.maximize();
+ this.postMessage({
+ name: "maximize-host"
+ });
+ },
+
+ postMessage: function(msg) {
+ // We sometime try to send messages in middle of destroy(), where the
+ // toolbox iframe may already be detached and no longer have a parent.
+ if (this.win.parent) {
+ // Toolbox document is still chrome and disallow identifying message
+ // origin via event.source as it is null. So use a custom id.
+ msg.frameId = this.frameId;
+ this.win.parent.postMessage(msg, "*");
+ }
},
_onBottomHostWillChange: function () {
- this._host.maximize();
+ this.postMessage({
+ name: "maximize-host"
+ });
- this._host.off("minimized", this._onBottomHostMinimized);
- this._host.off("maximized", this._onBottomHostMaximized);
this.off("before-select", this._onToolSelectWhileMinimized);
},
_toggleMinimizeMode: function () {
if (this.hostType !== Toolbox.HostType.BOTTOM) {
return;
}
// Calculate the height to which the host should be minimized so the
// tabbar is still visible.
let toolbarHeight = this.tabbar.getBoxQuads({box: "content"})[0].bounds
.height;
- this._host.toggleMinimizeMode(toolbarHeight);
+ this.postMessage({
+ name: "toggle-minimize-mode",
+ toolbarHeight
+ });
},
/**
* Add tabs to the toolbox UI for registered tools
*/
_buildTabs: function () {
for (let definition of gDevTools.getToolDefinitionArray()) {
this._buildTabForTool(definition);
@@ -1578,31 +1598,36 @@ Toolbox.prototype = {
let tab = this.doc.getElementById("toolbox-tab-" + id);
tab && tab.removeAttribute("highlighted");
},
/**
* Raise the toolbox host.
*/
raise: function () {
- this._host.raise();
+ this.postMessage({
+ name: "raise-host"
+ });
},
/**
* Refresh the host's title.
*/
_refreshHostTitle: function () {
let title;
if (this.target.name && this.target.name != this.target.url) {
title = L10N.getFormatStr("toolbox.titleTemplate2", this.target.name,
this.target.url);
} else {
title = L10N.getFormatStr("toolbox.titleTemplate1", this.target.url);
}
- this._host.setTitle(title);
+ this.postMessage({
+ name: "set-host-title",
+ title
+ });
},
// Returns an instance of the preference actor
get _preferenceFront() {
return this.target.root.then(rootForm => {
return new getPreferenceFront(this.target.client, rootForm);
});
},
@@ -1771,40 +1796,16 @@ Toolbox.prototype = {
// If non-top level frame is selected the toolbar button is
// marked as 'checked' indicating that a child frame is active.
if (!topFrameSelected && this.selectedFrameId) {
button.setAttribute("checked", "true");
}
},
/**
- * Create a host object based on the given host type.
- *
- * Warning: some hosts require that the toolbox target provides a reference to
- * the attached tab. Not all Targets have a tab property - make sure you
- * correctly mix and match hosts and targets.
- *
- * @param {string} hostType
- * The host type of the new host object
- *
- * @return {Host} host
- * The created host object
- */
- _createHost: function (hostType, options) {
- if (!Hosts[hostType]) {
- throw new Error("Unknown hostType: " + hostType);
- }
-
- // clean up the toolbox if its window is closed
- let newHost = new Hosts[hostType](this.target.tab, options);
- newHost.on("window-closed", this.destroy);
- return newHost;
- },
-
- /**
* Switch to the last used host for the toolbox UI.
* This is determined by the devtools.toolbox.previousHost pref.
*/
switchToPreviousHost: function () {
let hostType = Services.prefs.getCharPref(this._prefs.PREVIOUS_HOST);
// Handle the case where the previous host happens to match the current
// host. If so, switch to bottom if it's not already used, and side if not.
@@ -1834,43 +1835,41 @@ Toolbox.prototype = {
this.emit("host-will-change", hostType);
// If we call swapFrameLoaders() when a tool if focused it leaves the
// browser in a state where it thinks that the tool is focused but in
// reality the content area is focused. Blurring the tool before calling
// swapFrameLoaders() works around this issue.
this.focusTool(this.currentToolId, false);
- let newHost = this._createHost(hostType);
- return newHost.create().then(iframe => {
- // change toolbox document's parent to the new host
- iframe.QueryInterface(Ci.nsIFrameLoaderOwner);
- iframe.swapFrameLoaders(this._host.frame);
+ // Host code on the chrome side will send back a message once the host
+ // switched
+ this.postMessage({
+ name: "switch-host",
+ hostType
+ });
- this._host.off("window-closed", this.destroy);
- this.destroyHost();
-
- let prevHostType = this._host.type;
- this._host = newHost;
+ return this.once("host-changed");
+ },
- if (this.hostType != Toolbox.HostType.CUSTOM) {
- Services.prefs.setCharPref(this._prefs.LAST_HOST, this._host.type);
- Services.prefs.setCharPref(this._prefs.PREVIOUS_HOST, prevHostType);
- }
+ _onSwitchedHost: function ({ hostType }) {
+ if (hostType != Toolbox.HostType.CUSTOM) {
+ Services.prefs.setCharPref(this._prefs.PREVIOUS_HOST, this.hostType);
+ }
- this._buildDockButtons();
- this._addKeysToWindow();
+ this._hostType = hostType;
- // Focus the tool to make sure keyboard shortcuts work straight away.
- this.focusTool(this.currentToolId, true);
+ this._buildDockButtons();
+ this._addKeysToWindow();
- this.emit("host-changed");
+ // Focus the tool to make sure keyboard shortcuts work straight away.
+ this.focusTool(this.currentToolId, true);
- this._telemetry.log(HOST_HISTOGRAM, this._getTelemetryHostId());
- });
+ this.emit("host-changed");
+ this._telemetry.log(HOST_HISTOGRAM, this._getTelemetryHostId());
},
/**
* Return if the tool is available as a tab (i.e. if it's checked
* in the options panel). This is different from Toolbox.getPanel -
* a tool could be registered but not yet opened in which case
* isToolRegistered would return true but getPanel would return false.
*/
@@ -2030,26 +2029,16 @@ Toolbox.prototype = {
*
* @return The notification box component.
*/
getNotificationBox: function () {
return this.notificationBox;
},
/**
- * Destroy the current host, and remove event listeners from its frame.
- *
- * @return {promise} to be resolved when the host is destroyed.
- */
- destroyHost: function () {
- this._removeHostListeners();
- return this._host.destroy();
- },
-
- /**
* Remove all UI elements, detach from target and clear up
*/
destroy: function () {
// If several things call destroy then we give them all the same
// destruction promise so we're sure to destroy only once
if (this._destroyer) {
return this._destroyer;
}
@@ -2135,20 +2124,26 @@ Toolbox.prototype = {
this._telemetry.toolClosed("toolbox");
this._telemetry.destroy();
// Finish all outstanding tasks (which means finish destroying panels and
// then destroying the host, successfully or not) before destroying the
// target.
this._destroyer = settleAll(outstanding)
.catch(console.error)
- .then(() => this.destroyHost())
- .catch(console.error)
.then(() => {
- this._win = null;
+ this._removeHostListeners();
+
+ // Tell the chrome the toolbox is almost destroyed and we can start
+ // removing the toolbox from Firefox UI. Do it exactly here for
+ // historical reason. If we do it sooner or later we appear to leak
+ // the toolbox in many tests.
+ this.postMessage({
+ name: "destroy-host"
+ });
// Targets need to be notified that the toolbox is being torn down.
// This is done after other destruction tasks since it may tear down
// fronts and the debugger transport which earlier destroy methods may
// require to complete.
if (!this._target) {
return null;
}
@@ -2158,16 +2153,17 @@ Toolbox.prototype = {
target.off("close", this.destroy);
return target.destroy();
}, console.error).then(() => {
this.emit("destroyed");
// Free _host after the call to destroyed in order to let a chance
// to destroyed listeners to still query toolbox attributes
this._host = null;
+ this._win = null;
this._toolPanels.clear();
// Force GC to prevent long GC pauses when running tests and to free up
// memory in general when the toolbox is closed.
if (flags.testing) {
win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.garbageCollect();