OLD | NEW |
1 // Copyright 2017 The Chromium Authors. All rights reserved. | 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * Class containing predicates for the chrome automation API. Each predicate | 6 * Class containing predicates for the chrome automation API. Each predicate |
7 * can be run on one or more AutomationNodes and returns a boolean value. | 7 * can be run on one or more AutomationNodes and returns a boolean value. |
8 * | 8 * |
9 * @constructor | 9 * @constructor |
10 */ | 10 */ |
11 function AutomationPredicate() {} | 11 function AutomationPredicate() {} |
12 | 12 |
13 /** | 13 /** |
14 * Returns true if |node| is a subtreeLeaf, meaning that |node| is either | 14 * Returns true if |node| is a subtreeLeaf, meaning that |node| is either |
15 * interesting or a group (both defined below). | 15 * interesting or a group (both defined below). |
16 * | 16 * |
17 * @param {!chrome.automation.AutomationNode} node | 17 * @param {!chrome.automation.AutomationNode} node |
18 * @param {!chrome.automation.AutomationNode} scope | 18 * @param {!chrome.automation.AutomationNode} scope |
19 * @return {boolean} | 19 * @return {boolean} |
20 */ | 20 */ |
21 AutomationPredicate.isSubtreeLeaf = function(node, scope) { | 21 AutomationPredicate.isSubtreeLeaf = function(node, scope) { |
22 return AutomationPredicate.isInteresting(node) | 22 return AutomationPredicate.isInteresting(node) || |
23 || AutomationPredicate.isGroup(node, scope); | 23 AutomationPredicate.isGroup(node, scope); |
24 }; | 24 }; |
25 | 25 |
26 /** | 26 /** |
27 * Returns true if |node| is a group, meaning that the node has more than one | 27 * Returns true if |node| is a group, meaning that the node has more than one |
28 * interesting descendant, and that its interesting descendants exist in more | 28 * interesting descendant, and that its interesting descendants exist in more |
29 * than one subtree of its immediate children. | 29 * than one subtree of its immediate children. |
30 * | 30 * |
31 * Additionally, for |node| to be a group, it cannot have the same bounding | 31 * Additionally, for |node| to be a group, it cannot have the same bounding |
32 * box as its scope. | 32 * box as its scope. |
33 * | 33 * |
34 * @param {!chrome.automation.AutomationNode} node | 34 * @param {!chrome.automation.AutomationNode} node |
35 * @param {!chrome.automation.AutomationNode} scope | 35 * @param {!chrome.automation.AutomationNode} scope |
36 * @return {boolean} | 36 * @return {boolean} |
37 */ | 37 */ |
38 AutomationPredicate.isGroup = function(node, scope) { | 38 AutomationPredicate.isGroup = function(node, scope) { |
39 if (node !== scope && AutomationPredicate.hasSameLocation_(node, scope)) | 39 if (node !== scope && AutomationPredicate.hasSameLocation_(node, scope)) |
40 return false; | 40 return false; |
41 | 41 |
42 // Work around for client nested in client. No need to have user select both | 42 // Work around for client nested in client. No need to have user select both |
43 // clients for a window. Once locations for outer client updates correctly, | 43 // clients for a window. Once locations for outer client updates correctly, |
44 // this won't be needed. | 44 // this won't be needed. |
45 if (node.role === chrome.automation.RoleType.CLIENT | 45 if (node.role === chrome.automation.RoleType.CLIENT && |
46 && node.role === scope.role && node !== scope) | 46 node.role === scope.role && node !== scope) |
47 return false; | 47 return false; |
48 | 48 |
49 let interestingBranches = 0; | 49 let interestingBranches = 0; |
50 let children = node.children || []; | 50 let children = node.children || []; |
51 for (let child of children) { | 51 for (let child of children) { |
52 if (AutomationPredicate.isInterestingSubtree(child)) | 52 if (AutomationPredicate.isInterestingSubtree(child)) |
53 interestingBranches += 1; | 53 interestingBranches += 1; |
54 if (interestingBranches > 1) | 54 if (interestingBranches > 1) |
55 return true; | 55 return true; |
56 } | 56 } |
57 return false; | 57 return false; |
58 }; | 58 }; |
59 | 59 |
60 /** | 60 /** |
61 * Returns true if the two nodes have the same location. | 61 * Returns true if the two nodes have the same location. |
62 * | 62 * |
63 * @param {!chrome.automation.AutomationNode} node1 | 63 * @param {!chrome.automation.AutomationNode} node1 |
64 * @param {!chrome.automation.AutomationNode} node2 | 64 * @param {!chrome.automation.AutomationNode} node2 |
65 * @return {boolean} | 65 * @return {boolean} |
66 */ | 66 */ |
67 AutomationPredicate.hasSameLocation_ = function(node1, node2) { | 67 AutomationPredicate.hasSameLocation_ = function(node1, node2) { |
68 let l1 = node1.location; | 68 let l1 = node1.location; |
69 let l2 = node2.location; | 69 let l2 = node2.location; |
70 return l1.left === l2.left && l1.top === l2.top && l1.width === l2.width | 70 return l1.left === l2.left && l1.top === l2.top && l1.width === l2.width && |
71 && l1.height === l2.height; | 71 l1.height === l2.height; |
72 }; | 72 }; |
73 | 73 |
74 /** | 74 /** |
75 * Returns true if there is an interesting node in the subtree containing | 75 * Returns true if there is an interesting node in the subtree containing |
76 * |node| as its root (including |node| itself). | 76 * |node| as its root (including |node| itself). |
77 * | 77 * |
78 * @param {!chrome.automation.AutomationNode} node | 78 * @param {!chrome.automation.AutomationNode} node |
79 * @return {boolean} | 79 * @return {boolean} |
80 */ | 80 */ |
81 AutomationPredicate.isInterestingSubtree = function(node) { | 81 AutomationPredicate.isInterestingSubtree = function(node) { |
82 let children = node.children || []; | 82 let children = node.children || []; |
83 return AutomationPredicate.isInteresting(node) | 83 return AutomationPredicate.isInteresting(node) || |
84 || children.some(AutomationPredicate.isInterestingSubtree); | 84 children.some(AutomationPredicate.isInterestingSubtree); |
85 }; | 85 }; |
86 | 86 |
87 /** | 87 /** |
88 * Returns true if |node| is interesting, meaning that a user can perform some | 88 * Returns true if |node| is interesting, meaning that a user can perform some |
89 * type of action on it. | 89 * type of action on it. |
90 * | 90 * |
91 * @param {!chrome.automation.AutomationNode} node | 91 * @param {!chrome.automation.AutomationNode} node |
92 * @return {boolean} | 92 * @return {boolean} |
93 */ | 93 */ |
94 AutomationPredicate.isInteresting = function(node) { | 94 AutomationPredicate.isInteresting = function(node) { |
95 let loc = node.location; | 95 let loc = node.location; |
96 let parent = node.parent; | 96 let parent = node.parent; |
97 let root = node.root; | 97 let root = node.root; |
98 let role = node.role; | 98 let role = node.role; |
99 let state = node.state; | 99 let state = node.state; |
100 | 100 |
101 // TODO(elichtenberg): Define shorthand for chrome.automation.RoleType and | 101 // TODO(elichtenberg): Define shorthand for chrome.automation.RoleType and |
102 // StateType. | 102 // StateType. |
103 | 103 |
104 // Skip things that are offscreen | 104 // Skip things that are offscreen |
105 if (state[chrome.automation.StateType.OFFSCREEN] | 105 if (state[chrome.automation.StateType.OFFSCREEN] || loc.top < 0 || |
106 || loc.top < 0 || loc.left < 0) | 106 loc.left < 0) |
107 return false; | 107 return false; |
108 | 108 |
109 // Should just leave these as groups | 109 // Should just leave these as groups |
110 if (role === chrome.automation.RoleType.WEB_VIEW | 110 if (role === chrome.automation.RoleType.WEB_VIEW || |
111 || role === chrome.automation.RoleType.ROOT_WEB_AREA) | 111 role === chrome.automation.RoleType.ROOT_WEB_AREA) |
112 return false; | 112 return false; |
113 | 113 |
114 if (parent) { | 114 if (parent) { |
115 // crbug.com/710559 | 115 // crbug.com/710559 |
116 // Work around for browser tabs | 116 // Work around for browser tabs |
117 if (role === chrome.automation.RoleType.TAB | 117 if (role === chrome.automation.RoleType.TAB && |
118 && parent.role === chrome.automation.RoleType.TAB_LIST | 118 parent.role === chrome.automation.RoleType.TAB_LIST && |
119 && root.role === chrome.automation.RoleType.DESKTOP) | 119 root.role === chrome.automation.RoleType.DESKTOP) |
120 return true; | 120 return true; |
121 } | 121 } |
122 | 122 |
123 // The general rule that applies to everything. | 123 // The general rule that applies to everything. |
124 return state[chrome.automation.StateType.FOCUSABLE] === true; | 124 return state[chrome.automation.StateType.FOCUSABLE] === true; |
125 }; | 125 }; |
OLD | NEW |