cygprofile: increase timeouts to allow showing web contents
[chromium-blink-merge.git] / ui / accessibility / extensions / caretbrowsing / traverse_util.js
blobcc5ca6eaeaa60884f8c404b21501f0b0e5f95bbb
1 /* Copyright (c) 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. */
5 /**
6 * @fileoverview Low-level DOM traversal utility functions to find the
7 * next (or previous) character, word, sentence, line, or paragraph,
8 * in a completely stateless manner without actually manipulating the
9 * selection.
12 /**
13 * A class to represent a cursor location in the document,
14 * like the start position or end position of a selection range.
16 * Later this may be extended to support "virtual text" for an object,
17 * like the ALT text for an image.
19 * Note: we cache the text of a particular node at the time we
20 * traverse into it. Later we should add support for dynamically
21 * reloading it.
22 * @param {Node} node The DOM node.
23 * @param {number} index The index of the character within the node.
24 * @param {string} text The cached text contents of the node.
25 * @constructor
27 Cursor = function(node, index, text) {
28 this.node = node;
29 this.index = index;
30 this.text = text;
33 /**
34 * @return {Cursor} A new cursor pointing to the same location.
36 Cursor.prototype.clone = function() {
37 return new Cursor(this.node, this.index, this.text);
40 /**
41 * Modify this cursor to point to the location that another cursor points to.
42 * @param {Cursor} otherCursor The cursor to copy from.
44 Cursor.prototype.copyFrom = function(otherCursor) {
45 this.node = otherCursor.node;
46 this.index = otherCursor.index;
47 this.text = otherCursor.text;
50 /**
51 * Utility functions for stateless DOM traversal.
52 * @constructor
54 TraverseUtil = function() {};
56 /**
57 * Gets the text representation of a node. This allows us to substitute
58 * alt text, names, or titles for html elements that provide them.
59 * @param {Node} node A DOM node.
60 * @return {string} A text string representation of the node.
62 TraverseUtil.getNodeText = function(node) {
63 if (node.constructor == Text) {
64 return node.data;
65 } else {
66 return '';
70 /**
71 * Return true if a node should be treated as a leaf node, because
72 * its children are properties of the object that shouldn't be traversed.
74 * TODO(dmazzoni): replace this with a predicate that detects nodes with
75 * ARIA roles and other objects that have their own description.
76 * For now we just detect a couple of common cases.
78 * @param {Node} node A DOM node.
79 * @return {boolean} True if the node should be treated as a leaf node.
81 TraverseUtil.treatAsLeafNode = function(node) {
82 return node.childNodes.length == 0 ||
83 node.nodeName == 'SELECT' ||
84 node.nodeName == 'OBJECT';
87 /**
88 * Return true only if a single character is whitespace.
89 * From https://developer.mozilla.org/en/Whitespace_in_the_DOM,
90 * whitespace is defined as one of the characters
91 * "\t" TAB \u0009
92 * "\n" LF \u000A
93 * "\r" CR \u000D
94 * " " SPC \u0020.
96 * @param {string} c A string containing a single character.
97 * @return {boolean} True if the character is whitespace, otherwise false.
99 TraverseUtil.isWhitespace = function(c) {
100 return (c == ' ' || c == '\n' || c == '\r' || c == '\t');
104 * Set the selection to the range between the given start and end cursors.
105 * @param {Cursor} start The desired start of the selection.
106 * @param {Cursor} end The desired end of the selection.
107 * @return {Selection} the selection object.
109 TraverseUtil.setSelection = function(start, end) {
110 var sel = window.getSelection();
111 sel.removeAllRanges();
112 var range = document.createRange();
113 range.setStart(start.node, start.index);
114 range.setEnd(end.node, end.index);
115 sel.addRange(range);
117 return sel;
121 * Use the computed CSS style to figure out if this DOM node is currently
122 * visible.
123 * @param {Node} node A HTML DOM node.
124 * @return {boolean} Whether or not the html node is visible.
126 TraverseUtil.isVisible = function(node) {
127 if (!node.style)
128 return true;
129 var style = window.getComputedStyle(/** @type {Element} */(node), null);
130 return (!!style && style.display != 'none' && style.visibility != 'hidden');
134 * Use the class name to figure out if this DOM node should be traversed.
135 * @param {Node} node A HTML DOM node.
136 * @return {boolean} Whether or not the html node should be traversed.
138 TraverseUtil.isSkipped = function(node) {
139 if (node.constructor == Text)
140 node = node.parentElement;
141 if (node.className == 'CaretBrowsing_Caret' ||
142 node.className == 'CaretBrowsing_AnimateCaret') {
143 return true;
145 return false;
149 * Moves the cursor forwards until it has crossed exactly one character.
150 * @param {Cursor} cursor The cursor location where the search should start.
151 * On exit, the cursor will be immediately to the right of the
152 * character returned.
153 * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
154 * initial and final cursor position will be pushed onto this array.
155 * @return {?string} The character found, or null if the bottom of the
156 * document has been reached.
158 TraverseUtil.forwardsChar = function(cursor, nodesCrossed) {
159 while (true) {
160 // Move down until we get to a leaf node.
161 var childNode = null;
162 if (!TraverseUtil.treatAsLeafNode(cursor.node)) {
163 for (var i = cursor.index; i < cursor.node.childNodes.length; i++) {
164 var node = cursor.node.childNodes[i];
165 if (TraverseUtil.isSkipped(node)) {
166 nodesCrossed.push(node);
167 continue;
169 if (TraverseUtil.isVisible(node)) {
170 childNode = node;
171 break;
175 if (childNode) {
176 cursor.node = childNode;
177 cursor.index = 0;
178 cursor.text = TraverseUtil.getNodeText(cursor.node);
179 if (cursor.node.constructor != Text) {
180 nodesCrossed.push(cursor.node);
182 continue;
185 // Return the next character from this leaf node.
186 if (cursor.index < cursor.text.length)
187 return cursor.text[cursor.index++];
189 // Move to the next sibling, going up the tree as necessary.
190 while (cursor.node != null) {
191 // Try to move to the next sibling.
192 var siblingNode = null;
193 for (var node = cursor.node.nextSibling;
194 node != null;
195 node = node.nextSibling) {
196 if (TraverseUtil.isSkipped(node)) {
197 nodesCrossed.push(node);
198 continue;
200 if (TraverseUtil.isVisible(node)) {
201 siblingNode = node;
202 break;
205 if (siblingNode) {
206 cursor.node = siblingNode;
207 cursor.text = TraverseUtil.getNodeText(siblingNode);
208 cursor.index = 0;
210 if (cursor.node.constructor != Text) {
211 nodesCrossed.push(cursor.node);
214 break;
217 // Otherwise, move to the parent.
218 if (cursor.node.parentNode &&
219 cursor.node.parentNode.constructor != HTMLBodyElement) {
220 cursor.node = cursor.node.parentNode;
221 cursor.text = null;
222 cursor.index = 0;
223 } else {
224 return null;
231 * Moves the cursor backwards until it has crossed exactly one character.
232 * @param {Cursor} cursor The cursor location where the search should start.
233 * On exit, the cursor will be immediately to the left of the
234 * character returned.
235 * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
236 * initial and final cursor position will be pushed onto this array.
237 * @return {?string} The previous character, or null if the top of the
238 * document has been reached.
240 TraverseUtil.backwardsChar = function(cursor, nodesCrossed) {
241 while (true) {
242 // Move down until we get to a leaf node.
243 var childNode = null;
244 if (!TraverseUtil.treatAsLeafNode(cursor.node)) {
245 for (var i = cursor.index - 1; i >= 0; i--) {
246 var node = cursor.node.childNodes[i];
247 if (TraverseUtil.isSkipped(node)) {
248 nodesCrossed.push(node);
249 continue;
251 if (TraverseUtil.isVisible(node)) {
252 childNode = node;
253 break;
257 if (childNode) {
258 cursor.node = childNode;
259 cursor.text = TraverseUtil.getNodeText(cursor.node);
260 if (cursor.text.length)
261 cursor.index = cursor.text.length;
262 else
263 cursor.index = cursor.node.childNodes.length;
264 if (cursor.node.constructor != Text)
265 nodesCrossed.push(cursor.node);
266 continue;
269 // Return the previous character from this leaf node.
270 if (cursor.text.length > 0 && cursor.index > 0) {
271 return cursor.text[--cursor.index];
274 // Move to the previous sibling, going up the tree as necessary.
275 while (true) {
276 // Try to move to the previous sibling.
277 var siblingNode = null;
278 for (var node = cursor.node.previousSibling;
279 node != null;
280 node = node.previousSibling) {
281 if (TraverseUtil.isSkipped(node)) {
282 nodesCrossed.push(node);
283 continue;
285 if (TraverseUtil.isVisible(node)) {
286 siblingNode = node;
287 break;
290 if (siblingNode) {
291 cursor.node = siblingNode;
292 cursor.text = TraverseUtil.getNodeText(siblingNode);
293 if (cursor.text.length)
294 cursor.index = cursor.text.length;
295 else
296 cursor.index = cursor.node.childNodes.length;
297 if (cursor.node.constructor != Text)
298 nodesCrossed.push(cursor.node);
299 break;
302 // Otherwise, move to the parent.
303 if (cursor.node.parentNode &&
304 cursor.node.parentNode.constructor != HTMLBodyElement) {
305 cursor.node = cursor.node.parentNode;
306 cursor.text = null;
307 cursor.index = 0;
308 } else {
309 return null;
316 * Finds the next character, starting from endCursor. Upon exit, startCursor
317 * and endCursor will surround the next character. If skipWhitespace is
318 * true, will skip until a real character is found. Otherwise, it will
319 * attempt to select all of the whitespace between the initial position
320 * of endCursor and the next non-whitespace character.
321 * @param {Cursor} startCursor On exit, points to the position before
322 * the char.
323 * @param {Cursor} endCursor The position to start searching for the next
324 * char. On exit, will point to the position past the char.
325 * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
326 * initial and final cursor position will be pushed onto this array.
327 * @param {boolean} skipWhitespace If true, will keep scanning until a
328 * non-whitespace character is found.
329 * @return {?string} The next char, or null if the bottom of the
330 * document has been reached.
332 TraverseUtil.getNextChar = function(
333 startCursor, endCursor, nodesCrossed, skipWhitespace) {
335 // Save the starting position and get the first character.
336 startCursor.copyFrom(endCursor);
337 var c = TraverseUtil.forwardsChar(endCursor, nodesCrossed);
338 if (c == null)
339 return null;
341 // Keep track of whether the first character was whitespace.
342 var initialWhitespace = TraverseUtil.isWhitespace(c);
344 // Keep scanning until we find a non-whitespace or non-skipped character.
345 while ((TraverseUtil.isWhitespace(c)) ||
346 (TraverseUtil.isSkipped(endCursor.node))) {
347 c = TraverseUtil.forwardsChar(endCursor, nodesCrossed);
348 if (c == null)
349 return null;
351 if (skipWhitespace || !initialWhitespace) {
352 // If skipWhitepace is true, or if the first character we encountered
353 // was not whitespace, return that non-whitespace character.
354 startCursor.copyFrom(endCursor);
355 startCursor.index--;
356 return c;
358 else {
359 for (var i = 0; i < nodesCrossed.length; i++) {
360 if (TraverseUtil.isSkipped(nodesCrossed[i])) {
361 // We need to make sure that startCursor and endCursor aren't
362 // surrounding a skippable node.
363 endCursor.index--;
364 startCursor.copyFrom(endCursor);
365 startCursor.index--;
366 return ' ';
369 // Otherwise, return all of the whitespace before that last character.
370 endCursor.index--;
371 return ' ';
376 * Finds the previous character, starting from startCursor. Upon exit,
377 * startCursor and endCursor will surround the previous character.
378 * If skipWhitespace is true, will skip until a real character is found.
379 * Otherwise, it will attempt to select all of the whitespace between
380 * the initial position of endCursor and the next non-whitespace character.
381 * @param {Cursor} startCursor The position to start searching for the
382 * char. On exit, will point to the position before the char.
383 * @param {Cursor} endCursor The position to start searching for the next
384 * char. On exit, will point to the position past the char.
385 * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
386 * initial and final cursor position will be pushed onto this array.
387 * @param {boolean} skipWhitespace If true, will keep scanning until a
388 * non-whitespace character is found.
389 * @return {?string} The previous char, or null if the top of the
390 * document has been reached.
392 TraverseUtil.getPreviousChar = function(
393 startCursor, endCursor, nodesCrossed, skipWhitespace) {
395 // Save the starting position and get the first character.
396 endCursor.copyFrom(startCursor);
397 var c = TraverseUtil.backwardsChar(startCursor, nodesCrossed);
398 if (c == null)
399 return null;
401 // Keep track of whether the first character was whitespace.
402 var initialWhitespace = TraverseUtil.isWhitespace(c);
404 // Keep scanning until we find a non-whitespace or non-skipped character.
405 while ((TraverseUtil.isWhitespace(c)) ||
406 (TraverseUtil.isSkipped(startCursor.node))) {
407 c = TraverseUtil.backwardsChar(startCursor, nodesCrossed);
408 if (c == null)
409 return null;
411 if (skipWhitespace || !initialWhitespace) {
412 // If skipWhitepace is true, or if the first character we encountered
413 // was not whitespace, return that non-whitespace character.
414 endCursor.copyFrom(startCursor);
415 endCursor.index++;
416 return c;
417 } else {
418 for (var i = 0; i < nodesCrossed.length; i++) {
419 if (TraverseUtil.isSkipped(nodesCrossed[i])) {
420 startCursor.index++;
421 endCursor.copyFrom(startCursor);
422 endCursor.index++;
423 return ' ';
426 // Otherwise, return all of the whitespace before that last character.
427 startCursor.index++;
428 return ' ';
433 * Finds the next word, starting from endCursor. Upon exit, startCursor
434 * and endCursor will surround the next word. A word is defined to be
435 * a string of 1 or more non-whitespace characters in the same DOM node.
436 * @param {Cursor} startCursor On exit, will point to the beginning of the
437 * word returned.
438 * @param {Cursor} endCursor The position to start searching for the next
439 * word. On exit, will point to the end of the word returned.
440 * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
441 * initial and final cursor position will be pushed onto this array.
442 * @return {?string} The next word, or null if the bottom of the
443 * document has been reached.
445 TraverseUtil.getNextWord = function(startCursor, endCursor,
446 nodesCrossed) {
448 // Find the first non-whitespace or non-skipped character.
449 var cursor = endCursor.clone();
450 var c = TraverseUtil.forwardsChar(cursor, nodesCrossed);
451 if (c == null)
452 return null;
453 while ((TraverseUtil.isWhitespace(c)) ||
454 (TraverseUtil.isSkipped(cursor.node))) {
455 c = TraverseUtil.forwardsChar(cursor, nodesCrossed);
456 if (c == null)
457 return null;
460 // Set startCursor to the position immediately before the first
461 // character in our word. It's safe to decrement |index| because
462 // forwardsChar guarantees that the cursor will be immediately to the
463 // right of the returned character on exit.
464 startCursor.copyFrom(cursor);
465 startCursor.index--;
467 // Keep building up our word until we reach a whitespace character or
468 // would cross a tag. Don't actually return any tags crossed, because this
469 // word goes up until the tag boundary but not past it.
470 endCursor.copyFrom(cursor);
471 var word = c;
472 var newNodesCrossed = [];
473 c = TraverseUtil.forwardsChar(cursor, newNodesCrossed);
474 if (c == null) {
475 return word;
477 while (!TraverseUtil.isWhitespace(c) &&
478 newNodesCrossed.length == 0) {
479 word += c;
480 endCursor.copyFrom(cursor);
481 c = TraverseUtil.forwardsChar(cursor, newNodesCrossed);
482 if (c == null) {
483 return word;
486 return word;
490 * Finds the previous word, starting from startCursor. Upon exit, startCursor
491 * and endCursor will surround the previous word. A word is defined to be
492 * a string of 1 or more non-whitespace characters in the same DOM node.
493 * @param {Cursor} startCursor The position to start searching for the
494 * previous word. On exit, will point to the beginning of the
495 * word returned.
496 * @param {Cursor} endCursor On exit, will point to the end of the
497 * word returned.
498 * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
499 * initial and final cursor position will be pushed onto this array.
500 * @return {?string} The previous word, or null if the bottom of the
501 * document has been reached.
503 TraverseUtil.getPreviousWord = function(startCursor, endCursor,
504 nodesCrossed) {
505 // Find the first non-whitespace or non-skipped character.
506 var cursor = startCursor.clone();
507 var c = TraverseUtil.backwardsChar(cursor, nodesCrossed);
508 if (c == null)
509 return null;
510 while ((TraverseUtil.isWhitespace(c) ||
511 (TraverseUtil.isSkipped(cursor.node)))) {
512 c = TraverseUtil.backwardsChar(cursor, nodesCrossed);
513 if (c == null)
514 return null;
517 // Set endCursor to the position immediately after the first
518 // character we've found (the last character of the word, since we're
519 // searching backwards).
520 endCursor.copyFrom(cursor);
521 endCursor.index++;
523 // Keep building up our word until we reach a whitespace character or
524 // would cross a tag. Don't actually return any tags crossed, because this
525 // word goes up until the tag boundary but not past it.
526 startCursor.copyFrom(cursor);
527 var word = c;
528 var newNodesCrossed = [];
529 c = TraverseUtil.backwardsChar(cursor, newNodesCrossed);
530 if (c == null)
531 return word;
532 while (!TraverseUtil.isWhitespace(c) &&
533 newNodesCrossed.length == 0) {
534 word = c + word;
535 startCursor.copyFrom(cursor);
536 c = TraverseUtil.backwardsChar(cursor, newNodesCrossed);
537 if (c == null)
538 return word;
541 return word;
545 * Finds the next sentence, starting from endCursor. Upon exit,
546 * startCursor and endCursor will surround the next sentence.
548 * @param {Cursor} startCursor On exit, marks the beginning of the sentence.
549 * @param {Cursor} endCursor The position to start searching for the next
550 * sentence. On exit, will point to the end of the returned string.
551 * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
552 * initial and final cursor position will be pushed onto this array.
553 * @param {Object} breakTags Associative array of tags that should break
554 * the sentence.
555 * @return {?string} The next sentence, or null if the bottom of the
556 * document has been reached.
558 TraverseUtil.getNextSentence = function(
559 startCursor, endCursor, nodesCrossed, breakTags) {
560 return TraverseUtil.getNextString(
561 startCursor, endCursor, nodesCrossed,
562 function(str, word, nodes) {
563 if (str.substr(-1) == '.')
564 return true;
565 for (var i = 0; i < nodes.length; i++) {
566 if (TraverseUtil.isSkipped(nodes[i])) {
567 return true;
569 var style = window.getComputedStyle(nodes[i], null);
570 if (style && (style.display != 'inline' ||
571 breakTags[nodes[i].tagName])) {
572 return true;
575 return false;
580 * Finds the previous sentence, starting from startCursor. Upon exit,
581 * startCursor and endCursor will surround the previous sentence.
583 * @param {Cursor} startCursor The position to start searching for the next
584 * sentence. On exit, will point to the start of the returned string.
585 * @param {Cursor} endCursor On exit, the end of the returned string.
586 * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
587 * initial and final cursor position will be pushed onto this array.
588 * @param {Object} breakTags Associative array of tags that should break
589 * the sentence.
590 * @return {?string} The previous sentence, or null if the bottom of the
591 * document has been reached.
593 TraverseUtil.getPreviousSentence = function(
594 startCursor, endCursor, nodesCrossed, breakTags) {
595 return TraverseUtil.getPreviousString(
596 startCursor, endCursor, nodesCrossed,
597 function(str, word, nodes) {
598 if (word.substr(-1) == '.')
599 return true;
600 for (var i = 0; i < nodes.length; i++) {
601 if (TraverseUtil.isSkipped(nodes[i])) {
602 return true;
604 var style = window.getComputedStyle(nodes[i], null);
605 if (style && (style.display != 'inline' ||
606 breakTags[nodes[i].tagName])) {
607 return true;
610 return false;
615 * Finds the next line, starting from endCursor. Upon exit,
616 * startCursor and endCursor will surround the next line.
618 * @param {Cursor} startCursor On exit, marks the beginning of the line.
619 * @param {Cursor} endCursor The position to start searching for the next
620 * line. On exit, will point to the end of the returned string.
621 * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
622 * initial and final cursor position will be pushed onto this array.
623 * @param {number} lineLength The maximum number of characters in a line.
624 * @param {Object} breakTags Associative array of tags that should break
625 * the line.
626 * @return {?string} The next line, or null if the bottom of the
627 * document has been reached.
629 TraverseUtil.getNextLine = function(
630 startCursor, endCursor, nodesCrossed, lineLength, breakTags) {
631 return TraverseUtil.getNextString(
632 startCursor, endCursor, nodesCrossed,
633 function(str, word, nodes) {
634 if (str.length + word.length + 1 > lineLength)
635 return true;
636 for (var i = 0; i < nodes.length; i++) {
637 if (TraverseUtil.isSkipped(nodes[i])) {
638 return true;
640 var style = window.getComputedStyle(nodes[i], null);
641 if (style && (style.display != 'inline' ||
642 breakTags[nodes[i].tagName])) {
643 return true;
646 return false;
651 * Finds the previous line, starting from startCursor. Upon exit,
652 * startCursor and endCursor will surround the previous line.
654 * @param {Cursor} startCursor The position to start searching for the next
655 * line. On exit, will point to the start of the returned string.
656 * @param {Cursor} endCursor On exit, the end of the returned string.
657 * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
658 * initial and final cursor position will be pushed onto this array.
659 * @param {number} lineLength The maximum number of characters in a line.
660 * @param {Object} breakTags Associative array of tags that should break
661 * the sentence.
662 * @return {?string} The previous line, or null if the bottom of the
663 * document has been reached.
665 TraverseUtil.getPreviousLine = function(
666 startCursor, endCursor, nodesCrossed, lineLength, breakTags) {
667 return TraverseUtil.getPreviousString(
668 startCursor, endCursor, nodesCrossed,
669 function(str, word, nodes) {
670 if (str.length + word.length + 1 > lineLength)
671 return true;
672 for (var i = 0; i < nodes.length; i++) {
673 if (TraverseUtil.isSkipped(nodes[i])) {
674 return true;
676 var style = window.getComputedStyle(nodes[i], null);
677 if (style && (style.display != 'inline' ||
678 breakTags[nodes[i].tagName])) {
679 return true;
682 return false;
687 * Finds the next paragraph, starting from endCursor. Upon exit,
688 * startCursor and endCursor will surround the next paragraph.
690 * @param {Cursor} startCursor On exit, marks the beginning of the paragraph.
691 * @param {Cursor} endCursor The position to start searching for the next
692 * paragraph. On exit, will point to the end of the returned string.
693 * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
694 * initial and final cursor position will be pushed onto this array.
695 * @return {?string} The next paragraph, or null if the bottom of the
696 * document has been reached.
698 TraverseUtil.getNextParagraph = function(startCursor, endCursor,
699 nodesCrossed) {
700 return TraverseUtil.getNextString(
701 startCursor, endCursor, nodesCrossed,
702 function(str, word, nodes) {
703 for (var i = 0; i < nodes.length; i++) {
704 if (TraverseUtil.isSkipped(nodes[i])) {
705 return true;
707 var style = window.getComputedStyle(nodes[i], null);
708 if (style && style.display != 'inline') {
709 return true;
712 return false;
717 * Finds the previous paragraph, starting from startCursor. Upon exit,
718 * startCursor and endCursor will surround the previous paragraph.
720 * @param {Cursor} startCursor The position to start searching for the next
721 * paragraph. On exit, will point to the start of the returned string.
722 * @param {Cursor} endCursor On exit, the end of the returned string.
723 * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
724 * initial and final cursor position will be pushed onto this array.
725 * @return {?string} The previous paragraph, or null if the bottom of the
726 * document has been reached.
728 TraverseUtil.getPreviousParagraph = function(
729 startCursor, endCursor, nodesCrossed) {
730 return TraverseUtil.getPreviousString(
731 startCursor, endCursor, nodesCrossed,
732 function(str, word, nodes) {
733 for (var i = 0; i < nodes.length; i++) {
734 if (TraverseUtil.isSkipped(nodes[i])) {
735 return true;
737 var style = window.getComputedStyle(nodes[i], null);
738 if (style && style.display != 'inline') {
739 return true;
742 return false;
747 * Customizable function to return the next string of words in the DOM, based
748 * on provided functions to decide when to break one string and start
749 * the next. This can be used to get the next sentence, line, paragraph,
750 * or potentially other granularities.
752 * Finds the next contiguous string, starting from endCursor. Upon exit,
753 * startCursor and endCursor will surround the next string.
755 * The breakBefore function takes three parameters, and
756 * should return true if the string should be broken before the proposed
757 * next word:
758 * str The string so far.
759 * word The next word to be added.
760 * nodesCrossed The nodes crossed in reaching this next word.
762 * @param {Cursor} startCursor On exit, will point to the beginning of the
763 * next string.
764 * @param {Cursor} endCursor The position to start searching for the next
765 * string. On exit, will point to the end of the returned string.
766 * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
767 * initial and final cursor position will be pushed onto this array.
768 * @param {function(string, string, Array<string>)} breakBefore
769 * Function that takes the string so far, next word to be added, and
770 * nodes crossed, and returns true if the string should be ended before
771 * adding this word.
772 * @return {?string} The next string, or null if the bottom of the
773 * document has been reached.
775 TraverseUtil.getNextString = function(
776 startCursor, endCursor, nodesCrossed, breakBefore) {
777 // Get the first word and set the start cursor to the start of the
778 // first word.
779 var wordStartCursor = endCursor.clone();
780 var wordEndCursor = endCursor.clone();
781 var newNodesCrossed = [];
782 var str = '';
783 var word = TraverseUtil.getNextWord(
784 wordStartCursor, wordEndCursor, newNodesCrossed);
785 if (word == null)
786 return null;
787 startCursor.copyFrom(wordStartCursor);
789 // Always add the first word when the string is empty, and then keep
790 // adding more words as long as breakBefore returns false
791 while (!str || !breakBefore(str, word, newNodesCrossed)) {
792 // Append this word, set the end cursor to the end of this word, and
793 // update the returned list of nodes crossed to include ones we crossed
794 // in reaching this word.
795 if (str)
796 str += ' ';
797 str += word;
798 nodesCrossed = nodesCrossed.concat(newNodesCrossed);
799 endCursor.copyFrom(wordEndCursor);
801 // Get the next word and go back to the top of the loop.
802 newNodesCrossed = [];
803 word = TraverseUtil.getNextWord(
804 wordStartCursor, wordEndCursor, newNodesCrossed);
805 if (word == null)
806 return str;
809 return str;
813 * Customizable function to return the previous string of words in the DOM,
814 * based on provided functions to decide when to break one string and start
815 * the next. See getNextString, above, for more details.
817 * Finds the previous contiguous string, starting from startCursor. Upon exit,
818 * startCursor and endCursor will surround the next string.
820 * @param {Cursor} startCursor The position to start searching for the
821 * previous string. On exit, will point to the beginning of the
822 * string returned.
823 * @param {Cursor} endCursor On exit, will point to the end of the
824 * string returned.
825 * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
826 * initial and final cursor position will be pushed onto this array.
827 * @param {function(string, string, Array<string>)} breakBefore
828 * Function that takes the string so far, the word to be added, and
829 * nodes crossed, and returns true if the string should be ended before
830 * adding this word.
831 * @return {?string} The next string, or null if the top of the
832 * document has been reached.
834 TraverseUtil.getPreviousString = function(
835 startCursor, endCursor, nodesCrossed, breakBefore) {
836 // Get the first word and set the end cursor to the end of the
837 // first word.
838 var wordStartCursor = startCursor.clone();
839 var wordEndCursor = startCursor.clone();
840 var newNodesCrossed = [];
841 var str = '';
842 var word = TraverseUtil.getPreviousWord(
843 wordStartCursor, wordEndCursor, newNodesCrossed);
844 if (word == null)
845 return null;
846 endCursor.copyFrom(wordEndCursor);
848 // Always add the first word when the string is empty, and then keep
849 // adding more words as long as breakBefore returns false
850 while (!str || !breakBefore(str, word, newNodesCrossed)) {
851 // Prepend this word, set the start cursor to the start of this word, and
852 // update the returned list of nodes crossed to include ones we crossed
853 // in reaching this word.
854 if (str)
855 str = ' ' + str;
856 str = word + str;
857 nodesCrossed = nodesCrossed.concat(newNodesCrossed);
858 startCursor.copyFrom(wordStartCursor);
860 // Get the previous word and go back to the top of the loop.
861 newNodesCrossed = [];
862 word = TraverseUtil.getPreviousWord(
863 wordStartCursor, wordEndCursor, newNodesCrossed);
864 if (word == null)
865 return str;
868 return str;