123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425 |
- /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
- /* 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 defer = require("devtools/shared/defer");
- const Services = require("Services");
- const {DOMHelpers} = require("resource://devtools/client/shared/DOMHelpers.jsm");
- loader.lazyRequireGetter(this, "system", "devtools/shared/system");
- /* 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
- */
- exports.Hosts = {
- "bottom": BottomHost,
- "side": SidebarHost,
- "window": WindowHost,
- "custom": CustomHost
- };
- /**
- * 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: function () {
- let deferred = defer();
- let gBrowser = this.hostTab.ownerDocument.defaultView.gBrowser;
- let ownerDocument = gBrowser.ownerDocument;
- this._nbox = gBrowser.getNotificationBox(this.hostTab.linkedBrowser);
- this._splitter = ownerDocument.createElement("splitter");
- this._splitter.setAttribute("class", "devtools-horizontal-splitter");
- // Avoid resizing notification containers
- this._splitter.setAttribute("resizebefore", "flex");
- this.frame = ownerDocument.createElement("iframe");
- this.frame.className = "devtools-toolbox-bottom-iframe";
- this.frame.height = Math.min(
- Services.prefs.getIntPref(this.heightPref),
- this._nbox.clientHeight - MIN_PAGE_SIZE
- );
- this._nbox.appendChild(this._splitter);
- this._nbox.appendChild(this.frame);
- let frameLoad = () => {
- this.emit("ready", this.frame);
- deferred.resolve(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");
- let domHelper = new DOMHelpers(this.frame.contentWindow);
- domHelper.onceDOMReady(frameLoad);
- focusTab(this.hostTab);
- return deferred.promise;
- },
- /**
- * Raise the host.
- */
- raise: function () {
- focusTab(this.hostTab);
- },
- /**
- * Minimize this host so that only the toolbox tabbar remains visible.
- * @param {Number} height The height to minimize to. Defaults to 0, which
- * means that the toolbox won't be visible at all once minimized.
- */
- minimize: function (height = 0) {
- if (this.isMinimized) {
- return;
- }
- this.isMinimized = true;
- let onTransitionEnd = event => {
- if (event.propertyName !== "margin-bottom") {
- // Ignore transitionend on unrelated properties.
- return;
- }
- this.frame.removeEventListener("transitionend", onTransitionEnd);
- this.emit("minimized");
- };
- this.frame.addEventListener("transitionend", onTransitionEnd);
- this.frame.style.marginBottom = -this.frame.height + height + "px";
- this._splitter.classList.add("disabled");
- },
- /**
- * If the host was minimized before, maximize it again (the host will be
- * maximized to the height it previously had).
- */
- maximize: function () {
- if (!this.isMinimized) {
- return;
- }
- this.isMinimized = false;
- let onTransitionEnd = event => {
- if (event.propertyName !== "margin-bottom") {
- // Ignore transitionend on unrelated properties.
- return;
- }
- this.frame.removeEventListener("transitionend", onTransitionEnd);
- this.emit("maximized");
- };
- this.frame.addEventListener("transitionend", onTransitionEnd);
- this.frame.style.marginBottom = "0";
- this._splitter.classList.remove("disabled");
- },
- /**
- * Toggle the minimize mode.
- * @param {Number} minHeight The height to minimize to.
- */
- toggleMinimizeMode: function (minHeight) {
- this.isMinimized ? this.maximize() : this.minimize(minHeight);
- },
- /**
- * 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._nbox.removeChild(this._splitter);
- this._nbox.removeChild(this.frame);
- this.frame = null;
- this._nbox = null;
- this._splitter = null;
- }
- return promise.resolve(null);
- }
- };
- /**
- * Host object for the in-browser sidebar
- */
- function SidebarHost(hostTab) {
- this.hostTab = hostTab;
- EventEmitter.decorate(this);
- }
- SidebarHost.prototype = {
- type: "side",
- widthPref: "devtools.toolbox.sidebar.width",
- /**
- * Create a box in the sidebar of the host tab.
- */
- create: function () {
- let deferred = defer();
- let gBrowser = this.hostTab.ownerDocument.defaultView.gBrowser;
- let ownerDocument = gBrowser.ownerDocument;
- this._sidebar = gBrowser.getSidebarContainer(this.hostTab.linkedBrowser);
- this._splitter = ownerDocument.createElement("splitter");
- this._splitter.setAttribute("class", "devtools-side-splitter");
- this.frame = ownerDocument.createElement("iframe");
- this.frame.className = "devtools-toolbox-side-iframe";
- this.frame.width = Math.min(
- Services.prefs.getIntPref(this.widthPref),
- this._sidebar.clientWidth - MIN_PAGE_SIZE
- );
- this._sidebar.appendChild(this._splitter);
- this._sidebar.appendChild(this.frame);
- let frameLoad = () => {
- this.emit("ready", this.frame);
- deferred.resolve(this.frame);
- };
- this.frame.tooltip = "aHTMLTooltip";
- this.frame.setAttribute("src", "about:blank");
- let domHelper = new DOMHelpers(this.frame.contentWindow);
- domHelper.onceDOMReady(frameLoad);
- focusTab(this.hostTab);
- return deferred.promise;
- },
- /**
- * Raise the host.
- */
- raise: function () {
- focusTab(this.hostTab);
- },
- /**
- * Set the toolbox title.
- * Nothing to do for this host type.
- */
- setTitle: function () {},
- /**
- * Destroy the sidebar.
- */
- destroy: function () {
- if (!this._destroyed) {
- this._destroyed = true;
- Services.prefs.setIntPref(this.widthPref, this.frame.width);
- this._sidebar.removeChild(this._splitter);
- this._sidebar.removeChild(this.frame);
- }
- return promise.resolve(null);
- }
- };
- /**
- * 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 () {
- let deferred = defer();
- let flags = "chrome,centerscreen,resizable,dialog=no";
- let win = Services.ww.openWindow(null, this.WINDOW_URL, "_blank",
- flags, null);
- let frameLoad = () => {
- win.removeEventListener("load", frameLoad, true);
- win.focus();
- let key;
- if (system.constants.platform === "macosx") {
- key = win.document.getElementById("toolbox-key-toggle-osx");
- } else {
- key = win.document.getElementById("toolbox-key-toggle");
- }
- key.removeAttribute("disabled");
- this.frame = win.document.getElementById("toolbox-iframe");
- this.emit("ready", this.frame);
- deferred.resolve(this.frame);
- };
- win.addEventListener("load", frameLoad, true);
- win.addEventListener("unload", this._boundUnload);
- this._window = win;
- return deferred.promise;
- },
- /**
- * 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.
- let topWindow = this.frame.ownerDocument.defaultView;
- if (!topWindow) {
- return;
- }
- let json = {name: "toolbox-" + msg, uid: this.uid};
- if (data) {
- json.data = data;
- }
- topWindow.postMessage(JSON.stringify(json), "*");
- },
- /**
- * 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) {
- let browserWindow = tab.ownerDocument.defaultView;
- browserWindow.focus();
- browserWindow.gBrowser.selectedTab = tab;
- }
|