Bug 1273211 - Added side panel enter and return controls, added keyboard space selection for controls in netmonitor; r?yzen draft
authorNancy Pang <npang@mozilla.com>
Tue, 24 May 2016 16:45:16 -0400
changeset 371009 9822f96b3b7b753e0612cde5291a875aba11f074
parent 370469 4f3c68b4553dcc21b8cc2fb37b33a7f8de95f874
child 521879 4b8b83ef2b4548fccb5a15f3aa8dd541eef500db
push id19199
push userbmo:npang@mozilla.com
push dateWed, 25 May 2016 18:33:28 +0000
reviewersyzen
bugs1273211
milestone49.0a1
Bug 1273211 - Added side panel enter and return controls, added keyboard space selection for controls in netmonitor; r?yzen MozReview-Commit-ID: HGGTeshLBDs
devtools/client/debugger/test/mochitest/browser.ini
devtools/client/debugger/test/mochitest/browser_dbg_instruments-pane-collapse_keyboard.js
devtools/client/debugger/views/toolbar-view.js
devtools/client/inspector/inspector-panel.js
devtools/client/inspector/test/browser.ini
devtools/client/inspector/test/browser_inspector_pane-toggle-05.js
devtools/client/netmonitor/netmonitor-view.js
--- a/devtools/client/debugger/test/mochitest/browser.ini
+++ b/devtools/client/debugger/test/mochitest/browser.ini
@@ -278,16 +278,18 @@ skip-if = e10s
 [browser_dbg_host-layout.js]
 skip-if = e10s && debug
 [browser_dbg_jump-to-function-definition.js]
 skip-if = e10s && debug
 [browser_dbg_iframes.js]
 skip-if = e10s # TODO
 [browser_dbg_instruments-pane-collapse.js]
 skip-if = e10s && debug
