| Index: chrome/browser/resources/chromeos/switch_access/automation_manager.js
|
| diff --git a/chrome/browser/resources/chromeos/switch_access/automation_manager.js b/chrome/browser/resources/chromeos/switch_access/automation_manager.js
|
| index b23d3deed4e95f3cbc4c0fe4cd968840b32a0ce5..6c1d39f6cf7ebf68aa019123e2f0a66d846bb221 100644
|
| --- a/chrome/browser/resources/chromeos/switch_access/automation_manager.js
|
| +++ b/chrome/browser/resources/chromeos/switch_access/automation_manager.js
|
| @@ -7,44 +7,45 @@
|
| * to and selecting nodes.
|
| *
|
| * @constructor
|
| + * @param {!chrome.automation.AutomationNode} desktop
|
| */
|
| -function AutomationManager() {
|
| +function AutomationManager(desktop) {
|
| /**
|
| * Currently highlighted node.
|
| *
|
| - * @private {chrome.automation.AutomationNode}
|
| + * @private {!chrome.automation.AutomationNode}
|
| */
|
| - this.node_ = null;
|
| + this.node_ = desktop;
|
|
|
| /**
|
| * The root of the subtree that the user is navigating through.
|
| *
|
| - * @private {chrome.automation.AutomationNode}
|
| + * @private {!chrome.automation.AutomationNode}
|
| */
|
| - this.scope_ = null;
|
| + this.scope_ = desktop;
|
|
|
| /**
|
| * The desktop node.
|
| *
|
| - * @private {chrome.automation.AutomationNode}
|
| + * @private {!chrome.automation.AutomationNode}
|
| */
|
| - this.desktop_ = null;
|
| + this.desktop_ = desktop;
|
|
|
| /**
|
| * A stack of past scopes. Allows user to traverse back to previous groups
|
| * after selecting one or more groups. The most recent group is at the end
|
| * of the array.
|
| *
|
| - * @private {Array<chrome.automation.AutomationNode>}
|
| + * @private {Array<!chrome.automation.AutomationNode>}
|
| */
|
| this.scopeStack_ = [];
|
|
|
| /**
|
| * Moves to the appropriate node in the accessibility tree.
|
| *
|
| - * @private {AutomationTreeWalker}
|
| + * @private {!AutomationTreeWalker}
|
| */
|
| - this.treeWalker_ = null;
|
| + this.treeWalker_ = this.createTreeWalker_(desktop);
|
|
|
| this.init_();
|
| };
|
| @@ -69,14 +70,112 @@ AutomationManager.prototype = {
|
| * @private
|
| */
|
| init_: function() {
|
| - chrome.automation.getDesktop(function(desktop) {
|
| - this.node_ = desktop;
|
| - this.scope_ = desktop;
|
| - this.desktop_ = desktop;
|
| - this.treeWalker_ = this.createTreeWalker_(desktop);
|
| - console.log('AutomationNode for desktop is loaded');
|
| + console.log('AutomationNode for desktop is loaded');
|
| + this.printNode_(this.node_);
|
| +
|
| + this.desktop_.addEventListener(
|
| + chrome.automation.EventType.FOCUS,
|
| + this.handleFocusChange_.bind(this),
|
| + false);
|
| +
|
| + // TODO(elichtenberg): Eventually use a more specific filter than
|
| + // ALL_TREE_CHANGES.
|
| + chrome.automation.addTreeChangeObserver(
|
| + chrome.automation.TreeChangeObserverFilter.ALL_TREE_CHANGES,
|
| + this.handleNodeRemoved_.bind(this));
|
| + },
|
| +
|
| + /**
|
| + * When an interesting element gains focus on the page, move to it. If an
|
| + * element gains focus but is not interesting, move to the next interesting
|
| + * node after it.
|
| + *
|
| + * @param {!chrome.automation.AutomationEvent} event
|
| + * @private
|
| + */
|
| + handleFocusChange_: function(event) {
|
| + if (this.node_ === event.target)
|
| + return;
|
| + console.log('Focus changed');
|
| +
|
| + // Rebuild scope stack and set scope for focused node.
|
| + this.buildScopeStack_(event.target);
|
| +
|
| + // Move to focused node.
|
| + this.node_ = event.target;
|
| + this.treeWalker_ = this.createTreeWalker_(this.scope_, this.node_);
|
| +
|
| + // In case the node that gained focus is not a subtreeLeaf.
|
| + if (AutomationPredicate.isSubtreeLeaf(this.node_, this.scope_)) {
|
| this.printNode_(this.node_);
|
| - }.bind(this));
|
| + this.updateFocusRing_();
|
| + } else
|
| + this.moveToNode(true);
|
| + },
|
| +
|
| + /**
|
| + * Create a new scope stack and set the current scope for |node|.
|
| + *
|
| + * @param {!chrome.automation.AutomationNode} node
|
| + * @private
|
| + */
|
| + buildScopeStack_: function(node) {
|
| + // Create list of |node|'s ancestors, with highest level ancestor at the
|
| + // end.
|
| + let ancestorList = [];
|
| + while (node.parent) {
|
| + ancestorList.push(node.parent);
|
| + node = node.parent;
|
| + }
|
| +
|
| + // Starting with desktop as the scope, if an ancestor is a group, set it to
|
| + // the new scope and push the old scope onto the scope stack.
|
| + this.scopeStack_ = [];
|
| + this.scope_ = this.desktop_;
|
| + while (ancestorList.length > 0) {
|
| + let ancestor = ancestorList.pop();
|
| + if (ancestor.role === chrome.automation.RoleType.DESKTOP)
|
| + continue;
|
| + if (AutomationPredicate.isGroup(ancestor, this.scope_)) {
|
| + this.scopeStack_.push(this.scope_);
|
| + this.scope_ = ancestor;
|
| + }
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * When a node is removed from the page, move to a new valid node.
|
| + *
|
| + * @param {!chrome.automation.TreeChange} treeChange
|
| + * @private
|
| + */
|
| + handleNodeRemoved_: function(treeChange) {
|
| + // TODO(elichtenberg): Only listen to NODE_REMOVED callbacks. Don't need
|
| + // any others.
|
| + if (treeChange.type !== chrome.automation.TreeChangeType.NODE_REMOVED)
|
| + return;
|
| +
|
| + // TODO(elichtenberg): Currently not getting NODE_REMOVED event when whole
|
| + // tree is deleted. Once fixed, can delete this. Should only need to check
|
| + // if target is current node.
|
| + let removedByRWA =
|
| + treeChange.target.role === chrome.automation.RoleType.ROOT_WEB_AREA
|
| + && !this.node_.role;
|
| +
|
| + if (!removedByRWA && treeChange.target !== this.node_)
|
| + return;
|
| +
|
| + console.log('Node removed');
|
| + chrome.accessibilityPrivate.setFocusRing([]);
|
| +
|
| + // Current node not invalid until after treeChange callback, so move to
|
| + // valid node after callback. Delay added to prevent moving to another
|
| + // node about to be made invalid. If already at a valid node (e.g., user
|
| + // moves to it or focus changes to it), won't need to move to a new node.
|
| + window.setTimeout(function() {
|
| + if (!this.node_.role)
|
| + this.moveToNode(true);
|
| + }.bind(this), 100);
|
| },
|
|
|
| /**
|
| @@ -88,22 +187,14 @@ AutomationManager.prototype = {
|
| * @param {boolean} doNext
|
| */
|
| moveToNode: function(doNext) {
|
| - if (!this.treeWalker_)
|
| - return;
|
| + // If node is invalid, set node to last valid scope.
|
| + this.startAtValidNode_();
|
|
|
| let node = this.treeWalker_.moveToNode(doNext);
|
| if (node) {
|
| this.node_ = node;
|
| this.printNode_(this.node_);
|
| -
|
| - let color;
|
| - if (this.node_ === this.scope_)
|
| - color = AutomationManager.Color.SCOPE;
|
| - else if (AutomationPredicate.isInteresting(this.node_))
|
| - color = AutomationManager.Color.LEAF;
|
| - else
|
| - color = AutomationManager.Color.GROUP;
|
| - chrome.accessibilityPrivate.setFocusRing([this.node_.location], color);
|
| + this.updateFocusRing_();
|
| }
|
| },
|
|
|
| @@ -116,7 +207,7 @@ AutomationManager.prototype = {
|
| * it.
|
| */
|
| selectCurrentNode: function() {
|
| - if (!this.node_ || !this.scope_ || !this.treeWalker_)
|
| + if (!this.node_.role)
|
| return;
|
|
|
| if (this.node_ === this.scope_) {
|
| @@ -124,19 +215,16 @@ AutomationManager.prototype = {
|
| if (this.scopeStack_.length === 0)
|
| return;
|
|
|
| - // Find a previous scope that is still valid.
|
| - let oldScope;
|
| + // Find a previous scope that is still valid. The stack here always has
|
| + // at least one valid scope (i.e., the desktop node).
|
| do {
|
| - oldScope = this.scopeStack_.pop();
|
| - } while (oldScope && !oldScope.role);
|
| -
|
| - // oldScope will always be valid here, so this will always be true.
|
| - if (oldScope) {
|
| - this.scope_ = oldScope;
|
| - this.treeWalker_ = this.createTreeWalker_(this.scope_, this.node_);
|
| - chrome.accessibilityPrivate.setFocusRing(
|
| - [this.node_.location], AutomationManager.Color.GROUP);
|
| - }
|
| + this.scope_ = this.scopeStack_.pop();
|
| + } while (!this.scope_.role && this.scopeStack_.length > 0);
|
| +
|
| + this.treeWalker_ = this.createTreeWalker_(this.scope_, this.node_);
|
| + this.updateFocusRing_();
|
| + console.log('Moved to previous scope');
|
| + this.printNode_(this.node_);
|
| return;
|
| }
|
|
|
| @@ -144,11 +232,57 @@ AutomationManager.prototype = {
|
| this.scopeStack_.push(this.scope_);
|
| this.scope_ = this.node_;
|
| this.treeWalker_ = this.createTreeWalker_(this.scope_);
|
| + console.log('Entered scope');
|
| this.moveToNode(true);
|
| return;
|
| }
|
|
|
| this.node_.doDefault();
|
| + console.log('Performed default action');
|
| + console.log('\n');
|
| + },
|
| +
|
| + /**
|
| + * Set the focus ring for the current node and determine the color for it.
|
| + *
|
| + * @private
|
| + */
|
| + updateFocusRing_: function() {
|
| + let color;
|
| + if (this.node_ === this.scope_)
|
| + color = AutomationManager.Color.SCOPE;
|
| + else if (AutomationPredicate.isGroup(this.node_, this.scope_))
|
| + color = AutomationManager.Color.GROUP;
|
| + else
|
| + color = AutomationManager.Color.LEAF;
|
| + chrome.accessibilityPrivate.setFocusRing([this.node_.location], color);
|
| + },
|
| +
|
| + /**
|
| + * If this.node_ is invalid, set this.node_ to a valid scope. Will check the
|
| + * current scope and past scopes until a valid scope is found. this.node_
|
| + * is set to that valid scope.
|
| + *
|
| + * @private
|
| + */
|
| + startAtValidNode_: function() {
|
| + if (this.node_.role)
|
| + return;
|
| + console.log('Finding new valid node');
|
| +
|
| + // Current node is invalid, but current scope is still valid, so set node
|
| + // to the current scope.
|
| + if (this.scope_.role)
|
| + this.node_ = this.scope_;
|
| +
|
| + // Current node and current scope are invalid, so set both to a valid scope
|
| + // from the scope stack. The stack here always has at least one valid scope
|
| + // (i.e., the desktop node).
|
| + while (!this.node_.role && this.scopeStack_.length > 0) {
|
| + this.node_ = this.scopeStack_.pop();
|
| + this.scope_ = this.node_;
|
| + }
|
| + this.treeWalker_ = this.createTreeWalker_(this.scope_);
|
| },
|
|
|
| /**
|
| @@ -158,6 +292,7 @@ AutomationManager.prototype = {
|
| *
|
| * @param {!chrome.automation.AutomationNode} scope
|
| * @param {!chrome.automation.AutomationNode=} opt_start
|
| + * @private
|
| * @return {!AutomationTreeWalker}
|
| */
|
| createTreeWalker_: function(scope, opt_start) {
|
|
|