Chromium Code Reviews| Index: chrome/browser/resources/chromeos/chromevox/cvox2/background/editing.js |
| diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/editing.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/editing.js |
| index 43b60fa2e33a895665bba72defb95631aac1f16f..cee2254a32d29f7e7794f9baa2d2257978ed2866 100644 |
| --- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/editing.js |
| +++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/editing.js |
| @@ -22,7 +22,7 @@ goog.scope(function() { |
| var AutomationEvent = chrome.automation.AutomationEvent; |
| var AutomationNode = chrome.automation.AutomationNode; |
| var Cursor = cursors.Cursor; |
| -var Dir = AutomationUtil.Dir; |
| +var Dir = constants.Dir; |
| var EventType = chrome.automation.EventType; |
| var Range = cursors.Range; |
| var RoleType = chrome.automation.RoleType; |
| @@ -66,8 +66,16 @@ editing.TextEditHandler.prototype = { |
| */ |
| function TextFieldTextEditHandler(node) { |
| editing.TextEditHandler.call(this, node); |
| - /** @type {AutomationEditableText} @private */ |
| - this.editableText_ = new AutomationEditableText(node); |
| + |
| + chrome.automation.getDesktop(function(desktop) { |
| + // Chrome channel is empty when building from source. |
| + var useExperimentalRichText = desktop.chromeChannel == '' && |
| + node.state.richlyEditable; |
| + |
| + /** @private {!AutomationEditableText} */ |
| + this.editableText_ = useExperimentalRichText ? |
| + new AutomationRichEditableText(node) : new AutomationEditableText(node); |
| + }.bind(this)); |
| } |
| TextFieldTextEditHandler.prototype = { |
| @@ -199,6 +207,156 @@ AutomationEditableText.prototype = { |
| } |
| }; |
| + |
| +/** |
| + * A |ChromeVoxEditableTextBase| that implements text editing feedback |
| + * for automation tree text fields using anchor and focus selection. |
| + * @constructor |
| + * @param {!AutomationNode} node |
| + * @extends {AutomationEditableText} |
| + */ |
| +function AutomationRichEditableText(node) { |
| + AutomationEditableText.call(this, node); |
| + |
| + var root = this.node_.root; |
| + if (!root || !root.anchorObject || !root.focusObject) |
| + return; |
| + |
| + this.range = new cursors.Range( |
| + new cursors.Cursor(root.anchorObject, root.anchorOffset || 0), |
| + new cursors.Cursor(root.focusObject, root.focusOffset || 0)); |
| +} |
| + |
| +AutomationRichEditableText.prototype = { |
| + __proto__: AutomationEditableText.prototype, |
| + |
| + /** @override */ |
| + onUpdate: function() { |
| + var root = this.node_.root; |
| + if (!root.anchorObject || !root.focusObject) |
| + return; |
| + |
| + var cur = new cursors.Range( |
| + new cursors.Cursor(root.anchorObject, root.anchorOffset || 0), |
| + new cursors.Cursor(root.focusObject, root.focusOffset || 0)); |
| + var prev = this.range; |
| + |
| + if (prev.equals(cur)) |
|
dmazzoni
2017/05/09 04:49:47
What if the range stays the same but the contents
David Tseng
2017/05/09 23:13:50
Done.
|
| + return; |
| + |
| + this.range = cur; |
| + |
| + if (prev.start.node == cur.start.node && |
| + prev.end.node == cur.end.node && |
| + cur.start.node == cur.end.node) { |
| + // Plain text: diff the two positions. |
| + this.changed(new cvox.TextChangeEvent( |
| + root.anchorObject.name || '', |
| + root.anchorOffset || 0, |
| + root.focusOffset || 0, |
| + true)); |
| + |
| + var lineIndex = this.getLineIndex(this.start); |
| + var brailleLineStart = this.getLineStart(lineIndex); |
| + var buff = new Spannable(this.value); |
| + buff.setSpan(new cvox.ValueSpan(0), 0, this.value.length); |
| + |
| + var selStart = this.start - brailleLineStart; |
| + var selEnd = this.end - brailleLineStart; |
| + buff.setSpan(new cvox.ValueSelectionSpan(), selStart, selEnd); |
| + cvox.ChromeVox.braille.write(new cvox.NavBraille({text: buff, |
| + startIndex: selStart, |
| + endIndex: selEnd})); |
| + return; |
| + } else if (cur.start.equals(prev.start)) { |
|
dmazzoni
2017/05/09 04:49:47
Do you need a braille output rule for this case?
David Tseng
2017/05/09 23:13:50
Removed.
|
| + // Selection. |
| + var dir = prev.end.compare(cur.end); |
| + var msg = dir == Dir.FORWARD ? '@selected' : '@unselected'; |
| + new Output().withSpeech( |
| + new cursors.Range(cur.end, prev.end), prev, Output.EventType.NAVIGATE) |
| + .formatForSpeech(msg) |
| + .go(); |
| + } else if (cur.end.equals(prev.end)) { |
|
dmazzoni
2017/05/09 04:49:46
Same, this does this case need braille?
David Tseng
2017/05/09 23:13:50
Removed.
|
| + // Selection. |
| + var dir = prev.start.compare(cur.start); |
| + var msg = dir == Dir.FORWARD ? '@unselected' : '@selected'; |
| + new Output().withSpeech(new cursors.Range( |
| + cur.start, prev.start), prev, Output.EventType.NAVIGATE) |
| + .formatForSpeech(msg) |
| + .go(); |
| + } else { |
| + // Rich text: |
| + // If the position is collapsed, expand to the current line. |
| + var start = cur.start; |
| + var end = cur.end; |
| + if (start.equals(end)) { |
| + start = start.move(Unit.LINE, Movement.BOUND, Dir.BACKWARD); |
| + end = end.move(Unit.LINE, Movement.BOUND, Dir.FORWARD); |
| + } |
| + var range = new cursors.Range(start, end); |
| + var output = new Output().withRichSpeechAndBraille( |
| + range, prev, Output.EventType.NAVIGATE); |
| + |
| + // This position is not describable. |
| + if (!output.hasSpeech) { |
| + cvox.ChromeVox.tts.speak('blank', cvox.QueueMode.CATEGORY_FLUSH); |
| + cvox.ChromeVox.braille.write( |
| + new cvox.NavBraille({text: '', startIndex: 0, endIndex: 0})); |
| + } else { |
| + output.go(); |
| + } |
| + } |
| + |
| + // Keep the other members in sync for any future editable text base state |
| + // machine changes. |
| + this.value = cur.start.node.name || ''; |
| + this.start = cur.start.index; |
| + this.end = cur.start.index; |
| + }, |
| + |
| + /** @override */ |
| + describeSelectionChanged: function(evt) { |
| + // Ignore end of text announcements. |
| + if ((this.start + 1) == evt.start && evt.start == this.value.length) |
| + return; |
| + |
| + cvox.ChromeVoxEditableTextBase.prototype.describeSelectionChanged.call( |
| + this, evt); |
| + }, |
| + |
| + /** @override */ |
| + getLineIndex: function(charIndex) { |
| + var breaks = this.getLineBreaks_(); |
| + var index = 0; |
| + while (index < breaks.length && breaks[index] <= charIndex) |
| + ++index; |
| + return index; |
| + }, |
| + |
| + /** @override */ |
| + getLineStart: function(lineIndex) { |
| + if (lineIndex == 0) |
| + return 0; |
| + var breaks = this.getLineBreaks_(); |
| + return breaks[lineIndex - 1] || |
| + this.node_.root.focusObject.value.length; |
| + }, |
| + |
| + /** @override */ |
| + getLineEnd: function(lineIndex) { |
| + var breaks = this.getLineBreaks_(); |
| + var value = this.node_.root.focusObject.name; |
| + if (lineIndex >= breaks.length) |
| + return value.length; |
| + return breaks[lineIndex]; |
| + }, |
| + |
| + /** @override */ |
| + getLineBreaks_: function() { |
| + return this.node_.root.focusObject.lineStartOffsets || []; |
| + } |
| +}; |
| + |
| /** |
| * @param {!AutomationNode} node The root editable node, i.e. the root of a |
| * contenteditable subtree or a text field. |