Bug 1200836 - Land on first atomic object in container traversal. r=yzen
authorEitan Isaacson <eitan@monotonous.org>
Wed, 02 Sep 2015 09:44:30 -0700
changeset 293211 384ff965768a40a70907eb491c42543c6431bd76
parent 293210 b45b4b3f1ed5e7df24e62551e6a5e9fdd5c1acc9
child 293212 96691a3775edb41ed04a1783eaa1d4f10f642317
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersyzen
bugs1200836
milestone43.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 1200836 - Land on first atomic object in container traversal. r=yzen
accessible/jsat/ContentControl.jsm
accessible/jsat/EventManager.jsm
accessible/jsat/Traversal.jsm
accessible/jsat/TraversalRules.jsm
accessible/jsat/content-script.js
accessible/jsat/moz.build
accessible/tests/mochitest/jsat/a11y.ini
accessible/tests/mochitest/jsat/test_traversal.html
accessible/tests/mochitest/jsat/test_traversal_helper.html
--- a/accessible/jsat/ContentControl.jsm
+++ b/accessible/jsat/ContentControl.jsm
@@ -10,29 +10,30 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   'resource://gre/modules/Services.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Utils',
   'resource://gre/modules/accessibility/Utils.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Logger',
   'resource://gre/modules/accessibility/Utils.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Roles',
   'resource://gre/modules/accessibility/Constants.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'TraversalRules',
