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

Unified Diff: chrome/renderer/resources/extensions/automation/automation_node.js

Issue 1155183006: Reimplement automation API on top of C++-backed AXTree. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@automation_faster_2
Patch Set: Copy observers Created 5 years, 6 months 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 side-by-side diff with in-line comments
Download patch
Index: chrome/renderer/resources/extensions/automation/automation_node.js
diff --git a/chrome/renderer/resources/extensions/automation/automation_node.js b/chrome/renderer/resources/extensions/automation/automation_node.js
index 11ac8d8a3b065b5e292df2df259d81716aa3c95f..e5de7122504c8b6fac90adb8f02d587a45a0bfad 100644
--- a/chrome/renderer/resources/extensions/automation/automation_node.js
+++ b/chrome/renderer/resources/extensions/automation/automation_node.js
@@ -8,6 +8,124 @@ var automationInternal =
var IsInteractPermitted =
requireNative('automationInternal').IsInteractPermitted;
+/**
+ * @param {number} axTreeID The id of the accessibility tree.
+ * @return {?number} The id of the root node.
+ */
+var GetRootID = requireNative('automationInternal').GetRootID;
+
+/**
+ * @param {number} axTreeID The id of the accessibility tree.
+ * @param {number} nodeID The id of a node.
+ * @return {?number} The id of the node's parent, or undefined if it's the
+ * root of its tree or if the tree or node wasn't found.
+ */
+var GetParentID = requireNative('automationInternal').GetParentID;
+
+/**
+ * @param {number} axTreeID The id of the accessibility tree.
+ * @param {number} nodeID The id of a node.
+ * @return {?number} The number of children of the node, or undefined if
+ * the tree or node wasn't found.
+ */
+var GetChildCount = requireNative('automationInternal').GetChildCount;
+
+/**
+ * @param {number} axTreeID The id of the accessibility tree.
+ * @param {number} nodeID The id of a node.
+ * @param {number} childIndex An index of a child of this node.
+ * @return {?number} The id of the child at the given index, or undefined
+ * if the tree or node or child at that index wasn't found.
+ */
+var GetChildIDAtIndex = requireNative('automationInternal').GetChildIDAtIndex;
+
+/**
+ * @param {number} axTreeID The id of the accessibility tree.
+ * @param {number} nodeID The id of a node.
+ * @return {?number} The index of this node in its parent, or undefined if
+ * the tree or node or node parent wasn't found.
+ */
+var GetIndexInParent = requireNative('automationInternal').GetIndexInParent;
+
+/**
+ * @param {number} axTreeID The id of the accessibility tree.
+ * @param {number} nodeID The id of a node.
+ * @return {?Object} An object with a string key for every state flag set,
+ * or undefined if the tree or node or node parent wasn't found.
+ */
+var GetState = requireNative('automationInternal').GetState;
+
+/**
+ * @param {number} axTreeID The id of the accessibility tree.
+ * @param {number} nodeID The id of a node.
+ * @return {string} The role of the node, or undefined if the tree or
+ * node wasn't found.
+ */
+var GetRole = requireNative('automationInternal').GetRole;
+
+/**
+ * @param {number} axTreeID The id of the accessibility tree.
+ * @param {number} nodeID The id of a node.
+ * @return {?automation.Rect} The location of the node, or undefined if
+ * the tree or node wasn't found.
+ */
+var GetLocation = requireNative('automationInternal').GetLocation;
+
+/**
+ * @param {number} axTreeID The id of the accessibility tree.
+ * @param {number} nodeID The id of a node.
+ * @param {string} attr The name of a string attribute.
+ * @return {?string} The value of this attribute, or undefined if the tree,
+ * node, or attribute wasn't found.
+ */
+var GetStringAttribute = requireNative('automationInternal').GetStringAttribute;
+
+/**
+ * @param {number} axTreeID The id of the accessibility tree.
+ * @param {number} nodeID The id of a node.
+ * @param {string} attr The name of an attribute.
+ * @return {?boolean} The value of this attribute, or undefined if the tree,
+ * node, or attribute wasn't found.
+ */
+var GetBoolAttribute = requireNative('automationInternal').GetBoolAttribute;
+
+/**
+ * @param {number} axTreeID The id of the accessibility tree.
+ * @param {number} nodeID The id of a node.
+ * @param {string} attr The name of an attribute.
+ * @return {?number} The value of this attribute, or undefined if the tree,
+ * node, or attribute wasn't found.
+ */
+var GetIntAttribute = requireNative('automationInternal').GetIntAttribute;
+
+/**
+ * @param {number} axTreeID The id of the accessibility tree.
+ * @param {number} nodeID The id of a node.
+ * @param {string} attr The name of an attribute.
+ * @return {?number} The value of this attribute, or undefined if the tree,
+ * node, or attribute wasn't found.
+ */
+var GetFloatAttribute = requireNative('automationInternal').GetFloatAttribute;
+
+/**
+ * @param {number} axTreeID The id of the accessibility tree.
+ * @param {number} nodeID The id of a node.
+ * @param {string} attr The name of an attribute.
+ * @return {?Array.<number>} The value of this attribute, or undefined
+ * if the tree, node, or attribute wasn't found.
+ */
+var GetIntListAttribute =
+ requireNative('automationInternal').GetIntListAttribute;
+
+/**
+ * @param {number} axTreeID The id of the accessibility tree.
+ * @param {number} nodeID The id of a node.
+ * @param {string} attr The name of an HTML attribute.
+ * @return {?string} The value of this attribute, or undefined if the tree,
+ * node, or attribute wasn't found.
+ */
+var GetHtmlAttribute = requireNative('automationInternal').GetHtmlAttribute;
+
var lastError = require('lastError');
var logging = requireNative('logging');
var schema = requireNative('automationInternal').GetSchemaAdditions();
@@ -20,16 +138,12 @@ var utils = require('utils');
*/
function AutomationNodeImpl(root) {
this.rootImpl = root;
- this.childIds = [];
// Public attributes. No actual data gets set on this object.
- this.attributes = {};
- // Internal object holding all attributes.
- this.attributesInternal = {};
this.listeners = {};
- this.location = { left: 0, top: 0, width: 0, height: 0 };
}
AutomationNodeImpl.prototype = {
+ treeID: -1,
id: -1,
role: '',
state: { busy: true },
@@ -40,16 +154,51 @@ AutomationNodeImpl.prototype = {
},
get parent() {
- return this.hostTree || this.rootImpl.get(this.parentID);
+ if (this.hostNode_)
+ return this.hostNode_;
+ var parentID = GetParentID(this.treeID, this.id);
+ return this.rootImpl.get(parentID);
+ },
+
+ get state() {
+ return GetState(this.treeID, this.id);
+ },
+
+ get role() {
+ return GetRole(this.treeID, this.id);
+ },
+
+ get location() {
+ return GetLocation(this.treeID, this.id);
+ },
+
+ get indexInParent() {
+ return GetIndexInParent(this.treeID, this.id);
+ },
+
+ get childTree() {
+ var childTreeID = GetIntAttribute(this.treeID, this.id, 'childTreeId');
+ if (childTreeID)
+ return AutomationRootNodeImpl.get(childTreeID);
},
get firstChild() {
- return this.childTree || this.rootImpl.get(this.childIds[0]);
+ if (this.childTree)
+ return this.childTree;
+ if (!GetChildCount(this.treeID, this.id))
+ return undefined;
+ var firstChildID = GetChildIDAtIndex(this.treeID, this.id, 0);
+ return this.rootImpl.get(firstChildID);
},
get lastChild() {
- var childIds = this.childIds;
- return this.childTree || this.rootImpl.get(childIds[childIds.length - 1]);
+ if (this.childTree)
+ return this.childTree;
+ var count = GetChildCount(this.treeID, this.id);
+ if (!count)
+ return undefined;
+ var lastChildID = GetChildIDAtIndex(this.treeID, this.id, count - 1);
+ return this.rootImpl.get(lastChildID);
},
get children() {
@@ -57,24 +206,28 @@ AutomationNodeImpl.prototype = {
return [this.childTree];
var children = [];
- for (var i = 0, childID; childID = this.childIds[i]; i++) {
- logging.CHECK(this.rootImpl.get(childID));
- children.push(this.rootImpl.get(childID));
+ var count = GetChildCount(this.treeID, this.id);
+ for (var i = 0; i < count; ++i) {
+ var childID = GetChildIDAtIndex(this.treeID, this.id, i);
+ var child = this.rootImpl.get(childID);
+ children.push(child);
}
return children;
},
get previousSibling() {
var parent = this.parent;
- if (parent && this.indexInParent > 0)
- return parent.children[this.indexInParent - 1];
+ var indexInParent = GetIndexInParent(this.treeID, this.id);
+ if (parent && indexInParent > 0)
+ return parent.children[indexInParent - 1];
return undefined;
},
get nextSibling() {
var parent = this.parent;
- if (parent && this.indexInParent < parent.children.length)
- return parent.children[this.indexInParent + 1];
+ var indexInParent = GetIndexInParent(this.treeID, this.id);
+ if (parent && indexInParent < parent.children.length)
+ return parent.children[indexInParent + 1];
return undefined;
},
@@ -170,12 +323,20 @@ AutomationNodeImpl.prototype = {
var impl = privates(this).impl;
if (!impl)
impl = this;
+
+ var parentID = GetParentID(this.treeID, this.id);
+ var count = GetChildCount(this.treeID, this.id);
+ var childIDs = [];
+ for (var i = 0; i < count; ++i) {
+ var childID = GetChildIDAtIndex(this.treeID, this.id, i);
+ childIDs.push(childID);
+ }
+
return 'node id=' + impl.id +
' role=' + this.role +
' state=' + $JSON.stringify(this.state) +
- ' parentID=' + impl.parentID +
- ' childIds=' + $JSON.stringify(impl.childIds) +
- ' attributes=' + $JSON.stringify(this.attributes);
+ ' parentID=' + parentID +
+ ' childIds=' + $JSON.stringify(childIDs);
},
dispatchEventAtCapturing_: function(event, path) {
@@ -219,9 +380,9 @@ AutomationNodeImpl.prototype = {
try {
listeners[i].callback(event);
} catch (e) {
- console.error('Error in event handler for ' + event.type +
- 'during phase ' + eventPhase + ': ' +
- e.message + '\nStack trace: ' + e.stack);
+ logging.WARNING('Error in event handler for ' + event.type +
+ ' during phase ' + eventPhase + ': ' +
+ e.message + '\nStack trace: ' + e.stack);
}
}
},
@@ -312,17 +473,14 @@ AutomationNodeImpl.prototype = {
}
if ('attributes' in params) {
for (var attribute in params.attributes) {
- if (!(attribute in this.attributesInternal))
- return false;
-
var attrValue = params.attributes[attribute];
if (typeof attrValue != 'object') {
- if (this.attributesInternal[attribute] !== attrValue)
+ if (this[attribute] !== attrValue)
return false;
} else if (attrValue instanceof RegExp) {
- if (typeof this.attributesInternal[attribute] != 'string')
+ if (typeof this[attribute] != 'string')
return false;
- if (!attrValue.test(this.attributesInternal[attribute]))
+ if (!attrValue.test(this[attribute]))
return false;
} else {
// TODO(aboxhall): handle intlist case.
@@ -334,171 +492,196 @@ AutomationNodeImpl.prototype = {
}
};
-// 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'
-];
-
-/**
- * Maps an attribute name to another attribute who's value is an id or an array
- * of ids referencing an AutomationNode.
- * @param {!Object<string>}
- * @const
- */
-var ATTRIBUTE_NAME_TO_ID_ATTRIBUTE = {
- 'aria-activedescendant': 'activedescendantId',
- 'aria-controls': 'controlsIds',
- 'aria-describedby': 'describedbyIds',
- 'aria-flowto': 'flowtoIds',
- 'aria-labelledby': 'labelledbyIds',
- 'aria-owns': 'ownsIds'
-};
-
-/**
- * A set of attributes ignored in the automation API.
- * @param {!Object<boolean>}
- * @const
- */
-var ATTRIBUTE_BLACKLIST = {'activedescendantId': true,
- 'childTreeId': true,
- 'controlsIds': true,
- 'describedbyIds': true,
- 'flowtoIds': true,
- 'labelledbyIds': true,
- 'ownsIds': true
-};
-
-function defaultStringAttribute(opt_defaultVal) {
- return { default: undefined, reflectFrom: 'stringAttributes' };
-}
-
-function defaultIntAttribute(opt_defaultVal) {
- var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : 0;
- return { default: defaultVal, reflectFrom: 'intAttributes' };
-}
-
-function defaultFloatAttribute(opt_defaultVal) {
- var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : 0;
- return { default: defaultVal, reflectFrom: 'floatAttributes' };
-}
-
-function defaultBoolAttribute(opt_defaultVal) {
- var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : false;
- return { default: defaultVal, reflectFrom: 'boolAttributes' };
-}
-
-function defaultHtmlAttribute(opt_defaultVal) {
- var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : '';
- return { default: defaultVal, reflectFrom: 'htmlAttributes' };
-}
-
-function defaultIntListAttribute(opt_defaultVal) {
- var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : [];
- return { default: defaultVal, reflectFrom: 'intlistAttributes' };
-}
-
-function defaultNodeRefAttribute(idAttribute, opt_defaultVal) {
- var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : null;
- return { default: defaultVal,
- idFrom: 'intAttributes',
- idAttribute: idAttribute,
- isRef: true };
-}
-
-function defaultNodeRefListAttribute(idAttribute, opt_defaultVal) {
- var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : [];
- return { default: [],
- idFrom: 'intlistAttributes',
- idAttribute: idAttribute,
- isRef: true };
-}
-
-// Maps an attribute to its default value in an invalidated node.
-// These attributes are taken directly from the Automation idl.
-var DefaultMixinAttributes = {
- description: defaultStringAttribute(),
- help: defaultStringAttribute(),
- name: defaultStringAttribute(),
- value: defaultStringAttribute(),
- htmlTag: defaultStringAttribute(),
- hierarchicalLevel: defaultIntAttribute(),
- controls: defaultNodeRefListAttribute('controlsIds'),
- describedby: defaultNodeRefListAttribute('describedbyIds'),
- flowto: defaultNodeRefListAttribute('flowtoIds'),
- labelledby: defaultNodeRefListAttribute('labelledbyIds'),
- owns: defaultNodeRefListAttribute('ownsIds'),
- wordStarts: defaultIntListAttribute(),
- wordEnds: defaultIntListAttribute()
-};
-
-var ActiveDescendantMixinAttribute = {
- activedescendant: defaultNodeRefAttribute('activedescendantId')
-};
-
-var LinkMixinAttributes = {
- url: defaultStringAttribute()
-};
-
-var DocumentMixinAttributes = {
- docUrl: defaultStringAttribute(),
- docTitle: defaultStringAttribute(),
- docLoaded: defaultStringAttribute(),
- docLoadingProgress: defaultFloatAttribute()
-};
-
-var ScrollableMixinAttributes = {
- scrollX: defaultIntAttribute(),
- scrollXMin: defaultIntAttribute(),
- scrollXMax: defaultIntAttribute(),
- scrollY: defaultIntAttribute(),
- scrollYMin: defaultIntAttribute(),
- scrollYMax: defaultIntAttribute()
-};
-
-var EditableTextMixinAttributes = {
- textSelStart: defaultIntAttribute(-1),
- textSelEnd: defaultIntAttribute(-1),
- type: defaultHtmlAttribute()
-};
-
-var RangeMixinAttributes = {
- valueForRange: defaultFloatAttribute(),
- minValueForRange: defaultFloatAttribute(),
- maxValueForRange: defaultFloatAttribute()
-};
-
-var TableMixinAttributes = {
- tableRowCount: defaultIntAttribute(),
- tableColumnCount: defaultIntAttribute()
-};
+var stringAttributes = [
+ 'accessKey',
+ 'action',
+ 'ariaInvalidValue',
+ 'autoComplete',
+ 'containerLiveRelevant',
+ 'containerLiveStatus',
+ 'description',
+ 'display',
+ 'docDoctype',
+ 'docMimetype',
+ 'docTitle',
+ 'docUrl',
+ 'dropeffect',
+ 'help',
+ 'htmlTag',
+ 'liveRelevant',
+ 'liveStatus',
+ 'name',
+ 'placeholder',
+ 'shortcut',
+ 'textInputType',
+ 'url',
+ 'value'];
+
+var boolAttributes = [
+ 'ariaReadonly',
+ 'buttonMixed',
+ 'canSetValue',
+ 'canvasHasFallback',
+ 'containerLiveAtomic',
+ 'containerLiveBusy',
+ 'docLoaded',
+ 'grabbed',
+ 'isAxTreeHost',
+ 'liveAtomic',
+ 'liveBusy',
+ 'updateLocationOnly'];
+
+var intAttributes = [
+ 'backgroundColor',
+ 'color',
+ 'colorValue',
+ 'hierarchicalLevel',
+ 'invalidState',
+ 'posInSet',
+ 'scrollX',
+ 'scrollXMax',
+ 'scrollXMin',
+ 'scrollY',
+ 'scrollYMax',
+ 'scrollYMin',
+ 'setSize',
+ 'sortDirection',
+ 'tableCellColumnIndex',
+ 'tableCellColumnSpan',
+ 'tableCellRowIndex',
+ 'tableCellRowSpan',
+ 'tableColumnCount',
+ 'tableColumnIndex',
+ 'tableRowCount',
+ 'tableRowIndex',
+ 'textDirection',
+ 'textSelEnd',
+ 'textSelStart',
+ 'textStyle'];
+
+var nodeRefAttributes = [
+ ['activedescendantId', 'activedescendant'],
+ ['tableColumnHeaderId', 'tableColumnHeader'],
+ ['tableHeaderId', 'tableHeader'],
+ ['tableRowHeaderId', 'tableRowHeader'],
+ ['titleUiElement', 'titleUIElement']];
+
+var intListAttributes = [
+ 'characterOffsets',
+ 'lineBreaks',
+ 'wordEnds',
+ 'wordStarts'];
+
+var nodeRefListAttributes = [
+ ['cellIds', 'cells'],
+ ['controlsIds', 'controls'],
+ ['describedbyIds', 'describedBy'],
+ ['flowtoIds', 'flowTo'],
+ ['labelledbyIds', 'labelledBy'],
+ ['uniqueCellIds', 'uniqueCells']];
+
+var floatAttributes = [
+ 'docLoadingProgress',
+ 'valueForRange',
+ 'minValueForRange',
+ 'maxValueForRange',
+ 'fontSize'];
+
+var htmlAttributes = [
+ ['type', 'inputType']];
+
+var publicAttributes = [];
+
+stringAttributes.forEach(function (attributeName) {
+ publicAttributes.push(attributeName);
+ Object.defineProperty(AutomationNodeImpl.prototype, attributeName, {
+ get: function() {
+ return GetStringAttribute(this.treeID, this.id, attributeName);
+ }
+ });
+});
+
+boolAttributes.forEach(function (attributeName) {
+ publicAttributes.push(attributeName);
+ Object.defineProperty(AutomationNodeImpl.prototype, attributeName, {
+ get: function() {
+ return GetBoolAttribute(this.treeID, this.id, attributeName);
+ }
+ });
+});
+
+intAttributes.forEach(function (attributeName) {
+ publicAttributes.push(attributeName);
+ Object.defineProperty(AutomationNodeImpl.prototype, attributeName, {
+ get: function() {
+ return GetIntAttribute(this.treeID, this.id, attributeName);
+ }
+ });
+});
+
+nodeRefAttributes.forEach(function (params) {
+ var srcAttributeName = params[0];
+ var dstAttributeName = params[1];
+ publicAttributes.push(dstAttributeName);
+ Object.defineProperty(AutomationNodeImpl.prototype, dstAttributeName, {
+ get: function() {
+ var id = GetIntAttribute(this.treeID, this.id, srcAttributeName);
+ if (id)
+ return this.rootImpl.get(id);
+ else
+ return undefined;
+ }
+ });
+});
+
+intListAttributes.forEach(function (attributeName) {
+ publicAttributes.push(attributeName);
+ Object.defineProperty(AutomationNodeImpl.prototype, attributeName, {
+ get: function() {
+ return GetIntListAttribute(this.treeID, this.id, attributeName);
+ }
+ });
+});
+
+nodeRefListAttributes.forEach(function (params) {
+ var srcAttributeName = params[0];
+ var dstAttributeName = params[1];
+ publicAttributes.push(dstAttributeName);
+ Object.defineProperty(AutomationNodeImpl.prototype, dstAttributeName, {
+ get: function() {
+ var ids = GetIntListAttribute(this.treeID, this.id, srcAttributeName);
+ if (!ids)
+ return undefined;
+ var result = [];
+ for (var i = 0; i < ids.length; ++i) {
+ var node = this.rootImpl.get(ids[i]);
+ if (node)
+ result.push(node);
+ }
+ return result;
+ }
+ });
+});
-var TableCellMixinAttributes = {
- tableCellColumnIndex: defaultIntAttribute(),
- tableCellColumnSpan: defaultIntAttribute(1),
- tableCellRowIndex: defaultIntAttribute(),
- tableCellRowSpan: defaultIntAttribute(1)
-};
+floatAttributes.forEach(function (attributeName) {
+ publicAttributes.push(attributeName);
+ Object.defineProperty(AutomationNodeImpl.prototype, attributeName, {
+ get: function() {
+ return GetFloatAttribute(this.treeID, this.id, attributeName);
+ }
+ });
+});
-var LiveRegionMixinAttributes = {
- containerLiveAtomic: defaultBoolAttribute(),
- containerLiveBusy: defaultBoolAttribute(),
- containerLiveRelevant: defaultStringAttribute(),
- containerLiveStatus: defaultStringAttribute(),
-};
+htmlAttributes.forEach(function (params) {
+ var srcAttributeName = params[0];
+ var dstAttributeName = params[1];
+ publicAttributes.push(dstAttributeName);
+ Object.defineProperty(AutomationNodeImpl.prototype, dstAttributeName, {
+ get: function() {
+ return GetHtmlAttribute(this.treeID, this.id, srcAttributeName);
+ }
+ });
+});
/**
* AutomationRootNode.
@@ -523,107 +706,88 @@ function AutomationRootNodeImpl(treeID) {
this.axNodeDataCache_ = {};
}
+AutomationRootNodeImpl.idToAutomationRootNode_ = {};
+
+AutomationRootNodeImpl.get = function(treeID) {
+ var result = AutomationRootNodeImpl.idToAutomationRootNode_[treeID];
+ return result || undefined;
+}
+
+AutomationRootNodeImpl.getOrCreate = function(treeID) {
+ if (AutomationRootNodeImpl.idToAutomationRootNode_[treeID])
+ return AutomationRootNodeImpl.idToAutomationRootNode_[treeID];
+ var result = new AutomationRootNode(treeID);
+ AutomationRootNodeImpl.idToAutomationRootNode_[treeID] = result;
+ return result;
+}
+
+AutomationRootNodeImpl.destroy = function(treeID) {
+ delete AutomationRootNodeImpl.idToAutomationRootNode_[treeID];
+}
+
AutomationRootNodeImpl.prototype = {
__proto__: AutomationNodeImpl.prototype,
+ /**
+ * @type {boolean}
+ */
isRootNode: true,
+
+ /**
+ * @type {number}
+ */
treeID: -1,
+ /**
+ * The parent of this node from a different tree.
+ * @type {?AutomationNode}
+ * @private
+ */
+ hostNode_: null,
+
+ /**
+ * A map from id to AutomationNode.
+ * @type {Object.<number, AutomationNode>}
+ * @private
+ */
+ axNodeDataCache_: null,
+
+ get id() {
+ return GetRootID(this.treeID);
+ },
+
get: function(id) {
if (id == undefined)
return undefined;
- return this.axNodeDataCache_[id];
- },
+ if (id == this.id)
+ return this.wrapper;
- unserialize: function(update) {
- var updateState = { pendingNodes: {}, newNodes: {} };
- var oldRootId = this.id;
+ var obj = this.axNodeDataCache_[id];
+ if (obj)
+ return obj;
- if (update.nodeIdToClear < 0) {
- logging.WARNING('Bad nodeIdToClear: ' + update.nodeIdToClear);
- lastError.set('automation',
- 'Bad update received on automation tree',
- null,
- chrome);
- return false;
- } else if (update.nodeIdToClear > 0) {
- var nodeToClear = this.axNodeDataCache_[update.nodeIdToClear];
- if (!nodeToClear) {
- logging.WARNING('Bad nodeIdToClear: ' + update.nodeIdToClear +
- ' (not in cache)');
- lastError.set('automation',
- 'Bad update received on automation tree',
- null,
- chrome);
- return false;
- }
- if (nodeToClear === this.wrapper) {
- this.invalidate_(nodeToClear);
- } else {
- var children = nodeToClear.children;
- for (var i = 0; i < children.length; i++)
- this.invalidate_(children[i]);
- var nodeToClearImpl = privates(nodeToClear).impl;
- nodeToClearImpl.childIds = []
- updateState.pendingNodes[nodeToClearImpl.id] = nodeToClear;
- }
- }
-
- for (var i = 0; i < update.nodes.length; i++) {
- if (!this.updateNode_(update.nodes[i], updateState))
- return false;
- }
-
- if (Object.keys(updateState.pendingNodes).length > 0) {
- logging.WARNING('Nodes left pending by the update: ' +
- $JSON.stringify(updateState.pendingNodes));
- lastError.set('automation',
- 'Bad update received on automation tree',
- null,
- chrome);
- return false;
- }
+ obj = new AutomationNode(this);
+ privates(obj).impl.treeID = this.treeID;
+ privates(obj).impl.id = id;
+ this.axNodeDataCache_[id] = obj;
- // Notify tree change observers of new nodes.
- // TODO(dmazzoni): Notify tree change observers of changed nodes,
- // and handle subtreeCreated and nodeCreated properly.
- var observers = automationUtil.treeChangeObservers;
- if (observers.length > 0) {
- for (var nodeId in updateState.newNodes) {
- var node = updateState.newNodes[nodeId];
- var treeChange =
- {target: node, type: schema.TreeChangeType.nodeCreated};
- for (var i = 0; i < observers.length; i++) {
- try {
- observers[i](treeChange);
- } catch (e) {
- console.error('Error in tree change observer for ' +
- treeChange.type + ': ' + e.message +
- '\nStack trace: ' + e.stack);
- }
- }
- }
- }
+ return obj;
+ },
- return true;
+ remove: function(id) {
+ delete this.axNodeDataCache_[id];
},
destroy: function() {
- if (this.hostTree)
- this.hostTree.childTree = undefined;
- this.hostTree = undefined;
-
this.dispatchEvent(schema.EventType.destroyed);
- this.invalidate_(this.wrapper);
},
- onAccessibilityEvent: function(eventParams) {
- if (!this.unserialize(eventParams.update)) {
- logging.WARNING('unserialization failed');
- return false;
- }
+ setHostNode(hostNode) {
+ this.hostNode_ = hostNode;
+ },
+ onAccessibilityEvent: function(eventParams) {
var targetNode = this.get(eventParams.targetID);
if (targetNode) {
var targetNodeImpl = privates(targetNode).impl;
@@ -651,373 +815,8 @@ AutomationRootNodeImpl.prototype = {
}
return toStringInternal(this, 0);
},
-
- invalidate_: function(node) {
- if (!node)
- return;
-
- // Notify tree change observers of the removed node.
- var observers = automationUtil.treeChangeObservers;
- if (observers.length > 0) {
- var treeChange = {target: node, type: schema.TreeChangeType.nodeRemoved};
- for (var i = 0; i < observers.length; i++) {
- try {
- observers[i](treeChange);
- } catch (e) {
- console.error('Error in tree change observer for ' + treeChange.type +
- ': ' + e.message + '\nStack trace: ' + e.stack);
- }
- }
- }
-
- var children = node.children;
-
- for (var i = 0, child; child = children[i]; i++) {
- // Do not invalidate into subrooted nodes.
- // TODO(dtseng): Revisit logic once out of proc iframes land.
- if (child.root != node.root)
- continue;
- 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 = nodeImpl.id;
- for (var key in AutomationAttributeDefaults) {
- nodeImpl[key] = AutomationAttributeDefaults[key];
- }
-
- nodeImpl.attributesInternal = {};
- for (var key in DefaultMixinAttributes) {
- var mixinAttribute = DefaultMixinAttributes[key];
- if (!mixinAttribute.isRef)
- nodeImpl.attributesInternal[key] = mixinAttribute.default;
- }
- nodeImpl.childIds = [];
- nodeImpl.id = id;
- delete this.axNodeDataCache_[id];
- },
-
- deleteOldChildren_: function(node, newChildIds) {
- // Create a set of child ids in |src| for fast lookup, and return false
- // if a duplicate is found;
- var newChildIdSet = {};
- for (var i = 0; i < newChildIds.length; i++) {
- var childId = newChildIds[i];
- if (newChildIdSet[childId]) {
- logging.WARNING('Node ' + privates(node).impl.id +
- ' has duplicate child id ' + childId);
- lastError.set('automation',
- 'Bad update received on automation tree',
- null,
- chrome);
- return false;
- }
- newChildIdSet[newChildIds[i]] = true;
- }
-
- // Delete the old children.
- var nodeImpl = privates(node).impl;
- var oldChildIds = nodeImpl.childIds;
- for (var i = 0; i < oldChildIds.length;) {
- var oldId = oldChildIds[i];
- if (!newChildIdSet[oldId]) {
- this.invalidate_(this.axNodeDataCache_[oldId]);
- oldChildIds.splice(i, 1);
- } else {
- i++;
- }
- }
- nodeImpl.childIds = oldChildIds;
-
- return true;
- },
-
- createNewChildren_: function(node, newChildIds, updateState) {
- logging.CHECK(node);
- var success = true;
-
- for (var i = 0; i < newChildIds.length; i++) {
- var childId = newChildIds[i];
- var childNode = this.axNodeDataCache_[childId];
- if (childNode) {
- if (childNode.parent != node) {
- var parentId = -1;
- if (childNode.parent) {
- var parentImpl = privates(childNode.parent).impl;
- parentId = parentImpl.id;
- }
- // This is a serious error - nodes should never be reparented.
- // If this case occurs, continue so this node isn't left in an
- // inconsistent state, but return failure at the end.
- logging.WARNING('Node ' + childId + ' reparented from ' +
- parentId + ' to ' + privates(node).impl.id);
- lastError.set('automation',
- 'Bad update received on automation tree',
- null,
- chrome);
- success = false;
- continue;
- }
- } else {
- childNode = new AutomationNode(this);
- this.axNodeDataCache_[childId] = childNode;
- privates(childNode).impl.id = childId;
- updateState.pendingNodes[childId] = childNode;
- updateState.newNodes[childId] = childNode;
- }
- privates(childNode).impl.indexInParent = i;
- privates(childNode).impl.parentID = privates(node).impl.id;
- }
-
- return success;
- },
-
- setData_: function(node, nodeData) {
- var nodeImpl = privates(node).impl;
-
- // TODO(dtseng): Make into set listing all hosting node roles.
- if (nodeData.role == schema.RoleType.webView) {
- if (nodeImpl.childTreeID !== nodeData.intAttributes.childTreeId)
- nodeImpl.pendingChildFrame = true;
-
- if (nodeImpl.pendingChildFrame) {
- nodeImpl.childTreeID = nodeData.intAttributes.childTreeId;
- automationUtil.storeTreeCallback(nodeImpl.childTreeID, function(root) {
- nodeImpl.pendingChildFrame = false;
- nodeImpl.childTree = root;
- privates(root).impl.hostTree = node;
- if (root.attributes.docLoadingProgress == 1)
- privates(root).impl.dispatchEvent(schema.EventType.loadComplete);
- nodeImpl.dispatchEvent(schema.EventType.childrenChanged);
- });
- automationInternal.enableFrame(nodeImpl.childTreeID);
- }
- }
- for (var key in AutomationAttributeDefaults) {
- if (key in nodeData)
- nodeImpl[key] = nodeData[key];
- else
- nodeImpl[key] = AutomationAttributeDefaults[key];
- }
-
- // Set all basic attributes.
- this.mixinAttributes_(nodeImpl, DefaultMixinAttributes, nodeData);
-
- // If this is a rootWebArea or webArea, set document attributes.
- if (nodeData.role == schema.RoleType.rootWebArea ||
- nodeData.role == schema.RoleType.webArea) {
- this.mixinAttributes_(nodeImpl, DocumentMixinAttributes, nodeData);
- }
-
- // If this is a scrollable area, set scrollable attributes.
- for (var scrollAttr in ScrollableMixinAttributes) {
- var spec = ScrollableMixinAttributes[scrollAttr];
- if (this.findAttribute_(scrollAttr, spec, nodeData) !== undefined) {
- this.mixinAttributes_(nodeImpl, ScrollableMixinAttributes, nodeData);
- break;
- }
- }
-
- // If this is inside a live region, set live region mixins.
- var attr = 'containerLiveStatus';
- var spec = LiveRegionMixinAttributes[attr];
- if (this.findAttribute_(attr, spec, nodeData) !== undefined) {
- this.mixinAttributes_(nodeImpl, LiveRegionMixinAttributes, nodeData);
- }
-
- // If this is a link, set link attributes
- if (nodeData.role == 'link') {
- this.mixinAttributes_(nodeImpl, LinkMixinAttributes, nodeData);
- }
-
- // If this is an editable text area, set editable text attributes.
- if (nodeData.role == schema.RoleType.textField ||
- nodeData.role == schema.RoleType.spinButton) {
- this.mixinAttributes_(nodeImpl, EditableTextMixinAttributes, nodeData);
- }
-
- // If this is a range type, set range attributes.
- if (nodeData.role == schema.RoleType.progressIndicator ||
- nodeData.role == schema.RoleType.scrollBar ||
- nodeData.role == schema.RoleType.slider ||
- nodeData.role == schema.RoleType.spinButton) {
- this.mixinAttributes_(nodeImpl, RangeMixinAttributes, nodeData);
- }
-
- // If this is a table, set table attributes.
- if (nodeData.role == schema.RoleType.table) {
- this.mixinAttributes_(nodeImpl, TableMixinAttributes, nodeData);
- }
-
- // If this is a table cell, set table cell attributes.
- if (nodeData.role == schema.RoleType.cell) {
- this.mixinAttributes_(nodeImpl, TableCellMixinAttributes, nodeData);
- }
-
- // If this has an active descendant, expose it.
- if ('intAttributes' in nodeData &&
- 'activedescendantId' in nodeData.intAttributes) {
- this.mixinAttributes_(nodeImpl, ActiveDescendantMixinAttribute, nodeData);
- }
-
- for (var i = 0; i < AutomationAttributeTypes.length; i++) {
- var attributeType = AutomationAttributeTypes[i];
- for (var attributeName in nodeData[attributeType]) {
- nodeImpl.attributesInternal[attributeName] =
- nodeData[attributeType][attributeName];
- if (ATTRIBUTE_BLACKLIST.hasOwnProperty(attributeName) ||
- nodeImpl.attributes.hasOwnProperty(attributeName)) {
- continue;
- } else if (
- ATTRIBUTE_NAME_TO_ID_ATTRIBUTE.hasOwnProperty(attributeName)) {
- this.defineReadonlyAttribute_(nodeImpl,
- nodeImpl.attributes,
- attributeName,
- true);
- } else {
- this.defineReadonlyAttribute_(nodeImpl,
- nodeImpl.attributes,
- attributeName);
- }
- }
- }
- },
-
- mixinAttributes_: function(nodeImpl, attributes, nodeData) {
- for (var attribute in attributes) {
- var spec = attributes[attribute];
- if (spec.isRef)
- this.mixinRelationshipAttribute_(nodeImpl, attribute, spec, nodeData);
- else
- this.mixinAttribute_(nodeImpl, attribute, spec, nodeData);
- }
- },
-
- mixinAttribute_: function(nodeImpl, attribute, spec, nodeData) {
- var value = this.findAttribute_(attribute, spec, nodeData);
- if (value === undefined)
- value = spec.default;
- nodeImpl.attributesInternal[attribute] = value;
- this.defineReadonlyAttribute_(nodeImpl, nodeImpl, attribute);
- },
-
- mixinRelationshipAttribute_: function(nodeImpl, attribute, spec, nodeData) {
- var idAttribute = spec.idAttribute;
- var idValue = spec.default;
- if (spec.idFrom in nodeData) {
- idValue = idAttribute in nodeData[spec.idFrom]
- ? nodeData[spec.idFrom][idAttribute] : idValue;
- }
-
- // Ok to define a list attribute with an empty list, but not a
- // single ref with a null ID.
- if (idValue === null)
- return;
-
- nodeImpl.attributesInternal[idAttribute] = idValue;
- this.defineReadonlyAttribute_(
- nodeImpl, nodeImpl, attribute, true, idAttribute);
- },
-
- findAttribute_: function(attribute, spec, nodeData) {
- if (!('reflectFrom' in spec))
- return;
- var attributeGroup = spec.reflectFrom;
- if (!(attributeGroup in nodeData))
- return;
-
- return nodeData[attributeGroup][attribute];
- },
-
- defineReadonlyAttribute_: function(
- node, object, attributeName, opt_isIDRef, opt_idAttribute) {
- if (attributeName in object)
- return;
-
- if (opt_isIDRef) {
- $Object.defineProperty(object, attributeName, {
- enumerable: true,
- get: function() {
- var idAttribute = opt_idAttribute ||
- ATTRIBUTE_NAME_TO_ID_ATTRIBUTE[attributeName];
- var idValue = node.attributesInternal[idAttribute];
- if (Array.isArray(idValue)) {
- return idValue.map(function(current) {
- return node.rootImpl.get(current);
- }, this);
- }
- return node.rootImpl.get(idValue);
- }.bind(this),
- });
- } else {
- $Object.defineProperty(object, attributeName, {
- enumerable: true,
- get: function() {
- return node.attributesInternal[attributeName];
- }.bind(this),
- });
- }
-
- if (object instanceof AutomationNodeImpl) {
- // Also expose attribute publicly on the wrapper.
- $Object.defineProperty(object.wrapper, attributeName, {
- enumerable: true,
- get: function() {
- return object[attributeName];
- },
- });
-
- }
- },
-
- updateNode_: function(nodeData, updateState) {
- var node = this.axNodeDataCache_[nodeData.id];
- var didUpdateRoot = false;
- if (node) {
- delete updateState.pendingNodes[privates(node).impl.id];
- } else {
- if (nodeData.role != schema.RoleType.rootWebArea &&
- nodeData.role != schema.RoleType.desktop) {
- logging.WARNING(String(nodeData.id) +
- ' is not in the cache and not the new root.');
- lastError.set('automation',
- 'Bad update received on automation tree',
- null,
- chrome);
- return false;
- }
- // |this| is an AutomationRootNodeImpl; retrieve the
- // AutomationRootNode instance instead.
- node = this.wrapper;
- didUpdateRoot = true;
- updateState.newNodes[this.id] = this.wrapper;
- }
- this.setData_(node, nodeData);
-
- // TODO(aboxhall): send onChanged event?
- logging.CHECK(node);
- if (!this.deleteOldChildren_(node, nodeData.childIds)) {
- if (didUpdateRoot) {
- this.invalidate_(this.wrapper);
- }
- return false;
- }
- var nodeImpl = privates(node).impl;
-
- var success = this.createNewChildren_(node,
- nodeData.childIds,
- updateState);
- nodeImpl.childIds = nodeData.childIds;
- this.axNodeDataCache_[nodeImpl.id] = node;
-
- return success;
- }
};
-
var AutomationNode = utils.expose('AutomationNode',
AutomationNodeImpl,
{ functions: ['doDefault',
@@ -1032,7 +831,8 @@ var AutomationNode = utils.expose('AutomationNode',
'removeEventListener',
'domQuerySelector',
'toString' ],
- readonly: ['parent',
+ readonly: publicAttributes.concat(
+ ['parent',
'firstChild',
'lastChild',
'children',
@@ -1042,13 +842,24 @@ var AutomationNode = utils.expose('AutomationNode',
'role',
'state',
'location',
- 'attributes',
'indexInParent',
- 'root'] });
+ 'root']) });
var AutomationRootNode = utils.expose('AutomationRootNode',
AutomationRootNodeImpl,
{ superclass: AutomationNode });
+AutomationRootNode.get = function(treeID) {
+ return AutomationRootNodeImpl.get(treeID);
+}
+
+AutomationRootNode.getOrCreate = function(treeID) {
+ return AutomationRootNodeImpl.getOrCreate(treeID);
+}
+
+AutomationRootNode.destroy = function(treeID) {
+ AutomationRootNodeImpl.destroy(treeID);
+}
+
exports.AutomationNode = AutomationNode;
exports.AutomationRootNode = AutomationRootNode;

Powered by Google App Engine
This is Rietveld 408576698