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 = automationUtil.createAutomationRootNodeID(this.processID, | |
53 this.routingID); | |
54 | |
55 if (idToWebArea_[parentId]) | |
56 return idToWebArea_[parentId]; | |
57 } | |
43 return this.rootImpl.get(this.parentID); | 58 return this.rootImpl.get(this.parentID); |
44 }, | 59 }, |
45 | 60 |
46 firstChild: function() { | 61 firstChild: function() { |
47 var node = this.rootImpl.get(this.childIds[0]); | 62 return this.lookupWebAreaChild_() || this.rootImpl.get(this.childIds[0]); |
48 return node; | |
49 }, | 63 }, |
50 | 64 |
51 lastChild: function() { | 65 lastChild: function() { |
52 var childIds = this.childIds; | 66 var childIds = this.childIds; |
53 var node = this.rootImpl.get(childIds[childIds.length - 1]); | 67 return this.lookupWebAreaChild_() || |
54 return node; | 68 this.rootImpl.get(childIds[childIds.length - 1]); |
55 }, | 69 }, |
56 | 70 |
57 children: function() { | 71 children: function() { |
58 var children = []; | 72 var children = []; |
59 for (var i = 0, childID; childID = this.childIds[i]; i++) { | 73 for (var i = 0, childID; childID = this.childIds[i]; i++) { |
60 logging.CHECK(this.rootImpl.get(childID)); | 74 logging.CHECK(this.rootImpl.get(childID)); |
61 children.push(this.rootImpl.get(childID)); | 75 children.push(this.rootImpl.get(childID)); |
62 } | 76 } |
63 return children; | 77 return children; |
64 }, | 78 }, |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
111 listeners.splice(i, 1); | 125 listeners.splice(i, 1); |
112 } | 126 } |
113 } | 127 } |
114 }, | 128 }, |
115 | 129 |
116 dispatchEvent: function(eventType) { | 130 dispatchEvent: function(eventType) { |
117 var path = []; | 131 var path = []; |
118 var parent = this.parent(); | 132 var parent = this.parent(); |
119 while (parent) { | 133 while (parent) { |
120 path.push(parent); | 134 path.push(parent); |
121 // TODO(aboxhall/dtseng): handle unloaded parent node | |
122 parent = parent.parent(); | 135 parent = parent.parent(); |
123 } | 136 } |
124 var event = new AutomationEvent(eventType, this.wrapper); | 137 var event = new AutomationEvent(eventType, this.wrapper); |
125 | 138 |
126 // Dispatch the event through the propagation path in three phases: | 139 // Dispatch the event through the propagation path in three phases: |
127 // - capturing: starting from the root and going down to the target's parent | 140 // - capturing: starting from the root and going down to the target's parent |
128 // - targeting: dispatching the event on the target itself | 141 // - targeting: dispatching the event on the target itself |
129 // - bubbling: starting from the target's parent, going back up to the root. | 142 // - 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 | 143 // At any stage, a listener may call stopPropagation() on the event, which |
131 // will immediately stop event propagation through this path. | 144 // will immediately stop event propagation through this path. |
132 if (this.dispatchEventAtCapturing_(event, path)) { | 145 if (this.dispatchEventAtCapturing_(event, path)) { |
133 if (this.dispatchEventAtTargeting_(event, path)) | 146 if (this.dispatchEventAtTargeting_(event, path)) |
134 this.dispatchEventAtBubbling_(event, path); | 147 this.dispatchEventAtBubbling_(event, path); |
135 } | 148 } |
136 }, | 149 }, |
137 | 150 |
138 toString: function() { | 151 toString: function() { |
139 return 'node id=' + this.id + | 152 return 'node id=' + this.id + |
140 ' role=' + this.role + | 153 ' role=' + this.role + |
141 ' state=' + $JSON.stringify(this.state) + | 154 ' state=' + $JSON.stringify(this.state) + |
142 ' parentID=' + this.parentID + | 155 ' parentID=' + this.parentID + |
143 ' childIds=' + $JSON.stringify(this.childIds) + | 156 ' childIds=' + $JSON.stringify(this.childIds) + |
144 ' attributes=' + $JSON.stringify(this.attributes); | 157 ' attributes=' + $JSON.stringify(this.attributes); |
145 }, | 158 }, |
146 | 159 |
160 lookupWebAreaChild_: function() { | |
161 if (this.role != schema.RoleType.webArea) | |
162 return null; | |
163 | |
164 return automationUtil.idToAutomationRootNode[ | |
165 automationUtil.createAutomationRootNodeID( | |
166 this.processID, this.routingID)]; | |
167 }, | |
168 | |
147 dispatchEventAtCapturing_: function(event, path) { | 169 dispatchEventAtCapturing_: function(event, path) { |
148 privates(event).impl.eventPhase = Event.CAPTURING_PHASE; | 170 privates(event).impl.eventPhase = Event.CAPTURING_PHASE; |
149 for (var i = path.length - 1; i >= 0; i--) { | 171 for (var i = path.length - 1; i >= 0; i--) { |
150 this.fireEventListeners_(path[i], event); | 172 this.fireEventListeners_(path[i], event); |
151 if (privates(event).impl.propagationStopped) | 173 if (privates(event).impl.propagationStopped) |
152 return false; | 174 return false; |
153 } | 175 } |
154 return true; | 176 return true; |
155 }, | 177 }, |
156 | 178 |
(...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
285 this.processID = processID; | 307 this.processID = processID; |
286 this.routingID = routingID; | 308 this.routingID = routingID; |
287 this.axNodeDataCache_ = {}; | 309 this.axNodeDataCache_ = {}; |
288 } | 310 } |
289 | 311 |
290 AutomationRootNodeImpl.prototype = { | 312 AutomationRootNodeImpl.prototype = { |
291 __proto__: AutomationNodeImpl.prototype, | 313 __proto__: AutomationNodeImpl.prototype, |
292 | 314 |
293 isRootNode: true, | 315 isRootNode: true, |
294 | 316 |
317 loaded: false, | |
318 | |
319 role: 'rootWebArea', | |
320 | |
295 get: function(id) { | 321 get: function(id) { |
296 if (id == undefined) | 322 if (id == undefined) |
297 return undefined; | 323 return undefined; |
298 | 324 |
299 return this.axNodeDataCache_[id]; | 325 return this.axNodeDataCache_[id]; |
300 }, | 326 }, |
301 | 327 |
302 unserialize: function(update) { | 328 unserialize: function(update) { |
303 var updateState = { pendingNodes: {}, newNodes: {} }; | 329 var updateState = { pendingNodes: {}, newNodes: {} }; |
304 var oldRootId = this.id; | 330 var oldRootId = this.id; |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
346 null, | 372 null, |
347 chrome); | 373 chrome); |
348 return false; | 374 return false; |
349 } | 375 } |
350 return true; | 376 return true; |
351 }, | 377 }, |
352 | 378 |
353 destroy: function() { | 379 destroy: function() { |
354 this.dispatchEvent(schema.EventType.destroyed); | 380 this.dispatchEvent(schema.EventType.destroyed); |
355 this.invalidate_(this.wrapper); | 381 this.invalidate_(this.wrapper); |
382 idToWebArea_[automationUtil.createAutomationRootNodeID(this.processID, | |
383 this.routingID)] = undefined; | |
356 }, | 384 }, |
357 | 385 |
358 onAccessibilityEvent: function(eventParams) { | 386 onAccessibilityEvent: function(eventParams) { |
359 if (!this.unserialize(eventParams.update)) { | 387 if (!this.unserialize(eventParams.update)) { |
360 logging.WARNING('unserialization failed'); | 388 logging.WARNING('unserialization failed'); |
361 return false; | 389 return false; |
362 } | 390 } |
363 | 391 |
364 var targetNode = this.get(eventParams.targetID); | 392 var targetNode = this.get(eventParams.targetID); |
365 if (targetNode) { | 393 if (targetNode) { |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
399 | 427 |
400 // Retrieve the internal AutomationNodeImpl instance for this node. | 428 // Retrieve the internal AutomationNodeImpl instance for this node. |
401 // This object is not accessible outside of bindings code, but we can access | 429 // This object is not accessible outside of bindings code, but we can access |
402 // it here. | 430 // it here. |
403 var nodeImpl = privates(node).impl; | 431 var nodeImpl = privates(node).impl; |
404 var id = nodeImpl.id; | 432 var id = nodeImpl.id; |
405 for (var key in AutomationAttributeDefaults) { | 433 for (var key in AutomationAttributeDefaults) { |
406 nodeImpl[key] = AutomationAttributeDefaults[key]; | 434 nodeImpl[key] = AutomationAttributeDefaults[key]; |
407 } | 435 } |
408 nodeImpl.childIds = []; | 436 nodeImpl.childIds = []; |
409 nodeImpl.loaded = false; | |
410 nodeImpl.id = id; | 437 nodeImpl.id = id; |
411 delete this.axNodeDataCache_[id]; | 438 delete this.axNodeDataCache_[id]; |
412 }, | 439 }, |
413 | 440 |
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) { | 441 deleteOldChildren_: function(node, newChildIds) { |
423 // Create a set of child ids in |src| for fast lookup, and return false | 442 // Create a set of child ids in |src| for fast lookup, and return false |
424 // if a duplicate is found; | 443 // if a duplicate is found; |
425 var newChildIdSet = {}; | 444 var newChildIdSet = {}; |
426 for (var i = 0; i < newChildIds.length; i++) { | 445 for (var i = 0; i < newChildIds.length; i++) { |
427 var childId = newChildIds[i]; | 446 var childId = newChildIds[i]; |
428 if (newChildIdSet[childId]) { | 447 if (newChildIdSet[childId]) { |
429 logging.WARNING('Node ' + privates(node).impl.id + | 448 logging.WARNING('Node ' + privates(node).impl.id + |
430 ' has duplicate child id ' + childId); | 449 ' has duplicate child id ' + childId); |
431 lastError.set('automation', | 450 lastError.set('automation', |
(...skipping 18 matching lines...) Expand all Loading... | |
450 } | 469 } |
451 } | 470 } |
452 nodeImpl.childIds = oldChildIds; | 471 nodeImpl.childIds = oldChildIds; |
453 | 472 |
454 return true; | 473 return true; |
455 }, | 474 }, |
456 | 475 |
457 createNewChildren_: function(node, newChildIds, updateState) { | 476 createNewChildren_: function(node, newChildIds, updateState) { |
458 logging.CHECK(node); | 477 logging.CHECK(node); |
459 var success = true; | 478 var success = true; |
479 | |
480 if (node.role == schema.RoleType.webArea && node.children().length == 0) { | |
481 var pid = privates(node).impl.processID; | |
dmazzoni
2014/10/31 18:28:35
I think it'd be more clear if this was childProces
| |
482 var rid = privates(node).impl.routingID; | |
483 var id = automationUtil.createAutomationRootNodeID(pid, rid); | |
484 var targetTree = automationUtil.idToAutomationRootNode[id]; | |
dmazzoni
2014/10/31 18:28:35
targetTree -> childTree
| |
485 if (!targetTree) | |
486 targetTree = new AutomationRootNode(pid, rid); | |
487 automationUtil.idToAutomationRootNode[id] = targetTree; | |
488 } | |
460 for (var i = 0; i < newChildIds.length; i++) { | 489 for (var i = 0; i < newChildIds.length; i++) { |
461 var childId = newChildIds[i]; | 490 var childId = newChildIds[i]; |
462 var childNode = this.axNodeDataCache_[childId]; | 491 var childNode = this.axNodeDataCache_[childId]; |
463 if (childNode) { | 492 if (childNode) { |
464 if (childNode.parent() != node) { | 493 if (childNode.parent() != node) { |
465 var parentId = -1; | 494 var parentId = -1; |
466 if (childNode.parent()) { | 495 if (childNode.parent()) { |
467 var parentImpl = privates(childNode.parent()).impl; | 496 var parentImpl = privates(childNode.parent()).impl; |
468 parentId = parentImpl.id; | 497 parentId = parentImpl.id; |
469 } | 498 } |
(...skipping 18 matching lines...) Expand all Loading... | |
488 } | 517 } |
489 privates(childNode).impl.indexInParent = i; | 518 privates(childNode).impl.indexInParent = i; |
490 privates(childNode).impl.parentID = privates(node).impl.id; | 519 privates(childNode).impl.parentID = privates(node).impl.id; |
491 } | 520 } |
492 | 521 |
493 return success; | 522 return success; |
494 }, | 523 }, |
495 | 524 |
496 setData_: function(node, nodeData) { | 525 setData_: function(node, nodeData) { |
497 var nodeImpl = privates(node).impl; | 526 var nodeImpl = privates(node).impl; |
527 | |
528 if (nodeData.role == schema.RoleType.webArea) { | |
529 nodeImpl.processID = nodeData.intAttributes.frameId; | |
530 nodeImpl.routingID = nodeData.intAttributes.childFrameId; | |
531 idToWebArea_[automationUtil.createAutomationRootNodeID(nodeImpl.processID, | |
532 nodeImpl.routingID)] = node; | |
533 delete nodeData.intAttributes['processId']; | |
dmazzoni
2014/10/31 18:28:35
this doesn't match, should be nodeData.intAttribut
| |
534 delete nodeData.intAttributes['routingId']; | |
535 } | |
498 for (var key in AutomationAttributeDefaults) { | 536 for (var key in AutomationAttributeDefaults) { |
499 if (key in nodeData) | 537 if (key in nodeData) |
500 nodeImpl[key] = nodeData[key]; | 538 nodeImpl[key] = nodeData[key]; |
501 else | 539 else |
502 nodeImpl[key] = AutomationAttributeDefaults[key]; | 540 nodeImpl[key] = AutomationAttributeDefaults[key]; |
503 } | 541 } |
504 for (var i = 0; i < AutomationAttributeTypes.length; i++) { | 542 for (var i = 0; i < AutomationAttributeTypes.length; i++) { |
505 var attributeType = AutomationAttributeTypes[i]; | 543 var attributeType = AutomationAttributeTypes[i]; |
506 for (var attributeName in nodeData[attributeType]) { | 544 for (var attributeName in nodeData[attributeType]) { |
507 nodeImpl.attributesInternal[attributeName] = | 545 nodeImpl.attributesInternal[attributeName] = |
(...skipping 26 matching lines...) Expand all Loading... | |
534 return node.rootImpl.get(current); | 572 return node.rootImpl.get(current); |
535 }, this); | 573 }, this); |
536 } | 574 } |
537 return node.rootImpl.get(attributeId); | 575 return node.rootImpl.get(attributeId); |
538 } | 576 } |
539 return node.attributesInternal[attributeName]; | 577 return node.attributesInternal[attributeName]; |
540 }.bind(this), | 578 }.bind(this), |
541 }); | 579 }); |
542 }, | 580 }, |
543 | 581 |
582 load: function(callback) { | |
583 if (!this.loaded) | |
584 automationInternal.enableFrame(this.processID, this.routingID); | |
585 | |
586 automationUtil.storeTreeCallback(this.processID, | |
587 this.routingID, | |
588 function(root) { | |
589 privates(root).impl.loaded = true; | |
590 if (callback) | |
591 callback(root); | |
592 }); | |
593 }, | |
594 | |
544 updateNode_: function(nodeData, updateState) { | 595 updateNode_: function(nodeData, updateState) { |
545 var node = this.axNodeDataCache_[nodeData.id]; | 596 var node = this.axNodeDataCache_[nodeData.id]; |
546 var didUpdateRoot = false; | 597 var didUpdateRoot = false; |
547 if (node) { | 598 if (node) { |
548 delete updateState.pendingNodes[privates(node).impl.id]; | 599 delete updateState.pendingNodes[privates(node).impl.id]; |
549 } else { | 600 } else { |
550 if (nodeData.role != schema.RoleType.rootWebArea && | 601 if (nodeData.role != schema.RoleType.rootWebArea && |
551 nodeData.role != schema.RoleType.desktop) { | 602 nodeData.role != schema.RoleType.desktop) { |
552 logging.WARNING(String(nodeData.id) + | 603 logging.WARNING(String(nodeData.id) + |
553 ' is not in the cache and not the new root.'); | 604 ' 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', | 643 'firstChild', |
593 'lastChild', | 644 'lastChild', |
594 'children', | 645 'children', |
595 'previousSibling', | 646 'previousSibling', |
596 'nextSibling', | 647 'nextSibling', |
597 'doDefault', | 648 'doDefault', |
598 'focus', | 649 'focus', |
599 'makeVisible', | 650 'makeVisible', |
600 'setSelection', | 651 'setSelection', |
601 'addEventListener', | 652 'addEventListener', |
602 'removeEventListener'], | 653 'removeEventListener', |
654 'load'], | |
603 readonly: ['isRootNode', | 655 readonly: ['isRootNode', |
604 'role', | 656 'role', |
605 'state', | 657 'state', |
606 'location', | 658 'location', |
607 'attributes', | 659 'attributes', |
608 'root'] }); | 660 'root'] }); |
609 | 661 |
610 var AutomationRootNode = utils.expose('AutomationRootNode', | 662 var AutomationRootNode = utils.expose('AutomationRootNode', |
611 AutomationRootNodeImpl, | 663 AutomationRootNodeImpl, |
612 { superclass: AutomationNode, | 664 { superclass: AutomationNode, |
613 functions: ['load'], | 665 functions: ['load'], |
614 readonly: ['loaded'] }); | 666 readonly: ['loaded'] }); |
615 | 667 |
616 exports.AutomationNode = AutomationNode; | 668 exports.AutomationNode = AutomationNode; |
617 exports.AutomationRootNode = AutomationRootNode; | 669 exports.AutomationRootNode = AutomationRootNode; |
OLD | NEW |