Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(25)

Side by Side Diff: chrome/browser/resources/access_chromevox/common/traverse_util.js

Issue 6254007: Adding ChromeVox as a component extensions (enabled only for ChromeOS, for no... (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 9 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
Property Changes:
Added: svn:executable
+ *
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 // Copyright (c) 2011 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.
4
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.
10 */
11
12 /**
13 * A class to represent a cursor location in the document,
14 * like the start position or end position of a selection range.
15 *
16 * Later this may be extended to support "virtual text" for an object,
17 * like the ALT text for an image.
18 *
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
26 */
27 function Cursor(node, index, text) {
28 this.node = node;
29 this.index = index;
30 this.text = text;
31 }
32
33 /**
34 * @return {Cursor} A new cursor pointing to the same location.
35 */
36 Cursor.prototype.clone = function() {
37 return new Cursor(this.node, this.index, this.text);
38 };
39
40 /**
41 * Modify this cursor to point to the location that another cursor points to.
42 * @param {Cursor} otherCursor The cursor to copy from.
43 */
44 Cursor.prototype.copyFrom = function(otherCursor) {
45 this.node = otherCursor.node;
46 this.index = otherCursor.index;
47 this.text = otherCursor.text;
48 };
49
50 goog.provide('cvox.TraverseUtil');
51
52
53 /**
54 * Utility functions for stateless DOM traversal.
55 * @constructor
56 */
57 cvox.TraverseUtil = function() {};
58
59 /**
60 * The name of the class that indicates an element and descendant elements
61 * should not be traversed.
62 * @type {string}
63 * @const
64 */
65 cvox.TraverseUtil.SKIP_CLASS = 'Axs_Chrome_Skip';
66
67 /**
68 * Gets the text representation of a node. This allows us to substitute
69 * alt text, names, or titles for html elements that provide them.
70 * @param {Node} node A DOM node.
71 * @return {string} A text string representation of the node.
72 */
73 cvox.TraverseUtil.getNodeText = function(node) {
74 if (node.constructor == Text) {
75 return node.data;
76 } else {
77 return '';
78 }
79 };
80
81 /**
82 * Return true if a node should be treated as a leaf node, because
83 * its children are properties of the object that shouldn't be traversed.
84 *
85 * TODO(dmazzoni): replace this with a predicate that detects nodes with
86 * ARIA roles and other objects that have their own description.
87 * For now we just detect a couple of common cases.
88 *
89 * @param {Node} node A DOM node.
90 * @return {boolean} True if the node should be treated as a leaf node.
91 */
92 cvox.TraverseUtil.treatAsLeafNode = function(node) {
93 return node.nodeName == 'SELECT' || node.nodeName == 'OBJECT';
94 };
95
96 /**
97 * Return true only if a single character is whitespace.
98 * From https://developer.mozilla.org/en/Whitespace_in_the_DOM,
99 * whitespace is defined as one of the characters
100 * "\t" TAB \u0009
101 * "\n" LF \u000A
102 * "\r" CR \u000D
103 * " " SPC \u0020.
104 *
105 * @param {string} c A string containing a single character.
106 * @return {boolean} True if the character is whitespace, otherwise false.
107 */
108 cvox.TraverseUtil.isWhitespace = function(c) {
109 return (c == ' ' || c == '\n' || c == '\r' || c == '\t');
110 };
111
112 /**
113 * Set the selection to the range between the given start and end cursors.
114 * @param {Cursor} start The desired start of the selection.
115 * @param {Cursor} end The desired end of the selection.
116 * @return {Selection} the selection object.
117 */
118 cvox.TraverseUtil.setSelection = function(start, end) {
119 var sel = window.getSelection();
120 sel.removeAllRanges();
121 var range = document.createRange();
122 range.setStart(start.node, start.index);
123 range.setEnd(end.node, end.index);
124 sel.addRange(range);
125
126 return sel;
127 };
128
129 /**
130 * Use the computed CSS style to figure out if this DOM node is currently
131 * visible.
132 * @param {Node} node A HTML DOM node.
133 * @return {boolean} Whether or not the html node is visible.
134 */
135 cvox.TraverseUtil.isVisible = function(node) {
136 if (!node.style)
137 return true;
138 var style = window.getComputedStyle(/** @type {Element} */(node), null);
139 return (!!style && style.display != 'none' && style.visibility != 'hidden');
140 };
141
142 /**
143 * Use the class name to figure out if this DOM node should be traversed.
144 * @param {Node} node A HTML DOM node.
145 * @return {boolean} Whether or not the html node should be traversed.
146 */
147 cvox.TraverseUtil.isSkipped = function(node) {
148 // Any node that is a descendant of a node with the blacklisted class
149 // will not be traversed.
150 if (cvox.DomUtil.isDescendantOf(node, null,
151 cvox.TraverseUtil.SKIP_CLASS)) {
152 return true;
153 }
154 return false;
155 };
156
157 /**
158 * Moves the cursor forwards until it has crossed exactly one character.
159 * @param {Cursor} cursor The cursor location where the search should start.
160 * On exit, the cursor will be immediately to the right of the
161 * character returned.
162 * @param {Array.<Node>} nodesCrossed Any HTML nodes crossed between the
163 * initial and final cursor position will be pushed onto this array.
164 * @return {?string} The character found, or null if the bottom of the
165 * document has been reached.
166 */
167 cvox.TraverseUtil.forwardsChar = function(cursor, nodesCrossed) {
168 while (true) {
169 // Move down until we get to a leaf node.
170 if (cursor.node.constructor != Text)
171 nodesCrossed.push(cursor.node);
172 var childNode = null;
173 if (!cvox.TraverseUtil.treatAsLeafNode(cursor.node)) {
174 for (var i = 0; i < cursor.node.childNodes.length; i++) {
175 var node = cursor.node.childNodes[i];
176 if (cvox.TraverseUtil.isSkipped(node)) {
177 nodesCrossed.push(node);
178 continue;
179 }
180 if (cvox.TraverseUtil.isVisible(node)) {
181 childNode = node;
182 break;
183 }
184 }
185 }
186 if (childNode) {
187 cursor.node = childNode;
188 cursor.index = 0;
189 cursor.text = cvox.TraverseUtil.getNodeText(cursor.node);
190 if (cursor.node.constructor != Text)
191 nodesCrossed.push(cursor.node);
192 continue;
193 }
194
195 // Return the next character from this leaf node.
196 if (cursor.index < cursor.text.length)
197 return cursor.text[cursor.index++];
198
199 // Move to the next sibling, going up the tree as necessary.
200 while (cursor.node != null) {
201 if (cursor.node.constructor != Text)
202 nodesCrossed.push(cursor.node);
203
204 // Try to move to the next sibling.
205 var siblingNode = null;
206 for (var node = cursor.node.nextSibling;
207 node != null;
208 node = node.nextSibling) {
209 if (cvox.TraverseUtil.isSkipped(node)) {
210 nodesCrossed.push(node);
211 continue;
212 }
213 if (cvox.TraverseUtil.isVisible(node)) {
214 siblingNode = node;
215 break;
216 }
217 }
218 if (siblingNode) {
219 cursor.node = siblingNode;
220 cursor.text = cvox.TraverseUtil.getNodeText(siblingNode);
221 cursor.index = 0;
222 break;
223 }
224
225 // Otherwise, move to the parent.
226 if (cursor.node.parentNode &&
227 cursor.node.parentNode.constructor != HTMLBodyElement) {
228 cursor.node = cursor.node.parentNode;
229 cursor.text = null;
230 cursor.index = 0;
231 } else {
232 return null;
233 }
234 }
235 }
236 };
237
238 /**
239 * Moves the cursor backwards until it has crossed exactly one character.
240 * @param {Cursor} cursor The cursor location where the search should start.
241 * On exit, the cursor will be immediately to the left of the
242 * character returned.
243 * @param {Array.<Node>} nodesCrossed Any HTML nodes crossed between the
244 * initial and final cursor position will be pushed onto this array.
245 * @return {?string} The previous character, or null if the top of the
246 * document has been reached.
247 */
248 cvox.TraverseUtil.backwardsChar = function(cursor, nodesCrossed) {
249 while (true) {
250 // Move down until we get to a leaf node.
251 if (cursor.node.constructor != Text)
252 nodesCrossed.push(cursor.node);
253 var childNode = null;
254 if (!cvox.TraverseUtil.treatAsLeafNode(cursor.node)) {
255 for (var i = cursor.node.childNodes.length - 1; i >= 0; i--) {
256 var node = cursor.node.childNodes[i];
257 if (cvox.TraverseUtil.isSkipped(node)) {
258 nodesCrossed.push(node);
259 continue;
260 }
261 if (cvox.TraverseUtil.isVisible(node)) {
262 childNode = node;
263 break;
264 }
265 }
266 }
267 if (childNode) {
268 cursor.node = childNode;
269 cursor.text = cvox.TraverseUtil.getNodeText(cursor.node);
270 cursor.index = cursor.text.length;
271 if (cursor.node.constructor != Text)
272 nodesCrossed.push(cursor.node);
273 continue;
274 }
275
276 // Return the previous character from this leaf node.
277 if (cursor.index > 0)
278 return cursor.text[--cursor.index];
279
280 // Move to the previous sibling, going up the tree as necessary.
281 while (true) {
282 if (cursor.node.constructor != Text)
283 nodesCrossed.push(cursor.node);
284
285 // Try to move to the next sibling.
286 var siblingNode = null;
287 for (var node = cursor.node.previousSibling;
288 node != null;
289 node = node.previousSibling) {
290 if (cvox.TraverseUtil.isSkipped(node)) {
291 nodesCrossed.push(node);
292 continue;
293 }
294 if (cvox.TraverseUtil.isVisible(node)) {
295 siblingNode = node;
296 break;
297 }
298 }
299 if (siblingNode) {
300 cursor.node = siblingNode;
301 cursor.text = cvox.TraverseUtil.getNodeText(siblingNode);
302 cursor.index = cursor.text.length;
303 break;
304 }
305
306 // Otherwise, move to the parent.
307 if (cursor.node.parentNode &&
308 cursor.node.parentNode.constructor != HTMLBodyElement) {
309 cursor.node = cursor.node.parentNode;
310 cursor.text = null;
311 cursor.index = 0;
312 } else {
313 return null;
314 }
315 }
316 }
317 };
318
319 /**
320 * Finds the next character, starting from endCursor. Upon exit, startCursor
321 * and endCursor will surround the next character. If skipWhitespace is
322 * true, will skip until a real character is found. Otherwise, it will
323 * attempt to select all of the whitespace between the initial position
324 * of endCursor and the next non-whitespace character.
325 * @param {Cursor} startCursor On exit, points to the position before
326 * the char.
327 * @param {Cursor} endCursor The position to start searching for the next
328 * char. On exit, will point to the position past the char.
329 * @param {Array.<Node>} nodesCrossed Any HTML nodes crossed between the
330 * initial and final cursor position will be pushed onto this array.
331 * @param {boolean} skipWhitespace If true, will keep scanning until a
332 * non-whitespace character is found.
333 * @return {?string} The next char, or null if the bottom of the
334 * document has been reached.
335 */
336 cvox.TraverseUtil.getNextChar = function(
337 startCursor, endCursor, nodesCrossed, skipWhitespace) {
338
339 // Save the starting position and get the first character.
340 startCursor.copyFrom(endCursor);
341 var c = cvox.TraverseUtil.forwardsChar(endCursor, nodesCrossed);
342 if (c == null)
343 return null;
344
345 // Keep track of whether the first character was whitespace.
346 var initialWhitespace = cvox.TraverseUtil.isWhitespace(c);
347
348 // Keep scanning until we find a non-whitespace or non-skipped character.
349 while ((cvox.TraverseUtil.isWhitespace(c)) ||
350 (cvox.TraverseUtil.isSkipped(endCursor.node))) {
351 c = cvox.TraverseUtil.forwardsChar(endCursor, nodesCrossed);
352 if (c == null)
353 return null;
354 }
355 if (skipWhitespace || !initialWhitespace) {
356 // If skipWhitepace is true, or if the first character we encountered
357 // was not whitespace, return that non-whitespace character.
358 startCursor.copyFrom(endCursor);
359 startCursor.index--;
360 return c;
361 }
362 else {
363 for (var i = 0; i < nodesCrossed.length; i++) {
364 if (cvox.TraverseUtil.isSkipped(nodesCrossed[i])) {
365 // We need to make sure that startCursor and endCursor aren't
366 // surrounding a skippable node.
367 endCursor.index--;
368 startCursor.copyFrom(endCursor);
369 startCursor.index--;
370 return ' ';
371 }
372 }
373 // Otherwise, return all of the whitespace before that last character.
374 endCursor.index--;
375 return ' ';
376 }
377 };
378
379 /**
380 * Finds the previous character, starting from startCursor. Upon exit,
381 * startCursor and endCursor will surround the previous character.
382 * If skipWhitespace is true, will skip until a real character is found.
383 * Otherwise, it will attempt to select all of the whitespace between
384 * the initial position of endCursor and the next non-whitespace character.
385 * @param {Cursor} startCursor The position to start searching for the
386 * char. On exit, will point to the position before the char.
387 * @param {Cursor} endCursor The position to start searching for the next
388 * char. On exit, will point to the position past the char.
389 * @param {Array.<Node>} nodesCrossed Any HTML nodes crossed between the
390 * initial and final cursor position will be pushed onto this array.
391 * @param {boolean} skipWhitespace If true, will keep scanning until a
392 * non-whitespace character is found.
393 * @return {?string} The previous char, or null if the top of the
394 * document has been reached.
395 */
396 cvox.TraverseUtil.getPreviousChar = function(
397 startCursor, endCursor, nodesCrossed, skipWhitespace) {
398
399 // Save the starting position and get the first character.
400 endCursor.copyFrom(startCursor);
401 var c = cvox.TraverseUtil.backwardsChar(startCursor, nodesCrossed);
402 if (c == null)
403 return null;
404
405 // Keep track of whether the first character was whitespace.
406 var initialWhitespace = cvox.TraverseUtil.isWhitespace(c);
407
408 // Keep scanning until we find a non-whitespace or non-skipped character.
409 while ((cvox.TraverseUtil.isWhitespace(c)) ||
410 (cvox.TraverseUtil.isSkipped(startCursor.node))) {
411 c = cvox.TraverseUtil.backwardsChar(startCursor, nodesCrossed);
412 if (c == null)
413 return null;
414 }
415 if (skipWhitespace || !initialWhitespace) {
416 // If skipWhitepace is true, or if the first character we encountered
417 // was not whitespace, return that non-whitespace character.
418 endCursor.copyFrom(startCursor);
419 endCursor.index++;
420 return c;
421 } else {
422 for (var i = 0; i < nodesCrossed.length; i++) {
423 if (cvox.TraverseUtil.isSkipped(nodesCrossed[i])) {
424 startCursor.index++;
425 endCursor.copyFrom(startCursor);
426 endCursor.index++;
427 return ' ';
428 }
429 }
430 // Otherwise, return all of the whitespace before that last character.
431 startCursor.index++;
432 return ' ';
433 }
434 };
435
436 /**
437 * Finds the next word, starting from endCursor. Upon exit, startCursor
438 * and endCursor will surround the next word. A word is defined to be
439 * a string of 1 or more non-whitespace characters in the same DOM node.
440 * @param {Cursor} startCursor On exit, will point to the beginning of the
441 * word returned.
442 * @param {Cursor} endCursor The position to start searching for the next
443 * word. On exit, will point to the end of the word returned.
444 * @param {Array.<Node>} nodesCrossed Any HTML nodes crossed between the
445 * initial and final cursor position will be pushed onto this array.
446 * @return {?string} The next word, or null if the bottom of the
447 * document has been reached.
448 */
449 cvox.TraverseUtil.getNextWord = function(startCursor, endCursor,
450 nodesCrossed) {
451
452 // Find the first non-whitespace or non-skipped character.
453 var cursor = endCursor.clone();
454 var c = cvox.TraverseUtil.forwardsChar(cursor, nodesCrossed);
455 if (c == null)
456 return null;
457 while ((cvox.TraverseUtil.isWhitespace(c)) ||
458 (cvox.TraverseUtil.isSkipped(cursor.node))) {
459 c = cvox.TraverseUtil.forwardsChar(cursor, nodesCrossed);
460 if (c == null)
461 return null;
462 }
463
464 // Set startCursor to the position immediately before the first
465 // character in our word. It's safe to decrement |index| because
466 // forwardsChar guarantees that the cursor will be immediately to the
467 // right of the returned character on exit.
468 startCursor.copyFrom(cursor);
469 startCursor.index--;
470
471 // Keep building up our word until we reach a whitespace character or
472 // would cross a tag. Don't actually return any tags crossed, because this
473 // word goes up until the tag boundary but not past it.
474 endCursor.copyFrom(cursor);
475 var word = c;
476 var newNodesCrossed = [];
477 c = cvox.TraverseUtil.forwardsChar(cursor, newNodesCrossed);
478 if (c == null) {
479 return word;
480 }
481 while (!cvox.TraverseUtil.isWhitespace(c) &&
482 newNodesCrossed.length == 0) {
483 word += c;
484 endCursor.copyFrom(cursor);
485 c = cvox.TraverseUtil.forwardsChar(cursor, newNodesCrossed);
486 if (c == null) {
487 return word;
488 }
489 }
490 return word;
491 };
492
493 /**
494 * Finds the previous word, starting from startCursor. Upon exit, startCursor
495 * and endCursor will surround the previous word. A word is defined to be
496 * a string of 1 or more non-whitespace characters in the same DOM node.
497 * @param {Cursor} startCursor The position to start searching for the
498 * previous word. On exit, will point to the beginning of the
499 * word returned.
500 * @param {Cursor} endCursor On exit, will point to the end of the
501 * word returned.
502 * @param {Array.<Node>} nodesCrossed Any HTML nodes crossed between the
503 * initial and final cursor position will be pushed onto this array.
504 * @return {?string} The previous word, or null if the bottom of the
505 * document has been reached.
506 */
507 cvox.TraverseUtil.getPreviousWord = function(startCursor, endCursor,
508 nodesCrossed) {
509
510 // Find the first non-whitespace or non-skipped character.
511 var cursor = startCursor.clone();
512 var c = cvox.TraverseUtil.backwardsChar(cursor, nodesCrossed);
513 if (c == null)
514 return null;
515 while ((cvox.TraverseUtil.isWhitespace(c) ||
516 (cvox.TraverseUtil.isSkipped(cursor.node)))) {
517 c = cvox.TraverseUtil.backwardsChar(cursor, nodesCrossed);
518 if (c == null)
519 return null;
520 }
521
522 // Set endCursor to the position immediately after the first
523 // character we've found (the last character of the word, since we're
524 // searching backwards).
525 endCursor.copyFrom(cursor);
526 endCursor.index++;
527
528 // Keep building up our word until we reach a whitespace character or
529 // would cross a tag. Don't actually return any tags crossed, because this
530 // word goes up until the tag boundary but not past it.
531 startCursor.copyFrom(cursor);
532 var word = c;
533 var newNodesCrossed = [];
534 c = cvox.TraverseUtil.backwardsChar(cursor, newNodesCrossed);
535 if (c == null)
536 return word;
537 while (!cvox.TraverseUtil.isWhitespace(c) &&
538 newNodesCrossed.length == 0) {
539 word = c + word;
540 startCursor.copyFrom(cursor);
541 c = cvox.TraverseUtil.backwardsChar(cursor, newNodesCrossed);
542 if (c == null)
543 return word;
544 }
545
546 return word;
547 };
548
549 /**
550 * Finds the next sentence, starting from endCursor. Upon exit,
551 * startCursor and endCursor will surround the next sentence.
552 *
553 * @param {Cursor} startCursor On exit, marks the beginning of the sentence.
554 * @param {Cursor} endCursor The position to start searching for the next
555 * sentence. On exit, will point to the end of the returned string.
556 * @param {Array.<Node>} nodesCrossed Any HTML nodes crossed between the
557 * initial and final cursor position will be pushed onto this array.
558 * @param {Object} breakTags Associative array of tags that should break
559 * the sentence.
560 * @return {?string} The next sentence, or null if the bottom of the
561 * document has been reached.
562 */
563 cvox.TraverseUtil.getNextSentence = function(
564 startCursor, endCursor, nodesCrossed, breakTags) {
565 return cvox.TraverseUtil.getNextString(
566 startCursor, endCursor, nodesCrossed,
567 function(str, word, nodes) {
568 if (str.substr(-1) == '.')
569 return true;
570 for (var i = 0; i < nodes.length; i++) {
571 if (cvox.TraverseUtil.isSkipped(nodes[i])) {
572 return true;
573 }
574 var style = window.getComputedStyle(nodes[i], null);
575 if (style && (style.display != 'inline' ||
576 breakTags[nodes[i].tagName])) {
577 return true;
578 }
579 }
580 return false;
581 });
582 };
583
584 /**
585 * Finds the previous sentence, starting from startCursor. Upon exit,
586 * startCursor and endCursor will surround the previous sentence.
587 *
588 * @param {Cursor} startCursor The position to start searching for the next
589 * sentence. On exit, will point to the start of the returned string.
590 * @param {Cursor} endCursor On exit, the end of the returned string.
591 * @param {Array.<Node>} nodesCrossed Any HTML nodes crossed between the
592 * initial and final cursor position will be pushed onto this array.
593 * @param {Object} breakTags Associative array of tags that should break
594 * the sentence.
595 * @return {?string} The previous sentence, or null if the bottom of the
596 * document has been reached.
597 */
598 cvox.TraverseUtil.getPreviousSentence = function(
599 startCursor, endCursor, nodesCrossed, breakTags) {
600 return cvox.TraverseUtil.getPreviousString(
601 startCursor, endCursor, nodesCrossed,
602 function(str, word, nodes) {
603 if (word.substr(-1) == '.')
604 return true;
605 for (var i = 0; i < nodes.length; i++) {
606 if (cvox.TraverseUtil.isSkipped(nodes[i])) {
607 return true;
608 }
609 var style = window.getComputedStyle(nodes[i], null);
610 if (style && (style.display != 'inline' ||
611 breakTags[nodes[i].tagName])) {
612 return true;
613 }
614 }
615 return false;
616 });
617 };
618
619 /**
620 * Finds the next line, starting from endCursor. Upon exit,
621 * startCursor and endCursor will surround the next line.
622 *
623 * @param {Cursor} startCursor On exit, marks the beginning of the line.
624 * @param {Cursor} endCursor The position to start searching for the next
625 * line. On exit, will point to the end of the returned string.
626 * @param {Array.<Node>} nodesCrossed Any HTML nodes crossed between the
627 * initial and final cursor position will be pushed onto this array.
628 * @param {number} lineLength The maximum number of characters in a line.
629 * @param {Object} breakTags Associative array of tags that should break
630 * the line.
631 * @return {?string} The next line, or null if the bottom of the
632 * document has been reached.
633 */
634 cvox.TraverseUtil.getNextLine = function(
635 startCursor, endCursor, nodesCrossed, lineLength, breakTags) {
636 return cvox.TraverseUtil.getNextString(
637 startCursor, endCursor, nodesCrossed,
638 function(str, word, nodes) {
639 if (str.length + word.length + 1 > lineLength)
640 return true;
641 for (var i = 0; i < nodes.length; i++) {
642 if (cvox.TraverseUtil.isSkipped(nodes[i])) {
643 return true;
644 }
645 var style = window.getComputedStyle(nodes[i], null);
646 if (style && (style.display != 'inline' ||
647 breakTags[nodes[i].tagName])) {
648 return true;
649 }
650 }
651 return false;
652 });
653 };
654
655 /**
656 * Finds the previous line, starting from startCursor. Upon exit,
657 * startCursor and endCursor will surround the previous line.
658 *
659 * @param {Cursor} startCursor The position to start searching for the next
660 * line. On exit, will point to the start of the returned string.
661 * @param {Cursor} endCursor On exit, the end of the returned string.
662 * @param {Array.<Node>} nodesCrossed Any HTML nodes crossed between the
663 * initial and final cursor position will be pushed onto this array.
664 * @param {number} lineLength The maximum number of characters in a line.
665 * @param {Object} breakTags Associative array of tags that should break
666 * the sentence.
667 * @return {?string} The previous line, or null if the bottom of the
668 * document has been reached.
669 */
670 cvox.TraverseUtil.getPreviousLine = function(
671 startCursor, endCursor, nodesCrossed, lineLength, breakTags) {
672 return cvox.TraverseUtil.getPreviousString(
673 startCursor, endCursor, nodesCrossed,
674 function(str, word, nodes) {
675 if (str.length + word.length + 1 > lineLength)
676 return true;
677 for (var i = 0; i < nodes.length; i++) {
678 if (cvox.TraverseUtil.isSkipped(nodes[i])) {
679 return true;
680 }
681 var style = window.getComputedStyle(nodes[i], null);
682 if (style && (style.display != 'inline' ||
683 breakTags[nodes[i].tagName])) {
684 return true;
685 }
686 }
687 return false;
688 });
689 };
690
691 /**
692 * Finds the next paragraph, starting from endCursor. Upon exit,
693 * startCursor and endCursor will surround the next paragraph.
694 *
695 * @param {Cursor} startCursor On exit, marks the beginning of the paragraph.
696 * @param {Cursor} endCursor The position to start searching for the next
697 * paragraph. On exit, will point to the end of the returned string.
698 * @param {Array.<Node>} nodesCrossed Any HTML nodes crossed between the
699 * initial and final cursor position will be pushed onto this array.
700 * @return {?string} The next paragraph, or null if the bottom of the
701 * document has been reached.
702 */
703 cvox.TraverseUtil.getNextParagraph = function(startCursor, endCursor,
704 nodesCrossed) {
705 return cvox.TraverseUtil.getNextString(
706 startCursor, endCursor, nodesCrossed,
707 function(str, word, nodes) {
708 for (var i = 0; i < nodes.length; i++) {
709 if (cvox.TraverseUtil.isSkipped(nodes[i])) {
710 return true;
711 }
712 var style = window.getComputedStyle(nodes[i], null);
713 if (style && style.display != 'inline') {
714 return true;
715 }
716 }
717 return false;
718 });
719 };
720
721 /**
722 * Finds the previous paragraph, starting from startCursor. Upon exit,
723 * startCursor and endCursor will surround the previous paragraph.
724 *
725 * @param {Cursor} startCursor The position to start searching for the next
726 * paragraph. On exit, will point to the start of the returned string.
727 * @param {Cursor} endCursor On exit, the end of the returned string.
728 * @param {Array.<Node>} nodesCrossed Any HTML nodes crossed between the
729 * initial and final cursor position will be pushed onto this array.
730 * @return {?string} The previous paragraph, or null if the bottom of the
731 * document has been reached.
732 */
733 cvox.TraverseUtil.getPreviousParagraph = function(
734 startCursor, endCursor, nodesCrossed) {
735 return cvox.TraverseUtil.getPreviousString(
736 startCursor, endCursor, nodesCrossed,
737 function(str, word, nodes) {
738 for (var i = 0; i < nodes.length; i++) {
739 if (cvox.TraverseUtil.isSkipped(nodes[i])) {
740 return true;
741 }
742 var style = window.getComputedStyle(nodes[i], null);
743 if (style && style.display != 'inline') {
744 return true;
745 }
746 }
747 return false;
748 });
749 };
750
751 /**
752 * Customizable function to return the next string of words in the DOM, based
753 * on provided functions to decide when to break one string and start
754 * the next. This can be used to get the next sentence, line, paragraph,
755 * or potentially other granularities.
756 *
757 * Finds the next contiguous string, starting from endCursor. Upon exit,
758 * startCursor and endCursor will surround the next string.
759 *
760 * The breakBefore function takes three parameters, and
761 * should return true if the string should be broken before the proposed
762 * next word:
763 * str The string so far.
764 * word The next word to be added.
765 * nodesCrossed The nodes crossed in reaching this next word.
766 *
767 * @param {Cursor} startCursor On exit, will point to the beginning of the
768 * next string.
769 * @param {Cursor} endCursor The position to start searching for the next
770 * string. On exit, will point to the end of the returned string.
771 * @param {Array.<Node>} nodesCrossed Any HTML nodes crossed between the
772 * initial and final cursor position will be pushed onto this array.
773 * @param {function(string, string, Array.<string>)} breakBefore
774 * Function that takes the string so far, next word to be added, and
775 * nodes crossed, and returns true if the string should be ended before
776 * adding this word.
777 * @return {?string} The next string, or null if the bottom of the
778 * document has been reached.
779 */
780 cvox.TraverseUtil.getNextString = function(
781 startCursor, endCursor, nodesCrossed, breakBefore) {
782 // Get the first word and set the start cursor to the start of the
783 // first word.
784 var wordStartCursor = endCursor.clone();
785 var wordEndCursor = endCursor.clone();
786 var newNodesCrossed = [];
787 var str = '';
788 var word = cvox.TraverseUtil.getNextWord(
789 wordStartCursor, wordEndCursor, newNodesCrossed);
790 if (word == null)
791 return null;
792 startCursor.copyFrom(wordStartCursor);
793
794 // Always add the first word when the string is empty, and then keep
795 // adding more words as long as breakBefore returns false
796 while (!str || !breakBefore(str, word, newNodesCrossed)) {
797 // Append this word, set the end cursor to the end of this word, and
798 // update the returned list of nodes crossed to include ones we crossed
799 // in reaching this word.
800 if (str)
801 str += ' ';
802 str += word;
803 nodesCrossed = nodesCrossed.concat(newNodesCrossed);
804 endCursor.copyFrom(wordEndCursor);
805
806 // Get the next word and go back to the top of the loop.
807 newNodesCrossed = [];
808 word = cvox.TraverseUtil.getNextWord(
809 wordStartCursor, wordEndCursor, newNodesCrossed);
810 if (word == null)
811 return str;
812 }
813
814 return str;
815 };
816
817 /**
818 * Customizable function to return the previous string of words in the DOM,
819 * based on provided functions to decide when to break one string and start
820 * the next. See getNextString, above, for more details.
821 *
822 * Finds the previous contiguous string, starting from startCursor. Upon exit,
823 * startCursor and endCursor will surround the next string.
824 *
825 * @param {Cursor} startCursor The position to start searching for the
826 * previous string. On exit, will point to the beginning of the
827 * string returned.
828 * @param {Cursor} endCursor On exit, will point to the end of the
829 * string returned.
830 * @param {Array.<Node>} nodesCrossed Any HTML nodes crossed between the
831 * initial and final cursor position will be pushed onto this array.
832 * @param {function(string, string, Array.<string>)} breakBefore
833 * Function that takes the string so far, the word to be added, and
834 * nodes crossed, and returns true if the string should be ended before
835 * adding this word.
836 * @return {?string} The next string, or null if the top of the
837 * document has been reached.
838 */
839 cvox.TraverseUtil.getPreviousString = function(
840 startCursor, endCursor, nodesCrossed, breakBefore) {
841 // Get the first word and set the end cursor to the end of the
842 // first word.
843 var wordStartCursor = startCursor.clone();
844 var wordEndCursor = startCursor.clone();
845 var newNodesCrossed = [];
846 var str = '';
847 var word = cvox.TraverseUtil.getPreviousWord(
848 wordStartCursor, wordEndCursor, newNodesCrossed);
849 if (word == null)
850 return null;
851 endCursor.copyFrom(wordEndCursor);
852
853 // Always add the first word when the string is empty, and then keep
854 // adding more words as long as breakBefore returns false
855 while (!str || !breakBefore(str, word, newNodesCrossed)) {
856 // Prepend this word, set the start cursor to the start of this word, and
857 // update the returned list of nodes crossed to include ones we crossed
858 // in reaching this word.
859 if (str)
860 str = ' ' + str;
861 str = word + str;
862 nodesCrossed = nodesCrossed.concat(newNodesCrossed);
863 startCursor.copyFrom(wordStartCursor);
864
865 // Get the previous word and go back to the top of the loop.
866 newNodesCrossed = [];
867 word = cvox.TraverseUtil.getPreviousWord(
868 wordStartCursor, wordEndCursor, newNodesCrossed);
869 if (word == null)
870 return str;
871 }
872
873 return str;
874 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698