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 |