Chromium Code Reviews| Index: chrome/browser/resources/chromeos/chromevox/cvox2/background/panel_menu.js |
| diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/panel_menu.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/panel_menu.js |
| index f160480783052f7ac7af4e9c20cd3572ef3a8094..fd5a46300cf67c3716b4bbdbee6cf7f621181ceb 100644 |
| --- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/panel_menu.js |
| +++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/panel_menu.js |
| @@ -9,6 +9,7 @@ |
| goog.provide('PanelMenu'); |
| goog.provide('PanelNodeMenu'); |
| +goog.require('AutomationTreeWalker'); |
| goog.require('Output'); |
| goog.require('PanelMenuItem'); |
| goog.require('constants'); |
| @@ -229,45 +230,26 @@ PanelMenu.prototype = { |
| * @param {string} menuMsg The msg id of the menu. |
| * @param {chrome.automation.AutomationNode} node ChromeVox's current position. |
| * @param {AutomationPredicate.Unary} pred Filter to use on the document. |
| + * @param {boolean} async If true, populates the menu asynchronously by |
| + * posting a task after searching each chunk of nodes. |
| * @extends {PanelMenu} |
| * @constructor |
| */ |
| -PanelNodeMenu = function(menuMsg, node, pred) { |
| +PanelNodeMenu = function(menuMsg, node, pred, async) { |
| PanelMenu.call(this, menuMsg); |
| - var nodes = []; |
| - var selectNext = false; |
| - var activeIndex = -1; |
| - AutomationUtil.findNodePre(node.root, constants.Dir.FORWARD, |
| - /** @type {AutomationPredicate.Unary} */(function(n) { |
| - if (n === node) |
| - selectNext = true; |
| - |
| - if (pred(n)) { |
| - var output = new Output(); |
| - var range = cursors.Range.fromNode(n); |
| - output.withSpeech(range, range, Output.EventType.NAVIGATE); |
| - var label = output.toString(); |
| - this.addMenuItem(label, '', function() { |
| - chrome.extension.getBackgroundPage().ChromeVoxState |
| - .instance['navigateToRange'](cursors.Range.fromNode(n)); |
| - }); |
| - if (selectNext) { |
| - activeIndex = this.items_.length - 1; |
| - selectNext = false; |
| - } |
| - } |
| - }).bind(this) |
| - ); |
| - |
| - if (!this.items_.length) { |
| - this.addMenuItem( |
| - Msgs.getMsg('panel_menu_item_none'), '', function() {}); |
| - this.activateItem(0); |
| - } |
| - if (activeIndex >= 0) |
| - this.activateItem(activeIndex); |
| + this.node_ = node; |
| + this.pred_ = pred; |
| + this.async_ = async; |
| + this.populate_(); |
| }; |
| +/** |
| + * The number of nodes to search before posting a task to finish |
| + * searching. |
| + * @const {number} |
| + */ |
| +PanelNodeMenu.MAX_NODES_BEFORE_ASYNC = 100; |
| + |
| PanelNodeMenu.prototype = { |
| __proto__: PanelMenu.prototype, |
| @@ -276,5 +258,84 @@ PanelNodeMenu.prototype = { |
| var activeItem = this.activeIndex_; |
| PanelMenu.prototype.activate.call(this); |
| this.activateItem(activeItem); |
| + }, |
| + |
| + /** |
| + * Create the AutomationTreeWalker and kick off the search to find |
| + * nodes that match the predicate for this menu. |
| + * @private |
| + */ |
| + populate_: function() { |
| + 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
|
| + return; |
| + |
| + var root = AutomationUtil.getTopLevelRoot(this.node_); |
| + if (!root) |
| + return; |
| + |
| + this.walker_ = new AutomationTreeWalker( |
| + root, |
| + constants.Dir.FORWARD, |
| + {visit: function(node) { |
| + return !AutomationPredicate.shouldIgnoreNode(node); |
| + }}); |
| + this.nodeCount_ = 0; |
| + this.selectNext_ = false; |
| + this.findMoreNodes_(); |
| + }, |
| + |
| + /** |
| + * Iterate over nodes from the tree walker. If a node matches the |
| + * predicate, add an item to the menu. |
| + * |
| + * If |this.async_| is true, then after MAX_NODES_BEFORE_ASYNC nodes |
| + * have been scanned, call setTimeout to defer searching. This frees |
| + * up the main event loop to keep the panel menu responsive, otherwise |
| + * it basically freezes up until all of the nodes have been found. |
| + * @private |
| + */ |
| + findMoreNodes_: function() { |
| + while (this.walker_.next().node) { |
| + var node = this.walker_.node; |
| + if (node == this.node_) |
| + this.selectNext_ = true; |
| + if (this.pred_(node)) { |
| + var output = new Output(); |
| + var range = cursors.Range.fromNode(node); |
| + output.withSpeech(range, range, Output.EventType.NAVIGATE); |
| + var label = output.toString(); |
| + this.addMenuItem(label, '', function() { |
| + chrome.extension.getBackgroundPage().ChromeVoxState |
| + .instance['navigateToRange'](cursors.Range.fromNode(node)); |
| + }); |
| + if (this.selectNext_) { |
| + this.activateItem(this.items_.length - 1); |
| + this.selectNext_ = false; |
| + } |
| + } |
| + |
| + if (this.async_) { |
| + this.nodeCount_++; |
| + if (this.nodeCount_ >= PanelNodeMenu.MAX_NODES_BEFORE_ASYNC) { |
| + this.nodeCount_ = 0; |
| + window.setTimeout(this.findMoreNodes_.bind(this), 0); |
| + return; |
| + } |
| + } |
| + } |
| + this.finish_(); |
| + }, |
| + |
| + /** |
| + * Called when we've finished searching for nodes. If no matches were |
| + * found, adds an item to the menu indicating none were found. |
| + * @private |
| + */ |
| + finish_: function() { |
| + if (!this.items_.length) { |
| + this.addMenuItem( |
| + Msgs.getMsg('panel_menu_item_none'), '', function() {}); |
| + this.activateItem(0); |
| + } |
| } |
| }; |