Bumping manifests a=b2g-bump
[gecko.git] / accessible / jsat / TraversalRules.jsm
blob954e9b7a8ab245ac11cf408bb30af9040a586739
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,
6    TraversalRules */
7 /* exported TraversalRules */
9 'use strict';
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);
36   }
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;
45     },
47     match: function BaseTraversalRule_match(aAccessible)
48     {
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;
53       }
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;
64         }
65       }
67       return matchResult;
68     },
70     QueryInterface: XPCOMUtils.generateQI([Ci.nsIAccessibleTraversalRule])
73 var gSimpleTraversalRoles =
74   [Roles.MENUITEM,
75    Roles.LINK,
76    Roles.PAGETAB,
77    Roles.GRAPHIC,
78    Roles.STATICTEXT,
79    Roles.TEXT_LEAF,
80    Roles.PUSHBUTTON,
81    Roles.CHECKBUTTON,
82    Roles.RADIOBUTTON,
83    Roles.COMBOBOX,
84    Roles.PROGRESSBAR,
85    Roles.BUTTONDROPDOWN,
86    Roles.BUTTONMENU,
87    Roles.CHECK_MENU_ITEM,
88    Roles.PASSWORD_TEXT,
89    Roles.RADIO_MENU_ITEM,
90    Roles.TOGGLE_BUTTON,
91    Roles.ENTRY,
92    Roles.KEY,
93    Roles.HEADER,
94    Roles.HEADING,
95    Roles.SLIDER,
96    Roles.SPINBUTTON,
97    Roles.OPTION,
98    Roles.LISTITEM,
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) {
108         return false;
109       }
110     }
111     return true;
112   }
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
117       // listener.
118       if ([Roles.TEXT_LEAF, Roles.STATICTEXT].indexOf(child.role) >= 0) {
119         continue;
120       }
121       if (child.childCount > 0 || child.actionCount > 0) {
122         return false;
123       }
124     }
125     return true;
126   }
128   switch (aAccessible.role) {
129   case Roles.COMBOBOX:
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:
134     {
135       // Nameless text leaves are boring, skip them.
136       let name = aAccessible.name;
137       return (name && name.trim()) ? Filters.MATCH : Filters.IGNORE;
138     }
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;
143   case Roles.GRAPHIC:
144     return TraversalRules._shouldSkipImage(aAccessible);
145   case Roles.HEADER:
146   case Roles.HEADING:
147     if ((aAccessible.childCount > 0 || aAccessible.name) &&
148         (isSingleLineage(aAccessible) || isFlatSubtree(aAccessible))) {
149       return Filters.MATCH | Filters.IGNORE_SUBTREE;
150     }
151     return Filters.IGNORE;
152   case Roles.LISTITEM:
153     {
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;
159     }
160   default:
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;
165   }
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(
182     [Roles.LINK],
183     function Anchor_match(aAccessible)
184     {
185       // We want to ignore links, only focus named anchors.
186       if (Utils.getState(aAccessible).contains(States.LINKED)) {
187         return Filters.IGNORE;
188       } else {
189         return Filters.MATCH;
190       }
191     }),
193   Button: new BaseTraversalRule(
194     [Roles.PUSHBUTTON,
195      Roles.SPINBUTTON,
196      Roles.TOGGLE_BUTTON,
197      Roles.BUTTONDROPDOWN,
198      Roles.BUTTONDROPDOWNGRID]),
200   Combobox: new BaseTraversalRule(
201     [Roles.COMBOBOX,
202      Roles.LISTBOX]),
204   Landmark: new BaseTraversalRule(
205     [],
206     function Landmark_match(aAccessible) {
207       return Utils.getLandmarkName(aAccessible) ? Filters.MATCH :
208         Filters.IGNORE;
209     }
210   ),
212   Entry: new BaseTraversalRule(
213     [Roles.ENTRY,
214      Roles.PASSWORD_TEXT]),
216   FormElement: new BaseTraversalRule(
217     [Roles.PUSHBUTTON,
218      Roles.SPINBUTTON,
219      Roles.TOGGLE_BUTTON,
220      Roles.BUTTONDROPDOWN,
221      Roles.BUTTONDROPDOWNGRID,
222      Roles.COMBOBOX,
223      Roles.LISTBOX,
224      Roles.ENTRY,
225      Roles.PASSWORD_TEXT,
226      Roles.PAGETAB,
227      Roles.RADIOBUTTON,
228      Roles.RADIO_MENU_ITEM,
229      Roles.SLIDER,
230      Roles.CHECKBUTTON,
231      Roles.CHECK_MENU_ITEM]),
233   Graphic: new BaseTraversalRule(
234     [Roles.GRAPHIC],
235     function Graphic_match(aAccessible) {
236       return TraversalRules._shouldSkipImage(aAccessible);
237     }),
239   Heading: new BaseTraversalRule(
240     [Roles.HEADING],
241     function Heading_match(aAccessible) {
242       return aAccessible.childCount > 0 ? Filters.MATCH : Filters.IGNORE;
243     }),
245   ListItem: new BaseTraversalRule(
246     [Roles.LISTITEM,
247      Roles.TERM]),
249   Link: new BaseTraversalRule(
250     [Roles.LINK],
251     function Link_match(aAccessible)
252     {
253       // We want to ignore anchors, only focus real links.
254       if (Utils.getState(aAccessible).contains(States.LINKED)) {
255         return Filters.MATCH;
256       } else {
257         return Filters.IGNORE;
258       }
259     }),
261   List: new BaseTraversalRule(
262     [Roles.LIST,
263      Roles.DEFINITION_LIST]),
265   PageTab: new BaseTraversalRule(
266     [Roles.PAGETAB]),
268   Paragraph: new BaseTraversalRule(
269     [Roles.PARAGRAPH,
270      Roles.SECTION],
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;
275         }
276       }
278       return Filters.IGNORE;
279     }),
281   RadioButton: new BaseTraversalRule(
282     [Roles.RADIOBUTTON,
283      Roles.RADIO_MENU_ITEM]),
285   Separator: new BaseTraversalRule(
286     [Roles.SEPARATOR]),
288   Table: new BaseTraversalRule(
289     [Roles.TABLE]),
291   Checkbox: new BaseTraversalRule(
292     [Roles.CHECKBUTTON,
293      Roles.CHECK_MENU_ITEM]),
295   _shouldSkipImage: function _shouldSkipImage(aAccessible) {
296     if (gSkipEmptyImages.value && aAccessible.name === '') {
297       return Filters.IGNORE;
298     }
299     return Filters.MATCH;
300   }