Bug 1331971 - Can't navigate out of style editor content window using keyboard. r=gl draft
authorNancy Pang <npang@mozilla.com>
Wed, 01 Feb 2017 12:56:03 -0500
changeset 469127 e47603380f7ea21a1a77fb6bcb53371b5ba6cb55
parent 469119 2e232a53e2dbc84cfc7694dff8d1c1ca4915559d
child 544097 71c8b4e36483ef413bea017c4ff157d7bafb85c6
push id43619
push userbmo:npang@mozilla.com
push dateWed, 01 Feb 2017 17:57:10 +0000
reviewersgl
bugs1331971
milestone54.0a1
Bug 1331971 - Can't navigate out of style editor content window using keyboard. r=gl MozReview-Commit-ID: 14mNmuLF35h
devtools/client/sourceeditor/editor.js
devtools/client/styleeditor/StyleEditorUI.jsm
devtools/client/styleeditor/test/browser.ini
devtools/client/styleeditor/test/browser_styleeditor_sourceditor_keyboard_navigation.js
devtools/client/themes/styleeditor.css
--- a/devtools/client/sourceeditor/editor.js
+++ b/devtools/client/sourceeditor/editor.js
@@ -366,16 +366,21 @@ Editor.prototype = {
       } else if (ev.deltaMode == ev.DOM_DELTA_PAGE) {
         deltaX *= cm.getWrapperElement().clientWidth;
         deltaY *= cm.getWrapperElement().clientHeight;
       }
 
       cm.getScrollerElement().scrollBy(deltaX, deltaY);
     });
 
