Bug 672902 - Highlighter should be useable via keyboard.; r=rcampbell
authorPanos Astithas <past@mozilla.com>
Mon, 03 Oct 2011 11:32:54 +0300
changeset 78710 f425a8109714005827b6a365415c192c82225440
parent 78709 4a06aceb7922954c5a946965e4d07eb6604df361
child 78712 c2288d6886d0fa63c41f1d10cf799bb8965442c1
push id506
push userclegnitto@mozilla.com
push dateWed, 09 Nov 2011 02:03:18 +0000
treeherdermozilla-aurora@63587fc7bb93 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrcampbell
bugs672902
milestone10.0a1
Bug 672902 - Highlighter should be useable via keyboard.; r=rcampbell
browser/devtools/highlighter/inspector.jsm
browser/devtools/highlighter/test/Makefile.in
browser/devtools/highlighter/test/browser_inspector_bug_672902_keyboard_shortcuts.js
--- 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();
+  }
+}