Commit a1007091 authored by Anastasia Helfinstein's avatar Anastasia Helfinstein Committed by Commit Bot

[Switch Access] Refactor in preparation for improved event handling

Move logic for checking visibility into its own function, centralize
logic to set the current group, and generally improve code cleanliness.

Bug: 1023035
Change-Id: Ib77a31cb11e221a1e0a3f15cc9da35b777d397ce
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1995780Reviewed-by: default avatarAkihiro Ota <akihiroota@chromium.org>
Commit-Queue: Anastasia Helfinstein <anastasi@google.com>
Cr-Commit-Position: refs/heads/master@{#732616}
parent bb336397
......@@ -48,10 +48,8 @@ class NavigationManager {
const newGroup = this.node_.asRootNode();
if (newGroup) {
this.groupStack_.push(this.group_);
this.group_ = newGroup;
this.setGroup_(newGroup);
}
this.setNode_(this.group_.firstChild);
}
/**
......@@ -230,18 +228,19 @@ class NavigationManager {
}
this.groupStack_ = [];
this.group_ = RootNodeWrapper.buildDesktopTree(this.desktop_);
let group = RootNodeWrapper.buildDesktopTree(this.desktop_);
while (ancestorList.length > 0) {
let ancestor = ancestorList.pop();
if (ancestor.role === chrome.automation.RoleType.DESKTOP) {
continue;
}
if (SwitchAccessPredicate.isGroup(ancestor, this.group_)) {
this.groupStack_.push(this.group_);
this.group_ = RootNodeWrapper.buildTree(ancestor);
if (SwitchAccessPredicate.isGroup(ancestor, group)) {
this.groupStack_.push(group);
group = RootNodeWrapper.buildTree(ancestor);
}
}
this.setGroup_(group, false /* shouldSetNode */);
}
/**
......@@ -255,17 +254,21 @@ class NavigationManager {
this.group_.onExit();
let group = this.groupStack_.pop();
// Find a group that is still valid.
do {
this.group_ = this.groupStack_.pop();
} while (!this.group_.isValid() && this.groupStack_.length);
while (!group.isValidGroup() && this.groupStack_.length) {
group = this.groupStack_.pop();
}
this.setNode_(this.group_.firstChild);
this.setGroup_(group);
}
/** @private */
init_() {
this.group_.onFocus();
this.node_.onFocus();
if (SwitchAccess.get().prefsAreReady()) {
this.onPrefsReady();
}
......@@ -293,8 +296,7 @@ class NavigationManager {
this.menuManager_.exit();
this.groupStack_.push(this.group_);
this.group_ = group;
this.setNode_(this.group_.firstChild);
this.setGroup_(group);
}
/**
......@@ -328,11 +330,11 @@ class NavigationManager {
* @private
*/
moveToValidNode_() {
if (this.node_.role && this.group_.isValid()) {
if (this.node_.isValidAndVisible() && this.group_.isValidGroup()) {
return;
}
if (this.node_.role) {
if (this.node_.isValidAndVisible()) {
// Our group has been invalidated. Move to this node to repair the group
// stack.
const node = this.node_.automationNode;
......@@ -342,7 +344,7 @@ class NavigationManager {
}
}
if (this.group_.isValid()) {
if (this.group_.isValidGroup()) {
const group = this.group_.automationNode;
if (group) {
this.moveTo_(group);
......@@ -351,7 +353,7 @@ class NavigationManager {
}
for (let group of this.groupStack_) {
if (group.isValid()) {
if (group.isValidGroup()) {
group = group.automationNode;
if (group) {
this.moveTo_(group);
......@@ -361,11 +363,25 @@ class NavigationManager {
}
// If there is no valid node in the group stack, go to the desktop.
this.group_ = RootNodeWrapper.buildDesktopTree(this.desktop_);
this.node_ = this.group_.firstChild;
this.setGroup_(RootNodeWrapper.buildDesktopTree(this.desktop_));
this.groupStack_ = [];
}
/**
* Set |this.group_| to |group|.
* @param {!SARootNode} group
* @param {boolean} shouldSetNode
*/
setGroup_(group, shouldSetNode = true) {
this.group_.onUnfocus();
this.group_ = group;
this.group_.onFocus();
if (shouldSetNode) {
this.setNode_(this.group_.firstChild);
}
}
/**
* Set |this.node_| to |node|, and update what is displayed onscreen.
* @param {!SAChildNode} node
......
......@@ -17,7 +17,7 @@ class BackButtonNode extends SAChildNode {
*/
this.group_ = group;
/** @private {chrome.automation.AutomationNode} */
/** @private {?chrome.automation.AutomationNode} */
this.node_ = SwitchAccess.get().getBackButtonAutomationNode();
}
......@@ -67,6 +67,11 @@ class BackButtonNode extends SAChildNode {
return false;
}
/** @override */
isValidAndVisible() {
return this.node_ !== null;
}
/** @override */
onFocus() {
chrome.accessibilityPrivate.setSwitchAccessMenuState(
......
......@@ -89,6 +89,16 @@ class GroupNode extends SAChildNode {
return true;
}
/** @override */
isValidAndVisible() {
for (const child of this.children_) {
if (child.isValidAndVisible()) {
return true;
}
}
return false;
}
/** @override */
performAction(action) {
return true;
......
......@@ -106,15 +106,6 @@ class KeyboardRootNode extends RootNodeWrapper {
chrome.accessibilityPrivate.setVirtualKeyboardVisible(false);
}
// ================= Private methods =================
/**
* Custom logic when entering the node.
*/
onEnter_() {
chrome.accessibilityPrivate.setVirtualKeyboardVisible(true);
}
// ================= Static methods =================
/**
......@@ -123,6 +114,8 @@ class KeyboardRootNode extends RootNodeWrapper {
* @return {!KeyboardRootNode}
*/
static buildTree(desktop) {
KeyboardRootNode.loadKeyboard_();
const keyboardContainer =
desktop.find({role: chrome.automation.RoleType.KEYBOARD});
const keyboard =
......@@ -134,8 +127,15 @@ class KeyboardRootNode extends RootNodeWrapper {
.node;
const root = new KeyboardRootNode(keyboard);
root.onEnter_();
KeyboardNode.findAndSetChildren(root);
return root;
}
/**
* Loads the keyboard.
* @private
*/
static loadKeyboard_() {
chrome.accessibilityPrivate.setVirtualKeyboardVisible(true);
}
}
......@@ -93,6 +93,13 @@ class NodeWrapper extends SAChildNode {
/** @override */
isEquivalentTo(node) {
if (node instanceof NodeWrapper || node instanceof RootNodeWrapper) {
return this.baseNode_ === node.baseNode_;
}
if (node instanceof SAChildNode) {
return node.isEquivalentTo(this);
}
return this.baseNode_ === node;
}
......@@ -101,6 +108,15 @@ class NodeWrapper extends SAChildNode {
return this.isGroup_;
}
/** @override */
isValidAndVisible() {
// Nodes without a role are not valid.
if (!this.baseNode_.role) {
return false;
}
return SwitchAccessPredicate.isVisible(this.baseNode_);
}
/** @override */
performAction(action) {
let ancestor;
......@@ -202,13 +218,20 @@ class RootNodeWrapper extends SARootNode {
}
/** @override */
isEquivalentTo(automationNode) {
return this.baseNode_ === automationNode;
isEquivalentTo(node) {
if (node instanceof RootNodeWrapper || node instanceof NodeWrapper) {
return this.baseNode_ === node.baseNode_;
}
if (node instanceof SAChildNode) {
return node.isEquivalentTo(this);
}
return this.baseNode_ === node;
}
/** @override */
isValid() {
return !!this.baseNode_.role;
isValidGroup() {
return !!this.baseNode_.role && super.isValidGroup();
}
// ================= Static methods =================
......@@ -228,8 +251,7 @@ class RootNodeWrapper extends SARootNode {
}
const childConstructor = (autoNode) => new NodeWrapper(autoNode, root);
let children = interestingChildren.map(childConstructor);
root.children = children;
root.children = interestingChildren.map(childConstructor);
return root;
}
......
......@@ -111,7 +111,7 @@ class SAChildNode {
}
/**
* @param {!chrome.automation.AutomationNode} node
* @param {!chrome.automation.AutomationNode|!SAChildNode|!SARootNode} node
* @return {boolean}
* @abstract
*/
......@@ -124,6 +124,14 @@ class SAChildNode {
*/
isGroup() {}
/**
* Returns whether this node is still both valid and visible onscreen (e.g.
* not hidden, not offscreen, not invisible)
* @return {boolean}
* @abstract
*/
isValidAndVisible() {}
/**
* Called when this node becomes the primary highlighted node.
*/
......@@ -259,19 +267,32 @@ class SARootNode {
}
/**
* @param {chrome.automation.AutomationNode} automationNode
* @param {chrome.automation.AutomationNode|!SARootNode|!SAChildNode} node
* @return {boolean}
*/
isEquivalentTo(automationNode) {
isEquivalentTo(node) {
if (node instanceof SARootNode) {
return this.equals(node);
}
if (node instanceof SAChildNode) {
return node.isEquivalentTo(this);
}
return false;
}
/** @return {boolean} */
isValid() {
return true;
isValidGroup() {
return this.children_.filter((child) => child.isValidAndVisible()).length >=
1;
}
/** Called when a group is exiting. */
/** Called when a group is set as the current group. */
onFocus() {}
/** Called when a group is no longer the current group. */
onUnfocus() {}
/** Called when a group is explicitly exited. */
onExit() {}
// ================= Debug methods =================
......
......@@ -289,12 +289,13 @@ class SwitchAccess {
}
}
/** @return {chrome.automation.AutomationNode} */
/** @return {?chrome.automation.AutomationNode} */
getBackButtonAutomationNode() {
if (!this.backButtonAutomationNode_) {
this.findBackButtonNode_();
if (!this.backButtonAutomationNode_) {
console.log('Error: unable to find back button');
return null;
}
}
return this.backButtonAutomationNode_;
......
......@@ -13,6 +13,7 @@ const DefaultActionVerb = chrome.automation.DefaultActionVerb;
* - isGroup
* - isInteresting
* - isInterestingSubtree
* - isVisible
* - isTextInput
* - isNotContainer
* - isSwitchAccessMenuPanel
......@@ -39,8 +40,7 @@ const SwitchAccessPredicate = {
const state = node.state;
// Skip things that are offscreen or invisible.
if (state[StateType.OFFSCREEN] || !loc || loc.top < 0 || loc.left < 0 ||
state[StateType.INVISIBLE]) {
if (!SwitchAccessPredicate.isVisible(node)) {
return false;
}
......@@ -145,6 +145,16 @@ const SwitchAccessPredicate = {
isInteresting: (node, scope) => SwitchAccessPredicate.isActionable(node) ||
SwitchAccessPredicate.isGroup(node, scope),
/**
* Returns true if the element is visible to the user for any reason.
*
* @param {chrome.automation.AutomationNode} node
* @return {boolean}
*/
isVisible: (node) => !node.state[StateType.OFFSCREEN] && !!node.location &&
node.location.top >= 0 && node.location.left >= 0 &&
!node.state[StateType.INVISIBLE],
/**
* Returns true if there is an interesting node in the subtree containing
* |node| as its root (including |node| itself).
......
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