| 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 1b509f03daa3a1a090e179932b0ef00868695ffa..1d90039527692cff43650cd42f81342703a7d353 100644
|
| --- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js
|
| +++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js
|
| @@ -46,10 +46,10 @@
|
| */
|
| Output = function() {
|
| // TODO(dtseng): Include braille specific rules.
|
| - /** @type {!Array<cvox.Spannable>} */
|
| - this.buffer_ = [];
|
| - /** @type {!Array<cvox.Spannable>} */
|
| - this.brailleBuffer_ = [];
|
| + /** @type {!cvox.Spannable} */
|
| + this.buffer_ = new cvox.Spannable();
|
| + /** @type {!cvox.Spannable} */
|
| + this.brailleBuffer_ = new cvox.Spannable();
|
| /** @type {!Array<Object>} */
|
| this.locations_ = [];
|
| /** @type {function()} */
|
| @@ -90,11 +90,8 @@
|
| msgId: 'tag_button',
|
| earcon: 'BUTTON'
|
| },
|
| - checkBox: {
|
| + checkbox: {
|
| msgId: 'input_type_checkbox'
|
| - },
|
| - dialog: {
|
| - msgId: 'dialog'
|
| },
|
| heading: {
|
| msgId: 'aria_role_heading',
|
| @@ -126,26 +123,6 @@
|
| },
|
| toolbar: {
|
| msgId: 'aria_role_toolbar'
|
| - }
|
| -};
|
| -
|
| -/**
|
| - * Metadata about supported automation states.
|
| - * @const {!Object<string,
|
| - * {on: {msgId: string, earconId: string},
|
| - * off: {msgId: string, earconId: string}}>}
|
| - * @private
|
| - */
|
| -Output.STATE_INFO_ = {
|
| - checked: {
|
| - on: {
|
| - earconId: 'CHECK_ON',
|
| - msgId: 'checkbox_checked_state'
|
| - },
|
| - off: {
|
| - earconId: 'CHECK_OFF',
|
| - msgId: 'checkbox_unchecked_state'
|
| - }
|
| }
|
| };
|
|
|
| @@ -160,10 +137,15 @@
|
| braille: ''
|
| },
|
| alert: {
|
| - speak: '!doNotInterrupt $role $descendants'
|
| + speak: '!doNotInterrupt ' +
|
| + '@aria_role_alert $name $earcon(ALERT_NONMODAL) $descendants'
|
| },
|
| checkBox: {
|
| - speak: '$name $role $checked'
|
| + speak: '$if($checked, @describe_checkbox_checked($name), ' +
|
| + '@describe_checkbox_unchecked($name)) ' +
|
| + '$if($checked, ' +
|
| + '$earcon(CHECK_ON, @input_type_checkbox), ' +
|
| + '$earcon(CHECK_OFF, @input_type_checkbox))'
|
| },
|
| dialog: {
|
| enter: '$name $role'
|
| @@ -176,9 +158,9 @@
|
| speak: '$value='
|
| },
|
| link: {
|
| - enter: '$name $visited $role',
|
| - stay: '$name= $visited $role',
|
| - speak: '$name= $visited $role'
|
| + enter: '$name= $visited $earcon(LINK, @tag_link)=',
|
| + stay: '$name= $visited @tag_link',
|
| + speak: '$name= $visited $earcon(LINK, @tag_link)='
|
| },
|
| list: {
|
| enter: '@aria_role_list @list_with_items($parentChildCount)'
|
| @@ -199,12 +181,15 @@
|
| speak: '$value'
|
| },
|
| popUpButton: {
|
| - speak: '$value $name $role @aria_has_popup ' +
|
| + speak: '$value $name @tag_button @aria_has_popup $earcon(LISTBOX) ' +
|
| '$if($collapsed, @aria_expanded_false, @aria_expanded_true)'
|
| },
|
| radioButton: {
|
| speak: '$if($checked, @describe_radio_selected($name), ' +
|
| - '@describe_radio_unselected($name))'
|
| + '@describe_radio_unselected($name)) ' +
|
| + '$if($checked, ' +
|
| + '$earcon(CHECK_ON, @input_type_radio), ' +
|
| + '$earcon(CHECK_OFF, @input_type_radio))'
|
| },
|
| slider: {
|
| speak: '@describe_slider($value, $name)'
|
| @@ -225,12 +210,12 @@
|
| },
|
| menuStart: {
|
| 'default': {
|
| - speak: '@chrome_menu_opened($name) $earcon(OBJECT_OPEN)'
|
| + speak: '@chrome_menu_opened($name) $role $earcon(OBJECT_OPEN)'
|
| }
|
| },
|
| menuEnd: {
|
| 'default': {
|
| - speak: '@chrome_menu_closed $earcon(OBJECT_CLOSE)'
|
| + speak: '$earcon(OBJECT_CLOSE)'
|
| }
|
| },
|
| menuListValueChanged: {
|
| @@ -246,6 +231,15 @@
|
| '@aria_role_alert $name $earcon(ALERT_NONMODAL) $descendants'
|
| }
|
| }
|
| +};
|
| +
|
| +/**
|
| + * Alias equivalent attributes.
|
| + * @type {!Object<string, string>}
|
| + */
|
| +Output.ATTRIBUTE_ALIAS = {
|
| + name: 'value',
|
| + value: 'name'
|
| };
|
|
|
| /**
|
| @@ -261,23 +255,6 @@
|
| run: function() {
|
| this.action_();
|
| }
|
| -};
|
| -
|
| -/**
|
| - * Action to play a earcon.
|
| - * @param {string} earconId
|
| - * @constructor
|
| - * @extends {Output.Action}
|
| - */
|
| -Output.EarconAction = function(earconId) {
|
| - Output.Action.call(this, function() {
|
| - cvox.ChromeVox.earcons.playEarcon(
|
| - cvox.AbstractEarcons[earconId]);
|
| - });
|
| -};
|
| -
|
| -Output.EarconAction.prototype = {
|
| - __proto__: Output.Action.prototype
|
| };
|
|
|
| /**
|
| @@ -305,11 +282,8 @@
|
| * Gets the output buffer for speech.
|
| * @return {!cvox.Spannable}
|
| */
|
| - toSpannable: function() {
|
| - return this.buffer_.reduce(function(prev, cur) {
|
| - prev.append(cur);
|
| - return prev;
|
| - }, new cvox.Spannable());
|
| + getBuffer: function() {
|
| + return this.buffer_;
|
| },
|
|
|
| /**
|
| @@ -390,56 +364,45 @@
|
| */
|
| go: function() {
|
| // Speech.
|
| - var queueMode = cvox.QueueMode.FLUSH;
|
| - this.buffer_.forEach(function(buff, i, a) {
|
| - if (buff.toString()) {
|
| - if (this.speechStartCallback_ && i == 0)
|
| - this.speechProperties_['startCallback'] = this.speechStartCallback_;
|
| - else
|
| - this.speechProperties_['startCallback'] = null;
|
| - if (this.speechEndCallback_ && i == a.length - 1)
|
| - this.speechProperties_['endCallback'] = this.speechEndCallback_;
|
| - else
|
| - this.speechProperties_['endCallback'] = null;
|
| - cvox.ChromeVox.tts.speak(
|
| - buff.toString(), queueMode, this.speechProperties_);
|
| - queueMode = cvox.QueueMode.QUEUE;
|
| + var buff = this.buffer_;
|
| + if (buff.toString()) {
|
| + if (this.speechStartCallback_)
|
| + this.speechProperties_['startCallback'] = this.speechStartCallback_;
|
| + if (this.speechEndCallback_) {
|
| + this.speechProperties_['endCallback'] = this.speechEndCallback_;
|
| }
|
| - var actions = buff.getSpansInstanceOf(Output.Action);
|
| - if (actions) {
|
| - actions.forEach(function(a) {
|
| - a.run();
|
| - });
|
| - }
|
| - }.bind(this));
|
| +
|
| + cvox.ChromeVox.tts.speak(
|
| + buff.toString(), cvox.QueueMode.FLUSH, this.speechProperties_);
|
| + }
|
| +
|
| + var actions = buff.getSpansInstanceOf(Output.Action);
|
| + if (actions) {
|
| + actions.forEach(function(a) {
|
| + a.run();
|
| + });
|
| + }
|
|
|
| // Braille.
|
| - var buff = this.brailleBuffer_.reduce(function(prev, cur) {
|
| - if (prev.getLength() > 0 && cur.getLength() > 0)
|
| - prev.append(Output.SPACE);
|
| - prev.append(cur);
|
| - return prev;
|
| - }, new cvox.Spannable());
|
| -
|
| var selSpan =
|
| - buff.getSpanInstanceOf(Output.SelectionSpan);
|
| + this.brailleBuffer_.getSpanInstanceOf(Output.SelectionSpan);
|
| var startIndex = -1, endIndex = -1;
|
| if (selSpan) {
|
| // Casts ok, since the span is known to be in the spannable.
|
| var valueStart =
|
| - /** @type {number} */ (buff.getSpanStart(selSpan));
|
| + /** @type {number} */ (this.brailleBuffer_.getSpanStart(selSpan));
|
| var valueEnd =
|
| - /** @type {number} */ (buff.getSpanEnd(selSpan));
|
| + /** @type {number} */ (this.brailleBuffer_.getSpanEnd(selSpan));
|
| startIndex = valueStart + selSpan.startIndex;
|
| endIndex = valueStart + selSpan.endIndex;
|
| - buff.setSpan(new cvox.ValueSpan(0),
|
| + this.brailleBuffer_.setSpan(new cvox.ValueSpan(0),
|
| valueStart, valueEnd);
|
| - buff.setSpan(new cvox.ValueSelectionSpan(),
|
| + this.brailleBuffer_.setSpan(new cvox.ValueSelectionSpan(),
|
| startIndex, endIndex);
|
| }
|
|
|
| var output = new cvox.NavBraille({
|
| - text: buff,
|
| + text: this.brailleBuffer_,
|
| startIndex: startIndex,
|
| endIndex: endIndex
|
| });
|
| @@ -457,7 +420,7 @@
|
| * @param {!cursors.Range} range
|
| * @param {cursors.Range} prevRange
|
| * @param {chrome.automation.EventType|string} type
|
| - * @param {!Array<cvox.Spannable>} buff Buffer to receive rendered output.
|
| + * @param {!cvox.Spannable} buff Buffer to receive rendered output.
|
| * @private
|
| */
|
| render_: function(range, prevRange, type, buff) {
|
| @@ -472,7 +435,7 @@
|
| * @param {chrome.automation.AutomationNode} node
|
| * @param {string|!Object} format The output format either specified as an
|
| * output template string or a parsed output format tree.
|
| - * @param {!Array<cvox.Spannable>} buff Buffer to receive rendered output.
|
| + * @param {!cvox.Spannable} buff Buffer to receive rendered output.
|
| * @param {!Object=} opt_exclude A set of attributes to exclude.
|
| * @private
|
| */
|
| @@ -506,9 +469,8 @@
|
|
|
| // Set suffix options.
|
| var options = {};
|
| - options.annotation = [];
|
| - options.isUnique = token[token.length - 1] == '=';
|
| - if (options.isUnique)
|
| + options.ifEmpty = token[token.length - 1] == '=';
|
| + if (options.ifEmpty)
|
| token = token.substring(0, token.length - 1);
|
|
|
| // Process token based on prefix.
|
| @@ -520,31 +482,30 @@
|
|
|
| // All possible tokens based on prefix.
|
| if (prefix == '$') {
|
| + options.annotation = token;
|
| if (token == 'value') {
|
| var text = node.attributes.value;
|
| if (text !== undefined) {
|
| + var offset = buff.getLength();
|
| if (node.attributes.textSelStart !== undefined) {
|
| - options.annotation.push(new Output.SelectionSpan(
|
| + options.annotation = new Output.SelectionSpan(
|
| node.attributes.textSelStart,
|
| - node.attributes.textSelEnd));
|
| + node.attributes.textSelEnd);
|
| }
|
| + } else if (node.role == chrome.automation.RoleType.staticText) {
|
| + // TODO(dtseng): Remove once Blink treats staticText values as
|
| + // names.
|
| + text = node.attributes.name;
|
| }
|
| - // Annotate this as a name so we don't duplicate names from ancestors.
|
| - if (node.role == chrome.automation.RoleType.inlineTextBox)
|
| - token = 'name';
|
| - options.annotation.push(token);
|
| - this.append_(buff, text, options);
|
| + this.addToSpannable_(buff, text, options);
|
| } else if (token == 'indexInParent') {
|
| - options.annotation.push(token);
|
| - this.append_(buff, node.indexInParent + 1);
|
| + this.addToSpannable_(buff, node.indexInParent + 1);
|
| } else if (token == 'parentChildCount') {
|
| - options.annotation.push(token);
|
| if (node.parent)
|
| - this.append_(buff, node.parent.children.length);
|
| + this.addToSpannable_(buff, node.parent.children.length);
|
| } else if (token == 'state') {
|
| - options.annotation.push(token);
|
| Object.getOwnPropertyNames(node.state).forEach(function(s) {
|
| - this.append_(buff, s, options);
|
| + this.addToSpannable_(buff, s, options);
|
| }.bind(this));
|
| } else if (token == 'find') {
|
| // Find takes two arguments: JSON query string and format string.
|
| @@ -573,7 +534,6 @@
|
| new cursors.Cursor(rightmost, 0));
|
| this.range_(subrange, null, 'navigate', buff);
|
| } else if (token == 'role') {
|
| - options.annotation.push(token);
|
| var msg = node.role;
|
| var earconId = null;
|
| var info = Output.ROLE_INFO_[node.role];
|
| @@ -586,23 +546,17 @@
|
| } else {
|
| console.error('Missing role info for ' + node.role);
|
| }
|
| - if (earconId)
|
| - options.annotation.push(new Output.EarconAction(earconId));
|
| - this.append_(buff, msg, options);
|
| - } else if (node.attributes[token] !== undefined) {
|
| - options.annotation.push(token);
|
| - this.append_(buff, node.attributes[token], options);
|
| - } else if (Output.STATE_INFO_[token]) {
|
| - options.annotation.push('state');
|
| - var stateInfo = Output.STATE_INFO_[token];
|
| - var resolvedInfo = node.state[token] ? stateInfo.on : stateInfo.off;
|
| - options.annotation.push(
|
| - new Output.EarconAction(resolvedInfo.earconId));
|
| - var msgId =
|
| - this.formatOptions_.braille ? resolvedInfo.msgId + '_brl' :
|
| - resolvedInfo.msgId;
|
| - var msg = cvox.ChromeVox.msgs.getMsg(msgId);
|
| - this.append_(buff, msg, options);
|
| + if (earconId) {
|
| + options.annotation = new Output.Action(function() {
|
| + cvox.ChromeVox.earcons.playEarcon(
|
| + cvox.AbstractEarcons[earconId]);
|
| + });
|
| + }
|
| + this.addToSpannable_(buff, msg, options);
|
| + } else if (node.attributes[token]) {
|
| + this.addToSpannable_(buff, node.attributes[token], options);
|
| + } else if (node.state[token]) {
|
| + this.addToSpannable_(buff, token, options);
|
| } else if (tree.firstChild) {
|
| // Custom functions.
|
| if (token == 'if') {
|
| @@ -613,13 +567,14 @@
|
| else
|
| this.format_(node, cond.nextSibling.nextSibling, buff);
|
| } else if (token == 'earcon') {
|
| - // Assumes there's existing output in our buffer.
|
| - var lastBuff = buff[buff.length - 1];
|
| - if (!lastBuff)
|
| - return;
|
| -
|
| - lastBuff.setSpan(
|
| - new Output.EarconAction(tree.firstChild.value), 0, 0);
|
| + var contentBuff = new cvox.Spannable();
|
| + if (tree.firstChild.nextSibling)
|
| + this.format_(node, tree.firstChild.nextSibling, contentBuff);
|
| + options.annotation = new Output.Action(function() {
|
| + cvox.ChromeVox.earcons.playEarcon(
|
| + cvox.AbstractEarcons[tree.firstChild.value]);
|
| + });
|
| + this.addToSpannable_(buff, contentBuff, options);
|
| }
|
| }
|
| } else if (prefix == '@') {
|
| @@ -633,9 +588,9 @@
|
| console.error('Unexpected value: ' + arg);
|
| return;
|
| }
|
| - var msgBuff = [];
|
| + var msgBuff = new cvox.Spannable();
|
| this.format_(node, arg, msgBuff);
|
| - msgArgs = msgArgs.concat(msgBuff);
|
| + msgArgs.push(msgBuff.toString());
|
| curMsg = curMsg.nextSibling;
|
| }
|
| var msg = cvox.ChromeVox.msgs.getMsg(msgId, msgArgs);
|
| @@ -645,7 +600,7 @@
|
| } catch(e) {}
|
|
|
| if (msg) {
|
| - this.append_(buff, msg, options);
|
| + this.addToSpannable_(buff, msg, options);
|
| }
|
| } else if (prefix == '!') {
|
| this.speechProperties_[token] = true;
|
| @@ -657,7 +612,7 @@
|
| * @param {!cursors.Range} range
|
| * @param {cursors.Range} prevRange
|
| * @param {chrome.automation.EventType|string} type
|
| - * @param {!Array<cvox.Spannable>} rangeBuff
|
| + * @param {!cvox.Spannable} rangeBuff
|
| * @private
|
| */
|
| range_: function(range, prevRange, type, rangeBuff) {
|
| @@ -668,7 +623,7 @@
|
| var prevNode = prevRange.getStart().getNode();
|
|
|
| var formatNodeAndAncestors = function(node, prevNode) {
|
| - var buff = [];
|
| + var buff = new cvox.Spannable();
|
| this.ancestry_(node, prevNode, type, buff);
|
| this.node_(node, prevNode, type, buff);
|
| if (this.formatOptions_.location)
|
| @@ -678,21 +633,22 @@
|
|
|
| while (cursor.getNode() != range.getEnd().getNode()) {
|
| var node = cursor.getNode();
|
| - rangeBuff.push.apply(rangeBuff, formatNodeAndAncestors(node, prevNode));
|
| + this.addToSpannable_(
|
| + rangeBuff, formatNodeAndAncestors(node, prevNode));
|
| prevNode = node;
|
| cursor = cursor.move(cursors.Unit.NODE,
|
| cursors.Movement.DIRECTIONAL,
|
| Dir.FORWARD);
|
| }
|
| var lastNode = range.getEnd().getNode();
|
| - rangeBuff.push.apply(rangeBuff, formatNodeAndAncestors(lastNode, prevNode));
|
| + this.addToSpannable_(rangeBuff, formatNodeAndAncestors(lastNode, prevNode));
|
| },
|
|
|
| /**
|
| * @param {!chrome.automation.AutomationNode} node
|
| * @param {!chrome.automation.AutomationNode} prevNode
|
| * @param {chrome.automation.EventType|string} type
|
| - * @param {!Array<cvox.Spannable>} buff
|
| + * @param {!cvox.Spannable} buff
|
| * @param {!Object=} opt_exclude A list of attributes to exclude from
|
| * processing.
|
| * @private
|
| @@ -715,7 +671,7 @@
|
| this.format_(formatPrevNode, roleBlock.leave, buff, opt_exclude);
|
| }
|
|
|
| - var enterOutputs = [];
|
| + var enterOutput = [];
|
| var enterRole = {};
|
| for (var j = uniqueAncestors.length - 2, formatNode;
|
| (formatNode = uniqueAncestors[j]);
|
| @@ -725,16 +681,14 @@
|
| if (enterRole[formatNode.role])
|
| continue;
|
| enterRole[formatNode.role] = true;
|
| - var tempBuff = [];
|
| + var tempBuff = new cvox.Spannable('');
|
| this.format_(formatNode, roleBlock.enter, tempBuff, opt_exclude);
|
| - enterOutputs.unshift(tempBuff);
|
| + enterOutput.unshift(tempBuff);
|
| }
|
| - if (formatNode.role == 'window')
|
| - break;
|
| - }
|
| - enterOutputs.forEach(function(b) {
|
| - buff.push.apply(buff, b);
|
| - });
|
| + }
|
| + enterOutput.forEach(function(c) {
|
| + this.addToSpannable_(buff, c);
|
| + }.bind(this));
|
|
|
| if (!opt_exclude.stay) {
|
| var commonFormatNode = uniqueAncestors[0];
|
| @@ -752,7 +706,7 @@
|
| * @param {!chrome.automation.AutomationNode} node
|
| * @param {!chrome.automation.AutomationNode} prevNode
|
| * @param {chrome.automation.EventType|string} type
|
| - * @param {!Array<cvox.Spannable>} buff
|
| + * @param {!cvox.Spannable} buff
|
| * @private
|
| */
|
| node_: function(node, prevNode, type, buff) {
|
| @@ -767,7 +721,7 @@
|
| * @param {!cursors.Range} range
|
| * @param {cursors.Range} prevRange
|
| * @param {chrome.automation.EventType|string} type
|
| - * @param {!Array<cvox.Spannable>} buff
|
| + * @param {!cvox.Spannable} buff
|
| * @private
|
| */
|
| subNode_: function(range, prevRange, type, buff) {
|
| @@ -782,47 +736,38 @@
|
| var endIndex = range.getEnd().getIndex();
|
| if (startIndex === endIndex)
|
| endIndex++;
|
| - this.append_(
|
| + this.addToSpannable_(
|
| buff, range.getStart().getText().substring(startIndex, endIndex));
|
| },
|
|
|
| /**
|
| - * Appends output to the |buff|.
|
| - * @param {!Array<cvox.Spannable>} buff
|
| + * Adds to the given buffer with proper delimiters added.
|
| + * @param {!cvox.Spannable} spannable
|
| * @param {string|!cvox.Spannable} value
|
| - * @param {{isUnique: (boolean|undefined),
|
| - * annotation: !Array<*>}=} opt_options
|
| - */
|
| - append_: function(buff, value, opt_options) {
|
| - opt_options = opt_options || {isUnique: false, annotation: []};
|
| -
|
| - // Reject empty values without annotations.
|
| - if ((!value || value.length == 0) && opt_options.annotation.length == 0)
|
| + * @param {{ifEmpty: boolean,
|
| + * annotation: (string|Output.Action|undefined)}=} opt_options
|
| + */
|
| + addToSpannable_: function(spannable, value, opt_options) {
|
| + opt_options = opt_options || {ifEmpty: false, annotation: undefined};
|
| + if ((!value || value.length == 0) && !opt_options.annotation)
|
| return;
|
|
|
| - var spannableToAdd = new cvox.Spannable(value);
|
| - opt_options.annotation.forEach(function(a) {
|
| - spannableToAdd.setSpan(a, 0, spannableToAdd.getLength());
|
| - });
|
| -
|
| - // Early return if the buffer is empty.
|
| - if (buff.length == 0) {
|
| - buff.push(spannableToAdd);
|
| + var spannableToAdd = new cvox.Spannable(value, opt_options.annotation);
|
| + if (spannable.getLength() == 0) {
|
| + spannable.append(spannableToAdd);
|
| return;
|
| }
|
|
|
| - // |isUnique| specifies an annotation that cannot be duplicated.
|
| - if (opt_options.isUnique) {
|
| - var alreadyAnnotated = buff.some(function(s) {
|
| - return opt_options.annotation.some(function(annotation) {
|
| - return s.getSpanStart(annotation) != undefined;
|
| - });
|
| - });
|
| - if (alreadyAnnotated)
|
| - return;
|
| - }
|
| -
|
| - buff.push(spannableToAdd);
|
| + if (opt_options.ifEmpty &&
|
| + opt_options.annotation &&
|
| + (spannable.getSpanStart(opt_options.annotation) != undefined ||
|
| + spannable.getSpanStart(
|
| + Output.ATTRIBUTE_ALIAS[opt_options.annotation]) != undefined))
|
| + return;
|
| +
|
| + var prefixed = new cvox.Spannable(Output.SPACE);
|
| + prefixed.append(spannableToAdd);
|
| + spannable.append(prefixed);
|
| },
|
|
|
| /**
|
|
|