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

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: Fix Ozone by only activating panel when fullscreen Created 4 years, 10 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}
54 * @private
55 */
56 this.activeMenu_ = null;
57
58 /**
59 * True if the menu button in the panel is enabled at all. It's disabled if
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.
217 var categoryToMenu = {
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']['KeyMap']['fromCurrentKeyMap']();
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.error('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.getLastFocused(function(lastFocusedWindow) {
276 bkgnd.chrome.windows.getAll({'populate': true}, function(windows) {
277 for (var i = 0; i < windows.length; i++) {
278 var tabs = windows[i].tabs;
279 for (var j = 0; j < tabs.length; j++) {
280 var title = tabs[j].title;
281 if (tabs[j].active && windows[i].id == lastFocusedWindow.id)
282 title += ' ' + Msgs.getMsg('active_tab');
283 tabsMenu.addMenuItem(title, '', (function(win, tab) {
284 bkgnd.chrome.windows.update(win.id, {focused: true}, function() {
285 bkgnd.chrome.tabs.update(tab.id, {active: true});
286 });
287 }).bind(this, windows[i], tabs[j]));
288 }
289 }
290 });
291 });
292
293 // Add a menu item that disables / closes ChromeVox.
294 chromevoxMenu.addMenuItem(
295 Msgs.getMsg('disable_chromevox'), 'Ctrl+Alt+Z', function() {
296 Panel.onClose();
297 });
298
299 // Activate the first menu.
300 Panel.activateMenu(Panel.menus_[0]);
301 };
302
303 /**
304 * Clear any previous menus. The menus are all regenerated each time the
305 * menus are opened.
306 */
307 Panel.clearMenus = function() {
308 while (this.menus_.length) {
309 var menu = this.menus_.pop();
310 $('menu-bar').removeChild(menu.menuBarItemElement);
311 $('menus_background').removeChild(menu.menuContainerElement);
312 }
313 this.activeMenu_ = null;
314 };
315
316 /**
317 * Create a new menu with the given name and add it to the menu bar.
318 * @param {string} menuTitle The title of the new menu to add.
319 * @return {PanelMenu} The menu just created.
320 */
321 Panel.addMenu = function(menuTitle) {
322 var menu = new PanelMenu(menuTitle);
323 $('menu-bar').appendChild(menu.menuBarItemElement);
324 menu.menuBarItemElement.addEventListener('mouseover', function() {
325 Panel.activateMenu(menu);
326 }, false);
327
328 $('menus_background').appendChild(menu.menuContainerElement);
329 this.menus_.push(menu);
330 return menu;
331 };
332
333 /**
334 * Activate a menu, which implies hiding the previous active menu.
335 * @param {PanelMenu} menu The new menu to activate.
336 */
337 Panel.activateMenu = function(menu) {
338 if (menu == this.activeMenu_)
339 return;
340
341 if (this.activeMenu_) {
342 this.activeMenu_.deactivate();
343 this.activeMenu_ = null;
344 }
345
346 this.activeMenu_ = menu;
347 this.pendingCallback_ = null;
348
349 if (this.activeMenu_) {
350 this.activeMenu_.activate();
351 }
352 };
353
354 /**
355 * Advance the index of the current active menu by |delta|.
356 * @param {number} delta The number to add to the active menu index.
357 */
358 Panel.advanceActiveMenuBy = function(delta) {
359 var activeIndex = -1;
360 for (var i = 0; i < this.menus_.length; i++) {
361 if (this.activeMenu_ == this.menus_[i]) {
362 activeIndex = i;
363 break;
364 }
365 }
366
367 if (activeIndex >= 0) {
368 activeIndex += delta;
369 activeIndex = (activeIndex + this.menus_.length) % this.menus_.length;
370 } else {
371 if (delta >= 0)
372 activeIndex = 0;
373 else
374 activeIndex = this.menus_.length - 1;
375 }
376 Panel.activateMenu(this.menus_[activeIndex]);
377 };
378
379 /**
380 * Advance the index of the current active menu item by |delta|.
381 * @param {number} delta The number to add to the active menu item index.
382 */
383 Panel.advanceItemBy = function(delta) {
384 if (this.activeMenu_)
385 this.activeMenu_.advanceItemBy(delta);
386 };
387
388 /**
389 * Called when the user releases the mouse button. If it's anywhere other
390 * than on the menus button, close the menus and return focus to the page,
391 * and if the mouse was released over a menu item, execute that item's
392 * callback.
393 * @param {Event} event The mouse event.
394 */
395 Panel.onMouseUp = function(event) {
396 var target = event.target;
397 while (target && !target.classList.contains('menu-item')) {
398 // Allow the user to click and release on the menu button and leave
399 // the menu button. Otherwise releasing the mouse anywhere else will
400 // close the menu.
401 if (target.id == 'menus_button')
402 return;
403
404 target = target.parentElement;
405 }
406
407 if (target && Panel.activeMenu_)
408 Panel.pendingCallback_ = Panel.activeMenu_.getCallbackForElement(target);
409 Panel.closeMenusAndRestoreFocus();
410 };
411
412 /**
413 * Called when a key is pressed. Handle arrow keys to navigate the menus,
414 * Esc to close, and Enter/Space to activate an item.
415 * @param {Event} event The key event.
416 */
417 Panel.onKeyDown = function(event) {
418 if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey)
419 return;
420
421 switch (event.keyIdentifier) {
422 case 'Left':
423 Panel.advanceActiveMenuBy(-1);
424 break;
425 case 'Right':
426 Panel.advanceActiveMenuBy(1);
427 break;
428 case 'Up':
429 Panel.advanceItemBy(-1);
430 break;
431 case 'Down':
432 Panel.advanceItemBy(1);
433 break;
434 case 'U+001B': // Escape
435 Panel.closeMenusAndRestoreFocus();
436 break;
437 case 'Enter': // Enter
438 case 'U+0020': // Space
439 Panel.pendingCallback_ = Panel.getCallbackForCurrentItem();
440 Panel.closeMenusAndRestoreFocus();
441 break;
442 default:
443 // Don't mark this event as handled.
444 return;
445 }
446
447 event.preventDefault();
448 event.stopPropagation();
449 };
450
451 /**
127 * Open the ChromeVox Options. 452 * Open the ChromeVox Options.
128 */ 453 */
129 Panel.onOptions = function() { 454 Panel.onOptions = function() {
130 var bkgnd = 455 var bkgnd =
131 chrome.extension.getBackgroundPage()['global']['backgroundObj']; 456 chrome.extension.getBackgroundPage()['global']['backgroundObj'];
132 bkgnd['showOptionsPage'](); 457 bkgnd['showOptionsPage']();
133 window.location = '#'; 458 window.location = '#';
134 }; 459 };
135 460
136 /** 461 /**
137 * Exit ChromeVox. 462 * Exit ChromeVox.
138 */ 463 */
139 Panel.onClose = function() { 464 Panel.onClose = function() {
140 window.location = '#close'; 465 window.location = '#close';
141 }; 466 };
142 467
468 /**
469 * Get the callback for whatever item is currently selected.
470 * @return {Function} The callback for the current item.
471 */
472 Panel.getCallbackForCurrentItem = function() {
473 if (this.activeMenu_)
474 return this.activeMenu_.getCallbackForCurrentItem();
475 return null;
476 };
477
478 /**
479 * Close the menus and restore focus to the page. If a menu item's callback
480 * was queued, execute it once focus is restored.
481 */
482 Panel.closeMenusAndRestoreFocus = function() {
483 // Make sure we're not in full-screen mode.
484 window.location = '#';
485
486 var bkgnd =
487 chrome.extension.getBackgroundPage()['global']['backgroundObj'];
488 bkgnd['restoreCurrentRange']();
489 if (Panel.pendingCallback_)
490 Panel.pendingCallback_();
491 };
492
143 window.addEventListener('load', function() { 493 window.addEventListener('load', function() {
144 Panel.init(); 494 Panel.init();
145 }, false); 495 }, false);
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698