| 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 manage interactions with the accessibility tree, including moving | 6 * Class to manage interactions with the accessibility tree, including moving |
| 7 * to and selecting nodes. | 7 * to and selecting nodes. |
| 8 * | 8 * |
| 9 * @constructor | 9 * @constructor |
| 10 */ | 10 */ |
| 11 function AutomationManager() { | 11 function AutomationManager() { |
| 12 /** | 12 /** |
| 13 * Currently selected node. | 13 * Currently highlighted node. |
| 14 * | 14 * |
| 15 * @private {chrome.automation.AutomationNode} | 15 * @private {chrome.automation.AutomationNode} |
| 16 */ | 16 */ |
| 17 this.node_ = null; | 17 this.node_ = null; |
| 18 | 18 |
| 19 /** | 19 /** |
| 20 * Root node (i.e., the desktop). | 20 * The root of the subtree that the user is navigating through. |
| 21 * | 21 * |
| 22 * @private {chrome.automation.AutomationNode} | 22 * @private {chrome.automation.AutomationNode} |
| 23 */ | 23 */ |
| 24 this.root_ = null; | 24 this.scope_ = null; |
| 25 |
| 26 /** |
| 27 * The desktop node. |
| 28 * |
| 29 * @private {chrome.automation.AutomationNode} |
| 30 */ |
| 31 this.desktop_ = null; |
| 32 |
| 33 /** |
| 34 * A stack of past scopes. Allows user to traverse back to previous groups |
| 35 * after selecting one or more groups. The most recent group is at the end |
| 36 * of the array. |
| 37 * |
| 38 * @private {Array<chrome.automation.AutomationNode>} |
| 39 */ |
| 40 this.scopeStack_ = []; |
| 25 | 41 |
| 26 /** | 42 /** |
| 27 * Moves to the appropriate node in the accessibility tree. | 43 * Moves to the appropriate node in the accessibility tree. |
| 28 * | 44 * |
| 29 * @private {AutomationTreeWalker} | 45 * @private {AutomationTreeWalker} |
| 30 */ | 46 */ |
| 31 this.treeWalker_ = null; | 47 this.treeWalker_ = null; |
| 32 | 48 |
| 33 this.init_(); | 49 this.init_(); |
| 34 }; | 50 }; |
| 35 | 51 |
| 36 AutomationManager.prototype = { | 52 AutomationManager.prototype = { |
| 37 /** | 53 /** |
| 38 * Set this.node_ and this.root_ to the desktop node, and initialize the | 54 * Set this.node_, this.root_, and this.desktop_ to the desktop node, and |
| 39 * tree walker. | 55 * creates an initial tree walker. |
| 40 * | 56 * |
| 41 * @private | 57 * @private |
| 42 */ | 58 */ |
| 43 init_: function() { | 59 init_: function() { |
| 44 this.treeWalker_ = new AutomationTreeWalker(); | |
| 45 | |
| 46 chrome.automation.getDesktop(function(desktop) { | 60 chrome.automation.getDesktop(function(desktop) { |
| 47 this.node_ = desktop; | 61 this.node_ = desktop; |
| 48 this.root_ = desktop; | 62 this.scope_ = desktop; |
| 63 this.desktop_ = desktop; |
| 64 this.treeWalker_ = this.createTreeWalker_(desktop); |
| 49 console.log('AutomationNode for desktop is loaded'); | 65 console.log('AutomationNode for desktop is loaded'); |
| 50 this.printNode_(this.node_); | 66 this.printNode_(this.node_); |
| 51 }.bind(this)); | 67 }.bind(this)); |
| 52 }, | 68 }, |
| 53 | 69 |
| 54 /** | 70 /** |
| 55 * Set this.node_ to the next/previous interesting node, and then highlight | 71 * Set this.node_ to the next/previous interesting node, and then highlight |
| 56 * it on the screen. If no interesting node is found, set this.node_ to the | 72 * it on the screen. If no interesting node is found, set this.node_ to the |
| 57 * first/last interesting node. If |doNext| is true, will search for next | 73 * first/last interesting node. If |doNext| is true, will search for next |
| 58 * node. Otherwise, will search for previous node. | 74 * node. Otherwise, will search for previous node. |
| 59 * | 75 * |
| 60 * @param {boolean} doNext | 76 * @param {boolean} doNext |
| 61 */ | 77 */ |
| 62 moveToNode: function(doNext) { | 78 moveToNode: function(doNext) { |
| 63 let node = this.treeWalker_.moveToNode(this.node_, this.root_, doNext); | 79 if (!this.treeWalker_) |
| 80 return; |
| 81 |
| 82 let node = this.treeWalker_.moveToNode(doNext); |
| 64 if (node) { | 83 if (node) { |
| 65 this.node_ = node; | 84 this.node_ = node; |
| 66 this.printNode_(this.node_); | 85 this.printNode_(this.node_); |
| 67 chrome.accessibilityPrivate.setFocusRing([this.node_.location]); | 86 chrome.accessibilityPrivate.setFocusRing([this.node_.location]); |
| 68 } | 87 } |
| 69 }, | 88 }, |
| 70 | 89 |
| 71 /** | 90 /** |
| 72 * Perform the default action on the currently selected node. | 91 * Select the currently highlighted node. If the node is the current scope, |
| 92 * go back to the previous scope (i.e., create a new tree walker rooted at |
| 93 * the previous scope). If the node is a group other than the current scope, |
| 94 * create a new tree walker for the new subtree the user is scanning through. |
| 95 * Otherwise, meaning the node is interesting, perform the default action on |
| 96 * it. |
| 73 */ | 97 */ |
| 74 doDefault: function() { | 98 selectCurrentNode: function() { |
| 75 if (!this.node_) | 99 if (!this.node_ || !this.scope_ || !this.treeWalker_) |
| 76 return; | 100 return; |
| 77 | 101 |
| 102 if (this.node_ === this.scope_) { |
| 103 // Don't let user select the top-level root node (i.e., the desktop node). |
| 104 if (this.scopeStack_.length === 0) |
| 105 return; |
| 106 |
| 107 // Find a previous scope that is still valid. |
| 108 let oldScope; |
| 109 do { |
| 110 oldScope = this.scopeStack_.pop(); |
| 111 } while (oldScope && !oldScope.role); |
| 112 |
| 113 // oldScope will always be valid here, so this will always be true. |
| 114 if (oldScope) { |
| 115 this.scope_ = oldScope; |
| 116 this.treeWalker_ = this.createTreeWalker_(this.scope_, this.node_); |
| 117 } |
| 118 return; |
| 119 } |
| 120 |
| 121 if (AutomationPredicate.isGroup(this.node_, this.scope_)) { |
| 122 this.scopeStack_.push(this.scope_); |
| 123 this.scope_ = this.node_; |
| 124 this.treeWalker_ = this.createTreeWalker_(this.scope_); |
| 125 this.moveToNode(true); |
| 126 return; |
| 127 } |
| 128 |
| 78 this.node_.doDefault(); | 129 this.node_.doDefault(); |
| 79 }, | 130 }, |
| 80 | 131 |
| 132 /** |
| 133 * Create an AutomationTreeWalker for the subtree with |scope| as its root. |
| 134 * If |opt_start| is defined, the tree walker will start walking the tree |
| 135 * from |opt_start|; otherwise, it will start from |scope|. |
| 136 * |
| 137 * @param {!chrome.automation.AutomationNode} scope |
| 138 * @param {!chrome.automation.AutomationNode=} opt_start |
| 139 * @return {!AutomationTreeWalker} |
| 140 */ |
| 141 createTreeWalker_: function(scope, opt_start) { |
| 142 // If no explicit start node, start walking the tree from |scope|. |
| 143 let start = opt_start || scope; |
| 144 |
| 145 let leafPred = function(node) { |
| 146 return (node !== scope && AutomationPredicate.isSubtreeLeaf(node, scope)) |
| 147 || !AutomationPredicate.isInterestingSubtree(node); |
| 148 }; |
| 149 let visitPred = function(node) { |
| 150 // Avoid visiting the top-level root node (i.e., the desktop node). |
| 151 return node !== this.desktop_ |
| 152 && AutomationPredicate.isSubtreeLeaf(node, scope); |
| 153 }.bind(this); |
| 154 |
| 155 let restrictions = { |
| 156 leaf: leafPred, |
| 157 visit: visitPred |
| 158 }; |
| 159 return new AutomationTreeWalker(start, scope, restrictions); |
| 160 }, |
| 161 |
| 81 // TODO(elichtenberg): Move print functions to a custom logger class. Only | 162 // TODO(elichtenberg): Move print functions to a custom logger class. Only |
| 82 // log when debuggingEnabled is true. | 163 // log when debuggingEnabled is true. |
| 83 /** | 164 /** |
| 84 * Print out details about a node. | 165 * Print out details about a node. |
| 85 * | 166 * |
| 86 * @param {chrome.automation.AutomationNode} node | 167 * @param {chrome.automation.AutomationNode} node |
| 87 * @private | 168 * @private |
| 88 */ | 169 */ |
| 89 printNode_: function(node) { | 170 printNode_: function(node) { |
| 90 if (node) { | 171 if (node) { |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 148 */ | 229 */ |
| 149 debugMoveToParent: function() { | 230 debugMoveToParent: function() { |
| 150 let parent = this.treeWalker_.debugMoveToParent(this.node_); | 231 let parent = this.treeWalker_.debugMoveToParent(this.node_); |
| 151 if (parent) { | 232 if (parent) { |
| 152 this.node_ = parent; | 233 this.node_ = parent; |
| 153 this.printNode_(this.node_); | 234 this.printNode_(this.node_); |
| 154 chrome.accessibilityPrivate.setFocusRing([this.node_.location]); | 235 chrome.accessibilityPrivate.setFocusRing([this.node_.location]); |
| 155 } | 236 } |
| 156 } | 237 } |
| 157 }; | 238 }; |
| OLD | NEW |