Commit 4a88c7d0 authored by David Tseng's avatar David Tseng Committed by Commit Bot

Provide better output for incremental search

This change breaks up incremental change utterances spoken by ChromeVox.

For example:
- Search+/
- type 'chromi'
- speak 'Chromi, ',
' um, an open source project'
- type 'u'
- speak
'Chromiu, '
'm, an open source project'.
-

Change-Id: Ic34cfac814d8d376a041ae21c0cee44f75c9906e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1832529
Commit-Queue: David Tseng <dtseng@chromium.org>
Reviewed-by: default avatarDavid Tseng <dtseng@chromium.org>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#704442}
parent 5fae9add
......@@ -40,8 +40,11 @@ ISearchHandler.prototype = {
/**
* Called when search result node changes.
* @param {!AutomationNode} node The new search result.
* @param {number} start The index into the name where the search match
* starts.
* @param {number} end The index into the name where the search match ends.
*/
onSearchResultChanged: function(node) {}
onSearchResultChanged: function(node, start, end) {}
};
/**
......@@ -60,6 +63,9 @@ ISearch = function(cursor) {
/** @type {!cursors.Cursor} */
this.cursor = cursors.Cursor.fromNode(leaf);
/** @private {number} */
this.callbackId_ = 0;
// Global exports.
/** Exported for this background script. */
cvox.ChromeVox = chrome.extension.getBackgroundPage()['cvox']['ChromeVox'];
......@@ -77,40 +83,41 @@ ISearch.prototype = {
* Performs a search.
* @param {string} searchStr
* @param {Dir} dir
* @param {boolean=} opt_nextObject
*/
search: function(searchStr, dir) {
search: function(searchStr, dir, opt_nextObject) {
clearTimeout(this.callbackId_);
var step = function() {
searchStr = searchStr.toLocaleLowerCase();
var node = this.cursor.node;
var result = node;
var prev = node;
do {
// Because Closure cannot infer much about prev.
if (!prev)
break;
if (opt_nextObject) {
// We want to start/continue the search at the next object.
result =
AutomationUtil.findNextNode(prev, dir, AutomationPredicate.object);
if (!result)
break;
AutomationUtil.findNextNode(node, dir, AutomationPredicate.object);
}
do {
// Ask native to search the underlying data for a performance boost.
prev = result;
result = result.getNextTextMatch(searchStr, dir == Dir.BACKWARD);
prev = result || prev;
// Check to ensure we have an object-like node; otherwise, continue.
if (result && !AutomationPredicate.object(result))
continue;
} while (!result);
} while (result && !AutomationPredicate.object(result));
if (result) {
this.cursor = cursors.Cursor.fromNode(result);
this.handler_.onSearchResultChanged(result);
var start = result.name.toLocaleLowerCase().indexOf(searchStr);
var end = start + searchStr.length;
this.handler_.onSearchResultChanged(result, start, end);
} else {
this.handler_.onSearchReachedBoundary(this.cursor.node);
}
};
this.callbackId_ = setTimeout(step.bind(this), 0);
},
clear: function() {
clearTimeout(this.callbackId_);
}
};
......@@ -183,7 +190,7 @@ ISearchUI.prototype = {
default:
return false;
}
this.iSearch_.search(this.input_.value, this.dir_);
this.iSearch_.search(this.input_.value, this.dir_, true);
evt.preventDefault();
evt.stopPropagation();
return false;
......@@ -196,6 +203,7 @@ ISearchUI.prototype = {
*/
onTextInput: function(evt) {
var searchStr = evt.target.value + evt.data;
this.iSearch_.clear();
this.iSearch_.search(searchStr, this.dir_);
return true;
},
......@@ -211,21 +219,31 @@ ISearchUI.prototype = {
/**
* @override
*/
onSearchResultChanged: function(node) {
this.output_(node);
onSearchResultChanged: function(node, start, end) {
this.output_(node, start, end);
},
/**
* @param {!AutomationNode} node
* @param {number=} opt_start
* @param {number=} opt_end
* @private
*/
output_: function(node) {
output_: function(node, opt_start, opt_end) {
Output.forceModeForNextSpeechUtterance(cvox.QueueMode.FLUSH);
var o =
new Output()
.withRichSpeechAndBraille(
cursors.Range.fromNode(node), null, Output.EventType.NAVIGATE)
.go();
var o = new Output();
if (opt_start && opt_end) {
o.withString([
node.name.substr(0, opt_start),
node.name.substr(opt_start, opt_end - opt_start),
node.name.substr(opt_end)
].join(', '));
o.format('$role', node);
} else {
o.withRichSpeechAndBraille(
cursors.Range.fromNode(node), null, Output.EventType.NAVIGATE);
}
o.go();
this.background_.setCurrentRange(cursors.Range.fromNode(node));
},
......
......@@ -46,17 +46,23 @@ function FakeISearchHandler(testObj) {
FakeISearchHandler.prototype = {
/** @override */
onSearchReachedBoundary: function(boundaryNode) {
this.expect_.shift()(boundaryNode, true);
this.expect_.shift()({node: boundaryNode, isBoundary: true});
},
onSearchResultChanged: function(node) {
this.expect_.shift()(node);
/** @override */
onSearchResultChanged: function(node, start, end) {
this.expect_.shift()({node: node,
start: start,
end: end});
},
expect: function(str, opt_callback) {
this.expect_.push(this.test.newCallback(function(node, isBound) {
this.expect_.push(this.test.newCallback(function(args) {
var node = args.node;
var actual = node.name || node.role;
if (isBound)
if (args.start && args.end)
actual = 'start=' + args.start + ' end=' + args.end + ' text=' + actual;
if (args.isBoundary)
actual = 'boundary=' + actual;
assertEquals(str, actual);
opt_callback && opt_callback();
......@@ -64,15 +70,15 @@ FakeISearchHandler.prototype = {
}
};
TEST_F('ChromeVoxISearchTest', 'DISABLED_Simple', function() {
TEST_F('ChromeVoxISearchTest', 'Simple', function() {
this.runWithLoadedTree(this.linksAndHeadingsDoc, function(rootNode) {
var handler = new FakeISearchHandler(this);
var search = new ISearch(rootNode);
var search = new ISearch(new cursors.Cursor(rootNode, 0));
search.handler = handler;
// Simple forward search.
search.search('US', 'forward');
handler.expect('About US',
handler.expect('start=6 end=8 text=About US',
search.search.bind(search, 'start', 'backward'));
handler.expect('start',
......@@ -88,9 +94,14 @@ TEST_F('ChromeVoxISearchTest', 'DISABLED_Simple', function() {
// Mixed case substring.
search.search.bind(search, 'bReak', 'forward'));
handler.expect('Latest Breaking News',
search.search.bind(search, 'bReak', 'forward'));
handler.expect('start=7 end=12 text=Latest Breaking News',
search.search.bind(search, 'bReaki', 'forward'));
// Incremental search stays on the current node.
handler.expect('start=7 end=13 text=Latest Breaking News',
search.search.bind(search, 'bReakio', 'forward'));
// No results for the search.
handler.expect('boundary=Latest Breaking News');
});
});
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