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

Side by Side Diff: chrome/browser/resources/access_chromevox/common/selection_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 A collection of JavaScript utilities used to improve selection
7 * at different granularities.
8 */
9
10
11 goog.provide('cvox.SelectionUtil');
12
13 goog.require('cvox.DomUtil');
14 goog.require('cvox.XpathUtil');
15
16 /**
17 * Utilities for improving selection.
18 * @constructor
19 */
20 cvox.SelectionUtil = function() {};
21
22 /**
23 * Checks if a given node is a descendant of another given node.
24 *
25 * @param {Node} nod The candidate descendant.
26 * @param {Node} bound The candidate parent.
27 * @return {boolean} True if nod is a descendant of bound.
28 */
29 cvox.SelectionUtil.isWithinBound = function(nod, bound) {
30 var parent = nod;
31 while (parent != null) {
32 if (parent.isSameNode(bound)) {
33 return true;
34 } else {
35 parent = parent.parentNode;
36 }
37 }
38 return false;
39 };
40
41 /**
42 * Cleans up a paragraph selection acquired by extending forward.
43 * In this context, a paragraph selection is 'clean' when the focus
44 * node (the end of the selection) is not on a text node.
45 * @param {Selection} sel The paragraph-length selection.
46 * @return {boolean} True if the selection has been cleaned.
47 * False if the selection cannot be cleaned without invalid extension.
48 */
49 cvox.SelectionUtil.cleanUpParagraphForward = function(sel) {
50 var expand = true;
51
52 // nodeType:3 == TEXT_NODE
53 while (sel.focusNode.nodeType == 3) {
54 // Ending with a text node, which is incorrect. Keep extending forward.
55 var fnode = sel.focusNode;
56 var foffset = sel.focusOffset;
57
58 sel.modify('extend', 'forward', 'sentence');
59 if ((fnode == sel.focusNode) && (foffset == sel.focusOffset)) {
60 // Nothing more to be done, cannot extend forward further.
61 return false;
62 }
63 }
64
65 return true;
66 };
67
68 /**
69 * Cleans up a paragraph selection acquired by extending backward.
70 * In this context, a paragraph selection is 'clean' when the focus
71 * node (the end of the selection) is not on a text node.
72 * @param {Selection} sel The paragraph-length selection.
73 * @return {boolean} True if the selection has been cleaned.
74 * False if the selection cannot be cleaned without invalid extension.
75 */
76 cvox.SelectionUtil.cleanUpParagraphBack = function(sel) {
77 var expand = true;
78
79 var fnode;
80 var foffset;
81
82 // nodeType:3 == TEXT_NODE
83 while (sel.focusNode.nodeType == 3) {
84 // Ending with a text node, which is incorrect. Keep extending backward.
85 fnode = sel.focusNode;
86 foffset = sel.focusOffset;
87
88 sel.modify('extend', 'backward', 'sentence');
89
90 if ((fnode == sel.focusNode) && (foffset == sel.focusOffset)) {
91 // Nothing more to be done, cannot extend backward further.
92 return true;
93 }
94 }
95
96 return true;
97 };
98
99 /**
100 * Cleans up a sentence selection by extending forward.
101 * In this context, a sentence selection is 'clean' when the focus
102 * node (the end of the selection) is either:
103 * - not on a text node
104 * - on a text node that ends with a period or a space
105 * @param {Selection} sel The sentence-length selection.
106 * @return {boolean} True if the selection has been cleaned.
107 * False if the selection cannot be cleaned without invalid extension.
108 */
109 cvox.SelectionUtil.cleanUpSentence = function(sel) {
110 var expand = true;
111 var lastSelection;
112 var lastSelectionOffset;
113
114 while (expand) {
115
116 // nodeType:3 == TEXT_NODE
117 if (sel.focusNode.nodeType == 3) {
118 // The focus node is of type text, check end for period
119
120 var fnode = sel.focusNode;
121 var foffset = sel.focusOffset;
122
123 if (sel.rangeCount > 0 && sel.getRangeAt(0).endOffset > 0) {
124 if (fnode.substringData(sel.getRangeAt(0).endOffset - 1, 1) == '.') {
125 // Text node ends with period.
126 return true;
127 } else if (fnode.substringData(sel.getRangeAt(0).endOffset - 1, 1) ==
128 ' ') {
129 // Text node ends with space.
130 return true;
131 } else {
132 // Text node does not end with period or space. Extend forward.
133 sel.modify('extend', 'forward', 'sentence');
134
135 if ((fnode == sel.focusNode) && (foffset == sel.focusOffset)) {
136 // Nothing more to be done, cannot extend forward any further.
137 return false;
138 }
139 }
140 } else {
141 return true;
142 }
143 } else {
144 // Focus node is not text node, no further cleaning required.
145 return true;
146 }
147 }
148
149 return true;
150 };
151
152 /**
153 * Finds the starting position (height from top and left width) of a
154 * selection in a document.
155 * @param {Selection} sel The selection.
156 * @return {Array} The coordinates [top, left] of the selection.
157 */
158 cvox.SelectionUtil.findSelPosition = function(sel) {
159 if (sel.rangeCount == 0) {
160 return [0, 0];
161 }
162
163 var clientRect = sel.getRangeAt(0).getBoundingClientRect();
164 var top = window.pageYOffset + clientRect.top;
165 var left = window.pageXOffset + clientRect.left;
166 return [top, left];
167 };
168
169 /**
170 * Calculates the horizontal and vertical position of a node
171 * @param {Node} targetNode The node.
172 * @return {Array} The coordinates [top, left] of the node.
173 */
174 cvox.SelectionUtil.findTopLeftPosition = function(targetNode) {
175 var left = 0;
176 var top = 0;
177 var obj = targetNode;
178
179 if (obj.offsetParent) {
180 left = obj.offsetLeft;
181 top = obj.offsetTop;
182 obj = obj.offsetParent;
183
184 while (obj !== null) {
185 left += obj.offsetLeft;
186 top += obj.offsetTop;
187 obj = obj.offsetParent;
188 }
189 }
190
191 return [top, left];
192 };
193
194
195 /**
196 * Checks the contents of a selection for meaningful content.
197 * @param {Selection} sel The selection.
198 * @return {boolean} True if the selection is valid. False if the selection
199 * contains only whitespace or is an empty string.
200 */
201 cvox.SelectionUtil.isSelectionValid = function(sel) {
202 var regExpWhiteSpace = new RegExp(/^\s+$/);
203 return (! ((regExpWhiteSpace.test(sel.toString())) ||
204 (sel.toString() == '')));
205 };
206
207
208 /**
209 * Scrolls the selection into view if it is out of view in the current window.
210 * Inspired by workaround for already-on-screen elements @
211 * http://
212 * www.performantdesign.com/2009/08/2/scrollintoview-but-only-if-out-of-view/
213 * @param {Selection} sel The selection to be scrolled into view.
214 */
215 cvox.SelectionUtil.scrollToSelection = function(sel) {
216 if (sel.rangeCount == 0) {
217 return;
218 }
219
220 var pos = cvox.SelectionUtil.findSelPosition(sel);
221 var top = pos[0];
222 var left = pos[1];
223
224 var scrolledVertically = window.pageYOffset ||
225 document.documentElement.scrollTop ||
226 document.body.scrollTop;
227 var pageHeight = window.innerHeight ||
228 document.documentElement.clientHeight || document.body.clientHeight;
229 var pageWidth = window.innerWidth ||
230 document.documentElement.innerWidth || document.body.clientWidth;
231
232 if (left < pageWidth) {
233 left = 0;
234 }
235
236 // window.scroll puts specified pixel in upper left of window
237 if ((scrolledVertically + pageHeight) < top) {
238 // Align with bottom of page
239 var diff = top - pageHeight;
240 window.scroll(left, diff + 100);
241 } else if (top < scrolledVertically) {
242 // Align with top of page
243 window.scroll(left, top - 100);
244 }
245 };
246
247 /**
248 * This is from https://developer.mozilla.org/en/Whitespace_in_the_DOM
249 * Determine whether a node's text content is entirely whitespace.
250 *
251 * Throughout, whitespace is defined as one of the characters
252 * "\t" TAB \u0009
253 * "\n" LF \u000A
254 * "\r" CR \u000D
255 * " " SPC \u0020
256 *
257 * This does not use Javascript's "\s" because that includes non-breaking
258 * spaces (and also some other characters).
259 *
260 * @param {Node} node A node implementing the |CharacterData| interface (i.e.,
261 * a |Text|, |Comment|, or |CDATASection| node.
262 * @return {boolean} True if all of the text content of |node| is whitespace,
263 * otherwise false.
264 */
265 cvox.SelectionUtil.isAllWs = function(node) {
266 // Use ECMA-262 Edition 3 String and RegExp features
267 return !(/[^\t\n\r ]/.test(node.data));
268 };
269
270
271 /**
272 * This is from https://developer.mozilla.org/en/Whitespace_in_the_DOM
273 * Determine if a node should be ignored by the iterator functions.
274 *
275 * @param {Node} node An object implementing the DOM1 |Node| interface.
276 * @return {boolean} True if the node is:
277 * 1) A |Text| node that is all whitespace
278 * 2) A |Comment| node
279 * and otherwise false.
280 */
281
282 cvox.SelectionUtil.isIgnorable = function(node) {
283 return (node.nodeType == 8) || // A comment node
284 ((node.nodeType == 3) &&
285 cvox.SelectionUtil.isAllWs(node)); // a text node, all ws
286 };
287
288 /**
289 * This is from https://developer.mozilla.org/en/Whitespace_in_the_DOM
290 * Version of |previousSibling| that skips nodes that are entirely
291 * whitespace or comments. (Normally |previousSibling| is a property
292 * of all DOM nodes that gives the sibling node, the node that is
293 * a child of the same parent, that occurs immediately before the
294 * reference node.)
295 *
296 * @param {Node} sib The reference node.
297 * @return {Node} Either:
298 * 1) The closest previous sibling to |sib| that is not
299 * ignorable according to |isIgnorable|, or
300 * 2) null if no such node exists.
301 */
302 cvox.SelectionUtil.nodeBefore = function(sib) {
303 while ((sib = sib.previousSibling)) {
304 if (!cvox.SelectionUtil.isIgnorable(sib)) {
305 return sib;
306 }
307 }
308 return null;
309 };
310
311 /**
312 * This is from https://developer.mozilla.org/en/Whitespace_in_the_DOM
313 * Version of |nextSibling| that skips nodes that are entirely
314 * whitespace or comments.
315 *
316 * @param {Node} sib The reference node.
317 * @return {Node} Either:
318 * 1) The closest next sibling to |sib| that is not
319 * ignorable according to |isIgnorable|, or
320 * 2) null if no such node exists.
321 */
322 cvox.SelectionUtil.nodeAfter = function(sib) {
323 while ((sib = sib.nextSibling)) {
324 if (!cvox.SelectionUtil.isIgnorable(sib)) {
325 return sib;
326 }
327 }
328 return null;
329 };
330
331 /**
332 * This is from https://developer.mozilla.org/en/Whitespace_in_the_DOM
333 * Version of |lastChild| that skips nodes that are entirely
334 * whitespace or comments. (Normally |lastChild| is a property
335 * of all DOM nodes that gives the last of the nodes contained
336 * directly in the reference node.)
337 *
338 * @param {Node} par The reference node.
339 * @return {Node} Either:
340 * 1) The last child of |sib| that is not
341 * ignorable according to |isIgnorable|, or
342 * 2) null if no such node exists.
343 */
344 cvox.SelectionUtil.lastChildNode = function(par) {
345 var res = par.lastChild;
346 while (res) {
347 if (!cvox.SelectionUtil.isIgnorable(res)) {
348 return res;
349 }
350 res = res.previousSibling;
351 }
352 return null;
353 };
354
355 /**
356 * This is from https://developer.mozilla.org/en/Whitespace_in_the_DOM
357 * Version of |firstChild| that skips nodes that are entirely
358 * whitespace and comments.
359 *
360 * @param {Node} par The reference node.
361 * @return {Node} Either:
362 * 1) The first child of |sib| that is not
363 * ignorable according to |isIgnorable|, or
364 * 2) null if no such node exists.
365 */
366 cvox.SelectionUtil.firstChildNode = function(par) {
367 var res = par.firstChild;
368 while (res) {
369 if (!cvox.SelectionUtil.isIgnorable(res)) {
370 return res;
371 }
372 res = res.nextSibling;
373 }
374 return null;
375 };
376
377 /**
378 * This is from https://developer.mozilla.org/en/Whitespace_in_the_DOM
379 * Version of |data| that doesn't include whitespace at the beginning
380 * and end and normalizes all whitespace to a single space. (Normally
381 * |data| is a property of text nodes that gives the text of the node.)
382 *
383 * @param {Node} txt The text node whose data should be returned.
384 * @return {string} A string giving the contents of the text node with
385 * whitespace collapsed.
386 */
387 cvox.SelectionUtil.dataOf = function(txt) {
388 var data = txt.data;
389 // Use ECMA-262 Edition 3 String and RegExp features
390 data = data.replace(/[\t\n\r ]+/g, ' ');
391 if (data.charAt(0) == ' ') {
392 data = data.substring(1, data.length);
393 }
394 if (data.charAt(data.length - 1) == ' ') {
395 data = data.substring(0, data.length - 1);
396 }
397 return data;
398 };
399
400 /**
401 * Returns true if the selection has content from at least one node
402 * that has the specified tagName.
403 *
404 * @param {Selection} sel The selection.
405 * @param {string} tagName Tagname that the selection should be checked for.
406 * @return {boolean} True if the selection has content from at least one node
407 * with the specified tagName.
408 */
409 cvox.SelectionUtil.hasContentWithTag = function(sel, tagName) {
410 if (!sel || !sel.anchorNode || !sel.focusNode) {
411 return false;
412 }
413 if (sel.anchorNode.tagName && (sel.anchorNode.tagName == tagName)) {
414 return true;
415 }
416 if (sel.focusNode.tagName && (sel.focusNode.tagName == tagName)) {
417 return true;
418 }
419 if (sel.anchorNode.parentNode.tagName &&
420 (sel.anchorNode.parentNode.tagName == tagName)) {
421 return true;
422 }
423 if (sel.focusNode.parentNode.tagName &&
424 (sel.focusNode.parentNode.tagName == tagName)) {
425 return true;
426 }
427 var docFrag = sel.getRangeAt(0).cloneContents();
428 var span = document.createElement('span');
429 span.appendChild(docFrag);
430 return (span.getElementsByTagName(tagName).length > 0);
431 };
432
433 /**
434 * Selects text within a text node.
435 *
436 * Note that the input node MUST be of type TEXT; otherwise, the offset
437 * count would not mean # of characters - this is because of the way Range
438 * works in JavaScript.
439 *
440 * @param {Node} textNode The text node to select text within.
441 * @param {number} start The start of the selection.
442 * @param {number} end The end of the selection.
443 */
444 cvox.SelectionUtil.selectText = function(textNode, start, end) {
445 var newRange = document.createRange();
446 newRange.setStart(textNode, start);
447 newRange.setEnd(textNode, end);
448 var sel = window.getSelection();
449 sel.removeAllRanges();
450 sel.addRange(newRange);
451 };
452
453 /**
454 * Selects all the text in a given node.
455 *
456 * @param {Node} node The target node.
457 */
458 cvox.SelectionUtil.selectAllTextInNode = function(node) {
459 var newRange = document.createRange();
460 newRange.setStart(node, 0);
461 newRange.setEndAfter(node);
462 var sel = window.getSelection();
463 sel.removeAllRanges();
464 sel.addRange(newRange);
465 };
466
467 /**
468 * Retrieves all the text within a selection.
469 *
470 * Note that this can be different than simply using the string from
471 * window.getSelection() as this will account for IMG nodes, etc.
472 *
473 * @return {string} The string of text contained in the current selection.
474 */
475 cvox.SelectionUtil.getText = function() {
476 var text = '';
477 var sel = window.getSelection();
478 if (cvox.SelectionUtil.hasContentWithTag(sel, 'IMG')) {
479 var docFrag = sel.getRangeAt(0).cloneContents();
480 var span = document.createElement('span');
481 span.appendChild(docFrag);
482 var leafNodes = cvox.XpathUtil.getLeafNodes(span);
483 for (var i = 0, node; node = leafNodes[i]; i++) {
484 text = text + ' ' + cvox.DomUtil.getText(node);
485 }
486 } else {
487 text = text + sel;
488 }
489 return text;
490 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698