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

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

Powered by Google App Engine
This is Rietveld 408576698