blob: 8495713e8a009eced9ddea86bc3ac0a0edee3209 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview A JavaScript class for walking lines consisting of one or more
* clickable nodes.
*/
goog.provide('cvox.LayoutLineWalker');
goog.require('cvox.AbstractWalker');
goog.require('cvox.StructuralLineWalker');
/**
* @constructor
* @extends {cvox.AbstractWalker}
*/
cvox.LayoutLineWalker = function() {
this.subWalker_ = new cvox.StructuralLineWalker();
};
goog.inherits(cvox.LayoutLineWalker, cvox.AbstractWalker);
/**
* @override
*/
cvox.LayoutLineWalker.prototype.next = function(sel) {
// Collapse selection to the directed end.
var endSel = new cvox.CursorSelection(sel.end, sel.end, sel.isReversed());
// Sync to the line.
var sync = this.subWalker_.sync(endSel);
if (!sync) {
return null;
}
// Compute the next selection.
var start = this.subWalker_.next(endSel);
if (!start) {
return null;
}
start.setReversed(sel.isReversed());
return this.extend_(start).setReversed(false);
};
/**
* @override
*/
cvox.LayoutLineWalker.prototype.sync = function(sel) {
var line = this.subWalker_.sync(sel);
if (!line) {
return null;
}
// Extend to both line breaks (in each direction).
var end = this.extend_(line);
var start = this.extend_(line.setReversed(!line.isReversed()));
return new cvox.CursorSelection(start.end, end.end, sel.isReversed());
};
/**
* @override
*/
cvox.LayoutLineWalker.prototype.getDescription = function(prevSel, sel) {
var descriptions = [];
var prev = prevSel;
var absSel = sel.clone().setReversed(false);
var cur = new cvox.CursorSelection(absSel.start, absSel.start);
cur = this.subWalker_.sync(cur);
if (!cur) {
return [];
}
// No need to accumulate descriptions.
if (absSel.start.node == absSel.end.node) {
return this.subWalker_.getDescription(prevSel, sel);
}
// Walk through and collect descriptions for each line.
while (cur && !cur.end.equals(absSel.end)) {
descriptions.push.apply(
descriptions, this.subWalker_.getDescription(prev, cur));
prev = cur;
cur = this.subWalker_.next(cur);
}
if (cur) {
descriptions.push.apply(
descriptions, this.subWalker_.getDescription(prev, cur));
}
return descriptions;
};
/**
* @override
*/
cvox.LayoutLineWalker.prototype.getBraille = function(prevSel, sel) {
var braille = new cvox.NavBraille({});
var absSel = this.subWalker_.sync(sel.clone().setReversed(false));
var layoutSel = this.sync(sel).setReversed(false);
if (!layoutSel || !absSel) {
return braille;
}
var cur = new cvox.CursorSelection(layoutSel.start, layoutSel.start);
cur = this.subWalker_.sync(cur);
if (!cur) {
return braille;
}
// Walk through and collect braille for each line.
while (cur && !cur.end.equals(layoutSel.end)) {
this.appendBraille_(prevSel, absSel, cur, braille);
prevSel = cur;
cur = this.subWalker_.next(cur);
}
if (cur) {
this.appendBraille_(prevSel, absSel, cur, braille);
}
return braille;
};
/**
* @override
*/
cvox.LayoutLineWalker.prototype.getGranularityMsg = function() {
return cvox.ChromeVox.msgs.getMsg('layout_line');
};
/**
* Compares two selections and determines if the lie on the same horizontal
* line as determined by their bounding rectangles.
* @param {!cvox.CursorSelection} lSel Left selection.
* @param {!cvox.CursorSelection} rSel Right selection.
* @return {boolean} Whether lSel and rSel are on different visual lines.
* @private
*/
cvox.LayoutLineWalker.prototype.isVisualLineBreak_ = function(lSel, rSel) {
if (this.wantsOwnLine_(lSel.end.node) ||
this.wantsOwnLine_(rSel.start.node)) {
return true;
}
var lRect = lSel.getRange().getBoundingClientRect();
var rRect = rSel.getRange().getBoundingClientRect();
// Some ranges from the browser give us 0-sized rects (such as in the case of
// select's). Detect these cases and use a more reliable method (take the
// bounding rect of the actual element rather than the range).
if (lRect.width == 0 &&
lRect.height == 0 &&
lSel.end.node.nodeType == Node.ELEMENT_NODE) {
lRect = lSel.end.node.getBoundingClientRect();
}
if (rRect.width == 0 &&
rRect.height == 0 &&
rSel.start.node.nodeType == Node.ELEMENT_NODE) {
rRect = rSel.start.node.getBoundingClientRect();
}
return lRect.bottom != rRect.bottom;
};
/**
* Determines if node should force a line break.
* This is used for elements with unusual semantics, such as multi-line
* text fields, where the behaviour would otherwise be confusing.
* @param {!Node} node Node.
* @return {boolean} True if the node should appear next to a line break.
* @private
*/
cvox.LayoutLineWalker.prototype.wantsOwnLine_ = function(node) {
return node instanceof HTMLTextAreaElement ||
node.parentNode instanceof HTMLTextAreaElement;
};
/**
* Extends a given cursor selection up to the next visual line break.
* @param {!cvox.CursorSelection} start The selection.
* @return {!cvox.CursorSelection} The resulting selection.
* @private
*/
cvox.LayoutLineWalker.prototype.extend_ = function(start) {
// Extend the selection up to just before a new visual line break.
var end = start;
var next = start;
do {
end = next;
next = this.subWalker_.next(end);
} while (next && !this.isVisualLineBreak_(end, next));
return new cvox.CursorSelection(start.start, end.end, start.isReversed());
};
/**
* Private routine to append braille given three selections.
* @param {!cvox.CursorSelection} prevSel A previous selection in walker
* ordering.
* @param {!cvox.CursorSelection} sel A selection that represents the location
* of the braille cursor.
* @param {!cvox.CursorSelection} cur The specific selection to append.
* @param {!cvox.NavBraille} braille Braille on which to append.
* @private
*/
cvox.LayoutLineWalker.prototype.appendBraille_ = function(
prevSel, sel, cur, braille) {
var item = this.subWalker_.getBraille(prevSel, cur).text;
var valueSelectionSpan = item.getSpanInstanceOf(
cvox.BrailleUtil.ValueSelectionSpan);
if (braille.text.getLength() > 0) {
braille.text.append(cvox.BrailleUtil.ITEM_SEPARATOR);
}
// Find the surrounding logical "leaf node".
// This prevents us from labelling the braille output with the wrong node,
// such as a text node child of a <textarea>.
var node = cur.start.node;
while (node.parentNode && cvox.DomUtil.isLeafNode(node.parentNode)) {
node = node.parentNode;
}
var nodeStart = braille.text.getLength();
var nodeEnd = nodeStart + item.getLength();
braille.text.append(item);
braille.text.setSpan(node, nodeStart, nodeEnd);
if (sel && cur.absEquals(sel)) {
if (valueSelectionSpan) {
braille.startIndex = nodeStart + item.getSpanStart(valueSelectionSpan);
braille.endIndex = nodeStart + item.getSpanEnd(valueSelectionSpan);
} else {
braille.startIndex = nodeStart;
braille.endIndex = nodeStart + 1;
}
}
};