From be53323151a89b8b057227c9060305502e403889 Mon Sep 17 00:00:00 2001 From: Brian Grinstead Date: Mon, 11 Nov 2013 15:13:28 -0600 Subject: [PATCH] Bug 862558 - Web Console should always be available / visible;r=msucan --- browser/devtools/debugger/debugger-panes.js | 1 + browser/devtools/framework/toolbox.js | 88 ++++++- browser/devtools/framework/toolbox.xul | 7 +- browser/devtools/webconsole/test/browser.ini | 1 + .../test/browser_webconsole_split.js | 238 ++++++++++++++++++ 5 files changed, 329 insertions(+), 6 deletions(-) create mode 100644 browser/devtools/webconsole/test/browser_webconsole_split.js diff --git a/browser/devtools/debugger/debugger-panes.js b/browser/devtools/debugger/debugger-panes.js index 1af08b2c9db4..82ef6a0b3bb7 100644 --- a/browser/devtools/debugger/debugger-panes.js +++ b/browser/devtools/debugger/debugger-panes.js @@ -1516,6 +1516,7 @@ WatchExpressionsView.prototype = Heritage.extend(WidgetMethods, { case e.DOM_VK_RETURN: case e.DOM_VK_ENTER: case e.DOM_VK_ESCAPE: + e.stopPropagation(); DebuggerView.editor.focus(); return; } diff --git a/browser/devtools/framework/toolbox.js b/browser/devtools/framework/toolbox.js index fa585e8757b9..85fafacf2db3 100644 --- a/browser/devtools/framework/toolbox.js +++ b/browser/devtools/framework/toolbox.js @@ -67,6 +67,7 @@ function Toolbox(target, selectedTool, hostType, hostOptions) { this._toolRegistered = this._toolRegistered.bind(this); this._toolUnregistered = this._toolUnregistered.bind(this); this._refreshHostTitle = this._refreshHostTitle.bind(this); + this._splitConsoleOnKeypress = this._splitConsoleOnKeypress.bind(this) this.destroy = this.destroy.bind(this); this._target.on("close", this.destroy); @@ -229,11 +230,55 @@ Toolbox.prototype = { }, true); }, + _splitConsoleOnKeypress: function(e) { + if (e.keyCode === e.DOM_VK_ESCAPE) { + this.toggleSplitConsole(); + } + }, + _addToolSwitchingKeys: function() { let nextKey = this.doc.getElementById("toolbox-next-tool-key"); nextKey.addEventListener("command", this.selectNextTool.bind(this), true); let prevKey = this.doc.getElementById("toolbox-previous-tool-key"); prevKey.addEventListener("command", this.selectPreviousTool.bind(this), true); + + // Split console uses keypress instead of command so the event can be + // cancelled with stopPropagation on the keypress, and not preventDefault. + this.doc.addEventListener("keypress", this._splitConsoleOnKeypress, false); + }, + + /** + * Make sure that the console is showing up properly based on all the + * possible conditions. + * 1) If the console tab is selected, then regardless of split state + * it should take up the full height of the deck, and we should + * hide the deck and splitter. + * 2) If the console tab is not selected and it is split, then we should + * show the splitter, deck, and console. + * 3) If the console tab is not selected and it is *not* split, + * then we should hide the console and splitter, and show the deck + * at full height. + */ + _refreshConsoleDisplay: function() { + let deck = this.doc.getElementById("toolbox-deck"); + let webconsolePanel = this.doc.getElementById("toolbox-panel-webconsole"); + let splitter = this.doc.getElementById("toolbox-console-splitter"); + let openedConsolePanel = this.currentToolId === "webconsole"; + + if (openedConsolePanel) { + deck.setAttribute("collapsed", "true"); + splitter.setAttribute("hidden", "true"); + webconsolePanel.removeAttribute("collapsed"); + } else { + deck.removeAttribute("collapsed"); + if (this._splitConsole) { + webconsolePanel.removeAttribute("collapsed"); + splitter.removeAttribute("hidden"); + } else { + webconsolePanel.setAttribute("collapsed", "true"); + splitter.setAttribute("hidden", "true"); + } + } }, /** @@ -502,8 +547,11 @@ Toolbox.prototype = { } let vbox = this.doc.createElement("vbox"); vbox.className = "toolbox-panel " + toolDefinition.bgTheme; - vbox.id = "toolbox-panel-" + id; + // There is already a container for the webconsole frame. + if (!this.doc.getElementById("toolbox-panel-" + id)) { + vbox.id = "toolbox-panel-" + id; + } // If there is no tab yet, or the ordinal to be added is the largest one. if (tabs.childNodes.length == 0 || @@ -613,7 +661,6 @@ Toolbox.prototype = { let tab = this.doc.getElementById("toolbox-tab-" + id); tab.setAttribute("selected", "true"); - if (this.currentToolId == id) { // re-focus tool to get key events again this.focusTool(id); @@ -656,6 +703,7 @@ Toolbox.prototype = { deck.selectedIndex = index; this.currentToolId = id; + this._refreshConsoleDisplay(); if (id != "options") { Services.prefs.setCharPref(this._prefs.LAST_TOOL, id); } @@ -680,6 +728,27 @@ Toolbox.prototype = { iframe.focus(); }, + /** + * Toggles the split state of the webconsole. If the webconsole panel + * is already selected, then this command is ignored. + */ + toggleSplitConsole: function() { + let openedConsolePanel = this.currentToolId === "webconsole"; + + // Don't allow changes when console is open, since it could be confusing + if (!openedConsolePanel) { + this._splitConsole = !this._splitConsole; + this._refreshConsoleDisplay(); + this.emit("split-console"); + + if (this._splitConsole) { + this.loadTool("webconsole").then(() => { + this.focusTool("webconsole"); + }); + } + } + }, + /** * Loads the tool next to the currently selected tool. */ @@ -789,7 +858,7 @@ Toolbox.prototype = { iframe.swapFrameLoaders(this.frame); this._host.off("window-closed", this.destroy); - this._host.destroy(); + this.destroyHost(); this._host = newHost; @@ -876,6 +945,17 @@ Toolbox.prototype = { return this.doc.getElementById("toolbox-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.doc.removeEventListener("keypress", + this._splitConsoleOnKeypress, false); + return this._host.destroy(); + }, + /** * Remove all UI elements, detach from target and clear up */ @@ -917,7 +997,7 @@ Toolbox.prototype = { container.removeChild(container.firstChild); } - outstanding.push(this._host.destroy()); + outstanding.push(this.destroyHost()); this._telemetry.destroy(); diff --git a/browser/devtools/framework/toolbox.xul b/browser/devtools/framework/toolbox.xul index cb7742100a42..1d161c2ef184 100644 --- a/browser/devtools/framework/toolbox.xul +++ b/browser/devtools/framework/toolbox.xul @@ -74,7 +74,10 @@ #endif - - + + + diff --git a/browser/devtools/webconsole/test/browser.ini b/browser/devtools/webconsole/test/browser.ini index 4d718c90d679..5f0fdc6fedc3 100644 --- a/browser/devtools/webconsole/test/browser.ini +++ b/browser/devtools/webconsole/test/browser.ini @@ -236,6 +236,7 @@ skip-if = os == "linux" [browser_webconsole_output_order.js] [browser_webconsole_property_provider.js] [browser_webconsole_scratchpad_panel_link.js] +[browser_webconsole_split.js] [browser_webconsole_view_source.js] [browser_webconsole_reflow.js] [browser_webconsole_log_file_filter.js] diff --git a/browser/devtools/webconsole/test/browser_webconsole_split.js b/browser/devtools/webconsole/test/browser_webconsole_split.js new file mode 100644 index 000000000000..ebf3f3e7e53d --- /dev/null +++ b/browser/devtools/webconsole/test/browser_webconsole_split.js @@ -0,0 +1,238 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* 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/. */ + +function test() +{ + let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); + let {Task} = Cu.import("resource://gre/modules/Task.jsm", {}); + let Toolbox = devtools.Toolbox; + let toolbox; + + addTab("data:text/html;charset=utf-8,Web Console test for splitting"); + browser.addEventListener("load", function onLoad() { + browser.removeEventListener("load", onLoad, true); + testConsoleLoadOnDifferentPanel() + }, true); + + function testConsoleLoadOnDifferentPanel() + { + info("About to check console loads even when non-webconsole panel is open"); + + openPanel("inspector").then(() => { + toolbox.on("webconsole-ready", () => { + ok(true, "Webconsole has been triggered as loaded while another tool is active"); + testKeyboardShortcuts(); + }); + + // Opens split console. + toolbox.toggleSplitConsole(); + }); + } + + function testKeyboardShortcuts() + { + info("About to check that panel responds to ESCAPE keyboard shortcut"); + + toolbox.once("split-console", () => { + ok(true, "Split console has been triggered via ESCAPE keypress"); + checkAllTools(); + }); + + // Closes split console. + EventUtils.sendKey("ESCAPE", toolbox.frame.contentWindow); + } + + function checkAllTools() + { + info("About to check split console with each panel individually."); + + Task.spawn(function() { + yield openAndCheckPanel("jsdebugger"); + yield openAndCheckPanel("inspector"); + yield openAndCheckPanel("styleeditor"); + yield openAndCheckPanel("jsprofiler"); + yield openAndCheckPanel("netmonitor"); + + yield checkWebconsolePanelOpened(); + testBottomHost(); + }); + } + + function getCurrentUIState() + { + let win = toolbox.doc.defaultView; + let deck = toolbox.doc.getElementById("toolbox-deck"); + let webconsolePanel = toolbox.doc.getElementById("toolbox-panel-webconsole"); + let splitter = toolbox.doc.getElementById("toolbox-console-splitter"); + + let containerHeight = parseFloat(win.getComputedStyle(deck.parentNode).getPropertyValue("height")); + let deckHeight = parseFloat(win.getComputedStyle(deck).getPropertyValue("height")); + let webconsoleHeight = parseFloat(win.getComputedStyle(webconsolePanel).getPropertyValue("height")); + let splitterVisibility = !splitter.getAttribute("hidden"); + let openedConsolePanel = toolbox.currentToolId === "webconsole"; + + return { + deckHeight: deckHeight, + containerHeight: containerHeight, + webconsoleHeight: webconsoleHeight, + splitterVisibility: splitterVisibility, + openedConsolePanel: openedConsolePanel + }; + } + + function checkWebconsolePanelOpened() + { + info("About to check special cases when webconsole panel is open."); + + let deferred = promise.defer(); + + // Start with console split, so we can test for transition to main panel. + toolbox.toggleSplitConsole(); + + let currentUIState = getCurrentUIState(); + + ok (currentUIState.splitterVisibility, "Splitter is visible when console is split"); + ok (currentUIState.deckHeight > 0, "Deck has a height > 0 when console is split"); + ok (currentUIState.webconsoleHeight > 0, "Web console has a height > 0 when console is split"); + ok (!currentUIState.openedConsolePanel, "The console panel is not the current tool"); + + openPanel("webconsole").then(() => { + + let currentUIState = getCurrentUIState(); + + ok (!currentUIState.splitterVisibility, "Splitter is hidden when console is opened."); + is (currentUIState.deckHeight, 0, "Deck has a height == 0 when console is opened."); + is (currentUIState.webconsoleHeight, currentUIState.containerHeight, "Web console is full height."); + ok (currentUIState.openedConsolePanel, "The console panel is the current tool"); + + // Make sure splitting console does nothing while webconsole is opened + toolbox.toggleSplitConsole(); + + let currentUIState = getCurrentUIState(); + + ok (!currentUIState.splitterVisibility, "Splitter is hidden when console is opened."); + is (currentUIState.deckHeight, 0, "Deck has a height == 0 when console is opened."); + is (currentUIState.webconsoleHeight, currentUIState.containerHeight, "Web console is full height."); + ok (currentUIState.openedConsolePanel, "The console panel is the current tool"); + + // Make sure that split state is saved after opening another panel + openPanel("inspector").then(() => { + let currentUIState = getCurrentUIState(); + ok (currentUIState.splitterVisibility, "Splitter is visible when console is split"); + ok (currentUIState.deckHeight > 0, "Deck has a height > 0 when console is split"); + ok (currentUIState.webconsoleHeight > 0, "Web console has a height > 0 when console is split"); + ok (!currentUIState.openedConsolePanel, "The console panel is not the current tool"); + + toolbox.toggleSplitConsole(); + deferred.resolve(); + + }); + }); + return deferred.promise; + } + + function openPanel(toolId, callback) + { + let deferred = promise.defer(); + let target = TargetFactory.forTab(gBrowser.selectedTab); + gDevTools.showToolbox(target, toolId).then(function(box) { + toolbox = box; + deferred.resolve(); + }).then(null, console.error); + return deferred.promise; + } + + function openAndCheckPanel(toolId) + { + let deferred = promise.defer(); + openPanel(toolId).then(() => { + info ("Checking toolbox for " + toolId); + checkToolboxUI(toolbox.getCurrentPanel()); + deferred.resolve(); + }); + return deferred.promise; + } + + function checkToolboxUI() + { + let currentUIState = getCurrentUIState(); + + ok (!currentUIState.splitterVisibility, "Splitter is hidden by default"); + is (currentUIState.deckHeight, currentUIState.containerHeight, "Deck has a height > 0 by default"); + is (currentUIState.webconsoleHeight, 0, "Web console is collapsed by default"); + ok (!currentUIState.openedConsolePanel, "The console panel is not the current tool"); + + toolbox.toggleSplitConsole(); + + let currentUIState = getCurrentUIState(); + + ok (currentUIState.splitterVisibility, "Splitter is visible when console is split"); + ok (currentUIState.deckHeight > 0, "Deck has a height > 0 when console is split"); + ok (currentUIState.webconsoleHeight > 0, "Web console has a height > 0 when console is split"); + is (currentUIState.deckHeight + currentUIState.webconsoleHeight, + currentUIState.containerHeight, + "Everything adds up to container height"); + ok (!currentUIState.openedConsolePanel, "The console panel is not the current tool"); + + toolbox.toggleSplitConsole(); + + let currentUIState = getCurrentUIState(); + + ok (!currentUIState.splitterVisibility, "Splitter is hidden after toggling"); + is (currentUIState.deckHeight, currentUIState.containerHeight, "Deck has a height > 0 after toggling"); + is (currentUIState.webconsoleHeight, 0, "Web console is collapsed after toggling"); + ok (!currentUIState.openedConsolePanel, "The console panel is not the current tool"); + } + + + function testBottomHost() + { + checkHostType(Toolbox.HostType.BOTTOM); + + checkToolboxUI(); + + toolbox.switchHost(Toolbox.HostType.SIDE).then(testSidebarHost); + } + + function testSidebarHost() + { + checkHostType(Toolbox.HostType.SIDE); + + checkToolboxUI(); + + toolbox.switchHost(Toolbox.HostType.WINDOW).then(testWindowHost); + } + + function testWindowHost() + { + checkHostType(Toolbox.HostType.WINDOW); + + checkToolboxUI(); + + testDestroy(); + } + + function checkHostType(hostType) + { + is(toolbox.hostType, hostType, "host type is " + hostType); + + let pref = Services.prefs.getCharPref("devtools.toolbox.host"); + is(pref, hostType, "host pref is " + hostType); + } + + function testDestroy() + { + toolbox.destroy().then(function() { + let target = TargetFactory.forTab(gBrowser.selectedTab); + gDevTools.showToolbox(target).then(finish); + }); + } + + function finish() + { + toolbox = null; + finishTest(); + } +}