-  'resource://gre/modules/accessibility/TraversalRules.jsm');
+  'resource://gre/modules/accessibility/Traversal.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'TraversalHelper',
+  'resource://gre/modules/accessibility/Traversal.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Presentation',
   'resource://gre/modules/accessibility/Presentation.jsm');
 
 this.EXPORTED_SYMBOLS = ['ContentControl'];
 
 const MOVEMENT_GRANULARITY_CHARACTER = 1;
 const MOVEMENT_GRANULARITY_WORD = 2;
 const MOVEMENT_GRANULARITY_PARAGRAPH = 8;
 
 this.ContentControl = function ContentControl(aContentScope) {
   this._contentScope = Cu.getWeakReference(aContentScope);
-  this._vcCache = new WeakMap();
   this._childMessageSenders = new WeakMap();
 };
 
 this.ContentControl.prototype = {
   messagesOfInterest: ['AccessFu:MoveCursor',
                        'AccessFu:ClearCursor',
                        'AccessFu:MoveToPoint',
                        'AccessFu:AutoMove',
@@ -123,17 +124,17 @@ this.ContentControl.prototype = {
       // Forwarded succesfully to child cursor.
       return;
     }
 
     if (adjustRange && this.adjustRange(vc.position, action === 'moveNext')) {
       return;
     }
 
-    let moved = vc[action](TraversalRules[aMessage.json.rule]);
+    let moved = TraversalHelper.move(vc, action, aMessage.json.rule);
 
     if (moved) {
       if (origin === 'child') {
         // We just stepped out of a child, clear child cursor.
         Utils.getMessageManager(aMessage.target).sendAsyncMessage(
           'AccessFu:ClearCursor', {});
       } else {
         // We potentially landed on a new child cursor. If so, we want to
--- a/accessible/jsat/EventManager.jsm
+++ b/accessible/jsat/EventManager.jsm
@@ -13,18 +13,16 @@ Cu.import('resource://gre/modules/XPCOMU
 XPCOMUtils.defineLazyModuleGetter(this, 'Services',
   'resource://gre/modules/Services.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Utils',
   'resource://gre/modules/accessibility/Utils.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Logger',
   'resource://gre/modules/accessibility/Utils.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Presentation',
   'resource://gre/modules/accessibility/Presentation.jsm');
-XPCOMUtils.defineLazyModuleGetter(this, 'TraversalRules',
-  'resource://gre/modules/accessibility/TraversalRules.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Roles',
   'resource://gre/modules/accessibility/Constants.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Events',
   'resource://gre/modules/accessibility/Constants.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'States',
   'resource://gre/modules/accessibility/Constants.jsm');
 
 this.EXPORTED_SYMBOLS = ['EventManager'];
rename from accessible/jsat/TraversalRules.jsm
rename to accessible/jsat/Traversal.jsm
--- a/accessible/jsat/TraversalRules.jsm
+++ b/accessible/jsat/Traversal.jsm
@@ -1,50 +1,51 @@
 /* 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, Components, XPCOMUtils */
-/* exported TraversalRules */
+/* exported TraversalRules, TraversalHelper */
 
 'use strict';
 
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
-this.EXPORTED_SYMBOLS = ['TraversalRules']; // jshint ignore:line
+this.EXPORTED_SYMBOLS = ['TraversalRules', 'TraversalHelper']; // 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
   'resource://gre/modules/accessibility/Constants.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'States',  // jshint ignore:line
   'resource://gre/modules/accessibility/Constants.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Prefilters',  // jshint ignore:line
   'resource://gre/modules/accessibility/Constants.jsm');
 
 let gSkipEmptyImages = new PrefCache('accessibility.accessfu.skip_empty_images');
 
-function BaseTraversalRule(aRoles, aMatchFunc, aPreFilter) {
+function BaseTraversalRule(aRoles, aMatchFunc, aPreFilter, aContainerRule) {
   this._explicitMatchRoles = new Set(aRoles);
   this._matchRoles = aRoles;
   if (aRoles.length) {
     if (aRoles.indexOf(Roles.LABEL) < 0) {
       this._matchRoles.push(Roles.LABEL);
     }
     if (aRoles.indexOf(Roles.INTERNAL_FRAME) < 0) {
       // Used for traversing in to child OOP frames.
       this._matchRoles.push(Roles.INTERNAL_FRAME);
     }
   }
   this._matchFunc = aMatchFunc || function() { return Filters.MATCH; };
   this.preFilter = aPreFilter || gSimplePreFilter;
+  this.containerRule = aContainerRule;
 }
 
 BaseTraversalRule.prototype = {
     getMatchRoles: function BaseTraversalRule_getmatchRoles(aRoles) {
       aRoles.value = this._matchRoles;
       return aRoles.value.length;
     },
 
@@ -216,18 +217,17 @@ this.TraversalRules = { // jshint ignore
     [Roles.COMBOBOX,
      Roles.LISTBOX]),
 
   Landmark: new BaseTraversalRule(
     [],
     function Landmark_match(aAccessible) {
       return Utils.getLandmarkName(aAccessible) ? Filters.MATCH :
         Filters.IGNORE;
-    }
-  ),
+    }, null, true),
 
   Entry: new BaseTraversalRule(
     [Roles.ENTRY,
      Roles.PASSWORD_TEXT]),
 
   FormElement: new BaseTraversalRule(
     [Roles.PUSHBUTTON,
      Roles.SPINBUTTON,
@@ -271,17 +271,18 @@ this.TraversalRules = { // jshint ignore
         return Filters.MATCH;
       } else {
         return Filters.IGNORE;
       }
     }),
 
   List: new BaseTraversalRule(
     [Roles.LIST,
-     Roles.DEFINITION_LIST]),
+     Roles.DEFINITION_LIST],
+    null, null, true),
 
   PageTab: new BaseTraversalRule(
     [Roles.PAGETAB]),
 
   Paragraph: new BaseTraversalRule(
     [Roles.PARAGRAPH,
      Roles.SECTION],
     function Paragraph_match(aAccessible) {
@@ -311,8 +312,56 @@ this.TraversalRules = { // jshint ignore
 
   _shouldSkipImage: function _shouldSkipImage(aAccessible) {
     if (gSkipEmptyImages.value && aAccessible.name === '') {
       return Filters.IGNORE;
     }
     return Filters.MATCH;
   }
 };
+
+this.TraversalHelper = {
+  _helperPivotCache: null,
+
+  get helperPivotCache() {
+    delete this.helperPivotCache;
+    this.helperPivotCache = new WeakMap();
+    return this.helperPivotCache;
+  },
+
+  getHelperPivot: function TraversalHelper_getHelperPivot(aRoot) {
+    let pivot = this.helperPivotCache.get(aRoot.DOMNode);
+    if (!pivot) {
+      pivot = Utils.AccRetrieval.createAccessiblePivot(aRoot);
+      this.helperPivotCache.set(aRoot.DOMNode, pivot);
+    }
+
+    return pivot;
+  },
+
+  move: function TraversalHelper_move(aVirtualCursor, aMethod, aRule) {
+    let rule = TraversalRules[aRule];
+
+    if (rule.containerRule) {
+      let moved = false;
+      let helperPivot = this.getHelperPivot(aVirtualCursor.root);
+      helperPivot.position = aVirtualCursor.position;
+
+      // We continue to step through containers until there is one with an
+      // atomic child (via 'Simple') on which we could land.
+      while (!moved) {
+        if (helperPivot[aMethod](rule)) {
+          aVirtualCursor.modalRoot = helperPivot.position;
+          moved = aVirtualCursor.moveFirst(TraversalRules.Simple);
+          aVirtualCursor.modalRoot = null;
+        } else {
+          // If we failed to step to another container, break and return false.
+          break;
+        }
+      }
+
+      return moved;
+    } else {
+      return aVirtualCursor[aMethod](rule);
+    }
+  }
+
+};
--- a/accessible/jsat/content-script.js
+++ b/accessible/jsat/content-script.js
@@ -5,18 +5,16 @@
 let Ci = Components.interfaces;
 let Cu = Components.utils;
 
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Logger',
   'resource://gre/modules/accessibility/Utils.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Presentation',
   'resource://gre/modules/accessibility/Presentation.jsm');
-XPCOMUtils.defineLazyModuleGetter(this, 'TraversalRules',
-  'resource://gre/modules/accessibility/TraversalRules.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Utils',
   'resource://gre/modules/accessibility/Utils.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'EventManager',
   'resource://gre/modules/accessibility/EventManager.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'ContentControl',
   'resource://gre/modules/accessibility/ContentControl.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Roles',
   'resource://gre/modules/accessibility/Constants.jsm');
--- a/accessible/jsat/moz.build
+++ b/accessible/jsat/moz.build
@@ -8,13 +8,13 @@ EXTRA_JS_MODULES.accessibility += [
     'AccessFu.jsm',
     'Constants.jsm',
     'ContentControl.jsm',
     'EventManager.jsm',
     'Gestures.jsm',
     'OutputGenerator.jsm',
     'PointerAdapter.jsm',
     'Presentation.jsm',
-    'TraversalRules.jsm',
+    'Traversal.jsm',
     'Utils.jsm'
 ]
 
 JAR_MANIFESTS += ['jar.mn']
\ No newline at end of file
--- a/accessible/tests/mochitest/jsat/a11y.ini
+++ b/accessible/tests/mochitest/jsat/a11y.ini
@@ -19,8 +19,9 @@ skip-if = buildapp == 'mulet'
 [test_landmarks.html]
 [test_live_regions.html]
 [test_output_mathml.html]
 [test_output.html]
 [test_quicknav_modes.html]
 [test_tables.html]
 [test_pointer_relay.html]
 [test_traversal.html]
+[test_traversal_helper.html]
--- a/accessible/tests/mochitest/jsat/test_traversal.html
+++ b/accessible/tests/mochitest/jsat/test_traversal.html
@@ -16,17 +16,17 @@
   <script type="application/javascript" src="../browser.js"></script>
   <script type="application/javascript" src="../events.js"></script>
   <script type="application/javascript" src="../role.js"></script>
   <script type="application/javascript" src="../states.js"></script>
   <script type="application/javascript" src="../pivot.js"></script>
   <script type="application/javascript" src="../layout.js"></script>
 
   <script type="application/javascript">
-    Components.utils.import("resource://gre/modules/accessibility/TraversalRules.jsm");
+    Components.utils.import("resource://gre/modules/accessibility/Traversal.jsm");
     var gBrowserWnd = null;
     var gQueue = null;
 
     function doTest()
     {
       var doc = currentTabDocument();
       var docAcc = getAccessible(doc, [nsIAccessibleDocument]);
 
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/test_traversal_helper.html
@@ -0,0 +1,111 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Tests AccessFu TraversalRules</title>
+  <meta charset="utf-8" />
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js">
+  </script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/chrome-harness.js">
+  </script>
+
+  <script type="application/javascript" src="../common.js"></script>
+  <script type="application/javascript" src="../browser.js"></script>
+  <script type="application/javascript" src="../events.js"></script>
+  <script type="application/javascript" src="../role.js"></script>
+  <script type="application/javascript" src="../states.js"></script>
+  <script type="application/javascript" src="../pivot.js"></script>
+  <script type="application/javascript" src="../layout.js"></script>
+
+  <script type="application/javascript">
+    Components.utils.import("resource://gre/modules/accessibility/Traversal.jsm");
+
+    var vc;
+
+    function accessibleIs(aAccessible, aExpected, aMessage) {
+      if (!aAccessible && aAccessible == aExpected) {
+        ok(true, "Accessible is null. " + aMessage);
+      } else {
+        ok(aAccessible.DOMNode.id == aExpected || aAccessible.name == aExpected,
+          "expected '" + aExpected + "', got " + prettyName(vc.position) +
+          ". " + aMessage);
+      }
+    }
+
+    function walkSequence(aMethod, aRule, aExpectedSequence) {
+      for (var expected of aExpectedSequence) {
+        ok(TraversalHelper.move(vc, aMethod, aRule),
+          "successfully did " + aMethod + " with " + aRule);
+        accessibleIs(vc.position, expected, "landed on correct accessible");
+      }
+    }
+
+    function testTraversalHelper(aRule, aExpectedSequence) {
+      vc.position = null;
+
+      walkSequence('moveNext', aRule, aExpectedSequence);
+
+      ok(!TraversalHelper.move(vc, 'moveNext', aRule), "reached end");
+
+      TraversalHelper.move(vc, 'moveLast', 'Simple');
+
+      walkSequence('movePrevious', aRule,
+        Array.from(aExpectedSequence).reverse());
+
+      ok(!TraversalHelper.move(vc, 'movePrevious', aRule), "reached start");
+
+      vc.position = null;
+
+      ok(TraversalHelper.move(vc, 'moveFirst', aRule), "moveFirst");
+
+      accessibleIs(vc.position, aExpectedSequence[0],
+        "moveFirst to correct accessible");
+
+      ok(TraversalHelper.move(vc, 'moveLast', aRule), "moveLast");
+
+      accessibleIs(vc.position, aExpectedSequence[aExpectedSequence.length - 1],
+        "moveLast to correct accessible");
+    }
+
+
+    function doTest()
+    {
+      var doc = currentTabDocument();
+      var docAcc = getAccessible(doc, [nsIAccessibleDocument]);
+      vc = docAcc.virtualCursor;
+
+      testTraversalHelper('Landmark',
+        ['heading-1', 'heading-2', 'statusbar-1']);
+
+      testTraversalHelper('List',
+        ['Programming Language', 'listitem-2-1', 'listitem-3-1']);
+
+      vc.position = null;
+
+      SimpleTest.finish();
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addLoadEvent(function () {
+      /* We open a new browser because we need to test with a top-level content
+         document. */
+      openBrowserWindow(
+        doTest,
+        getRootDirectory(window.location.href) + "doc_traversal.html");
+    });
+  </script>
+</head>
+<body id="body">
+
+  <a target="_blank"
+     title="Add tests for AccessFu TraversalRules"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=xxx">Mozilla Bug xxx</a>
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test">
+  </pre>
+</body>
+</html>