Commit 422a57e0 authored by Sophie Yang's avatar Sophie Yang Committed by Commit Bot

[Switch Access] Use new functions to open and close menus

Change MenuManager to use the new open and close functions
introduced in issue 1760673. This change lays the foundation
for submenus to be implemented for the Switch Access menu
in a follow-up change. See go/cros-switch-menu-redesign for
more information.

Test: With emulated Chrome OS on Linux, manually verified that
opening the menu, navigating through the menu, selecting actions
on the menu, and closing the menu worked as before.

Bug: 994256
Change-Id: Iffdfdde22de712aebec117bd37662cff315ddc70
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1761086
Commit-Queue: Sophie Yang <sophyang@google.com>
Reviewed-by: default avatarAkihiro Ota <akihiroota@chromium.org>
Reviewed-by: default avatarAnastasia Helfinstein <anastasi@google.com>
Cr-Commit-Position: refs/heads/master@{#689742}
parent 8a3a24ec
...@@ -109,10 +109,10 @@ class MenuManager { ...@@ -109,10 +109,10 @@ class MenuManager {
} }
/** /**
* Enter the menu and highlight the first available action. * If multiple actions are available for the currently highlighted node,
* * opens the main menu. Otherwise, selects the node by default.
* @param {!chrome.automation.AutomationNode} navNode the currently selected * @param {!chrome.automation.AutomationNode} navNode the currently
* node, for which the menu is to be displayed. * highlighted node, for which the menu is to be displayed.
*/ */
enter(navNode) { enter(navNode) {
if (!this.menuPanel_) { if (!this.menuPanel_) {
...@@ -120,96 +120,24 @@ class MenuManager { ...@@ -120,96 +120,24 @@ class MenuManager {
return; return;
} }
const actions = this.getMainMenuActionsForNode_(navNode); if (!this.openMenu_(navNode, SAConstants.MenuId.MAIN)) {
// getMainMenuActionsForNode_ will return null when there is only one // openMenu_ will return false (indicating that the menu was not opened
// interesting action (selection) specific to this node. In this case, // successfully) when there is only one interesting action (selection)
// rather than forcing the user to repeatedly disambiguate, we will simply // specific to this node. In this case, rather than forcing the user to
// select by default. // repeatedly disambiguate, we will simply select by default.
if (actions === null) {
this.navigationManager_.selectCurrentNode(); this.navigationManager_.selectCurrentNode();
return; return;
} }
this.inMenu_ = true; this.inMenu_ = true;
if (actions !== this.actions_) {
this.actions_ = actions;
this.menuPanel_.setActions(this.actions_);
}
if (navNode.location) {
chrome.accessibilityPrivate.setSwitchAccessMenuState(
true, navNode.location, actions.length);
this.menuOriginNode_ = navNode;
if (window.switchAccess.improvedTextInputEnabled()) {
this.menuOriginNode_.addEventListener(
chrome.automation.EventType.TEXT_SELECTION_CHANGED,
this.onSelectionChanged_.bind(this),
false /** Don't use capture. */);
}
} else {
console.log('Unable to show Switch Access menu.');
}
let firstNode =
this.menuNode().find({role: chrome.automation.RoleType.BUTTON});
while (firstNode && !this.isActionAvailable_(firstNode.htmlAttributes.id))
firstNode = firstNode.nextSibling;
if (firstNode) {
this.node_ = firstNode;
this.updateFocusRing_();
}
}
/**
* TODO(rosalindag): Refactor enter and reloadMenu to reduce duplicated code.
* Reload the menu.
* @param {!chrome.automation.AutomationNode} navNode the node for which the
* menu is to be displayed.
* @private
*/
reloadMenu_(navNode) {
const actionNode = this.node_;
if (!this.menuPanel_) {
console.log('Error: Menu panel has not loaded.');
return;
}
const actions = this.getMainMenuActionsForNode_(navNode);
if (actions === null) {
return;
}
this.inMenu_ = true;
if (actions !== this.actions_) {
this.actions_ = actions;
this.menuPanel_.setActions(this.actions_);
}
if (navNode.location) {
chrome.accessibilityPrivate.setSwitchAccessMenuState(
true /* show menu */, navNode.location, actions.length);
this.menuOriginNode_ = navNode;
} else {
console.log('Unable to show Switch Access menu.');
}
if (actionNode) {
this.menuOriginNode_ = navNode;
this.node_ = actionNode;
this.updateFocusRing_();
}
} }
/** /**
* Exits the menu. * Exits the menu.
*/ */
exit() { exit() {
this.clearFocusRing_(); this.closeCurrentMenu_();
this.inMenu_ = false; this.inMenu_ = false;
if (this.node_)
this.node_ = null;
if (window.switchAccess.improvedTextInputEnabled()) { if (window.switchAccess.improvedTextInputEnabled()) {
this.menuOriginNode_.removeEventListener( this.menuOriginNode_.removeEventListener(
...@@ -228,8 +156,6 @@ class MenuManager { ...@@ -228,8 +156,6 @@ class MenuManager {
* will be highlighted. Otherwise, the first available action will * will be highlighted. Otherwise, the first available action will
* be highlighted. Returns a boolean of whether or not the menu was * be highlighted. Returns a boolean of whether or not the menu was
* successfully opened. * successfully opened.
* TODO(sophyang): Once menu_panel.html is reorganized into submenus, use
* this function to replace most of enter() and eliminate reloadMenu_().
* @param {!chrome.automation.AutomationNode} navNode The currently * @param {!chrome.automation.AutomationNode} navNode The currently
* highlighted node, for which the menu is being opened. * highlighted node, for which the menu is being opened.
* @param {!SAConstants.MenuId} menuId Indicates the menu being opened. * @param {!SAConstants.MenuId} menuId Indicates the menu being opened.
...@@ -267,7 +193,7 @@ class MenuManager { ...@@ -267,7 +193,7 @@ class MenuManager {
if (JSON.stringify(actions) !== JSON.stringify(this.actions_)) { if (JSON.stringify(actions) !== JSON.stringify(this.actions_)) {
// Set new menu actions in the panel. // Set new menu actions in the panel.
this.actions_ = actions; this.actions_ = actions;
this.menuPanel_.setActionsFromMenu(this.actions_, menuId); this.menuPanel_.setActions(this.actions_, menuId);
} }
if (!navNode.location) { if (!navNode.location) {
...@@ -437,22 +363,17 @@ class MenuManager { ...@@ -437,22 +363,17 @@ class MenuManager {
selectCurrentNode() { selectCurrentNode() {
this.calculateCurrentNode(); this.calculateCurrentNode();
if (!this.inMenu_ || !this.node_) if (!this.inMenu_ || !this.node_) {
return false; return false;
}
if (this.node_.role == RoleType.BUTTON) {
if (window.switchAccess.improvedTextInputEnabled()) { // An action was selected.
if (this.node_.role == RoleType.BUTTON) {
this.node_.doDefault();
} else {
this.exit();
}
} else {
this.clearFocusRing_();
this.node_.doDefault(); this.node_.doDefault();
} else {
// The back button was selected.
this.exit(); this.exit();
} }
return true; return true;
} }
...@@ -468,9 +389,6 @@ class MenuManager { ...@@ -468,9 +389,6 @@ class MenuManager {
/** /**
* Get the menu panel node. If it's not defined, search for it. * Get the menu panel node. If it's not defined, search for it.
* TODO(sophyang): Replace menuNode() with this function once menu_panel.html
* has been reorganized into submenus. Change menuNode() to get the node
* of the menu currently within the menu panel node.
* @return {!chrome.automation.AutomationNode} * @return {!chrome.automation.AutomationNode}
*/ */
menuPanelNode() { menuPanelNode() {
...@@ -480,7 +398,7 @@ class MenuManager { ...@@ -480,7 +398,7 @@ class MenuManager {
const treeWalker = new AutomationTreeWalker( const treeWalker = new AutomationTreeWalker(
this.desktop_, constants.Dir.FORWARD, this.desktop_, constants.Dir.FORWARD,
SwitchAccessPredicate.switchAccessMenuDiscoveryRestrictions()); SwitchAccessPredicate.switchAccessMenuPanelDiscoveryRestrictions());
const node = treeWalker.next().node; const node = treeWalker.next().node;
if (node) { if (node) {
this.menuPanelNode_ = node; this.menuPanelNode_ = node;
...@@ -491,22 +409,23 @@ class MenuManager { ...@@ -491,22 +409,23 @@ class MenuManager {
} }
/** /**
* Get the menu node. If it's not defined, search for it. * Get the menu node. With the current design, the menu panel should
* always contain at most one menu. When a menu is open in the panel,
* the menu node is the first child of the menu panel node.
* @return {!chrome.automation.AutomationNode} * @return {!chrome.automation.AutomationNode}
*/ */
menuNode() { menuNode() {
if (this.menuNode_) if (this.menuNode_) {
return this.menuNode_; return this.menuNode_;
}
const treeWalker = new AutomationTreeWalker( if (this.menuPanelNode() !== this.desktop_) {
this.desktop_, constants.Dir.FORWARD, if (this.menuPanelNode_.firstChild) {
SwitchAccessPredicate.switchAccessMenuDiscoveryRestrictions()); this.menuNode_ = this.menuPanelNode_.firstChild;
const node = treeWalker.next().node; return this.menuNode_;
if (node) { }
this.menuNode_ = node;
return this.menuNode_;
} }
console.log('Unable to find the Switch Access menu.');
return this.desktop_; return this.desktop_;
} }
...@@ -518,7 +437,7 @@ class MenuManager { ...@@ -518,7 +437,7 @@ class MenuManager {
updateClipboardHasData() { updateClipboardHasData() {
this.clipboardHasData_ = true; this.clipboardHasData_ = true;
if (this.menuOriginNode_) { if (this.menuOriginNode_) {
this.reloadMenu_(this.menuOriginNode_); this.openMenu_(this.menuOriginNode_, SAConstants.MenuId.MAIN);
} }
} }
...@@ -558,7 +477,7 @@ class MenuManager { ...@@ -558,7 +477,7 @@ class MenuManager {
if (this.selectionExists_ != newSelectionState) { if (this.selectionExists_ != newSelectionState) {
this.selectionExists_ = newSelectionState; this.selectionExists_ = newSelectionState;
if (this.menuOriginNode_) { if (this.menuOriginNode_) {
this.reloadMenu_(this.menuOriginNode_); this.openMenu_(this.menuOriginNode_, SAConstants.MenuId.MAIN);
} }
} }
} }
...@@ -727,8 +646,9 @@ class MenuManager { ...@@ -727,8 +646,9 @@ class MenuManager {
break; break;
case SAConstants.MenuAction.SELECT_START: case SAConstants.MenuAction.SELECT_START:
this.navigationManager_.saveSelectStart(); this.navigationManager_.saveSelectStart();
if (this.menuOriginNode_) if (this.menuOriginNode_) {
this.reloadMenu_(this.menuOriginNode_); this.openMenu_(this.menuOriginNode_, SAConstants.MenuId.MAIN);
}
break; break;
case SAConstants.MenuAction.SELECT_END: case SAConstants.MenuAction.SELECT_END:
this.navigationManager_.saveSelectEnd(); this.navigationManager_.saveSelectEnd();
...@@ -761,7 +681,7 @@ class MenuManager { ...@@ -761,7 +681,7 @@ class MenuManager {
let id = this.node_.htmlAttributes.id; let id = this.node_.htmlAttributes.id;
// If the selection will close the menu, highlight the back button. // If the selection will close the menu, highlight the back button.
if (id === SAConstants.MENU_PANEL_ID) { if (id === this.menuPanel_.currentMenuId()) {
id = SAConstants.BACK_ID; id = SAConstants.BACK_ID;
} }
......
...@@ -12,8 +12,10 @@ ...@@ -12,8 +12,10 @@
<link rel="stylesheet" href="menu_panel.css"> <link rel="stylesheet" href="menu_panel.css">
</head> </head>
<body> <body>
<div id="switchaccess_menu_actions" <!-- TODO(anastasi): Internationalize the aria-label strings. -->
aria-label="Switch Access Menu"> <div id="switchaccess_menu_actions" aria-label="Switch Access Menu">
</div>
<div id="main_menu" aria-label="Main Menu">
<button class="action" id="select" data-position="0"> <button class="action" id="select" data-position="0">
<img src="icons/select.svg"> <img src="icons/select.svg">
<p class="i18n" msgid="select"></p> <p class="i18n" msgid="select"></p>
...@@ -122,4 +124,4 @@ ...@@ -122,4 +124,4 @@
<button id="back"> <button id="back">
<img src="icons/back.svg"> <img src="icons/back.svg">
</button> </button>
</body> </body>
\ No newline at end of file
...@@ -87,28 +87,14 @@ class Panel { ...@@ -87,28 +87,14 @@ class Panel {
return; return;
} }
/**
* Sets which buttons are enabled/disabled, based on |actions|.
* @param {!Array<string>} actions
*/
setActions(actions) {
const div = document.getElementById(SAConstants.MENU_PANEL_ID);
for (const button of div.children)
button.hidden = !actions.includes(button.id);
this.setHeight_(actions.length);
}
/** /**
* Sets the actions in the menu panel to the actions in |actions| from * Sets the actions in the menu panel to the actions in |actions| from
* the menu with the given |menuId|. * the menu with the given |menuId|.
* TODO(sophyang): Replace setActions() with this function once
* submenus are implemented.
* @param {!Array<string>} actions * @param {!Array<string>} actions
* @param {!SAConstants.MenuId} menuId * @param {!SAConstants.MenuId} menuId
* @public * @public
*/ */
setActionsFromMenu(actions, menuId) { setActions(actions, menuId) {
const menu = document.getElementById(menuId); const menu = document.getElementById(menuId);
const menuButtons = Array.from(menu.children); const menuButtons = Array.from(menu.children);
......
...@@ -22,21 +22,13 @@ class PanelInterface { ...@@ -22,21 +22,13 @@ class PanelInterface {
*/ */
setFocusRing(id, enable) {} setFocusRing(id, enable) {}
/**
* Sets which buttons are enabled/disabled, based on |actions|.
* @param {!Array<string>} actions
*/
setActions(actions) {}
/** /**
* Sets the actions in the menu panel to the actions in |actions| from * Sets the actions in the menu panel to the actions in |actions| from
* the menu with the given |menuId|. * the menu with the given |menuId|.
* TODO(sophyang): Replace setActions() with this function once
* submenus are implemented.
* @param {!Array<string>} actions * @param {!Array<string>} actions
* @param {!SAConstants.MenuId} menuId * @param {!SAConstants.MenuId} menuId
*/ */
setActionsFromMenu(actions, menuId) {} setActions(actions, menuId) {}
/** /**
* Clears the current menu from the panel. * Clears the current menu from the panel.
......
...@@ -17,7 +17,7 @@ const GROUP_INTERESTING_CHILD_THRESHOLD = 2; ...@@ -17,7 +17,7 @@ const GROUP_INTERESTING_CHILD_THRESHOLD = 2;
* - isInterestingSubtree * - isInterestingSubtree
* - isTextInput * - isTextInput
* - isNotContainer * - isNotContainer
* - isSwitchAccessMenu * - isSwitchAccessMenuPanel
* *
* In addition to these basic predicates, there are also methods to get the * In addition to these basic predicates, there are also methods to get the
* restrictions required by TreeWalker for specific traversal situations. * restrictions required by TreeWalker for specific traversal situations.
...@@ -169,7 +169,7 @@ const SwitchAccessPredicate = { ...@@ -169,7 +169,7 @@ const SwitchAccessPredicate = {
* @param {!chrome.automation.AutomationNode} node * @param {!chrome.automation.AutomationNode} node
* @return {boolean} * @return {boolean}
*/ */
isSwitchAccessMenu: (node) => isSwitchAccessMenuPanel: (node) =>
node.htmlAttributes.id === SAConstants.MENU_PANEL_ID, node.htmlAttributes.id === SAConstants.MENU_PANEL_ID,
/** /**
...@@ -226,10 +226,10 @@ const SwitchAccessPredicate = { ...@@ -226,10 +226,10 @@ const SwitchAccessPredicate = {
* Returns a Restrictions object for finding the Switch Access Menu root. * Returns a Restrictions object for finding the Switch Access Menu root.
* @return {!AutomationTreeWalkerRestriction} * @return {!AutomationTreeWalkerRestriction}
*/ */
switchAccessMenuDiscoveryRestrictions: () => { switchAccessMenuPanelDiscoveryRestrictions: () => {
return { return {
leaf: SwitchAccessPredicate.isNotContainer, leaf: SwitchAccessPredicate.isNotContainer,
visit: SwitchAccessPredicate.isSwitchAccessMenu visit: SwitchAccessPredicate.isSwitchAccessMenuPanel
}; };
}, },
......
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