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