Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(770)

Side by Side Diff: chrome/browser/resources/chromeos/chromevox/cvox2/background/panel.js

Issue 1561773002: Implement ChromeVox Next menus. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@panel_view_type
Patch Set: Created 4 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 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 ChromeVox panel. 6 * @fileoverview The ChromeVox panel and menus.
7 *
8 */ 7 */
9 8
10 goog.provide('Panel'); 9 goog.provide('Panel');
11 10
12 goog.require('Msgs'); 11 goog.require('Msgs');
13 goog.require('PanelCommand'); 12 goog.require('PanelCommand');
14 13 goog.require('PanelMenu');
15 function $(id) { 14 goog.require('PanelMenuItem');
16 return document.getElementById(id); 15 goog.require('cvox.ChromeVoxKbHandler');
17 } 16 goog.require('cvox.CommandStore');
18 17
19 /** 18 /**
20 * Class to manage the panel. 19 * Class to manage the panel.
21 * @constructor 20 * @constructor
22 */ 21 */
23 Panel = function() { 22 Panel = function() {
24 }; 23 };
25 24
26 /** 25 /**
27 * Initialize the panel. 26 * Initialize the panel.
28 */ 27 */
29 Panel.init = function() { 28 Panel.init = function() {
30 /** @type {Element} @private */ 29 /** @type {Element} @private */
31 this.speechContainer_ = $('speech-container'); 30 this.speechContainer_ = $('speech-container');
32 31
33 /** @type {Element} @private */ 32 /** @type {Element} @private */
34 this.speechElement_ = $('speech'); 33 this.speechElement_ = $('speech');
35 34
36 /** @type {Element} @private */ 35 /** @type {Element} @private */
37 this.brailleContainer_ = $('braille-container'); 36 this.brailleContainer_ = $('braille-container');
38 37
39 /** @type {Element} @private */ 38 /** @type {Element} @private */
40 this.brailleTextElement_ = $('braille-text'); 39 this.brailleTextElement_ = $('braille-text');
41 40
42 /** @type {Element} @private */ 41 /** @type {Element} @private */
43 this.brailleCellsElement_ = $('braille-cells'); 42 this.brailleCellsElement_ = $('braille-cells');
44 43
44 /**
45 * The array of top-level menus.
46 * @type {!Array<PanelMenu>}
47 * @private
48 */
49 this.menus_ = [];
50
51 /**
52 * The currently active menu, if any.
53 * @type {?PanelMenu}
David Tseng 2016/01/08 21:39:33 Object types are nullable by default right?
dmazzoni 2016/01/11 22:04:06 Done.
54 * @private
55 */
56 this.activeMenu_ = null;
57
58 /**
59 * If the menu button in the panel is enabled at all. It's disabled if
David Tseng 2016/01/08 21:39:33 True if ...?
dmazzoni 2016/01/11 22:04:06 Done.
60 * ChromeVox Next is not active.
61 * @type {boolean}
62 * @private
63 */
64 this.menusEnabled_ = false;
65
66 /**
67 * A callback function to be executed to perform the action from selecting
68 * a menu item after the menu has been closed and focus has been restored
69 * to the page or wherever it was previously.
70 * @type {?Function}
71 * @private
72 */
73 this.pendingCallback_ = null;
74
45 Panel.updateFromPrefs(); 75 Panel.updateFromPrefs();
76
77 Msgs.addTranslatedMessagesToDom(document);
78
46 window.addEventListener('storage', function(event) { 79 window.addEventListener('storage', function(event) {
47 if (event.key == 'brailleCaptions') { 80 if (event.key == 'brailleCaptions') {
48 Panel.updateFromPrefs(); 81 Panel.updateFromPrefs();
49 } 82 }
50 }, false); 83 }, false);
51 84
52 window.addEventListener('message', function(message) { 85 window.addEventListener('message', function(message) {
53 var command = JSON.parse(message.data); 86 var command = JSON.parse(message.data);
54 Panel.exec(/** @type {PanelCommand} */(command)); 87 Panel.exec(/** @type {PanelCommand} */(command));
55 }, false); 88 }, false);
56 89
90 $('menus_button').addEventListener('mousedown', Panel.onOpenMenus, false);
57 $('options').addEventListener('click', Panel.onOptions, false); 91 $('options').addEventListener('click', Panel.onOptions, false);
58 $('close').addEventListener('click', Panel.onClose, false); 92 $('close').addEventListener('click', Panel.onClose, false);
59 93
60 // The ChromeVox menu isn't fully implemented yet, disable it. 94 document.addEventListener('keydown', Panel.onKeyDown, false);
61 $('menu').disabled = true; 95 document.addEventListener('mouseup', Panel.onMouseUp, false);
62 $('triangle').style.display = 'none';
63
64 Msgs.addTranslatedMessagesToDom(document);
65 }; 96 };
66 97
67 /** 98 /**
68 * Update the display based on prefs. 99 * Update the display based on prefs.
69 */ 100 */
70 Panel.updateFromPrefs = function() { 101 Panel.updateFromPrefs = function() {
71 if (localStorage['brailleCaptions'] === String(true)) { 102 if (localStorage['brailleCaptions'] === String(true)) {
72 this.speechContainer_.style.visibility = 'hidden'; 103 this.speechContainer_.style.visibility = 'hidden';
73 this.brailleContainer_.style.visibility = 'visible'; 104 this.brailleContainer_.style.visibility = 'visible';
74 } else { 105 } else {
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
113 case PanelCommandType.ADD_ANNOTATION_SPEECH: 144 case PanelCommandType.ADD_ANNOTATION_SPEECH:
114 if (this.speechElement_.innerHTML != '') { 145 if (this.speechElement_.innerHTML != '') {
115 this.speechElement_.innerHTML += '&nbsp;&nbsp;'; 146 this.speechElement_.innerHTML += '&nbsp;&nbsp;';
116 } 147 }
117 this.speechElement_.innerHTML += escapeForHtml(command.data); 148 this.speechElement_.innerHTML += escapeForHtml(command.data);
118 break; 149 break;
119 case PanelCommandType.UPDATE_BRAILLE: 150 case PanelCommandType.UPDATE_BRAILLE:
120 this.brailleTextElement_.textContent = command.data.text; 151 this.brailleTextElement_.textContent = command.data.text;
121 this.brailleCellsElement_.textContent = command.data.braille; 152 this.brailleCellsElement_.textContent = command.data.braille;
122 break; 153 break;
123 } 154 case PanelCommandType.ENABLE_MENUS:
124 }; 155 Panel.onEnableMenus();
125 156 break;
126 /** 157 case PanelCommandType.DISABLE_MENUS:
158 Panel.onDisableMenus();
159 break;
160 case PanelCommandType.OPEN_MENUS:
161 Panel.onOpenMenus();
162 break;
163 }
164 };
165
166 /**
167 * Enable the ChromeVox Menus.
168 */
169 Panel.onEnableMenus = function() {
170 Panel.menusEnabled_ = true;
171 $('menus_button').disabled = false;
172 $('triangle').style.display = '';
173 };
174
175 /**
176 * Disable the ChromeVox Menus.
177 */
178 Panel.onDisableMenus = function() {
179 Panel.menusEnabled_ = false;
180 $('menus_button').disabled = true;
181 $('triangle').style.display = 'none';
182 };
183
184 /**
185 * Open / show the ChromeVox Menus.
186 * @param {Event=} opt_event An optional event that triggered this.
187 */
188 Panel.onOpenMenus = function(opt_event) {
189 // Don't open the menu if it's not enabled, such as when ChromeVox Next
190 // is not active.
191 if (!Panel.menusEnabled_)
192 return;
193
194 // Eat the event so that a mousedown isn't turned into a drag, allowing
195 // users to click-drag-release to select a menu item.
196 if (opt_event) {
197 opt_event.stopPropagation();
198 opt_event.preventDefault();
199 }
200
201 // Change the url fragment to 'fullscreen', which signals the native
202 // host code to make the window fullscreen, revealing the menus.
203 window.location = '#fullscreen';
204
205 // Clear any existing menus and clear the callback.
206 Panel.clearMenus();
207 Panel.pendingCallback_ = null;
208
209 // Build the top-level menus.
210 var jumpMenu = Panel.addMenu('Jump');
211 var speechMenu = Panel.addMenu('Speech');
212 var tabsMenu = Panel.addMenu('Tabs');
213 var chromevoxMenu = Panel.addMenu('ChromeVox');
214
215 // Create a mapping between categories from CommandStore, and our
216 // top-level menus. Some categories aren't mapped to any menu.
David Tseng 2016/01/08 21:39:33 Do the non-categorized options get placed in the t
dmazzoni 2016/01/11 22:04:06 No, the top-level menus are hard-coded in the code
217 var categoryToMenu = {
David Tseng 2016/01/08 21:39:33 Could promote this to an assigned var of Panel.
dmazzoni 2016/01/11 22:04:06 Not easily because the mapping goes to PanelMenu o
218 'navigation': jumpMenu,
219 'jump_commands': jumpMenu,
220 'controlling_speech': speechMenu,
221 'modifier_keys': chromevoxMenu,
222 'help_commands': chromevoxMenu,
223
224 'information': null, // Get link URL, get page title, etc.
225 'overview': null, // Headings list, etc.
226 'tables': null, // Table navigation.
227 'braille': null,
228 'developer': null};
229
230 // Get the key map from the background page.
231 var bkgnd = chrome.extension.getBackgroundPage();
232 var keymap = bkgnd['cvox']['ChromeVoxKbHandler']['handlerKeyMap'];
David Tseng 2016/01/08 21:39:32 You could also use cvox.KeyMap.fromCurrent.
dmazzoni 2016/01/11 22:04:06 Done.
233
234 // Make a copy of the key bindings, get the localized title of each
235 // command, and then sort them.
236 var sortedBindings = keymap.bindings().slice();
237 sortedBindings.forEach(goog.bind(function(binding) {
238 var command = binding.command;
239 var keySeq = binding.sequence;
240 binding.keySeq = cvox.KeyUtil.keySequenceToString(keySeq, true);
241 var titleMsgId = cvox.CommandStore.messageForCommand(command);
242 if (!titleMsgId) {
243 console.log('No localization for: ' + command);
244 binding.title = '';
245 return;
246 }
247 var title = Msgs.getMsg(titleMsgId);
248 // Convert to title case.
249 title = title.replace(/\w\S*/g, function(word) {
250 return word.charAt(0).toUpperCase() + word.substr(1);
251 });
252 binding.title = title;
253 }, this));
254 sortedBindings.sort(function(binding1, binding2) {
255 return binding1.title.localeCompare(binding2.title);
256 });
257
258 // Insert items from the bindings into the menus.
259 sortedBindings.forEach(goog.bind(function(binding) {
260 var category = cvox.CommandStore.categoryForCommand(binding.command);
261 var menu = category ? categoryToMenu[category] : null;
262 if (binding.title && menu) {
263 menu.addMenuItem(
264 binding.title,
265 binding.keySeq,
266 function() {
267 var bkgnd =
268 chrome.extension.getBackgroundPage()['global']['backgroundObj'];
269 bkgnd['onGotCommand'](binding.command);
270 });
271 }
272 }, this));
273
274 // Add all open tabs to the Tabs menu.
275 bkgnd.chrome.windows.getAll({'populate': true}, function(windows) {
276 for (var i = 0; i < windows.length; i++) {
277 var tabs = windows[i].tabs;
278 for (var j = 0; j < tabs.length; j++) {
279 tabsMenu.addMenuItem(tabs[j].title, '', (function(win, tab) {
David Tseng 2016/01/08 21:39:33 It would be nice to include whether a tab was curr
dmazzoni 2016/01/11 22:04:06 Done.
280 bkgnd.chrome.windows.update(win.id, {focused: true}, function() {
281 bkgnd.chrome.tabs.update(tab.id, {active: true});
282 });
283 }).bind(this, windows[i], tabs[j]));
284 }
285 }
286 });
287
288 // Add a menu item that disables / closes ChromeVox.
289 chromevoxMenu.addMenuItem(
290 Msgs.getMsg('disable_chromevox'), 'Ctrl+Alt+Z', function() {
291 Panel.onClose();
292 });
293
294 // Activate the first menu.
295 Panel.activateMenu(Panel.menus_[0]);
296 };
297
298 /**
299 * Clear any previous menus. The menus are all regenerated each time the
300 * menus are opened.
301 */
302 Panel.clearMenus = function() {
303 while (this.menus_.length) {
304 var menu = this.menus_.pop();
305 $('menu-bar').removeChild(menu.menuBarItemElement);
306 $('menus_background').removeChild(menu.menuContainerElement);
307 }
308 this.activeMenu_ = null;
309 };
310
311 /**
312 * Create a new menu with the given name and add it to the menu bar.
313 * @param {string} menuTitle The title of the new menu to add.
314 * @return {PanelMenu} The menu just created.
315 */
316 Panel.addMenu = function(menuTitle) {
317 var menu = new PanelMenu(menuTitle);
318 $('menu-bar').appendChild(menu.menuBarItemElement);
319 menu.menuBarItemElement.addEventListener('mouseover', function() {
320 Panel.activateMenu(menu);
321 }, false);
322
323 $('menus_background').appendChild(menu.menuContainerElement);
324 this.menus_.push(menu);
325 return menu;
326 };
327
328 /**
329 * Activate a menu, which implies hiding the previous active menu.
330 * @param {PanelMenu} menu The new menu to activate.
331 */
332 Panel.activateMenu = function(menu) {
333 if (menu == this.activeMenu_)
334 return;
335
336 if (this.activeMenu_) {
337 this.activeMenu_.deactivate();
338 this.activeMenu_ = null;
339 }
340
341 this.activeMenu_ = menu;
342 this.pendingCallback_ = null;
343
344 if (this.activeMenu_) {
345 this.activeMenu_.activate();
346 }
347 };
348
349 /**
350 * Advance the index of the current active menu by |delta|.
351 * @param {number} delta The number to add to the active menu index.
352 */
353 Panel.advanceActiveMenuBy = function(delta) {
354 var activeIndex = -1;
355 for (var i = 0; i < this.menus_.length; i++) {
356 if (this.activeMenu_ == this.menus_[i]) {
357 activeIndex = i;
358 break;
359 }
360 }
361
362 if (activeIndex >= 0) {
363 activeIndex += delta;
364 activeIndex = (activeIndex + this.menus_.length) % this.menus_.length;
David Tseng 2016/01/08 21:39:32 Can't you just do: activeIndex %= this.menus_.leng
dmazzoni 2016/01/11 22:04:06 That wouldn't work with negative numbers. We want
365 } else {
366 if (delta >= 0)
367 activeIndex = 0;
368 else
369 activeIndex = this.menus_.length - 1;
370 }
371 Panel.activateMenu(this.menus_[activeIndex]);
372 };
373
374 /**
375 * Advance the index of the current active menu item by |delta|.
376 * @param {number} delta The number to add to the active menu item index.
377 */
378 Panel.advanceItemBy = function(delta) {
379 if (this.activeMenu_)
380 this.activeMenu_.advanceItemBy(delta);
381 };
382
383 /**
384 * Called when the user releases the mouse button. If it's anywhere other
385 * than on the menus button, close the menus and return focus to the page,
386 * and if the mouse was released over a menu item, execute that item's
387 * callback.
388 * @param {Event} event The mouse event.
389 */
390 Panel.onMouseUp = function(event) {
391 var target = event.target;
392 while (target && !target.classList.contains('menu-item')) {
393 // Allow the user to click and release on the menu button and leave
394 // the menu button. Otherwise releasing the mouse anywhere else will
395 // close the menu.
396 if (target.id == 'menus_button')
397 return;
398
399 target = target.parentElement;
400 }
401
402 if (target && Panel.activeMenu_)
403 Panel.pendingCallback_ = Panel.activeMenu_.getCallbackForElement(target);
404 Panel.closeMenusAndRestoreFocus();
405 };
406
407 /**
408 * Called when a key is pressed. Handle arrow keys to navigate the menus,
409 * Esc to close, and Enter/Space to activate an item.
410 * @param {Event} event The key event.
411 */
412 Panel.onKeyDown = function(event) {
413 if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey)
414 return;
415
416 switch (event.keyIdentifier) {
417 case 'Left':
418 Panel.advanceActiveMenuBy(-1);
419 break;
420 case 'Right':
421 Panel.advanceActiveMenuBy(1);
422 break;
423 case 'Up':
424 Panel.advanceItemBy(-1);
425 break;
426 case 'Down':
427 Panel.advanceItemBy(1);
428 break;
429 case 'U+001B': // Escape
430 Panel.closeMenusAndRestoreFocus();
431 break;
432 case 'Enter': // Enter
433 case 'U+0020': // Space
434 Panel.pendingCallback_ = Panel.getCallbackForCurrentItem();
435 Panel.closeMenusAndRestoreFocus();
436 break;
437 default:
438 // Don't mark this event as handled.
439 return;
440 }
441
442 event.preventDefault();
443 event.stopPropagation();
444 };
445
446 /**
127 * Open the ChromeVox Options. 447 * Open the ChromeVox Options.
128 */ 448 */
129 Panel.onOptions = function() { 449 Panel.onOptions = function() {
130 var bkgnd = 450 var bkgnd =
131 chrome.extension.getBackgroundPage()['global']['backgroundObj']; 451 chrome.extension.getBackgroundPage()['global']['backgroundObj'];
132 bkgnd['showOptionsPage'](); 452 bkgnd['showOptionsPage']();
133 window.location = '#'; 453 window.location = '#';
134 }; 454 };
135 455
136 /** 456 /**
137 * Exit ChromeVox. 457 * Exit ChromeVox.
138 */ 458 */
139 Panel.onClose = function() { 459 Panel.onClose = function() {
140 window.location = '#close'; 460 window.location = '#close';
141 }; 461 };
142 462
463 /**
464 * Get the callback for whatever item is currently selected.
465 * @return {Function} The callback for the current item.
466 */
467 Panel.getCallbackForCurrentItem = function() {
468 if (this.activeMenu_)
469 return this.activeMenu_.getCallbackForCurrentItem();
470 return null;
471 };
472
473 /**
474 * Close the menus and restore focus to the page. If a menu item's callback
475 * was queued, execute it once focus is restored.
476 */
477 Panel.closeMenusAndRestoreFocus = function() {
478 // Make sure we're not in full-screen mode.
479 window.location = '#';
480
481 var bkgnd =
482 chrome.extension.getBackgroundPage()['global']['backgroundObj'];
483 bkgnd['restoreCurrentRange']();
484 if (Panel.pendingCallback_)
485 Panel.pendingCallback_();
486 };
487
143 window.addEventListener('load', function() { 488 window.addEventListener('load', function() {
144 Panel.init(); 489 Panel.init();
145 }, false); 490 }, false);
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698