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

Side by Side Diff: chrome/renderer/resources/extensions/automation/automation_node.js

Issue 1251723003: Revert of Re-land: Reimplement automation API on top of C++-backed AXTree. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 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
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
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
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 nodeData.role == schema.RoleType.embeddedObject) {
784 if (nodeImpl.childTreeID !== nodeData.intAttributes.childTreeId)
785 nodeImpl.pendingChildFrame = true;
786
787 if (nodeImpl.pendingChildFrame) {
788 nodeImpl.childTreeID = nodeData.intAttributes.childTreeId;
789 automationUtil.storeTreeCallback(nodeImpl.childTreeID, function(root) {
790 nodeImpl.pendingChildFrame = false;
791 nodeImpl.childTree = root;
792 privates(root).impl.hostTree = node;
793 if (root.attributes.docLoadingProgress == 1)
794 privates(root).impl.dispatchEvent(schema.EventType.loadComplete);
795 nodeImpl.dispatchEvent(schema.EventType.childrenChanged);
796 });
797 automationInternal.enableFrame(nodeImpl.childTreeID);
798 }
799 }
800 for (var key in AutomationAttributeDefaults) {
801 if (key in nodeData)
802 nodeImpl[key] = nodeData[key];
803 else
804 nodeImpl[key] = AutomationAttributeDefaults[key];
805 }
806
807 // Set all basic attributes.
808 this.mixinAttributes_(nodeImpl, DefaultMixinAttributes, nodeData);
809
810 // If this is a rootWebArea or webArea, set document attributes.
811 if (nodeData.role == schema.RoleType.rootWebArea ||
812 nodeData.role == schema.RoleType.webArea) {
813 this.mixinAttributes_(nodeImpl, DocumentMixinAttributes, nodeData);
814 }
815
816 // If this is a scrollable area, set scrollable attributes.
817 for (var scrollAttr in ScrollableMixinAttributes) {
818 var spec = ScrollableMixinAttributes[scrollAttr];
819 if (this.findAttribute_(scrollAttr, spec, nodeData) !== undefined) {
820 this.mixinAttributes_(nodeImpl, ScrollableMixinAttributes, nodeData);
821 break;
822 }
823 }
824
825 // If this is inside a live region, set live region mixins.
826 var attr = 'containerLiveStatus';
827 var spec = LiveRegionMixinAttributes[attr];
828 if (this.findAttribute_(attr, spec, nodeData) !== undefined) {
829 this.mixinAttributes_(nodeImpl, LiveRegionMixinAttributes, nodeData);
830 }
831
832 // If this is a link, set link attributes
833 if (nodeData.role == 'link') {
834 this.mixinAttributes_(nodeImpl, LinkMixinAttributes, nodeData);
835 }
836
837 // If this is an editable text area, set editable text attributes.
838 if (nodeData.role == schema.RoleType.textField ||
839 nodeData.role == schema.RoleType.spinButton) {
840 this.mixinAttributes_(nodeImpl, EditableTextMixinAttributes, nodeData);
841 }
842
843 // If this is a range type, set range attributes.
844 if (nodeData.role == schema.RoleType.progressIndicator ||
845 nodeData.role == schema.RoleType.scrollBar ||
846 nodeData.role == schema.RoleType.slider ||
847 nodeData.role == schema.RoleType.spinButton) {
848 this.mixinAttributes_(nodeImpl, RangeMixinAttributes, nodeData);
849 }
850
851 // If this is a table, set table attributes.
852 if (nodeData.role == schema.RoleType.table) {
853 this.mixinAttributes_(nodeImpl, TableMixinAttributes, nodeData);
854 }
855
856 // If this is a table cell, set table cell attributes.
857 if (nodeData.role == schema.RoleType.cell) {
858 this.mixinAttributes_(nodeImpl, TableCellMixinAttributes, nodeData);
859 }
860
861 // If this has an active descendant, expose it.
862 if ('intAttributes' in nodeData &&
863 'activedescendantId' in nodeData.intAttributes) {
864 this.mixinAttributes_(nodeImpl, ActiveDescendantMixinAttribute, nodeData);
865 }
866
867 for (var i = 0; i < AutomationAttributeTypes.length; i++) {
868 var attributeType = AutomationAttributeTypes[i];
869 for (var attributeName in nodeData[attributeType]) {
870 nodeImpl.attributesInternal[attributeName] =
871 nodeData[attributeType][attributeName];
872 if (ATTRIBUTE_BLACKLIST.hasOwnProperty(attributeName) ||
873 nodeImpl.attributes.hasOwnProperty(attributeName)) {
874 continue;
875 } else if (
876 ATTRIBUTE_NAME_TO_ID_ATTRIBUTE.hasOwnProperty(attributeName)) {
877 this.defineReadonlyAttribute_(nodeImpl,
878 nodeImpl.attributes,
879 attributeName,
880 true);
881 } else {
882 this.defineReadonlyAttribute_(nodeImpl,
883 nodeImpl.attributes,
884 attributeName);
885 }
886 }
887 }
888 },
889
890 mixinAttributes_: function(nodeImpl, attributes, nodeData) {
891 for (var attribute in attributes) {
892 var spec = attributes[attribute];
893 if (spec.isRef)
894 this.mixinRelationshipAttribute_(nodeImpl, attribute, spec, nodeData);
895 else
896 this.mixinAttribute_(nodeImpl, attribute, spec, nodeData);
897 }
898 },
899
900 mixinAttribute_: function(nodeImpl, attribute, spec, nodeData) {
901 var value = this.findAttribute_(attribute, spec, nodeData);
902 if (value === undefined)
903 value = spec.default;
904 nodeImpl.attributesInternal[attribute] = value;
905 this.defineReadonlyAttribute_(nodeImpl, nodeImpl, attribute);
906 },
907
908 mixinRelationshipAttribute_: function(nodeImpl, attribute, spec, nodeData) {
909 var idAttribute = spec.idAttribute;
910 var idValue = spec.default;
911 if (spec.idFrom in nodeData) {
912 idValue = idAttribute in nodeData[spec.idFrom]
913 ? nodeData[spec.idFrom][idAttribute] : idValue;
914 }
915
916 // Ok to define a list attribute with an empty list, but not a
917 // single ref with a null ID.
918 if (idValue === null)
919 return;
920
921 nodeImpl.attributesInternal[idAttribute] = idValue;
922 this.defineReadonlyAttribute_(
923 nodeImpl, nodeImpl, attribute, true, idAttribute);
924 },
925
926 findAttribute_: function(attribute, spec, nodeData) {
927 if (!('reflectFrom' in spec))
928 return;
929 var attributeGroup = spec.reflectFrom;
930 if (!(attributeGroup in nodeData))
931 return;
932
933 return nodeData[attributeGroup][attribute];
934 },
935
936 defineReadonlyAttribute_: function(
937 node, object, attributeName, opt_isIDRef, opt_idAttribute) {
938 if (attributeName in object)
939 return;
940
941 if (opt_isIDRef) {
942 $Object.defineProperty(object, attributeName, {
943 enumerable: true,
944 get: function() {
945 var idAttribute = opt_idAttribute ||
946 ATTRIBUTE_NAME_TO_ID_ATTRIBUTE[attributeName];
947 var idValue = node.attributesInternal[idAttribute];
948 if (Array.isArray(idValue)) {
949 return idValue.map(function(current) {
950 return node.rootImpl.get(current);
951 }, this);
952 }
953 return node.rootImpl.get(idValue);
954 }.bind(this),
955 });
956 } else {
957 $Object.defineProperty(object, attributeName, {
958 enumerable: true,
959 get: function() {
960 return node.attributesInternal[attributeName];
961 }.bind(this),
962 });
963 }
964
965 if (object instanceof AutomationNodeImpl) {
966 // Also expose attribute publicly on the wrapper.
967 $Object.defineProperty(object.wrapper, attributeName, {
968 enumerable: true,
969 get: function() {
970 return object[attributeName];
971 },
972 });
973
974 }
975 },
976
977 updateNode_: function(nodeData, updateState) {
978 var node = this.axNodeDataCache_[nodeData.id];
979 var didUpdateRoot = false;
980 if (node) {
981 delete updateState.pendingNodes[privates(node).impl.id];
982 } else {
983 if (nodeData.role != schema.RoleType.rootWebArea &&
984 nodeData.role != schema.RoleType.desktop) {
985 logging.WARNING(String(nodeData.id) +
986 ' is not in the cache and not the new root.');
987 lastError.set('automation',
988 'Bad update received on automation tree',
989 null,
990 chrome);
991 return false;
992 }
993 // |this| is an AutomationRootNodeImpl; retrieve the
994 // AutomationRootNode instance instead.
995 node = this.wrapper;
996 didUpdateRoot = true;
997 updateState.newNodes[this.id] = this.wrapper;
998 }
999 this.setData_(node, nodeData);
1000
1001 // TODO(aboxhall): send onChanged event?
1002 logging.CHECK(node);
1003 if (!this.deleteOldChildren_(node, nodeData.childIds)) {
1004 if (didUpdateRoot) {
1005 this.invalidate_(this.wrapper);
1006 }
1007 return false;
1008 }
1009 var nodeImpl = privates(node).impl;
1010
1011 var success = this.createNewChildren_(node,
1012 nodeData.childIds,
1013 updateState);
1014 nodeImpl.childIds = nodeData.childIds;
1015 this.axNodeDataCache_[nodeImpl.id] = node;
1016
1017 return success;
1018 }
818 }; 1019 };
819 1020
1021
820 var AutomationNode = utils.expose('AutomationNode', 1022 var AutomationNode = utils.expose('AutomationNode',
821 AutomationNodeImpl, 1023 AutomationNodeImpl,
822 { functions: ['doDefault', 1024 { functions: ['doDefault',
823 'find', 1025 'find',
824 'findAll', 1026 'findAll',
825 'focus', 1027 'focus',
826 'makeVisible', 1028 'makeVisible',
827 'matches', 1029 'matches',
828 'setSelection', 1030 'setSelection',
829 'showContextMenu', 1031 'showContextMenu',
830 'addEventListener', 1032 'addEventListener',
831 'removeEventListener', 1033 'removeEventListener',
832 'domQuerySelector', 1034 'domQuerySelector',
833 'toString' ], 1035 'toString' ],
834 readonly: publicAttributes.concat( 1036 readonly: ['parent',
835 ['parent',
836 'firstChild', 1037 'firstChild',
837 'lastChild', 1038 'lastChild',
838 'children', 1039 'children',
839 'previousSibling', 1040 'previousSibling',
840 'nextSibling', 1041 'nextSibling',
841 'isRootNode', 1042 'isRootNode',
842 'role', 1043 'role',
843 'state', 1044 'state',
844 'location', 1045 'location',
1046 'attributes',
845 'indexInParent', 1047 'indexInParent',
846 'root']) }); 1048 'root'] });
847 1049
848 var AutomationRootNode = utils.expose('AutomationRootNode', 1050 var AutomationRootNode = utils.expose('AutomationRootNode',
849 AutomationRootNodeImpl, 1051 AutomationRootNodeImpl,
850 { superclass: AutomationNode }); 1052 { superclass: AutomationNode });
851 1053
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; 1054 exports.AutomationNode = AutomationNode;
865 exports.AutomationRootNode = AutomationRootNode; 1055 exports.AutomationRootNode = AutomationRootNode;
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698