| 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} |
| 11 */ | 10 */ |
| 12 let SwitchAccess = function() { | 11 function SwitchAccess() { |
| 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 {chrome.automation.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 {chrome.automation.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)); |
| 99 }, | 81 }, |
| 100 | 82 |
| 101 /** | 83 /** |
| 102 * Set this.node_ to the previous interesting node. If no interesting node | 84 * Set this.node_ to the next/previous interesting node. If no interesting |
| 103 * comes before this.node_, set this.node_ to the last interesting node. | 85 * node is found, set this.node_ to the first/last interesting node. If |
| 86 * |doNext| is true, will search for next node. Otherwise, will search for |
| 87 * previous node. |
| 104 * | 88 * |
| 105 * @private | 89 * @param {boolean} doNext |
| 90 * @override |
| 106 */ | 91 */ |
| 107 moveToPrevious_: function() { | 92 moveToNode: function(doNext) { |
| 108 if (this.node_) { | 93 let node = this.treeWalker_.moveToNode(this.node_, this.root_, doNext); |
| 109 let prev = this.getPreviousNode_(this.node_); | 94 if (node) { |
| 110 while (prev && !this.isInteresting_(prev)) { | 95 this.node_ = node; |
| 111 prev = this.getPreviousNode_(prev); | 96 this.printNode_(this.node_); |
| 112 } | 97 chrome.accessibilityPrivate.setFocusRing([this.node_.location]); |
| 113 if (prev) { | |
| 114 this.node_ = prev; | |
| 115 this.printNode_(this.node_); | |
| 116 return; | |
| 117 } | |
| 118 } | 98 } |
| 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 }, | 99 }, |
| 135 | 100 |
| 136 /** | 101 /** |
| 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. | 102 * Perform the default action on the currently selected node. |
| 260 * | 103 * |
| 261 * @private | 104 * @override |
| 262 */ | 105 */ |
| 263 doDefault_: function() { | 106 doDefault: function() { |
| 107 if (!this.node_) |
| 108 return; |
| 109 |
| 264 let state = this.node_.state; | 110 let state = this.node_.state; |
| 265 if (state && state.focusable) | 111 if (state && state.focusable) |
| 266 console.log('Node was focusable, doing default on it') | 112 console.log('Node was focusable, doing default on it') |
| 267 else if (state) | 113 else if (state) |
| 268 console.log('Node was not focusable, but still doing default'); | 114 console.log('Node was not focusable, but still doing default'); |
| 269 else | 115 else |
| 270 console.log('Node has no state, still doing default'); | 116 console.log('Node has no state, still doing default'); |
| 271 console.log('\n'); | 117 console.log('\n'); |
| 272 this.node_.doDefault(); | 118 this.node_.doDefault(); |
| 273 }, | 119 }, |
| 274 | 120 |
| 275 /** | 121 /** |
| 276 * Open the options page in a new tab. | 122 * Open the options page in a new tab. |
| 277 * | 123 * |
| 278 * @private | 124 * @override |
| 279 */ | 125 */ |
| 280 showOptionsPage_: function() { | 126 showOptionsPage: function() { |
| 281 let optionsPage = {url: 'options.html'}; | 127 let optionsPage = {url: 'options.html'}; |
| 282 chrome.tabs.create(optionsPage); | 128 chrome.tabs.create(optionsPage); |
| 283 }, | 129 }, |
| 284 | 130 |
| 285 /** | 131 /** |
| 132 * Perform actions as the result of actions by the user. Currently, restarts |
| 133 * auto-scan if it is enabled. |
| 134 * |
| 135 * @override |
| 136 */ |
| 137 performedUserAction: function() { |
| 138 if (this.autoScanManager_.isRunning()) |
| 139 this.autoScanManager_.restart(); |
| 140 }, |
| 141 |
| 142 /** |
| 286 * Handle a change in user preferences. | 143 * Handle a change in user preferences. |
| 287 * | 144 * |
| 288 * @param {!Event} event | 145 * @param {!Event} event |
| 289 * @private | 146 * @private |
| 290 */ | 147 */ |
| 291 handlePrefsUpdate_: function(event) { | 148 handlePrefsUpdate_: function(event) { |
| 292 let updatedPrefs = event.detail; | 149 let updatedPrefs = event.detail; |
| 293 for (let key of Object.keys(updatedPrefs)) { | 150 for (let key of Object.keys(updatedPrefs)) { |
| 294 switch (key) { | 151 switch (key) { |
| 295 case 'enableAutoScan': | 152 case 'enableAutoScan': |
| 296 console.log('Auto-scan enabled set to: ' + updatedPrefs[key]); | 153 this.autoScanManager_.setEnabled(updatedPrefs[key]); |
| 297 break; | 154 break; |
| 298 case 'autoScanTime': | 155 case 'autoScanTime': |
| 299 console.log( | 156 this.autoScanManager_.setScanTime(updatedPrefs[key]); |
| 300 'Auto-scan time set to: ' + updatedPrefs[key] + " seconds"); | |
| 301 break; | 157 break; |
| 302 } | 158 } |
| 303 } | 159 } |
| 304 }, | 160 }, |
| 305 | 161 |
| 306 // TODO(elichtenberg): Move print functions to a custom logger class. Only | 162 // TODO(elichtenberg): Move print functions to a custom logger class. Only |
| 307 // log when debuggingEnabled is true. | 163 // log when debuggingEnabled is true. |
| 308 /** | 164 /** |
| 309 * Print out details about a node. | 165 * Print out details about a node. |
| 310 * | 166 * |
| 311 * @param {AutomationNode} node | 167 * @param {chrome.automation.AutomationNode} node |
| 312 * @private | 168 * @private |
| 313 */ | 169 */ |
| 314 printNode_: function(node) { | 170 printNode_: function(node) { |
| 315 if (node) { | 171 if (node) { |
| 316 console.log('Name = ' + node.name); | 172 console.log('Name = ' + node.name); |
| 317 console.log('Role = ' + node.role); | 173 console.log('Role = ' + node.role); |
| 318 if (!node.parent) | 174 if (!node.parent) |
| 319 console.log('At index ' + node.indexInParent + ', has no parent'); | 175 console.log('At index ' + node.indexInParent + ', has no parent'); |
| 320 else { | 176 else { |
| 321 let numSiblings = node.parent.children.length; | 177 let numSiblings = node.parent.children.length; |
| 322 console.log( | 178 console.log( |
| 323 'At index ' + node.indexInParent + ', there are ' | 179 'At index ' + node.indexInParent + ', there are ' |
| 324 + numSiblings + ' siblings'); | 180 + numSiblings + ' siblings'); |
| 325 } | 181 } |
| 326 console.log('Has ' + node.children.length + ' children'); | 182 console.log('Has ' + node.children.length + ' children'); |
| 327 } else { | 183 } else { |
| 328 console.log('Node is null'); | 184 console.log('Node is null'); |
| 329 } | 185 } |
| 330 console.log(node); | 186 console.log(node); |
| 331 console.log('\n'); | 187 console.log('\n'); |
| 332 }, | 188 }, |
| 333 | 189 |
| 334 /** | 190 /** |
| 191 * Move to the next sibling of this.node_ if it has one. |
| 192 * |
| 193 * @override |
| 194 */ |
| 195 debugMoveToNext: function() { |
| 196 let next = this.treeWalker_.debugMoveToNext(this.node_); |
| 197 if (next) { |
| 198 this.node_ = next; |
| 199 this.printNode_(this.node_); |
| 200 chrome.accessibilityPrivate.setFocusRing([this.node_.location]); |
| 201 } |
| 202 }, |
| 203 |
| 204 /** |
| 335 * Move to the previous sibling of this.node_ if it has one. | 205 * Move to the previous sibling of this.node_ if it has one. |
| 336 * | 206 * |
| 337 * @private | 207 * @override |
| 338 */ | 208 */ |
| 339 debugMoveToPrevious_: function() { | 209 debugMoveToPrevious: function() { |
| 340 let previous = this.node_.previousSibling; | 210 let prev = this.treeWalker_.debugMoveToPrevious(this.node_); |
| 341 if (previous) { | 211 if (prev) { |
| 342 this.node_ = previous; | 212 this.node_ = prev; |
| 343 this.printNode_(this.node_); | 213 this.printNode_(this.node_); |
| 344 } else { | 214 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 } | 215 } |
| 364 }, | 216 }, |
| 365 | 217 |
| 366 /** | 218 /** |
| 367 * Move to the first child of this.node_ if it has one. | 219 * Move to the first child of this.node_ if it has one. |
| 368 * | 220 * |
| 369 * @private | 221 * @override |
| 370 */ | 222 */ |
| 371 debugMoveToFirstChild_: function() { | 223 debugMoveToFirstChild: function() { |
| 372 let child = this.node_.firstChild; | 224 let child = this.treeWalker_.debugMoveToFirstChild(this.node_); |
| 373 if (child) { | 225 if (child) { |
| 374 this.node_ = child; | 226 this.node_ = child; |
| 375 this.printNode_(this.node_); | 227 this.printNode_(this.node_); |
| 376 } else { | 228 chrome.accessibilityPrivate.setFocusRing([this.node_.location]); |
| 377 console.log('Node has no children'); | |
| 378 console.log('\n'); | |
| 379 } | 229 } |
| 380 }, | 230 }, |
| 381 | 231 |
| 382 /** | 232 /** |
| 383 * Move to the parent of this.node_ if it has one. | 233 * Move to the parent of this.node_ if it has one. |
| 384 * | 234 * |
| 385 * @private | 235 * @override |
| 386 */ | 236 */ |
| 387 debugMoveToParent_: function() { | 237 debugMoveToParent: function() { |
| 388 let parent = this.node_.parent; | 238 let parent = this.treeWalker_.debugMoveToParent(this.node_); |
| 389 if (parent) { | 239 if (parent) { |
| 390 this.node_ = parent; | 240 this.node_ = parent; |
| 391 this.printNode_(this.node_); | 241 this.printNode_(this.node_); |
| 392 } else { | 242 chrome.accessibilityPrivate.setFocusRing([this.node_.location]); |
| 393 console.log('Node has no parent'); | |
| 394 console.log('\n'); | |
| 395 } | 243 } |
| 396 } | 244 } |
| 397 }; | 245 }; |
| OLD | NEW |