Chromium Code Reviews| 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 let AutomationNode = chrome.automation.AutomationNode; | |
| 6 | |
| 7 let debuggingEnabled = true; | |
| 8 | |
| 9 /** | 5 /** |
| 6 * Class to manage SwitchAccess and interact with other controllers. | |
| 7 * | |
| 10 * @constructor | 8 * @constructor |
| 9 * @implements {SwitchAccessInterface} | |
|
dmazzoni
2017/03/29 05:52:20
This worked out well!
elichtenberg
2017/03/29 19:15:11
Thanks!
| |
| 11 */ | 10 */ |
| 12 let SwitchAccess = function() { | 11 let SwitchAccess = function() { |
| 13 console.log('Switch access is enabled'); | 12 console.log('Switch access is enabled'); |
| 14 | 13 |
| 15 /** | 14 /** |
| 16 * User preferences. | 15 * User preferences. |
| 17 * | 16 * |
| 18 * @type {SwitchAccessPrefs} | 17 * @type {SwitchAccessPrefs} |
| 19 */ | 18 */ |
| 20 this.switchAccessPrefs = null; | 19 this.switchAccessPrefs = null; |
| 21 | 20 |
| 22 /** | 21 /** |
| 22 * Handles changes to auto-scan. | |
| 23 * | |
| 24 * @private {AutoScanManager} | |
| 25 */ | |
| 26 this.autoScanManager_ = null; | |
| 27 | |
| 28 /** | |
| 29 * Handles keyboard input. | |
| 30 * | |
| 31 * @private {KeyboardHandler} | |
| 32 */ | |
| 33 this.keyboardHandler_ = null; | |
| 34 | |
| 35 /** | |
| 36 * Moves to the appropriate node in the accessibility tree. | |
| 37 * | |
| 38 * @private {AutomationTreeWalker} | |
| 39 */ | |
| 40 this.treeWalker_ = null; | |
| 41 | |
| 42 /** | |
| 23 * Currently selected node. | 43 * Currently selected node. |
| 24 * | 44 * |
| 25 * @private {AutomationNode} | 45 * @private {AutomationNode} |
| 26 */ | 46 */ |
| 27 this.node_ = null; | 47 this.node_ = null; |
| 28 | 48 |
| 29 /** | 49 /** |
| 30 * Root node (i.e., the desktop). | 50 * Root node (i.e., the desktop). |
| 31 * | 51 * |
| 32 * @private {AutomationNode} | 52 * @private {AutomationNode} |
| 33 */ | 53 */ |
| 34 this.root_ = null; | 54 this.root_ = null; |
| 35 | 55 |
| 36 this.init_(); | 56 this.init_(); |
| 37 }; | 57 }; |
| 38 | 58 |
| 39 SwitchAccess.prototype = { | 59 SwitchAccess.prototype = { |
| 40 /** | 60 /** |
| 41 * Set this.node_ and this.root_ to the desktop node, and set up preferences | 61 * Set this.node_ and this.root_ to the desktop node, and set up preferences, |
| 42 * and event listeners. | 62 * controllers, and event listeners. |
| 43 * | 63 * |
| 44 * @private | 64 * @private |
| 45 */ | 65 */ |
| 46 init_: function() { | 66 init_: function() { |
| 47 this.switchAccessPrefs = new SwitchAccessPrefs(); | 67 this.switchAccessPrefs = new SwitchAccessPrefs(); |
| 68 this.autoScanManager_ = new AutoScanManager(this); | |
| 69 this.keyboardHandler_ = new KeyboardHandler(this); | |
| 70 this.treeWalker_ = new AutomationTreeWalker(); | |
| 48 | 71 |
| 49 chrome.automation.getDesktop(function(desktop) { | 72 chrome.automation.getDesktop(function(desktop) { |
| 50 this.node_ = desktop; | 73 this.node_ = desktop; |
| 51 this.root_ = desktop; | 74 this.root_ = desktop; |
| 52 console.log('AutomationNode for desktop is loaded'); | 75 console.log('AutomationNode for desktop is loaded'); |
| 53 this.printNode_(this.node_); | 76 this.printNode_(this.node_); |
| 54 | |
| 55 document.addEventListener('keyup', function(event) { | |
| 56 switch (event.key) { | |
| 57 case '1': | |
| 58 console.log('1 = go to previous element'); | |
| 59 this.moveToPrevious_(); | |
| 60 break; | |
| 61 case '2': | |
| 62 console.log('2 = go to next element'); | |
| 63 this.moveToNext_(); | |
| 64 break; | |
| 65 case '3': | |
| 66 console.log('3 = do default on element'); | |
| 67 this.doDefault_(); | |
| 68 break; | |
| 69 case '4': | |
| 70 this.showOptionsPage_(); | |
| 71 break; | |
| 72 } | |
| 73 if (debuggingEnabled) { | |
| 74 switch (event.key) { | |
| 75 case '6': | |
| 76 console.log('6 = go to previous element (debug mode)'); | |
| 77 this.debugMoveToPrevious_(); | |
| 78 break; | |
| 79 case '7': | |
| 80 console.log('7 = go to next element (debug mode)'); | |
| 81 this.debugMoveToNext_(); | |
| 82 break; | |
| 83 case '8': | |
| 84 console.log('8 = go to child element (debug mode)'); | |
| 85 this.debugMoveToFirstChild_(); | |
| 86 break; | |
| 87 case '9': | |
| 88 console.log('9 = go to parent element (debug mode)'); | |
| 89 this.debugMoveToParent_(); | |
| 90 break; | |
| 91 } | |
| 92 } | |
| 93 if (this.node_) | |
| 94 chrome.accessibilityPrivate.setFocusRing([this.node_.location]); | |
| 95 }.bind(this)); | |
| 96 }.bind(this)); | 77 }.bind(this)); |
| 97 | 78 |
| 98 document.addEventListener('prefsUpdate', this.handlePrefsUpdate_); | 79 document.addEventListener( |
| 80 'prefsUpdate', this.handlePrefsUpdate_.bind(this)); | |
| 81 }, | |
| 82 | |
| 83 /** | |
| 84 * Set this.node_ to the next interesting node. If no interesting node comes | |
| 85 * after this.node_, set this.node_ to the first interesting node. | |
| 86 * | |
| 87 * @override | |
| 88 */ | |
| 89 moveToNext: function() { | |
| 90 let next = this.treeWalker_.moveToNext(this.node_, this.root_); | |
| 91 if (next) { | |
| 92 this.node_ = next; | |
| 93 this.printNode_(this.node_); | |
| 94 chrome.accessibilityPrivate.setFocusRing([this.node_.location]); | |
| 95 } | |
| 99 }, | 96 }, |
| 100 | 97 |
| 101 /** | 98 /** |
| 102 * Set this.node_ to the previous interesting node. If no interesting node | 99 * Set this.node_ to the previous interesting node. If no interesting node |
| 103 * comes before this.node_, set this.node_ to the last interesting node. | 100 * comes before this.node_, set this.node_ to the last interesting node. |
| 104 * | 101 * |
| 105 * @private | 102 * @override |
| 106 */ | 103 */ |
| 107 moveToPrevious_: function() { | 104 moveToPrevious: function() { |
| 108 if (this.node_) { | 105 let prev = this.treeWalker_.moveToPrevious(this.node_, this.root_); |
| 109 let prev = this.getPreviousNode_(this.node_); | 106 if (prev) { |
| 110 while (prev && !this.isInteresting_(prev)) { | 107 this.node_ = prev; |
| 111 prev = this.getPreviousNode_(prev); | 108 this.printNode_(this.node_); |
| 112 } | 109 chrome.accessibilityPrivate.setFocusRing([this.node_.location]); |
| 113 if (prev) { | |
| 114 this.node_ = prev; | |
| 115 this.printNode_(this.node_); | |
| 116 return; | |
| 117 } | |
| 118 } | 110 } |
| 119 | |
| 120 if (this.root_) { | |
| 121 console.log('Reached the first interesting node. Restarting with last.') | |
| 122 let prev = this.getYoungestDescendant_(this.root_); | |
| 123 while (prev && !this.isInteresting_(prev)) { | |
| 124 prev = this.getPreviousNode_(prev); | |
| 125 } | |
| 126 if (prev) { | |
| 127 this.node_ = prev; | |
| 128 this.printNode_(this.node_); | |
| 129 return; | |
| 130 } | |
| 131 } | |
| 132 | |
| 133 console.log('Found no interesting nodes to visit.') | |
| 134 }, | 111 }, |
| 135 | 112 |
| 136 /** | 113 /** |
| 137 * Set this.node_ to the next interesting node. If no interesting node comes | |
| 138 * after this.node_, set this.node_ to the first interesting node. | |
| 139 * | |
| 140 * @private | |
| 141 */ | |
| 142 moveToNext_: function() { | |
| 143 if (this.node_) { | |
| 144 let next = this.getNextNode_(this.node_); | |
| 145 while (next && !this.isInteresting_(next)) | |
| 146 next = this.getNextNode_(next); | |
| 147 if (next) { | |
| 148 this.node_ = next; | |
| 149 this.printNode_(this.node_); | |
| 150 return; | |
| 151 } | |
| 152 } | |
| 153 | |
| 154 if (this.root_) { | |
| 155 console.log('Reached the last interesting node. Restarting with first.'); | |
| 156 let next = this.root_.firstChild; | |
| 157 while (next && !this.isInteresting_(next)) | |
| 158 next = this.getNextNode_(next); | |
| 159 if (next) { | |
| 160 this.node_ = next; | |
| 161 this.printNode_(this.node_); | |
| 162 return; | |
| 163 } | |
| 164 } | |
| 165 | |
| 166 console.log('Found no interesting nodes to visit.'); | |
| 167 }, | |
| 168 | |
| 169 /** | |
| 170 * Given a flat list of nodes in pre-order, get the node that comes after | |
| 171 * |node|. | |
| 172 * | |
| 173 * @param {!AutomationNode} node | |
| 174 * @return {AutomationNode} | |
| 175 * @private | |
| 176 */ | |
| 177 getNextNode_: function(node) { | |
| 178 // Check for child. | |
| 179 let child = node.firstChild; | |
| 180 if (child) | |
| 181 return child; | |
| 182 | |
| 183 // No child. Check for right-sibling. | |
| 184 let sibling = node.nextSibling; | |
| 185 if (sibling) | |
| 186 return sibling; | |
| 187 | |
| 188 // No right-sibling. Get right-sibling of closest ancestor. | |
| 189 let ancestor = node.parent; | |
| 190 while (ancestor) { | |
| 191 let aunt = ancestor.nextSibling; | |
| 192 if (aunt) | |
| 193 return aunt; | |
| 194 ancestor = ancestor.parent; | |
| 195 } | |
| 196 | |
| 197 // No node found after |node|, so return null. | |
| 198 return null; | |
| 199 }, | |
| 200 | |
| 201 /** | |
| 202 * Given a flat list of nodes in pre-order, get the node that comes before | |
| 203 * |node|. | |
| 204 * | |
| 205 * @param {!AutomationNode} node | |
| 206 * @return {AutomationNode} | |
| 207 * @private | |
| 208 */ | |
| 209 getPreviousNode_: function(node) { | |
| 210 // Check for left-sibling. Return its youngest descendant if it has one. | |
| 211 // Otherwise, return the sibling. | |
| 212 let sibling = node.previousSibling; | |
| 213 if (sibling) { | |
| 214 let descendant = this.getYoungestDescendant_(sibling); | |
| 215 if (descendant) | |
| 216 return descendant; | |
| 217 return sibling; | |
| 218 } | |
| 219 | |
| 220 // No left-sibling. Check for parent. | |
| 221 let parent = node.parent; | |
| 222 if (parent) | |
| 223 return parent; | |
| 224 | |
| 225 // No node found before |node|, so return null. | |
| 226 return null; | |
| 227 }, | |
| 228 | |
| 229 /** | |
| 230 * Get the youngest descendant of |node| if it has one. | |
| 231 * | |
| 232 * @param {!AutomationNode} node | |
| 233 * @return {AutomationNode} | |
| 234 * @private | |
| 235 */ | |
| 236 getYoungestDescendant_: function(node) { | |
| 237 if (!node.lastChild) | |
| 238 return null; | |
| 239 | |
| 240 while (node.lastChild) | |
| 241 node = node.lastChild; | |
| 242 | |
| 243 return node; | |
| 244 }, | |
| 245 | |
| 246 /** | |
| 247 * Returns true if |node| is interesting. | |
| 248 * | |
| 249 * @param {!AutomationNode} node | |
| 250 * @return {boolean} | |
| 251 * @private | |
| 252 */ | |
| 253 isInteresting_: function(node) { | |
| 254 return node.state && node.state.focusable; | |
| 255 }, | |
| 256 | |
| 257 | |
| 258 /** | |
| 259 * Perform the default action on the currently selected node. | 114 * Perform the default action on the currently selected node. |
| 260 * | 115 * |
| 261 * @private | 116 * @override |
| 262 */ | 117 */ |
| 263 doDefault_: function() { | 118 doDefault: function() { |
| 119 if (!this.node_) | |
| 120 return; | |
| 121 | |
| 264 let state = this.node_.state; | 122 let state = this.node_.state; |
| 265 if (state && state.focusable) | 123 if (state && state.focusable) |
| 266 console.log('Node was focusable, doing default on it') | 124 console.log('Node was focusable, doing default on it') |
| 267 else if (state) | 125 else if (state) |
| 268 console.log('Node was not focusable, but still doing default'); | 126 console.log('Node was not focusable, but still doing default'); |
| 269 else | 127 else |
| 270 console.log('Node has no state, still doing default'); | 128 console.log('Node has no state, still doing default'); |
| 271 console.log('\n'); | 129 console.log('\n'); |
| 272 this.node_.doDefault(); | 130 this.node_.doDefault(); |
| 273 }, | 131 }, |
| 274 | 132 |
| 275 /** | 133 /** |
| 276 * Open the options page in a new tab. | 134 * Open the options page in a new tab. |
| 277 * | 135 * |
| 278 * @private | 136 * @override |
| 279 */ | 137 */ |
| 280 showOptionsPage_: function() { | 138 showOptionsPage: function() { |
| 281 let optionsPage = {url: 'options.html'}; | 139 let optionsPage = {url: 'options.html'}; |
| 282 chrome.tabs.create(optionsPage); | 140 chrome.tabs.create(optionsPage); |
| 283 }, | 141 }, |
| 284 | 142 |
| 285 /** | 143 /** |
| 144 * Perform actions as the result of actions by the user. Currently, restarts | |
| 145 * auto-scan if it is enabled. | |
| 146 * | |
| 147 * @override | |
| 148 */ | |
| 149 performedUserAction: function() { | |
| 150 if (this.autoScanManager_.isRunning()) | |
| 151 this.autoScanManager_.restart(); | |
| 152 }, | |
| 153 | |
| 154 /** | |
| 286 * Handle a change in user preferences. | 155 * Handle a change in user preferences. |
| 287 * | 156 * |
| 288 * @param {!Event} event | 157 * @param {!Event} event |
| 289 * @private | 158 * @private |
| 290 */ | 159 */ |
| 291 handlePrefsUpdate_: function(event) { | 160 handlePrefsUpdate_: function(event) { |
| 292 let updatedPrefs = event.detail; | 161 let updatedPrefs = event.detail; |
| 293 for (let key of Object.keys(updatedPrefs)) { | 162 for (let key of Object.keys(updatedPrefs)) { |
| 294 switch (key) { | 163 switch (key) { |
| 295 case 'enableAutoScan': | 164 case 'enableAutoScan': |
| 296 console.log('Auto-scan enabled set to: ' + updatedPrefs[key]); | 165 this.autoScanManager_.setEnabled(updatedPrefs[key]); |
| 297 break; | 166 break; |
| 298 case 'autoScanTime': | 167 case 'autoScanTime': |
| 299 console.log( | 168 this.autoScanManager_.setScanTime(updatedPrefs[key]); |
| 300 'Auto-scan time set to: ' + updatedPrefs[key] + " seconds"); | |
| 301 break; | 169 break; |
| 302 } | 170 } |
| 303 } | 171 } |
| 304 }, | 172 }, |
| 305 | 173 |
| 306 // TODO(elichtenberg): Move print functions to a custom logger class. Only | 174 // TODO(elichtenberg): Move print functions to a custom logger class. Only |
| 307 // log when debuggingEnabled is true. | 175 // log when debuggingEnabled is true. |
| 308 /** | 176 /** |
| 309 * Print out details about a node. | 177 * Print out details about a node. |
| 310 * | 178 * |
| (...skipping 14 matching lines...) Expand all Loading... | |
| 325 } | 193 } |
| 326 console.log('Has ' + node.children.length + ' children'); | 194 console.log('Has ' + node.children.length + ' children'); |
| 327 } else { | 195 } else { |
| 328 console.log('Node is null'); | 196 console.log('Node is null'); |
| 329 } | 197 } |
| 330 console.log(node); | 198 console.log(node); |
| 331 console.log('\n'); | 199 console.log('\n'); |
| 332 }, | 200 }, |
| 333 | 201 |
| 334 /** | 202 /** |
| 203 * Move to the next sibling of this.node_ if it has one. | |
| 204 * | |
| 205 * @override | |
| 206 */ | |
| 207 debugMoveToNext: function() { | |
| 208 let next = this.treeWalker_.debugMoveToNext(this.node_); | |
| 209 if (next) { | |
| 210 this.node_ = next; | |
| 211 this.printNode_(this.node_); | |
| 212 chrome.accessibilityPrivate.setFocusRing([this.node_.location]); | |
| 213 } | |
| 214 }, | |
| 215 | |
| 216 /** | |
| 335 * Move to the previous sibling of this.node_ if it has one. | 217 * Move to the previous sibling of this.node_ if it has one. |
| 336 * | 218 * |
| 337 * @private | 219 * @override |
| 338 */ | 220 */ |
| 339 debugMoveToPrevious_: function() { | 221 debugMoveToPrevious: function() { |
| 340 let previous = this.node_.previousSibling; | 222 let prev = this.treeWalker_.debugMoveToPrevious(this.node_); |
| 341 if (previous) { | 223 if (prev) { |
| 342 this.node_ = previous; | 224 this.node_ = prev; |
| 343 this.printNode_(this.node_); | 225 this.printNode_(this.node_); |
| 344 } else { | 226 chrome.accessibilityPrivate.setFocusRing([this.node_.location]); |
| 345 console.log('Node is first of siblings'); | |
| 346 console.log('\n'); | |
| 347 } | |
| 348 }, | |
| 349 | |
| 350 /** | |
| 351 * Move to the next sibling of this.node_ if it has one. | |
| 352 * | |
| 353 * @private | |
| 354 */ | |
| 355 debugMoveToNext_: function() { | |
| 356 let next = this.node_.nextSibling; | |
| 357 if (next) { | |
| 358 this.node_ = next; | |
| 359 this.printNode_(this.node_); | |
| 360 } else { | |
| 361 console.log('Node is last of siblings'); | |
| 362 console.log('\n'); | |
| 363 } | 227 } |
| 364 }, | 228 }, |
| 365 | 229 |
| 366 /** | 230 /** |
| 367 * Move to the first child of this.node_ if it has one. | 231 * Move to the first child of this.node_ if it has one. |
| 368 * | 232 * |
| 369 * @private | 233 * @override |
| 370 */ | 234 */ |
| 371 debugMoveToFirstChild_: function() { | 235 debugMoveToFirstChild: function() { |
| 372 let child = this.node_.firstChild; | 236 let child = this.treeWalker_.debugMoveToFirstChild(this.node_); |
| 373 if (child) { | 237 if (child) { |
| 374 this.node_ = child; | 238 this.node_ = child; |
| 375 this.printNode_(this.node_); | 239 this.printNode_(this.node_); |
| 376 } else { | 240 chrome.accessibilityPrivate.setFocusRing([this.node_.location]); |
| 377 console.log('Node has no children'); | |
| 378 console.log('\n'); | |
| 379 } | 241 } |
| 380 }, | 242 }, |
| 381 | 243 |
| 382 /** | 244 /** |
| 383 * Move to the parent of this.node_ if it has one. | 245 * Move to the parent of this.node_ if it has one. |
| 384 * | 246 * |
| 385 * @private | 247 * @override |
| 386 */ | 248 */ |
| 387 debugMoveToParent_: function() { | 249 debugMoveToParent: function() { |
| 388 let parent = this.node_.parent; | 250 let parent = this.treeWalker_.debugMoveToParent(this.node_); |
| 389 if (parent) { | 251 if (parent) { |
| 390 this.node_ = parent; | 252 this.node_ = parent; |
| 391 this.printNode_(this.node_); | 253 this.printNode_(this.node_); |
| 392 } else { | 254 chrome.accessibilityPrivate.setFocusRing([this.node_.location]); |
| 393 console.log('Node has no parent'); | |
| 394 console.log('\n'); | |
| 395 } | 255 } |
| 396 } | 256 } |
| 397 }; | 257 }; |
| OLD | NEW |