OLD | NEW |
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 var AutomationEvent = require('automationEvent').AutomationEvent; | 5 var AutomationEvent = require('automationEvent').AutomationEvent; |
6 var automationInternal = | 6 var automationInternal = |
7 require('binding').Binding.create('automationInternal').generate(); | 7 require('binding').Binding.create('automationInternal').generate(); |
8 var IsInteractPermitted = | 8 var IsInteractPermitted = |
9 requireNative('automationInternal').IsInteractPermitted; | 9 requireNative('automationInternal').IsInteractPermitted; |
10 | 10 |
11 /** | |
12 * @param {number} axTreeID The id of the accessibility tree. | |
13 * @return {?number} The id of the root node. | |
14 */ | |
15 var GetRootID = requireNative('automationInternal').GetRootID; | |
16 | |
17 /** | |
18 * @param {number} axTreeID The id of the accessibility tree. | |
19 * @param {number} nodeID The id of a node. | |
20 * @return {?number} The id of the node's parent, or undefined if it's the | |
21 * root of its tree or if the tree or node wasn't found. | |
22 */ | |
23 var GetParentID = requireNative('automationInternal').GetParentID; | |
24 | |
25 /** | |
26 * @param {number} axTreeID The id of the accessibility tree. | |
27 * @param {number} nodeID The id of a node. | |
28 * @return {?number} The number of children of the node, or undefined if | |
29 * the tree or node wasn't found. | |
30 */ | |
31 var GetChildCount = requireNative('automationInternal').GetChildCount; | |
32 | |
33 /** | |
34 * @param {number} axTreeID The id of the accessibility tree. | |
35 * @param {number} nodeID The id of a node. | |
36 * @param {number} childIndex An index of a child of this node. | |
37 * @return {?number} The id of the child at the given index, or undefined | |
38 * if the tree or node or child at that index wasn't found. | |
39 */ | |
40 var GetChildIDAtIndex = requireNative('automationInternal').GetChildIDAtIndex; | |
41 | |
42 /** | |
43 * @param {number} axTreeID The id of the accessibility tree. | |
44 * @param {number} nodeID The id of a node. | |
45 * @return {?number} The index of this node in its parent, or undefined if | |
46 * the tree or node or node parent wasn't found. | |
47 */ | |
48 var GetIndexInParent = requireNative('automationInternal').GetIndexInParent; | |
49 | |
50 /** | |
51 * @param {number} axTreeID The id of the accessibility tree. | |
52 * @param {number} nodeID The id of a node. | |
53 * @return {?Object} An object with a string key for every state flag set, | |
54 * or undefined if the tree or node or node parent wasn't found. | |
55 */ | |
56 var GetState = requireNative('automationInternal').GetState; | |
57 | |
58 /** | |
59 * @param {number} axTreeID The id of the accessibility tree. | |
60 * @param {number} nodeID The id of a node. | |
61 * @return {string} The role of the node, or undefined if the tree or | |
62 * node wasn't found. | |
63 */ | |
64 var GetRole = requireNative('automationInternal').GetRole; | |
65 | |
66 /** | |
67 * @param {number} axTreeID The id of the accessibility tree. | |
68 * @param {number} nodeID The id of a node. | |
69 * @return {?automation.Rect} The location of the node, or undefined if | |
70 * the tree or node wasn't found. | |
71 */ | |
72 var GetLocation = requireNative('automationInternal').GetLocation; | |
73 | |
74 /** | |
75 * @param {number} axTreeID The id of the accessibility tree. | |
76 * @param {number} nodeID The id of a node. | |
77 * @param {string} attr The name of a string attribute. | |
78 * @return {?string} The value of this attribute, or undefined if the tree, | |
79 * node, or attribute wasn't found. | |
80 */ | |
81 var GetStringAttribute = requireNative('automationInternal').GetStringAttribute; | |
82 | |
83 /** | |
84 * @param {number} axTreeID The id of the accessibility tree. | |
85 * @param {number} nodeID The id of a node. | |
86 * @param {string} attr The name of an attribute. | |
87 * @return {?boolean} The value of this attribute, or undefined if the tree, | |
88 * node, or attribute wasn't found. | |
89 */ | |
90 var GetBoolAttribute = requireNative('automationInternal').GetBoolAttribute; | |
91 | |
92 /** | |
93 * @param {number} axTreeID The id of the accessibility tree. | |
94 * @param {number} nodeID The id of a node. | |
95 * @param {string} attr The name of an attribute. | |
96 * @return {?number} The value of this attribute, or undefined if the tree, | |
97 * node, or attribute wasn't found. | |
98 */ | |
99 var GetIntAttribute = requireNative('automationInternal').GetIntAttribute; | |
100 | |
101 /** | |
102 * @param {number} axTreeID The id of the accessibility tree. | |
103 * @param {number} nodeID The id of a node. | |
104 * @param {string} attr The name of an attribute. | |
105 * @return {?number} The value of this attribute, or undefined if the tree, | |
106 * node, or attribute wasn't found. | |
107 */ | |
108 var GetFloatAttribute = requireNative('automationInternal').GetFloatAttribute; | |
109 | |
110 /** | |
111 * @param {number} axTreeID The id of the accessibility tree. | |
112 * @param {number} nodeID The id of a node. | |
113 * @param {string} attr The name of an attribute. | |
114 * @return {?Array.<number>} The value of this attribute, or undefined | |
115 * if the tree, node, or attribute wasn't found. | |
116 */ | |
117 var GetIntListAttribute = | |
118 requireNative('automationInternal').GetIntListAttribute; | |
119 | |
120 /** | |
121 * @param {number} axTreeID The id of the accessibility tree. | |
122 * @param {number} nodeID The id of a node. | |
123 * @param {string} attr The name of an HTML attribute. | |
124 * @return {?string} The value of this attribute, or undefined if the tree, | |
125 * node, or attribute wasn't found. | |
126 */ | |
127 var GetHtmlAttribute = requireNative('automationInternal').GetHtmlAttribute; | |
128 | |
129 var lastError = require('lastError'); | 11 var lastError = require('lastError'); |
130 var logging = requireNative('logging'); | 12 var logging = requireNative('logging'); |
131 var schema = requireNative('automationInternal').GetSchemaAdditions(); | 13 var schema = requireNative('automationInternal').GetSchemaAdditions(); |
132 var utils = require('utils'); | 14 var utils = require('utils'); |
133 | 15 |
134 /** | 16 /** |
135 * A single node in the Automation tree. | 17 * A single node in the Automation tree. |
136 * @param {AutomationRootNodeImpl} root The root of the tree. | 18 * @param {AutomationRootNodeImpl} root The root of the tree. |
137 * @constructor | 19 * @constructor |
138 */ | 20 */ |
139 function AutomationNodeImpl(root) { | 21 function AutomationNodeImpl(root) { |
140 this.rootImpl = root; | 22 this.rootImpl = root; |
| 23 this.childIds = []; |
141 // Public attributes. No actual data gets set on this object. | 24 // Public attributes. No actual data gets set on this object. |
| 25 this.attributes = {}; |
| 26 // Internal object holding all attributes. |
| 27 this.attributesInternal = {}; |
142 this.listeners = {}; | 28 this.listeners = {}; |
| 29 this.location = { left: 0, top: 0, width: 0, height: 0 }; |
143 } | 30 } |
144 | 31 |
145 AutomationNodeImpl.prototype = { | 32 AutomationNodeImpl.prototype = { |
146 treeID: -1, | |
147 id: -1, | 33 id: -1, |
148 role: '', | 34 role: '', |
149 state: { busy: true }, | 35 state: { busy: true }, |
150 isRootNode: false, | 36 isRootNode: false, |
151 | 37 |
152 get root() { | 38 get root() { |
153 return this.rootImpl.wrapper; | 39 return this.rootImpl.wrapper; |
154 }, | 40 }, |
155 | 41 |
156 get parent() { | 42 get parent() { |
157 if (this.hostNode_) | 43 return this.hostTree || this.rootImpl.get(this.parentID); |
158 return this.hostNode_; | |
159 var parentID = GetParentID(this.treeID, this.id); | |
160 return this.rootImpl.get(parentID); | |
161 }, | |
162 | |
163 get state() { | |
164 return GetState(this.treeID, this.id); | |
165 }, | |
166 | |
167 get role() { | |
168 return GetRole(this.treeID, this.id); | |
169 }, | |
170 | |
171 get location() { | |
172 return GetLocation(this.treeID, this.id); | |
173 }, | |
174 | |
175 get indexInParent() { | |
176 return GetIndexInParent(this.treeID, this.id); | |
177 }, | |
178 | |
179 get childTree() { | |
180 var childTreeID = GetIntAttribute(this.treeID, this.id, 'childTreeId'); | |
181 if (childTreeID) | |
182 return AutomationRootNodeImpl.get(childTreeID); | |
183 }, | 44 }, |
184 | 45 |
185 get firstChild() { | 46 get firstChild() { |
186 if (this.childTree) | 47 return this.childTree || this.rootImpl.get(this.childIds[0]); |
187 return this.childTree; | |
188 if (!GetChildCount(this.treeID, this.id)) | |
189 return undefined; | |
190 var firstChildID = GetChildIDAtIndex(this.treeID, this.id, 0); | |
191 return this.rootImpl.get(firstChildID); | |
192 }, | 48 }, |
193 | 49 |
194 get lastChild() { | 50 get lastChild() { |
195 if (this.childTree) | 51 var childIds = this.childIds; |
196 return this.childTree; | 52 return this.childTree || this.rootImpl.get(childIds[childIds.length - 1]); |
197 var count = GetChildCount(this.treeID, this.id); | |
198 if (!count) | |
199 return undefined; | |
200 var lastChildID = GetChildIDAtIndex(this.treeID, this.id, count - 1); | |
201 return this.rootImpl.get(lastChildID); | |
202 }, | 53 }, |
203 | 54 |
204 get children() { | 55 get children() { |
205 if (this.childTree) | 56 if (this.childTree) |
206 return [this.childTree]; | 57 return [this.childTree]; |
207 | 58 |
208 var children = []; | 59 var children = []; |
209 var count = GetChildCount(this.treeID, this.id); | 60 for (var i = 0, childID; childID = this.childIds[i]; i++) { |
210 for (var i = 0; i < count; ++i) { | 61 logging.CHECK(this.rootImpl.get(childID)); |
211 var childID = GetChildIDAtIndex(this.treeID, this.id, i); | 62 children.push(this.rootImpl.get(childID)); |
212 var child = this.rootImpl.get(childID); | |
213 children.push(child); | |
214 } | 63 } |
215 return children; | 64 return children; |
216 }, | 65 }, |
217 | 66 |
218 get previousSibling() { | 67 get previousSibling() { |
219 var parent = this.parent; | 68 var parent = this.parent; |
220 var indexInParent = GetIndexInParent(this.treeID, this.id); | 69 if (parent && this.indexInParent > 0) |
221 if (parent && indexInParent > 0) | 70 return parent.children[this.indexInParent - 1]; |
222 return parent.children[indexInParent - 1]; | |
223 return undefined; | 71 return undefined; |
224 }, | 72 }, |
225 | 73 |
226 get nextSibling() { | 74 get nextSibling() { |
227 var parent = this.parent; | 75 var parent = this.parent; |
228 var indexInParent = GetIndexInParent(this.treeID, this.id); | 76 if (parent && this.indexInParent < parent.children.length) |
229 if (parent && indexInParent < parent.children.length) | 77 return parent.children[this.indexInParent + 1]; |
230 return parent.children[indexInParent + 1]; | |
231 return undefined; | 78 return undefined; |
232 }, | 79 }, |
233 | 80 |
234 doDefault: function() { | 81 doDefault: function() { |
235 this.performAction_('doDefault'); | 82 this.performAction_('doDefault'); |
236 }, | 83 }, |
237 | 84 |
238 focus: function() { | 85 focus: function() { |
239 this.performAction_('focus'); | 86 this.performAction_('focus'); |
240 }, | 87 }, |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
316 if (this.dispatchEventAtCapturing_(event, path)) { | 163 if (this.dispatchEventAtCapturing_(event, path)) { |
317 if (this.dispatchEventAtTargeting_(event, path)) | 164 if (this.dispatchEventAtTargeting_(event, path)) |
318 this.dispatchEventAtBubbling_(event, path); | 165 this.dispatchEventAtBubbling_(event, path); |
319 } | 166 } |
320 }, | 167 }, |
321 | 168 |
322 toString: function() { | 169 toString: function() { |
323 var impl = privates(this).impl; | 170 var impl = privates(this).impl; |
324 if (!impl) | 171 if (!impl) |
325 impl = this; | 172 impl = this; |
326 | |
327 var parentID = GetParentID(this.treeID, this.id); | |
328 var count = GetChildCount(this.treeID, this.id); | |
329 var childIDs = []; | |
330 for (var i = 0; i < count; ++i) { | |
331 var childID = GetChildIDAtIndex(this.treeID, this.id, i); | |
332 childIDs.push(childID); | |
333 } | |
334 | |
335 return 'node id=' + impl.id + | 173 return 'node id=' + impl.id + |
336 ' role=' + this.role + | 174 ' role=' + this.role + |
337 ' state=' + $JSON.stringify(this.state) + | 175 ' state=' + $JSON.stringify(this.state) + |
338 ' parentID=' + parentID + | 176 ' parentID=' + impl.parentID + |
339 ' childIds=' + $JSON.stringify(childIDs); | 177 ' childIds=' + $JSON.stringify(impl.childIds) + |
| 178 ' attributes=' + $JSON.stringify(this.attributes); |
340 }, | 179 }, |
341 | 180 |
342 dispatchEventAtCapturing_: function(event, path) { | 181 dispatchEventAtCapturing_: function(event, path) { |
343 privates(event).impl.eventPhase = Event.CAPTURING_PHASE; | 182 privates(event).impl.eventPhase = Event.CAPTURING_PHASE; |
344 for (var i = path.length - 1; i >= 0; i--) { | 183 for (var i = path.length - 1; i >= 0; i--) { |
345 this.fireEventListeners_(path[i], event); | 184 this.fireEventListeners_(path[i], event); |
346 if (privates(event).impl.propagationStopped) | 185 if (privates(event).impl.propagationStopped) |
347 return false; | 186 return false; |
348 } | 187 } |
349 return true; | 188 return true; |
(...skipping 23 matching lines...) Expand all Loading... |
373 var eventPhase = event.eventPhase; | 212 var eventPhase = event.eventPhase; |
374 for (var i = 0; i < listeners.length; i++) { | 213 for (var i = 0; i < listeners.length; i++) { |
375 if (eventPhase == Event.CAPTURING_PHASE && !listeners[i].capture) | 214 if (eventPhase == Event.CAPTURING_PHASE && !listeners[i].capture) |
376 continue; | 215 continue; |
377 if (eventPhase == Event.BUBBLING_PHASE && listeners[i].capture) | 216 if (eventPhase == Event.BUBBLING_PHASE && listeners[i].capture) |
378 continue; | 217 continue; |
379 | 218 |
380 try { | 219 try { |
381 listeners[i].callback(event); | 220 listeners[i].callback(event); |
382 } catch (e) { | 221 } catch (e) { |
383 logging.WARNING('Error in event handler for ' + event.type + | 222 console.error('Error in event handler for ' + event.type + |
384 ' during phase ' + eventPhase + ': ' + | 223 'during phase ' + eventPhase + ': ' + |
385 e.message + '\nStack trace: ' + e.stack); | 224 e.message + '\nStack trace: ' + e.stack); |
386 } | 225 } |
387 } | 226 } |
388 }, | 227 }, |
389 | 228 |
390 performAction_: function(actionType, opt_args) { | 229 performAction_: function(actionType, opt_args) { |
391 // Not yet initialized. | 230 // Not yet initialized. |
392 if (this.rootImpl.treeID === undefined || | 231 if (this.rootImpl.treeID === undefined || |
393 this.id === undefined) { | 232 this.id === undefined) { |
394 return; | 233 return; |
395 } | 234 } |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
466 return false; | 305 return false; |
467 | 306 |
468 if ('state' in params) { | 307 if ('state' in params) { |
469 for (var state in params.state) { | 308 for (var state in params.state) { |
470 if (params.state[state] != (state in this.state)) | 309 if (params.state[state] != (state in this.state)) |
471 return false; | 310 return false; |
472 } | 311 } |
473 } | 312 } |
474 if ('attributes' in params) { | 313 if ('attributes' in params) { |
475 for (var attribute in params.attributes) { | 314 for (var attribute in params.attributes) { |
| 315 if (!(attribute in this.attributesInternal)) |
| 316 return false; |
| 317 |
476 var attrValue = params.attributes[attribute]; | 318 var attrValue = params.attributes[attribute]; |
477 if (typeof attrValue != 'object') { | 319 if (typeof attrValue != 'object') { |
478 if (this[attribute] !== attrValue) | 320 if (this.attributesInternal[attribute] !== attrValue) |
479 return false; | 321 return false; |
480 } else if (attrValue instanceof RegExp) { | 322 } else if (attrValue instanceof RegExp) { |
481 if (typeof this[attribute] != 'string') | 323 if (typeof this.attributesInternal[attribute] != 'string') |
482 return false; | 324 return false; |
483 if (!attrValue.test(this[attribute])) | 325 if (!attrValue.test(this.attributesInternal[attribute])) |
484 return false; | 326 return false; |
485 } else { | 327 } else { |
486 // TODO(aboxhall): handle intlist case. | 328 // TODO(aboxhall): handle intlist case. |
487 return false; | 329 return false; |
488 } | 330 } |
489 } | 331 } |
490 } | 332 } |
491 return true; | 333 return true; |
492 } | 334 } |
493 }; | 335 }; |
494 | 336 |
495 var stringAttributes = [ | 337 // Maps an attribute to its default value in an invalidated node. |
496 'accessKey', | 338 // These attributes are taken directly from the Automation idl. |
497 'action', | 339 var AutomationAttributeDefaults = { |
498 'ariaInvalidValue', | 340 'id': -1, |
499 'autoComplete', | 341 'role': '', |
500 'containerLiveRelevant', | 342 'state': {}, |
501 'containerLiveStatus', | 343 'location': { left: 0, top: 0, width: 0, height: 0 } |
502 'description', | 344 }; |
503 'display', | |
504 'docDoctype', | |
505 'docMimetype', | |
506 'docTitle', | |
507 'docUrl', | |
508 'dropeffect', | |
509 'help', | |
510 'htmlTag', | |
511 'liveRelevant', | |
512 'liveStatus', | |
513 'name', | |
514 'placeholder', | |
515 'shortcut', | |
516 'textInputType', | |
517 'url', | |
518 'value']; | |
519 | 345 |
520 var boolAttributes = [ | |
521 'ariaReadonly', | |
522 'buttonMixed', | |
523 'canSetValue', | |
524 'canvasHasFallback', | |
525 'containerLiveAtomic', | |
526 'containerLiveBusy', | |
527 'docLoaded', | |
528 'grabbed', | |
529 'isAxTreeHost', | |
530 'liveAtomic', | |
531 'liveBusy', | |
532 'updateLocationOnly']; | |
533 | 346 |
534 var intAttributes = [ | 347 var AutomationAttributeTypes = [ |
535 'backgroundColor', | 348 'boolAttributes', |
536 'color', | 349 'floatAttributes', |
537 'colorValue', | 350 'htmlAttributes', |
538 'hierarchicalLevel', | 351 'intAttributes', |
539 'invalidState', | 352 'intlistAttributes', |
540 'posInSet', | 353 'stringAttributes' |
541 'scrollX', | 354 ]; |
542 'scrollXMax', | |
543 'scrollXMin', | |
544 'scrollY', | |
545 'scrollYMax', | |
546 'scrollYMin', | |
547 'setSize', | |
548 'sortDirection', | |
549 'tableCellColumnIndex', | |
550 'tableCellColumnSpan', | |
551 'tableCellRowIndex', | |
552 'tableCellRowSpan', | |
553 'tableColumnCount', | |
554 'tableColumnIndex', | |
555 'tableRowCount', | |
556 'tableRowIndex', | |
557 'textDirection', | |
558 'textSelEnd', | |
559 'textSelStart', | |
560 'textStyle']; | |
561 | 355 |
562 var nodeRefAttributes = [ | 356 /** |
563 ['activedescendantId', 'activedescendant'], | 357 * Maps an attribute name to another attribute who's value is an id or an array |
564 ['tableColumnHeaderId', 'tableColumnHeader'], | 358 * of ids referencing an AutomationNode. |
565 ['tableHeaderId', 'tableHeader'], | 359 * @param {!Object<string>} |
566 ['tableRowHeaderId', 'tableRowHeader'], | 360 * @const |
567 ['titleUiElement', 'titleUIElement']]; | 361 */ |
| 362 var ATTRIBUTE_NAME_TO_ID_ATTRIBUTE = { |
| 363 'aria-activedescendant': 'activedescendantId', |
| 364 'aria-controls': 'controlsIds', |
| 365 'aria-describedby': 'describedbyIds', |
| 366 'aria-flowto': 'flowtoIds', |
| 367 'aria-labelledby': 'labelledbyIds', |
| 368 'aria-owns': 'ownsIds' |
| 369 }; |
568 | 370 |
569 var intListAttributes = [ | 371 /** |
570 'characterOffsets', | 372 * A set of attributes ignored in the automation API. |
571 'lineBreaks', | 373 * @param {!Object<boolean>} |
572 'wordEnds', | 374 * @const |
573 'wordStarts']; | 375 */ |
| 376 var ATTRIBUTE_BLACKLIST = {'activedescendantId': true, |
| 377 'childTreeId': true, |
| 378 'controlsIds': true, |
| 379 'describedbyIds': true, |
| 380 'flowtoIds': true, |
| 381 'labelledbyIds': true, |
| 382 'ownsIds': true |
| 383 }; |
574 | 384 |
575 var nodeRefListAttributes = [ | 385 function defaultStringAttribute(opt_defaultVal) { |
576 ['cellIds', 'cells'], | 386 return { default: undefined, reflectFrom: 'stringAttributes' }; |
577 ['controlsIds', 'controls'], | 387 } |
578 ['describedbyIds', 'describedBy'], | |
579 ['flowtoIds', 'flowTo'], | |
580 ['labelledbyIds', 'labelledBy'], | |
581 ['uniqueCellIds', 'uniqueCells']]; | |
582 | 388 |
583 var floatAttributes = [ | 389 function defaultIntAttribute(opt_defaultVal) { |
584 'docLoadingProgress', | 390 var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : 0; |
585 'valueForRange', | 391 return { default: defaultVal, reflectFrom: 'intAttributes' }; |
586 'minValueForRange', | 392 } |
587 'maxValueForRange', | |
588 'fontSize']; | |
589 | 393 |
590 var htmlAttributes = [ | 394 function defaultFloatAttribute(opt_defaultVal) { |
591 ['type', 'inputType']]; | 395 var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : 0; |
| 396 return { default: defaultVal, reflectFrom: 'floatAttributes' }; |
| 397 } |
592 | 398 |
593 var publicAttributes = []; | 399 function defaultBoolAttribute(opt_defaultVal) { |
| 400 var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : false; |
| 401 return { default: defaultVal, reflectFrom: 'boolAttributes' }; |
| 402 } |
594 | 403 |
595 stringAttributes.forEach(function (attributeName) { | 404 function defaultHtmlAttribute(opt_defaultVal) { |
596 publicAttributes.push(attributeName); | 405 var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : ''; |
597 Object.defineProperty(AutomationNodeImpl.prototype, attributeName, { | 406 return { default: defaultVal, reflectFrom: 'htmlAttributes' }; |
598 get: function() { | 407 } |
599 return GetStringAttribute(this.treeID, this.id, attributeName); | |
600 } | |
601 }); | |
602 }); | |
603 | 408 |
604 boolAttributes.forEach(function (attributeName) { | 409 function defaultIntListAttribute(opt_defaultVal) { |
605 publicAttributes.push(attributeName); | 410 var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : []; |
606 Object.defineProperty(AutomationNodeImpl.prototype, attributeName, { | 411 return { default: defaultVal, reflectFrom: 'intlistAttributes' }; |
607 get: function() { | 412 } |
608 return GetBoolAttribute(this.treeID, this.id, attributeName); | |
609 } | |
610 }); | |
611 }); | |
612 | 413 |
613 intAttributes.forEach(function (attributeName) { | 414 function defaultNodeRefAttribute(idAttribute, opt_defaultVal) { |
614 publicAttributes.push(attributeName); | 415 var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : null; |
615 Object.defineProperty(AutomationNodeImpl.prototype, attributeName, { | 416 return { default: defaultVal, |
616 get: function() { | 417 idFrom: 'intAttributes', |
617 return GetIntAttribute(this.treeID, this.id, attributeName); | 418 idAttribute: idAttribute, |
618 } | 419 isRef: true }; |
619 }); | 420 } |
620 }); | |
621 | 421 |
622 nodeRefAttributes.forEach(function (params) { | 422 function defaultNodeRefListAttribute(idAttribute, opt_defaultVal) { |
623 var srcAttributeName = params[0]; | 423 var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : []; |
624 var dstAttributeName = params[1]; | 424 return { default: [], |
625 publicAttributes.push(dstAttributeName); | 425 idFrom: 'intlistAttributes', |
626 Object.defineProperty(AutomationNodeImpl.prototype, dstAttributeName, { | 426 idAttribute: idAttribute, |
627 get: function() { | 427 isRef: true }; |
628 var id = GetIntAttribute(this.treeID, this.id, srcAttributeName); | 428 } |
629 if (id) | |
630 return this.rootImpl.get(id); | |
631 else | |
632 return undefined; | |
633 } | |
634 }); | |
635 }); | |
636 | 429 |
637 intListAttributes.forEach(function (attributeName) { | 430 // Maps an attribute to its default value in an invalidated node. |
638 publicAttributes.push(attributeName); | 431 // These attributes are taken directly from the Automation idl. |
639 Object.defineProperty(AutomationNodeImpl.prototype, attributeName, { | 432 var DefaultMixinAttributes = { |
640 get: function() { | 433 description: defaultStringAttribute(), |
641 return GetIntListAttribute(this.treeID, this.id, attributeName); | 434 help: defaultStringAttribute(), |
642 } | 435 name: defaultStringAttribute(), |
643 }); | 436 value: defaultStringAttribute(), |
644 }); | 437 htmlTag: defaultStringAttribute(), |
| 438 hierarchicalLevel: defaultIntAttribute(), |
| 439 controls: defaultNodeRefListAttribute('controlsIds'), |
| 440 describedby: defaultNodeRefListAttribute('describedbyIds'), |
| 441 flowto: defaultNodeRefListAttribute('flowtoIds'), |
| 442 labelledby: defaultNodeRefListAttribute('labelledbyIds'), |
| 443 owns: defaultNodeRefListAttribute('ownsIds'), |
| 444 wordStarts: defaultIntListAttribute(), |
| 445 wordEnds: defaultIntListAttribute() |
| 446 }; |
645 | 447 |
646 nodeRefListAttributes.forEach(function (params) { | 448 var ActiveDescendantMixinAttribute = { |
647 var srcAttributeName = params[0]; | 449 activedescendant: defaultNodeRefAttribute('activedescendantId') |
648 var dstAttributeName = params[1]; | 450 }; |
649 publicAttributes.push(dstAttributeName); | |
650 Object.defineProperty(AutomationNodeImpl.prototype, dstAttributeName, { | |
651 get: function() { | |
652 var ids = GetIntListAttribute(this.treeID, this.id, srcAttributeName); | |
653 if (!ids) | |
654 return undefined; | |
655 var result = []; | |
656 for (var i = 0; i < ids.length; ++i) { | |
657 var node = this.rootImpl.get(ids[i]); | |
658 if (node) | |
659 result.push(node); | |
660 } | |
661 return result; | |
662 } | |
663 }); | |
664 }); | |
665 | 451 |
666 floatAttributes.forEach(function (attributeName) { | 452 var LinkMixinAttributes = { |
667 publicAttributes.push(attributeName); | 453 url: defaultStringAttribute() |
668 Object.defineProperty(AutomationNodeImpl.prototype, attributeName, { | 454 }; |
669 get: function() { | |
670 return GetFloatAttribute(this.treeID, this.id, attributeName); | |
671 } | |
672 }); | |
673 }); | |
674 | 455 |
675 htmlAttributes.forEach(function (params) { | 456 var DocumentMixinAttributes = { |
676 var srcAttributeName = params[0]; | 457 docUrl: defaultStringAttribute(), |
677 var dstAttributeName = params[1]; | 458 docTitle: defaultStringAttribute(), |
678 publicAttributes.push(dstAttributeName); | 459 docLoaded: defaultStringAttribute(), |
679 Object.defineProperty(AutomationNodeImpl.prototype, dstAttributeName, { | 460 docLoadingProgress: defaultFloatAttribute() |
680 get: function() { | 461 }; |
681 return GetHtmlAttribute(this.treeID, this.id, srcAttributeName); | 462 |
682 } | 463 var ScrollableMixinAttributes = { |
683 }); | 464 scrollX: defaultIntAttribute(), |
684 }); | 465 scrollXMin: defaultIntAttribute(), |
| 466 scrollXMax: defaultIntAttribute(), |
| 467 scrollY: defaultIntAttribute(), |
| 468 scrollYMin: defaultIntAttribute(), |
| 469 scrollYMax: defaultIntAttribute() |
| 470 }; |
| 471 |
| 472 var EditableTextMixinAttributes = { |
| 473 textSelStart: defaultIntAttribute(-1), |
| 474 textSelEnd: defaultIntAttribute(-1), |
| 475 type: defaultHtmlAttribute() |
| 476 }; |
| 477 |
| 478 var RangeMixinAttributes = { |
| 479 valueForRange: defaultFloatAttribute(), |
| 480 minValueForRange: defaultFloatAttribute(), |
| 481 maxValueForRange: defaultFloatAttribute() |
| 482 }; |
| 483 |
| 484 var TableMixinAttributes = { |
| 485 tableRowCount: defaultIntAttribute(), |
| 486 tableColumnCount: defaultIntAttribute() |
| 487 }; |
| 488 |
| 489 var TableCellMixinAttributes = { |
| 490 tableCellColumnIndex: defaultIntAttribute(), |
| 491 tableCellColumnSpan: defaultIntAttribute(1), |
| 492 tableCellRowIndex: defaultIntAttribute(), |
| 493 tableCellRowSpan: defaultIntAttribute(1) |
| 494 }; |
| 495 |
| 496 var LiveRegionMixinAttributes = { |
| 497 containerLiveAtomic: defaultBoolAttribute(), |
| 498 containerLiveBusy: defaultBoolAttribute(), |
| 499 containerLiveRelevant: defaultStringAttribute(), |
| 500 containerLiveStatus: defaultStringAttribute(), |
| 501 }; |
685 | 502 |
686 /** | 503 /** |
687 * AutomationRootNode. | 504 * AutomationRootNode. |
688 * | 505 * |
689 * An AutomationRootNode is the javascript end of an AXTree living in the | 506 * An AutomationRootNode is the javascript end of an AXTree living in the |
690 * browser. AutomationRootNode handles unserializing incremental updates from | 507 * browser. AutomationRootNode handles unserializing incremental updates from |
691 * the source AXTree. Each update contains node data that form a complete tree | 508 * the source AXTree. Each update contains node data that form a complete tree |
692 * after applying the update. | 509 * after applying the update. |
693 * | 510 * |
694 * A brief note about ids used through this class. The source AXTree assigns | 511 * A brief note about ids used through this class. The source AXTree assigns |
695 * unique ids per node and we use these ids to build a hash to the actual | 512 * unique ids per node and we use these ids to build a hash to the actual |
696 * AutomationNode object. | 513 * AutomationNode object. |
697 * Thus, tree traversals amount to a lookup in our hash. | 514 * Thus, tree traversals amount to a lookup in our hash. |
698 * | 515 * |
699 * The tree itself is identified by the accessibility tree id of the | 516 * The tree itself is identified by the accessibility tree id of the |
700 * renderer widget host. | 517 * renderer widget host. |
701 * @constructor | 518 * @constructor |
702 */ | 519 */ |
703 function AutomationRootNodeImpl(treeID) { | 520 function AutomationRootNodeImpl(treeID) { |
704 AutomationNodeImpl.call(this, this); | 521 AutomationNodeImpl.call(this, this); |
705 this.treeID = treeID; | 522 this.treeID = treeID; |
706 this.axNodeDataCache_ = {}; | 523 this.axNodeDataCache_ = {}; |
707 } | 524 } |
708 | 525 |
709 AutomationRootNodeImpl.idToAutomationRootNode_ = {}; | |
710 | |
711 AutomationRootNodeImpl.get = function(treeID) { | |
712 var result = AutomationRootNodeImpl.idToAutomationRootNode_[treeID]; | |
713 return result || undefined; | |
714 } | |
715 | |
716 AutomationRootNodeImpl.getOrCreate = function(treeID) { | |
717 if (AutomationRootNodeImpl.idToAutomationRootNode_[treeID]) | |
718 return AutomationRootNodeImpl.idToAutomationRootNode_[treeID]; | |
719 var result = new AutomationRootNode(treeID); | |
720 AutomationRootNodeImpl.idToAutomationRootNode_[treeID] = result; | |
721 return result; | |
722 } | |
723 | |
724 AutomationRootNodeImpl.destroy = function(treeID) { | |
725 delete AutomationRootNodeImpl.idToAutomationRootNode_[treeID]; | |
726 } | |
727 | |
728 AutomationRootNodeImpl.prototype = { | 526 AutomationRootNodeImpl.prototype = { |
729 __proto__: AutomationNodeImpl.prototype, | 527 __proto__: AutomationNodeImpl.prototype, |
730 | 528 |
731 /** | |
732 * @type {boolean} | |
733 */ | |
734 isRootNode: true, | 529 isRootNode: true, |
735 | |
736 /** | |
737 * @type {number} | |
738 */ | |
739 treeID: -1, | 530 treeID: -1, |
740 | 531 |
741 /** | |
742 * The parent of this node from a different tree. | |
743 * @type {?AutomationNode} | |
744 * @private | |
745 */ | |
746 hostNode_: null, | |
747 | |
748 /** | |
749 * A map from id to AutomationNode. | |
750 * @type {Object.<number, AutomationNode>} | |
751 * @private | |
752 */ | |
753 axNodeDataCache_: null, | |
754 | |
755 get id() { | |
756 return GetRootID(this.treeID); | |
757 }, | |
758 | |
759 get: function(id) { | 532 get: function(id) { |
760 if (id == undefined) | 533 if (id == undefined) |
761 return undefined; | 534 return undefined; |
762 | 535 |
763 if (id == this.id) | 536 return this.axNodeDataCache_[id]; |
764 return this.wrapper; | |
765 | |
766 var obj = this.axNodeDataCache_[id]; | |
767 if (obj) | |
768 return obj; | |
769 | |
770 obj = new AutomationNode(this); | |
771 privates(obj).impl.treeID = this.treeID; | |
772 privates(obj).impl.id = id; | |
773 this.axNodeDataCache_[id] = obj; | |
774 | |
775 return obj; | |
776 }, | 537 }, |
777 | 538 |
778 remove: function(id) { | 539 unserialize: function(update) { |
779 delete this.axNodeDataCache_[id]; | 540 var updateState = { pendingNodes: {}, newNodes: {} }; |
| 541 var oldRootId = this.id; |
| 542 |
| 543 if (update.nodeIdToClear < 0) { |
| 544 logging.WARNING('Bad nodeIdToClear: ' + update.nodeIdToClear); |
| 545 lastError.set('automation', |
| 546 'Bad update received on automation tree', |
| 547 null, |
| 548 chrome); |
| 549 return false; |
| 550 } else if (update.nodeIdToClear > 0) { |
| 551 var nodeToClear = this.axNodeDataCache_[update.nodeIdToClear]; |
| 552 if (!nodeToClear) { |
| 553 logging.WARNING('Bad nodeIdToClear: ' + update.nodeIdToClear + |
| 554 ' (not in cache)'); |
| 555 lastError.set('automation', |
| 556 'Bad update received on automation tree', |
| 557 null, |
| 558 chrome); |
| 559 return false; |
| 560 } |
| 561 if (nodeToClear === this.wrapper) { |
| 562 this.invalidate_(nodeToClear); |
| 563 } else { |
| 564 var children = nodeToClear.children; |
| 565 for (var i = 0; i < children.length; i++) |
| 566 this.invalidate_(children[i]); |
| 567 var nodeToClearImpl = privates(nodeToClear).impl; |
| 568 nodeToClearImpl.childIds = [] |
| 569 updateState.pendingNodes[nodeToClearImpl.id] = nodeToClear; |
| 570 } |
| 571 } |
| 572 |
| 573 for (var i = 0; i < update.nodes.length; i++) { |
| 574 if (!this.updateNode_(update.nodes[i], updateState)) |
| 575 return false; |
| 576 } |
| 577 |
| 578 if (Object.keys(updateState.pendingNodes).length > 0) { |
| 579 logging.WARNING('Nodes left pending by the update: ' + |
| 580 $JSON.stringify(updateState.pendingNodes)); |
| 581 lastError.set('automation', |
| 582 'Bad update received on automation tree', |
| 583 null, |
| 584 chrome); |
| 585 return false; |
| 586 } |
| 587 |
| 588 // Notify tree change observers of new nodes. |
| 589 // TODO(dmazzoni): Notify tree change observers of changed nodes, |
| 590 // and handle subtreeCreated and nodeCreated properly. |
| 591 var observers = automationUtil.treeChangeObservers; |
| 592 if (observers.length > 0) { |
| 593 for (var nodeId in updateState.newNodes) { |
| 594 var node = updateState.newNodes[nodeId]; |
| 595 var treeChange = |
| 596 {target: node, type: schema.TreeChangeType.nodeCreated}; |
| 597 for (var i = 0; i < observers.length; i++) { |
| 598 try { |
| 599 observers[i](treeChange); |
| 600 } catch (e) { |
| 601 console.error('Error in tree change observer for ' + |
| 602 treeChange.type + ': ' + e.message + |
| 603 '\nStack trace: ' + e.stack); |
| 604 } |
| 605 } |
| 606 } |
| 607 } |
| 608 |
| 609 return true; |
780 }, | 610 }, |
781 | 611 |
782 destroy: function() { | 612 destroy: function() { |
| 613 if (this.hostTree) |
| 614 this.hostTree.childTree = undefined; |
| 615 this.hostTree = undefined; |
| 616 |
783 this.dispatchEvent(schema.EventType.destroyed); | 617 this.dispatchEvent(schema.EventType.destroyed); |
784 }, | 618 this.invalidate_(this.wrapper); |
785 | |
786 setHostNode(hostNode) { | |
787 this.hostNode_ = hostNode; | |
788 }, | 619 }, |
789 | 620 |
790 onAccessibilityEvent: function(eventParams) { | 621 onAccessibilityEvent: function(eventParams) { |
| 622 if (!this.unserialize(eventParams.update)) { |
| 623 logging.WARNING('unserialization failed'); |
| 624 return false; |
| 625 } |
| 626 |
791 var targetNode = this.get(eventParams.targetID); | 627 var targetNode = this.get(eventParams.targetID); |
792 if (targetNode) { | 628 if (targetNode) { |
793 var targetNodeImpl = privates(targetNode).impl; | 629 var targetNodeImpl = privates(targetNode).impl; |
794 targetNodeImpl.dispatchEvent(eventParams.eventType); | 630 targetNodeImpl.dispatchEvent(eventParams.eventType); |
795 } else { | 631 } else { |
796 logging.WARNING('Got ' + eventParams.eventType + | 632 logging.WARNING('Got ' + eventParams.eventType + |
797 ' event on unknown node: ' + eventParams.targetID + | 633 ' event on unknown node: ' + eventParams.targetID + |
798 '; this: ' + this.id); | 634 '; this: ' + this.id); |
799 } | 635 } |
800 return true; | 636 return true; |
801 }, | 637 }, |
802 | 638 |
803 toString: function() { | 639 toString: function() { |
804 function toStringInternal(node, indent) { | 640 function toStringInternal(node, indent) { |
805 if (!node) | 641 if (!node) |
806 return ''; | 642 return ''; |
807 var output = | 643 var output = |
808 new Array(indent).join(' ') + | 644 new Array(indent).join(' ') + |
809 AutomationNodeImpl.prototype.toString.call(node) + | 645 AutomationNodeImpl.prototype.toString.call(node) + |
810 '\n'; | 646 '\n'; |
811 indent += 2; | 647 indent += 2; |
812 for (var i = 0; i < node.children.length; i++) | 648 for (var i = 0; i < node.children.length; i++) |
813 output += toStringInternal(node.children[i], indent); | 649 output += toStringInternal(node.children[i], indent); |
814 return output; | 650 return output; |
815 } | 651 } |
816 return toStringInternal(this, 0); | 652 return toStringInternal(this, 0); |
817 }, | 653 }, |
| 654 |
| 655 invalidate_: function(node) { |
| 656 if (!node) |
| 657 return; |
| 658 |
| 659 // Notify tree change observers of the removed node. |
| 660 var observers = automationUtil.treeChangeObservers; |
| 661 if (observers.length > 0) { |
| 662 var treeChange = {target: node, type: schema.TreeChangeType.nodeRemoved}; |
| 663 for (var i = 0; i < observers.length; i++) { |
| 664 try { |
| 665 observers[i](treeChange); |
| 666 } catch (e) { |
| 667 console.error('Error in tree change observer for ' + treeChange.type + |
| 668 ': ' + e.message + '\nStack trace: ' + e.stack); |
| 669 } |
| 670 } |
| 671 } |
| 672 |
| 673 var children = node.children; |
| 674 |
| 675 for (var i = 0, child; child = children[i]; i++) { |
| 676 // Do not invalidate into subrooted nodes. |
| 677 // TODO(dtseng): Revisit logic once out of proc iframes land. |
| 678 if (child.root != node.root) |
| 679 continue; |
| 680 this.invalidate_(child); |
| 681 } |
| 682 |
| 683 // Retrieve the internal AutomationNodeImpl instance for this node. |
| 684 // This object is not accessible outside of bindings code, but we can access |
| 685 // it here. |
| 686 var nodeImpl = privates(node).impl; |
| 687 var id = nodeImpl.id; |
| 688 for (var key in AutomationAttributeDefaults) { |
| 689 nodeImpl[key] = AutomationAttributeDefaults[key]; |
| 690 } |
| 691 |
| 692 nodeImpl.attributesInternal = {}; |
| 693 for (var key in DefaultMixinAttributes) { |
| 694 var mixinAttribute = DefaultMixinAttributes[key]; |
| 695 if (!mixinAttribute.isRef) |
| 696 nodeImpl.attributesInternal[key] = mixinAttribute.default; |
| 697 } |
| 698 nodeImpl.childIds = []; |
| 699 nodeImpl.id = id; |
| 700 delete this.axNodeDataCache_[id]; |
| 701 }, |
| 702 |
| 703 deleteOldChildren_: function(node, newChildIds) { |
| 704 // Create a set of child ids in |src| for fast lookup, and return false |
| 705 // if a duplicate is found; |
| 706 var newChildIdSet = {}; |
| 707 for (var i = 0; i < newChildIds.length; i++) { |
| 708 var childId = newChildIds[i]; |
| 709 if (newChildIdSet[childId]) { |
| 710 logging.WARNING('Node ' + privates(node).impl.id + |
| 711 ' has duplicate child id ' + childId); |
| 712 lastError.set('automation', |
| 713 'Bad update received on automation tree', |
| 714 null, |
| 715 chrome); |
| 716 return false; |
| 717 } |
| 718 newChildIdSet[newChildIds[i]] = true; |
| 719 } |
| 720 |
| 721 // Delete the old children. |
| 722 var nodeImpl = privates(node).impl; |
| 723 var oldChildIds = nodeImpl.childIds; |
| 724 for (var i = 0; i < oldChildIds.length;) { |
| 725 var oldId = oldChildIds[i]; |
| 726 if (!newChildIdSet[oldId]) { |
| 727 this.invalidate_(this.axNodeDataCache_[oldId]); |
| 728 oldChildIds.splice(i, 1); |
| 729 } else { |
| 730 i++; |
| 731 } |
| 732 } |
| 733 nodeImpl.childIds = oldChildIds; |
| 734 |
| 735 return true; |
| 736 }, |
| 737 |
| 738 createNewChildren_: function(node, newChildIds, updateState) { |
| 739 logging.CHECK(node); |
| 740 var success = true; |
| 741 |
| 742 for (var i = 0; i < newChildIds.length; i++) { |
| 743 var childId = newChildIds[i]; |
| 744 var childNode = this.axNodeDataCache_[childId]; |
| 745 if (childNode) { |
| 746 if (childNode.parent != node) { |
| 747 var parentId = -1; |
| 748 if (childNode.parent) { |
| 749 var parentImpl = privates(childNode.parent).impl; |
| 750 parentId = parentImpl.id; |
| 751 } |
| 752 // This is a serious error - nodes should never be reparented. |
| 753 // If this case occurs, continue so this node isn't left in an |
| 754 // inconsistent state, but return failure at the end. |
| 755 logging.WARNING('Node ' + childId + ' reparented from ' + |
| 756 parentId + ' to ' + privates(node).impl.id); |
| 757 lastError.set('automation', |
| 758 'Bad update received on automation tree', |
| 759 null, |
| 760 chrome); |
| 761 success = false; |
| 762 continue; |
| 763 } |
| 764 } else { |
| 765 childNode = new AutomationNode(this); |
| 766 this.axNodeDataCache_[childId] = childNode; |
| 767 privates(childNode).impl.id = childId; |
| 768 updateState.pendingNodes[childId] = childNode; |
| 769 updateState.newNodes[childId] = childNode; |
| 770 } |
| 771 privates(childNode).impl.indexInParent = i; |
| 772 privates(childNode).impl.parentID = privates(node).impl.id; |
| 773 } |
| 774 |
| 775 return success; |
| 776 }, |
| 777 |
| 778 setData_: function(node, nodeData) { |
| 779 var nodeImpl = privates(node).impl; |
| 780 |
| 781 // TODO(dtseng): Make into set listing all hosting node roles. |
| 782 if (nodeData.role == schema.RoleType.webView) { |
| 783 if (nodeImpl.childTreeID !== nodeData.intAttributes.childTreeId) |
| 784 nodeImpl.pendingChildFrame = true; |
| 785 |
| 786 if (nodeImpl.pendingChildFrame) { |
| 787 nodeImpl.childTreeID = nodeData.intAttributes.childTreeId; |
| 788 automationUtil.storeTreeCallback(nodeImpl.childTreeID, function(root) { |
| 789 nodeImpl.pendingChildFrame = false; |
| 790 nodeImpl.childTree = root; |
| 791 privates(root).impl.hostTree = node; |
| 792 if (root.attributes.docLoadingProgress == 1) |
| 793 privates(root).impl.dispatchEvent(schema.EventType.loadComplete); |
| 794 nodeImpl.dispatchEvent(schema.EventType.childrenChanged); |
| 795 }); |
| 796 automationInternal.enableFrame(nodeImpl.childTreeID); |
| 797 } |
| 798 } |
| 799 for (var key in AutomationAttributeDefaults) { |
| 800 if (key in nodeData) |
| 801 nodeImpl[key] = nodeData[key]; |
| 802 else |
| 803 nodeImpl[key] = AutomationAttributeDefaults[key]; |
| 804 } |
| 805 |
| 806 // Set all basic attributes. |
| 807 this.mixinAttributes_(nodeImpl, DefaultMixinAttributes, nodeData); |
| 808 |
| 809 // If this is a rootWebArea or webArea, set document attributes. |
| 810 if (nodeData.role == schema.RoleType.rootWebArea || |
| 811 nodeData.role == schema.RoleType.webArea) { |
| 812 this.mixinAttributes_(nodeImpl, DocumentMixinAttributes, nodeData); |
| 813 } |
| 814 |
| 815 // If this is a scrollable area, set scrollable attributes. |
| 816 for (var scrollAttr in ScrollableMixinAttributes) { |
| 817 var spec = ScrollableMixinAttributes[scrollAttr]; |
| 818 if (this.findAttribute_(scrollAttr, spec, nodeData) !== undefined) { |
| 819 this.mixinAttributes_(nodeImpl, ScrollableMixinAttributes, nodeData); |
| 820 break; |
| 821 } |
| 822 } |
| 823 |
| 824 // If this is inside a live region, set live region mixins. |
| 825 var attr = 'containerLiveStatus'; |
| 826 var spec = LiveRegionMixinAttributes[attr]; |
| 827 if (this.findAttribute_(attr, spec, nodeData) !== undefined) { |
| 828 this.mixinAttributes_(nodeImpl, LiveRegionMixinAttributes, nodeData); |
| 829 } |
| 830 |
| 831 // If this is a link, set link attributes |
| 832 if (nodeData.role == 'link') { |
| 833 this.mixinAttributes_(nodeImpl, LinkMixinAttributes, nodeData); |
| 834 } |
| 835 |
| 836 // If this is an editable text area, set editable text attributes. |
| 837 if (nodeData.role == schema.RoleType.textField || |
| 838 nodeData.role == schema.RoleType.spinButton) { |
| 839 this.mixinAttributes_(nodeImpl, EditableTextMixinAttributes, nodeData); |
| 840 } |
| 841 |
| 842 // If this is a range type, set range attributes. |
| 843 if (nodeData.role == schema.RoleType.progressIndicator || |
| 844 nodeData.role == schema.RoleType.scrollBar || |
| 845 nodeData.role == schema.RoleType.slider || |
| 846 nodeData.role == schema.RoleType.spinButton) { |
| 847 this.mixinAttributes_(nodeImpl, RangeMixinAttributes, nodeData); |
| 848 } |
| 849 |
| 850 // If this is a table, set table attributes. |
| 851 if (nodeData.role == schema.RoleType.table) { |
| 852 this.mixinAttributes_(nodeImpl, TableMixinAttributes, nodeData); |
| 853 } |
| 854 |
| 855 // If this is a table cell, set table cell attributes. |
| 856 if (nodeData.role == schema.RoleType.cell) { |
| 857 this.mixinAttributes_(nodeImpl, TableCellMixinAttributes, nodeData); |
| 858 } |
| 859 |
| 860 // If this has an active descendant, expose it. |
| 861 if ('intAttributes' in nodeData && |
| 862 'activedescendantId' in nodeData.intAttributes) { |
| 863 this.mixinAttributes_(nodeImpl, ActiveDescendantMixinAttribute, nodeData); |
| 864 } |
| 865 |
| 866 for (var i = 0; i < AutomationAttributeTypes.length; i++) { |
| 867 var attributeType = AutomationAttributeTypes[i]; |
| 868 for (var attributeName in nodeData[attributeType]) { |
| 869 nodeImpl.attributesInternal[attributeName] = |
| 870 nodeData[attributeType][attributeName]; |
| 871 if (ATTRIBUTE_BLACKLIST.hasOwnProperty(attributeName) || |
| 872 nodeImpl.attributes.hasOwnProperty(attributeName)) { |
| 873 continue; |
| 874 } else if ( |
| 875 ATTRIBUTE_NAME_TO_ID_ATTRIBUTE.hasOwnProperty(attributeName)) { |
| 876 this.defineReadonlyAttribute_(nodeImpl, |
| 877 nodeImpl.attributes, |
| 878 attributeName, |
| 879 true); |
| 880 } else { |
| 881 this.defineReadonlyAttribute_(nodeImpl, |
| 882 nodeImpl.attributes, |
| 883 attributeName); |
| 884 } |
| 885 } |
| 886 } |
| 887 }, |
| 888 |
| 889 mixinAttributes_: function(nodeImpl, attributes, nodeData) { |
| 890 for (var attribute in attributes) { |
| 891 var spec = attributes[attribute]; |
| 892 if (spec.isRef) |
| 893 this.mixinRelationshipAttribute_(nodeImpl, attribute, spec, nodeData); |
| 894 else |
| 895 this.mixinAttribute_(nodeImpl, attribute, spec, nodeData); |
| 896 } |
| 897 }, |
| 898 |
| 899 mixinAttribute_: function(nodeImpl, attribute, spec, nodeData) { |
| 900 var value = this.findAttribute_(attribute, spec, nodeData); |
| 901 if (value === undefined) |
| 902 value = spec.default; |
| 903 nodeImpl.attributesInternal[attribute] = value; |
| 904 this.defineReadonlyAttribute_(nodeImpl, nodeImpl, attribute); |
| 905 }, |
| 906 |
| 907 mixinRelationshipAttribute_: function(nodeImpl, attribute, spec, nodeData) { |
| 908 var idAttribute = spec.idAttribute; |
| 909 var idValue = spec.default; |
| 910 if (spec.idFrom in nodeData) { |
| 911 idValue = idAttribute in nodeData[spec.idFrom] |
| 912 ? nodeData[spec.idFrom][idAttribute] : idValue; |
| 913 } |
| 914 |
| 915 // Ok to define a list attribute with an empty list, but not a |
| 916 // single ref with a null ID. |
| 917 if (idValue === null) |
| 918 return; |
| 919 |
| 920 nodeImpl.attributesInternal[idAttribute] = idValue; |
| 921 this.defineReadonlyAttribute_( |
| 922 nodeImpl, nodeImpl, attribute, true, idAttribute); |
| 923 }, |
| 924 |
| 925 findAttribute_: function(attribute, spec, nodeData) { |
| 926 if (!('reflectFrom' in spec)) |
| 927 return; |
| 928 var attributeGroup = spec.reflectFrom; |
| 929 if (!(attributeGroup in nodeData)) |
| 930 return; |
| 931 |
| 932 return nodeData[attributeGroup][attribute]; |
| 933 }, |
| 934 |
| 935 defineReadonlyAttribute_: function( |
| 936 node, object, attributeName, opt_isIDRef, opt_idAttribute) { |
| 937 if (attributeName in object) |
| 938 return; |
| 939 |
| 940 if (opt_isIDRef) { |
| 941 $Object.defineProperty(object, attributeName, { |
| 942 enumerable: true, |
| 943 get: function() { |
| 944 var idAttribute = opt_idAttribute || |
| 945 ATTRIBUTE_NAME_TO_ID_ATTRIBUTE[attributeName]; |
| 946 var idValue = node.attributesInternal[idAttribute]; |
| 947 if (Array.isArray(idValue)) { |
| 948 return idValue.map(function(current) { |
| 949 return node.rootImpl.get(current); |
| 950 }, this); |
| 951 } |
| 952 return node.rootImpl.get(idValue); |
| 953 }.bind(this), |
| 954 }); |
| 955 } else { |
| 956 $Object.defineProperty(object, attributeName, { |
| 957 enumerable: true, |
| 958 get: function() { |
| 959 return node.attributesInternal[attributeName]; |
| 960 }.bind(this), |
| 961 }); |
| 962 } |
| 963 |
| 964 if (object instanceof AutomationNodeImpl) { |
| 965 // Also expose attribute publicly on the wrapper. |
| 966 $Object.defineProperty(object.wrapper, attributeName, { |
| 967 enumerable: true, |
| 968 get: function() { |
| 969 return object[attributeName]; |
| 970 }, |
| 971 }); |
| 972 |
| 973 } |
| 974 }, |
| 975 |
| 976 updateNode_: function(nodeData, updateState) { |
| 977 var node = this.axNodeDataCache_[nodeData.id]; |
| 978 var didUpdateRoot = false; |
| 979 if (node) { |
| 980 delete updateState.pendingNodes[privates(node).impl.id]; |
| 981 } else { |
| 982 if (nodeData.role != schema.RoleType.rootWebArea && |
| 983 nodeData.role != schema.RoleType.desktop) { |
| 984 logging.WARNING(String(nodeData.id) + |
| 985 ' is not in the cache and not the new root.'); |
| 986 lastError.set('automation', |
| 987 'Bad update received on automation tree', |
| 988 null, |
| 989 chrome); |
| 990 return false; |
| 991 } |
| 992 // |this| is an AutomationRootNodeImpl; retrieve the |
| 993 // AutomationRootNode instance instead. |
| 994 node = this.wrapper; |
| 995 didUpdateRoot = true; |
| 996 updateState.newNodes[this.id] = this.wrapper; |
| 997 } |
| 998 this.setData_(node, nodeData); |
| 999 |
| 1000 // TODO(aboxhall): send onChanged event? |
| 1001 logging.CHECK(node); |
| 1002 if (!this.deleteOldChildren_(node, nodeData.childIds)) { |
| 1003 if (didUpdateRoot) { |
| 1004 this.invalidate_(this.wrapper); |
| 1005 } |
| 1006 return false; |
| 1007 } |
| 1008 var nodeImpl = privates(node).impl; |
| 1009 |
| 1010 var success = this.createNewChildren_(node, |
| 1011 nodeData.childIds, |
| 1012 updateState); |
| 1013 nodeImpl.childIds = nodeData.childIds; |
| 1014 this.axNodeDataCache_[nodeImpl.id] = node; |
| 1015 |
| 1016 return success; |
| 1017 } |
818 }; | 1018 }; |
819 | 1019 |
| 1020 |
820 var AutomationNode = utils.expose('AutomationNode', | 1021 var AutomationNode = utils.expose('AutomationNode', |
821 AutomationNodeImpl, | 1022 AutomationNodeImpl, |
822 { functions: ['doDefault', | 1023 { functions: ['doDefault', |
823 'find', | 1024 'find', |
824 'findAll', | 1025 'findAll', |
825 'focus', | 1026 'focus', |
826 'makeVisible', | 1027 'makeVisible', |
827 'matches', | 1028 'matches', |
828 'setSelection', | 1029 'setSelection', |
829 'showContextMenu', | 1030 'showContextMenu', |
830 'addEventListener', | 1031 'addEventListener', |
831 'removeEventListener', | 1032 'removeEventListener', |
832 'domQuerySelector', | 1033 'domQuerySelector', |
833 'toString' ], | 1034 'toString' ], |
834 readonly: publicAttributes.concat( | 1035 readonly: ['parent', |
835 ['parent', | |
836 'firstChild', | 1036 'firstChild', |
837 'lastChild', | 1037 'lastChild', |
838 'children', | 1038 'children', |
839 'previousSibling', | 1039 'previousSibling', |
840 'nextSibling', | 1040 'nextSibling', |
841 'isRootNode', | 1041 'isRootNode', |
842 'role', | 1042 'role', |
843 'state', | 1043 'state', |
844 'location', | 1044 'location', |
| 1045 'attributes', |
845 'indexInParent', | 1046 'indexInParent', |
846 'root']) }); | 1047 'root'] }); |
847 | 1048 |
848 var AutomationRootNode = utils.expose('AutomationRootNode', | 1049 var AutomationRootNode = utils.expose('AutomationRootNode', |
849 AutomationRootNodeImpl, | 1050 AutomationRootNodeImpl, |
850 { superclass: AutomationNode }); | 1051 { superclass: AutomationNode }); |
851 | 1052 |
852 AutomationRootNode.get = function(treeID) { | |
853 return AutomationRootNodeImpl.get(treeID); | |
854 } | |
855 | |
856 AutomationRootNode.getOrCreate = function(treeID) { | |
857 return AutomationRootNodeImpl.getOrCreate(treeID); | |
858 } | |
859 | |
860 AutomationRootNode.destroy = function(treeID) { | |
861 AutomationRootNodeImpl.destroy(treeID); | |
862 } | |
863 | |
864 exports.AutomationNode = AutomationNode; | 1053 exports.AutomationNode = AutomationNode; |
865 exports.AutomationRootNode = AutomationRootNode; | 1054 exports.AutomationRootNode = AutomationRootNode; |
OLD | NEW |