Commit 9e9b8a24 authored by Anastasia Helfinstein's avatar Anastasia Helfinstein Committed by Commit Bot

[Switch Access] Create DesktopNode type

The desktop needs to be fundamentally different from other root nodes
in Switch Access because it is the only root that does not have a back
button.

Bug: 1054482
Change-Id: I47d1f8f5383d271debf1ef5749c106015102679a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2065732
Commit-Queue: Anastasia Helfinstein <anastasi@google.com>
Reviewed-by: default avatarKatie Dektar <katie@chromium.org>
Cr-Commit-Position: refs/heads/master@{#744043}
parent e76e0e7f
...@@ -73,6 +73,7 @@ run_jsbundler("switch_access_copied_files") { ...@@ -73,6 +73,7 @@ run_jsbundler("switch_access_copied_files") {
"metrics.js", "metrics.js",
"navigation_manager.js", "navigation_manager.js",
"nodes/back_button_node.js", "nodes/back_button_node.js",
"nodes/desktop_node.js",
"nodes/editable_text_node.js", "nodes/editable_text_node.js",
"nodes/group_node.js", "nodes/group_node.js",
"nodes/keyboard_node.js", "nodes/keyboard_node.js",
...@@ -114,6 +115,7 @@ js2gtest("switch_access_extjs_tests") { ...@@ -114,6 +115,7 @@ js2gtest("switch_access_extjs_tests") {
sources = [ sources = [
"auto_scan_manager_test.js", "auto_scan_manager_test.js",
"navigation_manager_test.js", "navigation_manager_test.js",
"nodes/desktop_node_test.js",
"nodes/node_wrapper_test.js", "nodes/node_wrapper_test.js",
"nodes/tab_node_test.js", "nodes/tab_node_test.js",
"switch_access_predicate_test.js", "switch_access_predicate_test.js",
...@@ -179,6 +181,7 @@ js_type_check("closure_compile") { ...@@ -179,6 +181,7 @@ js_type_check("closure_compile") {
":back_button_node", ":back_button_node",
":background", ":background",
":commands", ":commands",
":desktop_node",
":editable_text_node", ":editable_text_node",
":event_helper", ":event_helper",
":focus_ring_manager", ":focus_ring_manager",
...@@ -231,6 +234,12 @@ js_library("commands") { ...@@ -231,6 +234,12 @@ js_library("commands") {
externs_list = [ "$externs_path/accessibility_private.js" ] externs_list = [ "$externs_path/accessibility_private.js" ]
} }
js_library("desktop_node") {
sources = [ "nodes/desktop_node.js" ]
deps = [ ":node_wrapper" ]
externs_list = [ "$externs_path/automation.js" ]
}
js_library("editable_text_node") { js_library("editable_text_node") {
sources = [ "nodes/editable_text_node.js" ] sources = [ "nodes/editable_text_node.js" ]
deps = [ deps = [
...@@ -330,6 +339,7 @@ js_library("metrics") { ...@@ -330,6 +339,7 @@ js_library("metrics") {
js_library("navigation_manager") { js_library("navigation_manager") {
deps = [ deps = [
":desktop_node",
":focus_ring_manager", ":focus_ring_manager",
":keyboard_node", ":keyboard_node",
":menu_manager", ":menu_manager",
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
"nodes/switch_access_node.js", "nodes/switch_access_node.js",
"nodes/node_wrapper.js", "nodes/node_wrapper.js",
"nodes/back_button_node.js", "nodes/back_button_node.js",
"nodes/desktop_node.js",
"nodes/editable_text_node.js", "nodes/editable_text_node.js",
"nodes/group_node.js", "nodes/group_node.js",
"nodes/keyboard_node.js", "nodes/keyboard_node.js",
......
...@@ -16,7 +16,7 @@ class NavigationManager { ...@@ -16,7 +16,7 @@ class NavigationManager {
this.desktop_ = desktop; this.desktop_ = desktop;
/** @private {!SARootNode} */ /** @private {!SARootNode} */
this.group_ = RootNodeWrapper.buildDesktopTree(this.desktop_); this.group_ = DesktopNode.build(this.desktop_);
/** @private {!SAChildNode} */ /** @private {!SAChildNode} */
this.node_ = this.group_.firstChild; this.node_ = this.group_.firstChild;
...@@ -113,8 +113,7 @@ class NavigationManager { ...@@ -113,8 +113,7 @@ class NavigationManager {
return NavigationManager.instance.group_; return NavigationManager.instance.group_;
} }
const desktopRoot = const desktopRoot = DesktopNode.build(NavigationManager.instance.desktop_);
RootNodeWrapper.buildDesktopTree(NavigationManager.instance.desktop_);
console.log(desktopRoot.debugString( console.log(desktopRoot.debugString(
wholeTree, '', NavigationManager.instance.node_)); wholeTree, '', NavigationManager.instance.node_));
return desktopRoot; return desktopRoot;
...@@ -198,7 +197,7 @@ class NavigationManager { ...@@ -198,7 +197,7 @@ class NavigationManager {
} }
// If there is no valid node in the group stack, go to the desktop. // If there is no valid node in the group stack, go to the desktop.
navigator.setGroup_(RootNodeWrapper.buildDesktopTree(navigator.desktop_)); navigator.setGroup_(DesktopNode.build(navigator.desktop_));
navigator.groupStack_ = []; navigator.groupStack_ = [];
} }
...@@ -333,7 +332,7 @@ class NavigationManager { ...@@ -333,7 +332,7 @@ class NavigationManager {
} }
this.groupStack_ = []; this.groupStack_ = [];
let group = RootNodeWrapper.buildDesktopTree(this.desktop_); let group = DesktopNode.build(this.desktop_);
while (ancestorList.length > 0) { while (ancestorList.length > 0) {
const ancestor = ancestorList.pop(); const ancestor = ancestorList.pop();
if (ancestor.role === chrome.automation.RoleType.DESKTOP) { if (ancestor.role === chrome.automation.RoleType.DESKTOP) {
......
...@@ -43,7 +43,7 @@ SwitchAccessNavigationManagerTest.prototype = { ...@@ -43,7 +43,7 @@ SwitchAccessNavigationManagerTest.prototype = {
function moveToPageContents() { function moveToPageContents() {
const navigator = NavigationManager.instance; const navigator = NavigationManager.instance;
// Start from the desktop node. // Start from the desktop node.
navigator.group_ = RootNodeWrapper.buildDesktopTree(navigator.desktop_); navigator.group_ = DesktopNode.build(navigator.desktop_);
navigator.node_ = navigator.group_.firstChild; navigator.node_ = navigator.group_.firstChild;
// The first item should be the browser window. // The first item should be the browser window.
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* This class handles interactions with the desktop automation node.
*/
class DesktopNode extends RootNodeWrapper {
/**
* @param {!AutomationNode} autoNode The automation node representing the
* desktop.
*/
constructor(autoNode) {
super(autoNode);
}
// ================= General methods =================
/** @override */
equals(other) {
// The underlying automation tree only has one desktop node, so all
// DesktopNode instances are equal.
return other instanceof DesktopNode;
}
/** @override */
isValidGroup() {
return true;
}
// ================= Private methods =================
/** @override */
refresh_() {
// Find the currently focused child.
let focusedChild = null;
for (const child of this.children) {
if (child.isFocused()) {
focusedChild = child;
break;
}
}
// Update this DesktopNode's children.
const childConstructor = (node) => NodeWrapper.create(node, this);
DesktopNode.findAndSetChildren(this, childConstructor);
// Set the new instance of that child to be the focused node.
for (const child of this.children) {
if (child.isEquivalentTo(focusedChild)) {
NavigationManager.forceFocusedNode(child);
return;
}
}
// If the previously focused node no longer exists, focus the first node in
// the group.
NavigationManager.forceFocusedNode(this.children[0]);
}
// ================= Static methods =================
/**
* @param {!AutomationNode} desktop
* @return {!DesktopNode}
*/
static build(desktop) {
const root = new DesktopNode(desktop);
const childConstructor = (autoNode) => NodeWrapper.create(autoNode, root);
DesktopNode.findAndSetChildren(root, childConstructor);
return root;
}
/** @override */
static findAndSetChildren(root, childConstructor) {
const interestingChildren = RootNodeWrapper.getInterestingChildren(root);
if (interestingChildren.length < 1) {
throw SwitchAccess.error(
SAConstants.ErrorType.MALFORMED_DESKTOP,
'Desktop node must have at least 1 interesting child.');
}
root.children = interestingChildren.map(childConstructor);
}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
GEN_INCLUDE(['../switch_access_e2e_test_base.js']);
/**
* @constructor
* @extends {SwitchAccessE2ETest}
*/
function SwitchAccessDesktopNodeTest() {
SwitchAccessE2ETest.call(this);
}
SwitchAccessDesktopNodeTest.prototype = {
__proto__: SwitchAccessE2ETest.prototype,
};
TEST_F('SwitchAccessDesktopNodeTest', 'Build', function() {
this.runWithLoadedTree('', (desktop) => {
const desktopNode = DesktopNode.build(desktop);
const children = desktopNode.children;
for (let i = 0; i < children.length; i++) {
const child = children[i];
// The desktop tree should not include a back button.
assertFalse(child instanceof BackButtonNode);
// Check that the children form a loop.
const next = children[(i + 1) % children.length];
assertEquals(
next, child.next, 'next not properly initialized on child ' + i);
// We add children.length to ensure the value is greater than zero.
const previous = children[(i - 1 + children.length) % children.length];
assertEquals(
previous, child.previous,
'previous not properly initialized on child ' + i);
}
});
});
...@@ -302,7 +302,7 @@ class RootNodeWrapper extends SARootNode { ...@@ -302,7 +302,7 @@ class RootNodeWrapper extends SARootNode {
/** /**
* Refreshes the children of this root node. * Refreshes the children of this root node.
* @private * @protected
*/ */
refresh_() { refresh_() {
// Find the currently focused child. // Find the currently focused child.
...@@ -340,26 +340,6 @@ class RootNodeWrapper extends SARootNode { ...@@ -340,26 +340,6 @@ class RootNodeWrapper extends SARootNode {
// ================= Static methods ================= // ================= Static methods =================
/**
* @param {!AutomationNode} desktop
* @return {!RootNodeWrapper}
*/
static buildDesktopTree(desktop) {
const root = new RootNodeWrapper(desktop);
const interestingChildren = RootNodeWrapper.getInterestingChildren(root);
if (interestingChildren.length < 1) {
throw SwitchAccess.error(
SAConstants.ErrorType.MALFORMED_DESKTOP,
'Desktop node must have at least 1 interesting child.');
}
const childConstructor = (autoNode) => NodeWrapper.create(autoNode, root);
root.children = interestingChildren.map(childConstructor);
return root;
}
/** /**
* @param {!AutomationNode} rootNode * @param {!AutomationNode} rootNode
* @return {!RootNodeWrapper} * @return {!RootNodeWrapper}
......
...@@ -16,29 +16,6 @@ SwitchAccessNodeWrapperTest.prototype = { ...@@ -16,29 +16,6 @@ SwitchAccessNodeWrapperTest.prototype = {
__proto__: SwitchAccessE2ETest.prototype, __proto__: SwitchAccessE2ETest.prototype,
}; };
TEST_F('SwitchAccessNodeWrapperTest', 'BuildDesktopTree', function() {
this.runWithLoadedTree('', (desktop) => {
const desktopRootNode = RootNodeWrapper.buildDesktopTree(desktop);
const children = desktopRootNode.children;
for (let i = 0; i < children.length; i++) {
const child = children[i];
// The desktop tree should not include a back button.
assertFalse(child instanceof BackButtonNode);
// Check that the children form a loop.
const next = children[(i + 1) % children.length];
assertEquals(
next, child.next, 'next not properly initialized on child ' + i);
// We add children.length to ensure the value is greater than zero.
const previous = children[(i - 1 + children.length) % children.length];
assertEquals(
previous, child.previous,
'previous not properly initialized on child ' + i);
}
});
});
TEST_F('SwitchAccessNodeWrapperTest', 'AsRootNode', function() { TEST_F('SwitchAccessNodeWrapperTest', 'AsRootNode', function() {
const website = `<div aria-label="outer"> const website = `<div aria-label="outer">
<div aria-label="inner"> <div aria-label="inner">
...@@ -75,46 +52,45 @@ TEST_F('SwitchAccessNodeWrapperTest', 'AsRootNode', function() { ...@@ -75,46 +52,45 @@ TEST_F('SwitchAccessNodeWrapperTest', 'AsRootNode', function() {
TEST_F('SwitchAccessNodeWrapperTest', 'Equals', function() { TEST_F('SwitchAccessNodeWrapperTest', 'Equals', function() {
this.runWithLoadedTree('', (desktop) => { this.runWithLoadedTree('', (desktop) => {
const desktopRootNode = RootNodeWrapper.buildDesktopTree(desktop); const desktopNode = DesktopNode.build(desktop);
let childGroup = desktopRootNode.firstChild; let childGroup = desktopNode.firstChild;
let i = 0; let i = 0;
while (!childGroup.isGroup() && i < desktopRootNode.children.length) { while (!childGroup.isGroup() && i < desktopNode.children.length) {
childGroup = childGroup.next; childGroup = childGroup.next;
i++; i++;
} }
childGroup = childGroup.asRootNode(); childGroup = childGroup.asRootNode();
assertFalse(desktopRootNode.equals(), 'Root node equals nothing'); assertFalse(desktopNode.equals(), 'Root node equals nothing');
assertFalse( assertFalse(
desktopRootNode.equals(new SARootNode()), desktopNode.equals(new SARootNode()),
'Different type root nodes are equal'); 'Different type root nodes are equal');
assertFalse( assertFalse(
new SARootNode().equals(desktopRootNode), new SARootNode().equals(desktopNode),
'Equals is not symmetric? Different types of root are equal'); 'Equals is not symmetric? Different types of root are equal');
assertFalse( assertFalse(
desktopRootNode.equals(childGroup), desktopNode.equals(childGroup),
'Groups with different children are equal'); 'Groups with different children are equal');
assertFalse( assertFalse(
childGroup.equals(desktopRootNode), childGroup.equals(desktopNode),
'Equals is not symmetric? Groups with different children are equal'); 'Equals is not symmetric? Groups with different children are equal');
assertTrue( assertTrue(
desktopRootNode.equals(desktopRootNode), desktopNode.equals(desktopNode),
'Equals is not reflexive? (root node)'); 'Equals is not reflexive? (root node)');
const desktopCopy = RootNodeWrapper.buildDesktopTree(desktop); const desktopCopy = DesktopNode.build(desktop);
assertTrue( assertTrue(
desktopRootNode.equals(desktopCopy), 'Two desktop roots are not equal'); desktopNode.equals(desktopCopy), 'Two desktop roots are not equal');
assertTrue( assertTrue(
desktopCopy.equals(desktopRootNode), desktopCopy.equals(desktopNode),
'Equals is not symmetric? Two desktop roots aren\'t equal'); 'Equals is not symmetric? Two desktop roots aren\'t equal');
const wrappedNode = desktopRootNode.firstChild; const wrappedNode = desktopNode.firstChild;
assertTrue( assertTrue(
wrappedNode instanceof NodeWrapper, wrappedNode instanceof NodeWrapper,
'Child node is not of type NodeWrapper'); 'Child node is not of type NodeWrapper');
assertGT( assertGT(desktopNode.children.length, 1, 'Desktop root has only 1 child');
desktopRootNode.children.length, 1, 'Desktop root has only 1 child');
assertFalse(wrappedNode.equals(), 'Child NodeWrapper equals nothing'); assertFalse(wrappedNode.equals(), 'Child NodeWrapper equals nothing');
assertFalse( assertFalse(
...@@ -124,14 +100,14 @@ TEST_F('SwitchAccessNodeWrapperTest', 'Equals', function() { ...@@ -124,14 +100,14 @@ TEST_F('SwitchAccessNodeWrapperTest', 'Equals', function() {
new BackButtonNode().equals(wrappedNode), new BackButtonNode().equals(wrappedNode),
'Equals is not symmetric? NodeWrapper equals a BackButtonNode'); 'Equals is not symmetric? NodeWrapper equals a BackButtonNode');
assertFalse( assertFalse(
wrappedNode.equals(desktopRootNode.lastChild), wrappedNode.equals(desktopNode.lastChild),
'Children with different base nodes are equal'); 'Children with different base nodes are equal');
assertFalse( assertFalse(
desktopRootNode.lastChild.equals(wrappedNode), desktopNode.lastChild.equals(wrappedNode),
'Equals is not symmetric? Nodes with different base nodes are equal'); 'Equals is not symmetric? Nodes with different base nodes are equal');
const equivalentWrappedNode = const equivalentWrappedNode =
NodeWrapper.create(wrappedNode.baseNode_, desktopRootNode); NodeWrapper.create(wrappedNode.baseNode_, desktopNode);
assertTrue( assertTrue(
wrappedNode.equals(wrappedNode), wrappedNode.equals(wrappedNode),
'Equals is not reflexive? (child node)'); 'Equals is not reflexive? (child node)');
......
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