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 c18ebb4fc4d148cf2ac9e7dacb6c445495e47f46..8ccff0731423c00385b4a502409be6467bab6115 100644 |
| --- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/editing.js |
| +++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/editing.js |
| @@ -215,11 +215,15 @@ function AutomationRichEditableText(node) { |
| AutomationEditableText.call(this, node); |
| var root = this.node_.root; |
| - if (!root || !root.anchorObject || !root.focusObject) |
| + if (!root || !root.anchorObject || !root.focusObject || |
| + root.anchorOffset === undefined || root.focusOffset === undefined) |
| return; |
| - this.line_ = new editing.EditableLine( |
| - root.anchorObject, root.anchorOffset, root.focusObject, root.focusOffset); |
| + this.anchorLine_ = new editing.EditableLine( |
| + root.anchorObject, root.anchorOffset, root.anchorObject, |
| + root.anchorOffset); |
| + this.focusLine_ = new editing.EditableLine( |
| + root.focusObject, root.focusOffset, root.focusObject, root.focusOffset); |
| } |
| AutomationRichEditableText.prototype = { |
| @@ -228,19 +232,37 @@ AutomationRichEditableText.prototype = { |
| /** @override */ |
| onUpdate: function() { |
| var root = this.node_.root; |
| - if (!root.anchorObject || !root.focusObject) |
| + if (!root.anchorObject || !root.focusObject || |
| + root.anchorOffset === undefined || root.focusOffset === undefined) |
| return; |
| + var anchorLine = new editing.EditableLine( |
| + root.anchorObject, root.anchorOffset, root.anchorObject, |
| + root.anchorOffset); |
| + var focusLine = new editing.EditableLine( |
| + root.focusObject, root.focusOffset, root.focusObject, root.focusOffset); |
| + |
| + var prevAnchorLine = this.anchorLine_; |
| + var prevFocusLine = this.focusLine_; |
| + this.anchorLine_ = anchorLine; |
| + this.focusLine_ = focusLine; |
| + |
| + // Compute the current line based upon whether the current selection was |
| + // extended from anchor or focus. The default behavior is to compute lines |
| + // via focus. |
| + var baseLineOnStart = prevFocusLine.equalsStrict(focusLine); |
| + |
| var cur = new editing.EditableLine( |
| - root.anchorObject, root.anchorOffset || 0, root.focusObject, |
| - root.focusOffset || 0); |
| + root.anchorObject, root.anchorOffset, root.focusObject, |
| + root.focusOffset, baseLineOnStart); |
| var prev = this.line_; |
| this.line_ = cur; |
| - if (prev.equals(cur)) { |
| - // Collapsed cursor. |
| + // Selection stayed within the same line(s) and didn't cross into new lines. |
| + if (anchorLine.equals(prevAnchorLine) && focusLine.equals(prevFocusLine)) { |
| + // Intra-line changes. |
| this.changed(new cvox.TextChangeEvent( |
| - cur.text || '', cur.startOffset || 0, cur.endOffset || 0, true)); |
| + cur.text || '', cur.startOffset, cur.endOffset, true)); |
| this.brailleCurrentRichLine_(); |
| // Finally, queue up any text markers/styles at bounds. |
| @@ -283,9 +305,20 @@ AutomationRichEditableText.prototype = { |
| return; |
| } |
| - // Just output the current line. |
| - cvox.ChromeVox.tts.speak(cur.text, cvox.QueueMode.CATEGORY_FLUSH); |
| - this.brailleCurrentRichLine_(); |
| + if (cur.text == '') { |
| + // This line has no text content. Describe the DOM selection. |
| + new Output() |
| + .withRichSpeechAndBraille( |
| + new Range(cur.start_, cur.end_), |
| + new Range(prev.start_, prev.end_), Output.EventType.NAVIGATE) |
| + .go(); |
| + } else { |
| + // Describe the current line. This accounts for previous/current |
| + // selections and picking the line edge boundary that changed (as computed |
| + // above). This is also the code path for describing paste. |
| + cvox.ChromeVox.tts.speak(cur.text, cvox.QueueMode.CATEGORY_FLUSH); |
| + this.brailleCurrentRichLine_(); |
| + } |
| // The state in EditableTextBase needs to get updated with the new line |
| // contents, so that subsequent intra-line changes get the right state |
| @@ -441,9 +474,19 @@ editing.observer_ = new editing.EditingChromeVoxStateObserver(); |
| /** |
| * An EditableLine encapsulates all data concerning a line in the automation |
| * tree necessary to provide output. |
| + * Editable: an editable selection (e.g. start/end offsets) get saved. |
| + * Line: nodes/offsets at the beginning/end of a line get saved. |
| + * @param {!AutomationNode} startNode |
| + * @param {number} startIndex |
| + * @param {!AutomationNode} endNode |
| + * @param {number} endIndex |
| + * @param {boolean=} opt_baseLineOnStart Controls whether to use anchor or |
| + * focus for Line computations as described above. Selections are automatically |
| + * truncated up to either the line start or end. |
| * @constructor |
| */ |
| -editing.EditableLine = function(startNode, startIndex, endNode, endIndex) { |
| +editing.EditableLine = function( |
| + startNode, startIndex, endNode, endIndex, opt_baseLineOnStart) { |
| /** @private {!Cursor} */ |
| this.start_ = new Cursor(startNode, startIndex); |
| this.start_ = this.start_.deepEquivalent || this.start_; |
| @@ -467,29 +510,41 @@ editing.EditableLine = function(startNode, startIndex, endNode, endIndex) { |
| this.lineStartContainer_; |
| /** @private {number} */ |
| this.localLineStartContainerOffset_ = 0; |
| + /** @private {AutomationNode|undefined} */ |
| + this.lineEndContainer_; |
| + /** @private {number} */ |
| + this.localLineEndContainerOffset_ = 0; |
| - this.computeLineData_(); |
| + this.computeLineData_(opt_baseLineOnStart); |
| }; |
| editing.EditableLine.prototype = { |
| /** @private */ |
| - computeLineData_: function() { |
| + computeLineData_: function(opt_baseLineOnStart) { |
| + // Note that we calculate the line based only upon anchor or focus even if |
| + // they do not fall on the same line. It is up to the caller to specify |
| + // which end to base this line upon since it requires reasoning about two |
| + // lines. |
| var nameLen = 0; |
| - if (this.start_.node.name) |
| - nameLen = this.start_.node.name.length; |
| + var lineBase = opt_baseLineOnStart ? this.start_ : this.end_; |
| + var lineExtend = opt_baseLineOnStart ? this.end_ : this.start_; |
| + |
| + if (lineBase.node.name) |
| + nameLen = lineBase.node.name.length; |
| - this.value_ = new Spannable(this.start_.node.name || '', this.start_); |
| - if (this.start_.node == this.end_.node) |
| - this.value_.setSpan(this.end_, 0, nameLen); |
| + this.value_ = new Spannable(lineBase.node.name || '', lineBase); |
| + if (lineBase.node == lineExtend.node) |
| + this.value_.setSpan(lineExtend, 0, nameLen); |
| // Initialize defaults. |
| - this.lineStart_ = this.start_.node; |
| + this.lineStart_ = lineBase.node; |
| this.lineEnd_ = this.lineStart_; |
| this.startContainer_ = this.lineStart_.parent; |
| this.lineStartContainer_ = this.lineStart_.parent; |
| + this.lineEndContainer_ = this.lineStart_.parent; |
| // Annotate each chunk with its associated inline text box node. |
| - this.value_.setSpan(this.lineStart_, 0, this.lineStart_.name.length); |
| + this.value_.setSpan(this.lineStart_, 0, nameLen); |
| // If the current selection is not on an inline text box (e.g. an image), |
| // return early here so that the line contents are just the node. This is |
| @@ -533,6 +588,7 @@ editing.EditableLine.prototype = { |
| this.value_.append(new Spannable(lineEnd.name, annotation)); |
| } |
| + this.lineEndContainer_ = this.lineEnd_.parent; |
| // Finally, annotate with all parent static texts as NodeSpan's so that |
| // braille routing can key properly into the node with an offset. |
| @@ -552,6 +608,11 @@ editing.EditableLine.prototype = { |
| textCountAfterLineEnd += finder.name.length; |
| } |
| + if (this.lineEndContainer_.name) { |
| + this.localLineEndContainerOffset_ = |
| + this.lineEndContainer_.name.length - textCountAfterLineEnd; |
| + } |
| + |
| var len = 0; |
| for (var i = 0; i < parents.length; i++) { |
| var parent = parents[i]; |
| @@ -592,7 +653,14 @@ editing.EditableLine.prototype = { |
| * @return {number} |
| */ |
| get startOffset() { |
| - return this.value_.getSpanStart(this.start_) + this.start_.index; |
| + // It is possible that the start cursor points to content before this line |
| + // (e.g. in a multi-line selection). |
| + try { |
| + return this.value_.getSpanStart(this.start_) + this.start_.index; |
| + } catch (e) { |
| + // When that happens, fall back to the start of this line. |
| + return 0; |
| + } |
| }, |
| /** |
| @@ -600,7 +668,11 @@ editing.EditableLine.prototype = { |
| * @return {number} |
| */ |
| get endOffset() { |
| - return this.value_.getSpanStart(this.end_) + this.end_.index; |
| + try { |
| + return this.value_.getSpanStart(this.end_) + this.end_.index; |
| + } catch (e) { |
| + return this.value_.length; |
| + } |
| }, |
| /** |
| @@ -647,6 +719,26 @@ editing.EditableLine.prototype = { |
| return this.value_.toString(); |
| }, |
| + /** |
| + * Returns true if |otherLine| shares its start position with this line. |
| + * @return {boolean} |
|
dmazzoni
2017/06/21 06:22:07
Add @param {editing.EditableLine}, I'm assuming a
David Tseng
2017/06/21 16:58:28
Acknowledged and done elsewhere for isSameLine and
|
| + */ |
| + equalsStart: function(otherLine) { |
|
dmazzoni
2017/06/21 06:22:08
how about hasSameStart?
David Tseng
2017/06/21 16:58:28
Start is really overloaded in all of the editing c
|
| + return otherLine.lineStartContainer_ == this.lineStartContainer_ && |
| + otherLine.localLineStartContainerOffset_ == |
| + this.localLineStartContainerOffset_; |
| + }, |
| + |
| + /** |
| + * Returns true if |otherLine| shares its end position with this line. |
| + * @return {boolean} |
| + */ |
| + equalsEnd: function(otherLine) { |
| + return otherLine.lineEndContainer_ == this.lineEndContainer_ && |
| + otherLine.localLineEndContainerOffset_ == |
| + this.localLineEndContainerOffset_; |
| + }, |
| + |
| /** |
| * Returns true if |otherLine| surrounds the same line as |this|. Note that |
| * the contents of the line might be different. |
| @@ -656,9 +748,18 @@ editing.EditableLine.prototype = { |
| // Equality is intentionally loose here as any of the state nodes can be |
| // invalidated at any time. We rely upon the start/anchor of the line |
| // staying the same. |
| - return otherLine.lineStartContainer_ == this.lineStartContainer_ && |
| - otherLine.localLineStartContainerOffset_ == |
| - this.localLineStartContainerOffset_; |
| + return this.equalsStart(otherLine) || this.equalsEnd(otherLine); |
| + }, |
| + |
| + /** |
| + * Returns true if |otherLine| surrounds the same line as |this| and has the |
| + * same selection. |
| + * @return {boolean} |
| + */ |
| + equalsStrict: function(otherLine) { |
| + return this.equals(otherLine) && |
| + this.startOffset == otherLine.startOffset && |
| + this.endOffset == otherLine.endOffset; |
| } |
| }; |