1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 /* global PrefCache, Roles, Prefilters, States, Filters, Utils,
7 /* exported TraversalRules */
11 const Cc = Components.classes;
12 const Ci = Components.interfaces;
13 const Cu = Components.utils;
14 const Cr = Components.results;
16 this.EXPORTED_SYMBOLS = ['TraversalRules']; // jshint ignore:line
18 Cu.import('resource://gre/modules/accessibility/Utils.jsm');
19 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
20 XPCOMUtils.defineLazyModuleGetter(this, 'Roles', // jshint ignore:line
21 'resource://gre/modules/accessibility/Constants.jsm');
22 XPCOMUtils.defineLazyModuleGetter(this, 'Filters', // jshint ignore:line
23 'resource://gre/modules/accessibility/Constants.jsm');
24 XPCOMUtils.defineLazyModuleGetter(this, 'States', // jshint ignore:line
25 'resource://gre/modules/accessibility/Constants.jsm');
26 XPCOMUtils.defineLazyModuleGetter(this, 'Prefilters', // jshint ignore:line
27 'resource://gre/modules/accessibility/Constants.jsm');
29 let gSkipEmptyImages = new PrefCache('accessibility.accessfu.skip_empty_images');
31 function BaseTraversalRule(aRoles, aMatchFunc, aPreFilter) {
32 this._explicitMatchRoles = new Set(aRoles);
33 this._matchRoles = aRoles;
34 if (aRoles.indexOf(Roles.LABEL) < 0) {
35 this._matchRoles.push(Roles.LABEL);
37 this._matchFunc = aMatchFunc || function() { return Filters.MATCH; };
38 this.preFilter = aPreFilter || gSimplePreFilter;
41 BaseTraversalRule.prototype = {
42 getMatchRoles: function BaseTraversalRule_getmatchRoles(aRules) {
43 aRules.value = this._matchRoles;
44 return aRules.value.length;
47 match: function BaseTraversalRule_match(aAccessible)
49 let role = aAccessible.role;
50 if (role == Roles.INTERNAL_FRAME) {
51 return (Utils.getMessageManager(aAccessible.DOMNode)) ?
52 Filters.MATCH | Filters.IGNORE_SUBTREE : Filters.IGNORE;
55 let matchResult = this._explicitMatchRoles.has(role) ?
56 this._matchFunc(aAccessible) : Filters.IGNORE;
58 // If we are on a label that nests a checkbox/radio we should land on it.
59 // It is a bigger touch target, and it reduces clutter.
60 if (role == Roles.LABEL && !(matchResult & Filters.IGNORE_SUBTREE)) {
61 let control = Utils.getEmbeddedControl(aAccessible);
62 if (control && this._explicitMatchRoles.has(control.role)) {
63 matchResult = this._matchFunc(control) | Filters.IGNORE_SUBTREE;
70 QueryInterface: XPCOMUtils.generateQI([Ci.nsIAccessibleTraversalRule])
73 var gSimpleTraversalRoles =
87 Roles.CHECK_MENU_ITEM,
89 Roles.RADIO_MENU_ITEM,
99 // Used for traversing in to child OOP frames.
100 Roles.INTERNAL_FRAME];
102 var gSimpleMatchFunc = function gSimpleMatchFunc(aAccessible) {
103 // An object is simple, if it either has a single child lineage,
104 // or has a flat subtree.
105 function isSingleLineage(acc) {
106 for (let child = acc; child; child = child.firstChild) {
107 if (child.childCount > 1) {
114 function isFlatSubtree(acc) {
115 for (let child = acc.firstChild; child; child = child.nextSibling) {
116 // text leafs inherit the actionCount of any ancestor that has a click
118 if ([Roles.TEXT_LEAF, Roles.STATICTEXT].indexOf(child.role) >= 0) {
121 if (child.childCount > 0 || child.actionCount > 0) {
128 switch (aAccessible.role) {
130 // We don't want to ignore the subtree because this is often
131 // where the list box hangs out.
132 return Filters.MATCH;
133 case Roles.TEXT_LEAF:
135 // Nameless text leaves are boring, skip them.
136 let name = aAccessible.name;
137 return (name && name.trim()) ? Filters.MATCH : Filters.IGNORE;
139 case Roles.STATICTEXT:
140 // Ignore prefix static text in list items. They are typically bullets or numbers.
141 return Utils.isListItemDecorator(aAccessible) ?
142 Filters.IGNORE : Filters.MATCH;
144 return TraversalRules._shouldSkipImage(aAccessible);
147 if ((aAccessible.childCount > 0 || aAccessible.name) &&
148 (isSingleLineage(aAccessible) || isFlatSubtree(aAccessible))) {
149 return Filters.MATCH | Filters.IGNORE_SUBTREE;
151 return Filters.IGNORE;
154 let item = aAccessible.childCount === 2 &&
155 aAccessible.firstChild.role === Roles.STATICTEXT ?
156 aAccessible.lastChild : aAccessible;
157 return isSingleLineage(item) || isFlatSubtree(item) ?
158 Filters.MATCH | Filters.IGNORE_SUBTREE : Filters.IGNORE;
161 // Ignore the subtree, if there is one. So that we don't land on
162 // the same content that was already presented by its parent.
163 return Filters.MATCH |
164 Filters.IGNORE_SUBTREE;
168 var gSimplePreFilter = Prefilters.DEFUNCT |
169 Prefilters.INVISIBLE |
170 Prefilters.ARIA_HIDDEN |
171 Prefilters.TRANSPARENT;
173 this.TraversalRules = { // jshint ignore:line
174 Simple: new BaseTraversalRule(gSimpleTraversalRoles, gSimpleMatchFunc),
176 SimpleOnScreen: new BaseTraversalRule(
177 gSimpleTraversalRoles, gSimpleMatchFunc,
178 Prefilters.DEFUNCT | Prefilters.INVISIBLE | Prefilters.ARIA_HIDDEN |
179 Prefilters.TRANSPARENT | Prefilters.OFFSCREEN),
181 Anchor: new BaseTraversalRule(
183 function Anchor_match(aAccessible)
185 // We want to ignore links, only focus named anchors.
186 if (Utils.getState(aAccessible).contains(States.LINKED)) {
187 return Filters.IGNORE;
189 return Filters.MATCH;
193 Button: new BaseTraversalRule(
197 Roles.BUTTONDROPDOWN,
198 Roles.BUTTONDROPDOWNGRID]),
200 Combobox: new BaseTraversalRule(
204 Landmark: new BaseTraversalRule(
206 function Landmark_match(aAccessible) {
207 return Utils.getLandmarkName(aAccessible) ? Filters.MATCH :
212 Entry: new BaseTraversalRule(
214 Roles.PASSWORD_TEXT]),
216 FormElement: new BaseTraversalRule(
220 Roles.BUTTONDROPDOWN,
221 Roles.BUTTONDROPDOWNGRID,
228 Roles.RADIO_MENU_ITEM,
231 Roles.CHECK_MENU_ITEM]),
233 Graphic: new BaseTraversalRule(
235 function Graphic_match(aAccessible) {
236 return TraversalRules._shouldSkipImage(aAccessible);
239 Heading: new BaseTraversalRule(
241 function Heading_match(aAccessible) {
242 return aAccessible.childCount > 0 ? Filters.MATCH : Filters.IGNORE;
245 ListItem: new BaseTraversalRule(
249 Link: new BaseTraversalRule(
251 function Link_match(aAccessible)
253 // We want to ignore anchors, only focus real links.
254 if (Utils.getState(aAccessible).contains(States.LINKED)) {
255 return Filters.MATCH;
257 return Filters.IGNORE;
261 List: new BaseTraversalRule(
263 Roles.DEFINITION_LIST]),
265 PageTab: new BaseTraversalRule(
268 Paragraph: new BaseTraversalRule(
271 function Paragraph_match(aAccessible) {
272 for (let child = aAccessible.firstChild; child; child = child.nextSibling) {
273 if (child.role === Roles.TEXT_LEAF) {
274 return Filters.MATCH | Filters.IGNORE_SUBTREE;
278 return Filters.IGNORE;
281 RadioButton: new BaseTraversalRule(
283 Roles.RADIO_MENU_ITEM]),
285 Separator: new BaseTraversalRule(
288 Table: new BaseTraversalRule(
291 Checkbox: new BaseTraversalRule(
293 Roles.CHECK_MENU_ITEM]),
295 _shouldSkipImage: function _shouldSkipImage(aAccessible) {
296 if (gSkipEmptyImages.value && aAccessible.name === '') {
297 return Filters.IGNORE;
299 return Filters.MATCH;