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

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

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

Powered by Google App Engine
This is Rietveld 408576698