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

Side by Side Diff: chrome/renderer/resources/extensions/automation/automation_node.js

Issue 667713006: Implement automatic load of composed/embedded automation trees (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@lkcr
Patch Set: Get test to pass. Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 var AutomationEvent = require('automationEvent').AutomationEvent; 5 var AutomationEvent = require('automationEvent').AutomationEvent;
6 var automationInternal = 6 var automationInternal =
7 require('binding').Binding.create('automationInternal').generate(); 7 require('binding').Binding.create('automationInternal').generate();
8 var IsInteractPermitted = 8 var IsInteractPermitted =
9 requireNative('automationInternal').IsInteractPermitted; 9 requireNative('automationInternal').IsInteractPermitted;
10 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
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
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
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
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
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
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
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
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;
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698