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

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: 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 = 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
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
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
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
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
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
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
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;
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698