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 3f348a34bd282e26ed9ff4a1aa972604208d13e5..96fa3f5c069d0a76e23ef1518918725111d74699 100644 |
| --- a/chrome/renderer/resources/extensions/automation/automation_node.js |
| +++ b/chrome/renderer/resources/extensions/automation/automation_node.js |
| @@ -7,6 +7,20 @@ var automationInternal = |
| require('binding').Binding.create('automationInternal').generate(); |
| var IsInteractPermitted = |
| requireNative('automationInternal').IsInteractPermitted; |
| +var GetRootID = requireNative('automationInternal').GetRootID; |
| +var GetParentID = requireNative('automationInternal').GetParentID; |
| +var GetChildCount = requireNative('automationInternal').GetChildCount; |
| +var GetChildIDAtIndex = requireNative('automationInternal').GetChildIDAtIndex; |
| +var GetIndexInParent = requireNative('automationInternal').GetIndexInParent; |
| +var GetState = requireNative('automationInternal').GetState; |
| +var GetRole = requireNative('automationInternal').GetRole; |
| +var GetLocation = requireNative('automationInternal').GetLocation; |
| +var GetStringAttribute = requireNative('automationInternal').GetStringAttribute; |
| +var GetBoolAttribute = requireNative('automationInternal').GetBoolAttribute; |
| +var GetIntAttribute = requireNative('automationInternal').GetIntAttribute; |
| +var GetFloatAttribute = requireNative('automationInternal').GetFloatAttribute; |
| +var GetIntListAttribute = |
| + requireNative('automationInternal').GetIntListAttribute; |
| var lastError = require('lastError'); |
| var logging = requireNative('logging'); |
| @@ -23,13 +37,11 @@ function AutomationNodeImpl(root) { |
| this.childIds = []; |
| // Public attributes. No actual data gets set on this object. |
| this.attributes = {}; |
|
aboxhall
2015/06/11 17:42:46
Can this be removed too?
dmazzoni
2015/06/11 19:09:12
Yes! done.
|
| - // 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 +52,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 +104,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; |
| }, |
| @@ -220,7 +271,7 @@ AutomationNodeImpl.prototype = { |
| listeners[i].callback(event); |
| } catch (e) { |
| console.error('Error in event handler for ' + event.type + |
|
aboxhall
2015/06/11 17:42:46
Maybe logging.WARN here as well (I know you didn't
dmazzoni
2015/06/11 19:09:12
Done.
|
| - 'during phase ' + eventPhase + ': ' + |
| + ' during phase ' + eventPhase + ': ' + |
| e.message + '\nStack trace: ' + e.stack); |
| } |
| } |
| @@ -312,17 +363,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 +382,171 @@ 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 |
| -}; |
| +var publicAttributes = []; |
| -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 defineStringAttribute(attributeName) { |
| + publicAttributes.push(attributeName); |
| + Object.defineProperty(AutomationNodeImpl.prototype, attributeName, { |
| + get: function() { |
| + return GetStringAttribute(this.treeID, this.id, attributeName); |
| + } |
| + }); |
| } |
| -function defaultFloatAttribute(opt_defaultVal) { |
| - var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : 0; |
| - return { default: defaultVal, reflectFrom: 'floatAttributes' }; |
| +defineStringAttribute('accessKey'); |
|
aboxhall
2015/06/11 17:42:45
Maybe create arrays of each type of attribute and
aboxhall
2015/06/11 20:32:42
Ping on this.
dmazzoni
2015/06/12 17:55:37
Oops, this one's not included in the latest patch
dmazzoni
2015/06/12 18:08:00
Done now, is this better?
aboxhall
2015/06/12 20:43:10
This looks great, thanks!
|
| +defineStringAttribute('action'); |
| +defineStringAttribute('ariaInvalidValue'); |
| +defineStringAttribute('autoComplete'); |
| +defineStringAttribute('containerLiveRelevant'); |
| +defineStringAttribute('containerLiveStatus'); |
| +defineStringAttribute('description'); |
| +defineStringAttribute('display'); |
| +defineStringAttribute('docDoctype'); |
| +defineStringAttribute('docMimetype'); |
| +defineStringAttribute('docTitle'); |
| +defineStringAttribute('docUrl'); |
| +defineStringAttribute('dropeffect'); |
| +defineStringAttribute('help'); |
| +defineStringAttribute('htmlTag'); |
| +defineStringAttribute('liveRelevant'); |
| +defineStringAttribute('liveStatus'); |
| +defineStringAttribute('name'); |
| +defineStringAttribute('placeholder'); |
| +defineStringAttribute('shortcut'); |
| +defineStringAttribute('textInputType'); |
| +defineStringAttribute('url'); |
| +defineStringAttribute('value'); |
| + |
| +function defineBoolAttribute(attributeName) { |
| + publicAttributes.push(attributeName); |
| + Object.defineProperty(AutomationNodeImpl.prototype, attributeName, { |
| + get: function() { |
| + return GetBoolAttribute(this.treeID, this.id, attributeName); |
| + } |
| + }); |
| } |
| -function defaultBoolAttribute(opt_defaultVal) { |
| - var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : false; |
| - return { default: defaultVal, reflectFrom: 'boolAttributes' }; |
| +defineBoolAttribute('ariaReadonly'); |
| +defineBoolAttribute('buttonMixed'); |
| +defineBoolAttribute('canSetValue'); |
| +defineBoolAttribute('canvasHasFallback'); |
| +defineBoolAttribute('containerLiveAtomic'); |
| +defineBoolAttribute('containerLiveBusy'); |
| +defineBoolAttribute('docLoaded'); |
| +defineBoolAttribute('grabbed'); |
| +defineBoolAttribute('isAxTreeHost'); |
| +defineBoolAttribute('liveAtomic'); |
| +defineBoolAttribute('liveBusy'); |
| +defineBoolAttribute('updateLocationOnly'); |
| + |
| +function defineIntAttribute(attributeName) { |
| + publicAttributes.push(attributeName); |
| + Object.defineProperty(AutomationNodeImpl.prototype, attributeName, { |
| + get: function() { |
| + return GetIntAttribute(this.treeID, this.id, attributeName); |
| + } |
| + }); |
| } |
| -function defaultHtmlAttribute(opt_defaultVal) { |
| - var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : ''; |
| - return { default: defaultVal, reflectFrom: 'htmlAttributes' }; |
| +defineIntAttribute('backgroundColor'); |
| +defineIntAttribute('color'); |
| +defineIntAttribute('colorValue'); |
| +defineIntAttribute('hierarchicalLevel'); |
| +defineIntAttribute('invalidState'); |
| +defineIntAttribute('posInSet'); |
| +defineIntAttribute('scrollX'); |
| +defineIntAttribute('scrollXMax'); |
| +defineIntAttribute('scrollXMin'); |
| +defineIntAttribute('scrollY'); |
| +defineIntAttribute('scrollYMax'); |
| +defineIntAttribute('scrollYMin'); |
| +defineIntAttribute('setSize'); |
| +defineIntAttribute('sortDirection'); |
| +defineIntAttribute('tableCellColumnIndex'); |
| +defineIntAttribute('tableCellColumnSpan'); |
| +defineIntAttribute('tableCellRowIndex'); |
| +defineIntAttribute('tableCellRowSpan'); |
| +defineIntAttribute('tableColumnCount'); |
| +defineIntAttribute('tableColumnIndex'); |
| +defineIntAttribute('tableRowCount'); |
| +defineIntAttribute('tableRowIndex'); |
| +defineIntAttribute('textDirection'); |
| +defineIntAttribute('textSelEnd'); |
| +defineIntAttribute('textSelStart'); |
| +defineIntAttribute('textStyle'); |
| + |
| +function defineNodeRefAttribute(srcAttributeName, dstAttributeName) { |
| + 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; |
| + } |
| + }); |
| } |
| -function defaultIntListAttribute(opt_defaultVal) { |
| - var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : []; |
| - return { default: defaultVal, reflectFrom: 'intlistAttributes' }; |
| -} |
| +defineNodeRefAttribute('activedescendantId', 'activedescendant'); |
| +defineNodeRefAttribute('tableColumnHeaderId', 'tableColumnHeader'); |
| +defineNodeRefAttribute('tableHeaderId', 'tableHeader'); |
| +defineNodeRefAttribute('tableRowHeaderId', 'tableRowHeader'); |
| +defineNodeRefAttribute('titleUiElement', 'titleUIElement'); |
| -function defaultNodeRefAttribute(idAttribute, opt_defaultVal) { |
| - var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : null; |
| - return { default: defaultVal, |
| - idFrom: 'intAttributes', |
| - idAttribute: idAttribute, |
| - isRef: true }; |
| +function defineIntListAttribute(attributeName) { |
| + publicAttributes.push(attributeName); |
| + Object.defineProperty(AutomationNodeImpl.prototype, attributeName, { |
| + get: function() { |
| + return GetIntListAttribute(this.treeID, this.id, attributeName); |
| + } |
| + }); |
| } |
| -function defaultNodeRefListAttribute(idAttribute, opt_defaultVal) { |
| - var defaultVal = (opt_defaultVal !== undefined) ? opt_defaultVal : []; |
| - return { default: [], |
| - idFrom: 'intlistAttributes', |
| - idAttribute: idAttribute, |
| - isRef: true }; |
| +defineIntListAttribute('characterOffsets'); |
| +defineIntListAttribute('lineBreaks'); |
| +defineIntListAttribute('wordEnds'); |
| +defineIntListAttribute('wordStarts'); |
| + |
| +function defineNodeRefListAttribute(srcAttributeName, dstAttributeName) { |
| + 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; |
| + } |
| + }); |
| } |
| -// 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), |
| - textInputType: defaultStringAttribute() |
| -}; |
| - |
| -var RangeMixinAttributes = { |
| - valueForRange: defaultFloatAttribute(), |
| - minValueForRange: defaultFloatAttribute(), |
| - maxValueForRange: defaultFloatAttribute() |
| -}; |
| +defineNodeRefListAttribute('cellIds', 'cells'); |
| +defineNodeRefListAttribute('controlsIds', 'controls'); |
| +defineNodeRefListAttribute('describedbyIds', 'describedBy'); |
| +defineNodeRefListAttribute('flowtoIds', 'flowTo'); |
| +defineNodeRefListAttribute('labelledbyIds', 'labelledBy'); |
| +defineNodeRefListAttribute('uniqueCellIds', 'uniqueCells'); |
| -var TableMixinAttributes = { |
| - tableRowCount: defaultIntAttribute(), |
| - tableColumnCount: defaultIntAttribute() |
| -}; |
| - |
| -var TableCellMixinAttributes = { |
| - tableCellColumnIndex: defaultIntAttribute(), |
| - tableCellColumnSpan: defaultIntAttribute(1), |
| - tableCellRowIndex: defaultIntAttribute(), |
| - tableCellRowSpan: defaultIntAttribute(1) |
| -}; |
| +function defineFloatAttribute(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(), |
| -}; |
| +defineFloatAttribute('docLoadingProgress'); |
| +defineFloatAttribute('valueForRange'); |
| +defineFloatAttribute('minValueForRange'); |
| +defineFloatAttribute('maxValueForRange'); |
| +defineFloatAttribute('fontSize'); |
| /** |
| * AutomationRootNode. |
| @@ -523,90 +571,54 @@ function AutomationRootNodeImpl(treeID) { |
| this.axNodeDataCache_ = {}; |
| } |
| +AutomationRootNodeImpl.idToAutomationRootNode_ = {}; |
| + |
| +AutomationRootNodeImpl.get = function(treeID) { |
| + var result = AutomationRootNodeImpl.idToAutomationRootNode_[treeID]; |
| + if (result) |
| + return result; |
| + 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]; |
| - }, |
| - |
| - unserialize: function(update) { |
| - var updateState = { pendingNodes: {}, newNodes: {} }; |
| - var oldRootId = this.id; |
| + if (id == this.id) |
| + return this.wrapper; |
| - 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; |
| - } |
| - } |
| + var obj = this.axNodeDataCache_[id]; |
| + if (obj) |
| + return obj; |
| - for (var i = 0; i < update.nodes.length; i++) { |
| - if (!this.updateNode_(update.nodes[i], updateState)) |
| - return false; |
| - } |
| + obj = new AutomationNode(this); |
| + privates(obj).impl.treeID = this.treeID; |
| + privates(obj).impl.id = id; |
| + this.axNodeDataCache_[id] = obj; |
| - 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; |
| + return obj; |
| }, |
| destroy: function() { |
| @@ -615,15 +627,10 @@ AutomationRootNodeImpl.prototype = { |
| this.hostTree = undefined; |
| this.dispatchEvent(schema.EventType.destroyed); |
| - this.invalidate_(this.wrapper); |
| + //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 +658,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 +674,8 @@ var AutomationNode = utils.expose('AutomationNode', |
| 'removeEventListener', |
| 'domQuerySelector', |
| 'toString' ], |
| - readonly: ['parent', |
| + readonly: publicAttributes.concat( |
| + ['parent', |
| 'firstChild', |
| 'lastChild', |
| 'children', |
| @@ -1042,13 +685,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; |