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'); |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
44 this.menuContainerElement.appendChild(this.menuElement); | 44 this.menuContainerElement.appendChild(this.menuElement); |
45 | 45 |
46 /** | 46 /** |
47 * The items in the menu. | 47 * The items in the menu. |
48 * @type {Array<PanelMenuItem>} | 48 * @type {Array<PanelMenuItem>} |
49 * @private | 49 * @private |
50 */ | 50 */ |
51 this.items_ = []; | 51 this.items_ = []; |
52 | 52 |
53 /** | 53 /** |
54 * The return value from window.setTimeout for a function to update the | 54 * The return value from window.setTimeout for a function to update the |
55 * scroll bars after an item has been added to a menu. Used so that we | 55 * scroll bars after an item has been added to a menu. Used so that we |
56 * don't re-layout too many times. | 56 * don't re-layout too many times. |
57 * @type {?number} | 57 * @type {?number} |
58 * @private | 58 * @private |
59 */ | 59 */ |
60 this.updateScrollbarsTimeout_ = null; | 60 this.updateScrollbarsTimeout_ = null; |
61 | 61 |
62 /** | 62 /** |
63 * The current active menu item index, or -1 if none. | 63 * The current active menu item index, or -1 if none. |
64 * @type {number} | 64 * @type {number} |
65 * @private | 65 * @private |
66 */ | 66 */ |
67 this.activeIndex_ = -1; | 67 this.activeIndex_ = -1; |
68 | 68 |
69 this.menuElement.addEventListener( | 69 this.menuElement.addEventListener( |
70 'keypress', this.onKeyPress_.bind(this), true); | 70 'keypress', this.onKeyPress_.bind(this), true); |
71 }; | 71 }; |
72 | 72 |
73 PanelMenu.prototype = { | 73 PanelMenu.prototype = { |
74 /** | 74 /** |
75 * @param {string} menuItemTitle The title of the menu item. | 75 * @param {string} menuItemTitle The title of the menu item. |
76 * @param {string} menuItemShortcut The keystrokes to select this item. | 76 * @param {string} menuItemShortcut The keystrokes to select this item. |
77 * @param {Function} callback The function to call if this item is selected. | 77 * @param {Function} callback The function to call if this item is selected. |
78 * @return {!PanelMenuItem} The menu item just created. | 78 * @return {!PanelMenuItem} The menu item just created. |
79 */ | 79 */ |
80 addMenuItem: function( | 80 addMenuItem: function( |
81 menuItemTitle, menuItemShortcut, menuItemBraille, callback) { | 81 menuItemTitle, menuItemShortcut, menuItemBraille, callback) { |
82 var menuItem = new PanelMenuItem( | 82 var menuItem = new PanelMenuItem( |
83 menuItemTitle, menuItemShortcut, menuItemBraille, callback); | 83 menuItemTitle, menuItemShortcut, menuItemBraille, callback); |
84 this.items_.push(menuItem); | 84 this.items_.push(menuItem); |
85 this.menuElement.appendChild(menuItem.element); | 85 this.menuElement.appendChild(menuItem.element); |
86 | 86 |
87 // Sync the active index with focus. | 87 // Sync the active index with focus. |
88 menuItem.element.addEventListener('focus', (function(index, event) { | 88 menuItem.element.addEventListener( |
89 this.activeIndex_ = index; | 89 'focus', (function(index, event) { |
90 }).bind(this, this.items_.length - 1), false); | 90 this.activeIndex_ = index; |
| 91 }).bind(this, this.items_.length - 1), |
| 92 false); |
91 | 93 |
92 // Update the container height, adding a scroll bar if necessary - but | 94 // Update the container height, adding a scroll bar if necessary - but |
93 // to avoid excessive layout, schedule this once per batch of adding | 95 // to avoid excessive layout, schedule this once per batch of adding |
94 // menu items rather than after each add. | 96 // menu items rather than after each add. |
95 if (!this.updateScrollbarsTimeout_) { | 97 if (!this.updateScrollbarsTimeout_) { |
96 this.updateScrollbarsTimeout_ = window.setTimeout((function() { | 98 this.updateScrollbarsTimeout_ = window.setTimeout( |
97 var menuBounds = this.menuElement.getBoundingClientRect(); | 99 (function() { |
98 var maxHeight = window.innerHeight - menuBounds.top; | 100 var menuBounds = this.menuElement.getBoundingClientRect(); |
99 this.menuContainerElement.style.maxHeight = maxHeight + 'px'; | 101 var maxHeight = window.innerHeight - menuBounds.top; |
100 this.updateScrollbarsTimeout_ = null; | 102 this.menuContainerElement.style.maxHeight = maxHeight + 'px'; |
101 }).bind(this), 0); | 103 this.updateScrollbarsTimeout_ = null; |
| 104 }).bind(this), |
| 105 0); |
102 } | 106 } |
103 | 107 |
104 return menuItem; | 108 return menuItem; |
105 }, | 109 }, |
106 | 110 |
107 /** | 111 /** |
108 * Activate this menu, which means showing it and positioning it on the | 112 * Activate this menu, which means showing it and positioning it on the |
109 * screen underneath its title in the menu bar. | 113 * screen underneath its title in the menu bar. |
110 */ | 114 */ |
111 activate: function() { | 115 activate: function() { |
(...skipping 20 matching lines...) Expand all Loading... |
132 | 136 |
133 /** | 137 /** |
134 * Hide this menu. Make it invisible first to minimize spurious | 138 * Hide this menu. Make it invisible first to minimize spurious |
135 * accessibility events before the next menu activates. | 139 * accessibility events before the next menu activates. |
136 */ | 140 */ |
137 deactivate: function() { | 141 deactivate: function() { |
138 this.menuContainerElement.style.opacity = 0.001; | 142 this.menuContainerElement.style.opacity = 0.001; |
139 this.menuBarItemElement.classList.remove('active'); | 143 this.menuBarItemElement.classList.remove('active'); |
140 this.activeIndex_ = -1; | 144 this.activeIndex_ = -1; |
141 | 145 |
142 window.setTimeout((function() { | 146 window.setTimeout( |
143 this.menuContainerElement.style.visibility = 'hidden'; | 147 (function() { |
144 }).bind(this), 0); | 148 this.menuContainerElement.style.visibility = 'hidden'; |
| 149 }).bind(this), |
| 150 0); |
145 }, | 151 }, |
146 | 152 |
147 /** | 153 /** |
148 * Make a specific menu item index active. | 154 * Make a specific menu item index active. |
149 * @param {number} itemIndex The index of the menu item. | 155 * @param {number} itemIndex The index of the menu item. |
150 */ | 156 */ |
151 activateItem: function(itemIndex) { | 157 activateItem: function(itemIndex) { |
152 this.activeIndex_ = itemIndex; | 158 this.activeIndex_ = itemIndex; |
153 if (this.activeIndex_ >= 0 && this.activeIndex_ < this.items_.length) | 159 if (this.activeIndex_ >= 0 && this.activeIndex_ < this.items_.length) |
154 this.items_[this.activeIndex_].element.focus(); | 160 this.items_[this.activeIndex_].element.focus(); |
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
214 }, | 220 }, |
215 | 221 |
216 /** | 222 /** |
217 * Handles key presses for first letter accelerators. | 223 * Handles key presses for first letter accelerators. |
218 */ | 224 */ |
219 onKeyPress_: function(evt) { | 225 onKeyPress_: function(evt) { |
220 if (!this.items_.length) | 226 if (!this.items_.length) |
221 return; | 227 return; |
222 | 228 |
223 var query = String.fromCharCode(evt.charCode).toLowerCase(); | 229 var query = String.fromCharCode(evt.charCode).toLowerCase(); |
224 for (var i = this.activeIndex_ + 1; | 230 for (var i = this.activeIndex_ + 1; i != this.activeIndex_; |
225 i != this.activeIndex_; | |
226 i = (i + 1) % this.items_.length) { | 231 i = (i + 1) % this.items_.length) { |
227 if (this.items_[i].text.toLowerCase().indexOf(query) == 0) { | 232 if (this.items_[i].text.toLowerCase().indexOf(query) == 0) { |
228 this.activateItem(i); | 233 this.activateItem(i); |
229 break; | 234 break; |
230 } | 235 } |
231 } | 236 } |
232 } | 237 } |
233 }; | 238 }; |
234 | 239 |
235 /** | 240 /** |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
276 this.finish_(); | 281 this.finish_(); |
277 return; | 282 return; |
278 } | 283 } |
279 | 284 |
280 var root = AutomationUtil.getTopLevelRoot(this.node_); | 285 var root = AutomationUtil.getTopLevelRoot(this.node_); |
281 if (!root) { | 286 if (!root) { |
282 this.finish_(); | 287 this.finish_(); |
283 return; | 288 return; |
284 } | 289 } |
285 | 290 |
286 this.walker_ = new AutomationTreeWalker( | 291 this.walker_ = new AutomationTreeWalker(root, constants.Dir.FORWARD, { |
287 root, | 292 visit: function(node) { |
288 constants.Dir.FORWARD, | 293 return !AutomationPredicate.shouldIgnoreNode(node); |
289 {visit: function(node) { | 294 } |
290 return !AutomationPredicate.shouldIgnoreNode(node); | 295 }); |
291 }}); | |
292 this.nodeCount_ = 0; | 296 this.nodeCount_ = 0; |
293 this.selectNext_ = false; | 297 this.selectNext_ = false; |
294 this.findMoreNodes_(); | 298 this.findMoreNodes_(); |
295 }, | 299 }, |
296 | 300 |
297 /** | 301 /** |
298 * Iterate over nodes from the tree walker. If a node matches the | 302 * Iterate over nodes from the tree walker. If a node matches the |
299 * predicate, add an item to the menu. | 303 * predicate, add an item to the menu. |
300 * | 304 * |
301 * If |this.async_| is true, then after MAX_NODES_BEFORE_ASYNC nodes | 305 * If |this.async_| is true, then after MAX_NODES_BEFORE_ASYNC nodes |
302 * have been scanned, call setTimeout to defer searching. This frees | 306 * have been scanned, call setTimeout to defer searching. This frees |
303 * up the main event loop to keep the panel menu responsive, otherwise | 307 * up the main event loop to keep the panel menu responsive, otherwise |
304 * it basically freezes up until all of the nodes have been found. | 308 * it basically freezes up until all of the nodes have been found. |
305 * @private | 309 * @private |
306 */ | 310 */ |
307 findMoreNodes_: function() { | 311 findMoreNodes_: function() { |
308 while (this.walker_.next().node) { | 312 while (this.walker_.next().node) { |
309 var node = this.walker_.node; | 313 var node = this.walker_.node; |
310 if (node == this.node_) | 314 if (node == this.node_) |
311 this.selectNext_ = true; | 315 this.selectNext_ = true; |
312 if (this.pred_(node)) { | 316 if (this.pred_(node)) { |
313 var output = new Output(); | 317 var output = new Output(); |
314 var range = cursors.Range.fromNode(node); | 318 var range = cursors.Range.fromNode(node); |
315 output.withSpeech(range, range, Output.EventType.NAVIGATE); | 319 output.withSpeech(range, range, Output.EventType.NAVIGATE); |
316 var label = output.toString(); | 320 var label = output.toString(); |
317 this.addMenuItem(label, '', '', (function() { | 321 this.addMenuItem(label, '', '', (function() { |
318 var savedNode = node; | 322 var savedNode = node; |
319 return function() { | 323 return function() { |
320 chrome.extension.getBackgroundPage().ChromeVoxState | 324 chrome.extension.getBackgroundPage() |
321 .instance['navigateToRange'](cursors.Range.fromNode(savedNode)); | 325 .ChromeVoxState.instance['navigateToRange']( |
322 }; | 326 cursors.Range.fromNode(savedNode)); |
323 }())); | 327 }; |
| 328 }())); |
324 | 329 |
325 if (this.selectNext_) { | 330 if (this.selectNext_) { |
326 this.activateItem(this.items_.length - 1); | 331 this.activateItem(this.items_.length - 1); |
327 this.selectNext_ = false; | 332 this.selectNext_ = false; |
328 } | 333 } |
329 } | 334 } |
330 | 335 |
331 if (this.async_) { | 336 if (this.async_) { |
332 this.nodeCount_++; | 337 this.nodeCount_++; |
333 if (this.nodeCount_ >= PanelNodeMenu.MAX_NODES_BEFORE_ASYNC) { | 338 if (this.nodeCount_ >= PanelNodeMenu.MAX_NODES_BEFORE_ASYNC) { |
(...skipping 12 matching lines...) Expand all Loading... |
346 * @private | 351 * @private |
347 */ | 352 */ |
348 finish_: function() { | 353 finish_: function() { |
349 if (!this.items_.length) { | 354 if (!this.items_.length) { |
350 this.addMenuItem( | 355 this.addMenuItem( |
351 Msgs.getMsg('panel_menu_item_none'), '', '', function() {}); | 356 Msgs.getMsg('panel_menu_item_none'), '', '', function() {}); |
352 this.activateItem(0); | 357 this.activateItem(0); |
353 } | 358 } |
354 } | 359 } |
355 }; | 360 }; |
OLD | NEW |