+    // Make code conatiner focusable
+    cm.getWrapperElement().tabIndex = 0;
+    let textarea = cm.getWrapperElement().querySelector("textarea");
+    textarea.tabIndex = -1;
+
     cm.getWrapperElement().addEventListener("contextmenu", ev => {
       ev.preventDefault();
 
       if (!this.config.contextMenu) {
         return;
       }
 
       let popup = this.config.contextMenu;
@@ -490,16 +495,38 @@ Editor.prototype = {
     // turn it off and back on again so the proper mode can be used.
     if (this.config.autocomplete) {
       this.setOption("autocomplete", false);
       this.setOption("autocomplete", true);
     }
   },
 
   /**
+   * Handles keyboard navigation
+   * - Focuses on editor code container when Escape is pressed
+   * - Focuses on editor code text area when Enter is pressed
+   * - Tabbing while focused on code container moves to next focusable item
+   */
+  onKeyDown: function (e) {
+    let {key, target} = e;
+    let codeWrapper = this.codeMirror.getWrapperElement();
+    let textArea = codeWrapper.querySelector("textarea");
+
+    if (key === "Escape" && target == textArea) {
+      e.stopPropagation();
+      e.preventDefault();
+      codeWrapper.focus();
+    } else if (key === "Enter" && target == codeWrapper) {
+      e.preventDefault();
+      // Focus into editor's text area
+      textArea.focus();
+    }
+  },
+
+  /**
    * Returns text from the text area. If line argument is provided
    * the method returns only that line.
    */
   getText: function (line) {
     let cm = editors.get(this);
 
     if (line == null) {
       return cm.getValue();
--- a/devtools/client/styleeditor/StyleEditorUI.jsm
+++ b/devtools/client/styleeditor/StyleEditorUI.jsm
@@ -79,16 +79,17 @@ function StyleEditorUI(debuggee, target,
   this._onStyleSheetCreated = this._onStyleSheetCreated.bind(this);
   this._onNewDocument = this._onNewDocument.bind(this);
   this._onMediaPrefChanged = this._onMediaPrefChanged.bind(this);
   this._updateMediaList = this._updateMediaList.bind(this);
   this._clear = this._clear.bind(this);
   this._onError = this._onError.bind(this);
   this._updateOpenLinkItem = this._updateOpenLinkItem.bind(this);
   this._openLinkNewTab = this._openLinkNewTab.bind(this);
+  this._onKeyDown = this._onKeyDown.bind(this);
 
   this._prefObserver = new PrefObserver("devtools.styleeditor.");
   this._prefObserver.on(PREF_ORIG_SOURCES, this._onNewDocument);
   this._prefObserver.on(PREF_MEDIA_SIDEBAR, this._onMediaPrefChanged);
 }
 this.StyleEditorUI = StyleEditorUI;
 
 StyleEditorUI.prototype = {
@@ -116,16 +117,21 @@ StyleEditorUI.prototype = {
   /*
    * Index of selected stylesheet in document.styleSheets
    */
   get selectedStyleSheetIndex() {
     return this.selectedEditor ?
            this.selectedEditor.styleSheet.styleSheetIndex : -1;
   },
 
+  _onKeyDown: function (e) {
+    let {sourceEditor} = this.selectedEditor;
+    sourceEditor.onKeyDown(e);
+  },
+
   /**
    * Initiates the style editor ui creation, the inspector front to get
    * reference to the walker and the selector highlighter if available
    */
   initialize: Task.async(function* () {
     yield this.initializeHighlighter();
 
     this.createUI();
@@ -197,16 +203,20 @@ StyleEditorUI.prototype = {
     this._mediaItem.addEventListener("command",
                                      this._toggleMediaSidebar);
 
     this._openLinkNewTabItem =
       this._panelDoc.getElementById("context-openlinknewtab");
     this._openLinkNewTabItem.addEventListener("command",
                                               this._openLinkNewTab);
 
+    this._detailContent = this._panelDoc.querySelector(
+      ".devtools-main-content");
+    this._detailContent.addEventListener("keydown", this._onKeyDown);
+
     let nav = this._panelDoc.querySelector(".splitview-controller");
     nav.setAttribute("width", Services.prefs.getIntPref(PREF_NAV_WIDTH));
   },
 
   /**
    * Listener handling the 'gear menu' popup showing event.
    * Update options menu items to reflect current preference settings.
    */
--- a/devtools/client/styleeditor/test/browser.ini
+++ b/devtools/client/styleeditor/test/browser.ini
@@ -88,16 +88,17 @@ skip-if = e10s && debug # Bug 1252201 - 
 [browser_styleeditor_opentab.js]
 [browser_styleeditor_pretty.js]
 [browser_styleeditor_private_perwindowpb.js]
 [browser_styleeditor_reload.js]
 [browser_styleeditor_scroll.js]
 [browser_styleeditor_sv_keynav.js]
 [browser_styleeditor_sv_resize.js]
 [browser_styleeditor_selectstylesheet.js]
+[browser_styleeditor_sourceditor_keyboard_navigation.js]
 [browser_styleeditor_sourcemaps.js]
 [browser_styleeditor_sourcemaps_inline.js]
 [browser_styleeditor_sourcemap_large.js]
 [browser_styleeditor_sourcemap_watching.js]
 [browser_styleeditor_sync.js]
 [browser_styleeditor_syncAddProperty.js]
 [browser_styleeditor_syncAddRule.js]
 [browser_styleeditor_syncAlreadyOpen.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/styleeditor/test/browser_styleeditor_sourceditor_keyboard_navigation.js
@@ -0,0 +1,31 @@
+/* vim: set 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 tests keyboard navigation out and into style editor
+
+const TESTCASE_URI = TEST_BASE_HTTPS + "media-rules.html";
+
+const LINE_NO = 0;
+const COL_NO = 0;
+
+add_task(function* () {
+  let { ui } = yield openStyleEditorForURL(TESTCASE_URI);
+  let editor = ui.editors[0];
+
+  info("Select the first style sheet");
+  yield ui.selectStyleSheet(editor.styleSheet, LINE_NO, COL_NO);
+  let editordoc = editor.sourceEditor.container.contentDocument;
+  let codeContainer = editordoc.querySelector(".CodeMirror");
+  let textArea = editordoc.querySelector("textarea");
+
+  info("Focus on container");
+  EventUtils.synthesizeKey("VK_ESCAPE", {});
+  is(codeContainer, editordoc.activeElement, "Container is focused");
+
+  info("Enter editor");
+  EventUtils.synthesizeKey("VK_RETURN", {});
+  is(textArea, editordoc.activeElement, "Editor is focused");
+});
--- a/devtools/client/themes/styleeditor.css
+++ b/devtools/client/themes/styleeditor.css
@@ -137,16 +137,20 @@ li.error > .stylesheet-info > .styleshee
 }
 
 .stylesheet-sidebar {
   max-width: 400px;
   min-width: 100px;
   border-color: var(--theme-splitter-color);
 }
 
+.stylesheet-sidebar[hidden] {
+  display: none;
+}
+
 .theme-light .media-rule-label {
   border-bottom-color: #cddae5; /* Grey */
 }
 
 .theme-dark .media-rule-label {
   border-bottom-color: #303b47; /* Grey */
 }