Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(468)

Side by Side Diff: chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_predicate.js

Issue 2367883002: Clean up AutomationPredicate (Closed)
Patch Set: m Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** 5 /**
6 * @fileoverview ChromeVox predicates for the automation extension API. 6 * @fileoverview ChromeVox predicates for the automation extension API.
7 */ 7 */
8 8
9 goog.provide('AutomationPredicate'); 9 goog.provide('AutomationPredicate');
10 goog.provide('AutomationPredicate.Binary'); 10 goog.provide('AutomationPredicate.Binary');
11 goog.provide('AutomationPredicate.Unary'); 11 goog.provide('AutomationPredicate.Unary');
12 12
13 goog.require('constants'); 13 goog.require('constants');
14 14
15 goog.scope(function() { 15 goog.scope(function() {
16 var AutomationNode = chrome.automation.AutomationNode; 16 var AutomationNode = chrome.automation.AutomationNode;
17 var Dir = constants.Dir; 17 var Dir = constants.Dir;
18 var RoleType = chrome.automation.RoleType; 18 var Role = chrome.automation.RoleType;
19 19
20 /** 20 /**
21 * @constructor 21 * @constructor
22 */ 22 */
23 AutomationPredicate = function() {}; 23 AutomationPredicate = function() {};
24 24
25 /** 25 /**
26 * @typedef {function(!AutomationNode) : boolean} 26 * @typedef {function(!AutomationNode) : boolean}
27 */ 27 */
28 AutomationPredicate.Unary; 28 AutomationPredicate.Unary;
29 29
30 /** 30 /**
31 * @typedef {function(!AutomationNode, !AutomationNode) : boolean} 31 * @typedef {function(!AutomationNode, !AutomationNode) : boolean}
32 */ 32 */
33 AutomationPredicate.Binary; 33 AutomationPredicate.Binary;
34 34
35 /** 35 /**
36 * Constructs a predicate given a role. 36 * Constructs a predicate given a list of roles or predicates.
37 * @param {RoleType} role 37 * @param {!Array<AutomationPredicate.Unary|Role>} roles
38 * @return {AutomationPredicate.Unary} 38 * @return {AutomationPredicate.Unary}
39 * @private
39 */ 40 */
40 AutomationPredicate.withRole = function(role) { 41 AutomationPredicate.roles_ = function(roles) {
dmazzoni 2016/09/23 17:52:57 I think it's a little surprising to have a functio
42 if (!roles.length)
43 throw 'Expected at least one predicate.';
41 return function(node) { 44 return function(node) {
42 return node.role == role; 45 return roles.some(function(role) {
46 return role == node.role ||
47 (typeof role == 'function' && role(node));
48 });
43 }; 49 };
44 }; 50 };
45 51
46 /** @type {AutomationPredicate.Unary} */ 52 /** @type {AutomationPredicate.Unary} */
47 AutomationPredicate.checkBox = AutomationPredicate.withRole(RoleType.checkBox); 53 AutomationPredicate.checkBox = AutomationPredicate.roles_([Role.checkBox]);
48 /** @type {AutomationPredicate.Unary} */ 54 /** @type {AutomationPredicate.Unary} */
49 AutomationPredicate.comboBox = AutomationPredicate.withRole(RoleType.comboBox); 55 AutomationPredicate.comboBox = AutomationPredicate.roles_(
56 [Role.comboBox, Role.popUpButton, Role.menuListPopup]);
50 /** @type {AutomationPredicate.Unary} */ 57 /** @type {AutomationPredicate.Unary} */
51 AutomationPredicate.heading = AutomationPredicate.withRole(RoleType.heading); 58 AutomationPredicate.heading = AutomationPredicate.roles_([Role.heading]);
52 /** @type {AutomationPredicate.Unary} */ 59 /** @type {AutomationPredicate.Unary} */
53 AutomationPredicate.inlineTextBox = 60 AutomationPredicate.inlineTextBox =
54 AutomationPredicate.withRole(RoleType.inlineTextBox); 61 AutomationPredicate.roles_([Role.inlineTextBox]);
55 /** @type {AutomationPredicate.Unary} */ 62 /** @type {AutomationPredicate.Unary} */
56 AutomationPredicate.link = AutomationPredicate.withRole(RoleType.link); 63 AutomationPredicate.link = AutomationPredicate.roles_([Role.link]);
57 /** @type {AutomationPredicate.Unary} */ 64 /** @type {AutomationPredicate.Unary} */
58 AutomationPredicate.row = AutomationPredicate.withRole(RoleType.row); 65 AutomationPredicate.row = AutomationPredicate.roles_([Role.row]);
59 /** @type {AutomationPredicate.Unary} */ 66 /** @type {AutomationPredicate.Unary} */
60 AutomationPredicate.table = AutomationPredicate.withRole(RoleType.table); 67 AutomationPredicate.table = AutomationPredicate.roles_([Role.table]);
61 68
62 /** 69 /**
63 * @param {!AutomationNode} node 70 * @param {!AutomationNode} node
64 * @return {boolean} 71 * @return {boolean}
65 */ 72 */
66 AutomationPredicate.button = function(node) { 73 AutomationPredicate.button = function(node) {
67 return /button/i.test(node.role); 74 return /button/i.test(node.role);
68 }; 75 };
69 76
70
71 /** 77 /**
72 * @param {!AutomationNode} node 78 * @param {!AutomationNode} node
73 * @return {boolean} 79 * @return {boolean}
74 */ 80 */
75 AutomationPredicate.editText = function(node) { 81 AutomationPredicate.editText = function(node) {
76 return node.state.editable && 82 return node.state.editable &&
77 node.parent && 83 node.parent &&
78 !node.parent.state.editable; 84 !node.parent.state.editable;
79 }; 85 };
80 86
81 /** 87 /** @type {AutomationPredicate.Unary} */
82 * @param {!AutomationNode} node 88 AutomationPredicate.formField = AutomationPredicate.roles_([
83 * @return {boolean} 89 AutomationPredicate.button,
84 */ 90 Role.checkBox,
85 AutomationPredicate.formField = function(node) { 91 AutomationPredicate.comboBox,
86 switch (node.role) { 92 AutomationPredicate.editText,
87 case 'button': 93 Role.listBox,
88 case 'buttonDropDown': 94 Role.slider,
89 case 'checkBox': 95 Role.tab,
90 case 'comboBox': 96 Role.tree]);
91 case 'date': 97
92 case 'dateTime': 98 /** @type {AutomationPredicate.Unary} */
93 case 'details': 99 AutomationPredicate.landmark = AutomationPredicate.roles_([
94 case 'disclosureTriangle': 100 Role.application,
95 case 'form': 101 Role.banner,
96 case 'menuButton': 102 Role.complementary,
97 case 'menuListPopup': 103 Role.contentInfo,
98 case 'popUpButton': 104 Role.form,
99 case 'radioButton': 105 Role.group,
100 case 'searchBox': 106 Role.main,
101 case 'slider': 107 Role.navigation,
102 case 'spinButton': 108 Role.region,
103 case 'switch': 109 Role.search]);
104 case 'tab':
105 case 'textField':
106 case 'time':
107 case 'toggleButton':
108 case 'tree':
109 return true;
110 }
111 return false;
112 };
113 110
114 /** 111 /**
115 * @param {!AutomationNode} node 112 * @param {!AutomationNode} node
116 * @return {boolean}
117 */
118 AutomationPredicate.landmark = function(node) {
119 switch (node.role) {
120 case 'application':
121 case 'banner':
122 case 'complementary':
123 case 'contentInfo':
124 case 'form':
125 case 'main':
126 case 'navigation':
127 case 'search':
128 return true;
129 }
130 return false;
131 };
132
133 /**
134 * @param {!AutomationNode} node
135 * @return {boolean} 113 * @return {boolean}
136 */ 114 */
137 AutomationPredicate.visitedLink = function(node) { 115 AutomationPredicate.visitedLink = function(node) {
138 return node.state.visited; 116 return node.state.visited;
139 }; 117 };
140 118
141 /** 119 /**
142 * @param {!AutomationNode} node 120 * @param {!AutomationNode} node
143 * @return {boolean} 121 * @return {boolean}
144 */ 122 */
145 AutomationPredicate.focused = function(node) { 123 AutomationPredicate.focused = function(node) {
146 return node.state.focused; 124 return node.state.focused;
147 }; 125 };
148 126
149 /** 127 /**
150 * @param {!AutomationNode} node 128 * @param {!AutomationNode} node
151 * @return {boolean} 129 * @return {boolean}
152 */ 130 */
153 AutomationPredicate.leaf = function(node) { 131 AutomationPredicate.leaf = function(node) {
154 return !node.firstChild || 132 return !node.firstChild ||
155 node.role == RoleType.button || 133 node.role == Role.button ||
156 node.role == RoleType.buttonDropDown || 134 node.role == Role.buttonDropDown ||
157 node.role == RoleType.popUpButton || 135 node.role == Role.popUpButton ||
158 node.role == RoleType.slider || 136 node.role == Role.slider ||
159 node.role == RoleType.textField || 137 node.role == Role.textField ||
160 node.state.invisible || 138 node.state.invisible ||
161 node.children.every(function(n) { 139 node.children.every(function(n) {
162 return n.state.invisible; 140 return n.state.invisible;
163 }); 141 });
164 }; 142 };
165 143
166 /** 144 /**
167 * @param {!AutomationNode} node 145 * @param {!AutomationNode} node
168 * @return {boolean} 146 * @return {boolean}
169 */ 147 */
170 AutomationPredicate.leafWithText = function(node) { 148 AutomationPredicate.leafWithText = function(node) {
171 return AutomationPredicate.leaf(node) && 149 return AutomationPredicate.leaf(node) &&
172 !!(node.name || node.value); 150 !!(node.name || node.value);
173 }; 151 };
174 152
175 /** 153 /**
176 * Matches against leaves or static text nodes. Useful when restricting 154 * Matches against leaves or static text nodes. Useful when restricting
177 * traversal to non-inline textboxes while still allowing them if navigation 155 * traversal to non-inline textboxes while still allowing them if navigation
178 * already entered into an inline textbox. 156 * already entered into an inline textbox.
179 * @param {!AutomationNode} node 157 * @param {!AutomationNode} node
180 * @return {boolean} 158 * @return {boolean}
181 */ 159 */
182 AutomationPredicate.leafOrStaticText = function(node) { 160 AutomationPredicate.leafOrStaticText = function(node) {
183 return AutomationPredicate.leaf(node) || 161 return AutomationPredicate.leaf(node) ||
184 node.role == RoleType.staticText; 162 node.role == Role.staticText;
185 }; 163 };
186 164
187 /** 165 /**
188 * Matches against nodes visited during object navigation. An object as 166 * Matches against nodes visited during object navigation. An object as
189 * defined below, are all nodes that are focusable or static text. When used in 167 * defined below, are all nodes that are focusable or static text. When used in
190 * tree walking, it should visit all nodes that tab traversal would as well as 168 * tree walking, it should visit all nodes that tab traversal would as well as
191 * non-focusable static text. 169 * non-focusable static text.
192 * @param {!AutomationNode} node 170 * @param {!AutomationNode} node
193 * @return {boolean} 171 * @return {boolean}
194 */ 172 */
195 AutomationPredicate.object = function(node) { 173 AutomationPredicate.object = function(node) {
196 // Editable nodes are within a text-like field and don't make sense when 174 // Editable nodes are within a text-like field and don't make sense when
197 // performing object navigation. Users should use line, word, or character 175 // performing object navigation. Users should use line, word, or character
198 // navigation. Only navigate to the top level node. 176 // navigation. Only navigate to the top level node.
199 if (node.parent && node.parent.state.editable) 177 if (node.parent && node.parent.state.editable)
200 return false; 178 return false;
201 179
202 return node.state.focusable || 180 return node.state.focusable ||
203 (AutomationPredicate.leafOrStaticText(node) && 181 (AutomationPredicate.leafOrStaticText(node) &&
204 (/\S+/.test(node.name) || 182 (/\S+/.test(node.name) ||
205 (node.role != RoleType.lineBreak && 183 (node.role != Role.lineBreak &&
206 node.role != RoleType.staticText && 184 node.role != Role.staticText &&
207 node.role != RoleType.inlineTextBox))); 185 node.role != Role.inlineTextBox)));
208 }; 186 };
209 187
210 /** 188 /**
211 * @param {!AutomationNode} first 189 * @param {!AutomationNode} first
212 * @param {!AutomationNode} second 190 * @param {!AutomationNode} second
213 * @return {boolean} 191 * @return {boolean}
214 */ 192 */
215 AutomationPredicate.linebreak = function(first, second) { 193 AutomationPredicate.linebreak = function(first, second) {
216 // TODO(dtseng): Use next/previousOnLin once available. 194 // TODO(dtseng): Use next/previousOnLin once available.
217 var fl = first.location; 195 var fl = first.location;
218 var sl = second.location; 196 var sl = second.location;
219 return fl.top != sl.top || 197 return fl.top != sl.top ||
220 (fl.top + fl.height != sl.top + sl.height); 198 (fl.top + fl.height != sl.top + sl.height);
221 }; 199 };
222 200
223 /** 201 /**
224 * Matches against a node that contains other interesting nodes. 202 * Matches against a node that contains other interesting nodes.
225 * These nodes should always have their subtrees scanned when navigating. 203 * These nodes should always have their subtrees scanned when navigating.
226 * @param {!AutomationNode} node 204 * @param {!AutomationNode} node
227 * @return {boolean} 205 * @return {boolean}
228 */ 206 */
229 AutomationPredicate.container = function(node) { 207 AutomationPredicate.container = function(node) {
230 return AutomationPredicate.structuralContainer(node) || 208 return AutomationPredicate.structuralContainer(node) ||
231 node.role == RoleType.div || 209 node.role == Role.div ||
232 node.role == RoleType.document || 210 node.role == Role.document ||
233 node.role == RoleType.group || 211 node.role == Role.group ||
234 node.role == RoleType.listItem || 212 node.role == Role.listItem ||
235 node.role == RoleType.toolbar || 213 node.role == Role.toolbar ||
236 node.role == RoleType.window || 214 node.role == Role.window ||
237 // For example, crosh. 215 // For example, crosh.
238 (node.role == RoleType.textField && node.state.readOnly) || 216 (node.role == Role.textField && node.state.readOnly) ||
239 (node.state.editable && node.parent && !node.parent.state.editable); 217 (node.state.editable && node.parent && !node.parent.state.editable);
240 }; 218 };
241 219
242 /** 220 /**
243 * Matches against nodes that contain interesting nodes, but should never be 221 * Matches against nodes that contain interesting nodes, but should never be
244 * visited. 222 * visited.
245 * @param {!AutomationNode} node 223 * @param {!AutomationNode} node
246 * @return {boolean} 224 * @return {boolean}
247 */ 225 */
248 AutomationPredicate.structuralContainer = function(node) { 226 AutomationPredicate.structuralContainer = function(node) {
249 return node.role == RoleType.rootWebArea || 227 return node.role == Role.rootWebArea ||
250 node.role == RoleType.embeddedObject || 228 node.role == Role.embeddedObject ||
251 node.role == RoleType.iframe || 229 node.role == Role.iframe ||
252 node.role == RoleType.iframePresentational; 230 node.role == Role.iframePresentational;
253 }; 231 };
254 232
255 /** 233 /**
256 * Returns whether the given node should not be crossed when performing 234 * Returns whether the given node should not be crossed when performing
257 * traversals up the ancestry chain. 235 * traversals up the ancestry chain.
258 * @param {AutomationNode} node 236 * @param {AutomationNode} node
259 * @return {boolean} 237 * @return {boolean}
260 */ 238 */
261 AutomationPredicate.root = function(node) { 239 AutomationPredicate.root = function(node) {
262 switch (node.role) { 240 switch (node.role) {
263 case RoleType.dialog: 241 case Role.dialog:
264 case RoleType.window: 242 case Role.window:
265 return true; 243 return true;
266 case RoleType.toolbar: 244 case Role.toolbar:
267 return node.root.role == RoleType.desktop; 245 return node.root.role == Role.desktop;
268 case RoleType.rootWebArea: 246 case Role.rootWebArea:
269 return !node.parent || node.parent.root.role == RoleType.desktop; 247 return !node.parent || node.parent.root.role == Role.desktop;
270 default: 248 default:
271 return false; 249 return false;
272 } 250 }
273 }; 251 };
274 252
275 /** 253 /**
276 * Nodes that should be ignored while traversing the automation tree. For 254 * Nodes that should be ignored while traversing the automation tree. For
277 * example, apply this predicate when moving to the next object. 255 * example, apply this predicate when moving to the next object.
278 * @param {!AutomationNode} node 256 * @param {!AutomationNode} node
279 * @return {boolean} 257 * @return {boolean}
280 */ 258 */
281 AutomationPredicate.shouldIgnoreNode = function(node) { 259 AutomationPredicate.shouldIgnoreNode = function(node) {
282 // Ignore invisible nodes. 260 // Ignore invisible nodes.
283 if (node.state.invisible || 261 if (node.state.invisible ||
284 (node.location.height == 0 && node.location.width == 0)) 262 (node.location.height == 0 && node.location.width == 0))
285 return true; 263 return true;
286 264
287 // Ignore structural containres. 265 // Ignore structural containres.
288 if (AutomationPredicate.structuralContainer(node)) 266 if (AutomationPredicate.structuralContainer(node))
289 return true; 267 return true;
290 268
291 // Ignore list markers since we already announce listitem role. 269 // Ignore list markers since we already announce listitem role.
292 if (node.role == RoleType.listMarker) 270 if (node.role == Role.listMarker)
293 return true; 271 return true;
294 272
295 // Don't ignore nodes with names. 273 // Don't ignore nodes with names.
296 if (node.name || node.value || node.description) 274 if (node.name || node.value || node.description)
297 return false; 275 return false;
298 276
299 // Ignore some roles. 277 // Ignore some roles.
300 return AutomationPredicate.leaf(node) && 278 return AutomationPredicate.leaf(node) &&
301 (node.role == RoleType.client || 279 (node.role == Role.client ||
dmazzoni 2016/09/23 17:52:57 This looks like a great opportunity to make it mor
302 node.role == RoleType.column || 280 node.role == Role.column ||
303 node.role == RoleType.div || 281 node.role == Role.div ||
304 node.role == RoleType.group || 282 node.role == Role.group ||
305 node.role == RoleType.image || 283 node.role == Role.image ||
306 node.role == RoleType.staticText || 284 node.role == Role.staticText ||
307 node.role == RoleType.tableHeaderContainer); 285 node.role == Role.tableHeaderContainer);
308 }; 286 };
309 287
310 288
311 /** 289 /**
312 * Returns if the node has a meaningful checked state. 290 * Returns if the node has a meaningful checked state.
313 * @param {!AutomationNode} node 291 * @param {!AutomationNode} node
314 * @return {boolean} 292 * @return {boolean}
315 */ 293 */
316 AutomationPredicate.checkable = function(node) { 294 AutomationPredicate.checkable = function(node) {
317 return node.role == RoleType.checkBox || 295 return node.role == Role.checkBox ||
318 node.role == RoleType.radioButton || 296 node.role == Role.radioButton ||
319 node.role == RoleType.menuItemCheckBox || 297 node.role == Role.menuItemCheckBox ||
320 node.role == RoleType.menuItemRadio || 298 node.role == Role.menuItemRadio ||
321 node.role == RoleType.treeItem; 299 node.role == Role.treeItem;
322 }; 300 };
323 301
324 // Table related predicates. 302 // Table related predicates.
325 /** 303 /**
326 * Returns if the node has a cell like role. 304 * Returns if the node has a cell like role.
327 * @param {!AutomationNode} node 305 * @param {!AutomationNode} node
328 * @return {boolean} 306 * @return {boolean}
329 */ 307 */
330 AutomationPredicate.cellLike = function(node) { 308 AutomationPredicate.cellLike = function(node) {
331 return node.role == RoleType.cell || 309 return node.role == Role.cell ||
332 node.role == RoleType.rowHeader || 310 node.role == Role.rowHeader ||
333 node.role == RoleType.columnHeader; 311 node.role == Role.columnHeader;
334 }; 312 };
335 313
336 /** 314 /**
337 * Returns a predicate that will match against the directed next cell taking 315 * Returns a predicate that will match against the directed next cell taking
338 * into account the current ancestor cell's position in the table. 316 * into account the current ancestor cell's position in the table.
339 * @param {AutomationNode} start 317 * @param {AutomationNode} start
340 * @param {{dir: (Dir|undefined), 318 * @param {{dir: (Dir|undefined),
341 * row: (boolean|undefined), 319 * row: (boolean|undefined),
342 * col: (boolean|undefined)}} opts 320 * col: (boolean|undefined)}} opts
343 * |dir|, specifies direction for |row or/and |col| movement by one cell. 321 * |dir|, specifies direction for |row or/and |col| movement by one cell.
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after
404 }; 382 };
405 383
406 /** 384 /**
407 * Returns a predicate that will match against a heading with a specific 385 * Returns a predicate that will match against a heading with a specific
408 * hierarchical level. 386 * hierarchical level.
409 * @param {number} level 1-6 387 * @param {number} level 1-6
410 * @return {AutomationPredicate.Unary} 388 * @return {AutomationPredicate.Unary}
411 */ 389 */
412 AutomationPredicate.makeHeadingPredicate = function(level) { 390 AutomationPredicate.makeHeadingPredicate = function(level) {
413 return function(node) { 391 return function(node) {
414 return node.role == RoleType.heading && node.hierarchicalLevel == level; 392 return node.role == Role.heading && node.hierarchicalLevel == level;
415 }; 393 };
416 }; 394 };
417 395
418 }); // goog.scope 396 }); // goog.scope
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698