blob: 0383f339085d0b1a68957335d3ae823fa1d31e48 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var AutomationEvent = require('automationEvent').AutomationEvent;
var automationInternal = getInternalApi('automationInternal');
var exceptionHandler = require('uncaught_exception_handler');
var natives = requireNative('automationInternal');
var IsInteractPermitted = natives.IsInteractPermitted;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @return {?number} The id of the root node.
*/
var GetRootID = natives.GetRootID;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @return {?string} The title of the document.
*/
var GetDocTitle = natives.GetDocTitle;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @return {?string} The url of the document.
*/
var GetDocURL = natives.GetDocURL;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @return {?boolean} True if the document has finished loading.
*/
var GetDocLoaded = natives.GetDocLoaded;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @return {?number} The loading progress, from 0.0 to 1.0 (fully loaded).
*/
var GetDocLoadingProgress =
natives.GetDocLoadingProgress;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @return {?number} The ID of the selection anchor object.
*/
var GetAnchorObjectID = natives.GetAnchorObjectID;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @return {?number} The selection anchor offset.
*/
var GetAnchorOffset = natives.GetAnchorOffset;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @return {?string} The selection anchor affinity.
*/
var GetAnchorAffinity = natives.GetAnchorAffinity;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @return {?number} The ID of the selection focus object.
*/
var GetFocusObjectID = natives.GetFocusObjectID;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @return {?number} The selection focus offset.
*/
var GetFocusOffset = natives.GetFocusOffset;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @return {?string} The selection focus affinity.
*/
var GetFocusAffinity = natives.GetFocusAffinity;
/**
* @param {string} 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 = natives.GetParentID;
/**
* @param {string} 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 = natives.GetChildCount;
/**
* @param {string} 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 = natives.GetChildIDAtIndex;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @return {?number} The ids of the children of the node, or undefined
* if the tree or node wasn't found.
*/
var GetChildIds = natives.GetChildIDs;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @return {?Object} An object mapping html attributes to values.
*/
var GetHtmlAttributes = natives.GetHtmlAttributes;
/**
* @param {string} 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 = natives.GetIndexInParent;
/**
* @param {string} 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 = natives.GetState;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @return {string} The restriction, one of
* "disabled", "readOnly" or undefined if enabled or other object not disabled
*/
var GetRestriction = natives.GetRestriction;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @return {string} The checked state, as undefined, "true", "false" or "mixed".
*/
var GetChecked = natives.GetChecked;
/**
* @param {string} 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 = natives.GetRole;
/**
* @param {string} 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 = natives.GetLocation;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @param {number} startIndex The start index of the range.
* @param {number} endIndex The end index of the range.
* @return {?automation.Rect} The bounding box of the subrange of this node,
* or the location if there are no subranges, or undefined if
* the tree or node wasn't found.
*/
var GetBoundsForRange = natives.GetBoundsForRange;
/**
* @param {number} left The left location of the text range.
* @param {number} top The top location of the text range.
* @param {number} width The width of text range.
* @param {number} height The height of the text range.
* @param {number} requestID The request id associated with the query
* for this range.
* @return {?automation.Rect} The bounding box of the subrange of this node,
* specified by arguments provided to the function.
*/
var ComputeGlobalBounds = natives.ComputeGlobalBounds;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @return {?automation.Rect} The unclipped location of the node, or
* undefined if the tree or node wasn't found.
*/
var GetUnclippedLocation = natives.GetUnclippedLocation;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @return {!Array<number>} The text offset where each line starts, or an empty
* array if this node has no text content, or undefined if the tree or node
* was not found.
*/
var GetLineStartOffsets = requireNative(
'automationInternal').GetLineStartOffsets;
/**
* @param {string} 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 = natives.GetStringAttribute;
/**
* @param {string} 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 = natives.GetBoolAttribute;
/**
* @param {string} 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 = natives.GetIntAttribute;
/**
* @param {string} 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 ids of nodes who have a relationship pointing
* to |nodeID| (a reverse relationship).
*/
var GetIntAttributeReverseRelations =
natives.GetIntAttributeReverseRelations;
/**
* @param {string} 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 = natives.GetFloatAttribute;
/**
* @param {string} 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 =
natives.GetIntListAttribute;
/**
* @param {string} 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 ids of nodes who have a relationship pointing
* to |nodeID| (a reverse relationship).
*/
var GetIntListAttributeReverseRelations =
natives.GetIntListAttributeReverseRelations;
/**
* @param {string} 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 = natives.GetHtmlAttribute;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @return {automation.NameFromType} The source of the node's name.
*/
var GetNameFrom = natives.GetNameFrom;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @return {boolean}
*/
var GetBold = natives.GetBold;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @return {boolean}
*/
var GetItalic = natives.GetItalic;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @return {boolean}
*/
var GetUnderline = natives.GetUnderline;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @return {boolean}
*/
var GetLineThrough = natives.GetLineThrough;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @return {?Array<automation.CustomAction>} List of custom actions of the
* node.
*/
var GetCustomActions = natives.GetCustomActions;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @return {?Array<string>} List of standard actions of the node.
*/
var GetStandardActions = natives.GetStandardActions;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @return {automation.NameFromType} The source of the node's name.
*/
var GetDefaultActionVerb = natives.GetDefaultActionVerb;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @param {string} searchStr
* @param {boolean} backward
* @return {{treeId: string, nodeId: number}}
*/
var GetNextTextMatch = natives.GetNextTextMatch;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @return {?Array<number>} A list of column header ids.
* @return {?number} The id of the column header, if it exists.
*/
var GetTableCellColumnHeaders = natives.GetTableCellColumnHeaders;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @return {?Array<number>} A list of row header ids.
*/
var GetTableCellRowHeaders = natives.GetTableCellRowHeaders;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @return {number} Column index for this cell.
*/
var GetTableCellColumnIndex = natives.GetTableCellColumnIndex;
/**
* @param {string} axTreeID The id of the accessibility tree.
* @param {number} nodeID The id of a node.
* @return {number} Row index for this cell.
*/
var GetTableCellRowIndex = natives.GetTableCellRowIndex;
var logging = requireNative('logging');
var utils = require('utils');
/**
* A single node in the Automation tree.
* @param {AutomationRootNodeImpl} root The root of the tree.
* @constructor
*/
function AutomationNodeImpl(root) {
this.rootImpl = root;
this.listeners = {__proto__: null};
}
AutomationNodeImpl.prototype = {
__proto__: null,
treeID: '',
id: -1,
isRootNode: false,
detach: function() {
this.rootImpl = null;
this.listeners = {__proto__: null};
},
get root() {
return this.rootImpl && this.rootImpl.wrapper;
},
get parent() {
var info = GetParentID(this.treeID, this.id);
if (!info)
return;
return AutomationRootNodeImpl.getNodeFromTree(info.treeId, info.nodeId);
},
get htmlAttributes() {
return GetHtmlAttributes(this.treeID, this.id) || {};
},
get state() {
return GetState(this.treeID, this.id) || {};
},
get role() {
return GetRole(this.treeID, this.id);
},
get restriction() {
return GetRestriction(this.treeID, this.id);
},
get checked() {
return GetChecked(this.treeID, this.id);
},
get location() {
return GetLocation(this.treeID, this.id);
},
boundsForRange: function(startIndex, endIndex, callback) {
if (!this.rootImpl)
return;
// Not yet initialized.
if (this.rootImpl.treeID === undefined || this.id === undefined) {
return;
}
if (!callback)
return;
if (!GetBoolAttribute(this.treeID, this.id, 'supportsTextLocation')) {
try {
callback(
GetBoundsForRange(this.treeID, this.id, startIndex, endIndex));
return;
} catch (e) {
logging.WARNING('Error with bounds for range callback' + e);
}
return;
}
this.performAction_(
'getTextLocation', {startIndex: startIndex, endIndex: endIndex},
callback);
return;
},
get unclippedLocation() {
var result = GetUnclippedLocation(this.treeID, this.id);
if (result === undefined)
result = GetLocation(this.treeID, this.id);
return result;
},
get indexInParent() {
return GetIndexInParent(this.treeID, this.id);
},
get lineStartOffsets() {
return GetLineStartOffsets(this.treeID, this.id);
},
get childTree() {
var childTreeID = GetStringAttribute(this.treeID, this.id, 'childTreeId');
if (childTreeID)
return AutomationRootNodeImpl.get(childTreeID);
},
get firstChild() {
if (GetChildCount(this.treeID, this.id) == 0)
return undefined;
var info = GetChildIDAtIndex(this.treeID, this.id, 0);
if (info)
return AutomationRootNodeImpl.getNodeFromTree(info.treeId, info.nodeId);
},
get lastChild() {
var count = GetChildCount(this.treeID, this.id);
if (count == 0)
return;
var info = GetChildIDAtIndex(this.treeID, this.id, count - 1);
if (info)
return AutomationRootNodeImpl.getNodeFromTree(info.treeId, info.nodeId);
},
get children() {
var info = GetChildIds(this.treeID, this.id);
if (!info)
return [];
var children = [];
for (var i = 0; i < info.nodeIds.length; ++i) {
var childID = info.nodeIds[i];
var child = AutomationRootNodeImpl.getNodeFromTree(info.treeId, childID);
if (child)
$Array.push(children, child);
}
return children;
},
get previousSibling() {
var parent = this.parent;
if (!parent)
return undefined;
parent = privates(parent).impl;
var indexInParent = GetIndexInParent(this.treeID, this.id);
var info = GetChildIDAtIndex(parent.treeID, parent.id, indexInParent - 1);
if (info)
return AutomationRootNodeImpl.getNodeFromTree(info.treeId, info.nodeId);
},
get nextSibling() {
var parent = this.parent;
if (!parent)
return undefined;
parent = privates(parent).impl;
var indexInParent = GetIndexInParent(this.treeID, this.id);
var info = GetChildIDAtIndex(parent.treeID, parent.id, indexInParent + 1);
if (info)
return AutomationRootNodeImpl.getNodeFromTree(info.treeId, info.nodeId);
},
get nameFrom() {
return GetNameFrom(this.treeID, this.id);
},
get bold() {
return GetBold(this.treeID, this.id);
},
get italic() {
return GetItalic(this.treeID, this.id);
},
get underline() {
return GetUnderline(this.treeID, this.id);
},
get lineThrough() {
return GetLineThrough(this.treeID, this.id);
},
get customActions() {
return GetCustomActions(this.treeID, this.id);
},
get standardActions() {
return GetStandardActions(this.treeID, this.id);
},
get defaultActionVerb() {
return GetDefaultActionVerb(this.treeID, this.id);
},
get tableCellColumnHeaders() {
var ids = GetTableCellColumnHeaders(this.treeID, this.id);
if (ids && this.rootImpl) {
var result = [];
for (var i = 0; i < ids.length; i++)
result.push(this.rootImpl.get(ids[i]));
return result;
}
},
get tableCellRowHeaders() {
var id = GetTableCellRowHeaders(this.treeID, this.id);
if (ids && this.rootImpl) {
var result = [];
for (var i = 0; i < ids.length; i++)
result.push(this.rootImpl.get(ids[i]));
return result;
}
},
get tableCellColumnIndex() {
return GetTableCellColumnIndex(this.treeID, this.id);
},
get tableCellRowIndex() {
return GetTableCellRowIndex(this.treeID, this.id);
},
doDefault: function() {
this.performAction_('doDefault');
},
focus: function() {
this.performAction_('focus');
},
getImageData: function(maxWidth, maxHeight) {
this.performAction_('getImageData',
{ maxWidth: maxWidth,
maxHeight: maxHeight });
},
hitTest: function(x, y, eventToFire) {
this.hitTestInternal(x, y, eventToFire);
},
hitTestWithReply: function(x, y, opt_callback) {
this.hitTestInternal(x, y, 'hitTestResult', opt_callback);
},
hitTestInternal: function(x, y, eventToFire, opt_callback) {
// Convert from global to tree-relative coordinates.
var location = GetLocation(this.treeID, GetRootID(this.treeID));
this.performAction_('hitTest',
{ x: Math.floor(x - location.left),
y: Math.floor(y - location.top),
eventToFire: eventToFire },
opt_callback);
},
makeVisible: function() {
this.performAction_('scrollToMakeVisible');
},
performCustomAction: function(customActionId) {
this.performAction_('customAction', { customActionID: customActionId });
},
performStandardAction: function(action) {
var standardActions = GetStandardActions(this.treeID, this.id);
if (!standardActions ||
!standardActions.find(item => action == item)) {
throw 'Inapplicable action for node: ' + action;
}
this.performAction_(action);
},
replaceSelectedText: function(value) {
if (this.state.editable) {
this.performAction_('replaceSelectedText', { value: value});
}
},
resumeMedia: function() {
this.performAction_('resumeMedia');
},
scrollBackward: function(opt_callback) {
this.performAction_('scrollBackward', {}, opt_callback);
},
scrollForward: function(opt_callback) {
this.performAction_('scrollForward', {}, opt_callback);
},
scrollUp: function(opt_callback) {
this.performAction_('scrollUp', {}, opt_callback);
},
scrollDown: function(opt_callback) {
this.performAction_('scrollDown', {}, opt_callback);
},
scrollLeft: function(opt_callback) {
this.performAction_('scrollLeft', {}, opt_callback);
},
scrollRight: function(opt_callback) {
this.performAction_('scrollRight', {}, opt_callback);
},
setSelection: function(startIndex, endIndex) {
if (this.state.editable) {
this.performAction_('setSelection',
{ focusNodeID: this.id,
anchorOffset: startIndex,
focusOffset: endIndex });
}
},
setSequentialFocusNavigationStartingPoint: function() {
this.performAction_('setSequentialFocusNavigationStartingPoint');
},
setValue: function(value) {
if (this.state.editable) {
this.performAction_('setValue', { value: value});
}
},
showContextMenu: function() {
this.performAction_('showContextMenu');
},
startDuckingMedia: function() {
this.performAction_('startDuckingMedia');
},
stopDuckingMedia: function() {
this.performAction_('stopDuckingMedia');
},
suspendMedia: function() {
this.performAction_('suspendMedia');
},
domQuerySelector: function(selector, callback) {
if (!this.rootImpl)
callback();
automationInternal.querySelector(
{ treeID: this.rootImpl.treeID,
automationNodeID: this.id,
selector: selector },
$Function.bind(this.domQuerySelectorCallback_, this, callback));
},
find: function(params) {
return this.findInternal_(params);
},
findAll: function(params) {
return this.findInternal_(params, []);
},
matches: function(params) {
return this.matchInternal_(params);
},
getNextTextMatch: function(searchStr, backward) {
var info = GetNextTextMatch(this.treeID, this.id, searchStr, backward);
if (!info)
return;
var impl = privates(AutomationRootNodeImpl.get(info.treeId)).impl;
if (impl)
return impl.get(info.nodeId);
},
addEventListener: function(eventType, callback, capture) {
this.removeEventListener(eventType, callback);
if (!this.listeners[eventType])
this.listeners[eventType] = [];
$Array.push(this.listeners[eventType], {
__proto__: null,
callback: callback,
capture: !!capture,
});
},
// TODO(dtseng/aboxhall): Check this impl against spec.
removeEventListener: function(eventType, callback) {
if (this.listeners[eventType]) {
var listeners = this.listeners[eventType];
for (var i = 0; i < listeners.length; i++) {
if (callback === listeners[i].callback)
$Array.splice(listeners, i, 1);
}
}
},
toJSON: function() {
return { treeID: this.treeID,
id: this.id,
role: this.role,
attributes: this.attributes };
},
dispatchEvent: function(eventType, eventFrom, mouseX, mouseY) {
var path = [];
var parent = this.parent;
while (parent) {
$Array.push(path, parent);
parent = parent.parent;
}
var event = new AutomationEvent(eventType, this.wrapper, eventFrom);
event.mouseX = mouseX;
event.mouseY = mouseY;
// Dispatch the event through the propagation path in three phases:
// - capturing: starting from the root and going down to the target's parent
// - targeting: dispatching the event on the target itself
// - bubbling: starting from the target's parent, going back up to the root.
// At any stage, a listener may call stopPropagation() on the event, which
// will immediately stop event propagation through this path.
if (this.dispatchEventAtCapturing_(event, path)) {
if (this.dispatchEventAtTargeting_(event, path))
this.dispatchEventAtBubbling_(event, path);
}
},
toString: function() {
var parentID = GetParentID(this.treeID, this.id);
parentID = parentID ? parentID.nodeId : null;
var childTreeID = GetStringAttribute(this.treeID, this.id, 'childTreeId');
var count = GetChildCount(this.treeID, this.id);
var childIDs = [];
for (var i = 0; i < count; ++i) {
var childID = GetChildIDAtIndex(this.treeID, this.id, i).nodeId;
$Array.push(childIDs, childID);
}
var name = GetStringAttribute(this.treeID, this.id, 'name');
var result = 'node id=' + this.id +
' role=' + this.role +
' state=' + $JSON.stringify(this.state) +
' parentID=' + parentID +
' childIds=' + $JSON.stringify(childIDs);
if (childTreeID)
result += ' childTreeID=' + childTreeID;
if (name)
result += ' name=' + name;
return result;
},
dispatchEventAtCapturing_: function(event, path) {
privates(event).impl.eventPhase = Event.CAPTURING_PHASE;
for (var i = path.length - 1; i >= 0; i--) {
this.fireEventListeners_(path[i], event);
if (privates(event).impl.propagationStopped)
return false;
}
return true;
},
dispatchEventAtTargeting_: function(event) {
privates(event).impl.eventPhase = Event.AT_TARGET;
this.fireEventListeners_(this.wrapper, event);
return !privates(event).impl.propagationStopped;
},
dispatchEventAtBubbling_: function(event, path) {
privates(event).impl.eventPhase = Event.BUBBLING_PHASE;
for (var i = 0; i < path.length; i++) {
this.fireEventListeners_(path[i], event);
if (privates(event).impl.propagationStopped)
return false;
}
return true;
},
fireEventListeners_: function(node, event) {
var nodeImpl = privates(node).impl;
if (!nodeImpl.rootImpl)
return;
var listeners = nodeImpl.listeners[event.type];
if (!listeners)
return;
var eventPhase = event.eventPhase;
for (var i = 0; i < listeners.length; i++) {
if (eventPhase == Event.CAPTURING_PHASE && !listeners[i].capture)
continue;
if (eventPhase == Event.BUBBLING_PHASE && listeners[i].capture)
continue;
try {
listeners[i].callback(event);
} catch (e) {
exceptionHandler.handle('Error in event handler for ' + event.type +
' during phase ' + eventPhase, e);
}
}
},
performAction_: function(actionType, opt_args, opt_callback) {
if (!this.rootImpl)
return;
// Not yet initialized.
if (this.rootImpl.treeID === undefined ||
this.id === undefined) {
return;
}
// Check permissions.
if (!IsInteractPermitted()) {
throw new Error(actionType + ' requires {"desktop": true} or' +
' {"interact": true} in the "automation" manifest key.');
}
var requestID = -1;
if (opt_callback) {
requestID = this.rootImpl.addActionResultCallback(opt_callback);
}
automationInternal.performAction({ treeID: this.rootImpl.treeID,
automationNodeID: this.id,
actionType: actionType,
requestID: requestID},
opt_args || {});
},
domQuerySelectorCallback_: function(userCallback, resultAutomationNodeID) {
// resultAutomationNodeID could be zero or undefined or (unlikely) null;
// they all amount to the same thing here, which is that no node was
// returned.
if (!resultAutomationNodeID || !this.rootImpl) {
userCallback(null);
return;
}
var resultNode = this.rootImpl.get(resultAutomationNodeID);
if (!resultNode) {
logging.WARNING('Query selector result not in tree: ' +
resultAutomationNodeID);
userCallback(null);
}
userCallback(resultNode);
},
findInternal_: function(params, opt_results) {
var result = null;
this.forAllDescendants_(function(node) {
if (privates(node).impl.matchInternal_(params)) {
if (opt_results)
$Array.push(opt_results, node);
else
result = node;
return !opt_results;
}
});
if (opt_results)
return opt_results;
return result;
},
/**
* Executes a closure for all of this node's descendants, in pre-order.
* Early-outs if the closure returns true.
* @param {Function(AutomationNode):boolean} closure Closure to be executed
* for each node. Return true to early-out the traversal.
*/
forAllDescendants_: function(closure) {
var stack = $Array.reverse(this.wrapper.children);
while (stack.length > 0) {
var node = $Array.pop(stack);
if (closure(node))
return;
var children = node.children;
for (var i = children.length - 1; i >= 0; i--)
$Array.push(stack, children[i]);
}
},
matchInternal_: function(params) {
if ($Object.keys(params).length === 0)
return false;
if ('role' in params && this.role != params.role)
return false;
if ('state' in params) {
for (var state in params.state) {
if (params.state[state] != (state in this.state))
return false;
}
}
if ('attributes' in params) {
for (var attribute in params.attributes) {
var attrValue = params.attributes[attribute];
if (typeof attrValue != 'object') {
if (this[attribute] !== attrValue)
return false;
} else if (attrValue instanceof $RegExp.self) {
if (typeof this[attribute] != 'string')
return false;
if (!attrValue.test(this[attribute]))
return false;
} else {
// TODO(aboxhall): handle intlist case.
return false;
}
}
}
return true;
}
};
var stringAttributes = [
'accessKey',
'ariaInvalidValue',
'autoComplete',
'className',
'containerLiveRelevant',
'containerLiveStatus',
'description',
'display',
'htmlTag',
'imageDataUrl',
'innerHtml',
'language',
'liveRelevant',
'liveStatus',
'name',
'placeholder',
'roleDescription',
'textInputType',
'url',
'value'];
var boolAttributes = [
'busy', 'clickable', 'containerLiveAtomic', 'containerLiveBusy', 'liveAtomic',
'modal', 'scrollable', 'selected', 'supportsTextLocation'
];
var intAttributes = [
'backgroundColor',
'color',
'colorValue',
'hierarchicalLevel',
'posInSet',
'scrollX',
'scrollXMax',
'scrollXMin',
'scrollY',
'scrollYMax',
'scrollYMin',
'setSize',
'ariaCellColumnIndex',
'tableCellColumnSpan',
'ariaCellRowIndex',
'tableCellRowSpan',
'tableColumnCount',
'ariaColumnCount',
'tableColumnIndex',
'tableRowCount',
'ariaRowCount',
'tableRowIndex',
'textSelEnd',
'textSelStart'];
// Int attribute, relation property to expose, reverse relation to expose.
var nodeRefAttributes = [
['activedescendantId', 'activeDescendant', 'activeDescendantFor'],
['detailsId', 'details', 'detailsFor'],
['errorMessageId', 'errorMessage', 'errorMessageFor'],
['inPageLinkTargetId', 'inPageLinkTarget', null],
['nextFocusId', 'nextFocus', null],
['nextOnLineId', 'nextOnLine', null],
['previousFocusId', 'previousFocus', null],
['previousOnLineId', 'previousOnLine', null],
['tableColumnHeaderId', 'tableColumnHeader', null],
['tableHeaderId', 'tableHeader', null],
['tableRowHeaderId', 'tableRowHeader', null]];
var intListAttributes = [
'lineBreaks',
'markerEnds',
'markerStarts',
'markerTypes',
'wordEnds',
'wordStarts'];
// Intlist attribute, relation property to expose, reverse relation to expose.
var nodeRefListAttributes = [
['controlsIds', 'controls', 'controlledBy'],
['describedbyIds', 'describedBy', 'descriptionFor'],
['flowtoIds', 'flowTo', 'flowFrom'],
['labelledbyIds', 'labelledBy', 'labelFor']];
var floatAttributes = [
'valueForRange',
'minValueForRange',
'maxValueForRange'];
var htmlAttributes = [
['type', 'inputType']];
var publicAttributes = [];
$Array.forEach(stringAttributes, function(attributeName) {
$Array.push(publicAttributes, attributeName);
$Object.defineProperty(AutomationNodeImpl.prototype, attributeName, {
__proto__: null,
get: function() {
return GetStringAttribute(this.treeID, this.id, attributeName);
}
});
});
$Array.forEach(boolAttributes, function(attributeName) {
$Array.push(publicAttributes, attributeName);
$Object.defineProperty(AutomationNodeImpl.prototype, attributeName, {
__proto__: null,
get: function() {
return GetBoolAttribute(this.treeID, this.id, attributeName);
}
});
});
$Array.forEach(intAttributes, function(attributeName) {
$Array.push(publicAttributes, attributeName);
$Object.defineProperty(AutomationNodeImpl.prototype, attributeName, {
__proto__: null,
get: function() {
return GetIntAttribute(this.treeID, this.id, attributeName);
}
});
});
$Array.forEach(nodeRefAttributes, function(params) {
var srcAttributeName = params[0];
var dstAttributeName = params[1];
var dstReverseAttributeName = params[2];
$Array.push(publicAttributes, dstAttributeName);
$Object.defineProperty(AutomationNodeImpl.prototype, dstAttributeName, {
__proto__: null,
get: function() {
var id = GetIntAttribute(this.treeID, this.id, srcAttributeName);
if (id && this.rootImpl)
return this.rootImpl.get(id);
else
return undefined;
}
});
if (dstReverseAttributeName) {
$Array.push(publicAttributes, dstReverseAttributeName);
$Object.defineProperty(AutomationNodeImpl.prototype,
dstReverseAttributeName, {
__proto__: null,
get: function() {
var ids = GetIntAttributeReverseRelations(
this.treeID, this.id, srcAttributeName);
if (!ids || !this.rootImpl)
return undefined;
var result = [];
for (var i = 0; i < ids.length; ++i) {
var node = this.rootImpl.get(ids[i]);
if (node)
$Array.push(result, node);
}
return result;
}
});
}
});
$Array.forEach(intListAttributes, function(attributeName) {
$Array.push(publicAttributes, attributeName);
$Object.defineProperty(AutomationNodeImpl.prototype, attributeName, {
__proto__: null,
get: function() {
return GetIntListAttribute(this.treeID, this.id, attributeName);
}
});
});
$Array.forEach(nodeRefListAttributes, function(params) {
var srcAttributeName = params[0];
var dstAttributeName = params[1];
var dstReverseAttributeName = params[2];
$Array.push(publicAttributes, dstAttributeName);
$Object.defineProperty(AutomationNodeImpl.prototype, dstAttributeName, {
__proto__: null,
get: function() {
var ids = GetIntListAttribute(this.treeID, this.id, srcAttributeName);
if (!ids || !this.rootImpl)
return undefined;
var result = [];
for (var i = 0; i < ids.length; ++i) {
var node = this.rootImpl.get(ids[i]);
if (node)
$Array.push(result, node);
}
return result;
}
});
if (dstReverseAttributeName) {
$Array.push(publicAttributes, dstReverseAttributeName);
$Object.defineProperty(AutomationNodeImpl.prototype,
dstReverseAttributeName, {
__proto__: null,
get: function() {
var ids = GetIntListAttributeReverseRelations(
this.treeID, this.id, srcAttributeName);
if (!ids || !this.rootImpl)
return undefined;
var result = [];
for (var i = 0; i < ids.length; ++i) {
var node = this.rootImpl.get(ids[i]);
if (node)
$Array.push(result, node);
}
return result;
}
});
}
});
$Array.forEach(floatAttributes, function(attributeName) {
$Array.push(publicAttributes, attributeName);
$Object.defineProperty(AutomationNodeImpl.prototype, attributeName, {
__proto__: null,
get: function() {
return GetFloatAttribute(this.treeID, this.id, attributeName);
}
});
});
$Array.forEach(htmlAttributes, function(params) {
var srcAttributeName = params[0];
var dstAttributeName = params[1];
$Array.push(publicAttributes, dstAttributeName);
$Object.defineProperty(AutomationNodeImpl.prototype, dstAttributeName, {
__proto__: null,
get: function() {
return GetHtmlAttribute(this.treeID, this.id, srcAttributeName);
}
});
});
/**
* AutomationRootNode.
*
* An AutomationRootNode is the javascript end of an AXTree living in the
* browser. AutomationRootNode handles unserializing incremental updates from
* the source AXTree. Each update contains node data that form a complete tree
* after applying the update.
*
* A brief note about ids used through this class. The source AXTree assigns
* unique ids per node and we use these ids to build a hash to the actual
* AutomationNode object.
* Thus, tree traversals amount to a lookup in our hash.
*
* The tree itself is identified by the accessibility tree id of the
* renderer widget host.
* @constructor
*/
function AutomationRootNodeImpl(treeID) {
$Function.call(AutomationNodeImpl, this, this);
this.treeID = treeID;
this.axNodeDataCache_ = {__proto__: null};
}
utils.defineProperty(AutomationRootNodeImpl, 'idToAutomationRootNode_',
{__proto__: null});
utils.defineProperty(AutomationRootNodeImpl, 'get', function(treeID) {
var result = AutomationRootNodeImpl.idToAutomationRootNode_[treeID];
return result || undefined;
});
utils.defineProperty(AutomationRootNodeImpl, 'getOrCreate', function(treeID) {
if (AutomationRootNodeImpl.idToAutomationRootNode_[treeID])
return AutomationRootNodeImpl.idToAutomationRootNode_[treeID];
var result = new AutomationRootNode(treeID);
AutomationRootNodeImpl.idToAutomationRootNode_[treeID] = result;
return result;
});
utils.defineProperty(
AutomationRootNodeImpl, 'getNodeFromTree', function(treeId, nodeId) {
var tree = AutomationRootNodeImpl.get(treeId);
if (!tree)
return;
var impl = privates(tree).impl;
if (impl)
return impl.get(nodeId);
});
utils.defineProperty(AutomationRootNodeImpl, 'destroy', function(treeID) {
delete AutomationRootNodeImpl.idToAutomationRootNode_[treeID];
});
/**
* A counter keeping track of IDs to use for mapping action requests to
* their callback function.
*/
AutomationRootNodeImpl.actionRequestCounter = 0;
/**
* A map from a request ID to the corresponding callback function to call
* when the action response event is received.
*/
AutomationRootNodeImpl.actionRequestIDToCallback = {};
AutomationRootNodeImpl.prototype = {
__proto__: AutomationNodeImpl.prototype,
/**
* @type {boolean}
*/
isRootNode: true,
/**
* @type {string}
*/
treeID: '',
/**
* A map from id to AutomationNode.
* @type {Object.<number, AutomationNode>}
* @private
*/
axNodeDataCache_: null,
get id() {
var result = GetRootID(this.treeID);
// Don't return undefined, because the id is often passed directly
// as an argument to a native binding that expects only a valid number.
if (result === undefined)
return -1;
return result;
},
get docUrl() {
return GetDocURL(this.treeID);
},
get docTitle() {
return GetDocTitle(this.treeID);
},
get docLoaded() {
return GetDocLoaded(this.treeID);
},
get docLoadingProgress() {
return GetDocLoadingProgress(this.treeID);
},
get anchorObject() {
var id = GetAnchorObjectID(this.treeID);
if (id && id != -1)
return this.get(id);
else
return undefined;
},
get anchorOffset() {
var id = GetAnchorObjectID(this.treeID);
if (id && id != -1)
return GetAnchorOffset(this.treeID);
},
get anchorAffinity() {
var id = GetAnchorObjectID(this.treeID);
if (id && id != -1)
return GetAnchorAffinity(this.treeID);
},
get focusObject() {
var id = GetFocusObjectID(this.treeID);
if (id && id != -1)
return this.get(id);
else
return undefined;
},
get focusOffset() {
var id = GetFocusObjectID(this.treeID);
if (id && id != -1)
return GetFocusOffset(this.treeID);
},
get focusAffinity() {
var id = GetFocusObjectID(this.treeID);
if (id && id != -1)
return GetFocusAffinity(this.treeID);
},
get: function(id) {
if (id == undefined)
return undefined;
if (id == this.id)
return this.wrapper;
var obj = this.axNodeDataCache_[id];
if (obj)
return obj;
// Validate the backing AXTree has the specified node.
if (!GetRole(this.treeID, id))
return;
obj = new AutomationNode(this);
privates(obj).impl.treeID = this.treeID;
privates(obj).impl.id = id;
this.axNodeDataCache_[id] = obj;
return obj;
},
remove: function(id) {
if (this.axNodeDataCache_[id])
privates(this.axNodeDataCache_[id]).impl.detach();
delete this.axNodeDataCache_[id];
},
destroy: function() {
this.dispatchEvent('destroyed', 'none');
for (var id in this.axNodeDataCache_)
this.remove(id);
this.detach();
},
onAccessibilityEvent: function(eventParams) {
var targetNode = this.get(eventParams.targetID);
if (targetNode) {
var targetNodeImpl = privates(targetNode).impl;
targetNodeImpl.dispatchEvent(
eventParams.eventType, eventParams.eventFrom,
eventParams.mouseX, eventParams.mouseY);
if (eventParams.actionRequestID != -1) {
this.onActionResult(eventParams.actionRequestID, targetNode);
}
} else {
logging.WARNING('Got ' + eventParams.eventType +
' event on unknown node: ' + eventParams.targetID +
'; this: ' + this.id);
}
return true;
},
addActionResultCallback: function(callback) {
AutomationRootNodeImpl.actionRequestIDToCallback[
++AutomationRootNodeImpl.actionRequestCounter] = callback;
return AutomationRootNodeImpl.actionRequestCounter;
},
onGetTextLocationResult: function(textLocationParams) {
let requestID = textLocationParams.requestID;
if (requestID in AutomationRootNodeImpl.actionRequestIDToCallback) {
let callback =
AutomationRootNodeImpl.actionRequestIDToCallback[requestID];
try {
if (textLocationParams.result) {
callback(ComputeGlobalBounds(
this.treeID, textLocationParams.nodeID, textLocationParams.left,
textLocationParams.top, textLocationParams.width,
textLocationParams.height));
} else {
callback(undefined);
}
} catch (e) {
logging.WARNING('Error with onGetTextLocationResult callback:' + e);
}
delete AutomationNodeImpl.actionRequestIDToCallback[requestID];
}
},
onActionResult: function(requestID, result) {
if (requestID in AutomationRootNodeImpl.actionRequestIDToCallback) {
AutomationRootNodeImpl.actionRequestIDToCallback[requestID](result);
delete AutomationRootNodeImpl.actionRequestIDToCallback[requestID];
}
},
toString: function() {
function toStringInternal(nodeImpl, indent) {
if (!nodeImpl)
return '';
var output = '';
if (nodeImpl.isRootNode)
output += indent + 'tree id=' + nodeImpl.treeID + '\n';
output += indent +
$Function.call(AutomationNodeImpl.prototype.toString, nodeImpl) + '\n';
indent += ' ';
var children = nodeImpl.children;
for (var i = 0; i < children.length; ++i)
output += toStringInternal(privates(children[i]).impl, indent);
return output;
}
return toStringInternal(this, '');
},
};
function AutomationNode() {
privates(AutomationNode).constructPrivate(this, arguments);
}
utils.expose(AutomationNode, AutomationNodeImpl, {
functions: [
'doDefault',
'find',
'findAll',
'focus',
'getImageData',
'getNextTextMatch',
'hitTest',
'hitTestWithReply',
'makeVisible',
'matches',
'performCustomAction',
'performStandardAction',
'replaceSelectedText',
'resumeMedia',
'scrollBackward',
'scrollForward',
'scrollUp',
'scrollDown',
'scrollLeft',
'scrollRight',
'setSelection',
'setSequentialFocusNavigationStartingPoint',
'setValue',
'showContextMenu',
'startDuckingMedia',
'stopDuckingMedia',
'suspendMedia',
'addEventListener',
'removeEventListener',
'domQuerySelector',
'toString',
'boundsForRange',
],
readonly: $Array.concat(
publicAttributes,
[
'parent',
'firstChild',
'lastChild',
'children',
'previousSibling',
'nextSibling',
'isRootNode',
'role',
'checked',
'defaultActionVerb',
'restriction',
'state',
'location',
'indexInParent',
'lineStartOffsets',
'root',
'htmlAttributes',
'nameFrom',
'bold',
'italic',
'underline',
'lineThrough',
'customActions',
'standardActions',
'unclippedLocation',
'tableCellColumnHeaders',
'tableCellRowHeaders',
'tableCellColumnIndex',
'tableCellRowIndex',
]),
});
function AutomationRootNode() {
privates(AutomationRootNode).constructPrivate(this, arguments);
}
utils.expose(AutomationRootNode, AutomationRootNodeImpl, {
superclass: AutomationNode,
readonly: [
'docTitle',
'docUrl',
'docLoaded',
'docLoadingProgress',
'anchorObject',
'anchorOffset',
'anchorAffinity',
'focusObject',
'focusOffset',
'focusAffinity',
],
});
utils.defineProperty(AutomationRootNode, 'get', function(treeID) {
return AutomationRootNodeImpl.get(treeID);
});
utils.defineProperty(AutomationRootNode, 'getOrCreate', function(treeID) {
return AutomationRootNodeImpl.getOrCreate(treeID);
});
utils.defineProperty(AutomationRootNode, 'destroy', function(treeID) {
AutomationRootNodeImpl.destroy(treeID);
});
exports.$set('AutomationNode', AutomationNode);
exports.$set('AutomationRootNode', AutomationRootNode);