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. */
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
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
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.
27 Cursor = function(node
, index
, text
) {
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
);
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
;
51 * Utility functions for stateless DOM traversal.
54 TraverseUtil = function() {};
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
) {
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';
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
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
);
121 * Use the computed CSS style to figure out if this DOM node is currently
123 * @param {Node} node A HTML DOM node.
124 * @return {boolean} Whether or not the html node is visible.
126 TraverseUtil
.isVisible = function(node
) {
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') {
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
) {
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
);
169 if (TraverseUtil
.isVisible(node
)) {
176 cursor
.node
= childNode
;
178 cursor
.text
= TraverseUtil
.getNodeText(cursor
.node
);
179 if (cursor
.node
.constructor != Text
) {
180 nodesCrossed
.push(cursor
.node
);
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
;
195 node
= node
.nextSibling
) {
196 if (TraverseUtil
.isSkipped(node
)) {
197 nodesCrossed
.push(node
);
200 if (TraverseUtil
.isVisible(node
)) {
206 cursor
.node
= siblingNode
;
207 cursor
.text
= TraverseUtil
.getNodeText(siblingNode
);
210 if (cursor
.node
.constructor != Text
) {
211 nodesCrossed
.push(cursor
.node
);
217 // Otherwise, move to the parent.
218 if (cursor
.node
.parentNode
&&
219 cursor
.node
.parentNode
.constructor != HTMLBodyElement
) {
220 cursor
.node
= cursor
.node
.parentNode
;
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
) {
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
);
251 if (TraverseUtil
.isVisible(node
)) {
258 cursor
.node
= childNode
;
259 cursor
.text
= TraverseUtil
.getNodeText(cursor
.node
);
260 if (cursor
.text
.length
)
261 cursor
.index
= cursor
.text
.length
;
263 cursor
.index
= cursor
.node
.childNodes
.length
;
264 if (cursor
.node
.constructor != Text
)
265 nodesCrossed
.push(cursor
.node
);
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.
276 // Try to move to the previous sibling.
277 var siblingNode
= null;
278 for (var node
= cursor
.node
.previousSibling
;
280 node
= node
.previousSibling
) {
281 if (TraverseUtil
.isSkipped(node
)) {
282 nodesCrossed
.push(node
);
285 if (TraverseUtil
.isVisible(node
)) {
291 cursor
.node
= siblingNode
;
292 cursor
.text
= TraverseUtil
.getNodeText(siblingNode
);
293 if (cursor
.text
.length
)
294 cursor
.index
= cursor
.text
.length
;
296 cursor
.index
= cursor
.node
.childNodes
.length
;
297 if (cursor
.node
.constructor != Text
)
298 nodesCrossed
.push(cursor
.node
);
302 // Otherwise, move to the parent.
303 if (cursor
.node
.parentNode
&&
304 cursor
.node
.parentNode
.constructor != HTMLBodyElement
) {
305 cursor
.node
= cursor
.node
.parentNode
;
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
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
);
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
);
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
);
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.
364 startCursor
.copyFrom(endCursor
);
369 // Otherwise, return all of the whitespace before that last character.
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
);
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
);
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
);
418 for (var i
= 0; i
< nodesCrossed
.length
; i
++) {
419 if (TraverseUtil
.isSkipped(nodesCrossed
[i
])) {
421 endCursor
.copyFrom(startCursor
);
426 // Otherwise, return all of the whitespace before that last character.
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
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
,
448 // Find the first non-whitespace or non-skipped character.
449 var cursor
= endCursor
.clone();
450 var c
= TraverseUtil
.forwardsChar(cursor
, nodesCrossed
);
453 while ((TraverseUtil
.isWhitespace(c
)) ||
454 (TraverseUtil
.isSkipped(cursor
.node
))) {
455 c
= TraverseUtil
.forwardsChar(cursor
, nodesCrossed
);
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
);
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
);
472 var newNodesCrossed
= [];
473 c
= TraverseUtil
.forwardsChar(cursor
, newNodesCrossed
);
477 while (!TraverseUtil
.isWhitespace(c
) &&
478 newNodesCrossed
.length
== 0) {
480 endCursor
.copyFrom(cursor
);
481 c
= TraverseUtil
.forwardsChar(cursor
, newNodesCrossed
);
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
496 * @param {Cursor} endCursor On exit, will point to the end of the
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
,
505 // Find the first non-whitespace or non-skipped character.
506 var cursor
= startCursor
.clone();
507 var c
= TraverseUtil
.backwardsChar(cursor
, nodesCrossed
);
510 while ((TraverseUtil
.isWhitespace(c
) ||
511 (TraverseUtil
.isSkipped(cursor
.node
)))) {
512 c
= TraverseUtil
.backwardsChar(cursor
, nodesCrossed
);
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
);
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
);
528 var newNodesCrossed
= [];
529 c
= TraverseUtil
.backwardsChar(cursor
, newNodesCrossed
);
532 while (!TraverseUtil
.isWhitespace(c
) &&
533 newNodesCrossed
.length
== 0) {
535 startCursor
.copyFrom(cursor
);
536 c
= TraverseUtil
.backwardsChar(cursor
, newNodesCrossed
);
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
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) == '.')
565 for (var i
= 0; i
< nodes
.length
; i
++) {
566 if (TraverseUtil
.isSkipped(nodes
[i
])) {
569 var style
= window
.getComputedStyle(nodes
[i
], null);
570 if (style
&& (style
.display
!= 'inline' ||
571 breakTags
[nodes
[i
].tagName
])) {
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
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) == '.')
600 for (var i
= 0; i
< nodes
.length
; i
++) {
601 if (TraverseUtil
.isSkipped(nodes
[i
])) {
604 var style
= window
.getComputedStyle(nodes
[i
], null);
605 if (style
&& (style
.display
!= 'inline' ||
606 breakTags
[nodes
[i
].tagName
])) {
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
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
)
636 for (var i
= 0; i
< nodes
.length
; i
++) {
637 if (TraverseUtil
.isSkipped(nodes
[i
])) {
640 var style
= window
.getComputedStyle(nodes
[i
], null);
641 if (style
&& (style
.display
!= 'inline' ||
642 breakTags
[nodes
[i
].tagName
])) {
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
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
)
672 for (var i
= 0; i
< nodes
.length
; i
++) {
673 if (TraverseUtil
.isSkipped(nodes
[i
])) {
676 var style
= window
.getComputedStyle(nodes
[i
], null);
677 if (style
&& (style
.display
!= 'inline' ||
678 breakTags
[nodes
[i
].tagName
])) {
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
,
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
])) {
707 var style
= window
.getComputedStyle(nodes
[i
], null);
708 if (style
&& style
.display
!= 'inline') {
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
])) {
737 var style
= window
.getComputedStyle(nodes
[i
], null);
738 if (style
&& style
.display
!= 'inline') {
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
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
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
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
779 var wordStartCursor
= endCursor
.clone();
780 var wordEndCursor
= endCursor
.clone();
781 var newNodesCrossed
= [];
783 var word
= TraverseUtil
.getNextWord(
784 wordStartCursor
, wordEndCursor
, newNodesCrossed
);
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.
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
);
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
823 * @param {Cursor} endCursor On exit, will point to the end of the
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
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
838 var wordStartCursor
= startCursor
.clone();
839 var wordEndCursor
= startCursor
.clone();
840 var newNodesCrossed
= [];
842 var word
= TraverseUtil
.getPreviousWord(
843 wordStartCursor
, wordEndCursor
, newNodesCrossed
);
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.
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
);