Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 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 * @fileoverview A drop-down menu in the ChromeVox panel. | 6 * @fileoverview A drop-down menu in the ChromeVox panel. |
| 7 */ | 7 */ |
| 8 | 8 |
| 9 goog.provide('PanelMenu'); | 9 goog.provide('PanelMenu'); |
| 10 goog.provide('PanelNodeMenu'); | 10 goog.provide('PanelNodeMenu'); |
| 11 | 11 |
| 12 goog.require('AutomationTreeWalker'); | |
| 12 goog.require('Output'); | 13 goog.require('Output'); |
| 13 goog.require('PanelMenuItem'); | 14 goog.require('PanelMenuItem'); |
| 14 goog.require('constants'); | 15 goog.require('constants'); |
| 15 goog.require('cursors.Range'); | 16 goog.require('cursors.Range'); |
| 16 | 17 |
| 17 /** | 18 /** |
| 18 * @param {string} menuMsg The msg id of the menu. | 19 * @param {string} menuMsg The msg id of the menu. |
| 19 * @constructor | 20 * @constructor |
| 20 */ | 21 */ |
| 21 PanelMenu = function(menuMsg) { | 22 PanelMenu = function(menuMsg) { |
| (...skipping 200 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 222 break; | 223 break; |
| 223 } | 224 } |
| 224 } | 225 } |
| 225 } | 226 } |
| 226 }; | 227 }; |
| 227 | 228 |
| 228 /** | 229 /** |
| 229 * @param {string} menuMsg The msg id of the menu. | 230 * @param {string} menuMsg The msg id of the menu. |
| 230 * @param {chrome.automation.AutomationNode} node ChromeVox's current position. | 231 * @param {chrome.automation.AutomationNode} node ChromeVox's current position. |
| 231 * @param {AutomationPredicate.Unary} pred Filter to use on the document. | 232 * @param {AutomationPredicate.Unary} pred Filter to use on the document. |
| 233 * @param {boolean} async If true, populates the menu asynchronously by | |
| 234 * posting a task after searching each chunk of nodes. | |
| 232 * @extends {PanelMenu} | 235 * @extends {PanelMenu} |
| 233 * @constructor | 236 * @constructor |
| 234 */ | 237 */ |
| 235 PanelNodeMenu = function(menuMsg, node, pred) { | 238 PanelNodeMenu = function(menuMsg, node, pred, async) { |
| 236 PanelMenu.call(this, menuMsg); | 239 PanelMenu.call(this, menuMsg); |
| 237 var nodes = []; | 240 this.node_ = node; |
| 238 var selectNext = false; | 241 this.pred_ = pred; |
| 239 var activeIndex = -1; | 242 this.async_ = async; |
| 240 AutomationUtil.findNodePre(node.root, constants.Dir.FORWARD, | 243 this.populate_(); |
| 241 /** @type {AutomationPredicate.Unary} */(function(n) { | 244 }; |
| 242 if (n === node) | |
| 243 selectNext = true; | |
| 244 | 245 |
| 245 if (pred(n)) { | 246 /** |
| 246 var output = new Output(); | 247 * The number of nodes to search before posting a task to finish |
| 247 var range = cursors.Range.fromNode(n); | 248 * searching. |
| 248 output.withSpeech(range, range, Output.EventType.NAVIGATE); | 249 * @const {number} |
| 249 var label = output.toString(); | 250 */ |
| 250 this.addMenuItem(label, '', function() { | 251 PanelNodeMenu.MAX_NODES_BEFORE_ASYNC = 100; |
| 251 chrome.extension.getBackgroundPage().ChromeVoxState | |
| 252 .instance['navigateToRange'](cursors.Range.fromNode(n)); | |
| 253 }); | |
| 254 if (selectNext) { | |
| 255 activeIndex = this.items_.length - 1; | |
| 256 selectNext = false; | |
| 257 } | |
| 258 } | |
| 259 }).bind(this) | |
| 260 ); | |
| 261 | |
| 262 if (!this.items_.length) { | |
| 263 this.addMenuItem( | |
| 264 Msgs.getMsg('panel_menu_item_none'), '', function() {}); | |
| 265 this.activateItem(0); | |
| 266 } | |
| 267 if (activeIndex >= 0) | |
| 268 this.activateItem(activeIndex); | |
| 269 }; | |
| 270 | 252 |
| 271 PanelNodeMenu.prototype = { | 253 PanelNodeMenu.prototype = { |
| 272 __proto__: PanelMenu.prototype, | 254 __proto__: PanelMenu.prototype, |
| 273 | 255 |
| 274 /** @override */ | 256 /** @override */ |
| 275 activate: function() { | 257 activate: function() { |
| 276 var activeItem = this.activeIndex_; | 258 var activeItem = this.activeIndex_; |
| 277 PanelMenu.prototype.activate.call(this); | 259 PanelMenu.prototype.activate.call(this); |
| 278 this.activateItem(activeItem); | 260 this.activateItem(activeItem); |
| 261 }, | |
| 262 | |
| 263 /** | |
| 264 * Create the AutomationTreeWalker and kick off the search to find | |
| 265 * nodes that match the predicate for this menu. | |
| 266 * @private | |
| 267 */ | |
| 268 populate_: function() { | |
| 269 if (!this.node_) | |
|
David Tseng
2016/10/28 21:11:34
Does this happen? Maybe you should store the range
dmazzoni
2016/10/28 21:28:06
I didn't observe it happening, but the Closure com
David Tseng
2016/10/28 23:09:15
Yes; I guess the node could in theory be detached
| |
| 270 return; | |
| 271 | |
| 272 var root = AutomationUtil.getTopLevelRoot(this.node_); | |
| 273 if (!root) | |
| 274 return; | |
| 275 | |
| 276 this.walker_ = new AutomationTreeWalker( | |
| 277 root, | |
| 278 constants.Dir.FORWARD, | |
| 279 {visit: function(node) { | |
| 280 return !AutomationPredicate.shouldIgnoreNode(node); | |
| 281 }}); | |
| 282 this.nodeCount_ = 0; | |
| 283 this.selectNext_ = false; | |
| 284 this.findMoreNodes_(); | |
| 285 }, | |
| 286 | |
| 287 /** | |
| 288 * Iterate over nodes from the tree walker. If a node matches the | |
| 289 * predicate, add an item to the menu. | |
| 290 * | |
| 291 * If |this.async_| is true, then after MAX_NODES_BEFORE_ASYNC nodes | |
| 292 * have been scanned, call setTimeout to defer searching. This frees | |
| 293 * up the main event loop to keep the panel menu responsive, otherwise | |
| 294 * it basically freezes up until all of the nodes have been found. | |
| 295 * @private | |
| 296 */ | |
| 297 findMoreNodes_: function() { | |
| 298 while (this.walker_.next().node) { | |
| 299 var node = this.walker_.node; | |
| 300 if (node == this.node_) | |
| 301 this.selectNext_ = true; | |
| 302 if (this.pred_(node)) { | |
| 303 var output = new Output(); | |
| 304 var range = cursors.Range.fromNode(node); | |
| 305 output.withSpeech(range, range, Output.EventType.NAVIGATE); | |
| 306 var label = output.toString(); | |
| 307 this.addMenuItem(label, '', function() { | |
| 308 chrome.extension.getBackgroundPage().ChromeVoxState | |
| 309 .instance['navigateToRange'](cursors.Range.fromNode(node)); | |
| 310 }); | |
| 311 if (this.selectNext_) { | |
| 312 this.activateItem(this.items_.length - 1); | |
| 313 this.selectNext_ = false; | |
| 314 } | |
| 315 } | |
| 316 | |
| 317 if (this.async_) { | |
| 318 this.nodeCount_++; | |
| 319 if (this.nodeCount_ >= PanelNodeMenu.MAX_NODES_BEFORE_ASYNC) { | |
| 320 this.nodeCount_ = 0; | |
| 321 window.setTimeout(this.findMoreNodes_.bind(this), 0); | |
| 322 return; | |
| 323 } | |
| 324 } | |
| 325 } | |
| 326 this.finish_(); | |
| 327 }, | |
| 328 | |
| 329 /** | |
| 330 * Called when we've finished searching for nodes. If no matches were | |
| 331 * found, adds an item to the menu indicating none were found. | |
| 332 * @private | |
| 333 */ | |
| 334 finish_: function() { | |
| 335 if (!this.items_.length) { | |
| 336 this.addMenuItem( | |
| 337 Msgs.getMsg('panel_menu_item_none'), '', function() {}); | |
| 338 this.activateItem(0); | |
| 339 } | |
| 279 } | 340 } |
| 280 }; | 341 }; |
| OLD | NEW |