blob: 717fc8afa19a63ecdcc6d42fe560e4d66223ecbf [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 Low-level DOM traversal utility functions to find the
* next (or previous) character, word, sentence, line, or paragraph,
* in a completely stateless manner without actually manipulating the
* selection.
*/
goog.provide('cvox.TraverseUtil');
goog.require('cvox.Cursor');
goog.require('cvox.DomPredicates');
goog.require('cvox.DomUtil');
/**
* Utility functions for stateless DOM traversal.
* @constructor
*/
cvox.TraverseUtil = function() {};
/**
* Gets the text representation of a node. This allows us to substitute
* alt text, names, or titles for html elements that provide them.
* @param {Node} node A DOM node.
* @return {string} A text string representation of the node.
*/
cvox.TraverseUtil.getNodeText = function(node) {
if (node.constructor == Text) {
return node.data;
} else {
return '';
}
};
/**
* Return true if a node should be treated as a leaf node, because
* its children are properties of the object that shouldn't be traversed.
*
* TODO(dmazzoni): replace this with a predicate that detects nodes with
* ARIA roles and other objects that have their own description.
* For now we just detect a couple of common cases.
*
* @param {Node} node A DOM node.
* @return {boolean} True if the node should be treated as a leaf node.
*/
cvox.TraverseUtil.treatAsLeafNode = function(node) {
return node.childNodes.length == 0 || node.nodeName == 'SELECT' ||
node.getAttribute('role') == 'listbox' || node.nodeName == 'OBJECT';
};
/**
* Return true only if a single character is whitespace.
* From https://developer.mozilla.org/en/Whitespace_in_the_DOM,
* whitespace is defined as one of the characters
* "\t" TAB \u0009
* "\n" LF \u000A
* "\r" CR \u000D
* " " SPC \u0020.
*
* @param {string} c A string containing a single character.
* @return {boolean} True if the character is whitespace, otherwise false.
*/
cvox.TraverseUtil.isWhitespace = function(c) {
return (c == ' ' || c == '\n' || c == '\r' || c == '\t');
};
/**
* Set the selection to the range between the given start and end cursors.
* @param {cvox.Cursor} start The desired start of the selection.
* @param {cvox.Cursor} end The desired end of the selection.
* @return {Selection} the selection object.
*/
cvox.TraverseUtil.setSelection = function(start, end) {
var sel = window.getSelection();
sel.removeAllRanges();
var range = document.createRange();
range.setStart(start.node, start.index);
range.setEnd(end.node, end.index);
sel.addRange(range);
return sel;
};
// TODO(dtseng): Combine with cvox.DomUtil.hasContent.
/**
* Check if this DOM node has the attribute aria-hidden='true', which should
* hide it from screen readers.
* @param {Node} node An HTML DOM node.
* @return {boolean} Whether or not the html node should be traversed.
*/
cvox.TraverseUtil.isHidden = function(node) {
if (node instanceof HTMLElement &&
node.getAttribute('aria-hidden') == 'true') {
return true;
}
switch (node.tagName) {
case 'SCRIPT':
case 'NOSCRIPT':
return true;
}
return false;
};
/**
* Moves the cursor forwards until it has crossed exactly one character.
* @param {cvox.Cursor} cursor The cursor location where the search should
* start. On exit, the cursor will be immediately to the right of the
* character returned.
* @param {Array<Element>} elementsEntered Any HTML elements entered.
* @param {Array<Element>} elementsLeft Any HTML elements left.
* @return {?string} The character found, or null if the bottom of the
* document has been reached.
*/
cvox.TraverseUtil.forwardsChar = function(
cursor, elementsEntered, elementsLeft) {
while (true) {
// Move down until we get to a leaf node.
var childNode = null;
if (!cvox.TraverseUtil.treatAsLeafNode(cursor.node)) {
for (var i = cursor.index; i < cursor.node.childNodes.length; i++) {
var node = cursor.node.childNodes[i];
if (cvox.TraverseUtil.isHidden(node)) {
if (node instanceof HTMLElement) {
elementsEntered.push(node);
}
continue;
}
if (cvox.DomUtil.isVisible(node, {checkAncestors: false})) {
childNode = node;
break;
}
}
}
if (childNode) {
cursor.node = childNode;
cursor.index = 0;
cursor.text = cvox.TraverseUtil.getNodeText(cursor.node);
if (cursor.node instanceof HTMLElement) {
elementsEntered.push(cursor.node);
}
continue;
}
// Return the next character from this leaf node.
if (cursor.index < cursor.text.length)
return cursor.text[cursor.index++];
// Move to the next sibling, going up the tree as necessary.
while (cursor.node != null) {
// Try to move to the next sibling.
var siblingNode = null;
for (var node = cursor.node.nextSibling; node != null;
node = node.nextSibling) {
if (cvox.TraverseUtil.isHidden(node)) {
if (node instanceof HTMLElement) {
elementsEntered.push(node);
}
continue;
}
if (cvox.DomUtil.isVisible(node, {checkAncestors: false})) {
siblingNode = node;
break;
}
}
if (siblingNode) {
if (cursor.node instanceof HTMLElement) {
elementsLeft.push(cursor.node);
}
cursor.node = siblingNode;
cursor.text = cvox.TraverseUtil.getNodeText(siblingNode);
cursor.index = 0;
if (cursor.node instanceof HTMLElement) {
elementsEntered.push(cursor.node);
}
break;
}
// Otherwise, move to the parent.
if (cursor.node.parentNode &&
cursor.node.parentNode.constructor != HTMLBodyElement) {
if (cursor.node instanceof HTMLElement) {
elementsLeft.push(cursor.node);
}
cursor.node = cursor.node.parentNode;
cursor.text = null;
cursor.index = 0;
} else {
return null;
}
}
}
};
/**
* Moves the cursor backwards until it has crossed exactly one character.
* @param {cvox.Cursor} cursor The cursor location where the search should
* start. On exit, the cursor will be immediately to the left of the
* character returned.
* @param {Array<Element>} elementsEntered Any HTML elements entered.
* @param {Array<Element>} elementsLeft Any HTML elements left.
* @return {?string} The previous character, or null if the top of the
* document has been reached.
*/
cvox.TraverseUtil.backwardsChar = function(
cursor, elementsEntered, elementsLeft) {
while (true) {
// Move down until we get to a leaf node.
var childNode = null;
if (!cvox.TraverseUtil.treatAsLeafNode(cursor.node)) {
for (var i = cursor.index - 1; i >= 0; i--) {
var node = cursor.node.childNodes[i];
if (cvox.TraverseUtil.isHidden(node)) {
if (node instanceof HTMLElement) {
elementsEntered.push(node);
}
continue;
}
if (cvox.DomUtil.isVisible(node, {checkAncestors: false})) {
childNode = node;
break;
}
}
}
if (childNode) {
cursor.node = childNode;
cursor.text = cvox.TraverseUtil.getNodeText(cursor.node);
if (cursor.text.length)
cursor.index = cursor.text.length;
else
cursor.index = cursor.node.childNodes.length;
if (cursor.node instanceof HTMLElement) {
elementsEntered.push(cursor.node);
}
continue;
}
// Return the previous character from this leaf node.
if (cursor.text.length > 0 && cursor.index > 0) {
return cursor.text[--cursor.index];
}
// Move to the previous sibling, going up the tree as necessary.
while (true) {
// Try to move to the previous sibling.
var siblingNode = null;
for (var node = cursor.node.previousSibling; node != null;
node = node.previousSibling) {
if (cvox.TraverseUtil.isHidden(node)) {
if (node instanceof HTMLElement) {
elementsEntered.push(node);
}
continue;
}
if (cvox.DomUtil.isVisible(node, {checkAncestors: false})) {
siblingNode = node;
break;
}
}
if (siblingNode) {
if (cursor.node instanceof HTMLElement) {
elementsLeft.push(cursor.node);
}
cursor.node = siblingNode;
cursor.text = cvox.TraverseUtil.getNodeText(siblingNode);
if (cursor.text.length)
cursor.index = cursor.text.length;
else
cursor.index = cursor.node.childNodes.length;
if (cursor.node instanceof HTMLElement) {
elementsEntered.push(cursor.node);
}
break;
}
// Otherwise, move to the parent.
if (cursor.node.parentNode &&
cursor.node.parentNode.constructor != HTMLBodyElement) {
if (cursor.node instanceof HTMLElement) {
elementsLeft.push(cursor.node);
}
cursor.node = cursor.node.parentNode;
cursor.text = null;
cursor.index = 0;
} else {
return null;
}
}
}
};
/**
* Finds the next character, starting from endCursor. Upon exit, startCursor
* and endCursor will surround the next character. If skipWhitespace is
* true, will skip until a real character is found. Otherwise, it will
* attempt to select all of the whitespace between the initial position
* of endCursor and the next non-whitespace character.
* @param {!cvox.Cursor} startCursor On exit, points to the position before
* the char.
* @param {!cvox.Cursor} endCursor The position to start searching for the next
* char. On exit, will point to the position past the char.
* @param {Array<Element>} elementsEntered Any HTML elements entered.
* @param {Array<Element>} elementsLeft Any HTML elements left.
* initial and final cursor position will be pushed onto this array.
* @param {boolean} skipWhitespace If true, will keep scanning until a
* non-whitespace character is found.
* @return {?string} The next char, or null if the bottom of the
* document has been reached.
*/
cvox.TraverseUtil.getNextChar = function(
startCursor, endCursor, elementsEntered, elementsLeft, skipWhitespace) {
// Save the starting position and get the first character.
startCursor.copyFrom(endCursor);
var c =
cvox.TraverseUtil.forwardsChar(endCursor, elementsEntered, elementsLeft);
if (c == null)
return null;
// Keep track of whether the first character was whitespace.
var initialWhitespace = cvox.TraverseUtil.isWhitespace(c);
// Keep scanning until we find a non-whitespace or non-skipped character.
while ((cvox.TraverseUtil.isWhitespace(c)) ||
(cvox.TraverseUtil.isHidden(endCursor.node))) {
c = cvox.TraverseUtil.forwardsChar(
endCursor, elementsEntered, elementsLeft);
if (c == null)
return null;
}
if (skipWhitespace || !initialWhitespace) {
// If skipWhitepace is true, or if the first character we encountered
// was not whitespace, return that non-whitespace character.
startCursor.copyFrom(endCursor);
startCursor.index--;
return c;
} else {
for (var i = 0; i < elementsEntered.length; i++) {
if (cvox.TraverseUtil.isHidden(elementsEntered[i])) {
// We need to make sure that startCursor and endCursor aren't
// surrounding a skippable node.
endCursor.index--;
startCursor.copyFrom(endCursor);
startCursor.index--;
return ' ';
}
}
// Otherwise, return all of the whitespace before that last character.
endCursor.index--;
return ' ';
}
};
/**
* Finds the previous character, starting from startCursor. Upon exit,
* startCursor and endCursor will surround the previous character.
* If skipWhitespace is true, will skip until a real character is found.
* Otherwise, it will attempt to select all of the whitespace between
* the initial position of endCursor and the next non-whitespace character.
* @param {!cvox.Cursor} startCursor The position to start searching for the
* char. On exit, will point to the position before the char.
* @param {!cvox.Cursor} endCursor The position to start searching for the next
* char. On exit, will point to the position past the char.
* @param {Array<Element>} elementsEntered Any HTML elements entered.
* @param {Array<Element>} elementsLeft Any HTML elements left.
* initial and final cursor position will be pushed onto this array.
* @param {boolean} skipWhitespace If true, will keep scanning until a
* non-whitespace character is found.
* @return {?string} The previous char, or null if the top of the
* document has been reached.
*/
cvox.TraverseUtil.getPreviousChar = function(
startCursor, endCursor, elementsEntered, elementsLeft, skipWhitespace) {
// Save the starting position and get the first character.
endCursor.copyFrom(startCursor);
var c = cvox.TraverseUtil.backwardsChar(
startCursor, elementsEntered, elementsLeft);
if (c == null)
return null;
// Keep track of whether the first character was whitespace.
var initialWhitespace = cvox.TraverseUtil.isWhitespace(c);
// Keep scanning until we find a non-whitespace or non-skipped character.
while ((cvox.TraverseUtil.isWhitespace(c)) ||
(cvox.TraverseUtil.isHidden(startCursor.node))) {
c = cvox.TraverseUtil.backwardsChar(
startCursor, elementsEntered, elementsLeft);
if (c == null)
return null;
}
if (skipWhitespace || !initialWhitespace) {
// If skipWhitepace is true, or if the first character we encountered
// was not whitespace, return that non-whitespace character.
endCursor.copyFrom(startCursor);
endCursor.index++;
return c;
} else {
for (var i = 0; i < elementsEntered.length; i++) {
if (cvox.TraverseUtil.isHidden(elementsEntered[i])) {
startCursor.index++;
endCursor.copyFrom(startCursor);
endCursor.index++;
return ' ';
}
}
// Otherwise, return all of the whitespace before that last character.
startCursor.index++;
return ' ';
}
};
/**
* Finds the next word, starting from endCursor. Upon exit, startCursor
* and endCursor will surround the next word. A word is defined to be
* a string of 1 or more non-whitespace characters in the same DOM node.
* @param {cvox.Cursor} startCursor On exit, will point to the beginning of the
* word returned.
* @param {cvox.Cursor} endCursor The position to start searching for the next
* word. On exit, will point to the end of the word returned.
* @param {Array<Element>} elementsEntered Any HTML elements entered.
* @param {Array<Element>} elementsLeft Any HTML elements left.
* @return {?string} The next word, or null if the bottom of the
* document has been reached.
*/
cvox.TraverseUtil.getNextWord = function(
startCursor, endCursor, elementsEntered, elementsLeft) {
// Find the first non-whitespace or non-skipped character.
var cursor = endCursor.clone();
var c = cvox.TraverseUtil.forwardsChar(cursor, elementsEntered, elementsLeft);
if (c == null)
return null;
while ((cvox.TraverseUtil.isWhitespace(c)) ||
(cvox.TraverseUtil.isHidden(cursor.node))) {
c = cvox.TraverseUtil.forwardsChar(cursor, elementsEntered, elementsLeft);
if (c == null)
return null;
}
// Set startCursor to the position immediately before the first
// character in our word. It's safe to decrement |index| because
// forwardsChar guarantees that the cursor will be immediately to the
// right of the returned character on exit.
startCursor.copyFrom(cursor);
startCursor.index--;
// Keep building up our word until we reach a whitespace character or
// would cross a tag. Don't actually return any tags crossed, because this
// word goes up until the tag boundary but not past it.
endCursor.copyFrom(cursor);
var word = c;
var newEntered = [];
var newLeft = [];
c = cvox.TraverseUtil.forwardsChar(cursor, newEntered, newLeft);
if (c == null) {
return word;
}
while (!cvox.TraverseUtil.isWhitespace(c) && newEntered.length == 0 &&
newLeft == 0) {
word += c;
endCursor.copyFrom(cursor);
c = cvox.TraverseUtil.forwardsChar(cursor, newEntered, newLeft);
if (c == null) {
return word;
}
}
return word;
};
/**
* Finds the previous word, starting from startCursor. Upon exit, startCursor
* and endCursor will surround the previous word. A word is defined to be
* a string of 1 or more non-whitespace characters in the same DOM node.
* @param {cvox.Cursor} startCursor The position to start searching for the
* previous word. On exit, will point to the beginning of the
* word returned.
* @param {cvox.Cursor} endCursor On exit, will point to the end of the
* word returned.
* @param {Array<Element>} elementsEntered Any HTML elements entered.
* @param {Array<Element>} elementsLeft Any HTML elements left.
* @return {?string} The previous word, or null if the bottom of the
* document has been reached.
*/
cvox.TraverseUtil.getPreviousWord = function(
startCursor, endCursor, elementsEntered, elementsLeft) {
// Find the first non-whitespace or non-skipped character.
var cursor = startCursor.clone();
var c =
cvox.TraverseUtil.backwardsChar(cursor, elementsEntered, elementsLeft);
if (c == null)
return null;
while (
(cvox.TraverseUtil.isWhitespace(c) ||
(cvox.TraverseUtil.isHidden(cursor.node)))) {
c = cvox.TraverseUtil.backwardsChar(cursor, elementsEntered, elementsLeft);
if (c == null)
return null;
}
// Set endCursor to the position immediately after the first
// character we've found (the last character of the word, since we're
// searching backwards).
endCursor.copyFrom(cursor);
endCursor.index++;
// Keep building up our word until we reach a whitespace character or
// would cross a tag. Don't actually return any tags crossed, because this
// word goes up until the tag boundary but not past it.
startCursor.copyFrom(cursor);
var word = c;
var newEntered = [];
var newLeft = [];
c = cvox.TraverseUtil.backwardsChar(cursor, newEntered, newLeft);
if (c == null)
return word;
while (!cvox.TraverseUtil.isWhitespace(c) && newEntered.length == 0 &&
newLeft.length == 0) {
word = c + word;
startCursor.copyFrom(cursor);
c = cvox.TraverseUtil.backwardsChar(cursor, newEntered, newLeft);
if (c == null)
return word;
}
return word;
};
/**
* Given elements entered and left, and break tags, returns true if the
* current word should break.
* @param {Array<Element>} elementsEntered Any HTML elements entered.
* @param {Array<Element>} elementsLeft Any HTML elements left.
* @param {Object<boolean>} breakTags Associative array of tags that should
* break.
* @return {boolean} True if elementsEntered or elementsLeft include an
* element with one of these tags.
*/
cvox.TraverseUtil.includesBreakTagOrSkippedNode = function(
elementsEntered, elementsLeft, breakTags) {
for (var i = 0; i < elementsEntered.length; i++) {
if (cvox.TraverseUtil.isHidden(elementsEntered[i])) {
return true;
}
var style = window.getComputedStyle(elementsEntered[i], null);
if ((style && style.display != 'inline') ||
breakTags[elementsEntered[i].tagName]) {
return true;
}
}
for (i = 0; i < elementsLeft.length; i++) {
var style = window.getComputedStyle(elementsLeft[i], null);
if ((style && style.display != 'inline') ||
breakTags[elementsLeft[i].tagName]) {
return true;
}
}
return false;
};
/**
* Finds the next sentence, starting from endCursor. Upon exit,
* startCursor and endCursor will surround the next sentence.
*
* @param {cvox.Cursor} startCursor On exit, marks the beginning of the
* sentence.
* @param {cvox.Cursor} endCursor The position to start searching for the next
* sentence. On exit, will point to the end of the returned string.
* @param {Array<Element>} elementsEntered Any HTML elements entered.
* @param {Array<Element>} elementsLeft Any HTML elements left.
* @param {Object<boolean>} breakTags Associative array of tags that should
* break the sentence.
* @return {?string} The next sentence, or null if the bottom of the
* document has been reached.
*/
cvox.TraverseUtil.getNextSentence = function(
startCursor, endCursor, elementsEntered, elementsLeft, breakTags) {
return cvox.TraverseUtil.getNextString(
startCursor, endCursor, elementsEntered, elementsLeft,
function(str, word, elementsEntered, elementsLeft) {
if (str.substr(-1) == '.')
return true;
return cvox.TraverseUtil.includesBreakTagOrSkippedNode(
elementsEntered, elementsLeft, breakTags);
});
};
/**
* Finds the previous sentence, starting from startCursor. Upon exit,
* startCursor and endCursor will surround the previous sentence.
*
* @param {cvox.Cursor} startCursor The position to start searching for the next
* sentence. On exit, will point to the start of the returned string.
* @param {cvox.Cursor} endCursor On exit, the end of the returned string.
* @param {Array<Element>} elementsEntered Any HTML elements entered.
* @param {Array<Element>} elementsLeft Any HTML elements left.
* @param {Object<boolean>} breakTags Associative array of tags that should
* break the sentence.
* @return {?string} The previous sentence, or null if the bottom of the
* document has been reached.
*/
cvox.TraverseUtil.getPreviousSentence = function(
startCursor, endCursor, elementsEntered, elementsLeft, breakTags) {
return cvox.TraverseUtil.getPreviousString(
startCursor, endCursor, elementsEntered, elementsLeft,
function(str, word, elementsEntered, elementsLeft) {
if (word.substr(-1) == '.')
return true;
return cvox.TraverseUtil.includesBreakTagOrSkippedNode(
elementsEntered, elementsLeft, breakTags);
});
};
/**
* Finds the next line, starting from endCursor. Upon exit,
* startCursor and endCursor will surround the next line.
*
* @param {cvox.Cursor} startCursor On exit, marks the beginning of the line.
* @param {cvox.Cursor} endCursor The position to start searching for the next
* line. On exit, will point to the end of the returned string.
* @param {Array<Element>} elementsEntered Any HTML elements entered.
* @param {Array<Element>} elementsLeft Any HTML elements left.
* @param {Object<boolean>} breakTags Associative array of tags that should
* break the line.
* @return {?string} The next line, or null if the bottom of the
* document has been reached.
*/
cvox.TraverseUtil.getNextLine = function(
startCursor, endCursor, elementsEntered, elementsLeft, breakTags) {
var range = document.createRange();
var currentRect = null;
var rightMostRect = null;
var prevCursor = endCursor.clone();
return cvox.TraverseUtil.getNextString(
startCursor, endCursor, elementsEntered, elementsLeft,
function(str, word, elementsEntered, elementsLeft) {
range.setStart(startCursor.node, startCursor.index);
range.setEnd(endCursor.node, endCursor.index);
var currentRect = range.getBoundingClientRect();
if (!rightMostRect) {
rightMostRect = currentRect;
}
// Break at new lines except when within a link.
if (currentRect.bottom != rightMostRect.bottom &&
!cvox.DomPredicates.linkPredicate(
cvox.DomUtil.getAncestors(endCursor.node))) {
endCursor.copyFrom(prevCursor);
return true;
}
rightMostRect = currentRect;
prevCursor.copyFrom(endCursor);
return cvox.TraverseUtil.includesBreakTagOrSkippedNode(
elementsEntered, elementsLeft, breakTags);
});
};
/**
* Finds the previous line, starting from startCursor. Upon exit,
* startCursor and endCursor will surround the previous line.
*
* @param {cvox.Cursor} startCursor The position to start searching for the next
* line. On exit, will point to the start of the returned string.
* @param {cvox.Cursor} endCursor On exit, the end of the returned string.
* @param {Array<Element>} elementsEntered Any HTML elements entered.
* @param {Array<Element>} elementsLeft Any HTML elements left.
* @param {Object<boolean>} breakTags Associative array of tags that should
* break the line.
* @return {?string} The previous line, or null if the bottom of the
* document has been reached.
*/
cvox.TraverseUtil.getPreviousLine = function(
startCursor, endCursor, elementsEntered, elementsLeft, breakTags) {
var range = document.createRange();
var currentRect = null;
var leftMostRect = null;
var prevCursor = startCursor.clone();
return cvox.TraverseUtil.getPreviousString(
startCursor, endCursor, elementsEntered, elementsLeft,
function(str, word, elementsEntered, elementsLeft) {
range.setStart(startCursor.node, startCursor.index);
range.setEnd(endCursor.node, endCursor.index);
var currentRect = range.getBoundingClientRect();
if (!leftMostRect) {
leftMostRect = currentRect;
}
// Break at new lines except when within a link.
if (currentRect.top != leftMostRect.top &&
!cvox.DomPredicates.linkPredicate(
cvox.DomUtil.getAncestors(startCursor.node))) {
startCursor.copyFrom(prevCursor);
return true;
}
leftMostRect = currentRect;
prevCursor.copyFrom(startCursor);
return cvox.TraverseUtil.includesBreakTagOrSkippedNode(
elementsEntered, elementsLeft, breakTags);
});
};
/**
* Finds the next paragraph, starting from endCursor. Upon exit,
* startCursor and endCursor will surround the next paragraph.
*
* @param {cvox.Cursor} startCursor On exit, marks the beginning of the
* paragraph.
* @param {cvox.Cursor} endCursor The position to start searching for the next
* paragraph. On exit, will point to the end of the returned string.
* @param {Array<Element>} elementsEntered Any HTML elements entered.
* @param {Array<Element>} elementsLeft Any HTML elements left.
* @return {?string} The next paragraph, or null if the bottom of the
* document has been reached.
*/
cvox.TraverseUtil.getNextParagraph = function(
startCursor, endCursor, elementsEntered, elementsLeft) {
return cvox.TraverseUtil.getNextString(
startCursor, endCursor, elementsEntered, elementsLeft,
function(str, word, elementsEntered, elementsLeft) {
for (var i = 0; i < elementsEntered.length; i++) {
if (cvox.TraverseUtil.isHidden(elementsEntered[i])) {
return true;
}
var style = window.getComputedStyle(elementsEntered[i], null);
if (style && style.display != 'inline') {
return true;
}
}
for (i = 0; i < elementsLeft.length; i++) {
var style = window.getComputedStyle(elementsLeft[i], null);
if (style && style.display != 'inline') {
return true;
}
}
return false;
});
};
/**
* Finds the previous paragraph, starting from startCursor. Upon exit,
* startCursor and endCursor will surround the previous paragraph.
*
* @param {cvox.Cursor} startCursor The position to start searching for the next
* paragraph. On exit, will point to the start of the returned string.
* @param {cvox.Cursor} endCursor On exit, the end of the returned string.
* @param {Array<Element>} elementsEntered Any HTML elements entered.
* @param {Array<Element>} elementsLeft Any HTML elements left.
* @return {?string} The previous paragraph, or null if the bottom of the
* document has been reached.
*/
cvox.TraverseUtil.getPreviousParagraph = function(
startCursor, endCursor, elementsEntered, elementsLeft) {
return cvox.TraverseUtil.getPreviousString(
startCursor, endCursor, elementsEntered, elementsLeft,
function(str, word, elementsEntered, elementsLeft) {
for (var i = 0; i < elementsEntered.length; i++) {
if (cvox.TraverseUtil.isHidden(elementsEntered[i])) {
return true;
}
var style = window.getComputedStyle(elementsEntered[i], null);
if (style && style.display != 'inline') {
return true;
}
}
for (i = 0; i < elementsLeft.length; i++) {
var style = window.getComputedStyle(elementsLeft[i], null);
if (style && style.display != 'inline') {
return true;
}
}
return false;
});
};
/**
* Customizable function to return the next string of words in the DOM, based
* on provided functions to decide when to break one string and start
* the next. This can be used to get the next sentence, line, paragraph,
* or potentially other granularities.
*
* Finds the next contiguous string, starting from endCursor. Upon exit,
* startCursor and endCursor will surround the next string.
*
* The breakBefore function takes four parameters, and
* should return true if the string should be broken before the proposed
* next word:
* str The string so far.
* word The next word to be added.
* elementsEntered The elements entered in reaching this next word.
* elementsLeft The elements left in reaching this next word.
*
* @param {cvox.Cursor} startCursor On exit, will point to the beginning of the
* next string.
* @param {cvox.Cursor} endCursor The position to start searching for the next
* string. On exit, will point to the end of the returned string.
* @param {Array<Element>} elementsEntered Any HTML elements entered.
* @param {Array<Element>} elementsLeft Any HTML elements left.
* @param {function(string, string, Array<Element>, Array<Element>)}
* breakBefore Function that takes the string so far, next word to be
* added, and elements entered and left, and returns true if the string
* should be ended before adding this word.
* @return {?string} The next string, or null if the bottom of the
* document has been reached.
*/
cvox.TraverseUtil.getNextString = function(
startCursor, endCursor, elementsEntered, elementsLeft, breakBefore) {
// Get the first word and set the start cursor to the start of the
// first word.
var wordStartCursor = endCursor.clone();
var wordEndCursor = endCursor.clone();
var newEntered = [];
var newLeft = [];
var str = '';
var word = cvox.TraverseUtil.getNextWord(
wordStartCursor, wordEndCursor, newEntered, newLeft);
if (word == null)
return null;
startCursor.copyFrom(wordStartCursor);
// Always add the first word when the string is empty, and then keep
// adding more words as long as breakBefore returns false
while (!str || !breakBefore(str, word, newEntered, newLeft)) {
// Append this word, set the end cursor to the end of this word, and
// update the returned list of nodes crossed to include ones we crossed
// in reaching this word.
if (str)
str += ' ';
str += word;
elementsEntered = elementsEntered.concat(newEntered);
elementsLeft = elementsLeft.concat(newLeft);
endCursor.copyFrom(wordEndCursor);
// Get the next word and go back to the top of the loop.
newEntered = [];
newLeft = [];
word = cvox.TraverseUtil.getNextWord(
wordStartCursor, wordEndCursor, newEntered, newLeft);
if (word == null)
return str;
}
return str;
};
/**
* Customizable function to return the previous string of words in the DOM,
* based on provided functions to decide when to break one string and start
* the next. See getNextString, above, for more details.
*
* Finds the previous contiguous string, starting from startCursor. Upon exit,
* startCursor and endCursor will surround the next string.
*
* @param {cvox.Cursor} startCursor The position to start searching for the
* previous string. On exit, will point to the beginning of the
* string returned.
* @param {cvox.Cursor} endCursor On exit, will point to the end of the
* string returned.
* @param {Array<Element>} elementsEntered Any HTML elements entered.
* @param {Array<Element>} elementsLeft Any HTML elements left.
* @param {function(string, string, Array<Element>, Array<Element>)}
* breakBefore Function that takes the string so far, the word to be
* added, and nodes crossed, and returns true if the string should be
* ended before adding this word.
* @return {?string} The next string, or null if the top of the
* document has been reached.
*/
cvox.TraverseUtil.getPreviousString = function(
startCursor, endCursor, elementsEntered, elementsLeft, breakBefore) {
// Get the first word and set the end cursor to the end of the
// first word.
var wordStartCursor = startCursor.clone();
var wordEndCursor = startCursor.clone();
var newEntered = [];
var newLeft = [];
var str = '';
var word = cvox.TraverseUtil.getPreviousWord(
wordStartCursor, wordEndCursor, newEntered, newLeft);
if (word == null)
return null;
endCursor.copyFrom(wordEndCursor);
// Always add the first word when the string is empty, and then keep
// adding more words as long as breakBefore returns false
while (!str || !breakBefore(str, word, newEntered, newLeft)) {
// Prepend this word, set the start cursor to the start of this word, and
// update the returned list of nodes crossed to include ones we crossed
// in reaching this word.
if (str)
str = ' ' + str;
str = word + str;
elementsEntered = elementsEntered.concat(newEntered);
elementsLeft = elementsLeft.concat(newLeft);
startCursor.copyFrom(wordStartCursor);
// Get the previous word and go back to the top of the loop.
newEntered = [];
newLeft = [];
word = cvox.TraverseUtil.getPreviousWord(
wordStartCursor, wordEndCursor, newEntered, newLeft);
if (word == null)
return str;
}
return str;
};