| 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 |