summaryrefslogtreecommitdiffstats
path: root/chrome/renderer/resources/extensions/automation/automation_node.js
diff options
context:
space:
mode:
authoraboxhall@chromium.org <aboxhall@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-06-10 18:37:14 +0000
committeraboxhall@chromium.org <aboxhall@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-06-10 18:37:14 +0000
commited51006db1465dc8542bb6508514fd89414f3f3b (patch)
tree6972994063c5793502e7a96882360d4dda516c87 /chrome/renderer/resources/extensions/automation/automation_node.js
parent09f60dd57009ea1ac711bf6592e9d90fe66a9e78 (diff)
downloadchromium_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.js212
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;