gecko-dev/devtools/client/framework/toolbox-hosts.js
Paolo Amadini 4270e25239 Bug 1505791 - Separate the browser panel from the notification box. r=dao,bgrins
This clarifies the intention of each caller, and opens up the possibility of converting the notificationbox element to a class that creates the DOM nodes on demand.

Differential Revision: https://phabricator.services.mozilla.com/D10577

--HG--
extra : rebase_source : e311dd7144cf9f7cd513761f97bb3dd0a35dce71
extra : source : 9532194794ba9c87025da92cf70f76c21a277220
2018-11-08 15:31:37 +00:00

390 lines
9.8 KiB
JavaScript

/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const EventEmitter = require("devtools/shared/event-emitter");
const promise = require("promise");
const Services = require("Services");
const {DOMHelpers} = require("resource://devtools/client/shared/DOMHelpers.jsm");
loader.lazyRequireGetter(this, "gDevToolsBrowser", "devtools/client/framework/devtools-browser", true);
/* A host should always allow this much space for the page to be displayed.
* There is also a min-height on the browser, but we still don't want to set
* frame.height to be larger than that, since it can cause problems with
* resizing the toolbox and panel layout. */
const MIN_PAGE_SIZE = 25;
/**
* A toolbox host represents an object that contains a toolbox (e.g. the
* sidebar or a separate window). Any host object should implement the
* following functions:
*
* create() - create the UI and emit a 'ready' event when the UI is ready to use
* destroy() - destroy the host's UI
*/
/**
* Host object for the dock on the bottom of the browser
*/
function BottomHost(hostTab) {
this.hostTab = hostTab;
EventEmitter.decorate(this);
}
BottomHost.prototype = {
type: "bottom",
heightPref: "devtools.toolbox.footer.height",
/**
* Create a box at the bottom of the host tab.
*/
create: async function() {
await gDevToolsBrowser.loadBrowserStyleSheet(this.hostTab.ownerGlobal);
const gBrowser = this.hostTab.ownerDocument.defaultView.gBrowser;
const ownerDocument = gBrowser.ownerDocument;
this._browserContainer =
gBrowser.getBrowserContainer(this.hostTab.linkedBrowser);
this._splitter = ownerDocument.createXULElement("splitter");
this._splitter.setAttribute("class", "devtools-horizontal-splitter");
// Avoid resizing notification containers
this._splitter.setAttribute("resizebefore", "flex");
this.frame = ownerDocument.createXULElement("iframe");
this.frame.flex = 1; // Required to be able to shrink when the window shrinks
this.frame.className = "devtools-toolbox-bottom-iframe";
this.frame.height = Math.min(
Services.prefs.getIntPref(this.heightPref),
this._browserContainer.clientHeight - MIN_PAGE_SIZE
);
this._browserContainer.appendChild(this._splitter);
this._browserContainer.appendChild(this.frame);
this.frame.tooltip = "aHTMLTooltip";
// we have to load something so we can switch documents if we have to
this.frame.setAttribute("src", "about:blank");
const frame = await new Promise(resolve => {
const domHelper = new DOMHelpers(this.frame.contentWindow);
const frameLoad = () => {
this.emit("ready", this.frame);
resolve(this.frame);
};
domHelper.onceDOMReady(frameLoad);
focusTab(this.hostTab);
});
return frame;
},
/**
* Raise the host.
*/
raise: function() {
focusTab(this.hostTab);
},
/**
* Set the toolbox title.
* Nothing to do for this host type.
*/
setTitle: function() {},
/**
* Destroy the bottom dock.
*/
destroy: function() {
if (!this._destroyed) {
this._destroyed = true;
Services.prefs.setIntPref(this.heightPref, this.frame.height);
this._browserContainer.removeChild(this._splitter);
this._browserContainer.removeChild(this.frame);
this.frame = null;
this._browserContainer = null;
this._splitter = null;
}
return promise.resolve(null);
},
};
/**
* Base Host object for the in-browser sidebar
*/
class SidebarHost {
constructor(hostTab, type) {
this.hostTab = hostTab;
this.type = type;
this.widthPref = "devtools.toolbox.sidebar.width";
EventEmitter.decorate(this);
}
/**
* Create a box in the sidebar of the host tab.
*/
async create() {
await gDevToolsBrowser.loadBrowserStyleSheet(this.hostTab.ownerGlobal);
const gBrowser = this.hostTab.ownerDocument.defaultView.gBrowser;
const ownerDocument = gBrowser.ownerDocument;
this._browserContainer = gBrowser.getBrowserContainer(this.hostTab.linkedBrowser);
this._browserPanel = gBrowser.getPanel(this.hostTab.linkedBrowser);
this._splitter = ownerDocument.createXULElement("splitter");
this._splitter.setAttribute("class", "devtools-side-splitter");
this.frame = ownerDocument.createXULElement("iframe");
this.frame.flex = 1; // Required to be able to shrink when the window shrinks
this.frame.className = "devtools-toolbox-side-iframe";
this.frame.width = Math.min(
Services.prefs.getIntPref(this.widthPref),
this._browserPanel.clientWidth - MIN_PAGE_SIZE
);
// We should consider the direction when changing the dock position.
const topWindow = this.hostTab.ownerDocument.defaultView.top;
const topDoc = topWindow.document.documentElement;
const isLTR = topWindow.getComputedStyle(topDoc).direction === "ltr";
if (isLTR && this.type == "right" ||
!isLTR && this.type == "left") {
this._browserPanel.appendChild(this._splitter);
this._browserPanel.appendChild(this.frame);
} else {
this._browserPanel.insertBefore(this.frame, this._browserContainer);
this._browserPanel.insertBefore(this._splitter, this._browserContainer);
}
this.frame.tooltip = "aHTMLTooltip";
this.frame.setAttribute("src", "about:blank");
const frame = await new Promise(resolve => {
const domHelper = new DOMHelpers(this.frame.contentWindow);
const frameLoad = () => {
this.emit("ready", this.frame);
resolve(this.frame);
};
domHelper.onceDOMReady(frameLoad);
focusTab(this.hostTab);
});
return frame;
}
/**
* Raise the host.
*/
raise() {
focusTab(this.hostTab);
}
/**
* Set the toolbox title.
* Nothing to do for this host type.
*/
setTitle() {}
/**
* Destroy the sidebar.
*/
destroy() {
if (!this._destroyed) {
this._destroyed = true;
Services.prefs.setIntPref(this.widthPref, this.frame.width);
this._browserPanel.removeChild(this._splitter);
this._browserPanel.removeChild(this.frame);
}
return promise.resolve(null);
}
}
/**
* Host object for the in-browser left sidebar
*/
class LeftHost extends SidebarHost {
constructor(hostTab) {
super(hostTab, "left");
}
}
/**
* Host object for the in-browser right sidebar
*/
class RightHost extends SidebarHost {
constructor(hostTab) {
super(hostTab, "right");
}
}
/**
* Host object for the toolbox in a separate window
*/
function WindowHost() {
this._boundUnload = this._boundUnload.bind(this);
EventEmitter.decorate(this);
}
WindowHost.prototype = {
type: "window",
WINDOW_URL: "chrome://devtools/content/framework/toolbox-window.xul",
/**
* Create a new xul window to contain the toolbox.
*/
create: function() {
return new Promise(resolve => {
const flags = "chrome,centerscreen,resizable,dialog=no";
const win = Services.ww.openWindow(null, this.WINDOW_URL, "_blank",
flags, null);
const frameLoad = () => {
win.removeEventListener("load", frameLoad, true);
win.focus();
this.frame = win.document.getElementById("toolbox-iframe");
this.emit("ready", this.frame);
resolve(this.frame);
};
win.addEventListener("load", frameLoad, true);
win.addEventListener("unload", this._boundUnload);
this._window = win;
});
},
/**
* Catch the user closing the window.
*/
_boundUnload: function(event) {
if (event.target.location != this.WINDOW_URL) {
return;
}
this._window.removeEventListener("unload", this._boundUnload);
this.emit("window-closed");
},
/**
* Raise the host.
*/
raise: function() {
this._window.focus();
},
/**
* Set the toolbox title.
*/
setTitle: function(title) {
this._window.document.title = title;
},
/**
* Destroy the window.
*/
destroy: function() {
if (!this._destroyed) {
this._destroyed = true;
this._window.removeEventListener("unload", this._boundUnload);
this._window.close();
}
return promise.resolve(null);
},
};
/**
* Host object for the toolbox in its own tab
*/
function CustomHost(hostTab, options) {
this.frame = options.customIframe;
this.uid = options.uid;
EventEmitter.decorate(this);
}
CustomHost.prototype = {
type: "custom",
_sendMessageToTopWindow: function(msg, data) {
// It's up to the custom frame owner (parent window) to honor
// "close" or "raise" instructions.
const topWindow = this.frame.ownerDocument.defaultView;
if (!topWindow) {
return;
}
const message = {
name: "toolbox-" + msg,
uid: this.uid,
data,
};
topWindow.postMessage(message, "*");
},
/**
* Create a new xul window to contain the toolbox.
*/
create: function() {
return promise.resolve(this.frame);
},
/**
* Raise the host.
*/
raise: function() {
this._sendMessageToTopWindow("raise");
},
/**
* Set the toolbox title.
*/
setTitle: function(title) {
this._sendMessageToTopWindow("title", { value: title });
},
/**
* Destroy the window.
*/
destroy: function() {
if (!this._destroyed) {
this._destroyed = true;
this._sendMessageToTopWindow("close");
}
return promise.resolve(null);
},
};
/**
* Switch to the given tab in a browser and focus the browser window
*/
function focusTab(tab) {
const browserWindow = tab.ownerDocument.defaultView;
browserWindow.focus();
browserWindow.gBrowser.selectedTab = tab;
}
exports.Hosts = {
"bottom": BottomHost,
"left": LeftHost,
"right": RightHost,
"window": WindowHost,
"custom": CustomHost,
};