diff --git a/toolkit/components/console/hudservice/HUDService.jsm b/toolkit/components/console/hudservice/HUDService.jsm index b087ad7a8a68..dfe7ae3556cf 100644 --- a/toolkit/components/console/hudservice/HUDService.jsm +++ b/toolkit/components/console/hudservice/HUDService.jsm @@ -2704,6 +2704,24 @@ HUD_SERVICE.prototype = } // capture JS Errors this.setOnErrorHandler(aContentWindow); + + // register the controller to handle "select all" properly + this.createController(xulWindow); + }, + + /** + * Adds the command controller to the XUL window if it's not already present. + * + * @param nsIDOMWindow aWindow + * The browser XUL window. + * @returns void + */ + createController: function HUD_createController(aWindow) + { + if (aWindow.commandController == null) { + aWindow.commandController = new CommandController(aWindow); + aWindow.controllers.insertControllerAt(0, aWindow.commandController); + } } }; @@ -3107,6 +3125,15 @@ HeadsUpDisplay.prototype = { copyItem.setAttribute("command", "cmd_copy"); menuPopup.appendChild(copyItem); + let selectAllItem = this.makeXULNode("menuitem"); + selectAllItem.setAttribute("label", this.getStr("selectAllCmd.label")); + selectAllItem.setAttribute("accesskey", + this.getStr("selectAllCmd.accesskey")); + selectAllItem.setAttribute("hudId", this.hudId); + selectAllItem.setAttribute("buttonType", "selectAll"); + selectAllItem.setAttribute("oncommand", "HUDConsoleUI.command(this);"); + menuPopup.appendChild(selectAllItem); + menuPopup.appendChild(this.makeXULNode("menuseparator")); let clearItem = this.makeXULNode("menuitem"); @@ -4700,8 +4727,16 @@ HeadsUpDisplayUICommands = { command: function UIC_command(aButton) { var filter = aButton.getAttribute("buttonType"); var hudId = aButton.getAttribute("hudId"); - if (filter == "clear") { - HUDService.clearDisplay(hudId); + switch (filter) { + case "clear": + HUDService.clearDisplay(hudId); + break; + case "selectAll": + let outputNode = HUDService.getOutputNodeById(hudId); + let chromeWindow = outputNode.ownerDocument.defaultView; + let commandController = chromeWindow.commandController; + commandController.selectAll(outputNode); + break; } }, @@ -5005,6 +5040,67 @@ HUDWindowObserver = { initialConsoleCreated: false, }; +/////////////////////////////////////////////////////////////////////////////// +// CommandController +/////////////////////////////////////////////////////////////////////////////// + +/** + * A controller (an instance of nsIController) that makes editing actions + * behave appropriately in the context of the Web Console. + */ +function CommandController(aWindow) { + this.window = aWindow; +} + +CommandController.prototype = { + /** + * Returns the HUD output node that currently has the focus, or null if the + * currently-focused element isn't inside the output node. + * + * @returns nsIDOMNode + * The currently-focused output node. + */ + _getFocusedOutputNode: function CommandController_getFocusedOutputNode() + { + let anchorNode = this.window.getSelection().anchorNode; + while (!(anchorNode.nodeType === anchorNode.ELEMENT_NODE && + anchorNode.classList.contains("hud-output-node"))) { + anchorNode = anchorNode.parentNode; + } + return anchorNode; + }, + + /** + * Selects all the text in the HUD output. + * + * @param nsIDOMNode aOutputNode + * The HUD output node. + * @returns void + */ + selectAll: function CommandController_selectAll(aOutputNode) + { + let selection = this.window.getSelection(); + selection.removeAllRanges(); + selection.selectAllChildren(aOutputNode); + }, + + supportsCommand: function CommandController_supportsCommand(aCommand) + { + return aCommand === "cmd_selectAll" && + this._getFocusedOutputNode() != null; + }, + + isCommandEnabled: function CommandController_isCommandEnabled(aCommand) + { + return aCommand === "cmd_selectAll"; + }, + + doCommand: function CommandController_doCommand(aCommand) + { + this.selectAll(this._getFocusedOutputNode()); + } +}; + /////////////////////////////////////////////////////////////////////////////// // HUDConsoleObserver /////////////////////////////////////////////////////////////////////////////// diff --git a/toolkit/components/console/hudservice/tests/browser/Makefile.in b/toolkit/components/console/hudservice/tests/browser/Makefile.in index 5c6222991982..2df623c6e498 100644 --- a/toolkit/components/console/hudservice/tests/browser/Makefile.in +++ b/toolkit/components/console/hudservice/tests/browser/Makefile.in @@ -19,6 +19,7 @@ # # Contributor(s): # David Dahl +# Patrick Walton # # Alternatively, the contents of this file may be used under the terms of # either of the GNU General Public License Version 2 or later (the "GPL"), @@ -43,9 +44,11 @@ relativesrcdir = toolkit/components/console/hudservice/tests/browser include $(DEPTH)/config/autoconf.mk include $(topsrcdir)/config/rules.mk + _BROWSER_TEST_FILES = \ browser_HUDServiceTestsAll.js \ browser_webconsole_bug_585237_line_limit.js \ + browser_webconsole_bug_586388_select_all.js \ browser_webconsole_bug_588967_input_expansion.js \ browser_webconsole_netlogging.js \ browser_webconsole_bug_581231_close_button.js \ diff --git a/toolkit/components/console/hudservice/tests/browser/browser_webconsole_bug_586388_select_all.js b/toolkit/components/console/hudservice/tests/browser/browser_webconsole_bug_586388_select_all.js new file mode 100644 index 000000000000..90e499af37c7 --- /dev/null +++ b/toolkit/components/console/hudservice/tests/browser/browser_webconsole_bug_586388_select_all.js @@ -0,0 +1,95 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + * + * Contributor(s): + * Patrick Walton + * + * ***** END LICENSE BLOCK ***** */ + +const Cu = Components.utils; + +Cu.import("resource://gre/modules/HUDService.jsm"); + +const TEST_URI = "http://example.com/"; + +function test() { + waitForExplicitFinish(); + gBrowser.selectedTab = gBrowser.addTab(TEST_URI); + gBrowser.selectedBrowser.addEventListener("DOMContentLoaded", onLoad, false); +} + +function onLoad() { + gBrowser.selectedBrowser.removeEventListener("DOMContentLoaded", onLoad, + false); + executeSoon(testSelectionWhenMovingBetweenBoxes); +} + +function testSelectionWhenMovingBetweenBoxes() { + HUDService.activateHUDForContext(gBrowser.selectedTab); + + let hudId = HUDService.displaysIndex()[0]; + let jsterm = HUDService.hudWeakReferences[hudId].get().jsterm; + + // Fill the console with some output. + jsterm.clearOutput(); + jsterm.execute("1 + 2"); + jsterm.execute("3 + 4"); + jsterm.execute("5 + 6"); + + let outputNode = jsterm.outputNode; + let groupNode = outputNode.querySelector(".hud-group"); + + ok(groupNode.childNodes.length >= 3, "the output node has children after " + + "executing some JavaScript"); + + // Test that the global Firefox "Select All" functionality (e.g. Edit > + // Select All) works properly in the Web Console. + let selection = window.getSelection(); + selection.removeAllRanges(); + let range = document.createRange(); + range.selectNode(outputNode.firstChild); + selection.addRange(range); + selection.collapseToStart(); + + let commandController = window.commandController; + ok(commandController != null, "the window has a command controller object"); + + commandController.selectAll(outputNode); + for (let i = 0; i < groupNode.childNodes.length; i++) { + ok(selection.containsNode(groupNode.childNodes[i], false), + "HUD message " + i + " is selected after performing a regular " + + "browser select-all operation"); + } + + selection.removeAllRanges(); + + // Test the context menu "Select All" (which has a different code path) works + // properly as well. + let contextMenuId = outputNode.getAttribute("context"); + let contextMenu = document.getElementById(contextMenuId); + ok(contextMenu != null, "the output node has a context menu"); + + let selectAllItem = contextMenu.childNodes[1]; + ok(selectAllItem != null, + "the context menu on the output node has a \"Select All\" item"); + + let commandEvent = document.createEvent("XULCommandEvent"); + commandEvent.initCommandEvent("command", true, true, window, 0, false, false, + false, false, null); + selectAllItem.dispatchEvent(commandEvent); + + for (let i = 0; i < groupNode.childNodes.length; i++) { + ok(selection.containsNode(groupNode.childNodes[i], false), + "HUD message " + i + " is selected after performing a select-all " + + "operation from the context menu"); + } + + selection.removeAllRanges(); + + HUDService.deactivateHUDForContext(gBrowser.selectedTab); + gBrowser.removeCurrentTab(); + finish(); +} + diff --git a/toolkit/locales/en-US/chrome/global/headsUpDisplay.properties b/toolkit/locales/en-US/chrome/global/headsUpDisplay.properties index 1fdef08b6dd6..ccf6a034165e 100644 --- a/toolkit/locales/en-US/chrome/global/headsUpDisplay.properties +++ b/toolkit/locales/en-US/chrome/global/headsUpDisplay.properties @@ -63,6 +63,9 @@ jsPropertyTitle=Object Inspector jsPropertyInspectTitle=Inspect: %S copyCmd.label=Copy copyCmd.accesskey=C +selectAllCmd.label=Select All +selectAllCmd.accesskey=A + helperFuncUnsupportedTypeError=Can't call pprint on this type of object. # LOCALIZATION NOTE (networkUrlWithStatus): #