Commit acbcc9ba authored by Akihiro Ota's avatar Akihiro Ota Committed by Commit Bot

ChromeVox Panel: Style elements as disabled when signed out.

This change:
1. Disables the options button (next to the 'disable ChromeVox button')
when signed out.
2. Disables certain menu items when signed out.
3. Disabled elements are styled visually and are populated with the
aria-disabled property so their state is announced by ChromeVox.
4. Stores session state in the panel to make this change cleaner.
5. Marks the panel's annotations element as hidden so it's only
accessible when visible.
6. Checks that UserAnnotationHandler.instance is defined before
performing any logic inside its functions. There was a case where
code in the panel triggered a call to UserAnnotationHandler when
its instance was undefined.
7. Ensures that navigation via arrow keys skips disabled menus and
items.

Bug: 1020003
Change-Id: I2b4204970e474054e53bc8425e0e538a9c3de556
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2050789Reviewed-by: default avatarDavid Tseng <dtseng@chromium.org>
Commit-Queue: Akihiro Ota <akihiroota@chromium.org>
Cr-Commit-Position: refs/heads/master@{#747561}
parent 2887e3fd
......@@ -95,7 +95,8 @@ UserAnnotationHandler = class {
*/
static getAnnotationForNode(node) {
const url = node.root.docUrl || '';
if (!UserAnnotationHandler.instance.enabled || !url) {
if (!UserAnnotationHandler.instance ||
!UserAnnotationHandler.instance.enabled || !url) {
return null;
}
......
......@@ -89,6 +89,17 @@ CommandStore.commandsForCategory = function(category) {
return ret;
};
/**
* @param {string} command The command to query.
* @return {boolean} Whether or not this command is disallowed in the OOBE.
*/
CommandStore.disallowOOBE = function(command) {
if (!CommandStore.CMD_WHITELIST[command]) {
return false;
}
return !!CommandStore.CMD_WHITELIST[command].disallowOOBE;
};
/**
* List of commands and their properties
......@@ -102,7 +113,8 @@ CommandStore.commandsForCategory = function(category) {
* nodeList: (undefined|string),
* skipInput: (undefined|boolean),
* allowEvents: (undefined|boolean),
* disallowContinuation: (undefined|boolean)}>}
* disallowContinuation: (undefined|boolean),
* disallowOOBE: (undefined|boolean)}>}
* forward: Whether this command points forward.
* backward: Whether this command points backward. If neither forward or
* backward are specified, it stays facing in the current direction.
......
......@@ -28,6 +28,14 @@ button {
padding: 0;
}
button:disabled,
.menu-item.disabled,
.menu-bar-item.disabled {
background-color: grey;
color: white;
}
#menus_button {
height: 35px;
min-width: 52px;
......
......@@ -43,7 +43,7 @@
<div id="search-container">
<input class="i18n" msgid="search_widget_intro" id="search" type="search">
</div>
<div id="annotations-container">
<div id="annotations-container" hidden>
<input class="i18n" msgid="annotations_widget_intro" id="annotations"
type="text">
<div hidden id="save-annotation-label" class="i18n"
......
......@@ -44,6 +44,16 @@ Panel = class {
* Initialize the panel.
*/
static init() {
/** @type {string} */
Panel.sessionState = '';
const updateSessionState = (sessionState) => {
Panel.sessionState = sessionState;
$('options').disabled = sessionState !== 'IN_SESSION';
};
chrome.loginState.getSessionState(updateSessionState);
chrome.loginState.onSessionStateChanged.addListener(updateSessionState);
// Called directly for initialization. In the background page context,
// |Background| subclasses |ChromeVoxState| (who's constructor is called as
// part of |Background| construction).
......@@ -407,45 +417,47 @@ Panel = class {
const CommandHandler =
chrome.extension.getBackgroundPage()['CommandHandler'];
CommandHandler['onCommand'](binding.command);
});
}, binding.command);
}
}, this));
// Only add all open tabs to the Tabs menu if we are logged in.
chrome.loginState.getSessionState(function(
/** @type {string} */ sessionState) {
if (sessionState === 'IN_OOBE_SCREEN' ||
sessionState === 'IN_LOGIN_SCREEN' ||
sessionState === 'IN_LOCK_SCREEN') {
tabsMenu.addMenuItem(
Msgs.getMsg('panel_menu_item_none'), '', '', '', function() {});
return;
}
// Add all open tabs to the Tabs menu.
bkgnd.chrome.windows.getLastFocused(function(lastFocusedWindow) {
bkgnd.chrome.windows.getAll({'populate': true}, function(windows) {
for (let i = 0; i < windows.length; i++) {
const tabs = windows[i].tabs;
for (let j = 0; j < tabs.length; j++) {
let 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 all open tabs to the Tabs menu.
bkgnd.chrome.windows.getLastFocused(function(lastFocusedWindow) {
bkgnd.chrome.windows.getAll({'populate': true}, function(windows) {
for (let i = 0; i < windows.length; i++) {
const tabs = windows[i].tabs;
for (let j = 0; j < tabs.length; j++) {
let 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]));
}
});
}
});
});
if (Panel.sessionState !== 'IN_SESSION') {
tabsMenu.disable();
// Disable commands that contain the property 'disallowOOBE'.
for (let i = 0; i < Panel.menus_.length; ++i) {
const menu = Panel.menus_[i];
for (let j = 0; j < menu.items.length; ++j) {
const item = menu.items[j];
if (CommandStore.disallowOOBE(item.element.id)) {
item.disable();
}
}
}
}
// Add a menu item that disables / closes ChromeVox.
chromevoxMenu.addMenuItem(
Msgs.getMsg('disable_chromevox'), 'Ctrl+Alt+Z', '', '', function() {
......@@ -795,9 +807,32 @@ Panel = class {
activeIndex = Panel.menus_.length - 1;
}
}
activeIndex = Panel.findEnabledMenuIndex_(activeIndex, delta > 0 ? 1 : -1);
if (activeIndex === -1) {
return;
}
Panel.activateMenu(Panel.menus_[activeIndex], true /* activateFirstItem */);
}
/**
* Starting at |startIndex|, looks for an enabled menu.
* @param {number} startIndex
* @param {number} delta
* @return {number} The index of the enabled menu. -1 if not found.
*/
static findEnabledMenuIndex_(startIndex, delta) {
const endIndex = (delta > 0) ? Panel.menus_.length : -1;
while (startIndex !== endIndex) {
if (Panel.menus_[startIndex].enabled) {
return startIndex;
}
startIndex += delta;
}
return -1;
}
/**
* Advance the index of the current active menu item by |delta|.
* @param {number} delta The number to add to the active menu item index.
......@@ -1089,7 +1124,9 @@ Panel = class {
}
const itemText = item.text.toLowerCase();
const match = itemText.includes(query) &&
(itemText !== Msgs.getMsg('panel_menu_item_none').toLowerCase());
(itemText !==
Msgs.getMsg('panel_menu_item_none').toLowerCase()) &&
item.enabled;
if (match) {
Panel.searchMenu.copyAndAddMenuItem(item);
}
......
......@@ -70,6 +70,9 @@ PanelMenu = class {
this.menuElement.addEventListener(
'keypress', this.onKeyPress_.bind(this), true);
/** @private {boolean} */
this.enabled_ = true;
}
/**
......@@ -122,6 +125,11 @@ PanelMenu = class {
* first item.
*/
activate(activateFirstItem) {
if (!this.enabled_) {
this.menuBarItemElement.focus();
return;
}
this.menuContainerElement.style.visibility = 'visible';
this.menuContainerElement.style.opacity = 1;
this.menuBarItemElement.classList.add('active');
......@@ -145,6 +153,21 @@ PanelMenu = class {
}
}
/**
* Disables this menu. When disabled, menu contents cannot be analyzed.
* When activated, focus gets placed on the menuBarItem (title element)
* instead of the first menu item.
*/
disable() {
this.enabled_ = false;
this.menuBarItemElement.classList.add('disabled');
this.menuBarItemElement.setAttribute('aria-disabled', true);
this.menuBarItemElement.setAttribute('tabindex', 0);
this.menuBarItemElement.setAttribute(
'aria-label', this.menuBarItemElement.textContent);
this.activeIndex_ = -1;
}
/**
* Hide this menu. Make it invisible first to minimize spurious
* accessibility events before the next menu activates.
......@@ -177,6 +200,10 @@ PanelMenu = class {
* @param {number} delta The number to add to the active menu item index.
*/
advanceItemBy(delta) {
if (!this.enabled_) {
return;
}
if (this.activeIndex_ >= 0) {
this.activeIndex_ += delta;
this.activeIndex_ =
......@@ -189,6 +216,13 @@ PanelMenu = class {
}
}
this.activeIndex_ = this.findEnabledItemIndex_(
this.activeIndex_, delta > 0 ? 1 : -1 /* delta */);
if (this.activeIndex_ === -1) {
return;
}
this.items_[this.activeIndex_].element.focus();
}
......@@ -251,12 +285,37 @@ PanelMenu = class {
}
}
/**
* @return {boolean} The enabled state of this menu.
*/
get enabled() {
return this.enabled_;
}
/**
* @return {Array<PanelMenuItem>}
*/
get items() {
return this.items_;
}
/**
* Starting at |startIndex|, looks for an enabled menu item.
* @param {number} startIndex
* @param {number} delta
* @return {number} The index of the enabled item. -1 if not found.
* @private
*/
findEnabledItemIndex_(startIndex, delta) {
const endIndex = (delta > 0) ? this.items_.length : -1;
while (startIndex !== endIndex) {
if (this.items_[startIndex].enabled) {
return startIndex;
}
startIndex += delta;
}
return -1;
}
};
......
......@@ -73,9 +73,31 @@ PanelMenuItem = class {
braille.textContent = menuItemBraille;
this.element.appendChild(braille);
}
/** @type {boolean} */
this.enabled_ = true;
}
/**
* @return {string} The text content of this menu item.
*/
get text() {
return this.element.textContent;
}
/**
* @return {boolean} The enabled state of this item.
*/
get enabled() {
return this.enabled_;
}
/**
* Marks this item as disabled.
*/
disable() {
this.enabled_ = false;
this.element.classList.add('disabled');
this.element.setAttribute('aria-disabled', true);
}
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment