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 a07c9049b60bc9a84c4ab7e2d79e36e55742c34b..34a4f699a65959ab011d333545a5625dc20e3d7d 100644 |
| --- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/editing.js |
| +++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/editing.js |
| @@ -227,8 +227,14 @@ function AutomationRichEditableText(node) { |
| if (!root || !root.anchorObject || !root.focusObject) |
| return; |
| - this.line_ = new editing.EditableLine( |
| - root.anchorObject, root.anchorOffset, root.focusObject, root.focusOffset); |
| + this.anchorLine_ = new editing.EditableLine(root.anchorObject, |
|
dmazzoni
2017/06/19 15:11:51
Is the plan to refactor EditableLine to only take
David Tseng
2017/06/19 18:27:47
No, added comments to clarify. An editable line co
|
| + root.anchorOffset, |
| + root.anchorObject, |
| + root.anchorOffset); |
| + this.focusLine_ = new editing.EditableLine(root.focusObject, |
| + root.focusOffset, |
| + root.focusObject, |
| + root.focusOffset); |
| } |
| AutomationRichEditableText.prototype = { |
| @@ -240,14 +246,34 @@ AutomationRichEditableText.prototype = { |
| if (!root.anchorObject || !root.focusObject) |
| 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.strictEquals(focusLine); |
| + |
| var cur = new editing.EditableLine( |
| root.anchorObject, root.anchorOffset || 0, |
| - root.focusObject, root.focusOffset || 0); |
| - var prev = this.line_; |
| + root.focusObject, root.focusOffset || 0, |
| + baseLineOnStart); |
| 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, |
| @@ -295,9 +321,24 @@ AutomationRichEditableText.prototype = { |
| return; |
| } |
| - // Just output the current line. |
| - cvox.ChromeVox.tts.speak(cur.text, cvox.QueueMode.CATEGORY_FLUSH); |
| - this.brailleCurrentRichLine_(); |
| + if (anchorLine.equals(focusLine)) { |
| + // Current selection is on a new line not part of the previous selection. |
| + this.line_ = focusLine; |
| + cvox.ChromeVox.tts.speak(focusLine.text, cvox.QueueMode.CATEGORY_FLUSH); |
| + this.brailleCurrentRichLine_(); |
| + } if (prevAnchorLine.equals(anchorLine)) { |
| + // The selection changed in focus position. |
| + this.line_ = focusLine; |
| + cvox.ChromeVox.tts.speak(focusLine.text, cvox.QueueMode.CATEGORY_FLUSH); |
| + this.brailleCurrentRichLine_(); |
| + } else if (prevFocusLine.equals(focusLine)) { |
| + // The selection changed in anchor position. |
| + this.line_ = anchorLine; |
| + cvox.ChromeVox.tts.speak(anchorLine.text, cvox.QueueMode.CATEGORY_FLUSH); |
| + this.brailleCurrentRichLine_(); |
| + } else { |
| + // Describe a generic DOM selection. |
|
dmazzoni
2017/06/19 15:11:51
Want to put some sort of placeholder here?
Or may
David Tseng
2017/06/19 18:27:47
Removed. We don't want to favor focus but favor th
|
| + } |
| // The state in EditableTextBase needs to get updated with the new line |
| // contents, so that subsequent intra-line changes get the right state |
| @@ -455,9 +496,15 @@ editing.observer_ = new editing.EditingChromeVoxStateObserver(); |
| /** |
| * An EditableLine encapsulates all data concerning a line in the automation |
| * tree necessary to provide output. |
| + * @param {AutomationNode} startNode |
| + * @param {number} startIndex |
| + * @param {AutomationNode} endNode |
| + * @param {number} endIndex |
| + * @param {boolean=} opt_baseLineOnStart |
| * @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_; |
| @@ -481,26 +528,38 @@ 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); |
| @@ -547,6 +606,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. |
| @@ -566,6 +626,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]; |
| @@ -605,7 +670,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; |
| + } |
| }, |
| /** |
| @@ -613,7 +685,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 - 1; |
| + } |
| }, |
| /** |
| @@ -660,6 +736,21 @@ editing.EditableLine.prototype = { |
| return this.value_.toString(); |
| }, |
| + /** |
| + * |
| + */ |
| + equalsStart: function(otherLine) { |
| + return otherLine.lineStartContainer_ == this.lineStartContainer_ && |
| + otherLine.localLineStartContainerOffset_ == |
| + this.localLineStartContainerOffset_; |
| + }, |
| + |
| + 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. |
| @@ -669,9 +760,13 @@ 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); |
| + }, |
| + |
| + strictEquals: function(otherLine) { |
| + return this.equals(otherLine) && |
| + this.startOffset == otherLine.startOffset && |
| + this.endOffset == otherLine.endOffset; |
| } |
| }; |