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