Bug 719933 - In the Inspector, the arrowkeys shortcuts should be used only when the higlighter or the toolbar are focused. r=rcampbell
authorPaul Rouget <paul@mozilla.com>
Tue, 24 Jan 2012 19:56:47 +0100
changeset 86562 01a1a9d501067995a4748d8327af189974230494
parent 86561 006eb238ac904fdba62b7eb3eb7411d5d09b4c8b
child 86563 7fb03c72dbb060b935230b39ea4d18515e44e229
push id805
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 18:17:35 +0000
treeherdermozilla-aurora@6fb3bf232436 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrcampbell
bugs719933
milestone12.0a1
Bug 719933 - In the Inspector, the arrowkeys shortcuts should be used only when the higlighter or the toolbar are focused. r=rcampbell
browser/devtools/highlighter/TreePanel.jsm
browser/devtools/highlighter/highlighter.jsm
browser/devtools/highlighter/inspector.jsm
--- a/browser/devtools/highlighter/TreePanel.jsm
+++ b/browser/devtools/highlighter/TreePanel.jsm
@@ -136,17 +136,17 @@ TreePanel.prototype = {
     this.treePanelDiv = this.treeBrowserDocument.createElement("div");
     this.treeBrowserDocument.body.appendChild(this.treePanelDiv);
     this.treePanelDiv.ownerPanel = this;
     this.ioBox = new InsideOutBox(this, this.treePanelDiv);
     this.ioBox.createObjectBox(this.IUI.win.document.documentElement);
     this.treeLoaded = true;
     this.treeIFrame.addEventListener("click", this.onTreeClick.bind(this), false);
     this.treeIFrame.addEventListener("dblclick", this.onTreeDblClick.bind(this), false);
-    this.treeIFrame.addEventListener("keypress", this.IUI, false);
+    this.treeIFrame.focus();
     delete this.initializingTreePanel;
     Services.obs.notifyObservers(null,
       this.IUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, null);
     if (this.IUI.selection)
       this.select(this.IUI.selection, true);
   },
 
   /**
@@ -228,17 +228,17 @@ TreePanel.prototype = {
     treeBox.id = "inspector-tree-box";
     treeBox.state = "open"; // for the registerTools API.
     try {
       treeBox.height =
         Services.prefs.getIntPref("devtools.inspector.htmlHeight");
     } catch(e) {
       treeBox.height = 112;
     }
-                      
+
     treeBox.minHeight = 64;
     treeBox.flex = 1;
     toolbarParent.insertBefore(treeBox, toolbar);
 
     this.IUI.toolbar.setAttribute("treepanel-open", "true");
 
     treeBox.appendChild(this.treeIFrame);
 
@@ -461,19 +461,16 @@ TreePanel.prototype = {
     // position the editor
     editor.style.left = editorLeft + "px";
     editor.style.top = editorTop + "px";
 
     // set and select the text
     editorInput.value = aAttrVal;
     editorInput.select();
 
-    // remove tree key navigation events
-    this.treeIFrame.removeEventListener("keypress", this.IUI, false);
-
     // listen for editor specific events
     this.bindEditorEvent(editor, "click", function(aEvent) {
       aEvent.stopPropagation();
     });
     this.bindEditorEvent(editor, "dblclick", function(aEvent) {
       aEvent.stopPropagation();
     });
     this.bindEditorEvent(editor, "keypress",
@@ -556,19 +553,16 @@ TreePanel.prototype = {
     this.unbindEditorEvent(editor, "keypress");
 
     // clean up after the editor
     editorInput.value = "";
     editorInput.blur();
     this.editingContext = null;
     this.editingEvents = {};
 
-    // re-add navigation listener
-    this.treeIFrame.addEventListener("keypress", this.IUI, false);
-
     // event notification
     Services.obs.notifyObservers(null, this.IUI.INSPECTOR_NOTIFICATIONS.EDITOR_CLOSED,
                                   null);
   },
 
   /**
    * Commit the edits made in the editor, then close it.
    */
