Chromium Code Reviews| Index: chrome/browser/resources/chromeos/switch_access/switch_access.js |
| diff --git a/chrome/browser/resources/chromeos/switch_access/switch_access.js b/chrome/browser/resources/chromeos/switch_access/switch_access.js |
| index 5b35495f867bbb23f49876b9496628f1582a6585..1a9d91b2de9caae091f07c689aabad743b8ac98a 100644 |
| --- a/chrome/browser/resources/chromeos/switch_access/switch_access.js |
| +++ b/chrome/browser/resources/chromeos/switch_access/switch_access.js |
| @@ -2,24 +2,30 @@ |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| -var AutomationNode = chrome.automation.AutomationNode; |
| +let AutomationNode = chrome.automation.AutomationNode; |
| +let debuggingEnabled = true; |
| /** |
| * @constructor |
| */ |
| -var SwitchAccess = function() { |
| +let SwitchAccess = function() { |
| console.log("Switch access is enabled"); |
| // Currently selected node. |
| /** @private {AutomationNode} */ |
| this.node_ = null; |
| + // Root node (i.e., the desktop). |
| + /** @private {AutomationNode} */ |
| + this.root_ = null; |
| + |
| // List of nodes to push to / pop from in case this.node_ is lost. |
| /** @private {!Array<!AutomationNode>} */ |
| this.ancestorList_ = []; |
| chrome.automation.getDesktop(function(desktop) { |
| this.node_ = desktop; |
| + this.root_ = desktop; |
| console.log("AutomationNode for desktop is loaded"); |
| this.printDetails_(); |
| @@ -31,26 +37,274 @@ var SwitchAccess = function() { |
| console.log("2 = go to next element"); |
| this.moveToNext_(); |
| } else if (event.key === "3") { |
| - console.log("3 = go to child element"); |
| - this.moveToFirstChild_(); |
| - } else if (event.key === "4") { |
| - console.log("4 = go to parent element"); |
| - this.moveToParent_(); |
| - } else if (event.key === "5") { |
| - console.log("5 is not yet implemented"); |
| - console.log("\n"); |
| + console.log("3 = do default on element"); |
| + this.doDefault_(); |
| + } else if (debuggingEnabled) { |
| + if (event.key === "6") { |
| + console.log("6 = go to previous element (debug mode)"); |
| + this.debugMoveToPrevious_(); |
| + } else if (event.key === "7") { |
| + console.log("7 = go to next element (debug mode)"); |
| + this.debugMoveToNext_(); |
| + } else if (event.key === "8") { |
| + console.log("8 = go to child element (debug mode)"); |
| + this.debugMoveToFirstChild_(); |
| + } else if (event.key === "9") { |
| + console.log("9 = go to parent element (debug mode)"); |
| + this.debugMoveToParent_(); |
| + } |
| + } |
| + if (this.node_) { |
| + chrome.accessibilityPrivate.setFocusRing([this.node_.location]); |
| } |
| - chrome.accessibilityPrivate.setFocusRing([this.node_.location]); |
| }.bind(this)); |
| }.bind(this)); |
| }; |
| SwitchAccess.prototype = { |
| /** |
| - * Move to the previous sibling of this.node_ if it has one. |
| + * Set this.node_ to the previous interesting node. If no interesting node |
| + * comes before this.node_, set this.node_ to the last interesting node. |
| + * |
| + * @private |
| */ |
| moveToPrevious_: function() { |
| - var previous = this.node_.previousSibling; |
| + let prev = null; |
| + if (this.node_) { |
| + prev = this.getPreviousNode_(this.node_); |
| + } |
| + if (prev) { |
| + this.node_ = prev; |
| + } else if (this.root_) { |
| + console.log("Reached the first interesting node. Restarting with last."); |
| + this.node_ = this.getInterestingDescendant_(this.root_, false); |
| + } else { |
| + console.log("Found no interesting nodes to visit"); |
| + } |
| + this.printNode_(this.node_); |
| + console.log("\n"); |
| + }, |
| + |
| + /** |
| + * Set this.node_ to the next interesting node. If no interesting node comes |
| + * after this.node_, set this.node_ to the first interesting node. |
| + * |
| + * @private |
| + */ |
| + moveToNext_: function() { |
| + let next = null; |
| + if (this.node_) { |
| + next = this.getNextNode_(this.node_); |
| + } |
| + if (next) { |
| + this.node_ = next; |
| + } else if (this.root_) { |
| + console.log("Reached the last interesting node. Restarting with first."); |
| + this.node_ = this.getInterestingDescendant_(this.root_, true); |
| + } else { |
| + console.log("Found no interesting nodes to visit"); |
| + } |
| + this.printNode_(this.node_); |
| + console.log("\n"); |
| + }, |
| + |
| + /** |
| + * Given a flat list of nodes in pre-order that are considered interesting, |
| + * get the node that comes after |node|. |
| + * |
| + * @param {!AutomationNode} node |
| + * @return {AutomationNode} |
| + * @private |
| + */ |
| + getNextNode_: function(node) { |
| + // Check descendants. |
| + let descendant = this.getInterestingDescendant_(node, true); |
| + if (descendant) { |
| + return descendant; |
| + } |
| + |
| + // No interesting descendant. Check right-siblings and their descendants. |
| + let siblingOrDescendant = |
| + this.getInterestingSiblingOrDescendant_(node, false); |
| + if (siblingOrDescendant) { |
| + return siblingOrDescendant; |
| + } |
| + |
| + // No interesting right-sibling or descendant of it. Check ancestors and |
| + // other relatives that come after |node|. |
| + let ancestorOrRelative = |
| + this.getInterestingAncestorOrRelative_(node, false); |
| + if (ancestorOrRelative) { |
| + return ancestorOrRelative; |
| + } |
| + |
| + // No interesting node found after |node|, so return null. |
| + return null; |
| + }, |
| + |
| + /** |
| + * Given a flat list of nodes in pre-order that are considered interesting, |
| + * get the node that comes before |node|. |
| + * |
| + * @param {!AutomationNode} node |
| + * @return {AutomationNode} |
| + * @private |
| + */ |
| + getPreviousNode_: function(node) { |
| + // Check left-siblings and their descendants. |
| + let siblingOrDescendant = |
| + this.getInterestingSiblingOrDescendant_(node, true); |
| + if (siblingOrDescendant) { |
| + return siblingOrDescendant; |
| + } |
| + |
| + // No interesting left-sibling or descendant of it. Check ancestors and |
| + // other relatives that come before |node|. |
| + let ancestorOrRelative = this.getInterestingAncestorOrRelative_(node, true); |
| + if (ancestorOrRelative) { |
| + return ancestorOrRelative; |
| + } |
| + |
| + // No interesting node found before |node|, so return null. |
| + return null; |
| + }, |
| + |
| + |
| + /** |
| + * Find a descendant of |node| that is considered interesting. |
| + * If |startAtFirstChild| is true, this function will start by checking the |
| + * lowest index child, one level below |node|. |
| + * If |startAtFirstChild| is false, this function will start by checking the |
| + * leaf descendants of the highest index child. |
| + * |
| + * @param {!AutomationNode} node |
| + * @param {boolean} startAtFirstChild |
| + * @return {AutomationNode} |
| + * @private |
| + */ |
| + getInterestingDescendant_: function(node, startAtFirstChild) { |
| + if (startAtFirstChild) { |
| + let child = node.firstChild; |
| + while (child) { |
| + if (this.isInteresting_(child)) { |
|
dmazzoni
2017/03/09 00:00:01
Right now you have calls to isInteresting througho
elichtenberg
2017/03/09 19:31:44
Done.
|
| + return child; |
| + } |
| + let descendant = this.getInterestingDescendant_(child, true); |
| + if (descendant) { |
| + return descendant; |
| + } |
| + child = child.nextSibling; |
| + } |
| + } else { |
| + let child = node.lastChild; |
| + while (child) { |
| + let descendant = this.getInterestingDescendant_(child, false); |
| + if (descendant) { |
| + return descendant; |
| + } |
| + if (this.isInteresting_(child)) { |
| + return child; |
| + } |
| + child = child.previousSibling; |
| + } |
| + } |
| + return null; |
| + }, |
| + |
| + /** |
| + * Find a sibling or descendant of a sibling of |node| that is considered |
| + * interesting. |
| + * If |checkPreviousSibling| is true, this function will only look at |node|'s |
| + * lower index sibilngs, and it will first check the sibling's descendants. |
| + * If |checkPreviousSibling| is false, this function will only look at |
| + * |node|'s higher index siblings, and it will check the sibling before its |
| + * descendants. |
| + * |
| + * @param {!AutomationNode} node |
| + * @param {boolean} checkPreviousSibling |
| + * @return {AutomationNode} |
| + * @private |
| + */ |
| + getInterestingSiblingOrDescendant_: function(node, checkPreviousSibling) { |
| + if (checkPreviousSibling) { |
| + let sibling = node.previousSibling; |
| + while (sibling) { |
| + let descendant = this.getInterestingDescendant_(sibling, false); |
| + if (descendant) { |
| + return descendant; |
| + } |
| + if (this.isInteresting_(sibling)) { |
| + return sibling; |
| + } |
| + sibling = sibling.previousSibling; |
| + } |
| + } else { |
| + let sibling = node.nextSibling; |
| + while (sibling) { |
| + if (this.isInteresting_(sibling)) { |
| + return sibling; |
| + } |
| + let descendant = this.getInterestingDescendant_(sibling, true); |
| + if (descendant) { |
| + return descendant; |
| + } |
| + sibling = sibling.nextSibling; |
| + } |
| + } |
| + return null; |
| + }, |
| + |
| + /** |
| + * Find an ancestor or other relative of |node| that is considered |
| + * interesting. |
| + * If |findPrevious| is true, will check nodes that come before |node| in |
| + * pre-order, starting with |node|'s parent. |
| + * If |findPrevious| is false, will check nodes that come after |node| in |
| + * pre-order, not including any descendants or direct ancestors. |
| + * |
| + * @param {!AutomationNode} node |
| + * @param {boolean} findPrevious |
| + * @return {AutomationNode} |
| + * @private |
| + */ |
| + getInterestingAncestorOrRelative_: function(node, findPrevious) { |
| + let ancestor = node.parent; |
| + while (ancestor) { |
| + if (findPrevious && this.isInteresting_(ancestor)) { |
| + return ancestor; |
| + } |
| + let relative = |
| + this.getInterestingSiblingOrDescendant_(ancestor, findPrevious); |
| + if (relative) { |
| + return relative; |
| + } |
| + ancestor = ancestor.parent; |
| + } |
| + return null; |
| + }, |
| + |
| + /** |
| + * Returns true if |node| is interesting. |
| + * |
| + * @param {!AutomationNode} node |
| + * @return {boolean} |
| + * @private |
| + */ |
| + isInteresting_: function(node) { |
| + if (node.state && node.state.focusable) { |
| + return true; |
| + } else { |
| + return false; |
| + } |
| + }, |
| + |
| + /** |
| + * Move to the previous sibling of this.node_ if it has one. |
| + * |
| + * @private |
| + */ |
| + debugMoveToPrevious_: function() { |
| + let previous = this.node_.previousSibling; |
| if (previous) { |
| this.node_ = previous; |
| this.printDetails_(); |
| @@ -62,9 +316,11 @@ SwitchAccess.prototype = { |
| /** |
| * Move to the next sibling of this.node_ if it has one. |
| + * |
| + * @private |
| */ |
| - moveToNext_: function() { |
| - var next = this.node_.nextSibling; |
| + debugMoveToNext_: function() { |
| + let next = this.node_.nextSibling; |
| if (next) { |
| this.node_ = next; |
| this.printDetails_(); |
| @@ -76,9 +332,11 @@ SwitchAccess.prototype = { |
| /** |
| * Move to the first child of this.node_ if it has one. |
| + * |
| + * @private |
| */ |
| - moveToFirstChild_: function() { |
| - var child = this.node_.firstChild; |
| + debugMoveToFirstChild_: function() { |
| + let child = this.node_.firstChild; |
| if (child) { |
| this.ancestorList_.push(this.node_); |
| this.node_ = child; |
| @@ -93,9 +351,11 @@ SwitchAccess.prototype = { |
| * Move to the parent of this.node_ if it has one. If it does not have a |
| * parent but it is not the top level root node, then this.node_ lost track of |
| * its neighbors, and we move to an ancestor node. |
| + * |
| + * @private |
| */ |
| - moveToParent_: function() { |
| - var parent = this.node_.parent; |
| + debugMoveToParent_: function() { |
| + let parent = this.node_.parent; |
| if (parent) { |
| this.ancestorList_.pop(); |
| this.node_ = parent; |
| @@ -106,13 +366,31 @@ SwitchAccess.prototype = { |
| } else { |
| console.log( |
| "Node could not find its parent, so moved to recent ancestor"); |
| - var ancestor = this.ancestorList_.pop(); |
| + let ancestor = this.ancestorList_.pop(); |
| this.node_ = ancestor; |
| this.printDetails_(); |
| } |
| }, |
| /** |
| + * Perform the default action on the currently selected node. |
| + * |
| + * @private |
| + */ |
| + doDefault_: function() { |
| + let state = this.node_.state; |
| + if (state && state.focusable) { |
| + console.log("Node was focusable, doing default on it") |
| + } else if (state) { |
| + console.log("Node was not focusable, but still doing default"); |
| + } else { |
| + console.log("Node has no state, still doing default"); |
| + } |
| + console.log("\n"); |
| + this.node_.doDefault(); |
| + }, |
| + |
| + /** |
| * Print out details about the currently selected node and the list of |
| * ancestors. |
| * |
| @@ -137,7 +415,7 @@ SwitchAccess.prototype = { |
| if (!node.parent) { |
| console.log("At index " + node.indexInParent + ", has no parent"); |
| } else { |
| - var numSiblings = node.parent.children.length; |
| + let numSiblings = node.parent.children.length; |
| console.log( |
| "At index " + node.indexInParent + ", there are " |
| + numSiblings + " siblings"); |
| @@ -150,4 +428,4 @@ SwitchAccess.prototype = { |
| } |
| }; |
| -new SwitchAccess(); |
| +window.switchAccess = new SwitchAccess(); |