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 var AutomationNode = chrome.automation.AutomationNode; | 5 let AutomationNode = chrome.automation.AutomationNode; |
6 | 6 |
7 let debuggingEnabled = true; | |
7 /** | 8 /** |
8 * @constructor | 9 * @constructor |
9 */ | 10 */ |
10 var SwitchAccess = function() { | 11 let SwitchAccess = function() { |
11 console.log("Switch access is enabled"); | 12 console.log("Switch access is enabled"); |
12 | 13 |
13 // Currently selected node. | 14 // Currently selected node. |
14 /** @private {AutomationNode} */ | 15 /** @private {AutomationNode} */ |
15 this.node_ = null; | 16 this.node_ = null; |
16 | 17 |
18 // Root node (i.e., the desktop). | |
19 /** @private {AutomationNode} */ | |
20 this.root_ = null; | |
21 | |
17 // List of nodes to push to / pop from in case this.node_ is lost. | 22 // List of nodes to push to / pop from in case this.node_ is lost. |
18 /** @private {!Array<!AutomationNode>} */ | 23 /** @private {!Array<!AutomationNode>} */ |
19 this.ancestorList_ = []; | 24 this.ancestorList_ = []; |
20 | 25 |
21 chrome.automation.getDesktop(function(desktop) { | 26 chrome.automation.getDesktop(function(desktop) { |
22 this.node_ = desktop; | 27 this.node_ = desktop; |
28 this.root_ = desktop; | |
23 console.log("AutomationNode for desktop is loaded"); | 29 console.log("AutomationNode for desktop is loaded"); |
24 this.printDetails_(); | 30 this.printDetails_(); |
25 | 31 |
26 document.addEventListener("keyup", function(event) { | 32 document.addEventListener("keyup", function(event) { |
David Tseng
2017/03/09 20:57:40
nit: single quotes for string literals i.e. 'keyup
elichtenberg
2017/03/09 23:35:42
Done.
| |
27 if (event.key === "1") { | 33 switch (event.key) { |
28 console.log("1 = go to previous element"); | 34 case "1": |
29 this.moveToPrevious_(); | 35 console.log("1 = go to previous element"); |
30 } else if (event.key === "2") { | 36 this.moveToPrevious_(); |
31 console.log("2 = go to next element"); | 37 break; |
32 this.moveToNext_(); | 38 case "2": |
33 } else if (event.key === "3") { | 39 console.log("2 = go to next element"); |
34 console.log("3 = go to child element"); | 40 this.moveToNext_(); |
35 this.moveToFirstChild_(); | 41 break; |
36 } else if (event.key === "4") { | 42 case "3": |
37 console.log("4 = go to parent element"); | 43 console.log("3 = do default on element"); |
38 this.moveToParent_(); | 44 this.doDefault_(); |
39 } else if (event.key === "5") { | 45 break; |
40 console.log("5 is not yet implemented"); | 46 } |
41 console.log("\n"); | 47 if (debuggingEnabled) { |
David Tseng
2017/03/09 20:57:40
Consider using a custom logger that only logs if t
elichtenberg
2017/03/09 23:35:43
Will do in separate CL.
| |
42 } | 48 switch (event.key) { |
43 chrome.accessibilityPrivate.setFocusRing([this.node_.location]); | 49 case "6": |
50 console.log("6 = go to previous element (debug mode)"); | |
51 this.debugMoveToPrevious_(); | |
52 break; | |
53 case "7": | |
54 console.log("7 = go to next element (debug mode)"); | |
55 this.debugMoveToNext_(); | |
56 break; | |
57 case "8": | |
58 console.log("8 = go to child element (debug mode)"); | |
59 this.debugMoveToFirstChild_(); | |
60 break; | |
61 case "9": | |
62 console.log("9 = go to parent element (debug mode)"); | |
63 this.debugMoveToParent_(); | |
64 break; | |
65 } | |
66 } | |
67 if (this.node_) { | |
68 chrome.accessibilityPrivate.setFocusRing([this.node_.location]); | |
69 } | |
44 }.bind(this)); | 70 }.bind(this)); |
45 }.bind(this)); | 71 }.bind(this)); |
46 }; | 72 }; |
47 | 73 |
48 SwitchAccess.prototype = { | 74 SwitchAccess.prototype = { |
49 /** | 75 /** |
76 * Set this.node_ to the previous interesting node. If no interesting node | |
77 * comes before this.node_, set this.node_ to the last interesting node. | |
78 * | |
79 * @private | |
80 */ | |
81 moveToPrevious_: function() { | |
82 if (this.node_) { | |
83 let prev = this.getPreviousNode_(this.node_); | |
84 while (prev && !this.isInteresting_(prev)) { | |
85 prev = this.getPreviousNode_(prev); | |
86 } | |
87 if (prev) { | |
88 this.node_ = prev; | |
89 this.printNode_(this.node_); | |
90 return; | |
91 } | |
92 } | |
93 | |
94 if (this.root_) { | |
95 console.log("Reached the first interesting node. Restarting with last.") | |
96 let prev = this.getYoungestDescendant_(this.root_); | |
97 while (prev && !this.isInteresting_(prev)) { | |
98 prev = this.getPreviousNode_(prev); | |
99 } | |
100 if (prev) { | |
101 this.node_ = prev; | |
102 this.printNode_(this.node_); | |
103 return; | |
104 } | |
105 } | |
106 | |
107 console.log("Found no interesting nodes to visit.") | |
108 }, | |
109 | |
110 /** | |
111 * Set this.node_ to the next interesting node. If no interesting node comes | |
112 * after this.node_, set this.node_ to the first interesting node. | |
113 * | |
114 * @private | |
115 */ | |
116 moveToNext_: function() { | |
117 if (this.node_) { | |
118 let next = this.getNextNode_(this.node_); | |
119 while (next && !this.isInteresting_(next)) { | |
120 next = this.getNextNode_(next); | |
121 } | |
122 if (next) { | |
123 this.node_ = next; | |
124 this.printNode_(this.node_); | |
125 return; | |
126 } | |
127 } | |
128 | |
129 if (this.root_) { | |
130 console.log("Reached the last interesting node. Restarting with first."); | |
131 let next = this.root_.firstChild; | |
132 while (next && !this.isInteresting_(next)) { | |
133 next = this.getNextNode_(next); | |
134 } | |
135 if (next) { | |
136 this.node_ = next; | |
137 this.printNode_(this.node_); | |
138 return; | |
139 } | |
140 } | |
141 | |
142 console.log("Found no interesting nodes to visit."); | |
143 }, | |
144 | |
145 /** | |
146 * Given a flat list of nodes in pre-order, get the node that comes after | |
147 * |node|. | |
148 * | |
149 * @param {!AutomationNode} node | |
150 * @return {AutomationNode} | |
151 * @private | |
152 */ | |
153 getNextNode_: function(node) { | |
154 // Check for child. | |
155 let child = node.firstChild; | |
156 if (child) { | |
157 return child; | |
158 } | |
159 | |
160 // No child. Check for right-sibling. | |
161 let sibling = node.nextSibling; | |
162 if (sibling) { | |
163 return sibling; | |
164 } | |
165 | |
166 // No right-sibling. Get right-sibling of closest ancestor. | |
167 let ancestor = node.parent; | |
168 while (ancestor) { | |
169 let aunt = ancestor.nextSibling; | |
170 if (aunt) { | |
171 return aunt; | |
172 } | |
173 ancestor = ancestor.parent; | |
174 } | |
175 | |
176 // No node found after |node|, so return null. | |
177 return null; | |
178 }, | |
179 | |
180 /** | |
181 * Given a flat list of nodes in pre-order, get the node that comes before | |
182 * |node|. | |
183 * | |
184 * @param {!AutomationNode} node | |
185 * @return {AutomationNode} | |
186 * @private | |
187 */ | |
188 getPreviousNode_: function(node) { | |
189 // Check for left-sibling. Return its youngest descendant if it has one. | |
190 // Otherwise, return the sibling. | |
191 let sibling = node.previousSibling; | |
192 if (sibling) { | |
193 let descendant = this.getYoungestDescendant_(sibling); | |
194 if (descendant) { | |
195 return descendant; | |
196 } | |
197 return sibling; | |
198 } | |
199 | |
200 // No left-sibling. Check for parent. | |
201 let parent = node.parent; | |
202 if (parent) { | |
203 return parent; | |
204 } | |
205 | |
206 // No node found before |node|, so return null. | |
207 return null; | |
208 }, | |
209 | |
210 /** | |
211 * Get the youngest descendant of |node| if it has one. | |
212 * | |
213 * @param {!AutomationNode} node | |
214 * @return {AutomationNode} | |
215 * @private | |
216 */ | |
217 getYoungestDescendant_: function(node) { | |
218 let descendant = node.lastChild; | |
219 if (!descendant) { | |
David Tseng
2017/03/09 20:57:40
|node| is called by sharing, so no need to use ano
elichtenberg
2017/03/09 23:35:43
Done.
| |
220 return null; | |
221 } | |
David Tseng
2017/03/09 20:57:40
No curlies for single lined if bodies
elichtenberg
2017/03/09 23:35:42
Done.
| |
222 while (descendant.lastChild) { | |
223 descendant = descendant.lastChild; | |
224 } | |
225 return descendant; | |
226 }, | |
227 | |
228 /** | |
229 * Returns true if |node| is interesting. | |
230 * | |
231 * @param {!AutomationNode} node | |
232 * @return {boolean} | |
233 * @private | |
234 */ | |
235 isInteresting_: function(node) { | |
236 if (node.state && node.state.focusable) { | |
David Tseng
2017/03/09 20:57:40
return node.state && node.state.focusable;
node.s
elichtenberg
2017/03/09 23:35:42
Done. According to externs, node.state can be null
David Tseng
2017/03/10 18:09:22
To clarify, from observing many pages, it is usual
elichtenberg
2017/03/10 18:30:44
Thanks for clarifying. That makes sense. I'll acco
| |
237 return true; | |
238 } else { | |
239 return false; | |
240 } | |
241 }, | |
242 | |
243 /** | |
50 * Move to the previous sibling of this.node_ if it has one. | 244 * Move to the previous sibling of this.node_ if it has one. |
51 */ | 245 * |
52 moveToPrevious_: function() { | 246 * @private |
53 var previous = this.node_.previousSibling; | 247 */ |
248 debugMoveToPrevious_: function() { | |
249 let previous = this.node_.previousSibling; | |
54 if (previous) { | 250 if (previous) { |
55 this.node_ = previous; | 251 this.node_ = previous; |
56 this.printDetails_(); | 252 this.printDetails_(); |
David Tseng
2017/03/09 20:57:40
No need to have a special variant if you have a cu
elichtenberg
2017/03/09 23:35:43
Talked offline about this. Keeping debug functions
| |
57 } else { | 253 } else { |
58 console.log("Node is first of siblings"); | 254 console.log("Node is first of siblings"); |
59 console.log("\n"); | 255 console.log("\n"); |
60 } | 256 } |
61 }, | 257 }, |
62 | 258 |
63 /** | 259 /** |
64 * Move to the next sibling of this.node_ if it has one. | 260 * Move to the next sibling of this.node_ if it has one. |
261 * | |
262 * @private | |
65 */ | 263 */ |
66 moveToNext_: function() { | 264 debugMoveToNext_: function() { |
67 var next = this.node_.nextSibling; | 265 let next = this.node_.nextSibling; |
68 if (next) { | 266 if (next) { |
69 this.node_ = next; | 267 this.node_ = next; |
70 this.printDetails_(); | 268 this.printDetails_(); |
71 } else { | 269 } else { |
72 console.log("Node is last of siblings"); | 270 console.log("Node is last of siblings"); |
73 console.log("\n"); | 271 console.log("\n"); |
74 } | 272 } |
75 }, | 273 }, |
76 | 274 |
77 /** | 275 /** |
78 * Move to the first child of this.node_ if it has one. | 276 * Move to the first child of this.node_ if it has one. |
277 * | |
278 * @private | |
79 */ | 279 */ |
80 moveToFirstChild_: function() { | 280 debugMoveToFirstChild_: function() { |
81 var child = this.node_.firstChild; | 281 let child = this.node_.firstChild; |
82 if (child) { | 282 if (child) { |
83 this.ancestorList_.push(this.node_); | 283 this.ancestorList_.push(this.node_); |
84 this.node_ = child; | 284 this.node_ = child; |
85 this.printDetails_(); | 285 this.printDetails_(); |
86 } else { | 286 } else { |
87 console.log("Node has no children"); | 287 console.log("Node has no children"); |
88 console.log("\n"); | 288 console.log("\n"); |
89 } | 289 } |
90 }, | 290 }, |
91 | 291 |
92 /** | 292 /** |
93 * Move to the parent of this.node_ if it has one. If it does not have a | 293 * Move to the parent of this.node_ if it has one. If it does not have a |
94 * parent but it is not the top level root node, then this.node_ lost track of | 294 * parent but it is not the top level root node, then this.node_ lost track of |
95 * its neighbors, and we move to an ancestor node. | 295 * its neighbors, and we move to an ancestor node. |
296 * | |
297 * @private | |
96 */ | 298 */ |
97 moveToParent_: function() { | 299 debugMoveToParent_: function() { |
98 var parent = this.node_.parent; | 300 let parent = this.node_.parent; |
99 if (parent) { | 301 if (parent) { |
100 this.ancestorList_.pop(); | 302 this.ancestorList_.pop(); |
101 this.node_ = parent; | 303 this.node_ = parent; |
102 this.printDetails_(); | 304 this.printDetails_(); |
103 } else if (this.ancestorList_.length === 0) { | 305 } else if (this.ancestorList_.length === 0) { |
104 console.log("Node has no parent"); | 306 console.log("Node has no parent"); |
105 console.log("\n"); | 307 console.log("\n"); |
106 } else { | 308 } else { |
107 console.log( | 309 console.log( |
108 "Node could not find its parent, so moved to recent ancestor"); | 310 "Node could not find its parent, so moved to recent ancestor"); |
109 var ancestor = this.ancestorList_.pop(); | 311 let ancestor = this.ancestorList_.pop(); |
110 this.node_ = ancestor; | 312 this.node_ = ancestor; |
111 this.printDetails_(); | 313 this.printDetails_(); |
112 } | 314 } |
113 }, | 315 }, |
114 | 316 |
115 /** | 317 /** |
318 * Perform the default action on the currently selected node. | |
319 * | |
320 * @private | |
321 */ | |
322 doDefault_: function() { | |
323 let state = this.node_.state; | |
324 if (state && state.focusable) { | |
325 console.log("Node was focusable, doing default on it") | |
326 } else if (state) { | |
327 console.log("Node was not focusable, but still doing default"); | |
328 } else { | |
329 console.log("Node has no state, still doing default"); | |
330 } | |
331 console.log("\n"); | |
332 this.node_.doDefault(); | |
333 }, | |
334 | |
335 /** | |
116 * Print out details about the currently selected node and the list of | 336 * Print out details about the currently selected node and the list of |
117 * ancestors. | 337 * ancestors. |
118 * | 338 * |
119 * @private | 339 * @private |
120 */ | 340 */ |
121 printDetails_: function() { | 341 printDetails_: function() { |
122 this.printNode_(this.node_); | 342 this.printNode_(this.node_); |
123 console.log(this.ancestorList_); | 343 console.log(this.ancestorList_); |
124 console.log("\n"); | 344 console.log("\n"); |
125 }, | 345 }, |
126 | 346 |
127 /** | 347 /** |
128 * Print out details about a node. | 348 * Print out details about a node. |
129 * | 349 * |
130 * @param {AutomationNode} node | 350 * @param {AutomationNode} node |
131 * @private | 351 * @private |
132 */ | 352 */ |
133 printNode_: function(node) { | 353 printNode_: function(node) { |
134 if (node) { | 354 if (node) { |
135 console.log("Name = " + node.name); | 355 console.log("Name = " + node.name); |
136 console.log("Role = " + node.role); | 356 console.log("Role = " + node.role); |
137 if (!node.parent) { | 357 if (!node.parent) { |
138 console.log("At index " + node.indexInParent + ", has no parent"); | 358 console.log("At index " + node.indexInParent + ", has no parent"); |
139 } else { | 359 } else { |
140 var numSiblings = node.parent.children.length; | 360 let numSiblings = node.parent.children.length; |
141 console.log( | 361 console.log( |
142 "At index " + node.indexInParent + ", there are " | 362 "At index " + node.indexInParent + ", there are " |
143 + numSiblings + " siblings"); | 363 + numSiblings + " siblings"); |
144 } | 364 } |
145 console.log("Has " + node.children.length + " children"); | 365 console.log("Has " + node.children.length + " children"); |
146 } else { | 366 } else { |
147 console.log("Node is null"); | 367 console.log("Node is null"); |
148 } | 368 } |
149 console.log(node); | 369 console.log(node); |
370 console.log("\n"); | |
150 } | 371 } |
151 }; | 372 }; |
152 | 373 |
153 new SwitchAccess(); | 374 window.switchAccess = new SwitchAccess(); |
OLD | NEW |