Commit 0b414646 authored by dtseng's avatar dtseng Committed by Commit bot

Initial support for Ranges used to track ChromeVox focus.

Review URL: https://codereview.chromium.org/637223006

Cr-Commit-Position: refs/heads/master@{#301137}
parent b5a33976
...@@ -1239,6 +1239,12 @@ chrome.automation.AutomationNode.prototype.role; ...@@ -1239,6 +1239,12 @@ chrome.automation.AutomationNode.prototype.role;
chrome.automation.AutomationNode.prototype.attributes; chrome.automation.AutomationNode.prototype.attributes;
/**
* @type {!chrome.automation.AutomationNode}
*/
chrome.automation.AutomationNode.prototype.root;
/** /**
* @return {chrome.automation.AutomationNode} * @return {chrome.automation.AutomationNode}
*/ */
...@@ -1299,6 +1305,9 @@ chrome.automation.AutomationNode.prototype.removeEventListener = ...@@ -1299,6 +1305,9 @@ chrome.automation.AutomationNode.prototype.removeEventListener =
function(eventType, callback, capture) {}; function(eventType, callback, capture) {};
chrome.automation.AutomationNode.prototype.focus = function() {};
/** /**
* @param {function(chrome.automation.AutomationNode)} callback * @param {function(chrome.automation.AutomationNode)} callback
*/ */
......
...@@ -30,15 +30,16 @@ AutomationUtil.Dir = { ...@@ -30,15 +30,16 @@ AutomationUtil.Dir = {
goog.scope(function() { goog.scope(function() {
var AutomationNode = chrome.automation.AutomationNode;
var Dir = AutomationUtil.Dir; var Dir = AutomationUtil.Dir;
/** /**
* Find a node in subtree of |cur| satisfying |pred| using pre-order traversal. * Find a node in subtree of |cur| satisfying |pred| using pre-order traversal.
* @param {chrome.automation.AutomationNode} cur Node to begin the search from. * @param {AutomationNode} cur Node to begin the search from.
* @param {Dir} dir * @param {Dir} dir
* @param {AutomationPredicate.Unary} pred A predicate to apply * @param {AutomationPredicate.Unary} pred A predicate to apply
* to a candidate node. * to a candidate node.
* @return {chrome.automation.AutomationNode} * @return {AutomationNode}
*/ */
AutomationUtil.findNodePre = function(cur, dir, pred) { AutomationUtil.findNodePre = function(cur, dir, pred) {
if (pred(cur)) if (pred(cur))
...@@ -56,11 +57,11 @@ AutomationUtil.findNodePre = function(cur, dir, pred) { ...@@ -56,11 +57,11 @@ AutomationUtil.findNodePre = function(cur, dir, pred) {
/** /**
* Find a node in subtree of |cur| satisfying |pred| using post-order traversal. * Find a node in subtree of |cur| satisfying |pred| using post-order traversal.
* @param {chrome.automation.AutomationNode} cur Node to begin the search from. * @param {AutomationNode} cur Node to begin the search from.
* @param {Dir} dir * @param {Dir} dir
* @param {AutomationPredicate.Unary} pred A predicate to apply * @param {AutomationPredicate.Unary} pred A predicate to apply
* to a candidate node. * to a candidate node.
* @return {chrome.automation.AutomationNode} * @return {AutomationNode}
*/ */
AutomationUtil.findNodePost = function(cur, dir, pred) { AutomationUtil.findNodePost = function(cur, dir, pred) {
var child = dir == Dir.BACKWARD ? cur.lastChild() : cur.firstChild(); var child = dir == Dir.BACKWARD ? cur.lastChild() : cur.firstChild();
...@@ -79,9 +80,9 @@ AutomationUtil.findNodePost = function(cur, dir, pred) { ...@@ -79,9 +80,9 @@ AutomationUtil.findNodePost = function(cur, dir, pred) {
/** /**
* Find the next node in the given direction that is either an immediate sibling * Find the next node in the given direction that is either an immediate sibling
* or a sibling of an ancestor. * or a sibling of an ancestor.
* @param {chrome.automation.AutomationNode} cur Node to start search from. * @param {AutomationNode} cur Node to start search from.
* @param {Dir} dir * @param {Dir} dir
* @return {chrome.automation.AutomationNode} * @return {AutomationNode}
*/ */
AutomationUtil.findNextSubtree = function(cur, dir) { AutomationUtil.findNextSubtree = function(cur, dir) {
while (cur) { while (cur) {
...@@ -95,11 +96,11 @@ AutomationUtil.findNextSubtree = function(cur, dir) { ...@@ -95,11 +96,11 @@ AutomationUtil.findNextSubtree = function(cur, dir) {
/** /**
* Find the next node in the given direction in depth first order. * Find the next node in the given direction in depth first order.
* @param {chrome.automation.AutomationNode} cur Node to begin the search from. * @param {AutomationNode} cur Node to begin the search from.
* @param {Dir} dir * @param {Dir} dir
* @param {AutomationPredicate.Unary} pred A predicate to apply * @param {AutomationPredicate.Unary} pred A predicate to apply
* to a candidate node. * to a candidate node.
* @return {chrome.automation.AutomationNode} * @return {AutomationNode}
*/ */
AutomationUtil.findNextNode = function(cur, dir, pred) { AutomationUtil.findNextNode = function(cur, dir, pred) {
var next = cur; var next = cur;
...@@ -116,7 +117,7 @@ AutomationUtil.findNextNode = function(cur, dir, pred) { ...@@ -116,7 +117,7 @@ AutomationUtil.findNextNode = function(cur, dir, pred) {
* Given nodes a_1, ..., a_n starting at |cur| in pre order traversal, apply * Given nodes a_1, ..., a_n starting at |cur| in pre order traversal, apply
* |pred| to a_i and a_(i - 1) until |pred| is satisfied. Returns a_(i - 1) or * |pred| to a_i and a_(i - 1) until |pred| is satisfied. Returns a_(i - 1) or
* a_i (depending on opt_options.before) or null if no match was found. * a_i (depending on opt_options.before) or null if no match was found.
* @param {chrome.automation.AutomationNode} cur * @param {AutomationNode} cur
* @param {Dir} dir * @param {Dir} dir
* @param {AutomationPredicate.Binary} pred * @param {AutomationPredicate.Binary} pred
* @param {{filter: (AutomationPredicate.Unary|undefined), * @param {{filter: (AutomationPredicate.Unary|undefined),
...@@ -125,7 +126,7 @@ AutomationUtil.findNextNode = function(cur, dir, pred) { ...@@ -125,7 +126,7 @@ AutomationUtil.findNextNode = function(cur, dir, pred) {
* consider. Defaults to leaf nodes only. * consider. Defaults to leaf nodes only.
* before - True to return a_(i - * before - True to return a_(i -
* 1); a_i otherwise. Defaults to false. * 1); a_i otherwise. Defaults to false.
* @return {chrome.automation.AutomationNode} * @return {AutomationNode}
*/ */
AutomationUtil.findNodeUntil = function(cur, dir, pred, opt_options) { AutomationUtil.findNodeUntil = function(cur, dir, pred, opt_options) {
opt_options = opt_options =
......
...@@ -16,9 +16,9 @@ goog.require('cursors.Cursor'); ...@@ -16,9 +16,9 @@ goog.require('cursors.Cursor');
goog.require('cvox.TabsApiHandler'); goog.require('cvox.TabsApiHandler');
goog.scope(function() { goog.scope(function() {
var AutomationNode = chrome.automation.AutomationNode;
var Dir = AutomationUtil.Dir; var Dir = AutomationUtil.Dir;
var EventType = chrome.automation.EventType; var EventType = chrome.automation.EventType;
var AutomationNode = chrome.automation.AutomationNode;
/** Classic Chrome accessibility API. */ /** Classic Chrome accessibility API. */
global.accessibility = global.accessibility =
...@@ -46,10 +46,10 @@ Background = function() { ...@@ -46,10 +46,10 @@ Background = function() {
cvox.ChromeVox.earcons); cvox.ChromeVox.earcons);
/** /**
* @type {chrome.automation.AutomationNode} * @type {cursors.Range}
* @private * @private
*/ */
this.currentNode_ = null; this.currentRange_ = null;
/** /**
* Whether ChromeVox Next is active. * Whether ChromeVox Next is active.
...@@ -129,12 +129,10 @@ Background.prototype = { ...@@ -129,12 +129,10 @@ Background.prototype = {
return; return;
} }
if (!this.active_ || !this.current_) if (!this.active_ || !this.currentRange_)
return; return;
var previous = this.current_; var current = this.currentRange_;
var current = this.current_;
var dir = Dir.FORWARD; var dir = Dir.FORWARD;
var pred = null; var pred = null;
switch (command) { switch (command) {
...@@ -147,12 +145,10 @@ Background.prototype = { ...@@ -147,12 +145,10 @@ Background.prototype = {
pred = AutomationPredicate.heading; pred = AutomationPredicate.heading;
break; break;
case 'nextLine': case 'nextLine':
dir = Dir.FORWARD; current = current.move(cursors.Unit.LINE, Dir.FORWARD);
pred = AutomationPredicate.inlineTextBox;
break; break;
case 'previousLine': case 'previousLine':
dir = Dir.BACKWARD; current = current.move(cursors.Unit.LINE, Dir.BACKWARD);
pred = AutomationPredicate.inlineTextBox;
break; break;
case 'nextLink': case 'nextLink':
dir = Dir.FORWARD; dir = Dir.FORWARD;
...@@ -163,40 +159,42 @@ Background.prototype = { ...@@ -163,40 +159,42 @@ Background.prototype = {
pred = AutomationPredicate.link; pred = AutomationPredicate.link;
break; break;
case 'nextElement': case 'nextElement':
current = current.role == chrome.automation.RoleType.inlineTextBox ? current = current.move(cursors.Unit.NODE, Dir.FORWARD);
current.parent() : current;
current = AutomationUtil.findNextNode(current,
Dir.FORWARD,
AutomationPredicate.inlineTextBox);
current = current ? current.parent() : current;
break; break;
case 'previousElement': case 'previousElement':
current = current.role == chrome.automation.RoleType.inlineTextBox ? current = current.move(cursors.Unit.NODE, Dir.BACKWARD);
current.parent() : current;
current = AutomationUtil.findNextNode(current,
Dir.BACKWARD,
AutomationPredicate.inlineTextBox);
current = current ? current.parent() : current;
break; break;
case 'goToBeginning': case 'goToBeginning':
current = AutomationUtil.findNodePost(current.root, var node = AutomationUtil.findNodePost(current.getStart().getNode().root,
Dir.FORWARD, Dir.FORWARD,
AutomationPredicate.inlineTextBox); AutomationPredicate.leaf);
if (node)
current = cursors.Range.fromNode(node);
break; break;
case 'goToEnd': case 'goToEnd':
current = AutomationUtil.findNodePost(current.root, var node =
AutomationUtil.findNodePost(current.getStart().getNode().root,
Dir.BACKWARD, Dir.BACKWARD,
AutomationPredicate.inlineTextBox); AutomationPredicate.leaf);
if (node)
current = cursors.Range.fromNode(node);
break; break;
} }
if (pred) if (pred) {
current = AutomationUtil.findNextNode(current, dir, pred); var node = AutomationUtil.findNextNode(
current.getBound(dir).getNode(), dir, pred);
if (node)
current = cursors.Range.fromNode(node);
}
if (current) { if (current) {
current.focus(); // TODO(dtseng): Figure out what it means to focus a range.
current.getStart().getNode().focus();
this.onFocus({target: current}); this.currentRange_ = current;
this.handleOutput(this.currentRange_);
} }
}, },
...@@ -209,20 +207,8 @@ Background.prototype = { ...@@ -209,20 +207,8 @@ Background.prototype = {
if (!node) if (!node)
return; return;
this.current_ = node; this.currentRange_ = cursors.Range.fromNode(node);
var container = node; this.handleOutput(this.currentRange_);
while (container &&
(container.role == chrome.automation.RoleType.inlineTextBox ||
container.role == chrome.automation.RoleType.staticText))
container = container.parent();
var role = container ? container.role : node.role;
var output =
[node.attributes.name, node.attributes.value, role].join(', ');
cvox.ChromeVox.tts.speak(output, cvox.QueueMode.FLUSH);
cvox.ChromeVox.braille.write(cvox.NavBraille.fromText(output));
chrome.accessibilityPrivate.setFocusRing([evt.target.location]);
}, },
/** /**
...@@ -230,13 +216,17 @@ Background.prototype = { ...@@ -230,13 +216,17 @@ Background.prototype = {
* @param {Object} evt * @param {Object} evt
*/ */
onLoadComplete: function(evt) { onLoadComplete: function(evt) {
if (this.current_) if (this.currentRange_)
return; return;
this.current_ = AutomationUtil.findNodePost(evt.target, var node = AutomationUtil.findNodePost(evt.target,
Dir.FORWARD, Dir.FORWARD,
AutomationPredicate.inlineTextBox); AutomationPredicate.leaf);
this.onFocus({target: this.current_}); if (node)
this.currentRange_ = cursors.Range.fromNode(node);
if (this.currentRange_)
this.handleOutput(this.currentRange_);
}, },
/** /**
...@@ -278,7 +268,7 @@ Background.prototype = { ...@@ -278,7 +268,7 @@ Background.prototype = {
} else { } else {
if (this.active_) { if (this.active_) {
for (var eventType in this.listeners_) { for (var eventType in this.listeners_) {
this.current_.root.removeEventListener( this.currentRange_.getStart().getNode().root.removeEventListener(
eventType, this.listeners_[eventType], true); eventType, this.listeners_[eventType], true);
} }
} }
...@@ -294,6 +284,43 @@ Background.prototype = { ...@@ -294,6 +284,43 @@ Background.prototype = {
}.bind(this)); }.bind(this));
} }
}.bind(this)); }.bind(this));
},
/**
* Handles output of a Range.
* @param {!cursors.Range} range Current location.
*/
handleOutput: function(range) {
// TODO(dtseng): This is just placeholder logic for generating descriptions
// pending further design discussion.
function getCursorDesc(cursor) {
var node = cursor.getNode();
var container = node;
while (container &&
(container.role == chrome.automation.RoleType.inlineTextBox ||
container.role == chrome.automation.RoleType.staticText))
container = container.parent();
var role = container ? container.role : node.role;
return [node.attributes.name, node.attributes.value, role].join(', ');
}
// Walk the range and collect descriptions.
var output = '';
var cursor = range.getStart();
var nodeLocations = [];
while (cursor.getNode() != range.getEnd().getNode()) {
output += getCursorDesc(cursor);
nodeLocations.push(cursor.getNode().location);
cursor = cursor.move(
cursors.Unit.NODE, cursors.Movement.DIRECTIONAL, Dir.FORWARD);
}
output += getCursorDesc(range.getEnd());
nodeLocations.push(range.getEnd().getNode().location);
cvox.ChromeVox.tts.speak(output, cvox.QueueMode.FLUSH);
cvox.ChromeVox.braille.write(cvox.NavBraille.fromText(output));
chrome.accessibilityPrivate.setFocusRing(nodeLocations);
} }
}; };
......
...@@ -71,3 +71,53 @@ TEST_F('BackgroundTest', 'InitialFeedback', function() { ...@@ -71,3 +71,53 @@ TEST_F('BackgroundTest', 'InitialFeedback', function() {
cvox.ChromeVox.tts.expectSpeech('end', testDone); cvox.ChromeVox.tts.expectSpeech('end', testDone);
}.bind(this)); }.bind(this));
}); });
/** Tests consistency of navigating forward and backward. */
TEST_F('BackgroundTest', 'ForwardBackwardNavigation', function() {
this.runWithDocument(function() {/*!
<p>start</p>
<a href='#a'>alpha</a>
<a href='#b'>beta</a>
<p>
<h1>charlie</h1>
<a href='foo'>delta</a>
</p>
<a href='#bar'>echo</a>
<h2>foxtraut</h2>
<p>end<span>of test</span></p>
*/},
function() {
var doCmd = function(cmd) {
return function() {
global.backgroundObj.onGotCommand(cmd);
};
};
var expectAfter =
cvox.ChromeVox.tts.expectSpeechAfter.bind(cvox.ChromeVox.tts);
cvox.ChromeVox.tts.expectSpeech('start');
expectAfter('alpha', doCmd('nextLink'));
expectAfter('beta', doCmd('nextLink'));
expectAfter('delta', doCmd('nextLink'));
expectAfter('beta', doCmd('previousLink'));
expectAfter('charlie', doCmd('nextHeading'));
expectAfter('foxtraut', doCmd('nextHeading'));
expectAfter('charlie', doCmd('previousHeading'));
expectAfter('delta', doCmd('nextElement'));
expectAfter('echo', doCmd('nextElement'));
expectAfter('foxtraut', doCmd('nextElement'));
expectAfter('end', doCmd('nextElement'));
expectAfter('foxtraut', doCmd('previousElement'));
// TODO(dtseng): cleanup these utterances.
expectAfter(', end, paragraph, of test, paragraph', doCmd('nextLine'));
expectAfter('start', doCmd('goToBeginning'));
expectAfter('of test', doCmd('goToEnd'));
cvox.ChromeVox.tts.finishExpectations();
}.bind(this));
});
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