Bug 1166321 - [AccessFu] adding support for role='switch'. r=eeejay
authorYura Zenevich <yzenevich@mozilla.com>
Fri, 29 May 2015 11:55:53 -0400
changeset 277129 7526d40755d053850f4cdefc12ace437ca65b924
parent 277128 157f93335ed173523bebf17788ae9a1dd4190e0a
child 277130 419898d4e08757037cdbf50eb245283f461108cb
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-beta@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerseeejay
bugs1166321
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1166321 - [AccessFu] adding support for role='switch'. r=eeejay --- accessible/jsat/EventManager.jsm | 15 ++++++--- accessible/jsat/OutputGenerator.jsm | 36 ++++++++++++++-------- accessible/jsat/TraversalRules.jsm | 13 ++++---- .../mochitest/jsat/doc_content_integration.html | 7 +++++ accessible/tests/mochitest/jsat/doc_traversal.html | 2 ++ accessible/tests/mochitest/jsat/jsatcommon.js | 12 ++++++++ .../mochitest/jsat/test_content_integration.html | 12 ++++++++ accessible/tests/mochitest/jsat/test_output.html | 17 ++++++++++ .../tests/mochitest/jsat/test_traversal.html | 8 +++-- .../en-US/chrome/accessibility/AccessFu.properties | 5 +++ 10 files changed, 102 insertions(+), 25 deletions(-)
accessible/jsat/EventManager.jsm
accessible/jsat/OutputGenerator.jsm
accessible/jsat/TraversalRules.jsm
accessible/tests/mochitest/jsat/doc_content_integration.html
accessible/tests/mochitest/jsat/doc_traversal.html
accessible/tests/mochitest/jsat/jsatcommon.js
accessible/tests/mochitest/jsat/test_content_integration.html
accessible/tests/mochitest/jsat/test_output.html
accessible/tests/mochitest/jsat/test_traversal.html
dom/locales/en-US/chrome/accessibility/AccessFu.properties
--- a/accessible/jsat/EventManager.jsm
+++ b/accessible/jsat/EventManager.jsm
@@ -169,20 +169,27 @@ this.EventManager.prototype = {
 
         break;
       }
       case Events.STATE_CHANGE:
       {
         let event = aEvent.QueryInterface(Ci.nsIAccessibleStateChangeEvent);
         let state = Utils.getState(event);
         if (state.contains(States.CHECKED)) {
-          this.present(
-            Presentation.
-              actionInvoked(aEvent.accessible,
-                            event.isEnabled ? 'check' : 'uncheck'));
+          if (aEvent.accessible.role === Roles.SWITCH) {
+            this.present(
+              Presentation.
+                actionInvoked(aEvent.accessible,
+                              event.isEnabled ? 'on' : 'off'));
+          } else {
+            this.present(
+              Presentation.
+                actionInvoked(aEvent.accessible,
+                              event.isEnabled ? 'check' : 'uncheck'));
+          }
         } else if (state.contains(States.SELECTED)) {
           this.present(
             Presentation.
               actionInvoked(aEvent.accessible,
                             event.isEnabled ? 'select' : 'unselect'));
         }
         break;
       }
--- a/accessible/jsat/OutputGenerator.jsm
+++ b/accessible/jsat/OutputGenerator.jsm
@@ -205,17 +205,17 @@ let OutputGenerator = {
     let typeName = Utils.getAttributes(aAccessible)['text-input-type'];
     // Ignore the the input type="text" case.
     if (!typeName || typeName === 'text') {
       return;
     }
     aOutput.push({string: 'textInputType_' + typeName});
   },
 