@@ -695,17 +689,16 @@ TreePanel.prototype = {
       this.treePanelDiv.ownerPanel = null;
       let parent = this.treePanelDiv.parentNode;
       parent.removeChild(this.treePanelDiv);
       delete this.treePanelDiv;
       delete this.treeBrowserDocument;
     }
 
     if (this.treeIFrame) {
-      this.treeIFrame.removeEventListener("keypress", this.IUI, false);
       this.treeIFrame.removeEventListener("dblclick", this.onTreeDblClick, false);
       this.treeIFrame.removeEventListener("click", this.onTreeClick, false);
       let parent = this.treeIFrame.parentNode;
       parent.removeChild(this.treeIFrame);
       delete this.treeIFrame;
     }
 
     if (this.ioBox) {
--- a/browser/devtools/highlighter/highlighter.jsm
+++ b/browser/devtools/highlighter/highlighter.jsm
@@ -751,79 +751,16 @@ Highlighter.prototype = {
         break;
       case "keypress":
         switch (aEvent.keyCode) {
           case this.chromeWin.KeyEvent.DOM_VK_RETURN:
             this.locked ? this.unlock() : this.lock();
             aEvent.preventDefault();
             aEvent.stopPropagation();
             break;
-          case this.chromeWin.KeyEvent.DOM_VK_LEFT:
-            let node;
-            if (this.node) {
-              node = this.node.parentNode;
-            } else {
-              node = this.defaultSelection;
-            }
-            if (node && this.isNodeHighlightable(node)) {
-              this.highlight(node);
-            }
-            aEvent.preventDefault();
-            aEvent.stopPropagation();
-            break;
-          case this.chromeWin.KeyEvent.DOM_VK_RIGHT:
-            if (this.node) {
-              // Find the first child that is highlightable.
-              for (let i = 0; i < this.node.childNodes.length; i++) {
-                node = this.node.childNodes[i];
-                if (node && this.isNodeHighlightable(node)) {
-                  break;
-                }
-              }
-            } else {
-              node = this.defaultSelection;
-            }
-            if (node && this.isNodeHighlightable(node)) {
-              this.highlight(node, true);
-            }
-            aEvent.preventDefault();
-            aEvent.stopPropagation();
-            break;
-          case this.chromeWin.KeyEvent.DOM_VK_UP:
-            if (this.node) {
-              // Find a previous sibling that is highlightable.
-              node = this.node.previousSibling;
-              while (node && !this.isNodeHighlightable(node)) {
-                node = node.previousSibling;
-              }
-            } else {
-              node = this.defaultSelection;
-            }
-            if (node && this.isNodeHighlightable(node)) {
-              this.highlight(node, true);
-            }
-            aEvent.preventDefault();
-            aEvent.stopPropagation();
-            break;
-          case this.chromeWin.KeyEvent.DOM_VK_DOWN:
-            if (this.node) {
-              // Find a next sibling that is highlightable.
-              node = this.node.nextSibling;
-              while (node && !this.isNodeHighlightable(node)) {
-                node = node.nextSibling;
-              }
-            } else {
-              node = this.defaultSelection;
-            }
-            if (node && this.isNodeHighlightable(node)) {
-              this.highlight(node, true);
-            }
-            aEvent.preventDefault();
-            aEvent.stopPropagation();
-            break;
         }
     }
   },
 
   /**
    * Disable the CSS transitions for a short time to avoid laggy animations
    * during scrolling or resizing.
    */
