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..2e80cddeb3fa627ed1eb008c264fa70da96068f9 100644 |
--- a/chrome/renderer/resources/extensions/automation/automation_node.js |
+++ b/chrome/renderer/resources/extensions/automation/automation_node.js |
@@ -8,6 +8,115 @@ 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; |
+ |
var lastError = require('lastError'); |
var logging = requireNative('logging'); |
var schema = requireNative('automationInternal').GetSchemaAdditions(); |
@@ -20,16 +129,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 +145,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 +197,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 +314,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 +371,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 +464,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 +483,182 @@ 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 TableCellMixinAttributes = { |
- tableCellColumnIndex: defaultIntAttribute(), |
- tableCellColumnSpan: defaultIntAttribute(1), |
- tableCellRowIndex: defaultIntAttribute(), |
- tableCellRowSpan: defaultIntAttribute(1) |
-}; |
+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 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 LiveRegionMixinAttributes = { |
- containerLiveAtomic: defaultBoolAttribute(), |
- containerLiveBusy: defaultBoolAttribute(), |
- containerLiveRelevant: defaultStringAttribute(), |
- containerLiveStatus: defaultStringAttribute(), |
-}; |
+floatAttributes.forEach(function (attributeName) { |
+ publicAttributes.push(attributeName); |
+ Object.defineProperty(AutomationNodeImpl.prototype, attributeName, { |
+ get: function() { |
+ return GetFloatAttribute(this.treeID, this.id, attributeName); |
+ } |
+ }); |
+}); |
/** |
* AutomationRootNode. |
@@ -523,90 +683,58 @@ function AutomationRootNodeImpl(treeID) { |
this.axNodeDataCache_ = {}; |
} |
+AutomationRootNodeImpl.idToAutomationRootNode_ = {}; |
+ |
+AutomationRootNodeImpl.get = function(treeID) { |
+ var result = AutomationRootNodeImpl.idToAutomationRootNode_[treeID]; |
+ if (result) |
+ return result; |
David Tseng
2015/06/15 21:02:11
nit: return result || undefined;
dmazzoni
2015/06/16 18:19:49
Done.
|
+ return 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, |
isRootNode: true, |
treeID: -1, |
+ 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; |
- } |
- } |
+ obj = new AutomationNode(this); |
+ privates(obj).impl.treeID = this.treeID; |
+ privates(obj).impl.id = id; |
+ this.axNodeDataCache_[id] = obj; |
- 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 obj; |
+ }, |
- return true; |
+ remove: function(id) { |
+ delete this.axNodeDataCache_[id]; |
}, |
destroy: function() { |
@@ -615,15 +743,9 @@ AutomationRootNodeImpl.prototype = { |
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; |
- } |
- |
var targetNode = this.get(eventParams.targetID); |
if (targetNode) { |
var targetNodeImpl = privates(targetNode).impl; |
@@ -651,373 +773,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 +789,8 @@ var AutomationNode = utils.expose('AutomationNode', |
'removeEventListener', |
'domQuerySelector', |
'toString' ], |
- readonly: ['parent', |
+ readonly: publicAttributes.concat( |
+ ['parent', |
'firstChild', |
'lastChild', |
'children', |
@@ -1042,13 +800,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; |