michael@0: /* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ 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: const {Cu} = require("chrome"); michael@0: michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: michael@0: var {Promise: promise} = require("resource://gre/modules/Promise.jsm"); michael@0: var EventEmitter = require("devtools/toolkit/event-emitter"); michael@0: var Telemetry = require("devtools/shared/telemetry"); michael@0: michael@0: const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; michael@0: michael@0: /** michael@0: * ToolSidebar provides methods to register tabs in the sidebar. michael@0: * It's assumed that the sidebar contains a xul:tabbox. michael@0: * michael@0: * @param {Node} tabbox michael@0: * node; michael@0: * @param {ToolPanel} panel michael@0: * Related ToolPanel instance; michael@0: * @param {String} uid michael@0: * Unique ID michael@0: * @param {Boolean} showTabstripe michael@0: * Show the tabs. michael@0: */ michael@0: function ToolSidebar(tabbox, panel, uid, showTabstripe=true) michael@0: { michael@0: EventEmitter.decorate(this); michael@0: michael@0: this._tabbox = tabbox; michael@0: this._uid = uid; michael@0: this._panelDoc = this._tabbox.ownerDocument; michael@0: this._toolPanel = panel; michael@0: michael@0: try { michael@0: this._width = Services.prefs.getIntPref("devtools.toolsidebar-width." + this._uid); michael@0: } catch(e) {} michael@0: michael@0: this._telemetry = new Telemetry(); michael@0: michael@0: this._tabbox.tabpanels.addEventListener("select", this, true); michael@0: michael@0: this._tabs = new Map(); michael@0: michael@0: if (!showTabstripe) { michael@0: this._tabbox.setAttribute("hidetabs", "true"); michael@0: } michael@0: } michael@0: michael@0: exports.ToolSidebar = ToolSidebar; michael@0: michael@0: ToolSidebar.prototype = { michael@0: /** michael@0: * Register a tab. A tab is a document. michael@0: * The document must have a title, which will be used as the name of the tab. michael@0: * michael@0: * @param {string} tab uniq id michael@0: * @param {string} url michael@0: */ michael@0: addTab: function ToolSidebar_addTab(id, url, selected=false) { michael@0: let iframe = this._panelDoc.createElementNS(XULNS, "iframe"); michael@0: iframe.className = "iframe-" + id; michael@0: iframe.setAttribute("flex", "1"); michael@0: iframe.setAttribute("src", url); michael@0: iframe.tooltip = "aHTMLTooltip"; michael@0: michael@0: let tab = this._tabbox.tabs.appendItem(); michael@0: tab.setAttribute("label", ""); // Avoid showing "undefined" while the tab is loading michael@0: michael@0: let onIFrameLoaded = function() { michael@0: tab.setAttribute("label", iframe.contentDocument.title); michael@0: iframe.removeEventListener("load", onIFrameLoaded, true); michael@0: if ("setPanel" in iframe.contentWindow) { michael@0: iframe.contentWindow.setPanel(this._toolPanel, iframe); michael@0: } michael@0: this.emit(id + "-ready"); michael@0: }.bind(this); michael@0: michael@0: iframe.addEventListener("load", onIFrameLoaded, true); michael@0: michael@0: let tabpanel = this._panelDoc.createElementNS(XULNS, "tabpanel"); michael@0: tabpanel.setAttribute("id", "sidebar-panel-" + id); michael@0: tabpanel.appendChild(iframe); michael@0: this._tabbox.tabpanels.appendChild(tabpanel); michael@0: michael@0: this._tooltip = this._panelDoc.createElementNS(XULNS, "tooltip"); michael@0: this._tooltip.id = "aHTMLTooltip"; michael@0: tabpanel.appendChild(this._tooltip); michael@0: this._tooltip.page = true; michael@0: michael@0: tab.linkedPanel = "sidebar-panel-" + id; michael@0: michael@0: // We store the index of this tab. michael@0: this._tabs.set(id, tab); michael@0: michael@0: if (selected) { michael@0: // For some reason I don't understand, if we call this.select in this michael@0: // event loop (after inserting the tab), the tab will never get the michael@0: // the "selected" attribute set to true. michael@0: this._panelDoc.defaultView.setTimeout(function() { michael@0: this.select(id); michael@0: }.bind(this), 10); michael@0: } michael@0: michael@0: this.emit("new-tab-registered", id); michael@0: }, michael@0: michael@0: /** michael@0: * Select a specific tab. michael@0: */ michael@0: select: function ToolSidebar_select(id) { michael@0: let tab = this._tabs.get(id); michael@0: if (tab) { michael@0: this._tabbox.selectedTab = tab; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Return the id of the selected tab. michael@0: */ michael@0: getCurrentTabID: function ToolSidebar_getCurrentTabID() { michael@0: let currentID = null; michael@0: for (let [id, tab] of this._tabs) { michael@0: if (this._tabbox.tabs.selectedItem == tab) { michael@0: currentID = id; michael@0: break; michael@0: } michael@0: } michael@0: return currentID; michael@0: }, michael@0: michael@0: /** michael@0: * Returns the requested tab based on the id. michael@0: * michael@0: * @param String id michael@0: * unique id of the requested tab. michael@0: */ michael@0: getTab: function ToolSidebar_getTab(id) { michael@0: return this._tabbox.tabpanels.querySelector("#sidebar-panel-" + id); michael@0: }, michael@0: michael@0: /** michael@0: * Event handler. michael@0: */ michael@0: handleEvent: function ToolSidebar_eventHandler(event) { michael@0: if (event.type == "select") { michael@0: if (this._currentTool == this.getCurrentTabID()) { michael@0: // Tool hasn't changed. michael@0: return; michael@0: } michael@0: michael@0: let previousTool = this._currentTool; michael@0: this._currentTool = this.getCurrentTabID(); michael@0: if (previousTool) { michael@0: this._telemetry.toolClosed(previousTool); michael@0: this.emit(previousTool + "-unselected"); michael@0: } michael@0: michael@0: this._telemetry.toolOpened(this._currentTool); michael@0: this.emit(this._currentTool + "-selected"); michael@0: this.emit("select", this._currentTool); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Toggle sidebar's visibility state. michael@0: */ michael@0: toggle: function ToolSidebar_toggle() { michael@0: if (this._tabbox.hasAttribute("hidden")) { michael@0: this.show(); michael@0: } else { michael@0: this.hide(); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Show the sidebar. michael@0: */ michael@0: show: function ToolSidebar_show() { michael@0: if (this._width) { michael@0: this._tabbox.width = this._width; michael@0: } michael@0: this._tabbox.removeAttribute("hidden"); michael@0: }, michael@0: michael@0: /** michael@0: * Show the sidebar. michael@0: */ michael@0: hide: function ToolSidebar_hide() { michael@0: Services.prefs.setIntPref("devtools.toolsidebar-width." + this._uid, this._tabbox.width); michael@0: this._tabbox.setAttribute("hidden", "true"); michael@0: }, michael@0: michael@0: /** michael@0: * Return the window containing the tab content. michael@0: */ michael@0: getWindowForTab: function ToolSidebar_getWindowForTab(id) { michael@0: if (!this._tabs.has(id)) { michael@0: return null; michael@0: } michael@0: michael@0: let panel = this._panelDoc.getElementById(this._tabs.get(id).linkedPanel); michael@0: return panel.firstChild.contentWindow; michael@0: }, michael@0: michael@0: /** michael@0: * Clean-up. michael@0: */ michael@0: destroy: function ToolSidebar_destroy() { michael@0: if (this._destroyed) { michael@0: return promise.resolve(null); michael@0: } michael@0: this._destroyed = true; michael@0: michael@0: Services.prefs.setIntPref("devtools.toolsidebar-width." + this._uid, this._tabbox.width); michael@0: michael@0: this._tabbox.tabpanels.removeEventListener("select", this, true); michael@0: michael@0: while (this._tabbox.tabpanels.hasChildNodes()) { michael@0: this._tabbox.tabpanels.removeChild(this._tabbox.tabpanels.firstChild); michael@0: } michael@0: michael@0: while (this._tabbox.tabs.hasChildNodes()) { michael@0: this._tabbox.tabs.removeChild(this._tabbox.tabs.firstChild); michael@0: } michael@0: michael@0: if (this._currentTool) { michael@0: this._telemetry.toolClosed(this._currentTool); michael@0: } michael@0: michael@0: this._tabs = null; michael@0: this._tabbox = null; michael@0: this._panelDoc = null; michael@0: this._toolPanel = null; michael@0: michael@0: return promise.resolve(null); michael@0: }, michael@0: }