browser_caching_text_bounds.js (25159B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 requestLongerTimeout(3); 7 8 /* import-globals-from ../../mochitest/layout.js */ 9 loadScripts({ name: "layout.js", dir: MOCHITESTS_DIR }); 10 11 // Note that testTextNode, testChar and testTextRange currently don't handle 12 // white space in the code that doesn't get rendered on screen. To work around 13 // this, ensure that containers you want to test are all on a single line in the 14 // test snippet. 15 16 async function testTextNode(accDoc, browser, id) { 17 await testTextRange(accDoc, browser, id, 0, -1); 18 } 19 20 async function testChar(accDoc, browser, id, idx) { 21 await testTextRange(accDoc, browser, id, idx, idx + 1); 22 } 23 24 async function testTextRange(accDoc, browser, id, start, end) { 25 const r = await invokeContentTask( 26 browser, 27 [id, start, end], 28 (_id, _start, _end) => { 29 const htNode = content.document.getElementById(_id); 30 let [eX, eY, eW, eH] = [ 31 Number.MAX_SAFE_INTEGER, 32 Number.MAX_SAFE_INTEGER, 33 0, 34 0, 35 ]; 36 let traversed = 0; 37 let localStart = _start; 38 let endTraversal = false; 39 for (let element of htNode.childNodes) { 40 // ignore whitespace, but not embedded elements 41 let isEmbeddedElement = false; 42 if (element.length == undefined) { 43 let potentialTextContainer = element; 44 while ( 45 potentialTextContainer && 46 potentialTextContainer.length == undefined 47 ) { 48 potentialTextContainer = element.firstChild; 49 } 50 if (potentialTextContainer && potentialTextContainer.length) { 51 // If we can reach some text from this container, use that as part 52 // of our range. This is important when testing with intervening inline 53 // elements. ie. <pre><code>ab%0acd 54 element = potentialTextContainer; 55 } else if (element.firstChild) { 56 isEmbeddedElement = true; 57 } else { 58 continue; 59 } 60 } 61 if (element.length + traversed < _start) { 62 // If our start index is not within this 63 // node, keep looking. 64 traversed += element.length; 65 localStart -= element.length; 66 continue; 67 } 68 69 let rect; 70 if (isEmbeddedElement) { 71 rect = element.getBoundingClientRect(); 72 } else { 73 const range = content.document.createRange(); 74 range.setStart(element, localStart); 75 76 if (_end != -1 && _end - traversed <= element.length) { 77 // If the current node contains 78 // our end index, stop here. 79 endTraversal = true; 80 range.setEnd(element, _end - traversed); 81 } else { 82 range.setEnd(element, element.length); 83 } 84 85 rect = range.getBoundingClientRect(); 86 } 87 88 const oldX = eX == Number.MAX_SAFE_INTEGER ? 0 : eX; 89 const oldY = eY == Number.MAX_SAFE_INTEGER ? 0 : eY; 90 eX = Math.min(eX, rect.x); 91 eY = Math.min(eY, rect.y); 92 eW = Math.abs(Math.max(oldX + eW, rect.x + rect.width) - eX); 93 eH = Math.abs(Math.max(oldY + eH, rect.y + rect.height) - eY); 94 95 if (endTraversal) { 96 break; 97 } 98 localStart = 0; 99 traversed += element.length; 100 } 101 return [Math.round(eX), Math.round(eY), Math.round(eW), Math.round(eH)]; 102 } 103 ); 104 let hyperTextNode = findAccessibleChildByID(accDoc, id); 105 106 // Add in the doc's screen coords because getBoundingClientRect 107 // is relative to the document, not the screen. This assumes the doc's 108 // screen coords are correct. We use getBoundsInCSSPixels to avoid factoring 109 // in the DPR ourselves. 110 let x = {}; 111 let y = {}; 112 let w = {}; 113 let h = {}; 114 accDoc.getBoundsInCSSPixels(x, y, w, h); 115 r[0] += x.value; 116 r[1] += y.value; 117 if (end != -1 && end - start == 1) { 118 // If we're only testing a character, use this function because it calls 119 // CharBounds() directly instead of TextBounds(). 120 testTextPos(hyperTextNode, start, [r[0], r[1]], COORDTYPE_SCREEN_RELATIVE); 121 } else { 122 testTextBounds(hyperTextNode, start, end, r, COORDTYPE_SCREEN_RELATIVE); 123 } 124 } 125 126 /** 127 * Since testTextChar can't handle non-rendered white space, this function first 128 * uses testTextChar to verify the first character and then ensures all 129 * characters thereafter have an incrementing x and a non-0 width. 130 */ 131 async function testLineWithNonRenderedSpace(docAcc, browser, id, length) { 132 await testChar(docAcc, browser, id, 0); 133 const acc = findAccessibleChildByID(docAcc, id, [nsIAccessibleText]); 134 let prevX = -1; 135 for (let offset = 0; offset < length; ++offset) { 136 const x = {}; 137 const y = {}; 138 const w = {}; 139 const h = {}; 140 acc.getCharacterExtents(offset, x, y, w, h, COORDTYPE_SCREEN_RELATIVE); 141 Assert.greater( 142 x.value, 143 prevX, 144 `${id}: offset ${offset} x is larger (${x.value})` 145 ); 146 prevX = x.value; 147 Assert.greater(w.value, 0, `${id}: offset ${offset} width > 0`); 148 } 149 } 150 151 add_setup(async function () { 152 await SpecialPowers.pushPrefEnv({ 153 set: [["test.wait300msAfterTabSwitch", true]], 154 }); 155 }); 156 157 /** 158 * Test the text range boundary for simple LtR text 159 */ 160 addAccessibleTask( 161 ` 162 <p id='p1' style='font-family: monospace;'>Tilimilitryamdiya</p> 163 <p id='p2' style='font-family: monospace;'>ل</p> 164 <p id='p3' dir='ltr' style='font-family: monospace;'>Привіт Світ</p> 165 <pre id='p4' style='font-family: monospace;'>a%0abcdef</pre> 166 `, 167 async function (browser, accDoc) { 168 info("Testing simple LtR text"); 169 await testTextNode(accDoc, browser, "p1"); 170 await testTextNode(accDoc, browser, "p2"); 171 await testTextNode(accDoc, browser, "p3"); 172 await testTextNode(accDoc, browser, "p4"); 173 }, 174 { 175 iframe: true, 176 } 177 ); 178 179 /** 180 * Test the partial text range boundary for LtR text 181 */ 182 addAccessibleTask( 183 ` 184 <p id='p1' style='font-family: monospace;'>Tilimilitryamdiya</p> 185 <p id='p2' dir='ltr' style='font-family: monospace;'>Привіт Світ</p> 186 `, 187 async function (browser, accDoc) { 188 info("Testing partial ranges in LtR text"); 189 await testTextRange(accDoc, browser, "p1", 0, 4); 190 await testTextRange(accDoc, browser, "p1", 2, 8); 191 await testTextRange(accDoc, browser, "p1", 12, 17); 192 await testTextRange(accDoc, browser, "p2", 0, 4); 193 await testTextRange(accDoc, browser, "p2", 2, 8); 194 await testTextRange(accDoc, browser, "p2", 6, 11); 195 }, 196 { 197 topLevel: true, 198 iframe: true, 199 } 200 ); 201 202 /** 203 * Test the text boundary for multiline LtR text 204 */ 205 addAccessibleTask( 206 ` 207 <p id='p4' dir='ltr' style='font-family: monospace;'>Привіт Світ<br>Привіт Світ</p> 208 <p id='p5' dir='ltr' style='font-family: monospace;'>Привіт Світ<br> Я ще трохи тексту в другому рядку</p> 209 <p id='p6' style='font-family: monospace;'>hello world I'm on line one<br> and I'm a separate line two with slightly more text</p> 210 <p id='p7' style='font-family: monospace;'>hello world<br>hello world</p> 211 `, 212 async function (browser, accDoc) { 213 info("Testing multiline LtR text"); 214 await testTextNode(accDoc, browser, "p4"); 215 await testTextNode(accDoc, browser, "p5"); 216 // await testTextNode(accDoc, browser, "p6"); // w/o cache, fails width (a 259, e 250), w/ cache wrong w, h in iframe (line wrapping) 217 await testTextNode(accDoc, browser, "p7"); 218 }, 219 { 220 topLevel: true, 221 iframe: true, 222 } 223 ); 224 225 /** 226 * Test the text boundary for simple RtL text 227 */ 228 addAccessibleTask( 229 ` 230 <p id='p1' dir='rtl' style='font-family: monospace;'>Tilimilitryamdiya</p> 231 <p id='p2' dir='rtl' style='font-family: monospace;'>ل</p> 232 <p id='p3' dir='rtl' style='font-family: monospace;'>لل لللل لل</p> 233 <pre id='p4' dir='rtl' style='font-family: monospace;'>a%0abcdef</pre> 234 `, 235 async function (browser, accDoc) { 236 info("Testing simple RtL text"); 237 await testTextNode(accDoc, browser, "p1"); 238 await testTextNode(accDoc, browser, "p2"); 239 await testTextNode(accDoc, browser, "p3"); 240 await testTextNode(accDoc, browser, "p4"); 241 }, 242 { 243 topLevel: true, 244 iframe: true, 245 } 246 ); 247 248 /** 249 * Test the text boundary for multiline RtL text 250 */ 251 addAccessibleTask( 252 ` 253 <p id='p4' dir='rtl' style='font-family: monospace;'>لل لللل لل<br>لل لللل لل</p> 254 <p id='p5' dir='rtl' style='font-family: monospace;'>لل لللل لل<br> لل لل لل لل ل لل لل لل</p> 255 <p id='p6' dir='rtl' style='font-family: monospace;'>hello world I'm on line one<br> and I'm a separate line two with slightly more text</p> 256 <p id='p7' dir='rtl' style='font-family: monospace;'>hello world<br>hello world</p> 257 `, 258 async function (browser, accDoc) { 259 info("Testing multiline RtL text"); 260 await testTextNode(accDoc, browser, "p4"); 261 //await testTextNode(accDoc, browser, "p5"); // w/ cache fails x, w - off by one char 262 // await testTextNode(accDoc, browser, "p6"); // w/o cache, fails width (a 259, e 250), w/ cache fails w, h in iframe (line wrapping) 263 await testTextNode(accDoc, browser, "p7"); 264 }, 265 { 266 topLevel: true, 267 iframe: true, 268 } 269 ); 270 271 /** 272 * Test the partial text range boundary for RtL text 273 */ 274 addAccessibleTask( 275 ` 276 <p id='p1' dir='rtl' style='font-family: monospace;'>Tilimilitryamdiya</p> 277 <p id='p2' dir='rtl' style='font-family: monospace;'>لل لللل لل</p> 278 `, 279 async function (browser, accDoc) { 280 info("Testing partial ranges in RtL text"); 281 await testTextRange(accDoc, browser, "p1", 0, 4); 282 await testTextRange(accDoc, browser, "p1", 2, 8); 283 await testTextRange(accDoc, browser, "p1", 12, 17); 284 await testTextRange(accDoc, browser, "p2", 0, 4); 285 await testTextRange(accDoc, browser, "p2", 2, 8); 286 await testTextRange(accDoc, browser, "p2", 6, 10); 287 }, 288 { 289 topLevel: true, 290 iframe: true, 291 } 292 ); 293 294 /** 295 * Test simple vertical text in rl and lr layouts 296 */ 297 addAccessibleTask( 298 ` 299 <div style="writing-mode: vertical-rl;"> 300 <p id='p1'>你好世界</p> 301 <p id='p2'>hello world</p> 302 <br> 303 <p id='p3'>こんにちは世界</p> 304 </div> 305 <div style="writing-mode: vertical-lr;"> 306 <p id='p4'>你好世界</p> 307 <p id='p5'>hello world</p> 308 <br> 309 <p id='p6'>こんにちは世界</p> 310 </div> 311 `, 312 async function (browser, accDoc) { 313 info("Testing vertical-rl"); 314 await testTextNode(accDoc, browser, "p1"); 315 await testTextNode(accDoc, browser, "p2"); 316 await testTextNode(accDoc, browser, "p3"); 317 info("Testing vertical-lr"); 318 await testTextNode(accDoc, browser, "p4"); 319 await testTextNode(accDoc, browser, "p5"); 320 await testTextNode(accDoc, browser, "p6"); 321 }, 322 { 323 topLevel: true, 324 iframe: true, 325 } 326 ); 327 328 /** 329 * Test multiline vertical-rl text 330 */ 331 addAccessibleTask( 332 ` 333 <p id='p1' style='writing-mode: vertical-rl;'>你好世界<br>你好世界</p> 334 <p id='p2' style='writing-mode: vertical-rl;'>hello world<br>hello world</p> 335 <br> 336 <p id='p3' style='writing-mode: vertical-rl;'>你好世界<br> 你好世界 你好世界</p> 337 <p id='p4' style='writing-mode: vertical-rl;'>hello world<br> hello world hello world</p> 338 `, 339 async function (browser, accDoc) { 340 info("Testing vertical-rl multiline"); 341 await testTextNode(accDoc, browser, "p1"); 342 await testTextNode(accDoc, browser, "p2"); 343 await testTextNode(accDoc, browser, "p3"); 344 // await testTextNode(accDoc, browser, "p4"); // off by 4 with caching, iframe 345 }, 346 { 347 topLevel: true, 348 iframe: true, 349 } 350 ); 351 352 /** 353 * Test text with embedded chars 354 */ 355 addAccessibleTask( 356 `<p id='p1' style='font-family: monospace;'>hello <a href="google.com">world</a></p> 357 <p id='p2' style='font-family: monospace;'>hello<br><a href="google.com">world</a></p> 358 <div id='d3'><p></p>hello world</div> 359 <div id='d4'>hello world<p></p></div> 360 <div id='d5'>oh<p></p>hello world</div>`, 361 async function (browser, accDoc) { 362 info("Testing embedded chars"); 363 await testTextNode(accDoc, browser, "p1"); 364 await testTextNode(accDoc, browser, "p2"); 365 await testTextNode(accDoc, browser, "d3"); 366 await testTextNode(accDoc, browser, "d4"); 367 await testTextNode(accDoc, browser, "d5"); 368 }, 369 { 370 topLevel: true, 371 iframe: true, 372 } 373 ); 374 375 /** 376 * Test bounds after text mutations. 377 */ 378 addAccessibleTask( 379 `<p id="p">a</p>`, 380 async function (browser, docAcc) { 381 await testTextNode(docAcc, browser, "p"); 382 const p = findAccessibleChildByID(docAcc, "p"); 383 info("Appending a character to text leaf"); 384 let textInserted = waitForEvent(EVENT_TEXT_INSERTED, p); 385 await invokeContentTask(browser, [], () => { 386 content.document.getElementById("p").firstChild.data = "ab"; 387 }); 388 await textInserted; 389 await testTextNode(docAcc, browser, "p"); 390 }, 391 { 392 chrome: true, 393 topLevel: true, 394 iframe: true, 395 } 396 ); 397 398 /** 399 * Test character bounds on the insertion point at the end of a text box. 400 */ 401 addAccessibleTask( 402 `<input id="input" value="a">`, 403 async function (browser, docAcc) { 404 const input = findAccessibleChildByID(docAcc, "input"); 405 testTextPos(input, 1, [0, 0], COORDTYPE_SCREEN_RELATIVE); 406 }, 407 { 408 chrome: true, 409 topLevel: true, 410 iframe: true, 411 } 412 ); 413 414 /** 415 * Test character bounds after non-br line break. 416 */ 417 addAccessibleTask( 418 ` 419 <style> 420 @font-face { 421 font-family: Ahem; 422 src: url(${CURRENT_CONTENT_DIR}e10s/fonts/Ahem.sjs); 423 } 424 pre { 425 font: 20px/20px Ahem; 426 } 427 </style> 428 <pre id="t">XX 429 XXX</pre>`, 430 async function (browser, docAcc) { 431 await testChar(docAcc, browser, "t", 3); 432 }, 433 { 434 chrome: true, 435 topLevel: true, 436 iframe: true, 437 } 438 ); 439 440 /** 441 * Test character bounds in a pre with padding. 442 */ 443 addAccessibleTask( 444 ` 445 <style> 446 @font-face { 447 font-family: Ahem; 448 src: url(${CURRENT_CONTENT_DIR}e10s/fonts/Ahem.sjs); 449 } 450 pre { 451 font: 20px/20px Ahem; 452 padding: 20px; 453 } 454 </style> 455 <pre id="t">XX 456 XXX</pre>`, 457 async function (browser, docAcc) { 458 await testTextNode(docAcc, browser, "t"); 459 await testChar(docAcc, browser, "t", 3); 460 }, 461 { 462 chrome: true, 463 topLevel: true, 464 iframe: true, 465 } 466 ); 467 468 /** 469 * Test text bounds with an invalid end offset. 470 */ 471 addAccessibleTask( 472 `<p id="p">a</p>`, 473 async function (browser, docAcc) { 474 const p = findAccessibleChildByID(docAcc, "p"); 475 testTextBounds(p, 0, 2, [0, 0, 0, 0], COORDTYPE_SCREEN_RELATIVE); 476 }, 477 { chrome: true, topLevel: !true } 478 ); 479 480 /** 481 * Test character bounds in an intervening inline element with non-br line breaks 482 */ 483 addAccessibleTask( 484 ` 485 <style> 486 @font-face { 487 font-family: Ahem; 488 src: url(${CURRENT_CONTENT_DIR}e10s/fonts/Ahem.sjs); 489 } 490 pre { 491 font: 20px/20px Ahem; 492 } 493 </style> 494 <pre id="t"><code role="none">XX 495 XXX 496 XX 497 X</pre>`, 498 async function (browser, docAcc) { 499 await testChar(docAcc, browser, "t", 0); 500 await testChar(docAcc, browser, "t", 3); 501 await testChar(docAcc, browser, "t", 7); 502 await testChar(docAcc, browser, "t", 10); 503 }, 504 { 505 chrome: true, 506 topLevel: true, 507 iframe: true, 508 } 509 ); 510 511 /** 512 * Test character bounds in an intervening inline element with margins 513 * and with non-br line breaks 514 */ 515 addAccessibleTask( 516 ` 517 <style> 518 @font-face { 519 font-family: Ahem; 520 src: url(${CURRENT_CONTENT_DIR}e10s/fonts/Ahem.sjs); 521 } 522 </style> 523 <div>hello<pre id="t" style="margin-left:100px;margin-top:30px;background-color:blue;">XX 524 XXX 525 XX 526 X</pre></div>`, 527 async function (browser, docAcc) { 528 await testChar(docAcc, browser, "t", 0); 529 await testChar(docAcc, browser, "t", 3); 530 await testChar(docAcc, browser, "t", 7); 531 await testChar(docAcc, browser, "t", 10); 532 }, 533 { 534 chrome: true, 535 topLevel: true, 536 iframe: true, 537 } 538 ); 539 540 /** 541 * Test text bounds in a textarea after scrolling. 542 */ 543 addAccessibleTask( 544 ` 545 <textarea id="textarea" rows="1">a 546 b 547 c</textarea> 548 `, 549 async function (browser, docAcc) { 550 // We can't use testChar because Range.getBoundingClientRect isn't supported 551 // inside textareas. 552 const textarea = findAccessibleChildByID(docAcc, "textarea"); 553 textarea.QueryInterface(nsIAccessibleText); 554 const oldY = {}; 555 textarea.getCharacterExtents( 556 4, 557 {}, 558 oldY, 559 {}, 560 {}, 561 COORDTYPE_SCREEN_RELATIVE 562 ); 563 info("Moving textarea caret to c"); 564 await invokeContentTask(browser, [], () => { 565 const textareaDom = content.document.getElementById("textarea"); 566 textareaDom.focus(); 567 textareaDom.selectionStart = 4; 568 }); 569 await waitForContentPaint(browser); 570 const newY = {}; 571 textarea.getCharacterExtents( 572 4, 573 {}, 574 newY, 575 {}, 576 {}, 577 COORDTYPE_SCREEN_RELATIVE 578 ); 579 Assert.less( 580 newY.value, 581 oldY.value, 582 "y coordinate smaller after scrolling down" 583 ); 584 }, 585 { chrome: true, topLevel: true, iframe: !true } 586 ); 587 588 /** 589 * Test magic offsets with GetCharacter/RangeExtents. 590 */ 591 addAccessibleTask( 592 `<input id="input" value="abc">`, 593 async function (browser, docAcc) { 594 const input = findAccessibleChildByID(docAcc, "input", [nsIAccessibleText]); 595 info("Setting caret and focusing input"); 596 let caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, input); 597 await invokeContentTask(browser, [], () => { 598 const inputDom = content.document.getElementById("input"); 599 inputDom.selectionStart = inputDom.selectionEnd = 1; 600 inputDom.focus(); 601 }); 602 await caretMoved; 603 is(input.caretOffset, 1, "input caretOffset is 1"); 604 let expectedX = {}; 605 let expectedY = {}; 606 let expectedW = {}; 607 let expectedH = {}; 608 let magicX = {}; 609 let magicY = {}; 610 let magicW = {}; 611 let magicH = {}; 612 input.getCharacterExtents( 613 1, 614 expectedX, 615 expectedY, 616 expectedW, 617 expectedH, 618 COORDTYPE_SCREEN_RELATIVE 619 ); 620 input.getCharacterExtents( 621 nsIAccessibleText.TEXT_OFFSET_CARET, 622 magicX, 623 magicY, 624 magicW, 625 magicH, 626 COORDTYPE_SCREEN_RELATIVE 627 ); 628 Assert.deepEqual( 629 [magicX.value, magicY.value, magicW.value, magicH.value], 630 [expectedX.value, expectedY.value, expectedW.value, expectedH.value], 631 "GetCharacterExtents correct with TEXT_OFFSET_CARET" 632 ); 633 input.getRangeExtents( 634 1, 635 3, 636 expectedX, 637 expectedY, 638 expectedW, 639 expectedH, 640 COORDTYPE_SCREEN_RELATIVE 641 ); 642 input.getRangeExtents( 643 nsIAccessibleText.TEXT_OFFSET_CARET, 644 nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT, 645 magicX, 646 magicY, 647 magicW, 648 magicH, 649 COORDTYPE_SCREEN_RELATIVE 650 ); 651 Assert.deepEqual( 652 [magicX.value, magicY.value, magicW.value, magicH.value], 653 [expectedX.value, expectedY.value, expectedW.value, expectedH.value], 654 "GetRangeExtents correct with TEXT_OFFSET_CARET/END_OF_TEXT" 655 ); 656 }, 657 { chrome: true, topLevel: true, remoteIframe: !true } 658 ); 659 660 /** 661 * Test wrapped text and pre-formatted text beginning with an empty line. 662 */ 663 addAccessibleTask( 664 ` 665 <style> 666 #wrappedText { 667 width: 3ch; 668 font-family: monospace; 669 } 670 </style> 671 <p id="wrappedText"><a href="https://example.com/">a</a>b cd</p> 672 <p id="emptyFirstLine" style="white-space: pre-line;"> 673 foo</p> 674 `, 675 async function (browser, docAcc) { 676 await testChar(docAcc, browser, "wrappedText", 0); 677 await testChar(docAcc, browser, "wrappedText", 1); 678 await testChar(docAcc, browser, "wrappedText", 2); 679 await testChar(docAcc, browser, "wrappedText", 3); 680 await testChar(docAcc, browser, "wrappedText", 4); 681 682 // We can't use testChar for emptyFirstLine because it doesn't handle white 683 // space properly. Instead, verify that the first character is at the top 684 // left of the text leaf. 685 const emptyFirstLine = findAccessibleChildByID(docAcc, "emptyFirstLine", [ 686 nsIAccessibleText, 687 ]); 688 const emptyFirstLineLeaf = emptyFirstLine.firstChild; 689 const leafX = {}; 690 const leafY = {}; 691 emptyFirstLineLeaf.getBounds(leafX, leafY, {}, {}); 692 testTextPos( 693 emptyFirstLine, 694 0, 695 [leafX.value, leafY.value], 696 COORDTYPE_SCREEN_RELATIVE 697 ); 698 }, 699 { chrome: true, topLevel: true, remoteIframe: !true } 700 ); 701 702 /** 703 * Test character bounds in an intervening inline element with non-br line breaks 704 */ 705 addAccessibleTask( 706 ` 707 <style> 708 @font-face { 709 font-family: Ahem; 710 src: url(${CURRENT_CONTENT_DIR}e10s/fonts/Ahem.sjs); 711 } 712 pre { 713 font: 20px/20px Ahem; 714 } 715 </style> 716 <pre><code id="t" role="group">XX 717 XXX 718 XX 719 X</pre>`, 720 async function (browser, docAcc) { 721 await testChar(docAcc, browser, "t", 0); 722 await testChar(docAcc, browser, "t", 3); 723 await testChar(docAcc, browser, "t", 7); 724 await testChar(docAcc, browser, "t", 10); 725 }, 726 { 727 chrome: true, 728 topLevel: true, 729 iframe: true, 730 } 731 ); 732 733 /** 734 * Test character bounds where content white space isn't rendered. 735 */ 736 addAccessibleTask( 737 ` 738 <p id="single">a b</p> 739 <p id="multi"><ins>a </ins> 740 b</p> 741 <pre id="pre">a b</pre> 742 `, 743 async function (browser, docAcc) { 744 await testLineWithNonRenderedSpace(docAcc, browser, "single", 3); 745 await testLineWithNonRenderedSpace(docAcc, browser, "multi", 2); 746 for (let offset = 0; offset < 4; ++offset) { 747 await testChar(docAcc, browser, "pre", offset); 748 } 749 }, 750 { chrome: true, topLevel: true } 751 ); 752 753 function getCharacterExtents(acc, offset) { 754 const x = {}; 755 const y = {}; 756 const w = {}; 757 const h = {}; 758 acc.getCharacterExtents(offset, x, y, w, h, COORDTYPE_SCREEN_RELATIVE); 759 return [x.value, y.value, w.value, h.value]; 760 } 761 762 /** 763 * Test character bounds of line feed characters in a textarea. 764 */ 765 addAccessibleTask( 766 ` 767 <textarea id="textarea">a 768 769 b</textarea> 770 `, 771 async function testLineFeedTextarea(browser, docAcc) { 772 // We can't use testChar because it doesn't know how to handle line feeds. 773 // We check relative to other characters instead. 774 const textarea = findAccessibleChildByID(docAcc, "textarea", [ 775 nsIAccessibleText, 776 ]); 777 const [x0, y0, ,] = getCharacterExtents(textarea, 0); 778 const [x1, y1, w1, h1] = getCharacterExtents(textarea, 1); 779 const [x2, y2, w2, h2] = getCharacterExtents(textarea, 2); 780 const [x3, y3, ,] = getCharacterExtents(textarea, 3); 781 // Character 0 is a letter on the first line. 782 // Character 1 is a line feed at the end of the first line. 783 Assert.greater(x1, x0, "x1 > x0"); 784 is(y1, y0, "y1 == y0"); 785 Assert.greater(w1, 0, "w1 > 0"); 786 Assert.greater(h1, 0, "h1 > 0"); 787 // Character 2 is a line feed on a blank line. 788 is(x2, x0, "x2 == x0"); 789 Assert.greaterOrEqual(y2, y1 + h1, "y2 >= y1 + h1"); 790 Assert.greater(w2, 0, "w2 > 0"); 791 Assert.greater(h2, 0, "h2 > 0"); 792 // Character 3 is a letter on the final line. 793 is(x3, x0, "x3 == x0"); 794 Assert.greaterOrEqual(y3, y2 + h2, "y3 >= y2 + h2"); 795 }, 796 { chrome: true, topLevel: true } 797 ); 798 799 /** 800 * Test line feed characters in a contentEditable. 801 */ 802 addAccessibleTask( 803 ` 804 <div contenteditable role="textbox"> 805 <div id="ce0">a</div> 806 <div id="ce1"><br></div> 807 <div id="ce2">b</div> 808 </div> 809 `, 810 async function testLineFeedEditable(browser, docAcc) { 811 // We can't use testChar because it doesn't know how to handle line feeds. 812 // We check relative to other characters instead. 813 const ce0 = findAccessibleChildByID(docAcc, "ce0", [nsIAccessibleText]); 814 const [x0, y0, ,] = getCharacterExtents(ce0, 0); 815 const ce1 = findAccessibleChildByID(docAcc, "ce1", [nsIAccessibleText]); 816 const [x1, y1, w1, h1] = getCharacterExtents(ce1, 0); 817 const ce2 = findAccessibleChildByID(docAcc, "ce2", [nsIAccessibleText]); 818 const [x2, y2, ,] = getCharacterExtents(ce2, 0); 819 // Character 0 is a letter on the first line. 820 // Character 1 is a line feed on a blank line. 821 is(x1, x0, "x1 == x0"); 822 Assert.greater(y1, y0, "y1 > y0"); 823 Assert.greater(w1, 0, "w1 > 0"); 824 Assert.greater(h1, 0, "h1 > 0"); 825 // Character 2 is a letter on the final line. 826 is(x2, x0, "x2 == x0"); 827 Assert.greaterOrEqual(y2, y1 + h1, "y2 >= y1 + h1"); 828 }, 829 { chrome: true, topLevel: true } 830 ); 831 832 /** 833 * Test list bullets. 834 */ 835 addAccessibleTask( 836 `<ul><li id="li" style="font-family: monospace;">a</li></ul>`, 837 async function testBullet(browser, docAcc) { 838 // We can't use testChar because it doesn't know how to handle list bullets. 839 // We check relative to other characters instead. 840 const li = findAccessibleChildByID(docAcc, "li", [nsIAccessibleText]); 841 const [x0, y0, w0, h0] = getCharacterExtents(li, 0); 842 const [x1, y1, w1, h1] = getCharacterExtents(li, 1); 843 const [x2, y2, ,] = getCharacterExtents(li, 2); 844 // Characters 0 and 1 are the bullet. 845 // Character 2 is a letter. 846 Assert.less(x0, x2, "x0 < x2"); 847 isWithin(y0, y2, 5, "y0 ~ y2"); 848 Assert.greater(w0, 0, "w0 > 0"); 849 Assert.greater(h0, 0, "h0 > 0"); 850 Assert.less(x1, x2, "x1 < x2"); 851 isWithin(y1, y2, 5, "y1 ~ y2"); 852 Assert.greater(w1, 0, "w1 > 0"); 853 Assert.greater(h1, 0, "h1 > 0"); 854 }, 855 { chrome: true, topLevel: true } 856 );