Commit 731b4b02 authored by Anastasia Helfinstein's avatar Anastasia Helfinstein Committed by Commit Bot

[Switch Access] Use new views-based Switch Access menu

This is the (hopefully) penultimate change in the process of changing
to a views-based menu for Switch Access, as part of implementing the
visual specification.

This change rewrites the menu manager to use the new menu, rather than
the old menu. A clean-up change (to remove code for the old menu) will
follow this.

AX-Relnotes: n/a
Bug: 973719
Change-Id: I596d47a8f976a531e545bc56555f9f8da80efbd7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2176611
Commit-Queue: Anastasia Helfinstein <anastasi@google.com>
Reviewed-by: default avatarDavid Tseng <dtseng@chromium.org>
Cr-Commit-Position: refs/heads/master@{#772047}
parent 3806730b
...@@ -186,6 +186,11 @@ js_library("back_button_node") { ...@@ -186,6 +186,11 @@ js_library("back_button_node") {
} }
js_library("commands") { js_library("commands") {
deps = [
":auto_scan_manager",
":menu_manager",
":navigation_manager",
]
externs_list = [ "$externs_path/accessibility_private.js" ] externs_list = [ "$externs_path/accessibility_private.js" ]
} }
...@@ -216,6 +221,7 @@ js_library("focus_ring_manager") { ...@@ -216,6 +221,7 @@ js_library("focus_ring_manager") {
deps = [ deps = [
":menu_panel_interface", ":menu_panel_interface",
":node_wrapper", ":node_wrapper",
":switch_access_constants",
":switch_access_node", ":switch_access_node",
] ]
externs_list = [ "$externs_path/accessibility_private.js" ] externs_list = [ "$externs_path/accessibility_private.js" ]
...@@ -267,22 +273,13 @@ js_library("keyboard_node") { ...@@ -267,22 +273,13 @@ js_library("keyboard_node") {
js_library("menu_manager") { js_library("menu_manager") {
deps = [ deps = [
":event_helper",
":menu_panel_interface",
":metrics",
":node_wrapper",
":rect_helper",
":switch_access_constants", ":switch_access_constants",
":switch_access_node", ":switch_access_node",
":switch_access_predicate", "../common:array_util",
":text_navigation_manager",
"../common:constants",
"../common:tree_walker",
] ]
externs_list = [ externs_list = [
"$externs_path/accessibility_private.js", "$externs_path/accessibility_private.js",
"$externs_path/automation.js", "$externs_path/automation.js",
"$externs_path/clipboard.js",
] ]
} }
...@@ -306,6 +303,7 @@ js_library("navigation_manager") { ...@@ -306,6 +303,7 @@ js_library("navigation_manager") {
deps = [ deps = [
":desktop_node", ":desktop_node",
":focus_ring_manager", ":focus_ring_manager",
":history",
":keyboard_node", ":keyboard_node",
":menu_manager", ":menu_manager",
":menu_panel_interface", ":menu_panel_interface",
...@@ -413,5 +411,6 @@ js_library("text_navigation_manager") { ...@@ -413,5 +411,6 @@ js_library("text_navigation_manager") {
externs_list = [ externs_list = [
"$externs_path/accessibility_private.js", "$externs_path/accessibility_private.js",
"$externs_path/automation.js", "$externs_path/automation.js",
"$externs_path/clipboard.js",
] ]
} }
...@@ -15,7 +15,7 @@ class Commands { ...@@ -15,7 +15,7 @@ class Commands {
* @private {!Map<!SwitchAccessCommand, !function(): void>} * @private {!Map<!SwitchAccessCommand, !function(): void>}
*/ */
this.commandMap_ = new Map([ this.commandMap_ = new Map([
[SwitchAccessCommand.SELECT, NavigationManager.enterMenu], [SwitchAccessCommand.SELECT, MenuManager.enter],
[SwitchAccessCommand.NEXT, NavigationManager.moveForward], [SwitchAccessCommand.NEXT, NavigationManager.moveForward],
[SwitchAccessCommand.PREVIOUS, NavigationManager.moveBackward] [SwitchAccessCommand.PREVIOUS, NavigationManager.moveBackward]
]); ]);
......
...@@ -43,15 +43,10 @@ class FocusRingManager { ...@@ -43,15 +43,10 @@ class FocusRingManager {
* @param {!SARootNode} group * @param {!SARootNode} group
*/ */
setFocusNodes(primary, group) { setFocusNodes(primary, group) {
if (!primary.location) {
throw SwitchAccess.error(
SAConstants.ErrorType.MISSING_LOCATION,
'Cannot set focus rings if node location is undefined');
}
if (primary instanceof BackButtonNode) { if (primary instanceof BackButtonNode) {
MenuManager.requestBackButtonFocusChange(true); // The back button node handles setting its own focus, as it has special
// requirements (a round focus ring that has no gap with the edges of the
// view).
this.rings_.get(SAConstants.Focus.ID.PRIMARY).rects = []; this.rings_.get(SAConstants.Focus.ID.PRIMARY).rects = [];
// Clear the dashed ring between transitions, as the animation is // Clear the dashed ring between transitions, as the animation is
// distracting. // distracting.
...@@ -61,8 +56,12 @@ class FocusRingManager { ...@@ -61,8 +56,12 @@ class FocusRingManager {
this.rings_.get(SAConstants.Focus.ID.NEXT).rects = [group.location]; this.rings_.get(SAConstants.Focus.ID.NEXT).rects = [group.location];
this.updateFocusRings_(); this.updateFocusRings_();
return; return;
} else { }
MenuManager.requestBackButtonFocusChange(false);
if (!primary.location) {
throw SwitchAccess.error(
SAConstants.ErrorType.MISSING_LOCATION,
'Cannot set focus rings if node location is undefined');
} }
// If the primary node is a group, show its first child as the "next" focus. // If the primary node is a group, show its first child as the "next" focus.
......
...@@ -65,18 +65,19 @@ class NavigationManager { ...@@ -65,18 +65,19 @@ class NavigationManager {
navigator.jumpTo_(keyboard); navigator.jumpTo_(keyboard);
} }
/** Unconditionally exits the current group. */
static exitGroupUnconditionally() {
NavigationManager.instance.exitGroup_();
}
/** /**
* Open the Switch Access menu for the currently highlighted node. If there * Exits the specified node, if it is the currently focused group.
* are not enough actions available to trigger the menu, the current element * @param {?AutomationNode|!SAChildNode|!SARootNode} node
* is selected.
*/ */
static enterMenu() { static exitIfInGroup(node) {
const navigator = NavigationManager.instance; const navigator = NavigationManager.instance;
const didEnter = MenuManager.enter(navigator.node_); if (navigator.group_.isEquivalentTo(node)) {
navigator.exitGroup_();
// If the menu does not or cannot open, select the current node.
if (!didEnter) {
navigator.selectCurrentNode();
} }
} }
...@@ -134,18 +135,20 @@ class NavigationManager { ...@@ -134,18 +135,20 @@ class NavigationManager {
NavigationManager.instance = new NavigationManager(desktop); NavigationManager.instance = new NavigationManager(desktop);
} }
/** @param {AutomationNode} menuNode */
static jumpToSwitchAccessMenu(menuNode) {
if (!menuNode) {
return;
}
const menu = RootNodeWrapper.buildTree(menuNode);
NavigationManager.instance.jumpTo_(menu, false /* shouldExitMenu */);
}
/** /**
* Move to the previous interesting node. * Move to the previous interesting node.
*/ */
static moveBackward() { static moveBackward() {
const navigator = NavigationManager.instance; const navigator = NavigationManager.instance;
if (MenuManager.moveBackward()) {
// The menu navigation is handled separately. If we are in the menu, do
// not change the primary focus node.
return;
}
navigator.setNode_(navigator.node_.previous); navigator.setNode_(navigator.node_.previous);
} }
...@@ -159,12 +162,6 @@ class NavigationManager { ...@@ -159,12 +162,6 @@ class NavigationManager {
navigator.onMoveForwardForTesting_(); navigator.onMoveForwardForTesting_();
} }
if (MenuManager.moveForward()) {
// The menu navigation is handled separately. If we are in the menu, do
// not change the primary focus node.
return;
}
navigator.setNode_(navigator.node_.next); navigator.setNode_(navigator.node_.next);
} }
...@@ -207,54 +204,27 @@ class NavigationManager { ...@@ -207,54 +204,27 @@ class NavigationManager {
navigator.node_, navigator.group_); navigator.node_, navigator.group_);
} }
// =============== Getter Methods ==============
/** /**
* Returns the desktop automation node object. * Returns the currently focused node.
* @return {!AutomationNode} * @return {!SAChildNode}
*/ */
static get desktopNode() { static get currentNode() {
return NavigationManager.instance.desktop_; NavigationManager.moveToValidNode();
return NavigationManager.instance.node_;
} }
// =============== Instance Methods ==============
/** /**
* Selects the current node. * Returns the desktop automation node object.
* @return {!AutomationNode}
*/ */
selectCurrentNode() { static get desktopNode() {
if (MenuManager.selectCurrentNode()) { return NavigationManager.instance.desktop_;
// The menu navigation is handled separately. If we are in the menu, do
// not change the primary focus node.
return;
}
if (this.node_.isGroup()) {
NavigationManager.enterGroup();
return;
}
if (this.node_.hasAction(SwitchAccessMenuAction.KEYBOARD)) {
SwitchAccessMetrics.recordMenuAction(SwitchAccessMenuAction.KEYBOARD);
this.node_.performAction(SwitchAccessMenuAction.KEYBOARD);
return;
}
if (this.node_.hasAction(SwitchAccessMenuAction.SELECT)) {
SwitchAccessMetrics.recordMenuAction(SwitchAccessMenuAction.SELECT);
this.node_.performAction(SwitchAccessMenuAction.SELECT);
}
} }
// =============== Event Handlers ============== // =============== Event Handlers ==============
/**
* Sets up the connection between the menuPanel and menuManager.
* @param {!PanelInterface} menuPanel
*/
connectMenuPanel(menuPanel) {
menuPanel.backButtonElement().addEventListener(
'click', this.exitGroup_.bind(this));
}
/** /**
* When focus shifts, move to the element. Find the closest interesting * When focus shifts, move to the element. Find the closest interesting
* element to engage with. * element to engage with.
...@@ -303,18 +273,12 @@ class NavigationManager { ...@@ -303,18 +273,12 @@ class NavigationManager {
this.group_.onFocus(); this.group_.onFocus();
this.node_.onFocus(); this.node_.onFocus();
if (window.menuPanel) {
this.connectMenuPanel(window.menuPanel);
}
this.desktop_.addEventListener( this.desktop_.addEventListener(
chrome.automation.EventType.FOCUS, this.onFocusChange_.bind(this), chrome.automation.EventType.FOCUS, this.onFocusChange_.bind(this),
false); false);
this.desktop_.addEventListener( this.desktop_.addEventListener(
chrome.automation.EventType.MENU_START, this.onMenuStart_.bind(this), chrome.automation.EventType.MENU_START, this.onMenuStart_.bind(this),
false); false);
chrome.automation.addTreeChangeObserver( chrome.automation.addTreeChangeObserver(
chrome.automation.TreeChangeObserverFilter.ALL_TREE_CHANGES, chrome.automation.TreeChangeObserverFilter.ALL_TREE_CHANGES,
this.onTreeChange_.bind(this)); this.onTreeChange_.bind(this));
...@@ -324,10 +288,13 @@ class NavigationManager { ...@@ -324,10 +288,13 @@ class NavigationManager {
* Jumps Switch Access focus to a specified node, such as when opening a menu * Jumps Switch Access focus to a specified node, such as when opening a menu
* or the keyboard. Does not modify the groups already in the group stack. * or the keyboard. Does not modify the groups already in the group stack.
* @param {!SARootNode} group * @param {!SARootNode} group
* @param {boolean} shouldExitMenu
* @private * @private
*/ */
jumpTo_(group) { jumpTo_(group, shouldExitMenu = true) {
MenuManager.exit(); if (shouldExitMenu) {
MenuManager.exit();
}
this.history_.save(new FocusData(this.group_, this.node_)); this.history_.save(new FocusData(this.group_, this.node_));
this.setGroup_(group); this.setGroup_(group);
......
...@@ -27,7 +27,7 @@ class BackButtonNode extends SAChildNode { ...@@ -27,7 +27,7 @@ class BackButtonNode extends SAChildNode {
/** @override */ /** @override */
get automationNode() { get automationNode() {
return BackButtonNode.automationNode; return BackButtonNode.automationNode_;
} }
/** @override */ /** @override */
...@@ -69,28 +69,30 @@ class BackButtonNode extends SAChildNode { ...@@ -69,28 +69,30 @@ class BackButtonNode extends SAChildNode {
/** @override */ /** @override */
isValidAndVisible() { isValidAndVisible() {
return this.automationNode !== null; return this.automationNode !== null && !!this.location;
} }
/** @override */ /** @override */
onFocus() { onFocus() {
super.onFocus(); super.onFocus();
chrome.accessibilityPrivate.setSwitchAccessMenuState( chrome.accessibilityPrivate.updateSwitchAccessBubble(
true, this.group_.location, 0 /* num_actions */); chrome.accessibilityPrivate.SwitchAccessBubble.BACK_BUTTON,
true /* show */, this.group_.location);
BackButtonNode.findAutomationNode_();
} }
/** @override */ /** @override */
onUnfocus() { onUnfocus() {
super.onUnfocus(); super.onUnfocus();
chrome.accessibilityPrivate.setSwitchAccessMenuState( chrome.accessibilityPrivate.updateSwitchAccessBubble(
false, RectHelper.ZERO_RECT, 0 /* num_actions */); chrome.accessibilityPrivate.SwitchAccessBubble.BACK_BUTTON,
false /* show */);
} }
/** @override */ /** @override */
performAction(action) { performAction(action) {
if (action === SwitchAccessMenuAction.SELECT && if (action === SwitchAccessMenuAction.SELECT && this.automationNode) {
BackButtonNode.automationNode_) { BackButtonNode.onClick_();
BackButtonNode.automationNode_.doDefault();
return SAConstants.ActionResponse.CLOSE_MENU; return SAConstants.ActionResponse.CLOSE_MENU;
} }
return SAConstants.ActionResponse.NO_ACTION_TAKEN; return SAConstants.ActionResponse.NO_ACTION_TAKEN;
...@@ -106,18 +108,48 @@ class BackButtonNode extends SAChildNode { ...@@ -106,18 +108,48 @@ class BackButtonNode extends SAChildNode {
// ================= Static methods ================= // ================= Static methods =================
/** /**
* Looks for the back button node. * Looks for the back button automation node.
* @return {?AutomationNode} * @private
*/ */
static get automationNode() { static findAutomationNode_() {
if (BackButtonNode.automationNode_) { if (BackButtonNode.automationNode_ && BackButtonNode.automationNode_.role) {
return BackButtonNode.automationNode_; return;
} }
SwitchAccess.findNodeMatchingPredicate(
BackButtonNode.isBackButton_, BackButtonNode.saveAutomationNode_);
}
const treeWalker = new AutomationTreeWalker( /**
NavigationManager.desktopNode, constants.Dir.FORWARD, * Checks if the given node is the back button automation node.
{visit: (node) => node.htmlAttributes.id === SAConstants.BACK_ID}); * @param {!AutomationNode} node
BackButtonNode.automationNode_ = treeWalker.next().node; * @return {boolean}
return BackButtonNode.automationNode_; * @private
*/
static isBackButton_(node) {
return node.htmlAttributes.id === 'switch_access_back_button';
}
/**
* This function defines the behavior that should be taken when the back
* button is pressed.
* @private
*/
static onClick_() {
if (MenuManager.isMenuOpen()) {
MenuManager.exit();
} else {
NavigationManager.exitGroupUnconditionally();
}
}
/**
* Saves the back button automation node.
* @param {!AutomationNode} automationNode
* @private
*/
static saveAutomationNode_(automationNode) {
BackButtonNode.automationNode_ = automationNode;
BackButtonNode.automationNode_.addEventListener(
chrome.automation.EventType.CLICKED, BackButtonNode.onClick_, false);
} }
} }
...@@ -30,13 +30,22 @@ class EditableTextNode extends NodeWrapper { ...@@ -30,13 +30,22 @@ class EditableTextNode extends NodeWrapper {
actions.push(SwitchAccessMenuAction.KEYBOARD); actions.push(SwitchAccessMenuAction.KEYBOARD);
actions.push(SwitchAccessMenuAction.DICTATION); actions.push(SwitchAccessMenuAction.DICTATION);
if (SwitchAccess.instance.improvedTextInputEnabled() && if (SwitchAccess.instance.improvedTextInputEnabled()) {
this.automationNode.state[StateType.FOCUSED]) {
actions.push(SwitchAccessMenuAction.MOVE_CURSOR); actions.push(SwitchAccessMenuAction.MOVE_CURSOR);
actions.push(SwitchAccessMenuAction.JUMP_TO_BEGINNING_OF_TEXT);
actions.push(SwitchAccessMenuAction.JUMP_TO_END_OF_TEXT);
actions.push(SwitchAccessMenuAction.MOVE_BACKWARD_ONE_CHAR_OF_TEXT);
actions.push(SwitchAccessMenuAction.MOVE_FORWARD_ONE_CHAR_OF_TEXT);
actions.push(SwitchAccessMenuAction.MOVE_BACKWARD_ONE_WORD_OF_TEXT);
actions.push(SwitchAccessMenuAction.MOVE_FORWARD_ONE_WORD_OF_TEXT);
actions.push(SwitchAccessMenuAction.MOVE_DOWN_ONE_LINE_OF_TEXT);
actions.push(SwitchAccessMenuAction.MOVE_UP_ONE_LINE_OF_TEXT);
actions.push(SwitchAccessMenuAction.START_TEXT_SELECTION); actions.push(SwitchAccessMenuAction.START_TEXT_SELECTION);
if (TextNavigationManager.currentlySelecting()) { if (TextNavigationManager.currentlySelecting()) {
actions.push(SwitchAccessMenuAction.END_TEXT_SELECTION); actions.push(SwitchAccessMenuAction.END_TEXT_SELECTION);
} }
if (TextNavigationManager.selectionExists) { if (TextNavigationManager.selectionExists) {
actions.push(SwitchAccessMenuAction.CUT); actions.push(SwitchAccessMenuAction.CUT);
actions.push(SwitchAccessMenuAction.COPY); actions.push(SwitchAccessMenuAction.COPY);
...@@ -45,12 +54,16 @@ class EditableTextNode extends NodeWrapper { ...@@ -45,12 +54,16 @@ class EditableTextNode extends NodeWrapper {
actions.push(SwitchAccessMenuAction.PASTE); actions.push(SwitchAccessMenuAction.PASTE);
} }
} }
return actions; return actions;
} }
// ================= General methods ================= // ================= General methods =================
/** @override */
doDefaultAction() {
this.performAction(SwitchAccessMenuAction.KEYBOARD);
}
/** @override */ /** @override */
performAction(action) { performAction(action) {
switch (action) { switch (action) {
...@@ -60,6 +73,9 @@ class EditableTextNode extends NodeWrapper { ...@@ -60,6 +73,9 @@ class EditableTextNode extends NodeWrapper {
case SwitchAccessMenuAction.DICTATION: case SwitchAccessMenuAction.DICTATION:
chrome.accessibilityPrivate.toggleDictation(); chrome.accessibilityPrivate.toggleDictation();
return SAConstants.ActionResponse.CLOSE_MENU; return SAConstants.ActionResponse.CLOSE_MENU;
case SwitchAccessMenuAction.MOVE_CURSOR:
return SAConstants.ActionResponse.OPEN_TEXT_NAVIGATION_MENU;
case SwitchAccessMenuAction.CUT: case SwitchAccessMenuAction.CUT:
EventHelper.simulateKeyPress(EventHelper.KeyCode.X, {ctrl: true}); EventHelper.simulateKeyPress(EventHelper.KeyCode.X, {ctrl: true});
return SAConstants.ActionResponse.REMAIN_OPEN; return SAConstants.ActionResponse.REMAIN_OPEN;
...@@ -69,6 +85,38 @@ class EditableTextNode extends NodeWrapper { ...@@ -69,6 +85,38 @@ class EditableTextNode extends NodeWrapper {
case SwitchAccessMenuAction.PASTE: case SwitchAccessMenuAction.PASTE:
EventHelper.simulateKeyPress(EventHelper.KeyCode.V, {ctrl: true}); EventHelper.simulateKeyPress(EventHelper.KeyCode.V, {ctrl: true});
return SAConstants.ActionResponse.REMAIN_OPEN; return SAConstants.ActionResponse.REMAIN_OPEN;
case SwitchAccessMenuAction.START_TEXT_SELECTION:
TextNavigationManager.saveSelectStart();
return SAConstants.ActionResponse.OPEN_TEXT_NAVIGATION_MENU;
case SwitchAccessMenuAction.END_TEXT_SELECTION:
TextNavigationManager.saveSelectEnd();
return SAConstants.ActionResponse.RELOAD_MAIN_MENU;
case SwitchAccessMenuAction.JUMP_TO_BEGINNING_OF_TEXT:
TextNavigationManager.jumpToBeginning();
return SAConstants.ActionResponse.REMAIN_OPEN;
case SwitchAccessMenuAction.JUMP_TO_END_OF_TEXT:
TextNavigationManager.jumpToEnd();
return SAConstants.ActionResponse.REMAIN_OPEN;
case SwitchAccessMenuAction.MOVE_BACKWARD_ONE_CHAR_OF_TEXT:
TextNavigationManager.moveBackwardOneChar();
return SAConstants.ActionResponse.REMAIN_OPEN;
case SwitchAccessMenuAction.MOVE_BACKWARD_ONE_WORD_OF_TEXT:
TextNavigationManager.moveBackwardOneWord();
return SAConstants.ActionResponse.REMAIN_OPEN;
case SwitchAccessMenuAction.MOVE_DOWN_ONE_LINE_OF_TEXT:
TextNavigationManager.moveDownOneLine();
return SAConstants.ActionResponse.REMAIN_OPEN;
case SwitchAccessMenuAction.MOVE_FORWARD_ONE_CHAR_OF_TEXT:
TextNavigationManager.moveForwardOneChar();
return SAConstants.ActionResponse.REMAIN_OPEN;
case SwitchAccessMenuAction.MOVE_UP_ONE_LINE_OF_TEXT:
TextNavigationManager.moveUpOneLine();
return SAConstants.ActionResponse.REMAIN_OPEN;
case SwitchAccessMenuAction.MOVE_FORWARD_ONE_WORD_OF_TEXT:
TextNavigationManager.moveForwardOneWord();
return SAConstants.ActionResponse.REMAIN_OPEN;
} }
return super.performAction(action); return super.performAction(action);
} }
......
...@@ -25,7 +25,7 @@ class GroupNode extends SAChildNode { ...@@ -25,7 +25,7 @@ class GroupNode extends SAChildNode {
/** @override */ /** @override */
get actions() { get actions() {
return []; return [SwitchAccessMenuAction.SELECT];
} }
/** @override */ /** @override */
......
...@@ -19,9 +19,6 @@ class KeyboardNode extends NodeWrapper { ...@@ -19,9 +19,6 @@ class KeyboardNode extends NodeWrapper {
/** @override */ /** @override */
get actions() { get actions() {
if (this.isGroup()) {
return [];
}
return [SwitchAccessMenuAction.SELECT]; return [SwitchAccessMenuAction.SELECT];
} }
...@@ -45,10 +42,15 @@ class KeyboardNode extends NodeWrapper { ...@@ -45,10 +42,15 @@ class KeyboardNode extends NodeWrapper {
/** @override */ /** @override */
performAction(action) { performAction(action) {
if (this.isGroup() || action !== SwitchAccessMenuAction.SELECT) { if (action !== SwitchAccessMenuAction.SELECT) {
return SAConstants.ActionResponse.NO_ACTION_TAKEN; return SAConstants.ActionResponse.NO_ACTION_TAKEN;
} }
if (this.isGroup()) {
NavigationManager.enterGroup();
return SAConstants.ActionResponse.CLOSE_MENU;
}
const keyLocation = this.location; const keyLocation = this.location;
if (!keyLocation) { if (!keyLocation) {
return SAConstants.ActionResponse.NO_ACTION_TAKEN; return SAConstants.ActionResponse.NO_ACTION_TAKEN;
......
...@@ -149,25 +149,25 @@ class NodeWrapper extends SAChildNode { ...@@ -149,25 +149,25 @@ class NodeWrapper extends SAChildNode {
if (ancestor.scrollable) { if (ancestor.scrollable) {
ancestor.scrollDown(() => this.parent_.refresh()); ancestor.scrollDown(() => this.parent_.refresh());
} }
return SAConstants.ActionResponse.CLOSE_MENU; return SAConstants.ActionResponse.RELOAD_MAIN_MENU;
case SwitchAccessMenuAction.SCROLL_UP: case SwitchAccessMenuAction.SCROLL_UP:
ancestor = this.getScrollableAncestor_(); ancestor = this.getScrollableAncestor_();
if (ancestor.scrollable) { if (ancestor.scrollable) {
ancestor.scrollUp(() => this.parent_.refresh()); ancestor.scrollUp(() => this.parent_.refresh());
} }
return SAConstants.ActionResponse.CLOSE_MENU; return SAConstants.ActionResponse.RELOAD_MAIN_MENU;
case SwitchAccessMenuAction.SCROLL_RIGHT: case SwitchAccessMenuAction.SCROLL_RIGHT:
ancestor = this.getScrollableAncestor_(); ancestor = this.getScrollableAncestor_();
if (ancestor.scrollable) { if (ancestor.scrollable) {
ancestor.scrollRight(() => this.parent_.refresh()); ancestor.scrollRight(() => this.parent_.refresh());
} }
return SAConstants.ActionResponse.CLOSE_MENU; return SAConstants.ActionResponse.RELOAD_MAIN_MENU;
case SwitchAccessMenuAction.SCROLL_LEFT: case SwitchAccessMenuAction.SCROLL_LEFT:
ancestor = this.getScrollableAncestor_(); ancestor = this.getScrollableAncestor_();
if (ancestor.scrollable) { if (ancestor.scrollable) {
ancestor.scrollLeft(() => this.parent_.refresh()); ancestor.scrollLeft(() => this.parent_.refresh());
} }
return SAConstants.ActionResponse.CLOSE_MENU; return SAConstants.ActionResponse.RELOAD_MAIN_MENU;
default: default:
if (Object.values(chrome.automation.ActionType).includes(action)) { if (Object.values(chrome.automation.ActionType).includes(action)) {
this.baseNode_.performStandardAction( this.baseNode_.performStandardAction(
......
...@@ -97,6 +97,14 @@ class SAChildNode { ...@@ -97,6 +97,14 @@ class SAChildNode {
*/ */
asRootNode() {} asRootNode() {}
/** Performs the node's default action. */
doDefaultAction() {
if (!this.isFocused_) {
return;
}
this.performAction(SwitchAccessMenuAction.SELECT);
}
/** /**
* @param {SAChildNode} other * @param {SAChildNode} other
* @return {boolean} * @return {boolean}
......
...@@ -25,7 +25,7 @@ class TabNode extends NodeWrapper { ...@@ -25,7 +25,7 @@ class TabNode extends NodeWrapper {
/** @override */ /** @override */
get actions() { get actions() {
return []; return [SwitchAccessMenuAction.SELECT];
} }
// ================= General methods ================= // ================= General methods =================
...@@ -40,6 +40,15 @@ class TabNode extends NodeWrapper { ...@@ -40,6 +40,15 @@ class TabNode extends NodeWrapper {
return true; return true;
} }
/** @override */
performAction(action) {
if (action !== SwitchAccessMenuAction.SELECT) {
return SAConstants.ActionResponse.NO_ACTION_TAKEN;
}
NavigationManager.enterGroup();
return SAConstants.ActionResponse.CLOSE_MENU;
}
// ================= Static methods ================= // ================= Static methods =================
/** @override */ /** @override */
......
...@@ -47,9 +47,12 @@ TEST_F('SwitchAccessTabNodeTest', 'Construction', function() { ...@@ -47,9 +47,12 @@ TEST_F('SwitchAccessTabNodeTest', 'Construction', function() {
chrome.automation.RoleType.TAB, tab.role, 'Tab node is not a tab'); chrome.automation.RoleType.TAB, tab.role, 'Tab node is not a tab');
assertTrue(tab.isGroup(), 'Tab node should be a group'); assertTrue(tab.isGroup(), 'Tab node should be a group');
assertEquals( assertEquals(
0, tab.actions.length, 'Tab as a group should not have actions'); 1, tab.actions.length, 'Tab as a group should have 1 action (select)');
assertEquals(
chrome.accessibilityPrivate.SwitchAccessMenuAction.SELECT,
tab.actions[0], 'Tab as a group should have the action SELECT');
NavigationManager.instance.selectCurrentNode(); NavigationManager.instance.node_.doDefaultAction();
const tabAsRoot = NavigationManager.instance.group_; const tabAsRoot = NavigationManager.instance.group_;
assertTrue( assertTrue(
......
...@@ -12,17 +12,20 @@ class SwitchAccess { ...@@ -12,17 +12,20 @@ class SwitchAccess {
window.switchAccess = new SwitchAccess(); window.switchAccess = new SwitchAccess();
chrome.automation.getDesktop((desktop) => { chrome.automation.getDesktop((desktop) => {
// These two must be initialized before the others.
AutoScanManager.initialize(); AutoScanManager.initialize();
NavigationManager.initialize(desktop); NavigationManager.initialize(desktop);
Commands.initialize(); Commands.initialize();
KeyboardRootNode.startWatchingVisibility();
MenuManager.initialize(); MenuManager.initialize();
SwitchAccessPreferences.initialize(); SwitchAccessPreferences.initialize();
TextNavigationManager.initialize(); TextNavigationManager.initialize();
KeyboardRootNode.startWatchingVisibility();
}); });
} }
// TODO(anastasi): Remove once new menu is being used.
static get instance() { static get instance() {
return window.switchAccess; return window.switchAccess;
} }
...@@ -50,20 +53,53 @@ class SwitchAccess { ...@@ -50,20 +53,53 @@ class SwitchAccess {
return this.enableImprovedTextInput_; return this.enableImprovedTextInput_;
} }
/** TODO(anastasi): Remove this once menu migration is complete. */
connectMenuPanel() {}
/** /**
* Sets up the connection between the menuPanel and menuManager. * Helper function to robustly find a node fitting a given predicate, even if
* @param {!PanelInterface} menuPanel * that node has not yet been created.
* Used to find the menu and back button.
* @param {!function(!AutomationNode): boolean} predicate
* @param {!function(!AutomationNode): void} foundCallback
*/ */
connectMenuPanel(menuPanel) { static findNodeMatchingPredicate(predicate, foundCallback) {
// Because this may be called before init_(), check if navigationManager_ const desktop = NavigationManager.desktopNode;
// is initialized. // First, check if the node is currently in the tree.
const treeWalker = new AutomationTreeWalker(
if (NavigationManager.instance) { desktop, constants.Dir.FORWARD, {visit: predicate});
NavigationManager.instance.connectMenuPanel(menuPanel); treeWalker.next();
MenuManager.instance.connectMenuPanel(menuPanel); if (treeWalker.node) {
} else { foundCallback(treeWalker.node);
window.menuPanel = menuPanel; return;
} }
// If it's not currently in the tree, listen for changes to the desktop
// tree.
const onDesktopChildrenChanged = (event) => {
if (predicate(event.target)) {
// If the event target is the node we're looking for, we've found it.
desktop.removeEventListener(
chrome.automation.EventType.CHILDREN_CHANGED,
onDesktopChildrenChanged, false);
foundCallback(event.target);
} else if (event.target.children.length > 0) {
// Otherwise, see if one of its children is the node we're looking for.
const treeWalker = new AutomationTreeWalker(
event.target, constants.Dir.FORWARD,
{visit: predicate, root: (node) => node == event.target});
treeWalker.next();
if (treeWalker.node) {
desktop.removeEventListener(
chrome.automation.EventType.CHILDREN_CHANGED,
onDesktopChildrenChanged, false);
foundCallback(treeWalker.node);
}
}
};
desktop.addEventListener(
chrome.automation.EventType.CHILDREN_CHANGED, onDesktopChildrenChanged,
false);
} }
/* /*
......
...@@ -50,8 +50,10 @@ const SAConstants = { ...@@ -50,8 +50,10 @@ const SAConstants = {
*/ */
ActionResponse: { ActionResponse: {
NO_ACTION_TAKEN: -1, NO_ACTION_TAKEN: -1,
CLOSE_MENU: 0, REMAIN_OPEN: 0,
REMAIN_OPEN: 1, CLOSE_MENU: 1,
RELOAD_MAIN_MENU: 2,
OPEN_TEXT_NAVIGATION_MENU: 3,
}, },
/** /**
......
...@@ -276,6 +276,10 @@ class TextNavigationManager { ...@@ -276,6 +276,10 @@ class TextNavigationManager {
* @private * @private
*/ */
manageNavigationListener_(addListener) { manageNavigationListener_(addListener) {
if (!this.selectionStartObject_) {
return;
}
if (addListener) { if (addListener) {
this.selectionStartObject_.addEventListener( this.selectionStartObject_.addEventListener(
chrome.automation.EventType.TEXT_SELECTION_CHANGED, chrome.automation.EventType.TEXT_SELECTION_CHANGED,
...@@ -296,20 +300,21 @@ class TextNavigationManager { ...@@ -296,20 +300,21 @@ class TextNavigationManager {
onNavChange_() { onNavChange_() {
this.manageNavigationListener_(false); this.manageNavigationListener_(false);
if (this.currentlySelecting_) { if (this.currentlySelecting_) {
this.saveSelectEnd(); TextNavigationManager.saveSelectEnd();
} }
} }
/** /**
* Sets the selectionEnd variable based on the selection of the current node. * Sets the selectionEnd variable based on the selection of the current node.
*/ */
saveSelectEnd() { static saveSelectEnd() {
const manager = TextNavigationManager.instance;
chrome.automation.getFocus((focusedNode) => { chrome.automation.getFocus((focusedNode) => {
this.selectionEndObject_ = focusedNode; manager.selectionEndObject_ = focusedNode;
this.selectionEndIndex_ = this.getSelectionIndexFromNode_( manager.selectionEndIndex_ = manager.getSelectionIndexFromNode_(
this.selectionEndObject_, manager.selectionEndObject_,
false /*We are not getting the start index.*/); false /*We are not getting the start index.*/);
this.saveSelection_(); manager.saveSelection_();
}); });
} }
...@@ -371,7 +376,10 @@ class TextNavigationManager { ...@@ -371,7 +376,10 @@ class TextNavigationManager {
*/ */
updateClipboardHasData_() { updateClipboardHasData_() {
this.clipboardHasData_ = true; this.clipboardHasData_ = true;
MenuManager.reloadMenuIfNeeded(); const node = NavigationManager.currentNode;
if (node.hasAction(SwitchAccessMenuAction.PASTE)) {
MenuManager.reloadActionsForNode(node);
}
} }
} }
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
{% endif %} {% endif %}
"background": { "background": {
"scripts": [ "scripts": [
"common/array_util.js",
"switch_access/auto_scan_manager.js", "switch_access/auto_scan_manager.js",
"common/closure_shim.js", "common/closure_shim.js",
"switch_access/commands.js", "switch_access/commands.js",
......
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