| Index: chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js
|
| diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js
|
| index 84e99215d7150a3dc046190cebad4af40f8d3f1d..cdcdbc92e61984189169d7f240b76368ceec4c6b 100644
|
| --- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js
|
| +++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js
|
| @@ -650,14 +650,12 @@ Output.EarconAction.prototype = {
|
| * Annotation for text with a selection inside it.
|
| * @param {number} startIndex
|
| * @param {number} endIndex
|
| - * @param {number=} opt_offset
|
| * @constructor
|
| */
|
| -Output.SelectionSpan = function(startIndex, endIndex, opt_offset) {
|
| +Output.SelectionSpan = function(startIndex, endIndex) {
|
| // TODO(dtseng): Direction lost below; should preserve for braille panning.
|
| this.startIndex = startIndex < endIndex ? startIndex : endIndex;
|
| this.endIndex = endIndex > startIndex ? endIndex : startIndex;
|
| - this.offset = opt_offset || 0;
|
| };
|
|
|
| /**
|
| @@ -872,7 +870,6 @@ Output.prototype = {
|
|
|
| this.formatOptions_ = {speech: false, braille: true, auralStyle: false};
|
| this.format_(node, formatStr, this.brailleBuffer_);
|
| -
|
| return this;
|
| },
|
|
|
| @@ -932,7 +929,7 @@ Output.prototype = {
|
|
|
| // Braille.
|
| if (this.brailleBuffer_.length) {
|
| - var buff = this.createBrailleOutput_();
|
| + var buff = this.mergeBraille_(this.brailleBuffer_);
|
| var selSpan =
|
| buff.getSpanInstanceOf(Output.SelectionSpan);
|
| var startIndex = -1, endIndex = -1;
|
| @@ -941,7 +938,7 @@ Output.prototype = {
|
| var valueEnd = buff.getSpanEnd(selSpan);
|
| startIndex = valueStart + selSpan.startIndex;
|
| endIndex = valueStart + selSpan.endIndex;
|
| - buff.setSpan(new cvox.ValueSpan(selSpan.offset), valueStart, valueEnd);
|
| + buff.setSpan(new cvox.ValueSpan(0), valueStart, valueEnd);
|
| buff.setSpan(new cvox.ValueSelectionSpan(), startIndex, endIndex);
|
| }
|
|
|
| @@ -1038,11 +1035,6 @@ Output.prototype = {
|
| if (options.isUnique)
|
| token = token.substring(0, token.length - 1);
|
|
|
| - // Annotate braille output with the corresponding automation nodes
|
| - // to support acting on nodes based on location in the output.
|
| - if (node && this.formatOptions_.braille)
|
| - options.annotation.push(new Output.NodeSpan(node));
|
| -
|
| // Process token based on prefix.
|
| var prefix = token[0];
|
| token = token.slice(1);
|
| @@ -1068,7 +1060,7 @@ Output.prototype = {
|
| options.annotation.push(token);
|
| if (selectedText) {
|
| this.append_(buff, selectedText, options);
|
| - this.append_(buff, Msgs.getMsg('selected'), options);
|
| + this.append_(buff, Msgs.getMsg('selected'));
|
| } else {
|
| this.append_(buff, text, options);
|
| }
|
| @@ -1498,7 +1490,8 @@ Output.prototype = {
|
| this.format_(formatPrevNode, roleBlock.leave, buff, prevNode);
|
| }
|
|
|
| - var enterOutputs = [];
|
| + // Customize for braille node annotations.
|
| + var originalBuff = buff;
|
| var enterRole = {};
|
| for (var j = uniqueAncestors.length - 1, formatNode;
|
| (formatNode = uniqueAncestors[j]);
|
| @@ -1507,8 +1500,18 @@ Output.prototype = {
|
| if (roleBlock.enter) {
|
| if (enterRole[formatNode.role])
|
| continue;
|
| +
|
| + if (this.formatOptions_.braille)
|
| + buff = [];
|
| +
|
| enterRole[formatNode.role] = true;
|
| this.format_(formatNode, roleBlock.enter, buff, prevNode);
|
| +
|
| + if (this.formatOptions_.braille && buff.length) {
|
| + var nodeSpan = this.mergeBraille_(buff);
|
| + nodeSpan.setSpan(new Output.NodeSpan(formatNode), 0, nodeSpan.length);
|
| + originalBuff.push(nodeSpan);
|
| + }
|
| }
|
| }
|
| },
|
| @@ -1521,6 +1524,11 @@ Output.prototype = {
|
| * @private
|
| */
|
| node_: function(node, prevNode, type, buff) {
|
| + var originalBuff = buff;
|
| +
|
| + if (this.formatOptions_.braille)
|
| + buff = [];
|
| +
|
| // Navigate is the default event.
|
| var eventBlock = Output.RULES[type] || Output.RULES['navigate'];
|
| var roleBlock = eventBlock[node.role] || {};
|
| @@ -1531,6 +1539,13 @@ Output.prototype = {
|
| eventBlock['default'].speak;
|
|
|
| this.format_(node, speakFormat, buff, prevNode);
|
| +
|
| + // Restore braille and add an annotation for this node.
|
| + if (this.formatOptions_.braille) {
|
| + var nodeSpan = this.mergeBraille_(buff);
|
| + nodeSpan.setSpan(new Output.NodeSpan(node), 0, nodeSpan.length);
|
| + originalBuff.push(nodeSpan);
|
| + }
|
| },
|
|
|
| /**
|
| @@ -1550,18 +1565,32 @@ Output.prototype = {
|
| return;
|
|
|
| var options = {annotation: ['name'], isUnique: true};
|
| - var startIndex = range.start.index;
|
| - var endIndex = range.end.index;
|
| + var rangeStart = range.start.index;
|
| + var rangeEnd = range.end.index;
|
| if (this.formatOptions_.braille) {
|
| options.annotation.push(new Output.NodeSpan(node));
|
| var selStart = node.textSelStart;
|
| var selEnd = node.textSelEnd;
|
| +
|
| if (selStart !== undefined &&
|
| - (selEnd >= startIndex && selStart <= endIndex)) {
|
| + selEnd >= rangeStart && selStart <= rangeEnd) {
|
| + // Editable text selection.
|
| +
|
| + // |rangeStart| and |rangeEnd| are indices set by the caller and are
|
| + // assumed to be inside of the range. In braille, we only ever expect to
|
| + // get ranges surrounding a line as anything smaller doesn't make sense.
|
| +
|
| + // |selStart| and |selEnd| reflect the editable selection. The relative
|
| + // selStart and relative selEnd for the current line are then just the
|
| + // difference between |selStart|, |selEnd| with |rangeStart|.
|
| + // See editing_test.js for examples.
|
| options.annotation.push(new Output.SelectionSpan(
|
| - selStart - startIndex,
|
| - selEnd - startIndex,
|
| - startIndex));
|
| + selStart - rangeStart, selEnd - rangeStart));
|
| + } else if (rangeStart != 0 || rangeEnd != range.start.getText().length) {
|
| + // Non-editable text selection over less than the full contents covered
|
| + // by the range. We exclude full content underlines because it is
|
| + // distracting to read braille with all cells underlined with a cursor.
|
| + options.annotation.push(new Output.SelectionSpan(rangeStart, rangeEnd));
|
| }
|
| }
|
|
|
| @@ -1570,13 +1599,24 @@ Output.prototype = {
|
| var earcon = this.findEarcon_(node, prevNode);
|
| if (earcon)
|
| options.annotation.push(earcon);
|
| - this.append_(buff, range.start.getText().substring(startIndex, endIndex),
|
| - options);
|
| + var text = '';
|
| +
|
| + if (this.formatOptions_.braille && !node.state.editable) {
|
| + // In braille, we almost always want to show the entire contents and
|
| + // simply place the cursor under the SelectionSpan we set above.
|
| + text = range.start.getText();
|
| + } else {
|
| + // This is output for speech or editable braille.
|
| + text = range.start.getText().substring(rangeStart, rangeEnd);
|
| + }
|
| +
|
| + this.append_(buff, text, options);
|
| +
|
| if (!this.outputContextFirst_)
|
| this.ancestry_(node, prevNode, type, buff);
|
|
|
| var loc =
|
| - range.start.node.boundsForRange(startIndex, endIndex);
|
| + range.start.node.boundsForRange(rangeStart, rangeEnd);
|
| if (loc)
|
| this.locations_.push(loc);
|
| },
|
| @@ -1673,65 +1713,49 @@ Output.prototype = {
|
| },
|
|
|
| /**
|
| - * Converts the currently rendered braille buffers to a single spannable.
|
| + * Converts the braille |spans| buffer to a single spannable.
|
| + * @param {!Array<Spannable>} spans
|
| * @return {!Spannable}
|
| * @private
|
| */
|
| - createBrailleOutput_: function() {
|
| - var result = new Spannable();
|
| + mergeBraille_: function(spans) {
|
| var separator = ''; // Changes to space as appropriate.
|
| - this.brailleBuffer_.forEach(function(cur) {
|
| - // If this chunk is empty, don't add it since it won't result
|
| - // in any output on the braille display, but node spans would
|
| - // start before the separator in that case, which is not desired.
|
| - // The exception is if this chunk contains a selectionm, in which
|
| - // case it will result in a cursor which has to be preserved.
|
| - // In this case, having separators, potentially both before and after
|
| - // the empty string is correct.
|
| - if (cur.length == 0 && !cur.getSpanInstanceOf(Output.SelectionSpan))
|
| - return;
|
| - var spansToExtend = [];
|
| - var spansToRemove = [];
|
| - // Nodes that have node spans both on the character to the left
|
| - // of the separator and to the right should also cover the separator.
|
| - // We extend the left span to cover both the separator and what the
|
| - // right span used to cover, removing the right span, mostly for
|
| - // ease of writing tests and debug.
|
| - // Note that getSpan(position) never returns zero length spans
|
| - // (because they don't cover any position). Still, we want to include
|
| - // these because they can be included (the selection span in an empty
|
| - // text field is an example), which is why we write the below code
|
| - // using getSpansInstanceOf and check the endpoints (isntead of doing
|
| - // the opposite).
|
| - result.getSpansInstanceOf(Output.NodeSpan).forEach(function(leftSpan) {
|
| - if (result.getSpanEnd(leftSpan) < result.length)
|
| - return;
|
| - var newEnd = result.length;
|
| - cur.getSpansInstanceOf(Output.NodeSpan).forEach(function(rightSpan) {
|
| - if (cur.getSpanStart(rightSpan) == 0 &&
|
| - leftSpan.node === rightSpan.node) {
|
| - newEnd = Math.max(
|
| - newEnd,
|
| - result.length + separator.length +
|
| - cur.getSpanEnd(rightSpan));
|
| - spansToRemove.push(rightSpan);
|
| - }
|
| - });
|
| - if (newEnd > result.length)
|
| - spansToExtend.push({span: leftSpan, end: newEnd});
|
| - });
|
| + var prevHasInlineNode = false;
|
| + return spans.reduce(function(result, cur) {
|
| + // Ignore empty spans except when they contain a selection.
|
| + var hasSelection = cur.getSpanInstanceOf(Output.SelectionSpan);
|
| + if (cur.length == 0 && !hasSelection)
|
| + return result;
|
| +
|
| + // For empty selections, we just add the space separator to account for
|
| + // showing the braille cursor.
|
| + if (cur.length == 0 && hasSelection) {
|
| + result.append(cur);
|
| + result.append(Output.SPACE);
|
| + separator = '';
|
| + return result;
|
| + }
|
| +
|
| + // Keep track of if there's an inline node associated with
|
| + // |cur|.
|
| + var hasInlineNode = cur.getSpansInstanceOf(Output.NodeSpan)
|
| + .some(function(s) {
|
| + return s.node && s.node.display == 'inline';
|
| + });
|
| +
|
| + // Now, decide whether we should include separators between the previous
|
| + // span and |cur|.
|
| + // Never separate chunks without something already there at this point.
|
| + if (result.length == 0 || hasInlineNode || prevHasInlineNode)
|
| + separator = '';
|
| + else
|
| + separator = Output.SPACE;
|
| +
|
| + prevHasInlineNode = hasInlineNode;
|
| result.append(separator);
|
| result.append(cur);
|
| - spansToExtend.forEach(function(elem) {
|
| - result.setSpan(
|
| - elem.span,
|
| - result.getSpanStart(elem.span),
|
| - elem.end);
|
| - });
|
| - spansToRemove.forEach(result.removeSpan.bind(result));
|
| - separator = Output.SPACE;
|
| - });
|
| - return result;
|
| + return result;
|
| + }, new Spannable());
|
| },
|
|
|
| /**
|
| @@ -1796,7 +1820,7 @@ Output.prototype = {
|
| * @return {!Spannable}
|
| */
|
| get brailleOutputForTest() {
|
| - return this.createBrailleOutput_();
|
| + return this.mergeBraille_(this.brailleBuffer_);
|
| }
|
| };
|
|
|
|
|