Wed, 31 Dec 2014 07:16:47 +0100
Revert simplistic fix pending revisit of Mozilla integration attempt.
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 'use strict';
7 const Cc = Components.classes;
8 const Ci = Components.interfaces;
9 const Cu = Components.utils;
10 const Cr = Components.results;
12 this.EXPORTED_SYMBOLS = ['TraversalRules'];
14 Cu.import('resource://gre/modules/accessibility/Utils.jsm');
15 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
16 XPCOMUtils.defineLazyModuleGetter(this, 'Roles',
17 'resource://gre/modules/accessibility/Constants.jsm');
18 XPCOMUtils.defineLazyModuleGetter(this, 'Filters',
19 'resource://gre/modules/accessibility/Constants.jsm');
20 XPCOMUtils.defineLazyModuleGetter(this, 'States',
21 'resource://gre/modules/accessibility/Constants.jsm');
22 XPCOMUtils.defineLazyModuleGetter(this, 'Prefilters',
23 'resource://gre/modules/accessibility/Constants.jsm');
25 let gSkipEmptyImages = new PrefCache('accessibility.accessfu.skip_empty_images');
27 function BaseTraversalRule(aRoles, aMatchFunc, aPreFilter) {
28 this._explicitMatchRoles = new Set(aRoles);
29 this._matchRoles = aRoles;
30 if (aRoles.indexOf(Roles.LABEL) < 0) {
31 this._matchRoles.push(Roles.LABEL);
32 }
33 this._matchFunc = aMatchFunc || function (acc) { return Filters.MATCH; };
34 this.preFilter = aPreFilter || gSimplePreFilter;
35 }
37 BaseTraversalRule.prototype = {
38 getMatchRoles: function BaseTraversalRule_getmatchRoles(aRules) {
39 aRules.value = this._matchRoles;
40 return aRules.value.length;
41 },
43 match: function BaseTraversalRule_match(aAccessible)
44 {
45 let role = aAccessible.role;
46 if (role == Roles.INTERNAL_FRAME) {
47 return (Utils.getMessageManager(aAccessible.DOMNode)) ?
48 Filters.MATCH | Filters.IGNORE_SUBTREE : Filters.IGNORE;
49 }
51 let matchResult = this._explicitMatchRoles.has(role) ?
52 this._matchFunc(aAccessible) : Filters.IGNORE;
54 // If we are on a label that nests a checkbox/radio we should land on it.
55 // It is a bigger touch target, and it reduces clutter.
56 if (role == Roles.LABEL && !(matchResult & Filters.IGNORE_SUBTREE)) {
57 let control = Utils.getEmbeddedControl(aAccessible);
58 if (control && this._explicitMatchRoles.has(control.role)) {
59 matchResult = this._matchFunc(control) | Filters.IGNORE_SUBTREE;
60 }
61 }
63 return matchResult;
64 },
66 QueryInterface: XPCOMUtils.generateQI([Ci.nsIAccessibleTraversalRule])
67 };
69 var gSimpleTraversalRoles =
70 [Roles.MENUITEM,
71 Roles.LINK,
72 Roles.PAGETAB,
73 Roles.GRAPHIC,
74 Roles.STATICTEXT,
75 Roles.TEXT_LEAF,
76 Roles.PUSHBUTTON,
77 Roles.CHECKBUTTON,
78 Roles.RADIOBUTTON,
79 Roles.COMBOBOX,
80 Roles.PROGRESSBAR,
81 Roles.BUTTONDROPDOWN,
82 Roles.BUTTONMENU,
83 Roles.CHECK_MENU_ITEM,
84 Roles.PASSWORD_TEXT,
85 Roles.RADIO_MENU_ITEM,
86 Roles.TOGGLE_BUTTON,
87 Roles.ENTRY,
88 Roles.KEY,
89 Roles.HEADER,
90 Roles.HEADING,
91 Roles.SLIDER,
92 Roles.SPINBUTTON,
93 Roles.OPTION,
94 // Used for traversing in to child OOP frames.
95 Roles.INTERNAL_FRAME];
97 var gSimpleMatchFunc = function gSimpleMatchFunc(aAccessible) {
98 function hasZeroOrSingleChildDescendants () {
99 for (let acc = aAccessible; acc.childCount > 0; acc = acc.firstChild) {
100 if (acc.childCount > 1) {
101 return false;
102 }
103 }
105 return true;
106 }
108 switch (aAccessible.role) {
109 case Roles.COMBOBOX:
110 // We don't want to ignore the subtree because this is often
111 // where the list box hangs out.
112 return Filters.MATCH;
113 case Roles.TEXT_LEAF:
114 {
115 // Nameless text leaves are boring, skip them.
116 let name = aAccessible.name;
117 if (name && name.trim())
118 return Filters.MATCH;
119 else
120 return Filters.IGNORE;
121 }
122 case Roles.STATICTEXT:
123 {
124 let parent = aAccessible.parent;
125 // Ignore prefix static text in list items. They are typically bullets or numbers.
126 if (parent.childCount > 1 && aAccessible.indexInParent == 0 &&
127 parent.role == Roles.LISTITEM)
128 return Filters.IGNORE;
130 return Filters.MATCH;
131 }
132 case Roles.GRAPHIC:
133 return TraversalRules._shouldSkipImage(aAccessible);
134 case Roles.HEADER:
135 case Roles.HEADING:
136 if ((aAccessible.childCount > 0 || aAccessible.name) &&
137 hasZeroOrSingleChildDescendants()) {
138 return Filters.MATCH | Filters.IGNORE_SUBTREE;
139 } else {
140 return Filters.IGNORE;
141 }
142 default:
143 // Ignore the subtree, if there is one. So that we don't land on
144 // the same content that was already presented by its parent.
145 return Filters.MATCH |
146 Filters.IGNORE_SUBTREE;
147 }
148 };
150 var gSimplePreFilter = Prefilters.DEFUNCT |
151 Prefilters.INVISIBLE |
152 Prefilters.ARIA_HIDDEN |
153 Prefilters.TRANSPARENT;
155 this.TraversalRules = {
156 Simple: new BaseTraversalRule(gSimpleTraversalRoles, gSimpleMatchFunc),
158 SimpleOnScreen: new BaseTraversalRule(
159 gSimpleTraversalRoles, gSimpleMatchFunc,
160 Prefilters.DEFUNCT | Prefilters.INVISIBLE | Prefilters.ARIA_HIDDEN |
161 Prefilters.TRANSPARENT | Prefilters.OFFSCREEN),
163 Anchor: new BaseTraversalRule(
164 [Roles.LINK],
165 function Anchor_match(aAccessible)
166 {
167 // We want to ignore links, only focus named anchors.
168 if (Utils.getState(aAccessible).contains(States.LINKED)) {
169 return Filters.IGNORE;
170 } else {
171 return Filters.MATCH;
172 }
173 }),
175 Button: new BaseTraversalRule(
176 [Roles.PUSHBUTTON,
177 Roles.SPINBUTTON,
178 Roles.TOGGLE_BUTTON,
179 Roles.BUTTONDROPDOWN,
180 Roles.BUTTONDROPDOWNGRID]),
182 Combobox: new BaseTraversalRule(
183 [Roles.COMBOBOX,
184 Roles.LISTBOX]),
186 Landmark: new BaseTraversalRule(
187 [],
188 function Landmark_match(aAccessible) {
189 return Utils.getLandmarkName(aAccessible) ? Filters.MATCH :
190 Filters.IGNORE;
191 }
192 ),
194 Entry: new BaseTraversalRule(
195 [Roles.ENTRY,
196 Roles.PASSWORD_TEXT]),
198 FormElement: new BaseTraversalRule(
199 [Roles.PUSHBUTTON,
200 Roles.SPINBUTTON,
201 Roles.TOGGLE_BUTTON,
202 Roles.BUTTONDROPDOWN,
203 Roles.BUTTONDROPDOWNGRID,
204 Roles.COMBOBOX,
205 Roles.LISTBOX,
206 Roles.ENTRY,
207 Roles.PASSWORD_TEXT,
208 Roles.PAGETAB,
209 Roles.RADIOBUTTON,
210 Roles.RADIO_MENU_ITEM,
211 Roles.SLIDER,
212 Roles.CHECKBUTTON,
213 Roles.CHECK_MENU_ITEM]),
215 Graphic: new BaseTraversalRule(
216 [Roles.GRAPHIC],
217 function Graphic_match(aAccessible) {
218 return TraversalRules._shouldSkipImage(aAccessible);
219 }),
221 Heading: new BaseTraversalRule(
222 [Roles.HEADING],
223 function Heading_match(aAccessible) {
224 return aAccessible.childCount > 0 ? Filters.MATCH : Filters.IGNORE;
225 }),
227 ListItem: new BaseTraversalRule(
228 [Roles.LISTITEM,
229 Roles.TERM]),
231 Link: new BaseTraversalRule(
232 [Roles.LINK],
233 function Link_match(aAccessible)
234 {
235 // We want to ignore anchors, only focus real links.
236 if (Utils.getState(aAccessible).contains(States.LINKED)) {
237 return Filters.MATCH;
238 } else {
239 return Filters.IGNORE;
240 }
241 }),
243 List: new BaseTraversalRule(
244 [Roles.LIST,
245 Roles.DEFINITION_LIST]),
247 PageTab: new BaseTraversalRule(
248 [Roles.PAGETAB]),
250 Paragraph: new BaseTraversalRule(
251 [Roles.PARAGRAPH,
252 Roles.SECTION],
253 function Paragraph_match(aAccessible) {
254 for (let child = aAccessible.firstChild; child; child = child.nextSibling) {
255 if (child.role === Roles.TEXT_LEAF) {
256 return Filters.MATCH | Filters.IGNORE_SUBTREE;
257 }
258 }
260 return Filters.IGNORE;
261 }),
263 RadioButton: new BaseTraversalRule(
264 [Roles.RADIOBUTTON,
265 Roles.RADIO_MENU_ITEM]),
267 Separator: new BaseTraversalRule(
268 [Roles.SEPARATOR]),
270 Table: new BaseTraversalRule(
271 [Roles.TABLE]),
273 Checkbox: new BaseTraversalRule(
274 [Roles.CHECKBUTTON,
275 Roles.CHECK_MENU_ITEM]),
277 _shouldSkipImage: function _shouldSkipImage(aAccessible) {
278 if (gSkipEmptyImages.value && aAccessible.name === '') {
279 return Filters.IGNORE;
280 }
281 return Filters.MATCH;
282 }
283 };