|
1 <!DOCTYPE html> |
|
2 <html> |
|
3 <head> |
|
4 <title>Test: nsIAccessibleText getText* functions at caret offset</title> |
|
5 |
|
6 <link rel="stylesheet" type="text/css" |
|
7 href="chrome://mochikit/content/tests/SimpleTest/test.css" /> |
|
8 |
|
9 <script type="application/javascript" |
|
10 src="chrome://mochikit/content/MochiKit/packed.js"></script> |
|
11 <script type="application/javascript" |
|
12 src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> |
|
13 <script type="application/javascript" |
|
14 src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> |
|
15 |
|
16 <script type="application/javascript" |
|
17 src="../common.js"></script> |
|
18 <script type="application/javascript" |
|
19 src="../role.js"></script> |
|
20 <script type="application/javascript" |
|
21 src="../states.js"></script> |
|
22 <script type="application/javascript" |
|
23 src="../events.js"></script> |
|
24 <script type="application/javascript" |
|
25 src="../text.js"></script> |
|
26 |
|
27 <script type="application/javascript"> |
|
28 //gA11yEventDumpToConsole = true; // debugging |
|
29 |
|
30 function traverseTextByLines(aQueue, aID, aLines) |
|
31 { |
|
32 var wholeText = ""; |
|
33 for (var i = 0; i < aLines.length ; i++) |
|
34 wholeText += aLines[i][0] + aLines[i][1]; |
|
35 |
|
36 var baseInvokerFunc = synthClick; |
|
37 var charIter = new charIterator(wholeText, aLines); |
|
38 //charIter.debugOffset = 10; // enable to run tests at given offset only |
|
39 |
|
40 while (charIter.next()) { |
|
41 aQueue.push(new tmpl_moveTo(aID, baseInvokerFunc, wholeText, charIter)); |
|
42 baseInvokerFunc = synthRightKey; |
|
43 } |
|
44 } |
|
45 |
|
46 /** |
|
47 * Used to get test list for each traversed character. |
|
48 */ |
|
49 function charIterator(aWholeText, aLines) |
|
50 { |
|
51 this.next = function charIterator_next() |
|
52 { |
|
53 // Don't increment offset if we are at end of the wrapped line |
|
54 // (offset is shared between end of this line and start of next line). |
|
55 if (this.mAtWrappedLineEnd) { |
|
56 this.mAtWrappedLineEnd = false; |
|
57 this.mLine = this.mLine.nextLine; |
|
58 return true; |
|
59 } |
|
60 |
|
61 this.mOffset++; |
|
62 if (this.mOffset > aWholeText.length) |
|
63 return false; |
|
64 |
|
65 var nextLine = this.mLine.nextLine; |
|
66 if (!nextLine.isFakeLine() && this.mOffset == nextLine.start) { |
|
67 if (nextLine.start == this.mLine.end) |
|
68 this.mAtWrappedLineEnd = true; |
|
69 else |
|
70 this.mLine = nextLine; |
|
71 } |
|
72 |
|
73 return true; |
|
74 } |
|
75 |
|
76 Object.defineProperty(this, "offset", { get: function() |
|
77 { return this.mOffset; } |
|
78 }); |
|
79 |
|
80 Object.defineProperty(this, "offsetDescr", { get: function() |
|
81 { |
|
82 return this.mOffset + " offset (" + this.mLine.number + " line, " + |
|
83 (this.mOffset - this.mLine.start) + " offset on the line)"; |
|
84 } |
|
85 }); |
|
86 |
|
87 Object.defineProperty(this, "tests", { get: function() |
|
88 { |
|
89 // Line boundary tests. |
|
90 var cLine = this.mLine; |
|
91 var pLine = cLine.prevLine; |
|
92 var ppLine = pLine.prevLine; |
|
93 var nLine = cLine.nextLine; |
|
94 var nnLine = nLine.nextLine; |
|
95 |
|
96 var lineTests = [ |
|
97 [ testTextBeforeOffset, BOUNDARY_LINE_START, pLine.start, cLine.start], |
|
98 [ testTextBeforeOffset, BOUNDARY_LINE_END, ppLine.end, pLine.end], |
|
99 [ testTextAtOffset, BOUNDARY_LINE_START, cLine.start, nLine.start], |
|
100 [ testTextAtOffset, BOUNDARY_LINE_END, pLine.end, cLine.end], |
|
101 [ testTextAfterOffset, BOUNDARY_LINE_START, nLine.start, nnLine.start], |
|
102 [ testTextAfterOffset, BOUNDARY_LINE_END, cLine.end, nLine.end] |
|
103 ]; |
|
104 |
|
105 // Word boundary tests. |
|
106 var cWord = this.mLine.firstWord; |
|
107 var nWord = cWord.nextWord, pWord = cWord.prevWord; |
|
108 |
|
109 // The current word is a farthest word starting at or after the offset. |
|
110 if (this.mOffset >= nWord.start) { |
|
111 while (this.mOffset >= nWord.start && !this.mLine.isLastWord(cWord)) { |
|
112 cWord = nWord; |
|
113 nWord = nWord.nextWord; |
|
114 } |
|
115 pWord = cWord.prevWord; |
|
116 |
|
117 } else if (this.mOffset < cWord.start) { |
|
118 while (this.mOffset < cWord.start) { |
|
119 cWord = pWord; |
|
120 pWord = pWord.prevWord; |
|
121 } |
|
122 nWord = cWord.nextWord; |
|
123 } |
|
124 |
|
125 var nnWord = nWord.nextWord, ppWord = pWord.prevWord; |
|
126 |
|
127 var isAfterWordEnd = |
|
128 this.mOffset > cWord.end || cWord.line != this.mLine; |
|
129 var isAtOrAfterWordEnd = (this.mOffset >= cWord.end); |
|
130 var useNextWordForAtWordEnd = |
|
131 isAtOrAfterWordEnd && this.mOffset != aWholeText.length; |
|
132 |
|
133 var wordTests = [ |
|
134 [ testTextBeforeOffset, BOUNDARY_WORD_START, |
|
135 pWord.start, cWord.start ], |
|
136 [ testTextBeforeOffset, BOUNDARY_WORD_END, |
|
137 (isAfterWordEnd ? pWord : ppWord).end, |
|
138 (isAfterWordEnd ? cWord : pWord).end ], |
|
139 [ testTextAtOffset, BOUNDARY_WORD_START, |
|
140 cWord.start, nWord.start ], |
|
141 [ testTextAtOffset, BOUNDARY_WORD_END, |
|
142 (useNextWordForAtWordEnd ? cWord : pWord).end, |
|
143 (useNextWordForAtWordEnd ? nWord : cWord).end ], |
|
144 [ testTextAfterOffset, BOUNDARY_WORD_START, |
|
145 nWord.start, nnWord.start ], |
|
146 [ testTextAfterOffset, BOUNDARY_WORD_END, |
|
147 (isAfterWordEnd ? nWord : cWord).end, |
|
148 (isAfterWordEnd ? nnWord : nWord).end ] |
|
149 ]; |
|
150 |
|
151 // Character boundary tests. |
|
152 var prevOffset = this.offset > 1 ? this.offset - 1 : 0; |
|
153 var nextOffset = this.offset >= aWholeText.length ? |
|
154 this.offset : this.offset + 1; |
|
155 var nextAfterNextOffset = nextOffset >= aWholeText.length ? |
|
156 nextOffset : nextOffset + 1; |
|
157 |
|
158 var charTests = [ |
|
159 [ testTextBeforeOffset, BOUNDARY_CHAR, |
|
160 prevOffset, this.offset ], |
|
161 [ testTextAtOffset, BOUNDARY_CHAR, |
|
162 this.offset, |
|
163 this.mAtWrappedLineEnd ? this.offset : nextOffset ], |
|
164 [ testTextAfterOffset, BOUNDARY_CHAR, |
|
165 this.mAtWrappedLineEnd ? this.offset : nextOffset, |
|
166 this.mAtWrappedLineEnd ? nextOffset : nextAfterNextOffset ] |
|
167 ]; |
|
168 |
|
169 return lineTests.concat(wordTests.concat(charTests)); |
|
170 } |
|
171 }); |
|
172 |
|
173 Object.defineProperty(this, "failures", { get: function() |
|
174 { |
|
175 if (this.mOffset == this.mLine.start) |
|
176 return this.mLine.lineStartFailures; |
|
177 if (this.mOffset == this.mLine.end) |
|
178 return this.mLine.lineEndFailures; |
|
179 return []; |
|
180 } |
|
181 }); |
|
182 |
|
183 this.mOffset = -1; |
|
184 this.mLine = new line(aWholeText, aLines, 0); |
|
185 this.mAtWrappedLineEnd = false; |
|
186 this.mWord = this.mLine.firstWord; |
|
187 } |
|
188 |
|
189 /** |
|
190 * A line object. Allows to navigate by lines and by words. |
|
191 */ |
|
192 function line(aWholeText, aLines, aIndex) |
|
193 { |
|
194 Object.defineProperty(this, "prevLine", { get: function() |
|
195 { |
|
196 return new line(aWholeText, aLines, aIndex - 1); |
|
197 } |
|
198 }); |
|
199 Object.defineProperty(this, "nextLine", { get: function() |
|
200 { |
|
201 return new line(aWholeText, aLines, aIndex + 1); |
|
202 } |
|
203 }); |
|
204 |
|
205 Object.defineProperty(this, "start", { get: function() |
|
206 { |
|
207 if (aIndex < 0) |
|
208 return 0; |
|
209 |
|
210 if (aIndex >= aLines.length) |
|
211 return aWholeText.length; |
|
212 |
|
213 return aLines[aIndex][2]; |
|
214 } |
|
215 }); |
|
216 Object.defineProperty(this, "end", { get: function() |
|
217 { |
|
218 if (aIndex < 0) |
|
219 return 0; |
|
220 |
|
221 if (aIndex >= aLines.length) |
|
222 return aWholeText.length; |
|
223 |
|
224 return aLines[aIndex][3]; |
|
225 } |
|
226 }); |
|
227 |
|
228 Object.defineProperty(this, "number", { get: function() |
|
229 { return aIndex; } |
|
230 }); |
|
231 Object.defineProperty(this, "wholeText", { get: function() |
|
232 { return aWholeText; } |
|
233 }); |
|
234 this.isFakeLine = function line_isFakeLine() |
|
235 { |
|
236 return aIndex < 0 || aIndex >= aLines.length; |
|
237 } |
|
238 |
|
239 Object.defineProperty(this, "lastWord", { get: function() |
|
240 { |
|
241 if (aIndex < 0) |
|
242 return new word(this, [], -1); |
|
243 if (aIndex >= aLines.length) |
|
244 return new word(this, [], 0); |
|
245 |
|
246 var words = aLines[aIndex][4].words; |
|
247 return new word(this, words, words.length - 2); |
|
248 } |
|
249 }); |
|
250 Object.defineProperty(this, "firstWord", { get: function() |
|
251 { |
|
252 if (aIndex < 0) |
|
253 return new word(this, [], -1); |
|
254 if (aIndex >= aLines.length) |
|
255 return new word(this, [], 0); |
|
256 |
|
257 var words = aLines[aIndex][4].words; |
|
258 return new word(this, words, 0); |
|
259 } |
|
260 }); |
|
261 |
|
262 this.isLastWord = function line_isLastWord(aWord) |
|
263 { |
|
264 var lastWord = this.lastWord; |
|
265 return lastWord.start == aWord.start && lastWord.end == aWord.end; |
|
266 } |
|
267 |
|
268 Object.defineProperty(this, "lineStartFailures", { get: function() |
|
269 { |
|
270 if (aIndex < 0 || aIndex >= aLines.length) |
|
271 return []; |
|
272 |
|
273 return aLines[aIndex][4].lsf || []; |
|
274 } |
|
275 }); |
|
276 Object.defineProperty(this, "lineEndFailures", { get: function() |
|
277 { |
|
278 if (aIndex < 0 || aIndex >= aLines.length) |
|
279 return []; |
|
280 |
|
281 return aLines[aIndex][4].lef || []; |
|
282 } |
|
283 }); |
|
284 } |
|
285 |
|
286 /** |
|
287 * A word object. Allows to navigate by words. |
|
288 */ |
|
289 function word(aLine, aWords, aIndex) |
|
290 { |
|
291 Object.defineProperty(this, "prevWord", { get: function() |
|
292 { |
|
293 if (aIndex >= 2) |
|
294 return new word(aLine, aWords, aIndex - 2); |
|
295 |
|
296 var prevLineLastWord = aLine.prevLine.lastWord; |
|
297 if (this.start == prevLineLastWord.start && !this.isFakeStartWord()) |
|
298 return prevLineLastWord.prevWord; |
|
299 return prevLineLastWord; |
|
300 } |
|
301 }); |
|
302 Object.defineProperty(this, "nextWord", { get: function() |
|
303 { |
|
304 if (aIndex + 2 < aWords.length) |
|
305 return new word(aLine, aWords, aIndex + 2); |
|
306 |
|
307 var nextLineFirstWord = aLine.nextLine.firstWord; |
|
308 if (this.end == nextLineFirstWord.end && !this.isFakeEndWord()) |
|
309 return nextLineFirstWord.nextWord; |
|
310 return nextLineFirstWord; |
|
311 } |
|
312 }); |
|
313 |
|
314 Object.defineProperty(this, "line", { get: function() { return aLine; } }); |
|
315 |
|
316 Object.defineProperty(this, "start", { get: function() |
|
317 { |
|
318 if (this.isFakeStartWord()) |
|
319 return 0; |
|
320 |
|
321 if (this.isFakeEndWord()) |
|
322 return aLine.end; |
|
323 return aWords[aIndex]; |
|
324 } |
|
325 }); |
|
326 Object.defineProperty(this, "end", { get: function() |
|
327 { |
|
328 if (this.isFakeStartWord()) |
|
329 return 0; |
|
330 |
|
331 return this.isFakeEndWord() ? aLine.end : aWords[aIndex + 1]; |
|
332 } |
|
333 }); |
|
334 |
|
335 this.toString = function word_toString() |
|
336 { |
|
337 var start = this.start, end = this.end; |
|
338 return "'" + aLine.wholeText.substring(start, end) + |
|
339 "' at [" + start + ", " + end + "]"; |
|
340 } |
|
341 |
|
342 this.isFakeStartWord = function() { return aIndex < 0; } |
|
343 this.isFakeEndWord = function() { return aIndex >= aWords.length; } |
|
344 } |
|
345 |
|
346 /** |
|
347 * A template invoker to move through the text. |
|
348 */ |
|
349 function tmpl_moveTo(aID, aInvokerFunc, aWholeText, aCharIter) |
|
350 { |
|
351 this.offset = aCharIter.offset; |
|
352 |
|
353 var checker = new caretMoveChecker(this.offset, aID); |
|
354 this.__proto__ = new (aInvokerFunc)(aID, checker); |
|
355 |
|
356 this.finalCheck = function genericMoveTo_finalCheck() |
|
357 { |
|
358 if (this.noTests()) |
|
359 return; |
|
360 |
|
361 for (var i = 0; i < this.tests.length; i++) { |
|
362 var func = this.tests[i][0]; |
|
363 var boundary = this.tests[i][1]; |
|
364 var startOffset = this.tests[i][2]; |
|
365 var endOffset = this.tests[i][3]; |
|
366 var text = aWholeText.substring(startOffset, endOffset); |
|
367 |
|
368 var isOk1 = kOk, isOk2 = kOk, isOk3 = kOk; |
|
369 for (var fIdx = 0; fIdx < this.failures.length; fIdx++) { |
|
370 var failure = this.failures[fIdx]; |
|
371 if (func.name.indexOf(failure[0]) != -1 && boundary == failure[1]) { |
|
372 isOk1 = failure[2]; |
|
373 isOk2 = failure[3]; |
|
374 isOk3 = failure[4]; |
|
375 } |
|
376 } |
|
377 |
|
378 func.call(null, kCaretOffset, boundary, text, startOffset, endOffset, |
|
379 aID, isOk1, isOk2, isOk3); |
|
380 } |
|
381 } |
|
382 |
|
383 this.getID = function genericMoveTo_getID() |
|
384 { |
|
385 return "move to " + this.offsetDescr; |
|
386 } |
|
387 |
|
388 this.noTests = function tmpl_moveTo_noTests() |
|
389 { |
|
390 return ("debugOffset" in aCharIter) && |
|
391 (aCharIter.debugOffset != this.offset); |
|
392 } |
|
393 |
|
394 this.offsetDescr = aCharIter.offsetDescr; |
|
395 this.tests = this.noTests() ? null : aCharIter.tests; |
|
396 this.failures = aCharIter.failures; |
|
397 } |
|
398 |
|
399 var gQueue = null; |
|
400 function doTest() |
|
401 { |
|
402 gQueue = new eventQueue(); |
|
403 |
|
404 // __a__w__o__r__d__\n |
|
405 // 0 1 2 3 4 5 |
|
406 // __t__w__o__ (soft line break) |
|
407 // 6 7 8 9 |
|
408 // __w__o__r__d__s |
|
409 // 10 11 12 13 14 15 |
|
410 |
|
411 traverseTextByLines(gQueue, "textarea", |
|
412 [ [ "aword", "\n", 0, 5, { words: [ 0, 5 ] } ], |
|
413 [ "two ", "", 6, 10, { words: [ 6, 9 ] } ], |
|
414 [ "words", "", 10, 15, { words: [ 10, 15 ] } ] |
|
415 ] ); |
|
416 |
|
417 var line2 = [ // " my " |
|
418 [ "TextBeforeOffset", BOUNDARY_WORD_END, kTodo, kTodo, kTodo ], |
|
419 [ "TextAfterOffset", BOUNDARY_WORD_END, kTodo, kTodo, kTodo ] |
|
420 ]; |
|
421 var line4 = [ // "riend" |
|
422 [ "TextBeforeOffset", BOUNDARY_WORD_END, kTodo, kTodo, kTodo ], |
|
423 [ "TextAfterOffset", BOUNDARY_WORD_END, kTodo, kTodo, kTodo ] |
|
424 ]; |
|
425 var line5 = [ // " t " |
|
426 [ "TextBeforeOffset", BOUNDARY_WORD_END, kTodo, kTodo, kTodo ], |
|
427 [ "TextAfterOffset", BOUNDARY_WORD_END, kTodo, kTodo, kTodo ] |
|
428 ]; |
|
429 traverseTextByLines(gQueue, "ta_wrapped", |
|
430 [ [ "hi ", "", 0, 3, { words: [ 0, 2 ] } ], |
|
431 [ "hello", "", 3, 8, { words: [ 3, 8 ] } ], |
|
432 [ " my ", "", 8, 12, { words: [ 9, 11 ], lsf: line2 } ], |
|
433 [ "longf", "", 12, 17, { words: [ 12, 17 ] } ], |
|
434 [ "riend", "", 17, 22, { words: [ 17, 22 ], lsf: line4 } ], |
|
435 [ " t ", "", 22, 25, { words: [ 23, 24 ], lsf: line5 } ], |
|
436 [ "sq t", "", 25, 29, { words: [ 25, 27, 28, 29 ] } ] |
|
437 ] ); |
|
438 |
|
439 gQueue.invoke(); // will call SimpleTest.finish(); |
|
440 } |
|
441 |
|
442 SimpleTest.waitForExplicitFinish(); |
|
443 addA11yLoadEvent(doTest); |
|
444 </script> |
|
445 </head> |
|
446 <body> |
|
447 |
|
448 <a target="_blank" |
|
449 title="nsIAccessibleText getText related functions tests at caret offset" |
|
450 href="https://bugzilla.mozilla.org/show_bug.cgi?id=852021"> |
|
451 Bug 852021 |
|
452 </a> |
|
453 <p id="display"></p> |
|
454 <div id="content" style="display: none"></div> |
|
455 <pre id="test"> |
|
456 |
|
457 <textarea id="textarea" cols="5">aword |
|
458 two words</textarea> |
|
459 |
|
460 <textarea id="ta_wrapped" cols="5">hi hello my longfriend t sq t</textarea> |
|
461 </pre> |
|
462 </body> |
|
463 </html> |