Commit bf0a104c authored by Zach Helfinstein's avatar Zach Helfinstein Committed by Commit Bot

Refactor SwitchAccess to use same TreeWalker as CVox

Bug: 873016
Change-Id: If8ee78b9e739aaad0403964948534c3305b26580
Reviewed-on: https://chromium-review.googlesource.com/1170383Reviewed-by: default avatarDavid Tseng <dtseng@chromium.org>
Commit-Queue: Zach Helfinstein <zhelfins@chromium.org>
Cr-Commit-Position: refs/heads/master@{#582169}
parent f3b7eb37
...@@ -7,6 +7,7 @@ import("//chrome/common/features.gni") ...@@ -7,6 +7,7 @@ import("//chrome/common/features.gni")
import("//components/nacl/features.gni") import("//components/nacl/features.gni")
import("//testing/test.gni") import("//testing/test.gni")
import("//chrome/test/base/js2gtest.gni") import("//chrome/test/base/js2gtest.gni")
import("//third_party/closure_compiler/compile_js.gni")
import("run_jsbundler.gni") import("run_jsbundler.gni")
assert(is_chromeos) assert(is_chromeos)
...@@ -643,3 +644,36 @@ js2gtest("chromevox_extjs_tests") { ...@@ -643,3 +644,36 @@ js2gtest("chromevox_extjs_tests") {
] ]
defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ] defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
} }
js_library("tree_walker") {
sources = [
"cvox2/background/tree_walker.js",
]
deps = [
":automation_predicate",
":constants",
]
externs_list = [
"$externs_path/automation.js",
"$externs_path/chrome_extensions.js",
]
}
js_library("automation_predicate") {
sources = [
"cvox2/background/automation_predicate.js",
]
deps = [
":constants",
]
externs_list = [
"$externs_path/automation.js",
"$externs_path/chrome_extensions.js",
]
}
js_library("constants") {
sources = [
"cvox2/background/constants.js",
]
}
...@@ -26,9 +26,11 @@ run_jsbundler("switch_access_copied_files") { ...@@ -26,9 +26,11 @@ run_jsbundler("switch_access_copied_files") {
mode = "copy" mode = "copy"
dest_dir = switch_access_dir dest_dir = switch_access_dir
sources = [ sources = [
"../chromevox/cvox2/background/constants.js",
"../chromevox/cvox2/background/tree_walker.js",
"../select_to_speak/closure_shim.js",
"auto_scan_manager.js", "auto_scan_manager.js",
"automation_manager.js", "automation_manager.js",
"automation_predicate.js",
"background.js", "background.js",
"commands.js", "commands.js",
"keyboard_handler.js", "keyboard_handler.js",
...@@ -37,11 +39,13 @@ run_jsbundler("switch_access_copied_files") { ...@@ -37,11 +39,13 @@ run_jsbundler("switch_access_copied_files") {
"options.js", "options.js",
"prefs.js", "prefs.js",
"switch_access.js", "switch_access.js",
"tree_walker.js", "switch_access_predicate.js",
] ]
rewrite_rules = [ rewrite_rules = [
rebase_path(".", root_build_dir) + ":", rebase_path(".", root_build_dir) + ":",
rebase_path(closure_library_dir, root_build_dir) + ":closure", rebase_path(closure_library_dir, root_build_dir) + ":closure",
rebase_path("../chromevox/cvox2/background", root_build_dir) + ":",
rebase_path("../select_to_speak", root_build_dir) + ":",
] ]
} }
...@@ -56,14 +60,12 @@ js2gtest("switch_access_webuijs_tests") { ...@@ -56,14 +60,12 @@ js2gtest("switch_access_webuijs_tests") {
test_type = "webui" test_type = "webui"
sources = [ sources = [
"auto_scan_manager_unittest.gtestjs", "auto_scan_manager_unittest.gtestjs",
"automation_predicate_unittest.gtestjs", "switch_access_predicate_unittest.gtestjs",
"tree_walker_unittest.gtestjs",
] ]
extra_js_files = [ extra_js_files = [
"auto_scan_manager.js", "auto_scan_manager.js",
"automation_predicate.js", "switch_access_predicate.js",
"test_support.js", "test_support.js",
"tree_walker.js",
] ]
defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ] defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
} }
...@@ -113,16 +115,18 @@ js_type_check("closure_compile") { ...@@ -113,16 +115,18 @@ js_type_check("closure_compile") {
deps = [ deps = [
":auto_scan_manager", ":auto_scan_manager",
":automation_manager", ":automation_manager",
":automation_predicate",
":background", ":background",
":commands", ":commands",
":keyboard_handler", ":keyboard_handler",
":options", ":options",
":prefs", ":prefs",
":switch_access_predicate",
"../chromevox:constants",
"../chromevox:tree_walker",
"../select_to_speak:closure_shim",
# ":switch_access", # ":switch_access",
":switch_access_interface", ":switch_access_interface",
":tree_walker",
] ]
} }
...@@ -134,8 +138,9 @@ js_library("auto_scan_manager") { ...@@ -134,8 +138,9 @@ js_library("auto_scan_manager") {
js_library("automation_manager") { js_library("automation_manager") {
deps = [ deps = [
":automation_predicate", ":switch_access_predicate",
":tree_walker", "../chromevox:constants",
"../chromevox:tree_walker",
] ]
externs_list = [ externs_list = [
"$externs_path/accessibility_private.js", "$externs_path/accessibility_private.js",
...@@ -143,7 +148,7 @@ js_library("automation_manager") { ...@@ -143,7 +148,7 @@ js_library("automation_manager") {
] ]
} }
js_library("automation_predicate") { js_library("switch_access_predicate") {
externs_list = [ "$externs_path/automation.js" ] externs_list = [ "$externs_path/automation.js" ]
} }
...@@ -198,7 +203,3 @@ js_library("switch_access") { ...@@ -198,7 +203,3 @@ js_library("switch_access") {
js_library("switch_access_interface") { js_library("switch_access_interface") {
} }
js_library("tree_walker") {
externs_list = [ "$externs_path/automation.js" ]
}
...@@ -40,13 +40,6 @@ function AutomationManager(desktop) { ...@@ -40,13 +40,6 @@ function AutomationManager(desktop) {
*/ */
this.scopeStack_ = []; this.scopeStack_ = [];
/**
* Moves to the appropriate node in the accessibility tree.
*
* @private {!AutomationTreeWalker}
*/
this.treeWalker_ = this.createTreeWalker_(desktop);
this.init_(); this.init_();
} }
...@@ -102,10 +95,9 @@ AutomationManager.prototype = { ...@@ -102,10 +95,9 @@ AutomationManager.prototype = {
// Move to focused node. // Move to focused node.
this.node_ = event.target; this.node_ = event.target;
this.treeWalker_ = this.createTreeWalker_(this.scope_, this.node_);
// In case the node that gained focus is not a subtreeLeaf. // In case the node that gained focus is not a subtreeLeaf.
if (AutomationPredicate.isSubtreeLeaf(this.node_, this.scope_)) { if (SwitchAccessPredicate.isSubtreeLeaf(this.node_, this.scope_)) {
this.printNode_(this.node_); this.printNode_(this.node_);
this.updateFocusRing_(); this.updateFocusRing_();
} else } else
...@@ -135,7 +127,7 @@ AutomationManager.prototype = { ...@@ -135,7 +127,7 @@ AutomationManager.prototype = {
let ancestor = ancestorList.pop(); let ancestor = ancestorList.pop();
if (ancestor.role === chrome.automation.RoleType.DESKTOP) if (ancestor.role === chrome.automation.RoleType.DESKTOP)
continue; continue;
if (AutomationPredicate.isGroup(ancestor, this.scope_)) { if (SwitchAccessPredicate.isGroup(ancestor, this.scope_)) {
this.scopeStack_.push(this.scope_); this.scopeStack_.push(this.scope_);
this.scope_ = ancestor; this.scope_ = ancestor;
} }
...@@ -189,12 +181,24 @@ AutomationManager.prototype = { ...@@ -189,12 +181,24 @@ AutomationManager.prototype = {
// If node is invalid, set node to last valid scope. // If node is invalid, set node to last valid scope.
this.startAtValidNode_(); this.startAtValidNode_();
let node = this.treeWalker_.moveToNode(doNext); let treeWalker = new AutomationTreeWalker(
if (node) { this.node_, doNext ? constants.Dir.FORWARD : constants.Dir.BACKWARD,
this.node_ = node; SwitchAccessPredicate.restrictions(this.scope_));
this.printNode_(this.node_);
this.updateFocusRing_(); let node = treeWalker.next().node;
// If treeWalker returns undefined, that means we're at the end of the tree
// and we should start over.
if (!node) {
if (doNext)
node = this.scope_;
else
node = this.youngestDescendant_(this.scope_);
} }
this.node_ = node;
this.printNode_(this.node_);
this.updateFocusRing_();
}, },
/** /**
...@@ -220,17 +224,15 @@ AutomationManager.prototype = { ...@@ -220,17 +224,15 @@ AutomationManager.prototype = {
this.scope_ = this.scopeStack_.pop(); this.scope_ = this.scopeStack_.pop();
} while (!this.scope_.role && this.scopeStack_.length > 0); } while (!this.scope_.role && this.scopeStack_.length > 0);
this.treeWalker_ = this.createTreeWalker_(this.scope_, this.node_);
this.updateFocusRing_(); this.updateFocusRing_();
console.log('Moved to previous scope'); console.log('Moved to previous scope');
this.printNode_(this.node_); this.printNode_(this.node_);
return; return;
} }
if (AutomationPredicate.isGroup(this.node_, this.scope_)) { if (SwitchAccessPredicate.isGroup(this.node_, this.scope_)) {
this.scopeStack_.push(this.scope_); this.scopeStack_.push(this.scope_);
this.scope_ = this.node_; this.scope_ = this.node_;
this.treeWalker_ = this.createTreeWalker_(this.scope_);
console.log('Entered scope'); console.log('Entered scope');
this.moveToNode(true); this.moveToNode(true);
return; return;
...@@ -250,7 +252,7 @@ AutomationManager.prototype = { ...@@ -250,7 +252,7 @@ AutomationManager.prototype = {
let color; let color;
if (this.node_ === this.scope_) if (this.node_ === this.scope_)
color = AutomationManager.Color.SCOPE; color = AutomationManager.Color.SCOPE;
else if (AutomationPredicate.isGroup(this.node_, this.scope_)) else if (SwitchAccessPredicate.isGroup(this.node_, this.scope_))
color = AutomationManager.Color.GROUP; color = AutomationManager.Color.GROUP;
else else
color = AutomationManager.Color.LEAF; color = AutomationManager.Color.LEAF;
...@@ -281,36 +283,6 @@ AutomationManager.prototype = { ...@@ -281,36 +283,6 @@ AutomationManager.prototype = {
this.node_ = this.scopeStack_.pop(); this.node_ = this.scopeStack_.pop();
this.scope_ = this.node_; this.scope_ = this.node_;
} }
this.treeWalker_ = this.createTreeWalker_(this.scope_);
},
/**
* Create an AutomationTreeWalker for the subtree with |scope| as its root.
* If |opt_start| is defined, the tree walker will start walking the tree
* from |opt_start|; otherwise, it will start from |scope|.
*
* @param {!chrome.automation.AutomationNode} scope
* @param {!chrome.automation.AutomationNode=} opt_start
* @private
* @return {!AutomationTreeWalker}
*/
createTreeWalker_: function(scope, opt_start) {
// If no explicit start node, start walking the tree from |scope|.
let start = opt_start || scope;
let leafPred = function(node) {
return (node !== scope &&
AutomationPredicate.isSubtreeLeaf(node, scope)) ||
!AutomationPredicate.isInterestingSubtree(node);
};
let visitPred = function(node) {
// Avoid visiting the top-level root node (i.e., the desktop node).
return node !== this.desktop_ &&
AutomationPredicate.isSubtreeLeaf(node, scope);
}.bind(this);
let restrictions = {leaf: leafPred, visit: visitPred};
return new AutomationTreeWalker(start, scope, restrictions);
}, },
// TODO(elichtenberg): Move print functions to a custom logger class. Only // TODO(elichtenberg): Move print functions to a custom logger class. Only
...@@ -343,50 +315,19 @@ AutomationManager.prototype = { ...@@ -343,50 +315,19 @@ AutomationManager.prototype = {
}, },
/** /**
* Move to the next sibling of this.node_ if it has one. * Get the youngest descendant of |node|, if it has one within the current
*/ * scope.
debugMoveToNext: function() { *
let next = this.treeWalker_.debugMoveToNext(this.node_); * @param {!chrome.automation.AutomationNode} node
if (next) { * @return {!chrome.automation.AutomationNode}
this.node_ = next; * @private
this.printNode_(this.node_);
chrome.accessibilityPrivate.setFocusRing([this.node_.location]);
}
},
/**
* Move to the previous sibling of this.node_ if it has one.
*/ */
debugMoveToPrevious: function() { youngestDescendant_: function(node) {
let prev = this.treeWalker_.debugMoveToPrevious(this.node_); let leaf = SwitchAccessPredicate.leaf(this.scope_);
if (prev) {
this.node_ = prev;
this.printNode_(this.node_);
chrome.accessibilityPrivate.setFocusRing([this.node_.location]);
}
},
/** while (node.lastChild && !leaf(node))
* Move to the first child of this.node_ if it has one. node = node.lastChild;
*/
debugMoveToFirstChild: function() {
let child = this.treeWalker_.debugMoveToFirstChild(this.node_);
if (child) {
this.node_ = child;
this.printNode_(this.node_);
chrome.accessibilityPrivate.setFocusRing([this.node_.location]);
}
},
/** return node;
* Move to the parent of this.node_ if it has one.
*/
debugMoveToParent: function() {
let parent = this.treeWalker_.debugMoveToParent(this.node_);
if (parent) {
this.node_ = parent;
this.printNode_(this.node_);
chrome.accessibilityPrivate.setFocusRing([this.node_.location]);
}
} }
}; };
...@@ -78,24 +78,6 @@ Commands.prototype = { ...@@ -78,24 +78,6 @@ Commands.prototype = {
'options': { 'options': {
'defaultKeyCode': 52, /* '4' key */ 'defaultKeyCode': 52, /* '4' key */
'binding': this.switchAccess_.showOptionsPage.bind(this.switchAccess_) 'binding': this.switchAccess_.showOptionsPage.bind(this.switchAccess_)
},
'debugNext': {
'defaultKeyCode': -1, /* unused key */
'binding': this.switchAccess_.debugMoveToNext.bind(this.switchAccess_)
},
'debugPrevious': {
'defaultKeyCode': -1, /* unused key */
'binding':
this.switchAccess_.debugMoveToPrevious.bind(this.switchAccess_)
},
'debugChild': {
'defaultKeyCode': -1, /* unused key */
'binding':
this.switchAccess_.debugMoveToFirstChild.bind(this.switchAccess_)
},
'debugParent': {
'defaultKeyCode': -1, /* unused key */
'binding': this.switchAccess_.debugMoveToParent.bind(this.switchAccess_)
} }
}; };
} }
......
...@@ -13,8 +13,10 @@ ...@@ -13,8 +13,10 @@
"scripts": [ "scripts": [
"auto_scan_manager.js", "auto_scan_manager.js",
"automation_manager.js", "automation_manager.js",
"automation_predicate.js", "switch_access_predicate.js",
"closure_shim.js",
"commands.js", "commands.js",
"constants.js",
"keyboard_handler.js", "keyboard_handler.js",
"prefs.js", "prefs.js",
"switch_access.js", "switch_access.js",
......
...@@ -224,45 +224,5 @@ SwitchAccess.prototype = { ...@@ -224,45 +224,5 @@ SwitchAccess.prototype = {
*/ */
keyCodeIsUsed: function(keyCode) { keyCodeIsUsed: function(keyCode) {
return this.switchAccessPrefs_.keyCodeIsUsed(keyCode); return this.switchAccessPrefs_.keyCodeIsUsed(keyCode);
},
/**
* Move to the next sibling of the current node if it has one.
*
* @override
*/
debugMoveToNext: function() {
if (this.automationManager_)
this.automationManager_.debugMoveToNext();
},
/**
* Move to the previous sibling of the current node if it has one.
*
* @override
*/
debugMoveToPrevious: function() {
if (this.automationManager_)
this.automationManager_.debugMoveToPrevious();
},
/**
* Move to the first child of the current node if it has one.
*
* @override
*/
debugMoveToFirstChild: function() {
if (this.automationManager_)
this.automationManager_.debugMoveToFirstChild();
},
/**
* Move to the parent of the current node if it has one.
*
* @override
*/
debugMoveToParent: function() {
if (this.automationManager_)
this.automationManager_.debugMoveToParent();
} }
}; };
...@@ -99,25 +99,5 @@ SwitchAccessInterface.prototype = { ...@@ -99,25 +99,5 @@ SwitchAccessInterface.prototype = {
* @param {number} keyCode * @param {number} keyCode
* @return {boolean} * @return {boolean}
*/ */
keyCodeIsUsed: function(keyCode) {}, keyCodeIsUsed: function(keyCode) {}
/**
* Move to the next sibling of the current node if it has one.
*/
debugMoveToNext: function() {},
/**
* Move to the previous sibling of the current node if it has one.
*/
debugMoveToPrevious: function() {},
/**
* Move to the first child of the current node if it has one.
*/
debugMoveToFirstChild: function() {},
/**
* Move to the parent of the current node if it has one.
*/
debugMoveToParent: function() {}
}; };
...@@ -8,19 +8,75 @@ ...@@ -8,19 +8,75 @@
* *
* @constructor * @constructor
*/ */
function AutomationPredicate() {} function SwitchAccessPredicate() {}
/** /**
* Returns true if |node| is a subtreeLeaf, meaning that |node| is either * Returns a Restrictions object ready to be passed to AutomationTreeWalker.
* interesting or a group (both defined below). *
* @param {!chrome.automation.AutomationNode} scope
* @return {!AutomationTreeWalkerRestriction}
*/
SwitchAccessPredicate.restrictions = function(scope) {
return {
leaf: SwitchAccessPredicate.leaf(scope),
root: SwitchAccessPredicate.root(scope),
visit: SwitchAccessPredicate.visit(scope)
};
};
/**
* Creates a function that confirms if |node| is a terminal leaf node of a
* SwitchAccess scope tree when |scope| is the root.
*
* @param {!chrome.automation.AutomationNode} scope
* @return {function(!chrome.automation.AutomationNode): boolean}
*/
SwitchAccessPredicate.leaf = function(scope) {
return function(node) {
return (node !== scope &&
SwitchAccessPredicate.isSubtreeLeaf(node, scope)) ||
!SwitchAccessPredicate.isInterestingSubtree(node);
}.bind(scope);
};
/**
* Creates a function that confirms if |node| is the root of a SwitchAccess
* scope tree when |scope| is the root.
*
* @param {!chrome.automation.AutomationNode} scope
* @return {function(!chrome.automation.AutomationNode): boolean}
*/
SwitchAccessPredicate.root = function(scope) {
return function(node) {
return node === scope;
}.bind(scope);
};
/**
* Creates a function that determines whether |node| is to be visited in the
* SwitchAccess scope tree with |scope| as the root.
*
* @param {!chrome.automation.AutomationNode} scope
* @return {function(!chrome.automation.AutomationNode): boolean}
*/
SwitchAccessPredicate.visit = function(scope) {
return function(node) {
return node.role !== chrome.automation.RoleType.DESKTOP &&
SwitchAccessPredicate.isSubtreeLeaf(node, scope);
}.bind(scope);
};
/**
* Returns true if |node| is a subtreeLeaf, meaning that |node|
* is either interesting or a group (both defined below).
* *
* @param {!chrome.automation.AutomationNode} node * @param {!chrome.automation.AutomationNode} node
* @param {!chrome.automation.AutomationNode} scope * @param {!chrome.automation.AutomationNode} scope
* @return {boolean} * @return {boolean}
*/ */
AutomationPredicate.isSubtreeLeaf = function(node, scope) { SwitchAccessPredicate.isSubtreeLeaf = function(node, scope) {
return AutomationPredicate.isActionable(node) || return SwitchAccessPredicate.isActionable(node) ||
AutomationPredicate.isGroup(node, scope); SwitchAccessPredicate.isGroup(node, scope);
}; };
/** /**
...@@ -35,8 +91,8 @@ AutomationPredicate.isSubtreeLeaf = function(node, scope) { ...@@ -35,8 +91,8 @@ AutomationPredicate.isSubtreeLeaf = function(node, scope) {
* @param {!chrome.automation.AutomationNode} scope * @param {!chrome.automation.AutomationNode} scope
* @return {boolean} * @return {boolean}
*/ */
AutomationPredicate.isGroup = function(node, scope) { SwitchAccessPredicate.isGroup = function(node, scope) {
if (node !== scope && AutomationPredicate.hasSameLocation_(node, scope)) if (node !== scope && SwitchAccessPredicate.hasSameLocation_(node, scope))
return false; return false;
// Work around for client nested in client. No need to have user select both // Work around for client nested in client. No need to have user select both
...@@ -49,7 +105,7 @@ AutomationPredicate.isGroup = function(node, scope) { ...@@ -49,7 +105,7 @@ AutomationPredicate.isGroup = function(node, scope) {
let interestingBranches = 0; let interestingBranches = 0;
let children = node.children || []; let children = node.children || [];
for (let child of children) { for (let child of children) {
if (AutomationPredicate.isInterestingSubtree(child)) if (SwitchAccessPredicate.isInterestingSubtree(child))
interestingBranches += 1; interestingBranches += 1;
if (interestingBranches > 1) if (interestingBranches > 1)
return true; return true;
...@@ -64,7 +120,7 @@ AutomationPredicate.isGroup = function(node, scope) { ...@@ -64,7 +120,7 @@ AutomationPredicate.isGroup = function(node, scope) {
* @param {!chrome.automation.AutomationNode} node2 * @param {!chrome.automation.AutomationNode} node2
* @return {boolean} * @return {boolean}
*/ */
AutomationPredicate.hasSameLocation_ = function(node1, node2) { SwitchAccessPredicate.hasSameLocation_ = function(node1, node2) {
let l1 = node1.location; let l1 = node1.location;
let l2 = node2.location; let l2 = node2.location;
return l1.left === l2.left && l1.top === l2.top && l1.width === l2.width && return l1.left === l2.left && l1.top === l2.top && l1.width === l2.width &&
...@@ -78,10 +134,10 @@ AutomationPredicate.hasSameLocation_ = function(node1, node2) { ...@@ -78,10 +134,10 @@ AutomationPredicate.hasSameLocation_ = function(node1, node2) {
* @param {!chrome.automation.AutomationNode} node * @param {!chrome.automation.AutomationNode} node
* @return {boolean} * @return {boolean}
*/ */
AutomationPredicate.isInterestingSubtree = function(node) { SwitchAccessPredicate.isInterestingSubtree = function(node) {
let children = node.children || []; let children = node.children || [];
return AutomationPredicate.isActionable(node) || return SwitchAccessPredicate.isActionable(node) ||
children.some(AutomationPredicate.isInterestingSubtree); children.some(SwitchAccessPredicate.isInterestingSubtree);
}; };
/** /**
...@@ -91,7 +147,7 @@ AutomationPredicate.isInterestingSubtree = function(node) { ...@@ -91,7 +147,7 @@ AutomationPredicate.isInterestingSubtree = function(node) {
* @param {!chrome.automation.AutomationNode} node * @param {!chrome.automation.AutomationNode} node
* @return {boolean} * @return {boolean}
*/ */
AutomationPredicate.isActionable = function(node) { SwitchAccessPredicate.isActionable = function(node) {
let loc = node.location; let loc = node.location;
let parent = node.parent; let parent = node.parent;
let root = node.root; let root = node.root;
......
// Copyright 2017 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.
/**
* Class to move to the appropriate node in the accessibility tree. Stays in a
* subtree determined by restrictions passed to it.
*
* @constructor
* @param {!chrome.automation.AutomationNode} start
* @param {!chrome.automation.AutomationNode} scope
* @param {!AutomationTreeWalker.Restriction} restrictions
*/
function AutomationTreeWalker(start, scope, restrictions) {
/**
* Currently highlighted node.
*
* @private {!chrome.automation.AutomationNode}
*/
this.node_ = start;
/**
* The root of the subtree that the user is navigating through.
*
* @private {!chrome.automation.AutomationNode}
*/
this.scope_ = scope;
/**
* Function that returns true for a node that is a leaf of the current
* subtree.
*
* @private {!AutomationTreeWalker.Unary}
*/
this.leafPred_ = restrictions.leaf;
/**
* Function that returns true for a node in the current subtree that should
* be visited.
*
* @private {!AutomationTreeWalker.Unary}
*/
this.visitPred_ = restrictions.visit;
}
/**
* @typedef {{leaf: AutomationTreeWalker.Unary,
* visit: AutomationTreeWalker.Unary}}
*/
AutomationTreeWalker.Restriction;
/**
* @typedef {function(!chrome.automation.AutomationNode) : boolean}
*/
AutomationTreeWalker.Unary;
AutomationTreeWalker.prototype = {
/**
* Set this.node_ to the next/previous interesting node within the current
* scope and return it. If no interesting node is found, return the
* first/last interesting node. If |doNext| is true, will search for next
* node. Otherwise, will search for previous node.
*
* @param {boolean} doNext
* @return {chrome.automation.AutomationNode}
*/
moveToNode: function(doNext) {
let node = this.node_;
do {
node = doNext ? this.getNextNode_(node) : this.getPreviousNode_(node);
} while (node && !this.visitPred_(node));
if (node) {
this.node_ = node;
return node;
}
console.log('Restarting search for node at ' + (doNext ? 'first' : 'last'));
node = doNext ? this.scope_ : this.getYoungestDescendant_(this.scope_);
while (node && !this.visitPred_(node))
node = doNext ? this.getNextNode_(node) : this.getPreviousNode_(node);
if (node) {
this.node_ = node;
return node;
}
console.log('Found no interesting nodes to visit.');
return null;
},
/**
* Given a flat list of nodes in pre-order, get the node that comes after
* |node| within the current scope.
*
* @param {!chrome.automation.AutomationNode} node
* @return {!chrome.automation.AutomationNode|undefined}
* @private
*/
getNextNode_: function(node) {
// Check for child.
let child = node.firstChild;
if (child && !this.leafPred_(node))
return child;
// Has no children, and if node is root of subtree, don't check siblings
// or parent.
if (node === this.scope_)
return undefined;
// No child. Check for right-sibling.
let sibling = node.nextSibling;
if (sibling)
return sibling;
// No right-sibling. Get right-sibling of closest ancestor.
let ancestor = node.parent;
while (ancestor && ancestor !== this.scope_) {
let aunt = ancestor.nextSibling;
if (aunt)
return aunt;
ancestor = ancestor.parent;
}
// No node found after |node|, so return undefined.
return undefined;
},
/**
* Given a flat list of nodes in pre-order, get the node that comes before
* |node| within the current scope.
*
* @param {!chrome.automation.AutomationNode} node
* @return {!chrome.automation.AutomationNode|undefined}
* @private
*/
getPreviousNode_: function(node) {
// If node is root of subtree, there is no previous node.
if (node === this.scope_)
return undefined;
// Check for left-sibling. If a left-sibling exists, return its youngest
// descendant if it has one, or otherwise return the sibling.
let sibling = node.previousSibling;
if (sibling)
return this.getYoungestDescendant_(sibling) || sibling;
// No left-sibling. Return parent if it exists; otherwise return undefined.
let parent = node.parent;
if (parent)
return parent;
return undefined;
},
/**
* Get the youngest descendant of |node|, if it has one, within the current
* scope.
*
* @param {!chrome.automation.AutomationNode} node
* @return {!chrome.automation.AutomationNode|undefined}
* @private
*/
getYoungestDescendant_: function(node) {
if (!node.lastChild || this.leafPred_(node))
return undefined;
while (node.lastChild && !this.leafPred_(node))
node = node.lastChild;
return node;
},
/**
* Return the next sibling of |node| if it has one.
*
* @param {chrome.automation.AutomationNode} node
* @return {chrome.automation.AutomationNode}
*/
debugMoveToNext: function(node) {
if (!node)
return null;
let next = node.nextSibling;
if (next) {
return next;
} else {
console.log('Node is last of siblings');
console.log('\n');
return null;
}
},
/**
* Return the previous sibling of |node| if it has one.
*
* @param {chrome.automation.AutomationNode} node
* @return {chrome.automation.AutomationNode}
*/
debugMoveToPrevious: function(node) {
if (!node)
return null;
let prev = node.previousSibling;
if (prev) {
return prev;
} else {
console.log('Node is first of siblings');
console.log('\n');
return null;
}
},
/**
* Return the first child of |node| if it has one.
*
* @param {chrome.automation.AutomationNode} node
* @return {chrome.automation.AutomationNode}
*/
debugMoveToFirstChild: function(node) {
if (!node)
return null;
let child = node.firstChild;
if (child) {
return child;
} else {
console.log('Node has no children');
console.log('\n');
return null;
}
},
/**
* Return the parent of |node| if it has one.
*
* @param {chrome.automation.AutomationNode} node
* @return {chrome.automation.AutomationNode}
*/
debugMoveToParent: function(node) {
if (!node)
return null;
let parent = node.parent;
if (parent) {
return parent;
} else {
console.log('Node has no parent');
console.log('\n');
return null;
}
}
};
// Copyright 2017 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.
/**
* Test fixture for tree_walker.js.
* @constructor
* @extends {testing.Test}
*/
function AutomationTreeWalkerUnitTest() {
testing.Test.call(this);
};
AutomationTreeWalkerUnitTest.prototype = {
__proto__: testing.Test.prototype,
/** @override */
extraLibraries: [
'test_support.js',
'tree_walker.js',
],
/** @override */
browsePreload: DUMMY_URL,
getSampleTree: function() {
let loc = {left: 0, top: 0, width: 0, height: 0};
// root
// upper1
// middle1
// lower1
// leaf1
// leaf2
// leaf3
// lower2
// leaf4
// leaf5
// middle2
// lower3
// leaf6
// leaf7
// upper2
// leaf8
let root = {location: loc, state: {}};
let upper1 = {location: loc, state: {}};
let upper2 = {location: loc, state: {}};
let middle1 = {location: loc, state: {}};
let middle2 = {location: loc, state: {}};
let lower1 = {location: loc, state: {}};
let lower2 = {location: loc, state: {}};
let lower3 = {location: loc, state: {}};
let leaf1 = {location: loc, state: {}};
let leaf2 = {location: loc, state: {}};
let leaf3 = {location: loc, state: {}};
let leaf4 = {location: loc, state: {}};
let leaf5 = {location: loc, state: {}};
let leaf6 = {location: loc, state: {}};
let leaf7 = {location: loc, state: {}};
let leaf8 = {location: loc, state: {}};
let ts = new TestSupport();
ts.setChildren(root, [upper1, upper2]);
ts.setChildren(upper1, [middle1, middle2]);
ts.setChildren(upper2, [leaf8]);
ts.setChildren(middle1, [lower1, lower2]);
ts.setChildren(middle2, [lower3]);
ts.setChildren(lower1, [leaf1, leaf2, leaf3]);
ts.setChildren(lower2, [leaf4, leaf5]);
ts.setChildren(lower3, [leaf6, leaf7]);
return {
root: root,
upper1: upper1,
upper2: upper2,
middle1: middle1,
middle2: middle2,
lower1: lower1,
lower2: lower2,
lower3: lower3,
leaf1: leaf1,
leaf2: leaf2,
leaf3: leaf3,
leaf4: leaf4,
leaf5: leaf5,
leaf6: leaf6,
leaf7: leaf7,
leaf8: leaf8
};
},
getDefaultRestrictions: function() {
return {
leaf: function(node) {
return false;
},
visit: function(node) {
return node.state.focusable === true;
},
}
},
getSubtreeRestrictions: function() {
return {
leaf: function(node) {
return node.state.leaf === true;
},
visit: function(node) {
return node.state.focusable === true;
},
}
}
};
TEST_F('AutomationTreeWalkerUnitTest', 'MoveToNodeWholeTree', function() {
let t = this.getSampleTree();
let treeWalker = new AutomationTreeWalker(
t.root, t.root, this.getDefaultRestrictions());
t.root.state['focusable'] = true;
t.middle1.state['focusable'] = true;
t.leaf1.state['focusable'] = true;
t.leaf2.state['focusable'] = true;
t.leaf5.state['focusable'] = true;
t.lower3.state['focusable'] = true;
t.leaf6.state['focusable'] = true;
t.upper2.state['focusable'] = true;
t.leaf8.state['focusable'] = true;
// Move to next node.
assertEquals(t.middle1, treeWalker.moveToNode(true));
assertEquals(t.leaf1, treeWalker.moveToNode(true));
assertEquals(t.leaf2, treeWalker.moveToNode(true));
assertEquals(t.leaf5, treeWalker.moveToNode(true));
assertEquals(t.lower3, treeWalker.moveToNode(true));
assertEquals(t.leaf6, treeWalker.moveToNode(true));
assertEquals(t.upper2, treeWalker.moveToNode(true));
assertEquals(t.leaf8, treeWalker.moveToNode(true));
assertEquals(t.root, treeWalker.moveToNode(true));
assertEquals(t.middle1, treeWalker.moveToNode(true));
// Move to previous node.
assertEquals(t.root, treeWalker.moveToNode(false));
assertEquals(t.leaf8, treeWalker.moveToNode(false));
assertEquals(t.upper2, treeWalker.moveToNode(false));
assertEquals(t.leaf6, treeWalker.moveToNode(false));
assertEquals(t.lower3, treeWalker.moveToNode(false));
assertEquals(t.leaf5, treeWalker.moveToNode(false));
assertEquals(t.leaf2, treeWalker.moveToNode(false));
assertEquals(t.leaf1, treeWalker.moveToNode(false));
assertEquals(t.middle1, treeWalker.moveToNode(false));
});
TEST_F('AutomationTreeWalkerUnitTest', 'MoveToNodeInSubtree', function() {
let t = this.getSampleTree();
let treeWalker = new AutomationTreeWalker(
t.upper1, t.upper1, this.getSubtreeRestrictions());
t.lower2.state['leaf'] = true;
t.lower3.state['leaf'] = true;
t.root.state['focusable'] = true;
t.middle1.state['focusable'] = true;
t.leaf1.state['focusable'] = true;
t.leaf2.state['focusable'] = true;
t.leaf5.state['focusable'] = true;
t.lower3.state['focusable'] = true;
t.leaf6.state['focusable'] = true;
t.upper2.state['focusable'] = true;
t.leaf8.state['focusable'] = true;
// Move to next node.
assertEquals(t.middle1, treeWalker.moveToNode(true));
assertEquals(t.leaf1, treeWalker.moveToNode(true));
assertEquals(t.leaf2, treeWalker.moveToNode(true));
assertEquals(t.lower3, treeWalker.moveToNode(true));
assertEquals(t.middle1, treeWalker.moveToNode(true));
// Move to previous node.
assertEquals(t.lower3, treeWalker.moveToNode(false));
assertEquals(t.leaf2, treeWalker.moveToNode(false));
assertEquals(t.leaf1, treeWalker.moveToNode(false));
assertEquals(t.middle1, treeWalker.moveToNode(false));
assertEquals(t.lower3, treeWalker.moveToNode(false));
});
TEST_F('AutomationTreeWalkerUnitTest', 'GetNextNode', function() {
let t = this.getSampleTree();
let treeWalker = new AutomationTreeWalker(
t.middle1, t.middle1, this.getDefaultRestrictions());
let order =
[t.middle1, t.lower1, t.leaf1, t.leaf2, t.leaf3,
t.lower2, t.leaf4, t.leaf5];
let node = t.middle1;
for (let i = 0; i < order.length; i++) {
assertEquals(order[i], node);
node = treeWalker.getNextNode_(node);
}
assertEquals(undefined, node);
});
TEST_F('AutomationTreeWalkerUnitTest', 'GetPreviousNode', function() {
let t = this.getSampleTree();
let treeWalker = new AutomationTreeWalker(
t.leaf5, t.middle1, this.getDefaultRestrictions());
let order =
[t.leaf5, t.leaf4, t.lower2, t.leaf3, t.leaf2,
t.leaf1, t.lower1, t.middle1];
let node = t.leaf5;
for (let i = 0; i < order.length; i++) {
assertEquals(order[i], node);
node = treeWalker.getPreviousNode_(node);
}
assertEquals(undefined, node);
});
TEST_F('AutomationTreeWalkerUnitTest', 'GetYoungestDescendant', function() {
let t = this.getSampleTree();
let treeWalker = new AutomationTreeWalker(
t.root, t.root, this.getDefaultRestrictions());
assertEquals(t.leaf8, treeWalker.getYoungestDescendant_(t.root));
assertEquals(t.leaf7, treeWalker.getYoungestDescendant_(t.upper1));
assertEquals(t.leaf8, treeWalker.getYoungestDescendant_(t.upper2));
assertEquals(t.leaf5, treeWalker.getYoungestDescendant_(t.middle1));
assertEquals(t.leaf7, treeWalker.getYoungestDescendant_(t.middle2));
assertEquals(t.leaf3, treeWalker.getYoungestDescendant_(t.lower1));
assertEquals(t.leaf5, treeWalker.getYoungestDescendant_(t.lower2));
assertEquals(t.leaf7, treeWalker.getYoungestDescendant_(t.lower3));
assertEquals(undefined, treeWalker.getYoungestDescendant_(t.leaf1));
assertEquals(undefined, treeWalker.getYoungestDescendant_(t.leaf2));
assertEquals(undefined, treeWalker.getYoungestDescendant_(t.leaf3));
assertEquals(undefined, treeWalker.getYoungestDescendant_(t.leaf4));
assertEquals(undefined, treeWalker.getYoungestDescendant_(t.leaf5));
assertEquals(undefined, treeWalker.getYoungestDescendant_(t.leaf6));
assertEquals(undefined, treeWalker.getYoungestDescendant_(t.leaf7));
assertEquals(undefined, treeWalker.getYoungestDescendant_(t.leaf8));
});
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