| 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 var lastError = require('lastError'); | 11 var lastError = require('lastError'); |
| 12 var logging = requireNative('logging'); | 12 var logging = requireNative('logging'); |
| 13 var schema = requireNative('automationInternal').GetSchemaAdditions(); | 13 var schema = requireNative('automationInternal').GetSchemaAdditions(); |
| 14 var utils = require('utils'); | 14 var utils = require('utils'); |
| 15 | 15 |
| 16 /** | 16 /** |
| 17 * Maps an id in the form: |
| 18 * <process_id>_<routing_id> |
| 19 * to an AutomationNode with role type webArea. |
| 20 * @type {!Object.<string, string>} |
| 21 */ |
| 22 var idToWebArea_ = {}; |
| 23 |
| 24 /** |
| 17 * A single node in the Automation tree. | 25 * A single node in the Automation tree. |
| 18 * @param {AutomationRootNodeImpl} root The root of the tree. | 26 * @param {AutomationRootNodeImpl} root The root of the tree. |
| 19 * @constructor | 27 * @constructor |
| 20 */ | 28 */ |
| 21 function AutomationNodeImpl(root) { | 29 function AutomationNodeImpl(root) { |
| 22 this.rootImpl = root; | 30 this.rootImpl = root; |
| 23 this.childIds = []; | 31 this.childIds = []; |
| 24 // Public attributes. No actual data gets set on this object. | 32 // Public attributes. No actual data gets set on this object. |
| 25 this.attributes = {}; | 33 this.attributes = {}; |
| 26 // Internal object holding all attributes. | 34 // Internal object holding all attributes. |
| 27 this.attributesInternal = {}; | 35 this.attributesInternal = {}; |
| 28 this.listeners = {}; | 36 this.listeners = {}; |
| 29 this.location = { left: 0, top: 0, width: 0, height: 0 }; | 37 this.location = { left: 0, top: 0, width: 0, height: 0 }; |
| 30 } | 38 } |
| 31 | 39 |
| 32 AutomationNodeImpl.prototype = { | 40 AutomationNodeImpl.prototype = { |
| 33 id: -1, | 41 id: -1, |
| 34 role: '', | 42 role: '', |
| 35 state: { busy: true }, | 43 state: { busy: true }, |
| 36 isRootNode: false, | 44 isRootNode: false, |
| 37 | 45 |
| 38 get root() { | 46 get root() { |
| 39 return this.rootImpl.wrapper; | 47 return this.rootImpl.wrapper; |
| 40 }, | 48 }, |
| 41 | 49 |
| 42 parent: function() { | 50 parent: function() { |
| 51 if (this.role == schema.RoleType.rootWebArea) { |
| 52 var parentId = this.processID + '_' + this.routingID; |
| 53 if (idToWebArea_[parentId]) |
| 54 return idToWebArea_[parentId]; |
| 55 } |
| 43 return this.rootImpl.get(this.parentID); | 56 return this.rootImpl.get(this.parentID); |
| 44 }, | 57 }, |
| 45 | 58 |
| 46 firstChild: function() { | 59 firstChild: function() { |
| 47 var node = this.rootImpl.get(this.childIds[0]); | 60 return this.lookupWebAreaChild_() || this.rootImpl.get(this.childIds[0]); |
| 48 return node; | |
| 49 }, | 61 }, |
| 50 | 62 |
| 51 lastChild: function() { | 63 lastChild: function() { |
| 52 var childIds = this.childIds; | 64 var childIds = this.childIds; |
| 53 var node = this.rootImpl.get(childIds[childIds.length - 1]); | 65 return this.lookupWebAreaChild_() || |
| 54 return node; | 66 this.rootImpl.get(childIds[childIds.length - 1]); |
| 55 }, | 67 }, |
| 56 | 68 |
| 57 children: function() { | 69 children: function() { |
| 58 var children = []; | 70 var children = []; |
| 59 for (var i = 0, childID; childID = this.childIds[i]; i++) { | 71 for (var i = 0, childID; childID = this.childIds[i]; i++) { |
| 60 logging.CHECK(this.rootImpl.get(childID)); | 72 logging.CHECK(this.rootImpl.get(childID)); |
| 61 children.push(this.rootImpl.get(childID)); | 73 children.push(this.rootImpl.get(childID)); |
| 62 } | 74 } |
| 63 return children; | 75 return children; |
| 64 }, | 76 }, |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 111 listeners.splice(i, 1); | 123 listeners.splice(i, 1); |
| 112 } | 124 } |
| 113 } | 125 } |
| 114 }, | 126 }, |
| 115 | 127 |
| 116 dispatchEvent: function(eventType) { | 128 dispatchEvent: function(eventType) { |
| 117 var path = []; | 129 var path = []; |
| 118 var parent = this.parent(); | 130 var parent = this.parent(); |
| 119 while (parent) { | 131 while (parent) { |
| 120 path.push(parent); | 132 path.push(parent); |
| 121 // TODO(aboxhall/dtseng): handle unloaded parent node | |
| 122 parent = parent.parent(); | 133 parent = parent.parent(); |
| 123 } | 134 } |
| 124 var event = new AutomationEvent(eventType, this.wrapper); | 135 var event = new AutomationEvent(eventType, this.wrapper); |
| 125 | 136 |
| 126 // Dispatch the event through the propagation path in three phases: | 137 // Dispatch the event through the propagation path in three phases: |
| 127 // - capturing: starting from the root and going down to the target's parent | 138 // - capturing: starting from the root and going down to the target's parent |
| 128 // - targeting: dispatching the event on the target itself | 139 // - targeting: dispatching the event on the target itself |
| 129 // - bubbling: starting from the target's parent, going back up to the root. | 140 // - bubbling: starting from the target's parent, going back up to the root. |
| 130 // At any stage, a listener may call stopPropagation() on the event, which | 141 // At any stage, a listener may call stopPropagation() on the event, which |
| 131 // will immediately stop event propagation through this path. | 142 // will immediately stop event propagation through this path. |
| 132 if (this.dispatchEventAtCapturing_(event, path)) { | 143 if (this.dispatchEventAtCapturing_(event, path)) { |
| 133 if (this.dispatchEventAtTargeting_(event, path)) | 144 if (this.dispatchEventAtTargeting_(event, path)) |
| 134 this.dispatchEventAtBubbling_(event, path); | 145 this.dispatchEventAtBubbling_(event, path); |
| 135 } | 146 } |
| 136 }, | 147 }, |
| 137 | 148 |
| 138 toString: function() { | 149 toString: function() { |
| 139 return 'node id=' + this.id + | 150 return 'node id=' + this.id + |
| 140 ' role=' + this.role + | 151 ' role=' + this.role + |
| 141 ' state=' + $JSON.stringify(this.state) + | 152 ' state=' + $JSON.stringify(this.state) + |
| 142 ' parentID=' + this.parentID + | 153 ' parentID=' + this.parentID + |
| 143 ' childIds=' + $JSON.stringify(this.childIds) + | 154 ' childIds=' + $JSON.stringify(this.childIds) + |
| 144 ' attributes=' + $JSON.stringify(this.attributes); | 155 ' attributes=' + $JSON.stringify(this.attributes); |
| 145 }, | 156 }, |
| 146 | 157 |
| 158 lookupWebAreaChild_: function() { |
| 159 if (this.role != schema.RoleType.webArea) |
| 160 return null; |
| 161 |
| 162 return automationUtil.idToAutomationRootNode[ |
| 163 automationUtil.createAutomationRootNodeID( |
| 164 this.processID, this.routingID)]; |
| 165 }, |
| 166 |
| 147 dispatchEventAtCapturing_: function(event, path) { | 167 dispatchEventAtCapturing_: function(event, path) { |
| 148 privates(event).impl.eventPhase = Event.CAPTURING_PHASE; | 168 privates(event).impl.eventPhase = Event.CAPTURING_PHASE; |
| 149 for (var i = path.length - 1; i >= 0; i--) { | 169 for (var i = path.length - 1; i >= 0; i--) { |
| 150 this.fireEventListeners_(path[i], event); | 170 this.fireEventListeners_(path[i], event); |
| 151 if (privates(event).impl.propagationStopped) | 171 if (privates(event).impl.propagationStopped) |
| 152 return false; | 172 return false; |
| 153 } | 173 } |
| 154 return true; | 174 return true; |
| 155 }, | 175 }, |
| 156 | 176 |
| (...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 285 this.processID = processID; | 305 this.processID = processID; |
| 286 this.routingID = routingID; | 306 this.routingID = routingID; |
| 287 this.axNodeDataCache_ = {}; | 307 this.axNodeDataCache_ = {}; |
| 288 } | 308 } |
| 289 | 309 |
| 290 AutomationRootNodeImpl.prototype = { | 310 AutomationRootNodeImpl.prototype = { |
| 291 __proto__: AutomationNodeImpl.prototype, | 311 __proto__: AutomationNodeImpl.prototype, |
| 292 | 312 |
| 293 isRootNode: true, | 313 isRootNode: true, |
| 294 | 314 |
| 315 loaded: false, |
| 316 |
| 295 get: function(id) { | 317 get: function(id) { |
| 296 if (id == undefined) | 318 if (id == undefined) |
| 297 return undefined; | 319 return undefined; |
| 298 | 320 |
| 299 return this.axNodeDataCache_[id]; | 321 return this.axNodeDataCache_[id]; |
| 300 }, | 322 }, |
| 301 | 323 |
| 302 unserialize: function(update) { | 324 unserialize: function(update) { |
| 303 var updateState = { pendingNodes: {}, newNodes: {} }; | 325 var updateState = { pendingNodes: {}, newNodes: {} }; |
| 304 var oldRootId = this.id; | 326 var oldRootId = this.id; |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 346 null, | 368 null, |
| 347 chrome); | 369 chrome); |
| 348 return false; | 370 return false; |
| 349 } | 371 } |
| 350 return true; | 372 return true; |
| 351 }, | 373 }, |
| 352 | 374 |
| 353 destroy: function() { | 375 destroy: function() { |
| 354 this.dispatchEvent(schema.EventType.destroyed); | 376 this.dispatchEvent(schema.EventType.destroyed); |
| 355 this.invalidate_(this.wrapper); | 377 this.invalidate_(this.wrapper); |
| 378 idToWebArea_[automationUtil.createAutomationRootNodeID(this.processID, |
| 379 this.routingID)] = undefined; |
| 356 }, | 380 }, |
| 357 | 381 |
| 358 onAccessibilityEvent: function(eventParams) { | 382 onAccessibilityEvent: function(eventParams) { |
| 359 if (!this.unserialize(eventParams.update)) { | 383 if (!this.unserialize(eventParams.update)) { |
| 360 logging.WARNING('unserialization failed'); | 384 logging.WARNING('unserialization failed'); |
| 361 return false; | 385 return false; |
| 362 } | 386 } |
| 363 | 387 |
| 364 var targetNode = this.get(eventParams.targetID); | 388 var targetNode = this.get(eventParams.targetID); |
| 365 if (targetNode) { | 389 if (targetNode) { |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 399 | 423 |
| 400 // Retrieve the internal AutomationNodeImpl instance for this node. | 424 // Retrieve the internal AutomationNodeImpl instance for this node. |
| 401 // This object is not accessible outside of bindings code, but we can access | 425 // This object is not accessible outside of bindings code, but we can access |
| 402 // it here. | 426 // it here. |
| 403 var nodeImpl = privates(node).impl; | 427 var nodeImpl = privates(node).impl; |
| 404 var id = nodeImpl.id; | 428 var id = nodeImpl.id; |
| 405 for (var key in AutomationAttributeDefaults) { | 429 for (var key in AutomationAttributeDefaults) { |
| 406 nodeImpl[key] = AutomationAttributeDefaults[key]; | 430 nodeImpl[key] = AutomationAttributeDefaults[key]; |
| 407 } | 431 } |
| 408 nodeImpl.childIds = []; | 432 nodeImpl.childIds = []; |
| 409 nodeImpl.loaded = false; | |
| 410 nodeImpl.id = id; | 433 nodeImpl.id = id; |
| 411 delete this.axNodeDataCache_[id]; | 434 delete this.axNodeDataCache_[id]; |
| 412 }, | 435 }, |
| 413 | 436 |
| 414 load: function(callback) { | |
| 415 // TODO(dtseng/aboxhall): Implement. | |
| 416 if (!this.loaded) | |
| 417 throw 'Unsupported state: root node is not loaded.'; | |
| 418 | |
| 419 setTimeout(callback, 0); | |
| 420 }, | |
| 421 | |
| 422 deleteOldChildren_: function(node, newChildIds) { | 437 deleteOldChildren_: function(node, newChildIds) { |
| 423 // Create a set of child ids in |src| for fast lookup, and return false | 438 // Create a set of child ids in |src| for fast lookup, and return false |
| 424 // if a duplicate is found; | 439 // if a duplicate is found; |
| 425 var newChildIdSet = {}; | 440 var newChildIdSet = {}; |
| 426 for (var i = 0; i < newChildIds.length; i++) { | 441 for (var i = 0; i < newChildIds.length; i++) { |
| 427 var childId = newChildIds[i]; | 442 var childId = newChildIds[i]; |
| 428 if (newChildIdSet[childId]) { | 443 if (newChildIdSet[childId]) { |
| 429 logging.WARNING('Node ' + privates(node).impl.id + | 444 logging.WARNING('Node ' + privates(node).impl.id + |
| 430 ' has duplicate child id ' + childId); | 445 ' has duplicate child id ' + childId); |
| 431 lastError.set('automation', | 446 lastError.set('automation', |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 483 childNode = new AutomationNode(this); | 498 childNode = new AutomationNode(this); |
| 484 this.axNodeDataCache_[childId] = childNode; | 499 this.axNodeDataCache_[childId] = childNode; |
| 485 privates(childNode).impl.id = childId; | 500 privates(childNode).impl.id = childId; |
| 486 updateState.pendingNodes[childId] = childNode; | 501 updateState.pendingNodes[childId] = childNode; |
| 487 updateState.newNodes[childId] = childNode; | 502 updateState.newNodes[childId] = childNode; |
| 488 } | 503 } |
| 489 privates(childNode).impl.indexInParent = i; | 504 privates(childNode).impl.indexInParent = i; |
| 490 privates(childNode).impl.parentID = privates(node).impl.id; | 505 privates(childNode).impl.parentID = privates(node).impl.id; |
| 491 } | 506 } |
| 492 | 507 |
| 508 if (node.role == schema.RoleType.webArea && node.children().length == 0) { |
| 509 var pid = node.processID; |
| 510 var rid = node.attributes.routingID; |
| 511 var id = automationUtil.createAutomationRootNodeID(pid, rid); |
| 512 var targetTree = automationUtil.idToAutomationRootNode[id]; |
| 513 if (!targetTree) |
| 514 targetTree = new AutomationRootNode(pid, rid); |
| 515 automationUtil.idToAutomationRootNode[id] = targetTree; |
| 516 } |
| 517 |
| 493 return success; | 518 return success; |
| 494 }, | 519 }, |
| 495 | 520 |
| 496 setData_: function(node, nodeData) { | 521 setData_: function(node, nodeData) { |
| 497 var nodeImpl = privates(node).impl; | 522 var nodeImpl = privates(node).impl; |
| 523 |
| 524 if (nodeData.role == schema.RoleType.webArea) { |
| 525 nodeImpl.processID = nodeData.intAttributes.frameId; |
| 526 nodeImpl.routingID = nodeData.intAttributes.childFrameId; |
| 527 idToWebArea_[nodeImpl.processID + |
| 528 '_' + |
| 529 nodeImpl.routingID] = node; |
| 530 delete nodeData.intAttributes['processId']; |
| 531 delete nodeData.intAttributes['routingId']; |
| 532 } |
| 498 for (var key in AutomationAttributeDefaults) { | 533 for (var key in AutomationAttributeDefaults) { |
| 499 if (key in nodeData) | 534 if (key in nodeData) |
| 500 nodeImpl[key] = nodeData[key]; | 535 nodeImpl[key] = nodeData[key]; |
| 501 else | 536 else |
| 502 nodeImpl[key] = AutomationAttributeDefaults[key]; | 537 nodeImpl[key] = AutomationAttributeDefaults[key]; |
| 503 } | 538 } |
| 504 for (var i = 0; i < AutomationAttributeTypes.length; i++) { | 539 for (var i = 0; i < AutomationAttributeTypes.length; i++) { |
| 505 var attributeType = AutomationAttributeTypes[i]; | 540 var attributeType = AutomationAttributeTypes[i]; |
| 506 for (var attributeName in nodeData[attributeType]) { | 541 for (var attributeName in nodeData[attributeType]) { |
| 507 nodeImpl.attributesInternal[attributeName] = | 542 nodeImpl.attributesInternal[attributeName] = |
| (...skipping 26 matching lines...) Expand all Loading... |
| 534 return node.rootImpl.get(current); | 569 return node.rootImpl.get(current); |
| 535 }, this); | 570 }, this); |
| 536 } | 571 } |
| 537 return node.rootImpl.get(attributeId); | 572 return node.rootImpl.get(attributeId); |
| 538 } | 573 } |
| 539 return node.attributesInternal[attributeName]; | 574 return node.attributesInternal[attributeName]; |
| 540 }.bind(this), | 575 }.bind(this), |
| 541 }); | 576 }); |
| 542 }, | 577 }, |
| 543 | 578 |
| 579 load: function(callback) { |
| 580 if (!this.loaded) |
| 581 automationInternal.enableFrame(this.processID, this.routingID); |
| 582 |
| 583 automationUtil.storeTreeCallback(this.processID, this.routingID, |
| 584 function(root) { |
| 585 privates(root).impl.loaded = true; |
| 586 if (callback) |
| 587 callback(root); |
| 588 }); |
| 589 }, |
| 590 |
| 544 updateNode_: function(nodeData, updateState) { | 591 updateNode_: function(nodeData, updateState) { |
| 545 var node = this.axNodeDataCache_[nodeData.id]; | 592 var node = this.axNodeDataCache_[nodeData.id]; |
| 546 var didUpdateRoot = false; | 593 var didUpdateRoot = false; |
| 547 if (node) { | 594 if (node) { |
| 548 delete updateState.pendingNodes[privates(node).impl.id]; | 595 delete updateState.pendingNodes[privates(node).impl.id]; |
| 549 } else { | 596 } else { |
| 550 if (nodeData.role != schema.RoleType.rootWebArea && | 597 if (nodeData.role != schema.RoleType.rootWebArea && |
| 551 nodeData.role != schema.RoleType.desktop) { | 598 nodeData.role != schema.RoleType.desktop) { |
| 552 logging.WARNING(String(nodeData.id) + | 599 logging.WARNING(String(nodeData.id) + |
| 553 ' is not in the cache and not the new root.'); | 600 ' is not in the cache and not the new root.'); |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 592 'firstChild', | 639 'firstChild', |
| 593 'lastChild', | 640 'lastChild', |
| 594 'children', | 641 'children', |
| 595 'previousSibling', | 642 'previousSibling', |
| 596 'nextSibling', | 643 'nextSibling', |
| 597 'doDefault', | 644 'doDefault', |
| 598 'focus', | 645 'focus', |
| 599 'makeVisible', | 646 'makeVisible', |
| 600 'setSelection', | 647 'setSelection', |
| 601 'addEventListener', | 648 'addEventListener', |
| 602 'removeEventListener'], | 649 'removeEventListener', |
| 650 'load'], |
| 603 readonly: ['isRootNode', | 651 readonly: ['isRootNode', |
| 604 'role', | 652 'role', |
| 605 'state', | 653 'state', |
| 606 'location', | 654 'location', |
| 607 'attributes', | 655 'attributes', |
| 608 'root'] }); | 656 'root'] }); |
| 609 | 657 |
| 610 var AutomationRootNode = utils.expose('AutomationRootNode', | 658 var AutomationRootNode = utils.expose('AutomationRootNode', |
| 611 AutomationRootNodeImpl, | 659 AutomationRootNodeImpl, |
| 612 { superclass: AutomationNode, | 660 { superclass: AutomationNode, |
| 613 functions: ['load'], | 661 functions: ['load'], |
| 614 readonly: ['loaded'] }); | 662 readonly: ['loaded'] }); |
| 615 | 663 |
| 616 exports.AutomationNode = AutomationNode; | 664 exports.AutomationNode = AutomationNode; |
| 617 exports.AutomationRootNode = AutomationRootNode; | 665 exports.AutomationRootNode = AutomationRootNode; |
| OLD | NEW |