+[browser_dbg_instruments-pane-collapse_keyboard.js]
+skip-if = (os == 'mac' && e10s && debug) # Full keyboard navigation on OSX only works if Full Keyboard Access setting is set to All Control
 [browser_dbg_interrupts.js]
 skip-if = e10s && debug
 [browser_dbg_listaddons.js]
 skip-if = e10s && debug
 tags = addons
 [browser_dbg_listtabs-01.js]
 skip-if = e10s # TODO
 [browser_dbg_listtabs-02.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_instruments-pane-collapse_keyboard.js
@@ -0,0 +1,40 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the debugger panes collapse properly.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
+
+function test() {
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+    Task.spawn(function* () {
+      let gDebugger = aPanel.panelWin.document;
+      let pane = gDebugger.getElementById("instruments-pane");
+      let button = gDebugger.getElementById("instruments-pane-toggle");
+      ok(pane.hasAttribute("pane-collapsed"),
+          "The instruments panel is initially in collapsed state");
+
+      yield togglePane(button, "Press on the toggle button to expand", pane);
+      ok(!pane.hasAttribute("pane-collapsed"),
+          "The instruments panel is in the expanded state");
+
+      yield togglePane(button, "Press on the toggle button to collapse", pane);
+      ok(pane.hasAttribute("pane-collapsed"),
+        "The instruments panel is in the collapsed state");
+
+      closeDebuggerAndFinish(aPanel);
+    });
+  });
+}
+
+function* togglePane(button, message, pane) {
+  let onTransitionEnd = once(pane, "transitionend");
+  info(message);
+  button.focus();
+  EventUtils.synthesizeKey("VK_RETURN", {});
+  yield onTransitionEnd;
+}
--- a/devtools/client/debugger/views/toolbar-view.js
+++ b/devtools/client/debugger/views/toolbar-view.js
@@ -16,17 +16,18 @@
 function ToolbarView(DebuggerController, DebuggerView) {
   dumpn("ToolbarView was instantiated");
 
   this.StackFrames = DebuggerController.StackFrames;
   this.ThreadState = DebuggerController.ThreadState;
   this.DebuggerController = DebuggerController;
   this.DebuggerView = DebuggerView;
 
-  this._onTogglePanesPressed = this._onTogglePanesPressed.bind(this);
+  this._onTogglePanesActivated= this._onTogglePanesActivated.bind(this);
+  this._onTogglePanesPressed= this._onTogglePanesPressed.bind(this);
   this._onResumePressed = this._onResumePressed.bind(this);
   this._onStepOverPressed = this._onStepOverPressed.bind(this);
   this._onStepInPressed = this._onStepInPressed.bind(this);
   this._onStepOutPressed = this._onStepOutPressed.bind(this);
 }
 
 ToolbarView.prototype = {
   get activeThread() {
@@ -57,17 +58,18 @@ ToolbarView.prototype = {
     let stepOutKey = ShortcutUtils.prettifyShortcut(document.getElementById("stepOutKey"));
     this._resumeTooltip = L10N.getFormatStr("resumeButtonTooltip", resumeKey);
     this._pauseTooltip = L10N.getFormatStr("pauseButtonTooltip", resumeKey);
     this._pausePendingTooltip = L10N.getStr("pausePendingButtonTooltip");
     this._stepOverTooltip = L10N.getFormatStr("stepOverTooltip", stepOverKey);
     this._stepInTooltip = L10N.getFormatStr("stepInTooltip", stepInKey);
     this._stepOutTooltip = L10N.getFormatStr("stepOutTooltip", stepOutKey);
 
-    this._instrumentsPaneToggleButton.addEventListener("mousedown", this._onTogglePanesPressed, false);
+    this._instrumentsPaneToggleButton.addEventListener("mousedown", this._onTogglePanesActivated, false);
+    this._instrumentsPaneToggleButton.addEventListener("keydown", this._onTogglePanesPressed, false);
     this._resumeButton.addEventListener("mousedown", this._onResumePressed, false);
     this._stepOverButton.addEventListener("mousedown", this._onStepOverPressed, false);
     this._stepInButton.addEventListener("mousedown", this._onStepInPressed, false);
     this._stepOutButton.addEventListener("mousedown", this._onStepOutPressed, false);
 
     this._stepOverButton.setAttribute("tooltiptext", this._stepOverTooltip);
     this._stepInButton.setAttribute("tooltiptext", this._stepInTooltip);
     this._stepOutButton.setAttribute("tooltiptext", this._stepOutTooltip);
@@ -77,17 +79,20 @@ ToolbarView.prototype = {
   },
 
   /**
    * Destruction function, called when the debugger is closed.
    */
   destroy: function () {
     dumpn("Destroying the ToolbarView");
 
-    this._instrumentsPaneToggleButton.removeEventListener("mousedown", this._onTogglePanesPressed, false);
+    this._instrumentsPaneToggleButton.removeEventListener("mousedown",
+      this._onTogglePanesActivated, false);
+    this._instrumentsPaneToggleButton.removeEventListener("keydown",
+      this._onTogglePanesPressed, false);
     this._resumeButton.removeEventListener("mousedown", this._onResumePressed, false);
     this._stepOverButton.removeEventListener("mousedown", this._onStepOverPressed, false);
     this._stepInButton.removeEventListener("mousedown", this._onStepInPressed, false);
     this._stepOutButton.removeEventListener("mousedown", this._onStepOutPressed, false);
   },
 
   /**
    * Add commands that XUL can fire.
@@ -164,19 +169,29 @@ ToolbarView.prototype = {
       this._stepOverButton
     ];
     for (let button of buttons) {
       button.disabled = !enabled;
     }
   },
 
   /**
+  * Listener handling the toggle button space and return key event.
+  */
+  _onTogglePanesPressed: function (e) {
+    if (e.keyCode === e.DOM_VK_SPACE ||
+      e.keyCode === e.DOM_VK_RETURN) {
+      this._onTogglePanesActivated();
+    }
+  },
+
+  /**
    * Listener handling the toggle button click event.
    */
-  _onTogglePanesPressed: function () {
+  _onTogglePanesActivated: function() {
     DebuggerView.toggleInstrumentsPane({
       visible: DebuggerView.instrumentsPaneHidden,
       animated: true,
       delayed: true
     });
   },
 
   /**
--- a/devtools/client/inspector/inspector-panel.js
+++ b/devtools/client/inspector/inspector-panel.js
@@ -89,17 +89,18 @@ function InspectorPanel(iframeWindow, to
   this._onBeforeNavigate = this._onBeforeNavigate.bind(this);
   this.onNewRoot = this.onNewRoot.bind(this);
   this._setupNodeMenu = this._setupNodeMenu.bind(this);
   this._resetNodeMenu = this._resetNodeMenu.bind(this);
   this._updateSearchResultsLabel = this._updateSearchResultsLabel.bind(this);
   this.onNewSelection = this.onNewSelection.bind(this);
   this.onBeforeNewSelection = this.onBeforeNewSelection.bind(this);
   this.onDetached = this.onDetached.bind(this);
-  this.onPaneToggleButtonClicked = this.onPaneToggleButtonClicked.bind(this);
+  this.onPaneToggleButtonActivated = this.onPaneToggleButtonActivated.bind(this);
+  this.onPaneToggleButtonPressed = this.onPaneToggleButtonPressed.bind(this);
   this._onMarkupFrameLoad = this._onMarkupFrameLoad.bind(this);
 
   this._target.on("will-navigate", this._onBeforeNavigate);
 
   EventEmitter.decorate(this);
 }
 
 exports.InspectorPanel = InspectorPanel;
@@ -402,17 +403,19 @@ InspectorPanel.prototype = {
   /**
    * Add the expand/collapse behavior for the sidebar panel.
    */
   setupSidebarToggle: function () {
     this._paneToggleButton = this.panelDoc.getElementById("inspector-pane-toggle");
     this._paneToggleButton.setAttribute("tooltiptext",
       strings.GetStringFromName("inspector.collapsePane"));
     this._paneToggleButton.addEventListener("mousedown",
-      this.onPaneToggleButtonClicked);
+      this.onPaneToggleButtonActivated);
+    this._paneToggleButton.addEventListener("keydown",
+      this.onPaneToggleButtonPressed);
   },
 
   /**
    * Reset the inspector on new root mutation.
    */
   onNewRoot: function () {
     this._defaultNode = null;
     this.selection.setNodeFront(null);
@@ -653,17 +656,19 @@ InspectorPanel.prototype = {
     this.sidebar.off("select", this._setDefaultSidebar);
     let sidebarDestroyer = this.sidebar.destroy();
     this.sidebar = null;
 
     this.nodemenu.removeEventListener("popupshowing", this._setupNodeMenu, true);
     this.nodemenu.removeEventListener("popuphiding", this._resetNodeMenu, true);
     this.breadcrumbs.destroy();
     this._paneToggleButton.removeEventListener("mousedown",
-      this.onPaneToggleButtonClicked);
+      this.onPaneToggleButtonActivated);
+    this._paneToggleButton.removeEventListener("keydown",
+      this.onPaneToggleButtonPressed);
     this._paneToggleButton = null;
     this.selection.off("new-node-front", this.onNewSelection);
     this.selection.off("before-new-node", this.onBeforeNewSelection);
     this.selection.off("before-new-node-front", this.onBeforeNewSelection);
     this.selection.off("detached-front", this.onDetached);
     let markupDestroyer = this._destroyMarkup();
     this.panelWin.inspector = null;
     this.target = null;
@@ -1032,20 +1037,31 @@ InspectorPanel.prototype = {
     }
 
     this._markupBox = null;
 
     return destroyPromise;
   },
 
   /**
+  * When the pane toggle button is pressed with space and return keys toggle
+  * the pane, change the button state and tooltip.
+  */
+  onPaneToggleButtonPressed: function (e) {
+    if (e.keyCode === e.DOM_VK_SPACE ||
+      e.keyCode === e.DOM_VK_RETURN) {
+      this.onPaneToggleButtonActivated();
+    }
+  },
+
+  /**
    * When the pane toggle button is clicked, toggle the pane, change the button
    * state and tooltip.
    */
-  onPaneToggleButtonClicked: function (e) {
+  onPaneToggleButtonActivated: function(e) {
     let sidePane = this.panelDoc.querySelector("#inspector-sidebar");
     let button = this._paneToggleButton;
     let isVisible = !button.hasAttribute("pane-collapsed");
 
     // Make sure the sidebar has width and height attributes before collapsing
     // because ViewHelpers needs it.
     if (isVisible) {
       let rect = sidePane.getBoundingClientRect();
--- a/devtools/client/inspector/test/browser.ini
+++ b/devtools/client/inspector/test/browser.ini
@@ -103,16 +103,18 @@ skip-if = (e10s && debug) # Bug 1250058 
 [browser_inspector_menu-04-use-in-console.js]
 [browser_inspector_menu-05-attribute-items.js]
 [browser_inspector_menu-06-other.js]
 [browser_inspector_navigation.js]
 [browser_inspector_pane-toggle-01.js]
 [browser_inspector_pane-toggle-02.js]
 [browser_inspector_pane-toggle-03.js]
 [browser_inspector_pane-toggle-04.js]
+[browser_inspector_pane-toggle-05.js]
+skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keyboard Access setting is set to All Control in System Keyboard
 [browser_inspector_picker-stop-on-destroy.js]
 [browser_inspector_picker-stop-on-tool-change.js]
 [browser_inspector_pseudoclass-lock.js]
 [browser_inspector_pseudoclass-menu.js]
 [browser_inspector_reload-01.js]
 [browser_inspector_reload-02.js]
 [browser_inspector_remove-iframe-during-load.js]
 [browser_inspector_search-01.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_pane-toggle-05.js
@@ -0,0 +1,36 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that the inspector toggled panel is visible by default, is hidden after
+// pressing space or the enter key on the toggle button and
+// remains expanded/collapsed when switching hosts.
+
+add_task(function* () {
+  let {toolbox, inspector} = yield openInspectorForURL("about:blank", "side");
+  let panel = inspector.panelDoc.querySelector("#inspector-sidebar");
+  let button = inspector.panelDoc.getElementById("inspector-pane-toggle");
+
+  ok(!panel.hasAttribute("pane-collapsed"), "The panel is in expanded state");
+
+  yield togglePane(button, "Press on the toggle button", panel);
+  ok(panel.hasAttribute("pane-collapsed"), "The panel is in collapsed state");
+  ok(!panel.hasAttribute("animated"),
+    "The collapsed panel will not perform unwanted animations");
+
+  info("Switch the host to bottom type");
+  yield toolbox.switchHost("bottom");
+  ok(panel.hasAttribute("pane-collapsed"), "The panel is in collapsed state");
+
+  yield togglePane(button, "Press on the toggle button to expand the panel again", panel);
+  ok(!panel.hasAttribute("pane-collapsed"), "The panel is in expanded state");
+});
+
+function* togglePane(button, message, panel) {
+  let onTransitionEnd = once(panel, "transitionend");
+  info(message);
+  button.focus();
+  EventUtils.synthesizeKey("VK_RETURN", {});
+  yield onTransitionEnd;
+}
--- a/devtools/client/netmonitor/netmonitor-view.js
+++ b/devtools/client/netmonitor/netmonitor-view.js
@@ -438,17 +438,19 @@ RequestsMenuView.prototype = Heritage.ex
     this.maintainSelectionVisible = true;
 
     this.widget.addEventListener("select", this._onSelect, false);
     this.widget.addEventListener("swap", this._onSwap, false);
     this._splitter.addEventListener("mousemove", this._onResize, false);
     window.addEventListener("resize", this._onResize, false);
 
     this.requestsMenuSortEvent = getKeyWithEvent(this.sortBy.bind(this));
+    this.requestsMenuSortEventPress = getKeyWithEvent(this.sortByPress.bind(this));
     this.requestsMenuFilterEvent = getKeyWithEvent(this.filterOn.bind(this));
+    this.requestsMenuFilterEventPress = getKeyWithEvent(this.filterOnPress.bind(this));
     this.reqeustsMenuClearEvent = this.clear.bind(this);
     this._onContextShowing = this._onContextShowing.bind(this);
     this._onContextNewTabCommand = this.openRequestInTab.bind(this);
     this._onContextCopyUrlCommand = this.copyUrl.bind(this);
     this._onContextCopyImageAsDataUriCommand =
       this.copyImageAsDataUri.bind(this);
     this._onContextCopyResponseCommand = this.copyResponse.bind(this);
     this._onContextResendCommand = this.cloneSelectedRequest.bind(this);
@@ -470,18 +472,22 @@ RequestsMenuView.prototype = Heritage.ex
     this.freetextFilterBox = $("#requests-menu-filter-freetext-text");
     this.freetextFilterBox.addEventListener("input",
       this.requestsFreetextFilterEvent, false);
     this.freetextFilterBox.addEventListener("command",
       this.requestsFreetextFilterEvent, false);
 
     $("#toolbar-labels").addEventListener("click",
       this.requestsMenuSortEvent, false);
+    $("#toolbar-labels").addEventListener("keydown",
+      this.requestsMenuSortEventPress, false);
     $("#requests-menu-filter-buttons").addEventListener("click",
       this.requestsMenuFilterEvent, false);
+    $("#requests-menu-filter-buttons").addEventListener("keydown",
+      this.requestsMenuFilterEventPress, false);
     $("#requests-menu-clear-button").addEventListener("click",
       this.reqeustsMenuClearEvent, false);
     $("#network-request-popup").addEventListener("popupshowing",
       this._onContextShowing, false);
     $("#request-menu-context-newtab").addEventListener("command",
       this._onContextNewTabCommand, false);
     $("#request-menu-context-copy-url").addEventListener("command",
       this._onContextCopyUrlCommand, false);
@@ -545,18 +551,22 @@ RequestsMenuView.prototype = Heritage.ex
 
     this.widget.removeEventListener("select", this._onSelect, false);
     this.widget.removeEventListener("swap", this._onSwap, false);
     this._splitter.removeEventListener("mousemove", this._onResize, false);
     window.removeEventListener("resize", this._onResize, false);
 
     $("#toolbar-labels").removeEventListener("click",
       this.requestsMenuSortEvent, false);
+    $("#toolbar-labels").removeEventListener("keydown",
+      this.requestsMenuSortEventPress, false);
     $("#requests-menu-filter-buttons").removeEventListener("click",
       this.requestsMenuFilterEvent, false);
+    $("#requests-menu-filter-buttons").removeEventListener("keydown",
+      this.requestsMenuFilterEventPress, false);
     $("#requests-menu-clear-button").removeEventListener("click",
       this.reqeustsMenuClearEvent, false);
     this.freetextFilterBox.removeEventListener("input",
       this.requestsFreetextFilterEvent, false);
     this.freetextFilterBox.removeEventListener("command",
       this.requestsFreetextFilterEvent, false);
 
     this.userInputTimer.cancel();
@@ -981,16 +991,32 @@ RequestsMenuView.prototype = Heritage.ex
   },
 
   /**
    * Filters all network requests in this container by a specified type.
    *
    * @param string type
    *        Either "all", "html", "css", "js", "xhr", "fonts", "images", "media"
    *        "flash", "ws" or "other".
+   * @param event e
+   *        The event triggered by the keypress
+   */
+  filterOnPress: function (type, e) {
+    if (e.keyCode === e.DOM_VK_SPACE ||
+      e.keyCode === e.DOM_VK_RETURN) {
+      this.filterOn(type);
+    }
+  },
+
+  /**
+   * Filters all network requests in this container by a specified type.
+   *
+   * @param string type
+   *        Either "all", "html", "css", "js", "xhr", "fonts", "images", "media"
+   *        "flash", "ws" or "other".
    */
   filterOn: function (type = "all") {
     if (type === "all") {
       // The filter "all" is special as it doesn't toggle.
       // - If some filters are selected and 'all' is clicked, the previously
       //   selected filters will be disabled and 'all' is the only active one.
       // - If 'all' is already selected, do nothing.
       if (this._activeFilters.indexOf("all") !== -1) {
@@ -1109,16 +1135,32 @@ RequestsMenuView.prototype = Heritage.ex
   },
 
   /**
    * Sorts all network requests in this container by a specified detail.
    *
    * @param string type
    *        Either "status", "method", "file", "domain", "type", "transferred",
    *        "size" or "waterfall".
+   * @param event e
+   *        The event triggered by the keypress
+   */
+  sortByPress: function (type, e) {
+    if (e.keyCode === e.DOM_VK_SPACE ||
+      e.keyCode === e.DOM_VK_RETURN) {
+      this.sortBy(type);
+    }
+  },
+
+  /**
+   * Sorts all network requests in this container by a specified detail.
+   *
+   * @param string type
+   *        Either "status", "method", "file", "domain", "type", "transferred",
+   *        "size" or "waterfall".
    */
   sortBy: function (type = "waterfall") {
     let target = $("#requests-menu-" + type + "-button");
     let headers = document.querySelectorAll(".requests-menu-header-button");
 
     for (let header of headers) {
       if (header != target) {
         header.removeAttribute("sorted");
@@ -3870,23 +3912,24 @@ function responseIsFresh({ responseHeade
  * Helper method to get a wrapped function which can be bound to as
  * an event listener directly and is executed only when data-key is
  * present in event.target.
  *
  * @param function callback
  *          Function to execute execute when data-key
  *          is present in event.target.
  * @return function
- *          Wrapped function with the target data-key as the first argument.
+ *          Wrapped function with the target data-key as the first argument
+ *          and the event as the second argument.
  */
 function getKeyWithEvent(callback) {
   return function (event) {
     let key = event.target.getAttribute("data-key");
     if (key) {
-      callback.call(null, key);
+      callback.call(null, key, event);
     }
   };
 }
 
 /**
  * Form a data: URI given a mime type, encoding, and some text.
  *
  * @param {String} mimeType the mime type