Bug 672902 - Highlighter should be useable via keyboard.; r=rcampbell
--- a/browser/devtools/highlighter/inspector.jsm
+++ b/browser/devtools/highlighter/inspector.jsm
@@ -1,10 +1,10 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
@@ -341,17 +341,17 @@ Highlighter.prototype = {
* Highlight this.node, unhilighting first if necessary.
*
* @param boolean aScroll
* Boolean determining whether to scroll or not.
*/
highlight: function Highlighter_highlight(aScroll)
{
// node is not set or node is not highlightable, bail
- if (!this.node || !this.isNodeHighlightable()) {
+ if (!this.node || !this.isNodeHighlightable(this.node)) {
return;
}
if (aScroll) {
this.node.scrollIntoView();
}
let clientRect = this.node.getBoundingClientRect();
@@ -627,27 +627,29 @@ Highlighter.prototype = {
// Get midpoint of diagonal line.
let midpoint = this.midPoint(a, b);
return this.IUI.elementFromPoint(this.win.document, midpoint.x,
midpoint.y);
},
/**
- * Is this.node highlightable?
+ * Is the specified node highlightable?
*
+ * @param nsIDOMNode aNode
+ * the DOM element in question
* @returns boolean
* True if the node is highlightable or false otherwise.
*/
- isNodeHighlightable: function Highlighter_isNodeHighlightable()
+ isNodeHighlightable: function Highlighter_isNodeHighlightable(aNode)
{
- if (!this.node || this.node.nodeType != this.node.ELEMENT_NODE) {
+ if (aNode.nodeType != aNode.ELEMENT_NODE) {
return false;
}
- let nodeName = this.node.nodeName.toLowerCase();
+ let nodeName = aNode.nodeName.toLowerCase();
return !INSPECTOR_INVISIBLE_ELEMENTS[nodeName];
},
/////////////////////////////////////////////////////////////////////////
//// Event Handling
attachInspectListeners: function Highlighter_attachInspectListeners()
{
@@ -1164,16 +1166,79 @@ InspectorUI.prototype = {
case this.chromeWin.KeyEvent.DOM_VK_RETURN:
case this.chromeWin.KeyEvent.DOM_VK_ESCAPE:
if (this.inspecting) {
this.stopInspecting();
event.preventDefault();
event.stopPropagation();
}
break;
+ case this.chromeWin.KeyEvent.DOM_VK_LEFT:
+ let node;
+ if (this.selection) {
+ node = this.selection.parentNode;
+ } else {
+ node = this.defaultSelection;
+ }
+ if (node && this.highlighter.isNodeHighlightable(node)) {
+ this.inspectNode(node, true);
+ }
+ event.preventDefault();
+ event.stopPropagation();
+ break;
+ case this.chromeWin.KeyEvent.DOM_VK_RIGHT:
+ if (this.selection) {
+ // Find the first child that is highlightable.
+ for (let i = 0; i < this.selection.childNodes.length; i++) {
+ node = this.selection.childNodes[i];
+ if (node && this.highlighter.isNodeHighlightable(node)) {
+ break;
+ }
+ }
+ } else {
+ node = this.defaultSelection;
+ }
+ if (node && this.highlighter.isNodeHighlightable(node)) {
+ this.inspectNode(node, true);
+ }
+ event.preventDefault();
+ event.stopPropagation();
+ break;
+ case this.chromeWin.KeyEvent.DOM_VK_UP:
+ if (this.selection) {
+ // Find a previous sibling that is highlightable.
+ node = this.selection.previousSibling;
+ while (node && !this.highlighter.isNodeHighlightable(node)) {
+ node = node.previousSibling;
+ }
+ } else {
+ node = this.defaultSelection;
+ }
+ if (node && this.highlighter.isNodeHighlightable(node)) {
+ this.inspectNode(node, true);
+ }
+ event.preventDefault();
+ event.stopPropagation();
+ break;
+ case this.chromeWin.KeyEvent.DOM_VK_DOWN:
+ if (this.selection) {
+ // Find a next sibling that is highlightable.
+ node = this.selection.nextSibling;
+ while (node && !this.highlighter.isNodeHighlightable(node)) {
+ node = node.nextSibling;
+ }
+ } else {
+ node = this.defaultSelection;
+ }
+ if (node && this.highlighter.isNodeHighlightable(node)) {
+ this.inspectNode(node, true);
+ }
+ event.preventDefault();
+ event.stopPropagation();
+ break;
}
break;
}
},
/**
* Attach event listeners to content window and child windows to enable
* highlighting and click to stop inspection.
@@ -1198,21 +1263,23 @@ InspectorUI.prototype = {
//// Utility Methods
/**
* inspect the given node, highlighting it on the page and selecting the
* correct row in the tree panel
*
* @param aNode
* the element in the document to inspect
+ * @param aScroll
+ * force scroll?
*/
- inspectNode: function IUI_inspectNode(aNode)
+ inspectNode: function IUI_inspectNode(aNode, aScroll)
{
this.select(aNode, true, true);
- this.highlighter.highlightNode(aNode);
+ this.highlighter.highlightNode(aNode, { scroll: aScroll });
},
/**
* Find an element from the given coordinates. This method descends through
* frames to find the element the user clicked inside frames.
*
* @param DOMDocument aDocument the document to look into.
* @param integer aX
--- a/browser/devtools/highlighter/test/Makefile.in
+++ b/browser/devtools/highlighter/test/Makefile.in
@@ -56,15 +56,16 @@ include $(topsrcdir)/config/rules.mk
browser_inspector_treePanel_result.html \
browser_inspector_registertools.js \
browser_inspector_bug_665880.js \
browser_inspector_bug_674871.js \
browser_inspector_editor.js \
browser_inspector_bug_566084_location_changed.js \
browser_inspector_infobar.js \
browser_inspector_bug_690361.js \
+ browser_inspector_bug_672902_keyboard_shortcuts.js \
$(NULL)
# Disabled due to constant failures
# browser_inspector_treePanel_click.js \
libs:: $(_BROWSER_FILES)
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/highlighter/test/browser_inspector_bug_672902_keyboard_shortcuts.js
@@ -0,0 +1,142 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+
+// Tests that the keybindings for highlighting different elements work as
+// intended.
+
+function test()
+{
+ waitForExplicitFinish();
+
+ let doc;
+ let node;
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", function onload() {
+ gBrowser.selectedBrowser.removeEventListener("load", onload, true);
+ doc = content.document;
+ waitForFocus(setupKeyBindingsTest, content);
+ }, true);
+
+ content.location = "data:text/html,<html><head><title>Test for the " +
+ "highlighter keybindings</title></head><body><h1>Hello" +
+ "</h1><p><strong>Greetings, earthlings!</strong> I come" +
+ " in peace.</body></html>";
+
+ function setupKeyBindingsTest()
+ {
+ Services.obs.addObserver(findAndHighlightNode,
+ InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED,
+ false);
+ InspectorUI.toggleInspectorUI();
+ }
+
+ function findAndHighlightNode()
+ {
+ Services.obs.removeObserver(findAndHighlightNode,
+ InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
+
+ executeSoon(function() {
+ Services.obs.addObserver(highlightBodyNode,
+ InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING,
+ false);
+ // Test that navigating around without a selected node gets us to the
+ // body element.
+ node = doc.querySelector("body");
+ EventUtils.synthesizeKey("VK_RIGHT", { });
+ });
+ }
+
+ function highlightBodyNode()
+ {
+ Services.obs.removeObserver(highlightBodyNode,
+ InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
+ is(InspectorUI.selection, node, "selected body element");
+
+ executeSoon(function() {
+ Services.obs.addObserver(highlightHeaderNode,
+ InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING,
+ false);
+ // Test that moving to the child works.
+ node = doc.querySelector("h1");
+ EventUtils.synthesizeKey("VK_RIGHT", { });
+ });
+ }
+
+ function highlightHeaderNode()
+ {
+ Services.obs.removeObserver(highlightHeaderNode,
+ InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
+ is(InspectorUI.selection, node, "selected h1 element");
+
+ executeSoon(function() {
+ Services.obs.addObserver(highlightParagraphNode,
+ InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING,
+ false);
+ // Test that moving to the next sibling works.
+ node = doc.querySelector("p");
+ EventUtils.synthesizeKey("VK_DOWN", { });
+ });
+ }
+
+ function highlightParagraphNode()
+ {
+ Services.obs.removeObserver(highlightParagraphNode,
+ InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
+ is(InspectorUI.selection, node, "selected p element");
+
+ executeSoon(function() {
+ Services.obs.addObserver(highlightHeaderNodeAgain,
+ InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING,
+ false);
+ // Test that moving to the previous sibling works.
+ node = doc.querySelector("h1");
+ EventUtils.synthesizeKey("VK_UP", { });
+ });
+ }
+
+ function highlightHeaderNodeAgain()
+ {
+ Services.obs.removeObserver(highlightHeaderNodeAgain,
+ InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
+ is(InspectorUI.selection, node, "selected h1 element");
+
+ executeSoon(function() {
+ Services.obs.addObserver(highlightParentNode,
+ InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING,
+ false);
+ // Test that moving to the parent works.
+ node = doc.querySelector("body");
+ EventUtils.synthesizeKey("VK_LEFT", { });
+ });
+ }
+
+ function highlightParentNode()
+ {
+ Services.obs.removeObserver(highlightParentNode,
+ InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
+ is(InspectorUI.selection, node, "selected body element");
+
+ // Test that locking works.
+ EventUtils.synthesizeKey("VK_RETURN", { });
+ executeSoon(isTheNodeLocked);
+ }
+
+ function isTheNodeLocked()
+ {
+ ok(!InspectorUI.inspecting, "the node is locked");
+ Services.obs.addObserver(finishUp,
+ InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED,
+ false);
+ InspectorUI.closeInspectorUI();
+ }
+
+ function finishUp() {
+ Services.obs.removeObserver(finishUp,
+ InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
+ doc = node = null;
+ gBrowser.removeCurrentTab();
+ finish();
+ }
+}