1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 * @fileoverview ChromeVox utilities for the automation extension API.
9 goog.provide('AutomationUtil');
10 goog.provide('AutomationUtil.Dir');
12 goog.require('AutomationPredicate');
17 AutomationUtil = function() {};
20 * Possible directions to perform tree traversals.
23 AutomationUtil.Dir = {
24 // Search from left to right.
27 // Search from right to left.
32 goog.scope(function() {
33 var AutomationNode = chrome.automation.AutomationNode;
34 var Dir = AutomationUtil.Dir;
37 * Find a node in subtree of |cur| satisfying |pred| using pre-order traversal.
38 * @param {AutomationNode} cur Node to begin the search from.
40 * @param {AutomationPredicate.Unary} pred A predicate to apply
41 * to a candidate node.
42 * @return {AutomationNode}
44 AutomationUtil.findNodePre = function(cur, dir, pred) {
48 var child = dir == Dir.BACKWARD ? cur.lastChild : cur.firstChild;
50 var ret = AutomationUtil.findNodePre(child, dir, pred);
53 child = dir == Dir.BACKWARD ?
54 child.previousSibling : child.nextSibling;
59 * Find a node in subtree of |cur| satisfying |pred| using post-order traversal.
60 * @param {AutomationNode} cur Node to begin the search from.
62 * @param {AutomationPredicate.Unary} pred A predicate to apply
63 * to a candidate node.
64 * @return {AutomationNode}
66 AutomationUtil.findNodePost = function(cur, dir, pred) {
67 var child = dir == Dir.BACKWARD ? cur.lastChild : cur.firstChild;
69 var ret = AutomationUtil.findNodePost(child, dir, pred);
72 child = dir == Dir.BACKWARD ?
73 child.previousSibling : child.nextSibling;
81 * Find the next node in the given direction that is either an immediate sibling
82 * or a sibling of an ancestor.
83 * @param {AutomationNode} cur Node to start search from.
85 * @return {AutomationNode}
87 AutomationUtil.findNextSubtree = function(cur, dir) {
89 var next = dir == Dir.BACKWARD ?
90 cur.previousSibling : cur.nextSibling;
91 if (!AutomationUtil.isInSameTree(cur, next))
95 if (!AutomationUtil.isInSameTree(cur, cur.parent))
98 if (AutomationUtil.isTraversalRoot_(cur))
104 * Find the next node in the given direction in depth first order.
105 * @param {AutomationNode} cur Node to begin the search from.
107 * @param {AutomationPredicate.Unary} pred A predicate to apply
108 * to a candidate node.
109 * @return {AutomationNode}
111 AutomationUtil.findNextNode = function(cur, dir, pred) {
114 if (!(next = AutomationUtil.findNextSubtree(cur, dir)))
117 next = AutomationUtil.findNodePre(next, dir, pred);
118 if (next && AutomationPredicate.shouldIgnoreLeaf(next)) {
127 * Given nodes a_1, ..., a_n starting at |cur| in pre order traversal, apply
128 * |pred| to a_i and a_(i - 1) until |pred| is satisfied. Returns a_(i - 1) or
129 * a_i (depending on opt_options.before) or null if no match was found.
130 * @param {AutomationNode} cur
132 * @param {AutomationPredicate.Binary} pred
133 * @param {{filter: (AutomationPredicate.Unary|undefined),
134 * before: boolean?}=} opt_options
135 * filter - Filters which candidate nodes to consider. Defaults to leaf
137 * before - True to return a_(i - 1); a_i otherwise. Defaults to false.
138 * @return {AutomationNode}
140 AutomationUtil.findNodeUntil = function(cur, dir, pred, opt_options) {
142 opt_options || {filter: AutomationPredicate.leaf, before: false};
143 if (!opt_options.filter)
144 opt_options.filter = AutomationPredicate.leaf;
149 AutomationUtil.findNextNode(cur,
151 function(candidate) {
152 if (!opt_options.filter(candidate))
155 var satisfied = pred(prev, candidate);
164 return opt_options.before ? before : after;
168 * Returns an array containing ancestors of node starting at root down to node.
169 * @param {!AutomationNode} node
170 * @return {!Array<AutomationNode>}
172 AutomationUtil.getAncestors = function(node) {
174 var candidate = node;
178 if (!AutomationUtil.isInSameTree(candidate, candidate.parent))
181 candidate = candidate.parent;
183 return ret.reverse();
187 * Gets the first index where the two input arrays differ. Returns -1 if they
189 * @param {!Array<AutomationNode>} ancestorsA
190 * @param {!Array<AutomationNode>} ancestorsB
193 AutomationUtil.getDivergence = function(ancestorsA, ancestorsB) {
194 for (var i = 0; i < ancestorsA.length; i++) {
195 if (ancestorsA[i] !== ancestorsB[i])
198 if (ancestorsA.length == ancestorsB.length)
200 return ancestorsA.length;
204 * Returns ancestors of |node| that are not also ancestors of |prevNode|.
205 * @param {!AutomationNode} prevNode
206 * @param {!AutomationNode} node
207 * @return {!Array<AutomationNode>}
209 AutomationUtil.getUniqueAncestors = function(prevNode, node) {
210 var prevAncestors = AutomationUtil.getAncestors(prevNode);
211 var ancestors = AutomationUtil.getAncestors(node);
212 var divergence = AutomationUtil.getDivergence(prevAncestors, ancestors);
213 return ancestors.slice(divergence);
217 * Given |nodeA| and |nodeB| in that order, determines their ordering in the
219 * @param {!AutomationNode} nodeA
220 * @param {!AutomationNode} nodeB
221 * @return {AutomationUtil.Dir}
223 AutomationUtil.getDirection = function(nodeA, nodeB) {
224 var ancestorsA = AutomationUtil.getAncestors(nodeA);
225 var ancestorsB = AutomationUtil.getAncestors(nodeB);
226 var divergence = AutomationUtil.getDivergence(ancestorsA, ancestorsB);
228 // Default to Dir.FORWARD.
229 if (divergence == -1)
232 var divA = ancestorsA[divergence];
233 var divB = ancestorsB[divergence];
235 // One of the nodes is an ancestor of the other. Don't distinguish and just
236 // consider it Dir.FORWARD.
237 if (!divA || !divB || divA.parent === nodeB || divB.parent === nodeA)
240 return divA.indexInParent <= divB.indexInParent ? Dir.FORWARD : Dir.BACKWARD;
244 * Determines whether the two given nodes come from the same tree source.
245 * @param {AutomationNode} a
246 * @param {AutomationNode} b
249 AutomationUtil.isInSameTree = function(a, b) {
253 // Given two non-desktop roots, consider them in the "same" tree.
254 return a.root === b.root ||
255 (a.root.role == b.root.role && a.root.role == 'rootWebArea');
259 * Returns whether the given node should not be crossed when performing
260 * traversals up the ancestry chain.
261 * @param {AutomationNode} node
265 AutomationUtil.isTraversalRoot_ = function(node) {
271 return node.root.role == 'desktop';