Bug 1278473 - write shim Services.focus; r?gregtatum draft
authorTom Tromey <tom@tromey.com>
Tue, 26 Jul 2016 08:03:24 -0600
changeset 392938 f191192ce83cfac8833e2f5f8eee1c9ba7b2ef61
parent 392937 f26953095dea77cba60b66006f907594eb5fe736
child 526432 45d9ec0626070227ffa9e32c893b1670375adcb6
push id24147
push userbmo:ttromey@mozilla.com
push dateTue, 26 Jul 2016 14:11:42 +0000
reviewersgregtatum
bugs1278473
milestone50.0a1
Bug 1278473 - write shim Services.focus; r?gregtatum MozReview-Commit-ID: 1D2EjcoiEz6
devtools/client/shared/shim/Services.js
devtools/client/shared/shim/test/mochitest.ini
devtools/client/shared/shim/test/test_service_focus.html
--- a/devtools/client/shared/shim/Services.js
+++ b/devtools/client/shared/shim/Services.js
@@ -1,17 +1,17 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-/* globals localStorage, window */
+/* globals localStorage, window, document, NodeFilter */
 
 // Some constants from nsIPrefBranch.idl.
 const PREF_INVALID = 0;
 const PREF_STRING = 32;
 const PREF_INT = 64;
 const PREF_BOOL = 128;
 const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
 
@@ -514,16 +514,73 @@ const Services = {
     },
 
     getKeyedHistogramById: function (name) {
       return {
         add: () => {}
       };
     },
   },
+
+  /**
+   * An implementation of Services.focus that holds just the
+   * properties and methods needed by devtools.
+   * @see nsIFocusManager.idl for details.
+   */
+  focus: {
+    // These values match nsIFocusManager in order to make testing a
+    // bit simpler.
+    MOVEFOCUS_FORWARD: 1,
+    MOVEFOCUS_BACKWARD: 2,
+
+    get focusedElement() {
+      if (!document.hasFocus()) {
+        return null;
+      }
+      return document.activeElement;
+    },
+
+    moveFocus: function (window, startElement, type, flags) {
+      if (flags !== 0) {
+        throw new Error("shim Services.focus.moveFocus only accepts flags===0");
+      }
+      if (type !== Services.focus.MOVEFOCUS_FORWARD
+          && type !== Services.focus.MOVEFOCUS_BACKWARD) {
+        throw new Error("shim Services.focus.moveFocus only supports " +
+                        " MOVEFOCUS_FORWARD and MOVEFOCUS_BACKWARD");
+      }
+
+      if (!startElement) {
+        startElement = document.activeElement || document;
+      }
+
+      let iter = document.createTreeWalker(document, NodeFilter.SHOW_ELEMENT, {
+        acceptNode: function (node) {
+          let tabIndex = node.getAttribute("tabindex");
+          if (tabIndex === "-1") {
+            return NodeFilter.FILTER_SKIP;
+          }
+          node.focus();
+          if (document.activeElement == node) {
+            return NodeFilter.FILTER_ACCEPT;
+          }
+          return NodeFilter.FILTER_SKIP;
+        }
+      });
+
+      iter.currentNode = startElement;
+
+      // Sets the focus via side effect in the filter.
+      if (type === Services.focus.MOVEFOCUS_FORWARD) {
+        iter.nextNode();
+      } else {
+        iter.previousNode();
+      }
+    },
+  },
 };
 
 /**
  * Create a new preference.  This is used during startup (see
  * devtools/client/preferences/devtools.js) to install the
  * default preferences.
  *
  * @param {String} name the name of the preference
--- a/devtools/client/shared/shim/test/mochitest.ini
+++ b/devtools/client/shared/shim/test/mochitest.ini
@@ -1,6 +1,7 @@
 [DEFAULT]
 support-files =
   prefs-wrapper.js
 
 [test_service_appinfo.html]
+[test_service_focus.html]
 [test_service_prefs.html]
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/shim/test/test_service_focus.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1278473
+-->
+<head>
+  <title>Test for Bug 1278473 - replace Services.focus</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css"
+        href="chrome://mochikit/content/tests/SimpleTest/test.css">
+
+  <script type="application/javascript;version=1.8">
+  "use strict";
+  var exports = {};
+  </script>
+
+  <script type="application/javascript;version=1.8"
+	  src="resource://devtools/client/shared/shim/Services.js"></script>
+</head>
+<body>
+  <span>
+    <span id="start" testvalue="0" tabindex="0"> </span>
+    <label>
+      <input testvalue="1" type="radio">Hi</input>
+    </label>
+    <label>
+      <input type="radio" tabindex="-1">Bye</input>
+    </label>
+    <label style="display: none">
+      <input id="button3" type="radio" tabindex="-1">Invisible</input>
+    </label>
+    <input id="button4" type="radio" disabled="true">Disabled</input>
+    <span testvalue="2" tabindex="0"> </span>
+  </span>
+
+<script type="application/javascript;version=1.8">
+  "use strict";
+
+  // The test assumes these are identical, so assert it here.
+  is(Services.focus.MOVEFOCUS_BACKWARD, SpecialPowers.Services.focus.MOVEFOCUS_BACKWARD,
+     "check MOVEFOCUS_BACKWARD");
+  is(Services.focus.MOVEFOCUS_FORWARD, SpecialPowers.Services.focus.MOVEFOCUS_FORWARD,
+     "check MOVEFOCUS_FORWARD");
+
+  function moveFocus(element, type, expect) {
+    let current = document.activeElement;
+    const suffix = "(type=" + type + ", to=" + expect + ")";
+
+    // First try with the platform implementation.
+    SpecialPowers.Services.focus.moveFocus(window, element, type, 0);
+    is(document.activeElement.getAttribute("testvalue"), expect,
+       "platform moveFocus " + suffix);
+
+    // Reset the focus and try again with the shim.
+    current.focus();
+    is(document.activeElement, current, "reset " + suffix);
+
+    Services.focus.moveFocus(window, element, type, 0);
+    is(document.activeElement.getAttribute("testvalue"), expect,
+       "shim moveFocus " + suffix);
+  }
+
+  let start = document.querySelector("#start");
+  start.focus();
+  is(document.activeElement.getAttribute("testvalue"), "0", "initial focus");
+
+  moveFocus(null, Services.focus.MOVEFOCUS_FORWARD, "1");
+  moveFocus(null, Services.focus.MOVEFOCUS_FORWARD, "2");
+  let end = document.activeElement;
+  moveFocus(null, Services.focus.MOVEFOCUS_BACKWARD, "1");
+  moveFocus(null, Services.focus.MOVEFOCUS_BACKWARD, "0");
+
+  moveFocus(start, Services.focus.MOVEFOCUS_FORWARD, "1");
+  moveFocus(end, Services.focus.MOVEFOCUS_BACKWARD, "1");
+</script>
+</body>