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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: chrome/browser/resources/chromeos/chromevox/cvox2/background/panel.js
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/panel.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/panel.js
index 365c7c71b4d2f9e9a306f12d05e8aef7c05ae860..149f353f7f134443bba055fe54b644b09556dada 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/panel.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/panel.js
@@ -3,18 +3,17 @@
// found in the LICENSE file.
/**
- * @fileoverview ChromeVox panel.
- *
+ * @fileoverview The ChromeVox panel and menus.
*/
goog.provide('Panel');
goog.require('Msgs');
goog.require('PanelCommand');
-
-function $(id) {
- return document.getElementById(id);
-}
+goog.require('PanelMenu');
+goog.require('PanelMenuItem');
+goog.require('cvox.ChromeVoxKbHandler');
+goog.require('cvox.CommandStore');
/**
* Class to manage the panel.
@@ -42,7 +41,41 @@ Panel.init = function() {
/** @type {Element} @private */
this.brailleCellsElement_ = $('braille-cells');
+ /**
+ * The array of top-level menus.
+ * @type {!Array<PanelMenu>}
+ * @private
+ */
+ this.menus_ = [];
+
+ /**
+ * The currently active menu, if any.
+ * @type {PanelMenu}
+ * @private
+ */
+ this.activeMenu_ = null;
+
+ /**
+ * True if the menu button in the panel is enabled at all. It's disabled if
+ * ChromeVox Next is not active.
+ * @type {boolean}
+ * @private
+ */
+ this.menusEnabled_ = false;
+
+ /**
+ * A callback function to be executed to perform the action from selecting
+ * a menu item after the menu has been closed and focus has been restored
+ * to the page or wherever it was previously.
+ * @type {?Function}
+ * @private
+ */
+ this.pendingCallback_ = null;
+
Panel.updateFromPrefs();
+
+ Msgs.addTranslatedMessagesToDom(document);
+
window.addEventListener('storage', function(event) {
if (event.key == 'brailleCaptions') {
Panel.updateFromPrefs();
@@ -54,14 +87,12 @@ Panel.init = function() {
Panel.exec(/** @type {PanelCommand} */(command));
}, false);
+ $('menus_button').addEventListener('mousedown', Panel.onOpenMenus, false);
$('options').addEventListener('click', Panel.onOptions, false);
$('close').addEventListener('click', Panel.onClose, false);
- // The ChromeVox menu isn't fully implemented yet, disable it.
- $('menu').disabled = true;
- $('triangle').style.display = 'none';
-
- Msgs.addTranslatedMessagesToDom(document);
+ document.addEventListener('keydown', Panel.onKeyDown, false);
+ document.addEventListener('mouseup', Panel.onMouseUp, false);
};
/**
@@ -120,7 +151,301 @@ Panel.exec = function(command) {
this.brailleTextElement_.textContent = command.data.text;
this.brailleCellsElement_.textContent = command.data.braille;
break;
+ case PanelCommandType.ENABLE_MENUS:
+ Panel.onEnableMenus();
+ break;
+ case PanelCommandType.DISABLE_MENUS:
+ Panel.onDisableMenus();
+ break;
+ case PanelCommandType.OPEN_MENUS:
+ Panel.onOpenMenus();
+ break;
+ }
+};
+
+/**
+ * Enable the ChromeVox Menus.
+ */
+Panel.onEnableMenus = function() {
+ Panel.menusEnabled_ = true;
+ $('menus_button').disabled = false;
+ $('triangle').style.display = '';
+};
+
+/**
+ * Disable the ChromeVox Menus.
+ */
+Panel.onDisableMenus = function() {
+ Panel.menusEnabled_ = false;
+ $('menus_button').disabled = true;
+ $('triangle').style.display = 'none';
+};
+
+/**
+ * Open / show the ChromeVox Menus.
+ * @param {Event=} opt_event An optional event that triggered this.
+ */
+Panel.onOpenMenus = function(opt_event) {
+ // Don't open the menu if it's not enabled, such as when ChromeVox Next
+ // is not active.
+ if (!Panel.menusEnabled_)
+ return;
+
+ // Eat the event so that a mousedown isn't turned into a drag, allowing
+ // users to click-drag-release to select a menu item.
+ if (opt_event) {
+ opt_event.stopPropagation();
+ opt_event.preventDefault();
+ }
+
+ // Change the url fragment to 'fullscreen', which signals the native
+ // host code to make the window fullscreen, revealing the menus.
+ window.location = '#fullscreen';
+
+ // Clear any existing menus and clear the callback.
+ Panel.clearMenus();
+ Panel.pendingCallback_ = null;
+
+ // Build the top-level menus.
+ var jumpMenu = Panel.addMenu('Jump');
+ var speechMenu = Panel.addMenu('Speech');
+ var tabsMenu = Panel.addMenu('Tabs');
+ var chromevoxMenu = Panel.addMenu('ChromeVox');
+
+ // Create a mapping between categories from CommandStore, and our
+ // top-level menus. Some categories aren't mapped to any menu.
+ var categoryToMenu = {
+ 'navigation': jumpMenu,
+ 'jump_commands': jumpMenu,
+ 'controlling_speech': speechMenu,
+ 'modifier_keys': chromevoxMenu,
+ 'help_commands': chromevoxMenu,
+
+ 'information': null, // Get link URL, get page title, etc.
+ 'overview': null, // Headings list, etc.
+ 'tables': null, // Table navigation.
+ 'braille': null,
+ 'developer': null};
+
+ // Get the key map from the background page.
+ var bkgnd = chrome.extension.getBackgroundPage();
+ var keymap = bkgnd['cvox']['KeyMap']['fromCurrentKeyMap']();
+
+ // Make a copy of the key bindings, get the localized title of each
+ // command, and then sort them.
+ var sortedBindings = keymap.bindings().slice();
+ sortedBindings.forEach(goog.bind(function(binding) {
+ var command = binding.command;
+ var keySeq = binding.sequence;
+ binding.keySeq = cvox.KeyUtil.keySequenceToString(keySeq, true);
+ var titleMsgId = cvox.CommandStore.messageForCommand(command);
+ if (!titleMsgId) {
+ console.error('No localization for: ' + command);
+ binding.title = '';
+ return;
+ }
+ var title = Msgs.getMsg(titleMsgId);
+ // Convert to title case.
+ title = title.replace(/\w\S*/g, function(word) {
+ return word.charAt(0).toUpperCase() + word.substr(1);
+ });
+ binding.title = title;
+ }, this));
+ sortedBindings.sort(function(binding1, binding2) {
+ return binding1.title.localeCompare(binding2.title);
+ });
+
+ // Insert items from the bindings into the menus.
+ sortedBindings.forEach(goog.bind(function(binding) {
+ var category = cvox.CommandStore.categoryForCommand(binding.command);
+ var menu = category ? categoryToMenu[category] : null;
+ if (binding.title && menu) {
+ menu.addMenuItem(
+ binding.title,
+ binding.keySeq,
+ function() {
+ var bkgnd =
+ chrome.extension.getBackgroundPage()['global']['backgroundObj'];
+ bkgnd['onGotCommand'](binding.command);
+ });
+ }
+ }, this));
+
+ // Add all open tabs to the Tabs menu.
+ bkgnd.chrome.windows.getLastFocused(function(lastFocusedWindow) {
+ bkgnd.chrome.windows.getAll({'populate': true}, function(windows) {
+ for (var i = 0; i < windows.length; i++) {
+ var tabs = windows[i].tabs;
+ for (var j = 0; j < tabs.length; j++) {
+ var title = tabs[j].title;
+ if (tabs[j].active && windows[i].id == lastFocusedWindow.id)
+ title += ' ' + Msgs.getMsg('active_tab');
+ tabsMenu.addMenuItem(title, '', (function(win, tab) {
+ bkgnd.chrome.windows.update(win.id, {focused: true}, function() {
+ bkgnd.chrome.tabs.update(tab.id, {active: true});
+ });
+ }).bind(this, windows[i], tabs[j]));
+ }
+ }
+ });
+ });
+
+ // Add a menu item that disables / closes ChromeVox.
+ chromevoxMenu.addMenuItem(
+ Msgs.getMsg('disable_chromevox'), 'Ctrl+Alt+Z', function() {
+ Panel.onClose();
+ });
+
+ // Activate the first menu.
+ Panel.activateMenu(Panel.menus_[0]);
+};
+
+/**
+ * Clear any previous menus. The menus are all regenerated each time the
+ * menus are opened.
+ */
+Panel.clearMenus = function() {
+ while (this.menus_.length) {
+ var menu = this.menus_.pop();
+ $('menu-bar').removeChild(menu.menuBarItemElement);
+ $('menus_background').removeChild(menu.menuContainerElement);
+ }
+ this.activeMenu_ = null;
+};
+
+/**
+ * Create a new menu with the given name and add it to the menu bar.
+ * @param {string} menuTitle The title of the new menu to add.
+ * @return {PanelMenu} The menu just created.
+ */
+Panel.addMenu = function(menuTitle) {
+ var menu = new PanelMenu(menuTitle);
+ $('menu-bar').appendChild(menu.menuBarItemElement);
+ menu.menuBarItemElement.addEventListener('mouseover', function() {
+ Panel.activateMenu(menu);
+ }, false);
+
+ $('menus_background').appendChild(menu.menuContainerElement);
+ this.menus_.push(menu);
+ return menu;
+};
+
+/**
+ * Activate a menu, which implies hiding the previous active menu.
+ * @param {PanelMenu} menu The new menu to activate.
+ */
+Panel.activateMenu = function(menu) {
+ if (menu == this.activeMenu_)
+ return;
+
+ if (this.activeMenu_) {
+ this.activeMenu_.deactivate();
+ this.activeMenu_ = null;
+ }
+
+ this.activeMenu_ = menu;
+ this.pendingCallback_ = null;
+
+ if (this.activeMenu_) {
+ this.activeMenu_.activate();
+ }
+};
+
+/**
+ * Advance the index of the current active menu by |delta|.
+ * @param {number} delta The number to add to the active menu index.
+ */
+Panel.advanceActiveMenuBy = function(delta) {
+ var activeIndex = -1;
+ for (var i = 0; i < this.menus_.length; i++) {
+ if (this.activeMenu_ == this.menus_[i]) {
+ activeIndex = i;
+ break;
+ }
+ }
+
+ if (activeIndex >= 0) {
+ activeIndex += delta;
+ activeIndex = (activeIndex + this.menus_.length) % this.menus_.length;
+ } else {
+ if (delta >= 0)
+ activeIndex = 0;
+ else
+ activeIndex = this.menus_.length - 1;
}
+ Panel.activateMenu(this.menus_[activeIndex]);
+};
+
+/**
+ * Advance the index of the current active menu item by |delta|.
+ * @param {number} delta The number to add to the active menu item index.
+ */
+Panel.advanceItemBy = function(delta) {
+ if (this.activeMenu_)
+ this.activeMenu_.advanceItemBy(delta);
+};
+
+/**
+ * Called when the user releases the mouse button. If it's anywhere other
+ * than on the menus button, close the menus and return focus to the page,
+ * and if the mouse was released over a menu item, execute that item's
+ * callback.
+ * @param {Event} event The mouse event.
+ */
+Panel.onMouseUp = function(event) {
+ var target = event.target;
+ while (target && !target.classList.contains('menu-item')) {
+ // Allow the user to click and release on the menu button and leave
+ // the menu button. Otherwise releasing the mouse anywhere else will
+ // close the menu.
+ if (target.id == 'menus_button')
+ return;
+
+ target = target.parentElement;
+ }
+
+ if (target && Panel.activeMenu_)
+ Panel.pendingCallback_ = Panel.activeMenu_.getCallbackForElement(target);
+ Panel.closeMenusAndRestoreFocus();
+};
+
+/**
+ * Called when a key is pressed. Handle arrow keys to navigate the menus,
+ * Esc to close, and Enter/Space to activate an item.
+ * @param {Event} event The key event.
+ */
+Panel.onKeyDown = function(event) {
+ if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey)
+ return;
+
+ switch (event.keyIdentifier) {
+ case 'Left':
+ Panel.advanceActiveMenuBy(-1);
+ break;
+ case 'Right':
+ Panel.advanceActiveMenuBy(1);
+ break;
+ case 'Up':
+ Panel.advanceItemBy(-1);
+ break;
+ case 'Down':
+ Panel.advanceItemBy(1);
+ break;
+ case 'U+001B': // Escape
+ Panel.closeMenusAndRestoreFocus();
+ break;
+ case 'Enter': // Enter
+ case 'U+0020': // Space
+ Panel.pendingCallback_ = Panel.getCallbackForCurrentItem();
+ Panel.closeMenusAndRestoreFocus();
+ break;
+ default:
+ // Don't mark this event as handled.
+ return;
+ }
+
+ event.preventDefault();
+ event.stopPropagation();
};
/**
@@ -140,6 +465,31 @@ Panel.onClose = function() {
window.location = '#close';
};
+/**
+ * Get the callback for whatever item is currently selected.
+ * @return {Function} The callback for the current item.
+ */
+Panel.getCallbackForCurrentItem = function() {
+ if (this.activeMenu_)
+ return this.activeMenu_.getCallbackForCurrentItem();
+ return null;
+};
+
+/**
+ * Close the menus and restore focus to the page. If a menu item's callback
+ * was queued, execute it once focus is restored.
+ */
+Panel.closeMenusAndRestoreFocus = function() {
+ // Make sure we're not in full-screen mode.
+ window.location = '#';
+
+ var bkgnd =
+ chrome.extension.getBackgroundPage()['global']['backgroundObj'];
+ bkgnd['restoreCurrentRange']();
+ if (Panel.pendingCallback_)
+ Panel.pendingCallback_();
+};
+
window.addEventListener('load', function() {
Panel.init();
}, false);

Powered by Google App Engine
This is Rietveld 408576698