michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const {Cu} = require("chrome"); michael@0: const EventEmitter = require("devtools/toolkit/event-emitter"); michael@0: const {Promise: promise} = require("resource://gre/modules/Promise.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource:///modules/devtools/DOMHelpers.jsm"); michael@0: michael@0: /** michael@0: * A toolbox host represents an object that contains a toolbox (e.g. the michael@0: * sidebar or a separate window). Any host object should implement the michael@0: * following functions: michael@0: * michael@0: * create() - create the UI and emit a 'ready' event when the UI is ready to use michael@0: * destroy() - destroy the host's UI michael@0: */ michael@0: michael@0: exports.Hosts = { michael@0: "bottom": BottomHost, michael@0: "side": SidebarHost, michael@0: "window": WindowHost, michael@0: "custom": CustomHost michael@0: }; michael@0: michael@0: /** michael@0: * Host object for the dock on the bottom of the browser michael@0: */ michael@0: function BottomHost(hostTab) { michael@0: this.hostTab = hostTab; michael@0: michael@0: EventEmitter.decorate(this); michael@0: } michael@0: michael@0: BottomHost.prototype = { michael@0: type: "bottom", michael@0: michael@0: heightPref: "devtools.toolbox.footer.height", michael@0: michael@0: /** michael@0: * Create a box at the bottom of the host tab. michael@0: */ michael@0: create: function BH_create() { michael@0: let deferred = promise.defer(); michael@0: michael@0: let gBrowser = this.hostTab.ownerDocument.defaultView.gBrowser; michael@0: let ownerDocument = gBrowser.ownerDocument; michael@0: michael@0: this._splitter = ownerDocument.createElement("splitter"); michael@0: this._splitter.setAttribute("class", "devtools-horizontal-splitter"); michael@0: michael@0: this.frame = ownerDocument.createElement("iframe"); michael@0: this.frame.className = "devtools-toolbox-bottom-iframe"; michael@0: this.frame.height = Services.prefs.getIntPref(this.heightPref); michael@0: michael@0: this._nbox = gBrowser.getNotificationBox(this.hostTab.linkedBrowser); michael@0: this._nbox.appendChild(this._splitter); michael@0: this._nbox.appendChild(this.frame); michael@0: michael@0: let frameLoad = function() { michael@0: this.emit("ready", this.frame); michael@0: deferred.resolve(this.frame); michael@0: }.bind(this); michael@0: michael@0: this.frame.tooltip = "aHTMLTooltip"; michael@0: michael@0: // we have to load something so we can switch documents if we have to michael@0: this.frame.setAttribute("src", "about:blank"); michael@0: michael@0: let domHelper = new DOMHelpers(this.frame.contentWindow); michael@0: domHelper.onceDOMReady(frameLoad); michael@0: michael@0: focusTab(this.hostTab); michael@0: michael@0: return deferred.promise; michael@0: }, michael@0: michael@0: /** michael@0: * Raise the host. michael@0: */ michael@0: raise: function BH_raise() { michael@0: focusTab(this.hostTab); michael@0: }, michael@0: michael@0: /** michael@0: * Set the toolbox title. michael@0: */ michael@0: setTitle: function BH_setTitle(title) { michael@0: // Nothing to do for this host type. michael@0: }, michael@0: michael@0: /** michael@0: * Destroy the bottom dock. michael@0: */ michael@0: destroy: function BH_destroy() { michael@0: if (!this._destroyed) { michael@0: this._destroyed = true; michael@0: michael@0: Services.prefs.setIntPref(this.heightPref, this.frame.height); michael@0: this._nbox.removeChild(this._splitter); michael@0: this._nbox.removeChild(this.frame); michael@0: } michael@0: michael@0: return promise.resolve(null); michael@0: } michael@0: } michael@0: michael@0: michael@0: /** michael@0: * Host object for the in-browser sidebar michael@0: */ michael@0: function SidebarHost(hostTab) { michael@0: this.hostTab = hostTab; michael@0: michael@0: EventEmitter.decorate(this); michael@0: } michael@0: michael@0: SidebarHost.prototype = { michael@0: type: "side", michael@0: michael@0: widthPref: "devtools.toolbox.sidebar.width", michael@0: michael@0: /** michael@0: * Create a box in the sidebar of the host tab. michael@0: */ michael@0: create: function SH_create() { michael@0: let deferred = promise.defer(); michael@0: michael@0: let gBrowser = this.hostTab.ownerDocument.defaultView.gBrowser; michael@0: let ownerDocument = gBrowser.ownerDocument; michael@0: michael@0: this._splitter = ownerDocument.createElement("splitter"); michael@0: this._splitter.setAttribute("class", "devtools-side-splitter"); michael@0: michael@0: this.frame = ownerDocument.createElement("iframe"); michael@0: this.frame.className = "devtools-toolbox-side-iframe"; michael@0: this.frame.width = Services.prefs.getIntPref(this.widthPref); michael@0: michael@0: this._sidebar = gBrowser.getSidebarContainer(this.hostTab.linkedBrowser); michael@0: this._sidebar.appendChild(this._splitter); michael@0: this._sidebar.appendChild(this.frame); michael@0: michael@0: let frameLoad = function() { michael@0: this.emit("ready", this.frame); michael@0: deferred.resolve(this.frame); michael@0: }.bind(this); michael@0: michael@0: this.frame.tooltip = "aHTMLTooltip"; michael@0: this.frame.setAttribute("src", "about:blank"); michael@0: michael@0: let domHelper = new DOMHelpers(this.frame.contentWindow); michael@0: domHelper.onceDOMReady(frameLoad); michael@0: michael@0: focusTab(this.hostTab); michael@0: michael@0: return deferred.promise; michael@0: }, michael@0: michael@0: /** michael@0: * Raise the host. michael@0: */ michael@0: raise: function SH_raise() { michael@0: focusTab(this.hostTab); michael@0: }, michael@0: michael@0: /** michael@0: * Set the toolbox title. michael@0: */ michael@0: setTitle: function SH_setTitle(title) { michael@0: // Nothing to do for this host type. michael@0: }, michael@0: michael@0: /** michael@0: * Destroy the sidebar. michael@0: */ michael@0: destroy: function SH_destroy() { michael@0: if (!this._destroyed) { michael@0: this._destroyed = true; michael@0: michael@0: Services.prefs.setIntPref(this.widthPref, this.frame.width); michael@0: this._sidebar.removeChild(this._splitter); michael@0: this._sidebar.removeChild(this.frame); michael@0: } michael@0: michael@0: return promise.resolve(null); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Host object for the toolbox in a separate window michael@0: */ michael@0: function WindowHost() { michael@0: this._boundUnload = this._boundUnload.bind(this); michael@0: michael@0: EventEmitter.decorate(this); michael@0: } michael@0: michael@0: WindowHost.prototype = { michael@0: type: "window", michael@0: michael@0: WINDOW_URL: "chrome://browser/content/devtools/framework/toolbox-window.xul", michael@0: michael@0: /** michael@0: * Create a new xul window to contain the toolbox. michael@0: */ michael@0: create: function WH_create() { michael@0: let deferred = promise.defer(); michael@0: michael@0: let flags = "chrome,centerscreen,resizable,dialog=no"; michael@0: let win = Services.ww.openWindow(null, this.WINDOW_URL, "_blank", michael@0: flags, null); michael@0: michael@0: let frameLoad = function(event) { michael@0: win.removeEventListener("load", frameLoad, true); michael@0: win.focus(); michael@0: this.frame = win.document.getElementById("toolbox-iframe"); michael@0: this.emit("ready", this.frame); michael@0: michael@0: deferred.resolve(this.frame); michael@0: }.bind(this); michael@0: michael@0: win.addEventListener("load", frameLoad, true); michael@0: win.addEventListener("unload", this._boundUnload); michael@0: michael@0: this._window = win; michael@0: michael@0: return deferred.promise; michael@0: }, michael@0: michael@0: /** michael@0: * Catch the user closing the window. michael@0: */ michael@0: _boundUnload: function(event) { michael@0: if (event.target.location != this.WINDOW_URL) { michael@0: return; michael@0: } michael@0: this._window.removeEventListener("unload", this._boundUnload); michael@0: michael@0: this.emit("window-closed"); michael@0: }, michael@0: michael@0: /** michael@0: * Raise the host. michael@0: */ michael@0: raise: function RH_raise() { michael@0: this._window.focus(); michael@0: }, michael@0: michael@0: /** michael@0: * Set the toolbox title. michael@0: */ michael@0: setTitle: function WH_setTitle(title) { michael@0: this._window.document.title = title; michael@0: }, michael@0: michael@0: /** michael@0: * Destroy the window. michael@0: */ michael@0: destroy: function WH_destroy() { michael@0: if (!this._destroyed) { michael@0: this._destroyed = true; michael@0: michael@0: this._window.removeEventListener("unload", this._boundUnload); michael@0: this._window.close(); michael@0: } michael@0: michael@0: return promise.resolve(null); michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * Host object for the toolbox in its own tab michael@0: */ michael@0: function CustomHost(hostTab, options) { michael@0: this.frame = options.customIframe; michael@0: this.uid = options.uid; michael@0: EventEmitter.decorate(this); michael@0: } michael@0: michael@0: CustomHost.prototype = { michael@0: type: "custom", michael@0: michael@0: _sendMessageToTopWindow: function CH__sendMessageToTopWindow(msg, data) { michael@0: // It's up to the custom frame owner (parent window) to honor michael@0: // "close" or "raise" instructions. michael@0: let topWindow = this.frame.ownerDocument.defaultView; michael@0: let json = {name:"toolbox-" + msg, uid: this.uid}; michael@0: if (data) { michael@0: json.data = data; michael@0: } michael@0: topWindow.postMessage(JSON.stringify(json), "*"); michael@0: }, michael@0: michael@0: /** michael@0: * Create a new xul window to contain the toolbox. michael@0: */ michael@0: create: function CH_create() { michael@0: return promise.resolve(this.frame); michael@0: }, michael@0: michael@0: /** michael@0: * Raise the host. michael@0: */ michael@0: raise: function CH_raise() { michael@0: this._sendMessageToTopWindow("raise"); michael@0: }, michael@0: michael@0: /** michael@0: * Set the toolbox title. michael@0: */ michael@0: setTitle: function CH_setTitle(title) { michael@0: this._sendMessageToTopWindow("title", { value: title }); michael@0: }, michael@0: michael@0: /** michael@0: * Destroy the window. michael@0: */ michael@0: destroy: function WH_destroy() { michael@0: if (!this._destroyed) { michael@0: this._destroyed = true; michael@0: this._sendMessageToTopWindow("close"); michael@0: } michael@0: return promise.resolve(null); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Switch to the given tab in a browser and focus the browser window michael@0: */ michael@0: function focusTab(tab) { michael@0: let browserWindow = tab.ownerDocument.defaultView; michael@0: browserWindow.focus(); michael@0: browserWindow.gBrowser.selectedTab = tab; michael@0: }