1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 * @fileoverview A JavaScript class for walking lines consisting of one or more
11 goog.provide('cvox.LayoutLineWalker');
13 goog.require('cvox.AbstractWalker');
14 goog.require('cvox.StructuralLineWalker');
19 * @extends {cvox.AbstractWalker}
21 cvox.LayoutLineWalker = function() {
22 this.subWalker_ = new cvox.StructuralLineWalker();
24 goog.inherits(cvox.LayoutLineWalker, cvox.AbstractWalker);
30 cvox.LayoutLineWalker.prototype.next = function(sel) {
31 // Collapse selection to the directed end.
32 var endSel = new cvox.CursorSelection(sel.end, sel.end, sel.isReversed());
35 var sync = this.subWalker_.sync(endSel);
40 // Compute the next selection.
41 var start = this.subWalker_.next(endSel);
45 start.setReversed(sel.isReversed());
46 return this.extend_(start).setReversed(false);
53 cvox.LayoutLineWalker.prototype.sync = function(sel) {
54 var line = this.subWalker_.sync(sel);
59 // Extend to both line breaks (in each direction).
60 var end = this.extend_(line);
61 var start = this.extend_(line.setReversed(!line.isReversed()));
63 return new cvox.CursorSelection(start.end, end.end, sel.isReversed());
70 cvox.LayoutLineWalker.prototype.getDescription = function(prevSel, sel) {
71 var descriptions = [];
73 var absSel = sel.clone().setReversed(false);
74 var cur = new cvox.CursorSelection(absSel.start, absSel.start);
75 cur = this.subWalker_.sync(cur);
80 // No need to accumulate descriptions.
81 if (absSel.start.node == absSel.end.node) {
82 return this.subWalker_.getDescription(prevSel, sel);
85 // Walk through and collect descriptions for each line.
86 while (cur && !cur.end.equals(absSel.end)) {
87 descriptions.push.apply(
88 descriptions, this.subWalker_.getDescription(prev, cur));
90 cur = this.subWalker_.next(cur);
93 descriptions.push.apply(
94 descriptions, this.subWalker_.getDescription(prev, cur));
103 cvox.LayoutLineWalker.prototype.getBraille = function(prevSel, sel) {
104 var braille = new cvox.NavBraille({});
105 var absSel = this.subWalker_.sync(sel.clone().setReversed(false));
106 var layoutSel = this.sync(sel).setReversed(false);
107 if (!layoutSel || !absSel) {
110 var cur = new cvox.CursorSelection(layoutSel.start, layoutSel.start);
111 cur = this.subWalker_.sync(cur);
116 // Walk through and collect braille for each line.
117 while (cur && !cur.end.equals(layoutSel.end)) {
118 this.appendBraille_(prevSel, absSel, cur, braille);
120 cur = this.subWalker_.next(cur);
123 this.appendBraille_(prevSel, absSel, cur, braille);
132 cvox.LayoutLineWalker.prototype.getGranularityMsg = function() {
133 return cvox.ChromeVox.msgs.getMsg('layout_line');
138 * Compares two selections and determines if the lie on the same horizontal
139 * line as determined by their bounding rectangles.
140 * @param {!cvox.CursorSelection} lSel Left selection.
141 * @param {!cvox.CursorSelection} rSel Right selection.
142 * @return {boolean} Whether lSel and rSel are on different visual lines.
145 cvox.LayoutLineWalker.prototype.isVisualLineBreak_ = function(lSel, rSel) {
146 if (this.wantsOwnLine_(lSel.end.node) ||
147 this.wantsOwnLine_(rSel.start.node)) {
150 var lRect = lSel.getRange().getBoundingClientRect();
151 var rRect = rSel.getRange().getBoundingClientRect();
153 // Some ranges from the browser give us 0-sized rects (such as in the case of
154 // select's). Detect these cases and use a more reliable method (take the
155 // bounding rect of the actual element rather than the range).
156 if (lRect.width == 0 &&
158 lSel.end.node.nodeType == Node.ELEMENT_NODE) {
159 lRect = lSel.end.node.getBoundingClientRect();
162 if (rRect.width == 0 &&
164 rSel.start.node.nodeType == Node.ELEMENT_NODE) {
165 rRect = rSel.start.node.getBoundingClientRect();
167 return lRect.bottom != rRect.bottom;
172 * Determines if node should force a line break.
173 * This is used for elements with unusual semantics, such as multi-line
174 * text fields, where the behaviour would otherwise be confusing.
175 * @param {!Node} node Node.
176 * @return {boolean} True if the node should appear next to a line break.
179 cvox.LayoutLineWalker.prototype.wantsOwnLine_ = function(node) {
180 return node instanceof HTMLTextAreaElement ||
181 node.parentNode instanceof HTMLTextAreaElement;
186 * Extends a given cursor selection up to the next visual line break.
187 * @param {!cvox.CursorSelection} start The selection.
188 * @return {!cvox.CursorSelection} The resulting selection.
191 cvox.LayoutLineWalker.prototype.extend_ = function(start) {
192 // Extend the selection up to just before a new visual line break.
198 next = this.subWalker_.next(end);
199 } while (next && !this.isVisualLineBreak_(end, next));
200 return new cvox.CursorSelection(start.start, end.end, start.isReversed());
205 * Private routine to append braille given three selections.
206 * @param {!cvox.CursorSelection} prevSel A previous selection in walker
208 * @param {!cvox.CursorSelection} sel A selection that represents the location
209 * of the braille cursor.
210 * @param {!cvox.CursorSelection} cur The specific selection to append.
211 * @param {!cvox.NavBraille} braille Braille on which to append.
214 cvox.LayoutLineWalker.prototype.appendBraille_ = function(
215 prevSel, sel, cur, braille) {
216 var item = this.subWalker_.getBraille(prevSel, cur).text;
217 var valueSelectionSpan = item.getSpanInstanceOf(cvox.ValueSelectionSpan);
219 if (braille.text.getLength() > 0) {
220 braille.text.append(cvox.BrailleUtil.ITEM_SEPARATOR);
223 // Find the surrounding logical "leaf node".
224 // This prevents us from labelling the braille output with the wrong node,
225 // such as a text node child of a <textarea>.
226 var node = cur.start.node;
227 while (node.parentNode && cvox.DomUtil.isLeafNode(node.parentNode)) {
228 node = node.parentNode;
231 var nodeStart = braille.text.getLength();
232 var nodeEnd = nodeStart + item.getLength();
233 braille.text.append(item);
234 braille.text.setSpan(node, nodeStart, nodeEnd);
236 if (sel && cur.absEquals(sel)) {
237 if (valueSelectionSpan) {
238 braille.startIndex = nodeStart + item.getSpanStart(valueSelectionSpan);
239 braille.endIndex = nodeStart + item.getSpanEnd(valueSelectionSpan);
241 braille.startIndex = nodeStart;
242 braille.endIndex = nodeStart + 1;