diff options
author | aboxhall@chromium.org <aboxhall@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-06-10 18:37:14 +0000 |
---|---|---|
committer | aboxhall@chromium.org <aboxhall@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-06-10 18:37:14 +0000 |
commit | ed51006db1465dc8542bb6508514fd89414f3f3b (patch) | |
tree | 6972994063c5793502e7a96882360d4dda516c87 /chrome/renderer/resources/extensions/automation/automation_node.js | |
parent | 09f60dd57009ea1ac711bf6592e9d90fe66a9e78 (diff) | |
download | chromium_src-ed51006db1465dc8542bb6508514fd89414f3f3b.zip chromium_src-ed51006db1465dc8542bb6508514fd89414f3f3b.tar.gz chromium_src-ed51006db1465dc8542bb6508514fd89414f3f3b.tar.bz2 |
This replaces AutomationTree with AutomationRootNode, which is a regular node which happens to be the root of the tree as well, and contains the lookup table for all other nodes etc.
BUG=309681
TBR=jam@chromium.org
NOTRY=true
Review URL: https://codereview.chromium.org/311913002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@276096 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/renderer/resources/extensions/automation/automation_node.js')
-rw-r--r-- | chrome/renderer/resources/extensions/automation/automation_node.js | 212 |
1 files changed, 193 insertions, 19 deletions
diff --git a/chrome/renderer/resources/extensions/automation/automation_node.js b/chrome/renderer/resources/extensions/automation/automation_node.js index 944afc8..838de6e 100644 --- a/chrome/renderer/resources/extensions/automation/automation_node.js +++ b/chrome/renderer/resources/extensions/automation/automation_node.js @@ -11,42 +11,46 @@ var IsInteractPermitted = /** * A single node in the Automation tree. - * @param {AutomationTree} owner The owning tree. + * @param {AutomationRootNodeImpl} root The root of the tree. * @constructor */ -var AutomationNodeImpl = function(owner) { - this.owner = owner; +function AutomationNodeImpl(root) { + this.rootImpl = root; this.childIds = []; this.attributes = {}; this.listeners = {}; this.location = { left: 0, top: 0, width: 0, height: 0 }; -}; +} AutomationNodeImpl.prototype = { id: -1, role: '', - loaded: false, state: { busy: true }, + isRootNode: false, + + get root() { + return this.rootImpl.wrapper; + }, parent: function() { - return this.owner.get(this.parentID); + return this.rootImpl.get(this.parentID); }, firstChild: function() { - var node = this.owner.get(this.childIds[0]); + var node = this.rootImpl.get(this.childIds[0]); return node; }, lastChild: function() { var childIds = this.childIds; - var node = this.owner.get(childIds[childIds.length - 1]); + var node = this.rootImpl.get(childIds[childIds.length - 1]); return node; }, children: function() { var children = []; for (var i = 0, childID; childID = this.childIds[i]; i++) - children.push(this.owner.get(childID)); + children.push(this.rootImpl.get(childID)); return children; }, @@ -86,7 +90,7 @@ AutomationNodeImpl.prototype = { this.removeEventListener(eventType, callback); if (!this.listeners[eventType]) this.listeners[eventType] = []; - this.listeners[eventType].push({callback: callback, capture: capture}); + this.listeners[eventType].push({callback: callback, capture: !!capture}); }, // TODO(dtseng/aboxhall): Check this impl against spec. @@ -108,7 +112,6 @@ AutomationNodeImpl.prototype = { // TODO(aboxhall/dtseng): handle unloaded parent node parent = parent.parent(); } - path.push(this.owner.wrapper); var event = new AutomationEvent(eventType, this.wrapper); // Dispatch the event through the propagation path in three phases: @@ -162,7 +165,6 @@ AutomationNodeImpl.prototype = { var listeners = nodeImpl.listeners[event.type]; if (!listeners) return; - var eventPhase = event.eventPhase; for (var i = 0; i < listeners.length; i++) { if (eventPhase == Event.CAPTURING_PHASE && !listeners[i].capture) @@ -182,8 +184,8 @@ AutomationNodeImpl.prototype = { performAction_: function(actionType, opt_args) { // Not yet initialized. - if (this.owner.processID === undefined || - this.owner.routingID === undefined || + if (this.rootImpl.processID === undefined || + this.rootImpl.routingID === undefined || this.wrapper.id === undefined) { return; } @@ -194,14 +196,179 @@ AutomationNodeImpl.prototype = { ' {"interact": true} in the "automation" manifest key.'); } - automationInternal.performAction({ processID: this.owner.processID, - routingID: this.owner.routingID, + automationInternal.performAction({ processID: this.rootImpl.processID, + routingID: this.rootImpl.routingID, automationNodeID: this.wrapper.id, actionType: actionType }, opt_args || {}); } }; +// Maps an attribute to its default value in an invalidated node. +// These attributes are taken directly from the Automation idl. +var AutomationAttributeDefaults = { + 'id': -1, + 'role': '', + 'state': {}, + 'location': { left: 0, top: 0, width: 0, height: 0 } +}; + + +var AutomationAttributeTypes = [ + 'boolAttributes', + 'floatAttributes', + 'htmlAttributes', + 'intAttributes', + 'intlistAttributes', + 'stringAttributes' +]; + + +/** + * AutomationRootNode. + * + * An AutomationRootNode is the javascript end of an AXTree living in the + * browser. AutomationRootNode handles unserializing incremental updates from + * the source AXTree. Each update contains node data that form a complete tree + * after applying the update. + * + * A brief note about ids used through this class. The source AXTree assigns + * unique ids per node and we use these ids to build a hash to the actual + * AutomationNode object. + * Thus, tree traversals amount to a lookup in our hash. + * + * The tree itself is identified by the process id and routing id of the + * renderer widget host. + * @constructor + */ +function AutomationRootNodeImpl(processID, routingID) { + AutomationNodeImpl.call(this, this); + this.processID = processID; + this.routingID = routingID; + this.axNodeDataCache_ = {}; +} + +AutomationRootNodeImpl.prototype = { + __proto__: AutomationNodeImpl.prototype, + + isRootNode: true, + + get: function(id) { + return this.axNodeDataCache_[id]; + }, + + invalidate: function(node) { + if (!node) + return; + + var children = node.children(); + + for (var i = 0, child; child = children[i]; i++) + this.invalidate(child); + + // Retrieve the internal AutomationNodeImpl instance for this node. + // This object is not accessible outside of bindings code, but we can access + // it here. + var nodeImpl = privates(node).impl; + var id = node.id; + for (var key in AutomationAttributeDefaults) { + nodeImpl[key] = AutomationAttributeDefaults[key]; + } + nodeImpl.loaded = false; + nodeImpl.id = id; + this.axNodeDataCache_[id] = undefined; + }, + + update: function(data) { + var didUpdateRoot = false; + + if (data.nodes.length == 0) + return false; + + for (var i = 0; i < data.nodes.length; i++) { + var nodeData = data.nodes[i]; + var node = this.axNodeDataCache_[nodeData.id]; + if (!node) { + if (nodeData.role == 'rootWebArea' || nodeData.role == 'desktop') { + // |this| is an AutomationRootNodeImpl; retrieve the + // AutomationRootNode instance instead. + node = this.wrapper; + didUpdateRoot = true; + } else { + node = new AutomationNode(this); + } + } + var nodeImpl = privates(node).impl; + + // Update children. + var oldChildIDs = nodeImpl.childIds; + var newChildIDs = nodeData.childIds || []; + var newChildIDsHash = {}; + + for (var j = 0, newId; newId = newChildIDs[j]; j++) { + // Hash the new child ids for faster lookup. + newChildIDsHash[newId] = newId; + + // We need to update all new children's parent id regardless. + var childNode = this.get(newId); + if (!childNode) { + childNode = new AutomationNode(this); + this.axNodeDataCache_[newId] = childNode; + privates(childNode).impl.id = newId; + } + privates(childNode).impl.indexInParent = j; + privates(childNode).impl.parentID = nodeData.id; + } + + for (var k = 0, oldId; oldId = oldChildIDs[k]; k++) { + // However, we must invalidate all old child ids that are no longer + // children. + if (!newChildIDsHash[oldId]) { + this.invalidate(this.get(oldId)); + } + } + + for (var key in AutomationAttributeDefaults) { + if (key in nodeData) + nodeImpl[key] = nodeData[key]; + else + nodeImpl[key] = AutomationAttributeDefaults[key]; + } + for (var attributeTypeIndex = 0; + attributeTypeIndex < AutomationAttributeTypes.length; + attributeTypeIndex++) { + var attributeType = AutomationAttributeTypes[attributeTypeIndex]; + for (var attributeID in nodeData[attributeType]) { + nodeImpl.attributes[attributeID] = + nodeData[attributeType][attributeID]; + } + } + nodeImpl.childIds = newChildIDs; + nodeImpl.loaded = true; + this.axNodeDataCache_[node.id] = node; + } + var node = this.get(data.targetID); + if (node) + nodeImpl.dispatchEvent(data.eventType); + return true; + }, + + toString: function() { + function toStringInternal(node, indent) { + if (!node) + return ''; + var output = + new Array(indent).join(' ') + privates(node).impl.toString() + '\n'; + indent += 2; + for (var i = 0; i < node.children().length; i++) + output += toStringInternal(node.children()[i], indent); + return output; + } + return toStringInternal(this, 0); + } +}; + + var AutomationNode = utils.expose('AutomationNode', AutomationNodeImpl, { functions: ['parent', @@ -216,11 +383,18 @@ var AutomationNode = utils.expose('AutomationNode', 'setSelection', 'addEventListener', 'removeEventListener'], - readonly: ['id', + readonly: ['isRootNode', + 'id', 'role', 'state', 'location', - 'attributes', - 'loaded'] }); + 'attributes'] }); + +var AutomationRootNode = utils.expose('AutomationRootNode', + AutomationRootNodeImpl, + { superclass: AutomationNode, + functions: ['load'], + readonly: ['loaded'] }); exports.AutomationNode = AutomationNode; +exports.AutomationRootNode = AutomationRootNode; |