-  _addState: function _addState(aOutput, aState) {}, // jshint ignore:line
+  _addState: function _addState(aOutput, aState, aRoleStr) {}, // jshint ignore:line
 
   _addRole: function _addRole(aOutput, aRoleStr) {}, // jshint ignore:line
 
   get outputOrder() {
     if (!this._utteranceOrder) {
       this._utteranceOrder = new PrefCache('accessibility.accessfu.utterance');
     }
     return typeof this._utteranceOrder.value === 'number' ?
@@ -247,16 +247,17 @@ let OutputGenerator = {
     'link': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
     'helpballoon': NAME_FROM_SUBTREE_RULE,
     'list': INCLUDE_DESC | INCLUDE_NAME,
     'listitem': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
     'outline': INCLUDE_DESC,
     'outlineitem': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
     'pagetab': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
     'graphic': INCLUDE_DESC,
+    'switch': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
     'pushbutton': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
     'checkbutton': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
     'radiobutton': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
     'buttondropdown': NAME_FROM_SUBTREE_RULE,
     'combobox': INCLUDE_DESC | INCLUDE_VALUE,
     'droplist': INCLUDE_DESC,
     'progressbar': INCLUDE_DESC | INCLUDE_VALUE,
     'slider': INCLUDE_DESC | INCLUDE_VALUE,
@@ -302,17 +303,17 @@ let OutputGenerator = {
     'app root': IGNORE_EXPLICIT_NAME },
 
   objectOutputFunctions: {
     _generateBaseOutput:
       function _generateBaseOutput(aAccessible, aRoleStr, aState, aFlags) {
         let output = [];
 
         if (aFlags & INCLUDE_DESC) {
-          this._addState(output, aState);
+          this._addState(output, aState, aRoleStr);
           this._addType(output, aAccessible, aRoleStr);
           this._addRole(output, aRoleStr);
         }
 
         if (aFlags & INCLUDE_VALUE && aAccessible.value.trim()) {
           output[this.outputOrder === OUTPUT_DESC_FIRST ? 'push' : 'unshift'](
             aAccessible.value);
         }
@@ -408,23 +409,25 @@ let OutputGenerator = {
  *
  * An utterance is ordered from the least to the most important. Speaking the
  * last string usually makes sense, but speaking the first often won't.
  * For example {@link genForAction} might return ['button', 'clicked'] for a
  * clicked event. Speaking only 'clicked' makes sense. Speaking 'button' does
  * not.
  */
 this.UtteranceGenerator = {  // jshint ignore:line
-  __proto__: OutputGenerator,
+  __proto__: OutputGenerator, // jshint ignore:line
 
   gActionMap: {
     jump: 'jumpAction',
     press: 'pressAction',
     check: 'checkAction',
     uncheck: 'uncheckAction',
+    on: 'onAction',
+    off: 'offAction',
     select: 'selectAction',
     unselect: 'unselectAction',
     open: 'openAction',
     close: 'closeAction',
     switch: 'switchAction',
     click: 'clickAction',
     collapse: 'collapseAction',
     expand: 'expandAction',
@@ -470,17 +473,17 @@ this.UtteranceGenerator = {  // jshint i
   },
 
   genForEditingMode: function genForEditingMode(aIsEditing) {
     return [{string: aIsEditing ? 'editingMode' : 'navigationMode'}];
   },
 
   objectOutputFunctions: {
 
-    __proto__: OutputGenerator.objectOutputFunctions,
+    __proto__: OutputGenerator.objectOutputFunctions, // jshint ignore:line
 
     defaultFunc: function defaultFunc() {
       return this.objectOutputFunctions._generateBaseOutput.apply(
         this, arguments);
     },
 
     heading: function heading(aAccessible, aRoleStr, aState, aFlags) {
       let level = {};
@@ -592,34 +595,39 @@ this.UtteranceGenerator = {  // jshint i
   _getContextStart: function _getContextStart(aContext) {
     return aContext.newAncestry;
   },
 
   _addRole: function _addRole(aOutput, aRoleStr) {
     aOutput.push({string: this._getOutputName(aRoleStr)});
   },
 
-  _addState: function _addState(aOutput, aState) {
+  _addState: function _addState(aOutput, aState, aRoleStr) {
 
     if (aState.contains(States.UNAVAILABLE)) {
       aOutput.push({string: 'stateUnavailable'});
     }
 
     if (aState.contains(States.READONLY)) {
       aOutput.push({string: 'stateReadonly'});
     }
 
     // Don't utter this in Jelly Bean, we let TalkBack do it for us there.
     // This is because we expose the checked information on the node itself.
     // XXX: this means the checked state is always appended to the end,
     // regardless of the utterance ordering preference.
     if ((Utils.AndroidSdkVersion < 16 || Utils.MozBuildApp === 'browser') &&
       aState.contains(States.CHECKABLE)) {
-      let statetr = aState.contains(States.CHECKED) ?
-        'stateChecked' : 'stateNotChecked';
+      let checked = aState.contains(States.CHECKED);
+      let statetr;
+      if (aRoleStr === 'switch') {
+        statetr = checked ? 'stateOn' : 'stateOff';
+      } else {
+        statetr = checked ? 'stateChecked' : 'stateNotChecked';
+      }
       aOutput.push({string: statetr});
     }
 
     if (aState.contains(States.PRESSED)) {
       aOutput.push({string: 'statePressed'});
     }
 
     if (aState.contains(States.EXPANDABLE)) {
@@ -657,17 +665,17 @@ this.UtteranceGenerator = {  // jshint i
       this._addName(utterance, aAccessible, aFlags);
       this._addLandmark(utterance, aAccessible);
 
       return utterance;
     }
 };
 
 this.BrailleGenerator = {  // jshint ignore:line
-  __proto__: OutputGenerator,
+  __proto__: OutputGenerator, // jshint ignore:line
 
   genForContext: function genForContext(aContext) {
     let output = OutputGenerator.genForContext.apply(this, arguments);
 
     let acc = aContext.accessible;
 
     // add the static text indicating a list item; do this for both listitems or
     // direct first children of listitems, because these are both common
@@ -694,17 +702,17 @@ this.BrailleGenerator = {  // jshint ign
       }
     }
 
     return output;
   },
 
   objectOutputFunctions: {
 
-    __proto__: OutputGenerator.objectOutputFunctions,
+    __proto__: OutputGenerator.objectOutputFunctions, // jshint ignore:line
 
     defaultFunc: function defaultFunc() {
       return this.objectOutputFunctions._generateBaseOutput.apply(
         this, arguments);
     },
 
     listitem: function listitem(aAccessible, aRoleStr, aState, aFlags) {
       let braille = [];
@@ -755,23 +763,27 @@ this.BrailleGenerator = {  // jshint ign
       }
 
       return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
     },
 
     _useStateNotRole:
       function _useStateNotRole(aAccessible, aRoleStr, aState, aFlags) {
         let braille = [];
-        this._addState(braille, aState, aAccessible.role);
+        this._addState(braille, aState, aRoleStr);
         this._addName(braille, aAccessible, aFlags);
         this._addLandmark(braille, aAccessible);
 
         return braille;
       },
 
+    switch: function braille_generator_object_output_functions_switch() {
+      return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
+    },
+
     checkbutton: function checkbutton() {
       return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
     },
 
     radiobutton: function radiobutton() {
       return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
     },
 
@@ -791,25 +803,25 @@ this.BrailleGenerator = {  // jshint ign
   _getOutputName: function _getOutputName(aName) {
     return OutputGenerator._getOutputName(aName) + 'Abbr';
   },
 
   _addRole: function _addRole(aBraille, aRoleStr) {
     aBraille.push({string: this._getOutputName(aRoleStr)});
   },
 
-  _addState: function _addState(aBraille, aState, aRole) {
+  _addState: function _addState(aBraille, aState, aRoleStr) {
     if (aState.contains(States.CHECKABLE)) {
       aBraille.push({
         string: aState.contains(States.CHECKED) ?
           this._getOutputName('stateChecked') :
           this._getOutputName('stateUnchecked')
       });
     }
-    if (aRole === Roles.TOGGLE_BUTTON) {
+    if (aRoleStr === 'toggle button') {
       aBraille.push({
         string: aState.contains(States.PRESSED) ?
           this._getOutputName('statePressed') :
           this._getOutputName('stateUnpressed')
       });
     }
   }
 };
--- a/accessible/jsat/TraversalRules.jsm
+++ b/accessible/jsat/TraversalRules.jsm
@@ -1,22 +1,20 @@
 /* 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/. */
 
 /* global PrefCache, Roles, Prefilters, States, Filters, Utils,
-   TraversalRules */
+   TraversalRules, Components, XPCOMUtils */
 /* exported TraversalRules */
 
 'use strict';
 
-const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
-const Cr = Components.results;
 
 this.EXPORTED_SYMBOLS = ['TraversalRules']; // jshint ignore:line
 
 Cu.import('resource://gre/modules/accessibility/Utils.jsm');
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Roles',  // jshint ignore:line
   'resource://gre/modules/accessibility/Constants.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Filters',  // jshint ignore:line
@@ -98,17 +96,18 @@ var gSimpleTraversalRoles =
    Roles.HEADING,
    Roles.SLIDER,
    Roles.SPINBUTTON,
    Roles.OPTION,
    Roles.LISTITEM,
    Roles.GRID_CELL,
    Roles.COLUMNHEADER,
    Roles.ROWHEADER,
-   Roles.STATUSBAR];
+   Roles.STATUSBAR,
+   Roles.SWITCH];
 
 var gSimpleMatchFunc = function gSimpleMatchFunc(aAccessible) {
   // An object is simple, if it either has a single child lineage,
   // or has a flat subtree.
   function isSingleLineage(acc) {
     for (let child = acc; child; child = child.firstChild) {
       if (Utils.visibleChildCount(child) > 1) {
         return false;
@@ -235,17 +234,18 @@ this.TraversalRules = { // jshint ignore
      Roles.LISTBOX,
      Roles.ENTRY,
      Roles.PASSWORD_TEXT,
      Roles.PAGETAB,
      Roles.RADIOBUTTON,
      Roles.RADIO_MENU_ITEM,
      Roles.SLIDER,
      Roles.CHECKBUTTON,
-     Roles.CHECK_MENU_ITEM]),
+     Roles.CHECK_MENU_ITEM,
+     Roles.SWITCH]),
 
   Graphic: new BaseTraversalRule(
     [Roles.GRAPHIC],
     function Graphic_match(aAccessible) {
       return TraversalRules._shouldSkipImage(aAccessible);
     }),
 
   Heading: new BaseTraversalRule(
@@ -297,17 +297,18 @@ this.TraversalRules = { // jshint ignore
   Separator: new BaseTraversalRule(
     [Roles.SEPARATOR]),
 
   Table: new BaseTraversalRule(
     [Roles.TABLE]),
 
   Checkbox: new BaseTraversalRule(
     [Roles.CHECKBUTTON,
-     Roles.CHECK_MENU_ITEM]),
+     Roles.CHECK_MENU_ITEM,
+     Roles.SWITCH /* A type of checkbox that represents on/off values */]),
 
   _shouldSkipImage: function _shouldSkipImage(aAccessible) {
     if (gSkipEmptyImages.value && aAccessible.name === '') {
       return Filters.IGNORE;
     }
     return Filters.MATCH;
   }
 };
--- a/accessible/tests/mochitest/jsat/doc_content_integration.html
+++ b/accessible/tests/mochitest/jsat/doc_content_integration.html
@@ -50,16 +50,22 @@
     }
 
     function changeSliderValue() {
       document.getElementById('slider').setAttribute('aria-valuenow', '5');
       document.getElementById('slider').setAttribute(
         'aria-valuetext', 'medium');
     }
 
+    function toggleLight() {
+      var lightSwitch = document.getElementById('light');
+      lightSwitch.setAttribute('aria-checked',
+        lightSwitch.getAttribute('aria-checked') === 'true' ? 'false' : 'true');
+    }
+
   </script>
   <style>
     #windows {
       position: relative;
       width: 320px;
       height: 480px;
     }
 
@@ -95,14 +101,15 @@
       <p>Do you agree?</p>
       <button onclick="setTimeout(hideAlert, 500)">Yes</button>
       <button onclick="hideAlert()">No</button>
     </div>
     <div id="appframe"></div>
   </div>
   <button id="home">Home</button>
   <button id="fruit" aria-label="apple"></button>
+  <span id="light" role="switch" aria-label="Light" aria-checked="false" onclick="toggleLight()"></span>
   <div id="live" aria-live="polite" aria-label="live">
     <div id="slider" role="slider" aria-label="slider" aria-valuemin="0"
       aria-valuemax="10"  aria-valuenow="0"></div>
   </div>
 </body>
 </html>
--- a/accessible/tests/mochitest/jsat/doc_traversal.html
+++ b/accessible/tests/mochitest/jsat/doc_traversal.html
@@ -138,10 +138,12 @@
       <tr>
         <td>Dirt</td>
         <td>Messy Stuff</td>
       </tr>
     </tbody>
   </table>
   <div id="statusbar-1" role="status">Last sync:<span>2 days ago</span></div>
   <div aria-label="Last sync: 30min ago" id="statusbar-2" role="status"></div>
+
+  <span id="switch-1" role="switch" aria-checked="false" aria-label="Light switch"></span>
 </body>
 </html>
--- a/accessible/tests/mochitest/jsat/jsatcommon.js
+++ b/accessible/tests/mochitest/jsat/jsatcommon.js
@@ -612,16 +612,28 @@ function ExpectedCheckAction(aChecked, a
   }, [{
     eventType: AndroidEvent.VIEW_CLICKED,
     checked: aChecked
   }], aOptions);
 }
 
 ExpectedCheckAction.prototype = Object.create(ExpectedPresent.prototype);
 
+function ExpectedSwitchAction(aSwitched, aOptions) {
+  ExpectedPresent.call(this, {
+    eventType: 'action',
+    data: [{ string: aSwitched ? 'onAction' : 'offAction' }]
+  }, [{
+    eventType: AndroidEvent.VIEW_CLICKED,
+    checked: aSwitched
+  }], aOptions);
+}
+
+ExpectedSwitchAction.prototype = Object.create(ExpectedPresent.prototype);
+
 function ExpectedNameChange(aName, aOptions) {
   ExpectedPresent.call(this, {
     eventType: 'name-change',
     data: aName
   }, null, aOptions);
 }
 
 ExpectedNameChange.prototype = Object.create(ExpectedPresent.prototype);
--- a/accessible/tests/mochitest/jsat/test_content_integration.html
+++ b/accessible/tests/mochitest/jsat/test_content_integration.html
@@ -53,21 +53,33 @@
            new ExpectedCursorChange(['much range', {'string': 'label'}])],
           [ContentMessages.simpleMoveNext,
            new ExpectedCursorChange(['much range', '5', {'string': 'slider'}])],
           [ContentMessages.moveOrAdjustUp(), new ExpectedValueChange('6')],
           [ContentMessages.simpleMoveNext,
            new ExpectedCursorChange(['Home', {'string': 'pushbutton'}])],
           [ContentMessages.simpleMoveNext,
            new ExpectedCursorChange(['apple', {'string': 'pushbutton'}])],
+          [ContentMessages.simpleMoveNext,
+           new ExpectedCursorChange(['Light', {"string": "stateOff"}, {'string': 'switch'}])],
+          // switch on
+          [ContentMessages.activateCurrent(),
+           new ExpectedClickAction({ no_android: true }),
+           new ExpectedSwitchAction(true)],
            [ContentMessages.simpleMoveNext,
            new ExpectedCursorChange(['slider', '0', {'string': 'slider'}])],
 
           // Simple traversal backward
           [ContentMessages.simpleMovePrevious,
+           new ExpectedCursorChange(['Light', {"string": "stateOn"}, {'string': 'switch'}])],
+          // switch off
+          [ContentMessages.activateCurrent(),
+           new ExpectedClickAction({ no_android: true }),
+           new ExpectedSwitchAction(false)],
+          [ContentMessages.simpleMovePrevious,
            new ExpectedCursorChange(['apple', {'string': 'pushbutton'}])],
           [ContentMessages.simpleMovePrevious,
            new ExpectedCursorChange(['Home', {'string': 'pushbutton'}])],
           [ContentMessages.simpleMovePrevious,
            new ExpectedCursorChange(['much range', '6', {'string': 'slider'}, 'such app'])],
           [ContentMessages.moveOrAdjustDown(), new ExpectedValueChange('5')],
           [ContentMessages.simpleMovePrevious,
            new ExpectedCursorChange(['much range', {'string': 'label'}])],
--- a/accessible/tests/mochitest/jsat/test_output.html
+++ b/accessible/tests/mochitest/jsat/test_output.html
@@ -483,16 +483,31 @@ https://bugzilla.mozilla.org/show_bug.cg
                               ["Last sync:", "2 days ago"]],
           expectedBraille: [["Last sync:", "2 days ago"],
                             ["Last sync:", "2 days ago"]]
         }, {
           accOrElmOrID: "statusbar-2",
           expectedUtterance: [["Last sync: 30min ago"],
                               ["Last sync: 30min ago"]],
           expectedBraille: [["Last sync: 30min ago"], ["Last sync: 30min ago"]]
+        }, {
+          accOrElmOrID: "switch-1",
+          expectedUtterance: [[{"string": "stateOn"}, {"string": "switch"},
+            "Simple switch"], ["Simple switch", {"string": "stateOn"},
+            {"string": "switch"}]],
+          expectedBraille: [[{"string": "stateCheckedAbbr"}, "Simple switch"],
+            ["Simple switch", {"string": "stateCheckedAbbr"}]]
+        }, {
+          accOrElmOrID: "switch-2",
+          expectedUtterance: [[{"string": "stateOff"},
+            {"string": "switch"}, "Another switch"], ["Another switch",
+            {"string": "stateOff"}, {"string": "switch"}]],
+          expectedBraille: [
+            [{"string": "stateUncheckedAbbr"}, "Another switch"],
+            ["Another switch", {"string": "stateUncheckedAbbr"}]]
         }];
 
         // Test all possible utterance order preference values.
         tests.forEach(function run(test) {
           var utteranceOrderValues = [0, 1];
           utteranceOrderValues.forEach(
             function testUtteranceOrder(utteranceOrder) {
               SpecialPowers.setIntPref(PREF_UTTERANCE_ORDER, utteranceOrder);
@@ -640,11 +655,13 @@ https://bugzilla.mozilla.org/show_bug.cg
       </select>
       <select id="labelled-combobox" aria-label="Intervals">
         <option value="15">Every 15 min</option>
         <option value="30">Every 30 min</option>
         <option value="null" selected>Never</option>
       </select>
       <div id="statusbar-1" role="status">Last sync:<span>2 days ago</span></div>
       <div aria-label="Last sync: 30min ago" id="statusbar-2" role="status"></div>
+      <span id="switch-1" role="switch" aria-label="Simple switch" aria-checked="true"></span>
+      <span id="switch-2" role="switch" aria-label="Another switch" aria-checked="false"></span>
     </div>
   </body>
 </html>
--- a/accessible/tests/mochitest/jsat/test_traversal.html
+++ b/accessible/tests/mochitest/jsat/test_traversal.html
@@ -51,28 +51,29 @@
 
       queueTraversalSequence(gQueue, docAcc, TraversalRules.FormElement, null,
                              ['input-1-1', 'label-1-2', 'button-1-1',
                               'radio-1-1', 'radio-1-2', 'input-1-3',
                               'input-1-4', 'button-1-2', 'checkbox-1-1',
                               'select-1-1', 'select-1-2', 'checkbox-1-2',
                               'select-1-3', 'input-1-5', 'button-1-3',
                               'button-2-1', 'button-2-2', 'button-2-3',
-                              'button-2-4', 'checkbox-1-5']);
+                              'button-2-4', 'checkbox-1-5', 'switch-1']);
 
       queueTraversalSequence(gQueue, docAcc, TraversalRules.Button, null,
                              ['button-1-1', 'button-1-2', 'button-1-3',
                               'button-2-1', 'button-2-2', 'button-2-3',
                               'button-2-4']);
 
       queueTraversalSequence(gQueue, docAcc, TraversalRules.RadioButton, null,
                              ['radio-1-1', 'radio-1-2']);
 
       queueTraversalSequence(gQueue, docAcc, TraversalRules.Checkbox, null,
-                             ['checkbox-1-1', 'checkbox-1-2', 'checkbox-1-5']);
+                             ['checkbox-1-1', 'checkbox-1-2', 'checkbox-1-5',
+                              'switch-1']);
 
       queueTraversalSequence(gQueue, docAcc, TraversalRules.Combobox, null,
                              ['select-1-1', 'select-1-2', 'select-1-3']);
 
       queueTraversalSequence(gQueue, docAcc, TraversalRules.List, null,
                              ['list-1', 'list-2', 'list-3']);
 
       queueTraversalSequence(gQueue, docAcc, TraversalRules.ListItem, null,
@@ -117,17 +118,18 @@
                               '4. Standard Lisp', 'link-0', ' Lisp',
                               'checkbox-1-5', ' LeLisp', '• JavaScript',
                               'heading-5', 'image-2', 'image-3',
                               'Not actually an image', 'link-1', 'anchor-1',
                               'link-2', 'anchor-2', 'link-3', '3', '1', '4',
                               '1', 'Sunday', 'M', 'Week 1', '3', '4', '7', '2',
                               '5 8', 'gridcell4', 'Just an innocuous separator',
                               'Dirty Words', 'Meaning', 'Mud', 'Wet Dirt',
-                              'Dirt', 'Messy Stuff', 'statusbar-1', 'statusbar-2']);
+                              'Dirt', 'Messy Stuff', 'statusbar-1', 'statusbar-2',
+                              'switch-1']);
 
       gQueue.invoke();
     }
 
     SimpleTest.waitForExplicitFinish();
     addLoadEvent(function () {
       /* We open a new browser because we need to test with a top-level content
          document. */
--- a/dom/locales/en-US/chrome/accessibility/AccessFu.properties
+++ b/dom/locales/en-US/chrome/accessibility/AccessFu.properties
@@ -27,16 +27,17 @@ cell           =       cell
 link           =       link
 list           =       list
 listitem       =       list item
 outline        =       outline
 outlineitem    =       outline item
 pagetab        =       tab
 propertypage   =       property page
 graphic        =       graphic
+switch         =       switch
 pushbutton     =       button
 checkbutton    =       check button
 radiobutton    =       radio button
 combobox       =       combo box
 progressbar    =       progress bar
 slider         =       slider
 spinbutton     =       spin button
 diagram        =       diagram
@@ -122,16 +123,18 @@ rowInfo = Row %S
 spansColumns = spans %S columns
 spansRows = spans %S rows
 
 # Invoked actions
 jumpAction     =      jumped
 pressAction    =      pressed
 checkAction    =      checked
 uncheckAction  =      unchecked
+onAction       =      on
+offAction      =      off
 selectAction   =      selected
 unselectAction =      unselected
 openAction     =      opened
 closeAction    =      closed
 switchAction   =      switched
 clickAction    =      clicked
 collapseAction =      collapsed
 expandAction   =      expanded
@@ -146,17 +149,19 @@ hidden         =      hidden
 tabLoading     =      loading
 tabLoaded      =      loaded
 tabNew         =      new tab
 tabLoadStopped =      loading stopped
 tabReload      =      reloading
 
 # Object states
 stateChecked     =    checked
+stateOn          =    on
 stateNotChecked  =    not checked
+stateOff         =    off
 statePressed     =    pressed
 # No string for a not pressed toggle button
 stateExpanded    =    expanded
 stateCollapsed   =    collapsed
 stateUnavailable =    unavailable
 stateReadonly    =    readonly
 stateRequired    =    required
 stateTraversed   =    visited