OLD | NEW |
---|---|
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 var AutomationEvent = require('automationEvent').AutomationEvent; | 5 var AutomationEvent = require('automationEvent').AutomationEvent; |
6 var automationInternal = | 6 var automationInternal = |
7 require('binding').Binding.create('automationInternal').generate(); | 7 require('binding').Binding.create('automationInternal').generate(); |
8 var IsInteractPermitted = | 8 var IsInteractPermitted = |
9 requireNative('automationInternal').IsInteractPermitted; | 9 requireNative('automationInternal').IsInteractPermitted; |
10 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 Loading... | |
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 Loading... | |
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; |
OLD | NEW |