Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(5927)

Unified Diff: chrome/browser/resources/chromeos/switch_access/automation_manager.js

Issue 2872023005: Correctly follows focus and moves to valid node when current becomes invalid (Closed)
Patch Set: Responded to comments Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | chrome/browser/resources/chromeos/switch_access/compiled_resources2.gyp » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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) {
« no previous file with comments | « no previous file | chrome/browser/resources/chromeos/switch_access/compiled_resources2.gyp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698