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 | |
11 var lastError = require('lastError'); | 129 var lastError = require('lastError'); |
12 var logging = requireNative('logging'); | 130 var logging = requireNative('logging'); |
13 var schema = requireNative('automationInternal').GetSchemaAdditions(); | 131 var schema = requireNative('automationInternal').GetSchemaAdditions(); |
14 var utils = require('utils'); | 132 var utils = require('utils'); |
15 | 133 |
16 /** | 134 /** |
17 * A single node in the Automation tree. | 135 * A single node in the Automation tree. |
18 * @param {AutomationRootNodeImpl} root The root of the tree. | 136 * @param {AutomationRootNodeImpl} root The root of the tree. |
19 * @constructor | 137 * @constructor |
20 */ | 138 */ |
21 function AutomationNodeImpl(root) { | 139 function AutomationNodeImpl(root) { |
22 this.rootImpl = root; | 140 this.rootImpl = root; |
23 this.childIds = []; | |
24 // Public attributes. No actual data gets set on this object. | 141 // Public attributes. No actual data gets set on this object. |
25 this.attributes = {}; | |
26 // Internal object holding all attributes. | |
27 this.attributesInternal = {}; | |
28 this.listeners = {}; | 142 this.listeners = {}; |
29 this.location = { left: 0, top: 0, width: 0, height: 0 }; | |
30 } | 143 } |
31 | 144 |
32 AutomationNodeImpl.prototype = { | 145 AutomationNodeImpl.prototype = { |
146 treeID: -1, | |
33 id: -1, | 147 id: -1, |
34 role: '', | 148 role: '', |
35 state: { busy: true }, | 149 state: { busy: true }, |
36 isRootNode: false, | 150 isRootNode: false, |
37 | 151 |
38 get root() { | 152 get root() { |
39 return this.rootImpl.wrapper; | 153 return this.rootImpl.wrapper; |
40 }, | 154 }, |
41 | 155 |
42 get parent() { | 156 get parent() { |
43 return this.hostTree || this.rootImpl.get(this.parentID); | 157 if (this.hostNode) |
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); | |
44 }, | 183 }, |
45 | 184 |
46 get firstChild() { | 185 get firstChild() { |
47 return this.childTree || this.rootImpl.get(this.childIds[0]); | 186 if (this.childTree) |
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); | |
48 }, | 192 }, |
49 | 193 |
50 get lastChild() { | 194 get lastChild() { |
51 var childIds = this.childIds; | 195 if (this.childTree) |
52 return this.childTree || this.rootImpl.get(childIds[childIds.length - 1]); | 196 return this.childTree; |
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); | |
53 }, | 202 }, |
54 | 203 |
55 get children() { | 204 get children() { |
56 if (this.childTree) | 205 if (this.childTree) |
57 return [this.childTree]; | 206 return [this.childTree]; |
58 | 207 |
59 var children = []; | 208 var children = []; |
60 for (var i = 0, childID; childID = this.childIds[i]; i++) { | 209 var count = GetChildCount(this.treeID, this.id); |
61 logging.CHECK(this.rootImpl.get(childID)); | 210 for (var i = 0; i < count; ++i) { |
62 children.push(this.rootImpl.get(childID)); | 211 var childID = GetChildIDAtIndex(this.treeID, this.id, i); |
212 var child = this.rootImpl.get(childID); | |
213 children.push(child); | |
63 } | 214 } |
64 return children; | 215 return children; |
65 }, | 216 }, |
66 | 217 |
67 get previousSibling() { | 218 get previousSibling() { |
68 var parent = this.parent; | 219 var parent = this.parent; |
69 if (parent && this.indexInParent > 0) | 220 var indexInParent = GetIndexInParent(this.treeID, this.id); |
70 return parent.children[this.indexInParent - 1]; | 221 if (parent && indexInParent > 0) |
222 return parent.children[indexInParent - 1]; | |
71 return undefined; | 223 return undefined; |
72 }, | 224 }, |
73 | 225 |
74 get nextSibling() { | 226 get nextSibling() { |
75 var parent = this.parent; | 227 var parent = this.parent; |
76 if (parent && this.indexInParent < parent.children.length) | 228 var indexInParent = GetIndexInParent(this.treeID, this.id); |
77 return parent.children[this.indexInParent + 1]; | 229 if (parent && indexInParent < parent.children.length) |
230 return parent.children[indexInParent + 1]; | |
78 return undefined; | 231 return undefined; |
79 }, | 232 }, |
80 | 233 |
81 doDefault: function() { | 234 doDefault: function() { |
82 this.performAction_('doDefault'); | 235 this.performAction_('doDefault'); |
83 }, | 236 }, |
84 | 237 |
85 focus: function() { | 238 focus: function() { |
86 this.performAction_('focus'); | 239 this.performAction_('focus'); |
87 }, | 240 }, |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
163 if (this.dispatchEventAtCapturing_(event, path)) { | 316 if (this.dispatchEventAtCapturing_(event, path)) { |
164 if (this.dispatchEventAtTargeting_(event, path)) | 317 if (this.dispatchEventAtTargeting_(event, path)) |
165 this.dispatchEventAtBubbling_(event, path); | 318 this.dispatchEventAtBubbling_(event, path); |
166 } | 319 } |
167 }, | 320 }, |
168 | 321 |
169 toString: function() { | 322 toString: function() { |
170 var impl = privates(this).impl; | 323 var impl = privates(this).impl; |
171 if (!impl) | 324 if (!impl) |
172 impl = this; | 325 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 | |
173 return 'node id=' + impl.id + | 335 return 'node id=' + impl.id + |
174 ' role=' + this.role + | 336 ' role=' + this.role + |
175 ' state=' + $JSON.stringify(this.state) + | 337 ' state=' + $JSON.stringify(this.state) + |
176 ' parentID=' + impl.parentID + | 338 ' parentID=' + parentID + |
177 ' childIds=' + $JSON.stringify(impl.childIds) + | 339 ' childIds=' + $JSON.stringify(childIDs); |
178 ' attributes=' + $JSON.stringify(this.attributes); | |
179 }, | 340 }, |
180 | 341 |
181 dispatchEventAtCapturing_: function(event, path) { | 342 dispatchEventAtCapturing_: function(event, path) { |
182 privates(event).impl.eventPhase = Event.CAPTURING_PHASE; | 343 privates(event).impl.eventPhase = Event.CAPTURING_PHASE; |
183 for (var i = path.length - 1; i >= 0; i--) { | 344 for (var i = path.length - 1; i >= 0; i--) { |
184 this.fireEventListeners_(path[i], event); | 345 this.fireEventListeners_(path[i], event); |
185 if (privates(event).impl.propagationStopped) | 346 if (privates(event).impl.propagationStopped) |
186 return false; | 347 return false; |
187 } | 348 } |
188 return true; | 349 return true; |
(...skipping 23 matching lines...) Expand all Loading... | |
212 var eventPhase = event.eventPhase; | 373 var eventPhase = event.eventPhase; |
213 for (var i = 0; i < listeners.length; i++) { | 374 for (var i = 0; i < listeners.length; i++) { |
214 if (eventPhase == Event.CAPTURING_PHASE && !listeners[i].capture) | 375 if (eventPhase == Event.CAPTURING_PHASE && !listeners[i].capture) |
215 continue; | 376 continue; |
216 if (eventPhase == Event.BUBBLING_PHASE && listeners[i].capture) | 377 if (eventPhase == Event.BUBBLING_PHASE && listeners[i].capture) |
217 continue; | 378 continue; |
218 | 379 |
219 try { | 380 try { |
220 listeners[i].callback(event); | 381 listeners[i].callback(event); |
221 } catch (e) { | 382 } catch (e) { |
222 console.error('Error in event handler for ' + event.type + | 383 logging.WARNING('Error in event handler for ' + event.type + |
223 'during phase ' + eventPhase + ': ' + | 384 ' during phase ' + eventPhase + ': ' + |
224 e.message + '\nStack trace: ' + e.stack); | 385 e.message + '\nStack trace: ' + e.stack); |
225 } | 386 } |
226 } | 387 } |
227 }, | 388 }, |
228 | 389 |
229 performAction_: function(actionType, opt_args) { | 390 performAction_: function(actionType, opt_args) { |
230 // Not yet initialized. | 391 // Not yet initialized. |
231 if (this.rootImpl.treeID === undefined || | 392 if (this.rootImpl.treeID === undefined || |
232 this.id === undefined) { | 393 this.id === undefined) { |
233 return; | 394 return; |
234 } | 395 } |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
305 return false; | 466 return false; |
306 | 467 |
307 if ('state' in params) { | 468 if ('state' in params) { |
308 for (var state in params.state) { | 469 for (var state in params.state) { |
309 if (params.state[state] != (state in this.state)) | 470 if (params.state[state] != (state in this.state)) |
310 return false; | 471 return false; |
311 } | 472 } |
312 } | 473 } |
313 if ('attributes' in params) { | 474 if ('attributes' in params) { |
314 for (var attribute in params.attributes) { | 475 for (var attribute in params.attributes) { |
315 if (!(attribute in this.attributesInternal)) | |
316 return false; | |
317 | |
318 var attrValue = params.attributes[attribute]; | 476 var attrValue = params.attributes[attribute]; |
319 if (typeof attrValue != 'object') { | 477 if (typeof attrValue != 'object') { |
320 if (this.attributesInternal[attribute] !== attrValue) | 478 if (this[attribute] !== attrValue) |
321 return false; | 479 return false; |
322 } else if (attrValue instanceof RegExp) { | 480 } else if (attrValue instanceof RegExp) { |
323 if (typeof this.attributesInternal[attribute] != 'string') | 481 if (typeof this[attribute] != 'string') |
324 return false; | 482 return false; |
325 if (!attrValue.test(this.attributesInternal[attribute])) | 483 if (!attrValue.test(this[attribute])) |
326 return false; | 484 return false; |
327 } else { | 485 } else { |
328 // TODO(aboxhall): handle intlist case. | 486 // TODO(aboxhall): handle intlist case. |
329 return false; | 487 return false; |
330 } | 488 } |
331 } | 489 } |
332 } | 490 } |
333 return true; | 491 return true; |
334 } | 492 } |
335 }; | 493 }; |
336 | 494 |
337 // Maps an attribute to its default value in an invalidated node. | 495 var stringAttributes = [ |
338 // These attributes are taken directly from the Automation idl. | 496 'accessKey', |
339 var AutomationAttributeDefaults = { | 497 'action', |
340 'id': -1, | 498 'ariaInvalidValue', |
341 'role': '', | 499 'autoComplete', |
342 'state': {}, | 500 'containerLiveRelevant', |
343 'location': { left: 0, top: 0, width: 0, height: 0 } | 501 'containerLiveStatus', |
344 }; | 502 'description', |
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']; | |
345 | 519 |
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']; | |
346 | 533 |
347 var AutomationAttributeTypes = [ | 534 var intAttributes = [ |
348 'boolAttributes', | 535 'backgroundColor', |
349 'floatAttributes', | 536 'color', |
350 'htmlAttributes', | 537 'colorValue', |
351 'intAttributes', | 538 'hierarchicalLevel', |
352 'intlistAttributes', | 539 'invalidState', |
353 'stringAttributes' | 540 'posInSet', |
354 ]; | 541 'scrollX', |
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']; | |
355 | 561 |
356 /** | 562 var nodeRefAttributes = [ |
357 * Maps an attribute name to another attribute who's value is an id or an array | 563 ['activedescendantId', 'activedescendant'], |
358 * of ids referencing an AutomationNode. | 564 ['tableColumnHeaderId', 'tableColumnHeader'], |
359 * @param {!Object<string>} | 565 ['tableHeaderId', 'tableHeader'], |
360 * @const | 566 ['tableRowHeaderId', 'tableRowHeader'], |
361 */ | 567 ['titleUiElement', 'titleUIElement']]; |
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 }; | |
370 | 568 |
371 /** | 569 var intListAttributes = [ |
372 * A set of attributes ignored in the automation API. | 570 'characterOffsets', |
373 * @param {!Object<boolean>} | 571 'lineBreaks', |
374 * @const | 572 'wordEnds', |
375 */ | 573 'wordStarts']; |
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 }; | |
384 | 574 |
385 function defaultStringAttribute(opt_defaultVal) { | 575 var nodeRefListAttributes = [ |
386 return { default: undefined, reflectFrom: 'stringAttributes' }; | 576 ['cellIds', 'cells'], |
387 } | 577 ['controlsIds', 'controls'], |
578 ['describedbyIds', 'describedBy'], | |
579 ['flowtoIds', 'flowTo'], | |
580 ['labelledbyIds', 'labelledBy'], | |
581 ['uniqueCellIds', 'uniqueCells']]; | |
388 | 582 |
389 function defaultIntAttribute(opt_defaultVal) { | 583 var floatAttributes = [ |
390 var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : 0; | 584 'docLoadingProgress', |
391 return { default: defaultVal, reflectFrom: 'intAttributes' }; | 585 'valueForRange', |
392 } | 586 'minValueForRange', |
587 'maxValueForRange', | |
588 'fontSize']; | |
393 | 589 |
394 function defaultFloatAttribute(opt_defaultVal) { | 590 var htmlAttributes = [ |
395 var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : 0; | 591 ['type', 'inputType']]; |
396 return { default: defaultVal, reflectFrom: 'floatAttributes' }; | |
397 } | |
398 | 592 |
399 function defaultBoolAttribute(opt_defaultVal) { | 593 var publicAttributes = []; |
400 var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : false; | |
401 return { default: defaultVal, reflectFrom: 'boolAttributes' }; | |
402 } | |
403 | 594 |
404 function defaultHtmlAttribute(opt_defaultVal) { | 595 stringAttributes.forEach(function (attributeName) { |
405 var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : ''; | 596 publicAttributes.push(attributeName); |
406 return { default: defaultVal, reflectFrom: 'htmlAttributes' }; | 597 Object.defineProperty(AutomationNodeImpl.prototype, attributeName, { |
407 } | 598 get: function() { |
599 return GetStringAttribute(this.treeID, this.id, attributeName); | |
600 } | |
601 }); | |
602 }); | |
408 | 603 |
409 function defaultIntListAttribute(opt_defaultVal) { | 604 boolAttributes.forEach(function (attributeName) { |
410 var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : []; | 605 publicAttributes.push(attributeName); |
411 return { default: defaultVal, reflectFrom: 'intlistAttributes' }; | 606 Object.defineProperty(AutomationNodeImpl.prototype, attributeName, { |
412 } | 607 get: function() { |
608 return GetBoolAttribute(this.treeID, this.id, attributeName); | |
609 } | |
610 }); | |
611 }); | |
413 | 612 |
414 function defaultNodeRefAttribute(idAttribute, opt_defaultVal) { | 613 intAttributes.forEach(function (attributeName) { |
415 var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : null; | 614 publicAttributes.push(attributeName); |
416 return { default: defaultVal, | 615 Object.defineProperty(AutomationNodeImpl.prototype, attributeName, { |
417 idFrom: 'intAttributes', | 616 get: function() { |
418 idAttribute: idAttribute, | 617 return GetIntAttribute(this.treeID, this.id, attributeName); |
419 isRef: true }; | 618 } |
420 } | 619 }); |
620 }); | |
421 | 621 |
422 function defaultNodeRefListAttribute(idAttribute, opt_defaultVal) { | 622 nodeRefAttributes.forEach(function (params) { |
423 var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : []; | 623 var srcAttributeName = params[0]; |
424 return { default: [], | 624 var dstAttributeName = params[1]; |
425 idFrom: 'intlistAttributes', | 625 publicAttributes.push(dstAttributeName); |
426 idAttribute: idAttribute, | 626 Object.defineProperty(AutomationNodeImpl.prototype, dstAttributeName, { |
427 isRef: true }; | 627 get: function() { |
428 } | 628 var id = GetIntAttribute(this.treeID, this.id, srcAttributeName); |
629 if (id) | |
630 return this.rootImpl.get(id); | |
631 else | |
632 return undefined; | |
633 } | |
634 }); | |
635 }); | |
429 | 636 |
430 // Maps an attribute to its default value in an invalidated node. | 637 intListAttributes.forEach(function (attributeName) { |
431 // These attributes are taken directly from the Automation idl. | 638 publicAttributes.push(attributeName); |
432 var DefaultMixinAttributes = { | 639 Object.defineProperty(AutomationNodeImpl.prototype, attributeName, { |
433 description: defaultStringAttribute(), | 640 get: function() { |
434 help: defaultStringAttribute(), | 641 return GetIntListAttribute(this.treeID, this.id, attributeName); |
435 name: defaultStringAttribute(), | 642 } |
436 value: defaultStringAttribute(), | 643 }); |
437 htmlTag: defaultStringAttribute(), | 644 }); |
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 }; | |
447 | 645 |
448 var ActiveDescendantMixinAttribute = { | 646 nodeRefListAttributes.forEach(function (params) { |
449 activedescendant: defaultNodeRefAttribute('activedescendantId') | 647 var srcAttributeName = params[0]; |
450 }; | 648 var dstAttributeName = params[1]; |
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 }); | |
451 | 665 |
452 var LinkMixinAttributes = { | 666 floatAttributes.forEach(function (attributeName) { |
453 url: defaultStringAttribute() | 667 publicAttributes.push(attributeName); |
454 }; | 668 Object.defineProperty(AutomationNodeImpl.prototype, attributeName, { |
669 get: function() { | |
670 return GetFloatAttribute(this.treeID, this.id, attributeName); | |
671 } | |
672 }); | |
673 }); | |
455 | 674 |
456 var DocumentMixinAttributes = { | 675 htmlAttributes.forEach(function (params) { |
457 docUrl: defaultStringAttribute(), | 676 var srcAttributeName = params[0]; |
458 docTitle: defaultStringAttribute(), | 677 var dstAttributeName = params[1]; |
459 docLoaded: defaultStringAttribute(), | 678 publicAttributes.push(dstAttributeName); |
460 docLoadingProgress: defaultFloatAttribute() | 679 Object.defineProperty(AutomationNodeImpl.prototype, dstAttributeName, { |
461 }; | 680 get: function() { |
462 | 681 return GetHtmlAttribute(this.treeID, this.id, srcAttributeName); |
463 var ScrollableMixinAttributes = { | 682 } |
464 scrollX: defaultIntAttribute(), | 683 }); |
465 scrollXMin: defaultIntAttribute(), | 684 }); |
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 }; | |
502 | 685 |
503 /** | 686 /** |
504 * AutomationRootNode. | 687 * AutomationRootNode. |
505 * | 688 * |
506 * An AutomationRootNode is the javascript end of an AXTree living in the | 689 * An AutomationRootNode is the javascript end of an AXTree living in the |
507 * browser. AutomationRootNode handles unserializing incremental updates from | 690 * browser. AutomationRootNode handles unserializing incremental updates from |
508 * the source AXTree. Each update contains node data that form a complete tree | 691 * the source AXTree. Each update contains node data that form a complete tree |
509 * after applying the update. | 692 * after applying the update. |
510 * | 693 * |
511 * A brief note about ids used through this class. The source AXTree assigns | 694 * A brief note about ids used through this class. The source AXTree assigns |
512 * unique ids per node and we use these ids to build a hash to the actual | 695 * unique ids per node and we use these ids to build a hash to the actual |
513 * AutomationNode object. | 696 * AutomationNode object. |
514 * Thus, tree traversals amount to a lookup in our hash. | 697 * Thus, tree traversals amount to a lookup in our hash. |
515 * | 698 * |
516 * The tree itself is identified by the accessibility tree id of the | 699 * The tree itself is identified by the accessibility tree id of the |
517 * renderer widget host. | 700 * renderer widget host. |
518 * @constructor | 701 * @constructor |
519 */ | 702 */ |
520 function AutomationRootNodeImpl(treeID) { | 703 function AutomationRootNodeImpl(treeID) { |
521 AutomationNodeImpl.call(this, this); | 704 AutomationNodeImpl.call(this, this); |
522 this.treeID = treeID; | 705 this.treeID = treeID; |
523 this.axNodeDataCache_ = {}; | 706 this.axNodeDataCache_ = {}; |
707 this.hostNode = null; | |
not at google - send to devlin
2015/06/18 17:34:38
Initialisation of these properties should go in th
dmazzoni
2015/06/18 21:31:53
Done - moved to the prototype, made private, and a
| |
708 } | |
709 | |
710 AutomationRootNodeImpl.idToAutomationRootNode_ = {}; | |
711 | |
712 AutomationRootNodeImpl.get = function(treeID) { | |
713 var result = AutomationRootNodeImpl.idToAutomationRootNode_[treeID]; | |
714 return result || undefined; | |
715 } | |
716 | |
717 AutomationRootNodeImpl.getOrCreate = function(treeID) { | |
718 if (AutomationRootNodeImpl.idToAutomationRootNode_[treeID]) | |
719 return AutomationRootNodeImpl.idToAutomationRootNode_[treeID]; | |
720 var result = new AutomationRootNode(treeID); | |
721 AutomationRootNodeImpl.idToAutomationRootNode_[treeID] = result; | |
722 return result; | |
723 } | |
724 | |
725 AutomationRootNodeImpl.destroy = function(treeID) { | |
726 delete AutomationRootNodeImpl.idToAutomationRootNode_[treeID]; | |
524 } | 727 } |
525 | 728 |
526 AutomationRootNodeImpl.prototype = { | 729 AutomationRootNodeImpl.prototype = { |
527 __proto__: AutomationNodeImpl.prototype, | 730 __proto__: AutomationNodeImpl.prototype, |
528 | 731 |
529 isRootNode: true, | 732 isRootNode: true, |
530 treeID: -1, | 733 treeID: -1, |
531 | 734 |
735 get id() { | |
736 return GetRootID(this.treeID); | |
737 }, | |
738 | |
532 get: function(id) { | 739 get: function(id) { |
533 if (id == undefined) | 740 if (id == undefined) |
534 return undefined; | 741 return undefined; |
535 | 742 |
536 return this.axNodeDataCache_[id]; | 743 if (id == this.id) |
744 return this.wrapper; | |
745 | |
746 var obj = this.axNodeDataCache_[id]; | |
747 if (obj) | |
748 return obj; | |
749 | |
750 obj = new AutomationNode(this); | |
751 privates(obj).impl.treeID = this.treeID; | |
752 privates(obj).impl.id = id; | |
753 this.axNodeDataCache_[id] = obj; | |
754 | |
755 return obj; | |
537 }, | 756 }, |
538 | 757 |
539 unserialize: function(update) { | 758 remove: function(id) { |
540 var updateState = { pendingNodes: {}, newNodes: {} }; | 759 delete this.axNodeDataCache_[id]; |
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; | |
610 }, | 760 }, |
611 | 761 |
612 destroy: function() { | 762 destroy: function() { |
613 if (this.hostTree) | 763 if (this.hostTree) |
614 this.hostTree.childTree = undefined; | 764 this.hostTree.childTree = undefined; |
615 this.hostTree = undefined; | 765 this.hostTree = undefined; |
616 | 766 |
617 this.dispatchEvent(schema.EventType.destroyed); | 767 this.dispatchEvent(schema.EventType.destroyed); |
618 this.invalidate_(this.wrapper); | 768 }, |
769 | |
770 setHostNode(hostNode) { | |
771 this.hostNode = hostNode; | |
619 }, | 772 }, |
620 | 773 |
621 onAccessibilityEvent: function(eventParams) { | 774 onAccessibilityEvent: function(eventParams) { |
622 if (!this.unserialize(eventParams.update)) { | |
623 logging.WARNING('unserialization failed'); | |
624 return false; | |
625 } | |
626 | |
627 var targetNode = this.get(eventParams.targetID); | 775 var targetNode = this.get(eventParams.targetID); |
628 if (targetNode) { | 776 if (targetNode) { |
629 var targetNodeImpl = privates(targetNode).impl; | 777 var targetNodeImpl = privates(targetNode).impl; |
630 targetNodeImpl.dispatchEvent(eventParams.eventType); | 778 targetNodeImpl.dispatchEvent(eventParams.eventType); |
631 } else { | 779 } else { |
632 logging.WARNING('Got ' + eventParams.eventType + | 780 logging.WARNING('Got ' + eventParams.eventType + |
633 ' event on unknown node: ' + eventParams.targetID + | 781 ' event on unknown node: ' + eventParams.targetID + |
634 '; this: ' + this.id); | 782 '; this: ' + this.id); |
635 } | 783 } |
636 return true; | 784 return true; |
637 }, | 785 }, |
638 | 786 |
639 toString: function() { | 787 toString: function() { |
640 function toStringInternal(node, indent) { | 788 function toStringInternal(node, indent) { |
641 if (!node) | 789 if (!node) |
642 return ''; | 790 return ''; |
643 var output = | 791 var output = |
644 new Array(indent).join(' ') + | 792 new Array(indent).join(' ') + |
645 AutomationNodeImpl.prototype.toString.call(node) + | 793 AutomationNodeImpl.prototype.toString.call(node) + |
646 '\n'; | 794 '\n'; |
647 indent += 2; | 795 indent += 2; |
648 for (var i = 0; i < node.children.length; i++) | 796 for (var i = 0; i < node.children.length; i++) |
649 output += toStringInternal(node.children[i], indent); | 797 output += toStringInternal(node.children[i], indent); |
650 return output; | 798 return output; |
651 } | 799 } |
652 return toStringInternal(this, 0); | 800 return toStringInternal(this, 0); |
653 }, | 801 }, |
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 } | |
1018 }; | 802 }; |
1019 | 803 |
1020 | |
1021 var AutomationNode = utils.expose('AutomationNode', | 804 var AutomationNode = utils.expose('AutomationNode', |
1022 AutomationNodeImpl, | 805 AutomationNodeImpl, |
1023 { functions: ['doDefault', | 806 { functions: ['doDefault', |
1024 'find', | 807 'find', |
1025 'findAll', | 808 'findAll', |
1026 'focus', | 809 'focus', |
1027 'makeVisible', | 810 'makeVisible', |
1028 'matches', | 811 'matches', |
1029 'setSelection', | 812 'setSelection', |
1030 'showContextMenu', | 813 'showContextMenu', |
1031 'addEventListener', | 814 'addEventListener', |
1032 'removeEventListener', | 815 'removeEventListener', |
1033 'domQuerySelector', | 816 'domQuerySelector', |
1034 'toString' ], | 817 'toString' ], |
1035 readonly: ['parent', | 818 readonly: publicAttributes.concat( |
819 ['parent', | |
1036 'firstChild', | 820 'firstChild', |
1037 'lastChild', | 821 'lastChild', |
1038 'children', | 822 'children', |
1039 'previousSibling', | 823 'previousSibling', |
1040 'nextSibling', | 824 'nextSibling', |
1041 'isRootNode', | 825 'isRootNode', |
1042 'role', | 826 'role', |
1043 'state', | 827 'state', |
1044 'location', | 828 'location', |
1045 'attributes', | |
1046 'indexInParent', | 829 'indexInParent', |
1047 'root'] }); | 830 'root']) }); |
1048 | 831 |
1049 var AutomationRootNode = utils.expose('AutomationRootNode', | 832 var AutomationRootNode = utils.expose('AutomationRootNode', |
1050 AutomationRootNodeImpl, | 833 AutomationRootNodeImpl, |
1051 { superclass: AutomationNode }); | 834 { superclass: AutomationNode }); |
1052 | 835 |
836 AutomationRootNode.get = function(treeID) { | |
837 return AutomationRootNodeImpl.get(treeID); | |
838 } | |
839 | |
840 AutomationRootNode.getOrCreate = function(treeID) { | |
841 return AutomationRootNodeImpl.getOrCreate(treeID); | |
842 } | |
843 | |
844 AutomationRootNode.destroy = function(treeID) { | |
845 AutomationRootNodeImpl.destroy(treeID); | |
846 } | |
847 | |
1053 exports.AutomationNode = AutomationNode; | 848 exports.AutomationNode = AutomationNode; |
1054 exports.AutomationRootNode = AutomationRootNode; | 849 exports.AutomationRootNode = AutomationRootNode; |
OLD | NEW |