summaryrefslogtreecommitdiffstats
path: root/chromium/chrome/browser/resources/chromeos/chromevox/common/traverse_util.js
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/chrome/browser/resources/chromeos/chromevox/common/traverse_util.js')
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/traverse_util.js927
1 files changed, 927 insertions, 0 deletions
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/traverse_util.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/traverse_util.js
new file mode 100644
index 00000000000..18781e376d6
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/traverse_util.js
@@ -0,0 +1,927 @@
+// 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.<string, 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.<string, 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.<string, 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.<string, 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.<string, 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;
+};