Chromium Code Reviews| 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 5dc6b9b527ff8c4687616bb2910614a9048dace1..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() { |
|
David Tseng
2015/07/29 16:14:40
rootWebArea are no longer hooked up properly acros
|
| - 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,374 +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 || |
| - nodeData.role == schema.RoleType.embeddedObject) { |
| - 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', |
| @@ -1033,7 +831,8 @@ var AutomationNode = utils.expose('AutomationNode', |
| 'removeEventListener', |
| 'domQuerySelector', |
| 'toString' ], |
| - readonly: ['parent', |
| + readonly: publicAttributes.concat( |
| + ['parent', |
| 'firstChild', |
| 'lastChild', |
| 'children', |
| @@ -1043,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; |