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 /** | 5 /** |
| 6 * Class to move to the appropriate node in the accessibility tree. | 6 * Class to move to the appropriate node in the accessibility tree. Stays in a |
| 7 * subtree determined by restrictions passed to it. | |
| 7 * | 8 * |
| 8 * @constructor | 9 * @constructor |
| 10 * @param {!chrome.automation.AutomationNode} start | |
| 11 * @param {!chrome.automation.AutomationNode} scope | |
| 12 * @param {!AutomationTreeWalker.Restriction} restrictions | |
| 9 */ | 13 */ |
| 10 function AutomationTreeWalker() {}; | 14 function AutomationTreeWalker(start, scope, restrictions) { |
| 15 /** | |
| 16 * Currently highlighted node. | |
| 17 * | |
| 18 * @private {!chrome.automation.AutomationNode} | |
| 19 */ | |
| 20 this.node_ = start; | |
| 21 | |
| 22 /** | |
| 23 * The root of the subtree that the user is navigating through. | |
| 24 * | |
| 25 * @private {!chrome.automation.AutomationNode} | |
| 26 */ | |
| 27 this.scope_ = scope; | |
| 28 | |
| 29 /** | |
| 30 * Function that returns true for a node that is a leaf of the current | |
| 31 * subtree. | |
| 32 * | |
| 33 * @private {!AutomationTreeWalker.Unary} | |
| 34 */ | |
| 35 this.leafPred_ = restrictions.leaf; | |
| 36 | |
| 37 /** | |
| 38 * Function that returns true for a node in the current subtree that should | |
| 39 * be visited. | |
| 40 * | |
| 41 * @private {!AutomationTreeWalker.Unary} | |
| 42 */ | |
| 43 this.visitPred_ = restrictions.visit; | |
| 44 }; | |
| 45 | |
| 46 /** | |
| 47 * @typedef {{leaf: AutomationTreeWalker.Unary, | |
| 48 * visit: AutomationTreeWalker.Unary}} | |
| 49 */ | |
| 50 AutomationTreeWalker.Restriction; | |
| 51 | |
| 52 /** | |
| 53 * @typedef {function(!chrome.automation.AutomationNode) : boolean} | |
| 54 */ | |
| 55 AutomationTreeWalker.Unary; | |
| 11 | 56 |
| 12 AutomationTreeWalker.prototype = { | 57 AutomationTreeWalker.prototype = { |
| 13 /** | 58 /** |
| 14 * Return the next/previous interesting node from |start|. If no interesting | 59 * Set this.node_ to the next/previous interesting node and return it. If no |
| 15 * node is found, return the first/last interesting node. If |doNext| is true, | 60 * interesting node is found, return the first/last interesting node. If |
| 16 * will search for next node. Otherwise, will search for previous node. | 61 * |doNext| is true, will search for next node. Otherwise, will search for |
| 62 * previous node. | |
|
dmazzoni
2017/05/05 18:23:19
Maybe mention that it stays within scope_?
elichtenberg
2017/05/06 00:13:53
Done.
| |
| 17 * | 63 * |
| 18 * @param {chrome.automation.AutomationNode} start | |
| 19 * @param {chrome.automation.AutomationNode} root | |
| 20 * @param {boolean} doNext | 64 * @param {boolean} doNext |
| 21 * @return {chrome.automation.AutomationNode} | 65 * @return {chrome.automation.AutomationNode} |
| 22 */ | 66 */ |
| 23 moveToNode: function(start, root, doNext) { | 67 moveToNode: function(doNext) { |
| 24 let node = start; | 68 let node = this.node_; |
| 69 do { | |
| 70 node = doNext ? this.getNextNode_(node) : this.getPreviousNode_(node); | |
| 71 } while (node && !this.visitPred_(node)); | |
| 25 if (node) { | 72 if (node) { |
| 26 do { | 73 this.node_ = node; |
| 27 node = doNext ? this.getNextNode_(node) : this.getPreviousNode_(node); | 74 return node; |
| 28 } while (node && !this.isInteresting_(node)); | |
| 29 if (node) | |
| 30 return node; | |
| 31 } | 75 } |
| 32 | 76 |
| 33 if (root) { | 77 console.log('Restarting search for node at ' + (doNext ? 'first' : 'last')); |
| 34 console.log( | 78 node = doNext ? this.scope_ : this.getYoungestDescendant_(this.scope_); |
| 35 'Restarting search for node at ' + (doNext ? 'first' : 'last')); | 79 while (node && !this.visitPred_(node)) |
| 36 node = doNext ? root.firstChild : this.getYoungestDescendant_(root); | 80 node = doNext ? this.getNextNode_(node) : this.getPreviousNode_(node); |
| 37 while (node && !this.isInteresting_(node)) | 81 if (node) { |
| 38 node = doNext ? this.getNextNode_(node) : this.getPreviousNode_(node); | 82 this.node_ = node; |
| 39 if (node) | 83 return node; |
| 40 return node; | |
| 41 } | 84 } |
| 42 | 85 |
| 43 console.log('Found no interesting nodes to visit.'); | 86 console.log('Found no interesting nodes to visit.'); |
| 44 return null; | 87 return null; |
| 45 }, | 88 }, |
| 46 | 89 |
| 90 | |
| 47 /** | 91 /** |
| 48 * Given a flat list of nodes in pre-order, get the node that comes after | 92 * Given a flat list of nodes in pre-order, get the node that comes after |
| 49 * |node|. | 93 * |node|. |
| 50 * | 94 * |
| 51 * @param {!chrome.automation.AutomationNode} node | 95 * @param {!chrome.automation.AutomationNode} node |
| 52 * @return {!chrome.automation.AutomationNode|undefined} | 96 * @return {!chrome.automation.AutomationNode|undefined} |
| 53 * @private | 97 * @private |
| 54 */ | 98 */ |
| 55 getNextNode_: function(node) { | 99 getNextNode_: function(node) { |
| 56 // Check for child. | 100 // Check for child. |
| 57 let child = node.firstChild; | 101 let child = node.firstChild; |
| 58 if (child) | 102 if (child && !this.leafPred_(node)) |
| 59 return child; | 103 return child; |
| 60 | 104 |
| 105 // Has no children, and if node is root of subtree, don't check siblings | |
| 106 // or parent. | |
| 107 if (node === this.scope_) | |
| 108 return undefined; | |
| 109 | |
| 61 // No child. Check for right-sibling. | 110 // No child. Check for right-sibling. |
| 62 let sibling = node.nextSibling; | 111 let sibling = node.nextSibling; |
| 63 if (sibling) | 112 if (sibling) |
| 64 return sibling; | 113 return sibling; |
| 65 | 114 |
| 66 // No right-sibling. Get right-sibling of closest ancestor. | 115 // No right-sibling. Get right-sibling of closest ancestor. |
| 67 let ancestor = node.parent; | 116 let ancestor = node.parent; |
| 68 while (ancestor) { | 117 while (ancestor && ancestor !== this.scope_) { |
| 69 let aunt = ancestor.nextSibling; | 118 let aunt = ancestor.nextSibling; |
| 70 if (aunt) | 119 if (aunt) |
| 71 return aunt; | 120 return aunt; |
| 72 ancestor = ancestor.parent; | 121 ancestor = ancestor.parent; |
| 73 } | 122 } |
| 74 | 123 |
| 75 // No node found after |node|, so return undefined. | 124 // No node found after |node|, so return undefined. |
| 76 return undefined; | 125 return undefined; |
| 77 }, | 126 }, |
| 78 | 127 |
| 79 /** | 128 /** |
| 80 * Given a flat list of nodes in pre-order, get the node that comes before | 129 * Given a flat list of nodes in pre-order, get the node that comes before |
| 81 * |node|. | 130 * |node|. |
| 82 * | 131 * |
| 83 * @param {!chrome.automation.AutomationNode} node | 132 * @param {!chrome.automation.AutomationNode} node |
| 84 * @return {!chrome.automation.AutomationNode|undefined} | 133 * @return {!chrome.automation.AutomationNode|undefined} |
| 85 * @private | 134 * @private |
| 86 */ | 135 */ |
| 87 getPreviousNode_: function(node) { | 136 getPreviousNode_: function(node) { |
| 137 // If node is root of subtree, there is no previous node. | |
| 138 if (node === this.scope_) | |
| 139 return undefined; | |
| 140 | |
| 88 // Check for left-sibling. If a left-sibling exists, return its youngest | 141 // Check for left-sibling. If a left-sibling exists, return its youngest |
| 89 // descendant if it has one, or otherwise return the sibling. | 142 // descendant if it has one, or otherwise return the sibling. |
| 90 let sibling = node.previousSibling; | 143 let sibling = node.previousSibling; |
| 91 if (sibling) | 144 if (sibling) |
| 92 return this.getYoungestDescendant_(sibling) || sibling; | 145 return this.getYoungestDescendant_(sibling) || sibling; |
| 93 | 146 |
| 94 // No left-sibling. Return parent if it exists; otherwise return undefined. | 147 // No left-sibling. Return parent if it exists; otherwise return undefined. |
| 95 return node.parent; | 148 //return node.parent; |
| 149 let parent = node.parent; | |
| 150 if (parent) | |
| 151 return parent; | |
| 152 | |
| 153 return undefined; | |
| 96 }, | 154 }, |
| 97 | 155 |
| 98 /** | 156 /** |
| 99 * Get the youngest descendant of |node| if it has one. | 157 * Get the youngest descendant of |node| if it has one. |
| 100 * | 158 * |
| 101 * @param {!chrome.automation.AutomationNode} node | 159 * @param {!chrome.automation.AutomationNode} node |
| 102 * @return {!chrome.automation.AutomationNode|undefined} | 160 * @return {!chrome.automation.AutomationNode|undefined} |
| 103 * @private | 161 * @private |
| 104 */ | 162 */ |
| 105 getYoungestDescendant_: function(node) { | 163 getYoungestDescendant_: function(node) { |
| 106 if (!node.lastChild) | 164 if (!node.lastChild || this.leafPred_(node)) |
| 107 return undefined; | 165 return undefined; |
| 108 | 166 |
| 109 while (node.lastChild) | 167 while (node.lastChild && !this.leafPred_(node)) |
| 110 node = node.lastChild; | 168 node = node.lastChild; |
| 111 | 169 |
| 112 return node; | 170 return node; |
| 113 }, | 171 }, |
| 114 | 172 |
| 115 /** | 173 /** |
| 116 * Returns true if |node| is interesting. | |
| 117 * | |
| 118 * @param {!chrome.automation.AutomationNode} node | |
| 119 * @return {boolean} | |
| 120 * @private | |
| 121 */ | |
| 122 isInteresting_: function(node) { | |
| 123 let loc = node.location; | |
| 124 let parent = node.parent; | |
| 125 let root = node.root; | |
| 126 let role = node.role; | |
| 127 let state = node.state; | |
| 128 | |
| 129 // Skip things that are offscreen | |
| 130 if (state[chrome.automation.StateType.OFFSCREEN] | |
| 131 || loc.top < 0 || loc.left < 0) | |
| 132 return false; | |
| 133 | |
| 134 if (parent) { | |
| 135 // crbug.com/710559 | |
| 136 // Work around for browser tabs | |
| 137 if (role === chrome.automation.RoleType.TAB | |
| 138 && parent.role === chrome.automation.RoleType.TAB_LIST | |
| 139 && root.role === chrome.automation.RoleType.DESKTOP) | |
| 140 return true; | |
| 141 } | |
| 142 | |
| 143 // The general rule that applies to everything. | |
| 144 return state[chrome.automation.StateType.FOCUSABLE] === true; | |
| 145 }, | |
| 146 | |
| 147 /** | |
| 148 * Return the next sibling of |node| if it has one. | 174 * Return the next sibling of |node| if it has one. |
| 149 * | 175 * |
| 150 * @param {chrome.automation.AutomationNode} node | 176 * @param {chrome.automation.AutomationNode} node |
| 151 * @return {chrome.automation.AutomationNode} | 177 * @return {chrome.automation.AutomationNode} |
| 152 */ | 178 */ |
| 153 debugMoveToNext: function(node) { | 179 debugMoveToNext: function(node) { |
| 154 if (!node) | 180 if (!node) |
| 155 return null; | 181 return null; |
| 156 | 182 |
| 157 let next = node.nextSibling; | 183 let next = node.nextSibling; |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 217 let parent = node.parent; | 243 let parent = node.parent; |
| 218 if (parent) { | 244 if (parent) { |
| 219 return parent; | 245 return parent; |
| 220 } else { | 246 } else { |
| 221 console.log('Node has no parent'); | 247 console.log('Node has no parent'); |
| 222 console.log('\n'); | 248 console.log('\n'); |
| 223 return null; | 249 return null; |
| 224 } | 250 } |
| 225 } | 251 } |
| 226 }; | 252 }; |
| OLD | NEW |