Chromium Code Reviews| 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..94854d5476da9d1a8f529d8d4ec6340b28e25a3c 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,89 @@ 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'); |
| + |
| + // Create list of ancestors of node that encountered focus change. |
| + let node = event.target; |
| + let ancestorList = []; |
| + while (node.parent) { |
| + ancestorList.push(node.parent); |
| + node = node.parent; |
| + } |
| + |
| + // Rebuild scope stack. |
|
dmazzoni
2017/05/18 16:25:52
Might be simpler to just do this in one loop rathe
elichtenberg
2017/05/18 18:31:01
Need separate loops because need the node's scope
|
| + this.scopeStack_ = []; |
| + this.scope_ = this.desktop_; |
| + while (ancestorList.length > 0) { |
| + let newScope = ancestorList.pop(); |
| + if (newScope.role === chrome.automation.RoleType.DESKTOP) |
| + continue; |
| + if (AutomationPredicate.isGroup(newScope, this.scope_)) { |
| + this.scopeStack_.push(this.scope_); |
| + this.scope_ = newScope; |
| + } |
| + } |
| + |
| + // 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.setFocusRing_(); |
| + } else |
| + this.moveToNode(true); |
| + }, |
| + |
| + /** |
| + * When a node is removed from the page, move to a new valid node. |
| + * |
| + * @param {!chrome.automation.TreeChange} treeChange |
| + * @private |
| + */ |
| + handleNodeRemoved_: function(treeChange) { |
| + if (treeChange.type === chrome.automation.TreeChangeType.NODE_REMOVED |
|
dmazzoni
2017/05/18 16:25:52
Please split this up into a few separate checks, a
elichtenberg
2017/05/18 18:31:01
Split it up and added some TODOs. Do you think thi
|
| + && ((treeChange.target.role === 'rootWebArea' && !this.node_.role) |
|
dmazzoni
2017/05/18 16:25:52
Use RoleType.ROOT_WEB_AREA here and elsewhere
elichtenberg
2017/05/18 18:31:01
Done.
|
| + || treeChange.target === this.node_)) { |
| + 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 +164,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.setFocusRing_(); |
| } |
| }, |
| @@ -116,7 +184,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 +192,17 @@ 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_); |
| + chrome.accessibilityPrivate.setFocusRing( |
|
dmazzoni
2017/05/18 16:25:52
Can you call this.setFocusRing_() here?
elichtenberg
2017/05/18 18:31:01
Yep. Just changed it.
|
| + [this.node_.location], AutomationManager.Color.GROUP); |
| + console.log('Moved to previous scope'); |
| + this.printNode_(this.node_); |
| return; |
| } |
| @@ -144,11 +210,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 |
| + */ |
| + setFocusRing_: function() { |
|
dmazzoni
2017/05/18 16:25:52
How about updateFocusRing(), since it sets the foc
elichtenberg
2017/05/18 18:31:01
Done.
|
| + 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 +270,7 @@ AutomationManager.prototype = { |
| * |
| * @param {!chrome.automation.AutomationNode} scope |
| * @param {!chrome.automation.AutomationNode=} opt_start |
| + * @private |
| * @return {!AutomationTreeWalker} |
| */ |
| createTreeWalker_: function(scope, opt_start) { |