--- a/browser/devtools/highlighter/inspector.jsm
+++ b/browser/devtools/highlighter/inspector.jsm
@@ -281,16 +281,18 @@ InspectorUI.prototype = {
     this.isDirty = false;
 
     this.progressListener = new InspectorProgressListener(this);
 
     this.chromeWin.addEventListener("keypress", this, false);
 
     // initialize the highlighter
     this.highlighter = new Highlighter(this.chromeWin);
+
+    this.setupNavigationKeys();
     this.highlighterReady();
   },
 
   /**
    * Register the Rule View in the Sidebar.
    */
   registerRuleView: function IUI_registerRuleView()
   {
@@ -345,16 +347,46 @@ InspectorUI.prototype = {
       this.store.setValue(this.winID, "selectedNode", null);
       this.store.setValue(this.winID, "inspecting", true);
       this.store.setValue(this.winID, "isDirty", this.isDirty);
       this.win.addEventListener("pagehide", this, true);
     }
   },
 
   /**
+   * Browse nodes according to the breadcrumbs layout, only for some specific
+   * elements of the UI.
+   */
+   setupNavigationKeys: function IUI_setupNavigationKeys()
+   {
+     // UI elements that are arrow keys sensitive:
+     // - highlighter veil;
+     // - content window (when the highlighter `veil is pointer-events:none`;
+     // - the Inspector toolbar.
+
+     this.onKeypress = this.onKeypress.bind(this);
+
+     this.highlighter.highlighterContainer.addEventListener("keypress",
+       this.onKeypress, true);
+     this.win.addEventListener("keypress", this.onKeypress, true);
+     this.toolbar.addEventListener("keypress", this.onKeypress, true);
+   },
+
+  /**
+   * Remove the event listeners for the arrowkeys.
+   */
+   removeNavigationKeys: function IUI_removeNavigationKeys()
+   {
+      this.highlighter.highlighterContainer.removeEventListener("keypress",
+        this.onKeypress, true);
+      this.win.removeEventListener("keypress", this.onKeypress, true);
+      this.toolbar.removeEventListener("keypress", this.onKeypress, true);
+   },
+
+  /**
    * Close inspector UI and associated panels. Unhighlight and stop inspecting.
    * Remove event listeners for document scrolling, resize,
    * tabContainer.TabSelect and others.
    *
    * @param boolean aKeepStore
    *        Tells if you want the store associated to the current tab/window to
    *        be cleared or not. Set this to true to not clear the store, or false
    *        otherwise.
@@ -370,16 +402,18 @@ InspectorUI.prototype = {
       return;
     }
 
     let winId = new String(this.winID); // retain this to notify observers.
 
     this.closing = true;
     this.toolbar.hidden = true;
 
+    this.removeNavigationKeys();
+
     this.progressListener.destroy();
     delete this.progressListener;
 
     if (!aKeepStore) {
       this.store.deleteStore(this.winID);
       this.win.removeEventListener("pagehide", this, true);
     } else {
       // Update the store before closing.
@@ -587,16 +621,24 @@ InspectorUI.prototype = {
           }
         }
 
         if (this.store.isEmpty()) {
           this.tabbrowser.tabContainer.removeEventListener("TabSelect", this,
                                                          false);
         }
         break;
+      case "keypress":
+        switch (event.keyCode) {
+          case this.chromeWin.KeyEvent.DOM_VK_ESCAPE:
+            this.closeInspectorUI(false);
+            event.preventDefault();
+            event.stopPropagation();
+            break;
+      }
       case "pagehide":
         win = event.originalTarget.defaultView;
         // Skip iframes/frames.
         if (!win || win.frameElement || win.top != win) {
           break;
         }
 
         win.removeEventListener(event.type, this, true);
@@ -606,28 +648,76 @@ InspectorUI.prototype = {
           this.store.deleteStore(winID);
         }
 
         if (this.store.isEmpty()) {
           this.tabbrowser.tabContainer.removeEventListener("TabSelect", this,
                                                          false);
         }
         break;
-      case "keypress":
-        switch (event.keyCode) {
-          case this.chromeWin.KeyEvent.DOM_VK_ESCAPE:
-            this.closeInspectorUI(false);
-            event.preventDefault();
-            event.stopPropagation();
-            break;
+    }
+  },
+
+  /*
+   * handles "keypress" events.
+  */
+  onKeypress: function IUI_onKeypress(event)
+  {
+    let node = null;
+    let bc = this.breadcrumbs;
+    switch (event.keyCode) {
+      case this.chromeWin.KeyEvent.DOM_VK_LEFT:
+        if (bc.currentIndex != 0)
+          node = bc.nodeHierarchy[bc.currentIndex - 1].node;
+        if (node && this.highlighter.isNodeHighlightable(node))
+          this.highlighter.highlight(node);
+        event.preventDefault();
+        event.stopPropagation();
+        break;
+      case this.chromeWin.KeyEvent.DOM_VK_RIGHT:
+        if (bc.currentIndex < bc.nodeHierarchy.length - 1)
+          node = bc.nodeHierarchy[bc.currentIndex + 1].node;
+        if (node && this.highlighter.isNodeHighlightable(node)) {
+          this.highlighter.highlight(node);
         }
+        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;
+          }
+        }
+        if (node && this.highlighter.isNodeHighlightable(node)) {
+          this.highlighter.highlight(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;
+          }
+        }
+        if (node && this.highlighter.isNodeHighlightable(node)) {
+          this.highlighter.highlight(node, true);
+        }
+        event.preventDefault();
+        event.stopPropagation();
         break;
     }
   },
 
+
   /////////////////////////////////////////////////////////////////////////
   //// CssRuleView methods
 
   /**
    * Is the cssRuleView open?
    */
   isRuleViewOpen: function IUI_isRuleViewOpen()
   {
@@ -1715,16 +1805,18 @@ HTMLBreadcrumbs.prototype = {
   setCursor: function BC_setCursor(aIdx)
   {
     // Unselect the previously selected button
     if (this.currentIndex > -1 && this.currentIndex < this.nodeHierarchy.length) {
       this.nodeHierarchy[this.currentIndex].button.removeAttribute("checked");
     }
     if (aIdx > -1) {
       this.nodeHierarchy[aIdx].button.setAttribute("checked", "true");
+      if (this.hadFocus)
+        this.nodeHierarchy[aIdx].button.focus();
     }
     this.currentIndex = aIdx;
   },
 
   /**
    * Get the index of the node in the cache.
    *
    * @param aNode
@@ -1890,16 +1982,20 @@ HTMLBreadcrumbs.prototype = {
 
   /**
    * Update the breadcrumbs display when a new node is selected.
    */
   update: function BC_update()
   {
     this.menu.hidePopup();
 
+    let cmdDispatcher = this.IUI.chromeDoc.commandDispatcher;
+    this.hadFocus = (cmdDispatcher.focusedElement &&
+                     cmdDispatcher.focusedElement.parentNode == this.container);
+
     let selection = this.IUI.selection;
     let idx = this.indexOf(selection);
 
     // Is the node already displayed in the breadcrumbs?
     if (idx > -1) {
       // Yes. We select it.
       this.setCursor(idx);
     } else {
@@ -1919,17 +2015,18 @@ HTMLBreadcrumbs.prototype = {
       idx = this.indexOf(selection);
       this.setCursor(idx);
     }
     // Add the first child of the very last node of the breadcrumbs if possible.
     this.ensureFirstChild();
 
     // Make sure the selected node and its neighbours are visible.
     this.scroll();
-  }
+  },
+
 }
 
 /////////////////////////////////////////////////////////////////////////
 //// Initializers
 
 XPCOMUtils.defineLazyGetter(InspectorUI.prototype, "strings",
   function () {
     return Services.strings.createBundle(