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

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: Fix UAF 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
(...skipping 22 matching lines...) Expand all
33 id: -1, 33 id: -1,
34 role: '', 34 role: '',
35 state: { busy: true }, 35 state: { busy: true },
36 isRootNode: false, 36 isRootNode: false,
37 37
38 get root() { 38 get root() {
39 return this.rootImpl.wrapper; 39 return this.rootImpl.wrapper;
40 }, 40 },
41 41
42 parent: function() { 42 parent: function() {
43 return this.rootImpl.get(this.parentID); 43 return this.hostTree || this.rootImpl.get(this.parentID);
44 }, 44 },
45 45
46 firstChild: function() { 46 firstChild: function() {
47 var node = this.rootImpl.get(this.childIds[0]); 47 return this.childTree || this.rootImpl.get(this.childIds[0]);
48 return node;
49 }, 48 },
50 49
51 lastChild: function() { 50 lastChild: function() {
52 var childIds = this.childIds; 51 var childIds = this.childIds;
53 var node = this.rootImpl.get(childIds[childIds.length - 1]); 52 return this.childTree || this.rootImpl.get(childIds[childIds.length - 1]);
54 return node;
55 }, 53 },
56 54
57 children: function() { 55 children: function() {
58 var children = []; 56 var children = [];
59 for (var i = 0, childID; childID = this.childIds[i]; i++) { 57 for (var i = 0, childID; childID = this.childIds[i]; i++) {
60 logging.CHECK(this.rootImpl.get(childID)); 58 logging.CHECK(this.rootImpl.get(childID));
61 children.push(this.rootImpl.get(childID)); 59 children.push(this.rootImpl.get(childID));
62 } 60 }
63 return children; 61 return children;
64 }, 62 },
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
106 removeEventListener: function(eventType, callback) { 104 removeEventListener: function(eventType, callback) {
107 if (this.listeners[eventType]) { 105 if (this.listeners[eventType]) {
108 var listeners = this.listeners[eventType]; 106 var listeners = this.listeners[eventType];
109 for (var i = 0; i < listeners.length; i++) { 107 for (var i = 0; i < listeners.length; i++) {
110 if (callback === listeners[i].callback) 108 if (callback === listeners[i].callback)
111 listeners.splice(i, 1); 109 listeners.splice(i, 1);
112 } 110 }
113 } 111 }
114 }, 112 },
115 113
114 toJSON: function() {
115 return { treeID: this.treeID,
116 id: this.id,
117 role: this.role,
118 attributes: this.attributes };
119 },
120
116 dispatchEvent: function(eventType) { 121 dispatchEvent: function(eventType) {
117 var path = []; 122 var path = [];
118 var parent = this.parent(); 123 var parent = this.parent();
119 while (parent) { 124 while (parent) {
120 path.push(parent); 125 path.push(parent);
121 // TODO(aboxhall/dtseng): handle unloaded parent node
122 parent = parent.parent(); 126 parent = parent.parent();
123 } 127 }
124 var event = new AutomationEvent(eventType, this.wrapper); 128 var event = new AutomationEvent(eventType, this.wrapper);
125 129
126 // Dispatch the event through the propagation path in three phases: 130 // Dispatch the event through the propagation path in three phases:
127 // - capturing: starting from the root and going down to the target's parent 131 // - capturing: starting from the root and going down to the target's parent
128 // - targeting: dispatching the event on the target itself 132 // - targeting: dispatching the event on the target itself
129 // - bubbling: starting from the target's parent, going back up to the root. 133 // - 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 134 // At any stage, a listener may call stopPropagation() on the event, which
131 // will immediately stop event propagation through this path. 135 // will immediately stop event propagation through this path.
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
187 } catch (e) { 191 } catch (e) {
188 console.error('Error in event handler for ' + event.type + 192 console.error('Error in event handler for ' + event.type +
189 'during phase ' + eventPhase + ': ' + 193 'during phase ' + eventPhase + ': ' +
190 e.message + '\nStack trace: ' + e.stack); 194 e.message + '\nStack trace: ' + e.stack);
191 } 195 }
192 } 196 }
193 }, 197 },
194 198
195 performAction_: function(actionType, opt_args) { 199 performAction_: function(actionType, opt_args) {
196 // Not yet initialized. 200 // Not yet initialized.
197 if (this.rootImpl.processID === undefined || 201 if (this.rootImpl.treeID === undefined ||
198 this.rootImpl.routingID === undefined ||
199 this.id === undefined) { 202 this.id === undefined) {
200 return; 203 return;
201 } 204 }
202 205
203 // Check permissions. 206 // Check permissions.
204 if (!IsInteractPermitted()) { 207 if (!IsInteractPermitted()) {
205 throw new Error(actionType + ' requires {"desktop": true} or' + 208 throw new Error(actionType + ' requires {"desktop": true} or' +
206 ' {"interact": true} in the "automation" manifest key.'); 209 ' {"interact": true} in the "automation" manifest key.');
207 } 210 }
208 211
209 automationInternal.performAction({ processID: this.rootImpl.processID, 212 automationInternal.performAction({ treeID: this.rootImpl.treeID,
210 routingID: this.rootImpl.routingID,
211 automationNodeID: this.id, 213 automationNodeID: this.id,
212 actionType: actionType }, 214 actionType: actionType },
213 opt_args || {}); 215 opt_args || {});
214 } 216 }
215 }; 217 };
216 218
217 // Maps an attribute to its default value in an invalidated node. 219 // Maps an attribute to its default value in an invalidated node.
218 // These attributes are taken directly from the Automation idl. 220 // These attributes are taken directly from the Automation idl.
219 var AutomationAttributeDefaults = { 221 var AutomationAttributeDefaults = {
220 'id': -1, 222 'id': -1,
(...skipping 27 matching lines...) Expand all
248 'aria-labelledby': 'labelledbyIds', 250 'aria-labelledby': 'labelledbyIds',
249 'aria-owns': 'ownsIds' 251 'aria-owns': 'ownsIds'
250 }; 252 };
251 253
252 /** 254 /**
253 * A set of attributes ignored in the automation API. 255 * A set of attributes ignored in the automation API.
254 * @param {!Object.<string, boolean>} 256 * @param {!Object.<string, boolean>}
255 * @const 257 * @const
256 */ 258 */
257 var ATTRIBUTE_BLACKLIST = {'activedescendantId': true, 259 var ATTRIBUTE_BLACKLIST = {'activedescendantId': true,
260 'childTreeId': true,
258 'controlsIds': true, 261 'controlsIds': true,
259 'describedbyIds': true, 262 'describedbyIds': true,
260 'flowtoIds': true, 263 'flowtoIds': true,
261 'labelledbyIds': true, 264 'labelledbyIds': true,
262 'ownsIds': true 265 'ownsIds': true
263 }; 266 };
264 267
265 268
266 /** 269 /**
267 * AutomationRootNode. 270 * AutomationRootNode.
268 * 271 *
269 * An AutomationRootNode is the javascript end of an AXTree living in the 272 * An AutomationRootNode is the javascript end of an AXTree living in the
270 * browser. AutomationRootNode handles unserializing incremental updates from 273 * browser. AutomationRootNode handles unserializing incremental updates from
271 * the source AXTree. Each update contains node data that form a complete tree 274 * the source AXTree. Each update contains node data that form a complete tree
272 * after applying the update. 275 * after applying the update.
273 * 276 *
274 * A brief note about ids used through this class. The source AXTree assigns 277 * A brief note about ids used through this class. The source AXTree assigns
275 * unique ids per node and we use these ids to build a hash to the actual 278 * unique ids per node and we use these ids to build a hash to the actual
276 * AutomationNode object. 279 * AutomationNode object.
277 * Thus, tree traversals amount to a lookup in our hash. 280 * Thus, tree traversals amount to a lookup in our hash.
278 * 281 *
279 * The tree itself is identified by the process id and routing id of the 282 * The tree itself is identified by the accessibility tree id of the
280 * renderer widget host. 283 * renderer widget host.
281 * @constructor 284 * @constructor
282 */ 285 */
283 function AutomationRootNodeImpl(processID, routingID) { 286 function AutomationRootNodeImpl(treeID) {
284 AutomationNodeImpl.call(this, this); 287 AutomationNodeImpl.call(this, this);
285 this.processID = processID; 288 this.treeID = treeID;
286 this.routingID = routingID;
287 this.axNodeDataCache_ = {}; 289 this.axNodeDataCache_ = {};
288 } 290 }
289 291
290 AutomationRootNodeImpl.prototype = { 292 AutomationRootNodeImpl.prototype = {
291 __proto__: AutomationNodeImpl.prototype, 293 __proto__: AutomationNodeImpl.prototype,
292 294
293 isRootNode: true, 295 isRootNode: true,
294 296
295 get: function(id) { 297 get: function(id) {
296 if (id == undefined) 298 if (id == undefined)
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
344 lastError.set('automation', 346 lastError.set('automation',
345 'Bad update received on automation tree', 347 'Bad update received on automation tree',
346 null, 348 null,
347 chrome); 349 chrome);
348 return false; 350 return false;
349 } 351 }
350 return true; 352 return true;
351 }, 353 },
352 354
353 destroy: function() { 355 destroy: function() {
356 if (this.hostTree)
357 this.hostTree.childTree = undefined;
358 this.hostTree = undefined;
359
354 this.dispatchEvent(schema.EventType.destroyed); 360 this.dispatchEvent(schema.EventType.destroyed);
355 this.invalidate_(this.wrapper); 361 this.invalidate_(this.wrapper);
356 }, 362 },
357 363
358 onAccessibilityEvent: function(eventParams) { 364 onAccessibilityEvent: function(eventParams) {
359 if (!this.unserialize(eventParams.update)) { 365 if (!this.unserialize(eventParams.update)) {
360 logging.WARNING('unserialization failed'); 366 logging.WARNING('unserialization failed');
361 return false; 367 return false;
362 } 368 }
363 369
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
399 405
400 // Retrieve the internal AutomationNodeImpl instance for this node. 406 // Retrieve the internal AutomationNodeImpl instance for this node.
401 // This object is not accessible outside of bindings code, but we can access 407 // This object is not accessible outside of bindings code, but we can access
402 // it here. 408 // it here.
403 var nodeImpl = privates(node).impl; 409 var nodeImpl = privates(node).impl;
404 var id = nodeImpl.id; 410 var id = nodeImpl.id;
405 for (var key in AutomationAttributeDefaults) { 411 for (var key in AutomationAttributeDefaults) {
406 nodeImpl[key] = AutomationAttributeDefaults[key]; 412 nodeImpl[key] = AutomationAttributeDefaults[key];
407 } 413 }
408 nodeImpl.childIds = []; 414 nodeImpl.childIds = [];
409 nodeImpl.loaded = false;
410 nodeImpl.id = id; 415 nodeImpl.id = id;
411 delete this.axNodeDataCache_[id]; 416 delete this.axNodeDataCache_[id];
412 }, 417 },
413 418
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) { 419 deleteOldChildren_: function(node, newChildIds) {
423 // Create a set of child ids in |src| for fast lookup, and return false 420 // Create a set of child ids in |src| for fast lookup, and return false
424 // if a duplicate is found; 421 // if a duplicate is found;
425 var newChildIdSet = {}; 422 var newChildIdSet = {};
426 for (var i = 0; i < newChildIds.length; i++) { 423 for (var i = 0; i < newChildIds.length; i++) {
427 var childId = newChildIds[i]; 424 var childId = newChildIds[i];
428 if (newChildIdSet[childId]) { 425 if (newChildIdSet[childId]) {
429 logging.WARNING('Node ' + privates(node).impl.id + 426 logging.WARNING('Node ' + privates(node).impl.id +
430 ' has duplicate child id ' + childId); 427 ' has duplicate child id ' + childId);
431 lastError.set('automation', 428 lastError.set('automation',
(...skipping 18 matching lines...) Expand all
450 } 447 }
451 } 448 }
452 nodeImpl.childIds = oldChildIds; 449 nodeImpl.childIds = oldChildIds;
453 450
454 return true; 451 return true;
455 }, 452 },
456 453
457 createNewChildren_: function(node, newChildIds, updateState) { 454 createNewChildren_: function(node, newChildIds, updateState) {
458 logging.CHECK(node); 455 logging.CHECK(node);
459 var success = true; 456 var success = true;
457
460 for (var i = 0; i < newChildIds.length; i++) { 458 for (var i = 0; i < newChildIds.length; i++) {
461 var childId = newChildIds[i]; 459 var childId = newChildIds[i];
462 var childNode = this.axNodeDataCache_[childId]; 460 var childNode = this.axNodeDataCache_[childId];
463 if (childNode) { 461 if (childNode) {
464 if (childNode.parent() != node) { 462 if (childNode.parent() != node) {
465 var parentId = -1; 463 var parentId = -1;
466 if (childNode.parent()) { 464 if (childNode.parent()) {
467 var parentImpl = privates(childNode.parent()).impl; 465 var parentImpl = privates(childNode.parent()).impl;
468 parentId = parentImpl.id; 466 parentId = parentImpl.id;
469 } 467 }
(...skipping 18 matching lines...) Expand all
488 } 486 }
489 privates(childNode).impl.indexInParent = i; 487 privates(childNode).impl.indexInParent = i;
490 privates(childNode).impl.parentID = privates(node).impl.id; 488 privates(childNode).impl.parentID = privates(node).impl.id;
491 } 489 }
492 490
493 return success; 491 return success;
494 }, 492 },
495 493
496 setData_: function(node, nodeData) { 494 setData_: function(node, nodeData) {
497 var nodeImpl = privates(node).impl; 495 var nodeImpl = privates(node).impl;
496
497 // TODO(dtseng): Make into set listing all hosting node roles.
498 if (nodeData.role == schema.RoleType.webView) {
499 if (nodeImpl.pendingChildFrame === undefined)
500 nodeImpl.pendingChildFrame = true;
501
502 if (nodeImpl.pendingChildFrame) {
503 nodeImpl.childTreeID = nodeData.intAttributes.childTreeId;
504 automationInternal.enableFrame(nodeImpl.childTreeID);
505 automationUtil.storeTreeCallback(nodeImpl.childTreeID, function(root) {
506 nodeImpl.pendingChildFrame = false;
507 nodeImpl.childTree = root;
508 privates(root).impl.hostTree = node;
509 nodeImpl.dispatchEvent(schema.EventType.childrenChanged);
510 });
511 }
512 }
498 for (var key in AutomationAttributeDefaults) { 513 for (var key in AutomationAttributeDefaults) {
499 if (key in nodeData) 514 if (key in nodeData)
500 nodeImpl[key] = nodeData[key]; 515 nodeImpl[key] = nodeData[key];
501 else 516 else
502 nodeImpl[key] = AutomationAttributeDefaults[key]; 517 nodeImpl[key] = AutomationAttributeDefaults[key];
503 } 518 }
504 for (var i = 0; i < AutomationAttributeTypes.length; i++) { 519 for (var i = 0; i < AutomationAttributeTypes.length; i++) {
505 var attributeType = AutomationAttributeTypes[i]; 520 var attributeType = AutomationAttributeTypes[i];
506 for (var attributeName in nodeData[attributeType]) { 521 for (var attributeName in nodeData[attributeType]) {
507 nodeImpl.attributesInternal[attributeName] = 522 nodeImpl.attributesInternal[attributeName] =
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after
592 'firstChild', 607 'firstChild',
593 'lastChild', 608 'lastChild',
594 'children', 609 'children',
595 'previousSibling', 610 'previousSibling',
596 'nextSibling', 611 'nextSibling',
597 'doDefault', 612 'doDefault',
598 'focus', 613 'focus',
599 'makeVisible', 614 'makeVisible',
600 'setSelection', 615 'setSelection',
601 'addEventListener', 616 'addEventListener',
602 'removeEventListener'], 617 'removeEventListener',
618 'toJSON'],
603 readonly: ['isRootNode', 619 readonly: ['isRootNode',
604 'role', 620 'role',
605 'state', 621 'state',
606 'location', 622 'location',
607 'attributes', 623 'attributes',
624 'indexInParent',
608 'root'] }); 625 'root'] });
609 626
610 var AutomationRootNode = utils.expose('AutomationRootNode', 627 var AutomationRootNode = utils.expose('AutomationRootNode',
611 AutomationRootNodeImpl, 628 AutomationRootNodeImpl,
612 { superclass: AutomationNode, 629 { superclass: AutomationNode });
613 functions: ['load'],
614 readonly: ['loaded'] });
615 630
616 exports.AutomationNode = AutomationNode; 631 exports.AutomationNode = AutomationNode;
617 exports.AutomationRootNode = AutomationRootNode; 632 exports.AutomationRootNode = AutomationRootNode;
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698