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;
chrome.automation.AutomationNode.prototype.attributes;
/**
* @type {!chrome.automation.AutomationNode}
*/
chrome.automation.AutomationNode.prototype.root;
/**
* @return {chrome.automation.AutomationNode}
*/
......@@ -1299,6 +1305,9 @@ chrome.automation.AutomationNode.prototype.removeEventListener =
function(eventType, callback, capture) {};
chrome.automation.AutomationNode.prototype.focus = function() {};
/**
* @param {function(chrome.automation.AutomationNode)} callback
*/
......
......@@ -30,15 +30,16 @@ AutomationUtil.Dir = {
goog.scope(function() {
var AutomationNode = chrome.automation.AutomationNode;
var Dir = AutomationUtil.Dir;
/**
* 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 {AutomationPredicate.Unary} pred A predicate to apply
* to a candidate node.
* @return {chrome.automation.AutomationNode}
* @return {AutomationNode}
*/
AutomationUtil.findNodePre = function(cur, dir, pred) {
if (pred(cur))
......@@ -56,11 +57,11 @@ AutomationUtil.findNodePre = function(cur, dir, pred) {
/**
* 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 {AutomationPredicate.Unary} pred A predicate to apply
* to a candidate node.
* @return {chrome.automation.AutomationNode}
* @return {AutomationNode}
*/
AutomationUtil.findNodePost = function(cur, dir, pred) {
var child = dir == Dir.BACKWARD ? cur.lastChild() : cur.firstChild();
......@@ -79,9 +80,9 @@ AutomationUtil.findNodePost = function(cur, dir, pred) {
/**
* Find the next node in the given direction that is either an immediate sibling
* 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
* @return {chrome.automation.AutomationNode}
* @return {AutomationNode}
*/
AutomationUtil.findNextSubtree = function(cur, dir) {
while (cur) {
......@@ -95,11 +96,11 @@ AutomationUtil.findNextSubtree = function(cur, dir) {
/**
* 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 {AutomationPredicate.Unary} pred A predicate to apply
* to a candidate node.
* @return {chrome.automation.AutomationNode}
* @return {AutomationNode}
*/
AutomationUtil.findNextNode = function(cur, dir, pred) {
var next = cur;
......@@ -116,7 +117,7 @@ AutomationUtil.findNextNode = function(cur, dir, pred) {
* 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
* 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 {AutomationPredicate.Binary} pred
* @param {{filter: (AutomationPredicate.Unary|undefined),
......@@ -125,7 +126,7 @@ AutomationUtil.findNextNode = function(cur, dir, pred) {
* consider. Defaults to leaf nodes only.
* before - True to return a_(i -
* 1); a_i otherwise. Defaults to false.
* @return {chrome.automation.AutomationNode}
* @return {AutomationNode}
*/
AutomationUtil.findNodeUntil = function(cur, dir, pred, opt_options) {
opt_options =
......
......@@ -16,9 +16,9 @@ goog.require('cursors.Cursor');
goog.require('cvox.TabsApiHandler');
goog.scope(function() {
var AutomationNode = chrome.automation.AutomationNode;
var Dir = AutomationUtil.Dir;
var EventType = chrome.automation.EventType;
var AutomationNode = chrome.automation.AutomationNode;
/** Classic Chrome accessibility API. */
global.accessibility =
......@@ -46,10 +46,10 @@ Background = function() {
cvox.ChromeVox.earcons);
/**
* @type {chrome.automation.AutomationNode}
* @type {cursors.Range}
* @private
*/
this.currentNode_ = null;
this.currentRange_ = null;
/**
* Whether ChromeVox Next is active.
......@@ -129,12 +129,10 @@ Background.prototype = {
return;
}
if (!this.active_ || !this.current_)
if (!this.active_ || !this.currentRange_)
return;
var previous = this.current_;
var current = this.current_;
var current = this.currentRange_;
var dir = Dir.FORWARD;
var pred = null;
switch (command) {
......@@ -147,12 +145,10 @@ Background.prototype = {
pred = AutomationPredicate.heading;
break;
case 'nextLine':
dir = Dir.FORWARD;
pred = AutomationPredicate.inlineTextBox;
current = current.move(cursors.Unit.LINE, Dir.FORWARD);
break;
case 'previousLine':
dir = Dir.BACKWARD;
pred = AutomationPredicate.inlineTextBox;
current = current.move(cursors.Unit.LINE, Dir.BACKWARD);
break;
case 'nextLink':
dir = Dir.FORWARD;
......@@ -163,40 +159,42 @@ Background.prototype = {
pred = AutomationPredicate.link;
break;
case 'nextElement':
current = current.role == chrome.automation.RoleType.inlineTextBox ?
current.parent() : current;
current = AutomationUtil.findNextNode(current,
Dir.FORWARD,
AutomationPredicate.inlineTextBox);
current = current ? current.parent() : current;
current = current.move(cursors.Unit.NODE, Dir.FORWARD);
break;
case 'previousElement':
current = current.role == chrome.automation.RoleType.inlineTextBox ?
current.parent() : current;
current = AutomationUtil.findNextNode(current,
Dir.BACKWARD,
AutomationPredicate.inlineTextBox);
current = current ? current.parent() : current;
current = current.move(cursors.Unit.NODE, Dir.BACKWARD);
break;
case 'goToBeginning':
current = AutomationUtil.findNodePost(current.root,
var node = AutomationUtil.findNodePost(current.getStart().getNode().root,
Dir.FORWARD,
AutomationPredicate.inlineTextBox);
AutomationPredicate.leaf);
if (node)
current = cursors.Range.fromNode(node);
break;
case 'goToEnd':
current = AutomationUtil.findNodePost(current.root,
var node =
AutomationUtil.findNodePost(current.getStart().getNode().root,
Dir.BACKWARD,
AutomationPredicate.inlineTextBox);
AutomationPredicate.leaf);
if (node)
current = cursors.Range.fromNode(node);
break;
}
if (pred)
current = AutomationUtil.findNextNode(current, dir, pred);
if (pred) {
var node = AutomationUtil.findNextNode(
current.getBound(dir).getNode(), dir, pred);
if (node)
current = cursors.Range.fromNode(node);
}
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 = {
if (!node)
return;
this.current_ = node;
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;
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]);
this.currentRange_ = cursors.Range.fromNode(node);
this.handleOutput(this.currentRange_);
},
/**
......@@ -230,13 +216,17 @@ Background.prototype = {
* @param {Object} evt
*/
onLoadComplete: function(evt) {
if (this.current_)
if (this.currentRange_)
return;
this.current_ = AutomationUtil.findNodePost(evt.target,
var node = AutomationUtil.findNodePost(evt.target,
Dir.FORWARD,
AutomationPredicate.inlineTextBox);
this.onFocus({target: this.current_});
AutomationPredicate.leaf);
if (node)
this.currentRange_ = cursors.Range.fromNode(node);
if (this.currentRange_)
this.handleOutput(this.currentRange_);
},
/**
......@@ -278,7 +268,7 @@ Background.prototype = {
} else {
if (this.active_) {
for (var eventType in this.listeners_) {
this.current_.root.removeEventListener(
this.currentRange_.getStart().getNode().root.removeEventListener(
eventType, this.listeners_[eventType], true);
}
}
......@@ -294,6 +284,43 @@ Background.prototype = {
}.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() {
cvox.ChromeVox.tts.expectSpeech('end', testDone);
}.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