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 to move to the appropriate node in the accessibility tree. | 6 * Class to move to the appropriate node in the accessibility tree. Stays in a |
| 7 * subtree determined by restrictions passed to it. |
7 * | 8 * |
8 * @constructor | 9 * @constructor |
| 10 * @param {!chrome.automation.AutomationNode} start |
| 11 * @param {!chrome.automation.AutomationNode} scope |
| 12 * @param {!AutomationTreeWalker.Restriction} restrictions |
9 */ | 13 */ |
10 function AutomationTreeWalker() {}; | 14 function AutomationTreeWalker(start, scope, restrictions) { |
| 15 /** |
| 16 * Currently highlighted node. |
| 17 * |
| 18 * @private {!chrome.automation.AutomationNode} |
| 19 */ |
| 20 this.node_ = start; |
| 21 |
| 22 /** |
| 23 * The root of the subtree that the user is navigating through. |
| 24 * |
| 25 * @private {!chrome.automation.AutomationNode} |
| 26 */ |
| 27 this.scope_ = scope; |
| 28 |
| 29 /** |
| 30 * Function that returns true for a node that is a leaf of the current |
| 31 * subtree. |
| 32 * |
| 33 * @private {!AutomationTreeWalker.Unary} |
| 34 */ |
| 35 this.leafPred_ = restrictions.leaf; |
| 36 |
| 37 /** |
| 38 * Function that returns true for a node in the current subtree that should |
| 39 * be visited. |
| 40 * |
| 41 * @private {!AutomationTreeWalker.Unary} |
| 42 */ |
| 43 this.visitPred_ = restrictions.visit; |
| 44 }; |
| 45 |
| 46 /** |
| 47 * @typedef {{leaf: AutomationTreeWalker.Unary, |
| 48 * visit: AutomationTreeWalker.Unary}} |
| 49 */ |
| 50 AutomationTreeWalker.Restriction; |
| 51 |
| 52 /** |
| 53 * @typedef {function(!chrome.automation.AutomationNode) : boolean} |
| 54 */ |
| 55 AutomationTreeWalker.Unary; |
11 | 56 |
12 AutomationTreeWalker.prototype = { | 57 AutomationTreeWalker.prototype = { |
13 /** | 58 /** |
14 * Return the next/previous interesting node from |start|. If no interesting | 59 * Set this.node_ to the next/previous interesting node within the current |
15 * node is found, return the first/last interesting node. If |doNext| is true, | 60 * scope and return it. If no interesting node is found, return the |
16 * will search for next node. Otherwise, will search for previous node. | 61 * first/last interesting node. If |doNext| is true, will search for next |
| 62 * node. Otherwise, will search for previous node. |
17 * | 63 * |
18 * @param {chrome.automation.AutomationNode} start | |
19 * @param {chrome.automation.AutomationNode} root | |
20 * @param {boolean} doNext | 64 * @param {boolean} doNext |
21 * @return {chrome.automation.AutomationNode} | 65 * @return {chrome.automation.AutomationNode} |
22 */ | 66 */ |
23 moveToNode: function(start, root, doNext) { | 67 moveToNode: function(doNext) { |
24 let node = start; | 68 let node = this.node_; |
| 69 do { |
| 70 node = doNext ? this.getNextNode_(node) : this.getPreviousNode_(node); |
| 71 } while (node && !this.visitPred_(node)); |
25 if (node) { | 72 if (node) { |
26 do { | 73 this.node_ = node; |
27 node = doNext ? this.getNextNode_(node) : this.getPreviousNode_(node); | 74 return node; |
28 } while (node && !this.isInteresting_(node)); | |
29 if (node) | |
30 return node; | |
31 } | 75 } |
32 | 76 |
33 if (root) { | 77 console.log('Restarting search for node at ' + (doNext ? 'first' : 'last')); |
34 console.log( | 78 node = doNext ? this.scope_ : this.getYoungestDescendant_(this.scope_); |
35 'Restarting search for node at ' + (doNext ? 'first' : 'last')); | 79 while (node && !this.visitPred_(node)) |
36 node = doNext ? root.firstChild : this.getYoungestDescendant_(root); | 80 node = doNext ? this.getNextNode_(node) : this.getPreviousNode_(node); |
37 while (node && !this.isInteresting_(node)) | 81 if (node) { |
38 node = doNext ? this.getNextNode_(node) : this.getPreviousNode_(node); | 82 this.node_ = node; |
39 if (node) | 83 return node; |
40 return node; | |
41 } | 84 } |
42 | 85 |
43 console.log('Found no interesting nodes to visit.'); | 86 console.log('Found no interesting nodes to visit.'); |
44 return null; | 87 return null; |
45 }, | 88 }, |
46 | 89 |
| 90 |
47 /** | 91 /** |
48 * Given a flat list of nodes in pre-order, get the node that comes after | 92 * Given a flat list of nodes in pre-order, get the node that comes after |
49 * |node|. | 93 * |node| within the current scope. |
50 * | 94 * |
51 * @param {!chrome.automation.AutomationNode} node | 95 * @param {!chrome.automation.AutomationNode} node |
52 * @return {!chrome.automation.AutomationNode|undefined} | 96 * @return {!chrome.automation.AutomationNode|undefined} |
53 * @private | 97 * @private |
54 */ | 98 */ |
55 getNextNode_: function(node) { | 99 getNextNode_: function(node) { |
56 // Check for child. | 100 // Check for child. |
57 let child = node.firstChild; | 101 let child = node.firstChild; |
58 if (child) | 102 if (child && !this.leafPred_(node)) |
59 return child; | 103 return child; |
60 | 104 |
| 105 // Has no children, and if node is root of subtree, don't check siblings |
| 106 // or parent. |
| 107 if (node === this.scope_) |
| 108 return undefined; |
| 109 |
61 // No child. Check for right-sibling. | 110 // No child. Check for right-sibling. |
62 let sibling = node.nextSibling; | 111 let sibling = node.nextSibling; |
63 if (sibling) | 112 if (sibling) |
64 return sibling; | 113 return sibling; |
65 | 114 |
66 // No right-sibling. Get right-sibling of closest ancestor. | 115 // No right-sibling. Get right-sibling of closest ancestor. |
67 let ancestor = node.parent; | 116 let ancestor = node.parent; |
68 while (ancestor) { | 117 while (ancestor && ancestor !== this.scope_) { |
69 let aunt = ancestor.nextSibling; | 118 let aunt = ancestor.nextSibling; |
70 if (aunt) | 119 if (aunt) |
71 return aunt; | 120 return aunt; |
72 ancestor = ancestor.parent; | 121 ancestor = ancestor.parent; |
73 } | 122 } |
74 | 123 |
75 // No node found after |node|, so return undefined. | 124 // No node found after |node|, so return undefined. |
76 return undefined; | 125 return undefined; |
77 }, | 126 }, |
78 | 127 |
79 /** | 128 /** |
80 * Given a flat list of nodes in pre-order, get the node that comes before | 129 * Given a flat list of nodes in pre-order, get the node that comes before |
81 * |node|. | 130 * |node| within the current scope. |
82 * | 131 * |
83 * @param {!chrome.automation.AutomationNode} node | 132 * @param {!chrome.automation.AutomationNode} node |
84 * @return {!chrome.automation.AutomationNode|undefined} | 133 * @return {!chrome.automation.AutomationNode|undefined} |
85 * @private | 134 * @private |
86 */ | 135 */ |
87 getPreviousNode_: function(node) { | 136 getPreviousNode_: function(node) { |
| 137 // If node is root of subtree, there is no previous node. |
| 138 if (node === this.scope_) |
| 139 return undefined; |
| 140 |
88 // Check for left-sibling. If a left-sibling exists, return its youngest | 141 // Check for left-sibling. If a left-sibling exists, return its youngest |
89 // descendant if it has one, or otherwise return the sibling. | 142 // descendant if it has one, or otherwise return the sibling. |
90 let sibling = node.previousSibling; | 143 let sibling = node.previousSibling; |
91 if (sibling) | 144 if (sibling) |
92 return this.getYoungestDescendant_(sibling) || sibling; | 145 return this.getYoungestDescendant_(sibling) || sibling; |
93 | 146 |
94 // No left-sibling. Return parent if it exists; otherwise return undefined. | 147 // No left-sibling. Return parent if it exists; otherwise return undefined. |
95 return node.parent; | 148 //return node.parent; |
| 149 let parent = node.parent; |
| 150 if (parent) |
| 151 return parent; |
| 152 |
| 153 return undefined; |
96 }, | 154 }, |
97 | 155 |
98 /** | 156 /** |
99 * Get the youngest descendant of |node| if it has one. | 157 * Get the youngest descendant of |node|, if it has one, within the current |
| 158 * scope. |
100 * | 159 * |
101 * @param {!chrome.automation.AutomationNode} node | 160 * @param {!chrome.automation.AutomationNode} node |
102 * @return {!chrome.automation.AutomationNode|undefined} | 161 * @return {!chrome.automation.AutomationNode|undefined} |
103 * @private | 162 * @private |
104 */ | 163 */ |
105 getYoungestDescendant_: function(node) { | 164 getYoungestDescendant_: function(node) { |
106 if (!node.lastChild) | 165 if (!node.lastChild || this.leafPred_(node)) |
107 return undefined; | 166 return undefined; |
108 | 167 |
109 while (node.lastChild) | 168 while (node.lastChild && !this.leafPred_(node)) |
110 node = node.lastChild; | 169 node = node.lastChild; |
111 | 170 |
112 return node; | 171 return node; |
113 }, | 172 }, |
114 | 173 |
115 /** | 174 /** |
116 * Returns true if |node| is interesting. | |
117 * | |
118 * @param {!chrome.automation.AutomationNode} node | |
119 * @return {boolean} | |
120 * @private | |
121 */ | |
122 isInteresting_: function(node) { | |
123 let loc = node.location; | |
124 let parent = node.parent; | |
125 let root = node.root; | |
126 let role = node.role; | |
127 let state = node.state; | |
128 | |
129 // Skip things that are offscreen | |
130 if (state[chrome.automation.StateType.OFFSCREEN] | |
131 || loc.top < 0 || loc.left < 0) | |
132 return false; | |
133 | |
134 if (parent) { | |
135 // crbug.com/710559 | |
136 // Work around for browser tabs | |
137 if (role === chrome.automation.RoleType.TAB | |
138 && parent.role === chrome.automation.RoleType.TAB_LIST | |
139 && root.role === chrome.automation.RoleType.DESKTOP) | |
140 return true; | |
141 } | |
142 | |
143 // The general rule that applies to everything. | |
144 return state[chrome.automation.StateType.FOCUSABLE] === true; | |
145 }, | |
146 | |
147 /** | |
148 * Return the next sibling of |node| if it has one. | 175 * Return the next sibling of |node| if it has one. |
149 * | 176 * |
150 * @param {chrome.automation.AutomationNode} node | 177 * @param {chrome.automation.AutomationNode} node |
151 * @return {chrome.automation.AutomationNode} | 178 * @return {chrome.automation.AutomationNode} |
152 */ | 179 */ |
153 debugMoveToNext: function(node) { | 180 debugMoveToNext: function(node) { |
154 if (!node) | 181 if (!node) |
155 return null; | 182 return null; |
156 | 183 |
157 let next = node.nextSibling; | 184 let next = node.nextSibling; |
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
217 let parent = node.parent; | 244 let parent = node.parent; |
218 if (parent) { | 245 if (parent) { |
219 return parent; | 246 return parent; |
220 } else { | 247 } else { |
221 console.log('Node has no parent'); | 248 console.log('Node has no parent'); |
222 console.log('\n'); | 249 console.log('\n'); |
223 return null; | 250 return null; |
224 } | 251 } |
225 } | 252 } |
226 }; | 253 }; |
OLD | NEW |