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") {
"metrics.js",
"navigation_manager.js",
"nodes/back_button_node.js",
"nodes/desktop_node.js",
"nodes/editable_text_node.js",
"nodes/group_node.js",
"nodes/keyboard_node.js",
......@@ -114,6 +115,7 @@ js2gtest("switch_access_extjs_tests") {
sources = [
"auto_scan_manager_test.js",
"navigation_manager_test.js",
"nodes/desktop_node_test.js",
"nodes/node_wrapper_test.js",
"nodes/tab_node_test.js",
"switch_access_predicate_test.js",
......@@ -179,6 +181,7 @@ js_type_check("closure_compile") {
":back_button_node",
":background",
":commands",
":desktop_node",
":editable_text_node",
":event_helper",
":focus_ring_manager",
......@@ -231,6 +234,12 @@ js_library("commands") {
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") {
sources = [ "nodes/editable_text_node.js" ]
deps = [
......@@ -330,6 +339,7 @@ js_library("metrics") {
js_library("navigation_manager") {
deps = [
":desktop_node",
":focus_ring_manager",
":keyboard_node",
":menu_manager",
......
......@@ -23,6 +23,7 @@
"nodes/switch_access_node.js",
"nodes/node_wrapper.js",
"nodes/back_button_node.js",
"nodes/desktop_node.js",
"nodes/editable_text_node.js",
"nodes/group_node.js",
"nodes/keyboard_node.js",
......
......@@ -16,7 +16,7 @@ class NavigationManager {
this.desktop_ = desktop;
/** @private {!SARootNode} */
this.group_ = RootNodeWrapper.buildDesktopTree(this.desktop_);
this.group_ = DesktopNode.build(this.desktop_);
/** @private {!SAChildNode} */
this.node_ = this.group_.firstChild;
......@@ -113,8 +113,7 @@ class NavigationManager {
return NavigationManager.instance.group_;
}
const desktopRoot =
RootNodeWrapper.buildDesktopTree(NavigationManager.instance.desktop_);
const desktopRoot = DesktopNode.build(NavigationManager.instance.desktop_);
console.log(desktopRoot.debugString(
wholeTree, '', NavigationManager.instance.node_));
return desktopRoot;
......@@ -198,7 +197,7 @@ class NavigationManager {
}
// 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_ = [];
}
......@@ -333,7 +332,7 @@ class NavigationManager {
}
this.groupStack_ = [];
let group = RootNodeWrapper.buildDesktopTree(this.desktop_);
let group = DesktopNode.build(this.desktop_);
while (ancestorList.length > 0) {
const ancestor = ancestorList.pop();
if (ancestor.role === chrome.automation.RoleType.DESKTOP) {
......
......@@ -43,7 +43,7 @@ SwitchAccessNavigationManagerTest.prototype = {
function moveToPageContents() {
const navigator = NavigationManager.instance;
// Start from the desktop node.
navigator.group_ = RootNodeWrapper.buildDesktopTree(navigator.desktop_);
navigator.group_ = DesktopNode.build(navigator.desktop_);
navigator.node_ = navigator.group_.firstChild;
// 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 {
/**
* Refreshes the children of this root node.
* @private
* @protected
*/
refresh_() {
// Find the currently focused child.
......@@ -340,26 +340,6 @@ class RootNodeWrapper extends SARootNode {
// ================= 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
* @return {!RootNodeWrapper}
......
......@@ -16,29 +16,6 @@ SwitchAccessNodeWrapperTest.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() {
const website = `<div aria-label="outer">
<div aria-label="inner">
......@@ -75,46 +52,45 @@ TEST_F('SwitchAccessNodeWrapperTest', 'AsRootNode', function() {
TEST_F('SwitchAccessNodeWrapperTest', 'Equals', function() {
this.runWithLoadedTree('', (desktop) => {
const desktopRootNode = RootNodeWrapper.buildDesktopTree(desktop);
const desktopNode = DesktopNode.build(desktop);
let childGroup = desktopRootNode.firstChild;
let childGroup = desktopNode.firstChild;
let i = 0;
while (!childGroup.isGroup() && i < desktopRootNode.children.length) {
while (!childGroup.isGroup() && i < desktopNode.children.length) {
childGroup = childGroup.next;
i++;
}
childGroup = childGroup.asRootNode();
assertFalse(desktopRootNode.equals(), 'Root node equals nothing');
assertFalse(desktopNode.equals(), 'Root node equals nothing');
assertFalse(
desktopRootNode.equals(new SARootNode()),
desktopNode.equals(new SARootNode()),
'Different type root nodes are equal');
assertFalse(
new SARootNode().equals(desktopRootNode),
new SARootNode().equals(desktopNode),
'Equals is not symmetric? Different types of root are equal');
assertFalse(
desktopRootNode.equals(childGroup),
desktopNode.equals(childGroup),
'Groups with different children are equal');
assertFalse(
childGroup.equals(desktopRootNode),
childGroup.equals(desktopNode),
'Equals is not symmetric? Groups with different children are equal');
assertTrue(
desktopRootNode.equals(desktopRootNode),
desktopNode.equals(desktopNode),
'Equals is not reflexive? (root node)');
const desktopCopy = RootNodeWrapper.buildDesktopTree(desktop);
const desktopCopy = DesktopNode.build(desktop);
assertTrue(
desktopRootNode.equals(desktopCopy), 'Two desktop roots are not equal');
desktopNode.equals(desktopCopy), 'Two desktop roots are not equal');
assertTrue(
desktopCopy.equals(desktopRootNode),
desktopCopy.equals(desktopNode),
'Equals is not symmetric? Two desktop roots aren\'t equal');
const wrappedNode = desktopRootNode.firstChild;
const wrappedNode = desktopNode.firstChild;
assertTrue(
wrappedNode instanceof NodeWrapper,
'Child node is not of type NodeWrapper');
assertGT(
desktopRootNode.children.length, 1, 'Desktop root has only 1 child');
assertGT(desktopNode.children.length, 1, 'Desktop root has only 1 child');
assertFalse(wrappedNode.equals(), 'Child NodeWrapper equals nothing');
assertFalse(
......@@ -124,14 +100,14 @@ TEST_F('SwitchAccessNodeWrapperTest', 'Equals', function() {
new BackButtonNode().equals(wrappedNode),
'Equals is not symmetric? NodeWrapper equals a BackButtonNode');
assertFalse(
wrappedNode.equals(desktopRootNode.lastChild),
wrappedNode.equals(desktopNode.lastChild),
'Children with different base nodes are equal');
assertFalse(
desktopRootNode.lastChild.equals(wrappedNode),
desktopNode.lastChild.equals(wrappedNode),
'Equals is not symmetric? Nodes with different base nodes are equal');
const equivalentWrappedNode =
NodeWrapper.create(wrappedNode.baseNode_, desktopRootNode);
NodeWrapper.create(wrappedNode.baseNode_, desktopNode);
assertTrue(
wrappedNode.equals(wrappedNode),
'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