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

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

Issue 1198613002: Revert of Reimplement automation API on top of C++-backed AXTree. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@automation_faster_2
Patch Set: 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 e5de7122504c8b6fac90adb8f02d587a45a0bfad..11ac8d8a3b065b5e292df2df259d81716aa3c95f 100644
--- a/chrome/renderer/resources/extensions/automation/automation_node.js
+++ b/chrome/renderer/resources/extensions/automation/automation_node.js
@@ -7,124 +7,6 @@
require('binding').Binding.create('automationInternal').generate();
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');
@@ -138,12 +20,16 @@
*/
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 },
@@ -154,51 +40,16 @@
},
get parent() {
- 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);
+ return this.hostTree || this.rootImpl.get(this.parentID);
},
get firstChild() {
- 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);
+ return this.childTree || this.rootImpl.get(this.childIds[0]);
},
get lastChild() {
- 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);
+ var childIds = this.childIds;
+ return this.childTree || this.rootImpl.get(childIds[childIds.length - 1]);
},
get children() {
@@ -206,28 +57,24 @@
return [this.childTree];
var children = [];
- 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);
+ for (var i = 0, childID; childID = this.childIds[i]; i++) {
+ logging.CHECK(this.rootImpl.get(childID));
+ children.push(this.rootImpl.get(childID));
}
return children;
},
get previousSibling() {
var parent = this.parent;
- var indexInParent = GetIndexInParent(this.treeID, this.id);
- if (parent && indexInParent > 0)
- return parent.children[indexInParent - 1];
+ if (parent && this.indexInParent > 0)
+ return parent.children[this.indexInParent - 1];
return undefined;
},
get nextSibling() {
var parent = this.parent;
- var indexInParent = GetIndexInParent(this.treeID, this.id);
- if (parent && indexInParent < parent.children.length)
- return parent.children[indexInParent + 1];
+ if (parent && this.indexInParent < parent.children.length)
+ return parent.children[this.indexInParent + 1];
return undefined;
},
@@ -323,20 +170,12 @@
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=' + parentID +
- ' childIds=' + $JSON.stringify(childIDs);
+ ' parentID=' + impl.parentID +
+ ' childIds=' + $JSON.stringify(impl.childIds) +
+ ' attributes=' + $JSON.stringify(this.attributes);
},
dispatchEventAtCapturing_: function(event, path) {
@@ -380,9 +219,9 @@
try {
listeners[i].callback(event);
} catch (e) {
- logging.WARNING('Error in event handler for ' + event.type +
- ' during phase ' + eventPhase + ': ' +
- e.message + '\nStack trace: ' + e.stack);
+ console.error('Error in event handler for ' + event.type +
+ 'during phase ' + eventPhase + ': ' +
+ e.message + '\nStack trace: ' + e.stack);
}
}
},
@@ -473,14 +312,17 @@
}
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[attribute] !== attrValue)
+ if (this.attributesInternal[attribute] !== attrValue)
return false;
} else if (attrValue instanceof RegExp) {
- if (typeof this[attribute] != 'string')
+ if (typeof this.attributesInternal[attribute] != 'string')
return false;
- if (!attrValue.test(this[attribute]))
+ if (!attrValue.test(this.attributesInternal[attribute]))
return false;
} else {
// TODO(aboxhall): handle intlist case.
@@ -492,196 +334,171 @@
}
};
-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;
- }
- });
-});
-
-floatAttributes.forEach(function (attributeName) {
- publicAttributes.push(attributeName);
- Object.defineProperty(AutomationNodeImpl.prototype, attributeName, {
- get: function() {
- return GetFloatAttribute(this.treeID, this.id, attributeName);
- }
- });
-});
-
-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);
- }
- });
-});
+// 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 TableCellMixinAttributes = {
+ tableCellColumnIndex: defaultIntAttribute(),
+ tableCellColumnSpan: defaultIntAttribute(1),
+ tableCellRowIndex: defaultIntAttribute(),
+ tableCellRowSpan: defaultIntAttribute(1)
+};
+
+var LiveRegionMixinAttributes = {
+ containerLiveAtomic: defaultBoolAttribute(),
+ containerLiveBusy: defaultBoolAttribute(),
+ containerLiveRelevant: defaultStringAttribute(),
+ containerLiveStatus: defaultStringAttribute(),
+};
/**
* AutomationRootNode.
@@ -706,88 +523,107 @@
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;
- if (id == this.id)
- return this.wrapper;
-
- var obj = this.axNodeDataCache_[id];
- if (obj)
- return obj;
-
- obj = new AutomationNode(this);
- privates(obj).impl.treeID = this.treeID;
- privates(obj).impl.id = id;
- this.axNodeDataCache_[id] = obj;
-
- return obj;
- },
-
- remove: function(id) {
- delete this.axNodeDataCache_[id];
+ return this.axNodeDataCache_[id];
+ },
+
+ unserialize: function(update) {
+ var updateState = { pendingNodes: {}, newNodes: {} };
+ var oldRootId = this.id;
+
+ 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;
+ }
+
+ // 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 true;
},
destroy: function() {
+ if (this.hostTree)
+ this.hostTree.childTree = undefined;
+ this.hostTree = undefined;
+
this.dispatchEvent(schema.EventType.destroyed);
- },
-
- setHostNode(hostNode) {
- this.hostNode_ = hostNode;
+ this.invalidate_(this.wrapper);
},
onAccessibilityEvent: function(eventParams) {
+ if (!this.unserialize(eventParams.update)) {
+ logging.WARNING('unserialization failed');
+ return false;
+ }
+
var targetNode = this.get(eventParams.targetID);
if (targetNode) {
var targetNodeImpl = privates(targetNode).impl;
@@ -815,7 +651,372 @@
}
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,
@@ -831,8 +1032,7 @@
'removeEventListener',
'domQuerySelector',
'toString' ],
- readonly: publicAttributes.concat(
- ['parent',
+ readonly: ['parent',
'firstChild',
'lastChild',
'children',
@@ -842,24 +1042,13 @@
'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