Commit d7a7e0be authored by Sophie Yang's avatar Sophie Yang Committed by Commit Bot

[Switch Access] Add click handler to the back button

This change makes the back button act the same way
when clicked as when selected (both in the menu,
and when the back button appears alone while
navigating outside the menu).

Test: With emulated Chrome OS on Linux and
the Switch Access and improved text input flags enabled
(--enable-experimental-accessibility-switch-access and
--enable-experimental-accessibility-switch-access-text),
checked that the back button behaved the same way when
clicked as when selected with a switch.

Bug: 999581
Change-Id: I564f2be0da353fb1c912b090d9bc9fc58dc84f2d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1779104Reviewed-by: default avatarAnastasia Helfinstein <anastasi@google.com>
Reviewed-by: default avatarAkihiro Ota <akihiroota@chromium.org>
Commit-Queue: Sophie Yang <sophyang@google.com>
Cr-Commit-Position: refs/heads/master@{#693935}
parent 07a6a58d
......@@ -12,7 +12,8 @@ class BackButtonManager {
*/
constructor(navigationManager) {
/**
* Keeps track of when the back button is open.
* Keeps track of when the back button is open and appears alone
* (rather than as part of the menu).
* @private {boolean}
*/
this.backButtonOpen_ = false;
......@@ -24,7 +25,7 @@ class BackButtonManager {
this.menuPanel_;
/** @private {chrome.automation.AutomationNode} */
this.buttonNode_;
this.backButtonNode_;
}
/**
......@@ -53,33 +54,40 @@ class BackButtonManager {
/**
* Selects the back button, hiding the button and exiting the current scope.
* @return {boolean} Whether or not the back button was successfully selected.
*/
select() {
if (!this.backButtonOpen_)
if (this.navigationManager_.selectBackButtonInMenu()) {
return true;
}
if (!this.backButtonOpen_) {
return false;
}
if (this.navigationManager_.leaveKeyboardIfNeeded())
if (this.navigationManager_.leaveKeyboardIfNeeded()) {
return true;
}
this.navigationManager_.exitCurrentScope();
return true;
}
/**
* Returns the button node, if we have found it.
* Returns the back button node, if we have found it.
* @return {chrome.automation.AutomationNode}
*/
buttonNode() {
return this.buttonNode_;
backButtonNode() {
return this.backButtonNode_;
}
/**
* Sets the reference to the menu panel and finds the back button node.
* @param {!PanelInterface} menuPanel
* Finds the back button node.
* @param {!chrome.automation.AutomationNode} desktop
* @private
*/
init(menuPanel, desktop) {
this.menuPanel_ = menuPanel;
this.buttonNode_ =
findBackButtonNode_(desktop) {
this.backButtonNode_ =
new AutomationTreeWalker(
desktop, constants.Dir.FORWARD,
{visit: (node) => node.htmlAttributes.id === SAConstants.BACK_ID})
......@@ -87,7 +95,25 @@ class BackButtonManager {
.node;
// TODO(anastasi): Determine appropriate event and listen for it, rather
// than setting a timeout.
if (!this.buttonNode_)
setTimeout(this.init.bind(this, menuPanel, desktop), 500);
if (!this.backButtonNode_) {
setTimeout(this.findBackButtonNode_.bind(this, desktop), 500);
}
}
/**
* Sets the reference to the menu panel, sets up a click handler for the
* back button, and finds the back button node.
* @param {!PanelInterface} menuPanel
* @param {!chrome.automation.AutomationNode} desktop
*/
init(menuPanel, desktop) {
this.menuPanel_ = menuPanel;
// Ensures that the back button behaves the same way when clicked
// as when selected.
const backButtonElement = this.menuPanel_.backButtonElement();
backButtonElement.addEventListener('click', this.select.bind(this));
this.findBackButtonNode_(desktop);
}
}
......@@ -104,7 +104,7 @@ class FocusRingManager {
scopeRect = currentScopeRects[0];
this.currentScope_ = scope;
if (primary === this.backButtonManager_.buttonNode()) {
if (primary === this.backButtonManager_.backButtonNode()) {
this.backButtonManager_.show(scopeRect);
this.rings_.get(SAConstants.Focus.ID.PRIMARY).rects = [];
......
......@@ -384,12 +384,7 @@ class MenuManager {
}
/**
* Perform the action indicated by the current button (or no action if the
* entire menu is selected). If the back button is selected and the current
* menu is a submenu (i.e. not the main menu), then the current menu will be
* closed and the parent menu that opened the current menu will be re-opened.
* If the current menu is the main menu, then exit the menu panel entirely
* and return to traditional navigation.
* Perform the action indicated by the current button.
* @return {boolean} Whether this function had any effect.
*/
selectCurrentNode() {
......@@ -399,25 +394,35 @@ class MenuManager {
return false;
}
if (this.node_.role == RoleType.BUTTON) {
// An action was selected.
if (this.node_.role === RoleType.BUTTON) {
// A menu action was selected.
this.node_.doDefault();
} else {
// The back button was selected.
// Id of the menu that opened the current menu (null if the current
// menu is the main menu and not a submenu).
const parentMenuId = this.menuStack_.pop();
if (parentMenuId && this.menuOriginNode_) {
// Re-open the parent menu.
this.openMenu_(this.menuOriginNode_, parentMenuId);
} else {
this.exit();
}
this.selectBackButton();
}
return true;
}
/**
* Selects the back button for the menu. If the current menu is a submenu
* (i.e. not the main menu), then the current menu will be
* closed and the parent menu that opened the current menu will be re-opened.
* If the current menu is the main menu, then exit the menu panel entirely
* and return to traditional navigation.
*/
selectBackButton() {
// Id of the menu that opened the current menu (null if the current
// menu is the main menu and not a submenu).
const parentMenuId = this.menuStack_.pop();
if (parentMenuId && this.menuOriginNode_) {
// Re-open the parent menu.
this.openMenu_(this.menuOriginNode_, parentMenuId);
} else {
this.exit();
}
}
/**
* Sets up the connection between the menuPanel and the menuManager.
* @param {!PanelInterface} menuPanel
......
......@@ -42,11 +42,11 @@ class Panel {
let menuList = Object.values(SAConstants.MenuId);
for (const menuId of menuList) {
this.updateButtonOrder_(menuId);
}
const buttons = document.getElementsByTagName('button');
for (const button of buttons) {
this.setupButton_(button);
const menu = document.getElementById(menuId);
for (const button of menu.children) {
this.setupButton_(button);
}
}
const background = chrome.extension.getBackgroundPage();
......@@ -68,7 +68,8 @@ class Panel {
}
/**
* Adds an event listener to the given button to send a message when clicked.
* Adds an event listener to the given button to perform
* the corresponding menu action when clicked.
* @param {!Element} button
* @private
*/
......@@ -79,6 +80,14 @@ class Panel {
}.bind(this, action));
}
/**
* Get the HTML element for the back button.
* @return {Element}
*/
backButtonElement() {
return document.getElementById(SAConstants.BACK_ID);
}
/**
* Temporary function, until multiple focus rings is implemented.
* Puts a focus ring around the given menu item.
......
......@@ -42,6 +42,13 @@ class PanelInterface {
*/
currentMenuId() {}
/**
* Get the HTML element for the back button.
* @return {Element}
*/
backButtonElement() {}
/**
* Tells the menu panel to try to connect to the background page.
*/
......
......@@ -102,18 +102,33 @@ class NavigationManager {
* Open the Switch Access menu for the currently highlighted node.
*/
enterMenu() {
// If the back button is focused, select it.
if (this.backButtonManager_.select())
return;
// If we're currently visiting the Switch Access menu, this command should
// select the highlighted element.
if (this.menuManager_.selectCurrentNode())
if (this.menuManager_.selectCurrentNode()) {
return;
}
// If the back button is focused, select it.
if (this.backButtonManager_.select()) {
return;
}
this.menuManager_.enter(this.node_);
}
/**
* Selects the back button while navigating in the menu.
* @return {boolean} Whether or not the back button was successfully selected.
*/
selectBackButtonInMenu() {
if (!this.menuManager_.inMenu()) {
return false;
}
this.menuManager_.selectBackButton();
return true;
}
/**
* Find the previous interesting node and update |this.node_|. If there is no
* previous node, |this.node_| will be set to the back button.
......@@ -122,7 +137,7 @@ class NavigationManager {
if (this.menuManager_.moveBackward())
return;
if (this.node_ === this.backButtonManager_.buttonNode()) {
if (this.node_ === this.backButtonManager_.backButtonNode()) {
if (SwitchAccessPredicate.isActionable(this.scope_))
this.setCurrentNode_(this.scope_);
else
......@@ -139,7 +154,7 @@ class NavigationManager {
let node = treeWalker.next().node;
if (node === this.scope_)
node = this.backButtonManager_.buttonNode();
node = this.backButtonManager_.backButtonNode();
if (!node)
node = this.youngestDescendant_(this.scope_);
......@@ -164,7 +179,7 @@ class NavigationManager {
this.startAtValidNode_();
const backButtonNode = this.backButtonManager_.buttonNode();
const backButtonNode = this.backButtonManager_.backButtonNode();
if (this.node_ === this.scope_ && backButtonNode) {
this.setCurrentNode_(backButtonNode);
return;
......@@ -378,14 +393,17 @@ class NavigationManager {
* interesting, perform the default action on it.
*/
selectCurrentNode() {
if (this.backButtonManager_.select())
if (this.menuManager_.selectCurrentNode()) {
return;
}
if (this.menuManager_.selectCurrentNode())
if (this.backButtonManager_.select()) {
return;
}
if (!this.node_.role)
if (!this.node_.role) {
return;
}
if (this.switchAccess_.improvedTextInputEnabled()) {
if (SwitchAccessPredicate.isTextInput(this.node_)) {
......@@ -673,7 +691,7 @@ class NavigationManager {
// The first node will come immediately after the back button, so we set
// |this.node_| to the back button and call |moveForward|.
const backButtonNode = this.backButtonManager_.buttonNode();
const backButtonNode = this.backButtonManager_.backButtonNode();
if (backButtonNode)
this.node_ = backButtonNode;
else
......
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