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

Side by Side Diff: chrome/browser/resources/access_chromevox/common/traverse_content.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 DOM traversal interface for moving a selection around a
7 * webpage. Provides multiple granularities:
8 * 1. Move by paragraph.
9 * 2. Move by sentence.
10 * 3. Move by line.
11 * 4. Move by word.
12 * 5. Move by character.
13 */
14
15 goog.provide('cvox.TraverseContent');
16
17 goog.require('cvox.SelectionUtil');
18 goog.require('cvox.TraverseUtil');
19
20 /**
21 * Moves a selection around a document or within a provided DOM object.
22 *
23 * @constructor
24 * @param {Node=} domObj a DOM node (optional).
25 */
26 cvox.TraverseContent = function(domObj) {
27 if (domObj != null) {
28 this.currentDomObj = domObj;
29 } else {
30 this.currentDomObj = document.body;
31 }
32 };
33
34 /**
35 * Whether we should skip whitespace when traversing individual characters.
36 * @type {boolean}
37 */
38 cvox.TraverseContent.prototype.skipWhitespace = true;
39
40 /**
41 * The maximum number of characters that can be on one line when doing
42 * line-based traversal.
43 * @type {number}
44 */
45 cvox.TraverseContent.prototype.lineLength = 40;
46
47 /**
48 * If moveNext and movePrev should skip past an invalid selection,
49 * so the user never gets stuck. Ideally the navigation code should never
50 * return a range that's not a valid selection, but this keeps the user from
51 * getting stuck if that code fails. This is set to false for unit testing.
52 * @type {boolean}
53 */
54 cvox.TraverseContent.prototype.skipInvalidSelections = true;
55
56 /**
57 * If line and sentence navigation should break at <a> links.
58 * @type {boolean}
59 */
60 cvox.TraverseContent.prototype.breakAtLinks = false;
61
62 /**
63 * The string constant for character granularity.
64 * @type {string}
65 * @const
66 */
67 cvox.TraverseContent.kCharacter = 'character';
68
69 /**
70 * The string constant for word granularity.
71 * @type {string}
72 * @const
73 */
74 cvox.TraverseContent.kWord = 'word';
75
76 /**
77 * The string constant for sentence granularity.
78 * @type {string}
79 * @const
80 */
81 cvox.TraverseContent.kSentence = 'sentence';
82
83 /**
84 * The string constant for line granularity.
85 * @type {string}
86 * @const
87 */
88 cvox.TraverseContent.kLine = 'line';
89
90 /**
91 * The string constant for paragraph granularity.
92 * @type {string}
93 * @const
94 */
95 cvox.TraverseContent.kParagraph = 'paragraph';
96
97 /**
98 * A constant array of all granularities.
99 * @type {Array.<string>}
100 * @const
101 */
102 cvox.TraverseContent.kAllGrains =
103 [cvox.TraverseContent.kParagraph,
104 cvox.TraverseContent.kSentence,
105 cvox.TraverseContent.kLine,
106 cvox.TraverseContent.kWord,
107 cvox.TraverseContent.kCharacter];
108
109 /**
110 * Moves selection forward.
111 *
112 * @param {string} grain specifies "sentence", "word", "character",
113 * or "paragraph" granularity.
114 * @return {Selection} Either:
115 * 1) The fixed-up selection.
116 * 2) null if the end of the domObj has been reached.
117 */
118 cvox.TraverseContent.prototype.moveNext = function(grain) {
119 this.normalizeSelection();
120
121 var selection = window.getSelection();
122 var startCursor = new Cursor(
123 selection.anchorNode, selection.anchorOffset,
124 cvox.TraverseUtil.getNodeText(selection.anchorNode));
125 var endCursor = new Cursor(
126 selection.focusNode, selection.focusOffset,
127 cvox.TraverseUtil.getNodeText(selection.focusNode));
128 var breakTags = this.getBreakTags();
129 // As a special case, if the current selection is empty or all
130 // whitespace, ensure that the next returned selection will NOT be
131 // only whitespace - otherwise you can get trapped.
132 var skipWhitespace = this.skipWhitespace;
133 if (!cvox.SelectionUtil.isSelectionValid(selection))
134 skipWhitespace = true;
135
136 var nodesCrossed = [];
137 var str;
138
139 do {
140 if (grain === cvox.TraverseContent.kSentence) {
141 str = cvox.TraverseUtil.getNextSentence(
142 startCursor, endCursor, nodesCrossed, breakTags);
143 } else if (grain === cvox.TraverseContent.kWord) {
144 str = cvox.TraverseUtil.getNextWord(startCursor, endCursor,
145 nodesCrossed);
146 } else if (grain === cvox.TraverseContent.kCharacter) {
147 str = cvox.TraverseUtil.getNextChar(startCursor, endCursor,
148 nodesCrossed, skipWhitespace);
149 } else if (grain === cvox.TraverseContent.kParagraph) {
150 str = cvox.TraverseUtil.getNextParagraph(startCursor, endCursor,
151 nodesCrossed);
152 } else if (grain === cvox.TraverseContent.kLine) {
153 str = cvox.TraverseUtil.getNextLine(
154 startCursor, endCursor, nodesCrossed, this.lineLength, breakTags);
155 } else {
156 // User has provided an invalid string.
157 // Fall through to default: extend by sentence
158 console.log('Invalid selection granularity: "' + grain + '"');
159 grain = cvox.TraverseContent.kSentence;
160 str = cvox.TraverseUtil.getNextSentence(
161 startCursor, endCursor, nodesCrossed, breakTags);
162 }
163
164 // Select the new object.
165 selection = cvox.TraverseUtil.setSelection(startCursor, endCursor);
166
167 if (str == null) {
168 // We reached the end of the document.
169 return null;
170 }
171 } while (this.skipInvalidSelections && selection.isCollapsed);
172
173 if (!cvox.SelectionUtil.isWithinBound(selection.focusNode,
174 this.currentDomObj)) {
175 // Extended outside of specified domObj; trim it back to just the domObj
176 // if we are dealing with a text node.
177 // Return NULL to indicate that we are wrapping to the beginning.
178 if (selection.anchorNode.nodeType == 3) { // NODETYPE 3 == text node
179 cvox.SelectionUtil.selectText(selection.anchorNode,
180 selection.anchorOffset,
181 selection.anchorNode.textContent.length);
182 }
183 return null;
184 }
185 return selection;
186 };
187
188
189 /**
190 * Moves selection backward.
191 *
192 * @param {string} grain specifies "sentence", "word", "character",
193 * or "paragraph" granularity.
194 * @return {Selection} Either:
195 * 1) The fixed-up selection.
196 * 2) null if the beginning of the domObj has been reached.
197 */
198 cvox.TraverseContent.prototype.movePrev = function(grain) {
199 this.normalizeSelection();
200
201 var selection = window.getSelection();
202 var startCursor = new Cursor(
203 selection.anchorNode, selection.anchorOffset,
204 cvox.TraverseUtil.getNodeText(selection.anchorNode));
205 var endCursor = new Cursor(
206 selection.focusNode, selection.focusOffset,
207 cvox.TraverseUtil.getNodeText(selection.focusNode));
208 var breakTags = this.getBreakTags();
209 // As a special case, if the current selection is empty or all
210 // whitespace, ensure that the next returned selection will NOT be
211 // only whitespace - otherwise you can get trapped.
212 var skipWhitespace = this.skipWhitespace;
213 if (!cvox.SelectionUtil.isSelectionValid(selection))
214 skipWhitespace = true;
215
216 var nodesCrossed = [];
217 var str;
218
219 do {
220 if (grain === cvox.TraverseContent.kSentence) {
221 str = cvox.TraverseUtil.getPreviousSentence(
222 startCursor, endCursor, nodesCrossed, breakTags);
223 } else if (grain === cvox.TraverseContent.kWord) {
224 str = cvox.TraverseUtil.getPreviousWord(startCursor, endCursor,
225 nodesCrossed);
226 } else if (grain === cvox.TraverseContent.kCharacter) {
227 var skipWhitespace = this.skipWhitespace;
228 if (!cvox.SelectionUtil.isSelectionValid(selection))
229 skipWhitespace = true;
230 str = cvox.TraverseUtil.getPreviousChar(startCursor, endCursor,
231 nodesCrossed, skipWhitespace);
232 } else if (grain === cvox.TraverseContent.kParagraph) {
233 str = cvox.TraverseUtil.getPreviousParagraph(
234 startCursor, endCursor, nodesCrossed);
235 } else if (grain === cvox.TraverseContent.kLine) {
236 str = cvox.TraverseUtil.getPreviousLine(
237 startCursor, endCursor, nodesCrossed, this.lineLength, breakTags);
238 } else {
239 // User has provided an invalid string.
240 // Fall through to default: extend by sentence
241 console.log('Invalid selection granularity: "' + grain + '"');
242 grain = cvox.TraverseContent.kSentence;
243 str = cvox.TraverseUtil.getPreviousSentence(
244 startCursor, endCursor, nodesCrossed, breakTags);
245 }
246
247 // Select the new object.
248 selection = cvox.TraverseUtil.setSelection(startCursor, endCursor);
249
250 if (str == null) {
251 // We reached the end of the document.
252 return null;
253 }
254
255 } while (this.skipInvalidSelections && selection.isCollapsed);
256
257 if (!cvox.SelectionUtil.isWithinBound(selection.focusNode,
258 this.currentDomObj)) {
259 console.log('not within bound');
260 // Extended outside of specified domObj, nothing more to be done
261 // Return NULL to indicate that we are wrapping to the beginning
262 return null;
263 }
264
265 return selection;
266 };
267
268 /**
269 * Get the tag names that should break a sentence or line. Currently
270 * just an anchor 'A' should break a sentence or line if the breakAtLinks
271 * flag is true, but in the future we might have other rules for breaking.
272 *
273 * @return {Object} An associative array mapping a tag name to true if
274 * it should break a sentence or line.
275 */
276 cvox.TraverseContent.prototype.getBreakTags = function() {
277 return this.breakAtLinks ? {'A': true} : {};
278 };
279
280 /**
281 * Selects the next element of the document or within the provided DOM object.
282 * Scrolls the window as appropriate.
283 *
284 * @param {string} grain specifies "sentence", "word", "character",
285 * or "paragraph" granularity.
286 * @param {Node=} domObj a DOM node (optional).
287 * @return {Selection} Either:
288 * 1) The current selection.
289 * 2) null if the end of the domObj has been reached.
290 */
291 cvox.TraverseContent.prototype.nextElement = function(grain, domObj) {
292
293 if (domObj != null) {
294 this.currentDomObj = domObj;
295 }
296
297 if (! ((grain === 'sentence') || (grain === 'word') ||
298 (grain === 'character') || (grain === 'paragraph'))) {
299 // User has provided an invalid string.
300 // Fall through to default: extend by sentence
301 console.log('Invalid selection granularity: "' + grain + '"');
302 grain = 'sentence';
303 }
304
305 var status = this.moveNext(grain);
306 if (status != null) {
307 // Force window scroll to current selection
308 cvox.SelectionUtil.scrollToSelection(window.getSelection());
309 }
310
311 return status;
312 };
313
314
315 /**
316 * Selects the previous element of the document or within the provided DOM
317 * object. Scrolls the window as appropriate.
318 *
319 * @param {string} grain specifies "sentence", "word", "character",
320 * or "paragraph" granularity.
321 * @param {Node=} domObj a DOM node (optional).
322 * @return {Selection} Either:
323 * 1) The current selection.
324 * 2) null if the beginning of the domObj has been reached.
325 */
326 cvox.TraverseContent.prototype.prevElement = function(grain, domObj) {
327
328 if (domObj != null) {
329 this.currentDomObj = domObj;
330 }
331
332 if (! ((grain === 'sentence') || (grain === 'word') ||
333 (grain === 'character') || (grain === 'paragraph'))) {
334 // User has provided an invalid string.
335 // Fall through to default: extend by sentence
336 console.log('Invalid selection granularity: "' + grain + '"');
337 grain = 'sentence';
338 }
339
340 var status = this.movePrev(grain);
341 if (status != null) {
342 // Force window scroll to current selection
343 cvox.SelectionUtil.scrollToSelection(window.getSelection());
344 }
345
346 return status;
347 };
348
349 /**
350 * Make sure that exactly one item is selected. If there's no selection,
351 * set the selection to the start of the document.
352 */
353 cvox.TraverseContent.prototype.normalizeSelection = function() {
354 var selection = window.getSelection();
355 if (selection.rangeCount < 1) {
356 // Before the user has clicked a freshly-loaded page
357
358 var range = document.createRange();
359 range.setStart(this.currentDomObj, 0);
360 range.setEnd(this.currentDomObj, 0);
361
362 selection.removeAllRanges();
363 selection.addRange(range);
364
365 } else if (selection.rangeCount > 1) {
366 // Multiple ranges exist - remove all ranges but the last one
367 for (var i = 0; i < (selection.rangeCount - 1); i++) {
368 selection.removeRange(selection.getRangeAt(i));
369 }
370 }
371 };
372
373 /**
374 * Resets the selection.
375 *
376 * @param {Node=} domObj a DOM node. Optional.
377 *
378 */
379 cvox.TraverseContent.prototype.reset = function(domObj) {
380 window.getSelection().removeAllRanges();
381 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698