| 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; | 5 let AutomationNode = chrome.automation.AutomationNode; |
| 6 | 6 |
| 7 let debuggingEnabled = true; | 7 let debuggingEnabled = true; |
| 8 |
| 8 /** | 9 /** |
| 9 * @constructor | 10 * @constructor |
| 10 */ | 11 */ |
| 11 let SwitchAccess = function() { | 12 let SwitchAccess = function() { |
| 12 console.log('Switch access is enabled'); | 13 console.log('Switch access is enabled'); |
| 13 | 14 |
| 14 // Currently selected node. | 15 /** |
| 15 /** @private {AutomationNode} */ | 16 * User preferences. |
| 17 * |
| 18 * @type {SwitchAccessPrefs} |
| 19 */ |
| 20 this.switchAccessPrefs = null; |
| 21 |
| 22 /** |
| 23 * Currently selected node. |
| 24 * |
| 25 * @private {AutomationNode} |
| 26 */ |
| 16 this.node_ = null; | 27 this.node_ = null; |
| 17 | 28 |
| 18 // Root node (i.e., the desktop). | 29 /** |
| 19 /** @private {AutomationNode} */ | 30 * Root node (i.e., the desktop). |
| 31 * |
| 32 * @private {AutomationNode} |
| 33 */ |
| 20 this.root_ = null; | 34 this.root_ = null; |
| 21 | 35 |
| 22 // List of nodes to push to / pop from in case this.node_ is lost. | 36 this.init_(); |
| 23 /** @private {!Array<!AutomationNode>} */ | |
| 24 this.ancestorList_ = []; | |
| 25 | |
| 26 chrome.automation.getDesktop(function(desktop) { | |
| 27 this.node_ = desktop; | |
| 28 this.root_ = desktop; | |
| 29 console.log('AutomationNode for desktop is loaded'); | |
| 30 this.printDetails_(); | |
| 31 | |
| 32 document.addEventListener('keyup', function(event) { | |
| 33 switch (event.key) { | |
| 34 case '1': | |
| 35 console.log('1 = go to previous element'); | |
| 36 this.moveToPrevious_(); | |
| 37 break; | |
| 38 case '2': | |
| 39 console.log('2 = go to next element'); | |
| 40 this.moveToNext_(); | |
| 41 break; | |
| 42 case '3': | |
| 43 console.log('3 = do default on element'); | |
| 44 this.doDefault_(); | |
| 45 break; | |
| 46 } | |
| 47 if (debuggingEnabled) { | |
| 48 switch (event.key) { | |
| 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 }.bind(this)); | |
| 70 }.bind(this)); | |
| 71 }; | 37 }; |
| 72 | 38 |
| 73 SwitchAccess.prototype = { | 39 SwitchAccess.prototype = { |
| 74 /** | 40 /** |
| 41 * Set this.node_ and this.root_ to the desktop node, and set up preferences |
| 42 * and event listeners. |
| 43 * |
| 44 * @private |
| 45 */ |
| 46 init_: function() { |
| 47 this.switchAccessPrefs = new SwitchAccessPrefs(); |
| 48 |
| 49 chrome.automation.getDesktop(function(desktop) { |
| 50 this.node_ = desktop; |
| 51 this.root_ = desktop; |
| 52 console.log('AutomationNode for desktop is loaded'); |
| 53 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)); |
| 97 |
| 98 document.addEventListener('prefsUpdate', this.handlePrefsUpdate_); |
| 99 }, |
| 100 |
| 101 /** |
| 75 * Set this.node_ to the previous interesting node. If no interesting node | 102 * Set this.node_ to the previous interesting node. If no interesting node |
| 76 * comes before this.node_, set this.node_ to the last interesting node. | 103 * comes before this.node_, set this.node_ to the last interesting node. |
| 77 * | 104 * |
| 78 * @private | 105 * @private |
| 79 */ | 106 */ |
| 80 moveToPrevious_: function() { | 107 moveToPrevious_: function() { |
| 81 if (this.node_) { | 108 if (this.node_) { |
| 82 let prev = this.getPreviousNode_(this.node_); | 109 let prev = this.getPreviousNode_(this.node_); |
| 83 while (prev && !this.isInteresting_(prev)) { | 110 while (prev && !this.isInteresting_(prev)) { |
| 84 prev = this.getPreviousNode_(prev); | 111 prev = this.getPreviousNode_(prev); |
| (...skipping 135 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 220 * Returns true if |node| is interesting. | 247 * Returns true if |node| is interesting. |
| 221 * | 248 * |
| 222 * @param {!AutomationNode} node | 249 * @param {!AutomationNode} node |
| 223 * @return {boolean} | 250 * @return {boolean} |
| 224 * @private | 251 * @private |
| 225 */ | 252 */ |
| 226 isInteresting_: function(node) { | 253 isInteresting_: function(node) { |
| 227 return node.state && node.state.focusable; | 254 return node.state && node.state.focusable; |
| 228 }, | 255 }, |
| 229 | 256 |
| 257 |
| 258 /** |
| 259 * Perform the default action on the currently selected node. |
| 260 * |
| 261 * @private |
| 262 */ |
| 263 doDefault_: function() { |
| 264 let state = this.node_.state; |
| 265 if (state && state.focusable) |
| 266 console.log('Node was focusable, doing default on it') |
| 267 else if (state) |
| 268 console.log('Node was not focusable, but still doing default'); |
| 269 else |
| 270 console.log('Node has no state, still doing default'); |
| 271 console.log('\n'); |
| 272 this.node_.doDefault(); |
| 273 }, |
| 274 |
| 275 /** |
| 276 * Open the options page in a new tab. |
| 277 * |
| 278 * @private |
| 279 */ |
| 280 showOptionsPage_: function() { |
| 281 let optionsPage = {url: 'options.html'}; |
| 282 chrome.tabs.create(optionsPage); |
| 283 }, |
| 284 |
| 285 /** |
| 286 * Handle a change in user preferences. |
| 287 * |
| 288 * @param {!Event} event |
| 289 * @private |
| 290 */ |
| 291 handlePrefsUpdate_: function(event) { |
| 292 let updatedPrefs = event.detail; |
| 293 for (let key of Object.keys(updatedPrefs)) { |
| 294 switch (key) { |
| 295 case 'enableAutoScan': |
| 296 console.log('Auto-scan enabled set to: ' + updatedPrefs[key]); |
| 297 break; |
| 298 case 'autoScanTime': |
| 299 console.log( |
| 300 'Auto-scan time set to: ' + updatedPrefs[key] + " seconds"); |
| 301 break; |
| 302 } |
| 303 } |
| 304 }, |
| 305 |
| 306 // TODO(elichtenberg): Move print functions to a custom logger class. Only |
| 307 // log when debuggingEnabled is true. |
| 308 /** |
| 309 * Print out details about a node. |
| 310 * |
| 311 * @param {AutomationNode} node |
| 312 * @private |
| 313 */ |
| 314 printNode_: function(node) { |
| 315 if (node) { |
| 316 console.log('Name = ' + node.name); |
| 317 console.log('Role = ' + node.role); |
| 318 if (!node.parent) |
| 319 console.log('At index ' + node.indexInParent + ', has no parent'); |
| 320 else { |
| 321 let numSiblings = node.parent.children.length; |
| 322 console.log( |
| 323 'At index ' + node.indexInParent + ', there are ' |
| 324 + numSiblings + ' siblings'); |
| 325 } |
| 326 console.log('Has ' + node.children.length + ' children'); |
| 327 } else { |
| 328 console.log('Node is null'); |
| 329 } |
| 330 console.log(node); |
| 331 console.log('\n'); |
| 332 }, |
| 333 |
| 230 /** | 334 /** |
| 231 * Move to the previous sibling of this.node_ if it has one. | 335 * Move to the previous sibling of this.node_ if it has one. |
| 232 * | 336 * |
| 233 * @private | 337 * @private |
| 234 */ | 338 */ |
| 235 debugMoveToPrevious_: function() { | 339 debugMoveToPrevious_: function() { |
| 236 let previous = this.node_.previousSibling; | 340 let previous = this.node_.previousSibling; |
| 237 if (previous) { | 341 if (previous) { |
| 238 this.node_ = previous; | 342 this.node_ = previous; |
| 239 this.printDetails_(); | 343 this.printNode_(this.node_); |
| 240 } else { | 344 } else { |
| 241 console.log('Node is first of siblings'); | 345 console.log('Node is first of siblings'); |
| 242 console.log('\n'); | 346 console.log('\n'); |
| 243 } | 347 } |
| 244 }, | 348 }, |
| 245 | 349 |
| 246 /** | 350 /** |
| 247 * Move to the next sibling of this.node_ if it has one. | 351 * Move to the next sibling of this.node_ if it has one. |
| 248 * | 352 * |
| 249 * @private | 353 * @private |
| 250 */ | 354 */ |
| 251 debugMoveToNext_: function() { | 355 debugMoveToNext_: function() { |
| 252 let next = this.node_.nextSibling; | 356 let next = this.node_.nextSibling; |
| 253 if (next) { | 357 if (next) { |
| 254 this.node_ = next; | 358 this.node_ = next; |
| 255 this.printDetails_(); | 359 this.printNode_(this.node_); |
| 256 } else { | 360 } else { |
| 257 console.log('Node is last of siblings'); | 361 console.log('Node is last of siblings'); |
| 258 console.log('\n'); | 362 console.log('\n'); |
| 259 } | 363 } |
| 260 }, | 364 }, |
| 261 | 365 |
| 262 /** | 366 /** |
| 263 * Move to the first child of this.node_ if it has one. | 367 * Move to the first child of this.node_ if it has one. |
| 264 * | 368 * |
| 265 * @private | 369 * @private |
| 266 */ | 370 */ |
| 267 debugMoveToFirstChild_: function() { | 371 debugMoveToFirstChild_: function() { |
| 268 let child = this.node_.firstChild; | 372 let child = this.node_.firstChild; |
| 269 if (child) { | 373 if (child) { |
| 270 this.ancestorList_.push(this.node_); | |
| 271 this.node_ = child; | 374 this.node_ = child; |
| 272 this.printDetails_(); | 375 this.printNode_(this.node_); |
| 273 } else { | 376 } else { |
| 274 console.log('Node has no children'); | 377 console.log('Node has no children'); |
| 275 console.log('\n'); | 378 console.log('\n'); |
| 276 } | 379 } |
| 277 }, | 380 }, |
| 278 | 381 |
| 279 /** | 382 /** |
| 280 * Move to the parent of this.node_ if it has one. If it does not have a | 383 * Move to the parent of this.node_ if it has one. |
| 281 * parent but it is not the top level root node, then this.node_ lost track of | |
| 282 * its neighbors, and we move to an ancestor node. | |
| 283 * | 384 * |
| 284 * @private | 385 * @private |
| 285 */ | 386 */ |
| 286 debugMoveToParent_: function() { | 387 debugMoveToParent_: function() { |
| 287 let parent = this.node_.parent; | 388 let parent = this.node_.parent; |
| 288 if (parent) { | 389 if (parent) { |
| 289 this.ancestorList_.pop(); | |
| 290 this.node_ = parent; | 390 this.node_ = parent; |
| 291 this.printDetails_(); | 391 this.printNode_(this.node_); |
| 292 } else if (this.ancestorList_.length === 0) { | 392 } else { |
| 293 console.log('Node has no parent'); | 393 console.log('Node has no parent'); |
| 294 console.log('\n'); | 394 console.log('\n'); |
| 295 } else { | |
| 296 console.log( | |
| 297 'Node could not find its parent, so moved to recent ancestor'); | |
| 298 let ancestor = this.ancestorList_.pop(); | |
| 299 this.node_ = ancestor; | |
| 300 this.printDetails_(); | |
| 301 } | 395 } |
| 302 }, | |
| 303 | |
| 304 /** | |
| 305 * Perform the default action on the currently selected node. | |
| 306 * | |
| 307 * @private | |
| 308 */ | |
| 309 doDefault_: function() { | |
| 310 let state = this.node_.state; | |
| 311 if (state && state.focusable) | |
| 312 console.log('Node was focusable, doing default on it') | |
| 313 else if (state) | |
| 314 console.log('Node was not focusable, but still doing default'); | |
| 315 else | |
| 316 console.log('Node has no state, still doing default'); | |
| 317 console.log('\n'); | |
| 318 this.node_.doDefault(); | |
| 319 }, | |
| 320 | |
| 321 // TODO(elichtenberg): Move print functions to a custom logger class. Only | |
| 322 // log when debuggingEnabled is true. | |
| 323 /** | |
| 324 * Print out details about the currently selected node and the list of | |
| 325 * ancestors. | |
| 326 * | |
| 327 * @private | |
| 328 */ | |
| 329 printDetails_: function() { | |
| 330 this.printNode_(this.node_); | |
| 331 console.log(this.ancestorList_); | |
| 332 console.log('\n'); | |
| 333 }, | |
| 334 | |
| 335 /** | |
| 336 * Print out details about a node. | |
| 337 * | |
| 338 * @param {AutomationNode} node | |
| 339 * @private | |
| 340 */ | |
| 341 printNode_: function(node) { | |
| 342 if (node) { | |
| 343 console.log('Name = ' + node.name); | |
| 344 console.log('Role = ' + node.role); | |
| 345 if (!node.parent) | |
| 346 console.log('At index ' + node.indexInParent + ', has no parent'); | |
| 347 else { | |
| 348 let numSiblings = node.parent.children.length; | |
| 349 console.log( | |
| 350 'At index ' + node.indexInParent + ', there are ' | |
| 351 + numSiblings + ' siblings'); | |
| 352 } | |
| 353 console.log('Has ' + node.children.length + ' children'); | |
| 354 } else { | |
| 355 console.log('Node is null'); | |
| 356 } | |
| 357 console.log(node); | |
| 358 console.log('\n'); | |
| 359 } | 396 } |
| 360 }; | 397 }; |
| 361 | |
| 362 window.switchAccess = new SwitchAccess(); | |
| OLD | NEW |