tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

browser_text_caret.js (20046B)


      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 
      7 /* import-globals-from ../../mochitest/attributes.js */
      8 loadScripts({ name: "attributes.js", dir: MOCHITESTS_DIR });
      9 /* import-globals-from ../../mochitest/text.js */
     10 
     11 /**
     12 * Test caret retrieval.
     13 */
     14 addAccessibleTask(
     15  `
     16 <textarea id="textarea"
     17          spellcheck="false"
     18          style="scrollbar-width: none; font-family: 'Liberation Mono', monospace;"
     19          cols="6">ab cd e</textarea>
     20 <textarea id="empty"></textarea>
     21 <div id="contentEditable" contenteditable>a<span>b</span></div>
     22  `,
     23  async function (browser, docAcc) {
     24    const textarea = findAccessibleChildByID(docAcc, "textarea", [
     25      nsIAccessibleText,
     26    ]);
     27    let caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
     28    textarea.takeFocus();
     29    let evt = await caretMoved;
     30    is(textarea.caretOffset, 0, "Initial caret offset is 0");
     31    evt.QueryInterface(nsIAccessibleCaretMoveEvent);
     32    ok(!evt.isAtEndOfLine, "Caret is not at end of line");
     33    testTextAtOffset(
     34      kCaretOffset,
     35      BOUNDARY_CHAR,
     36      "a",
     37      0,
     38      1,
     39      textarea,
     40      kOk,
     41      kOk,
     42      kOk
     43    );
     44    testTextAtOffset(
     45      kCaretOffset,
     46      BOUNDARY_WORD_START,
     47      "ab ",
     48      0,
     49      3,
     50      textarea,
     51      kOk,
     52      kOk,
     53      kOk
     54    );
     55    testTextAtOffset(
     56      kCaretOffset,
     57      BOUNDARY_LINE_START,
     58      "ab cd ",
     59      0,
     60      6,
     61      textarea,
     62      kOk,
     63      kOk,
     64      kOk
     65    );
     66 
     67    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
     68    EventUtils.synthesizeKey("KEY_ArrowRight");
     69    evt = await caretMoved;
     70    is(textarea.caretOffset, 1, "Caret offset is 1 after ArrowRight");
     71    evt.QueryInterface(nsIAccessibleCaretMoveEvent);
     72    ok(!evt.isAtEndOfLine, "Caret is not at end of line");
     73    testTextAtOffset(
     74      kCaretOffset,
     75      BOUNDARY_CHAR,
     76      "b",
     77      1,
     78      2,
     79      textarea,
     80      kOk,
     81      kOk,
     82      kOk
     83    );
     84    testTextAtOffset(
     85      kCaretOffset,
     86      BOUNDARY_WORD_START,
     87      "ab ",
     88      0,
     89      3,
     90      textarea,
     91      kOk,
     92      kOk,
     93      kOk
     94    );
     95    testTextAtOffset(
     96      kCaretOffset,
     97      BOUNDARY_LINE_START,
     98      "ab cd ",
     99      0,
    100      6,
    101      textarea,
    102      kOk,
    103      kOk,
    104      kOk
    105    );
    106 
    107    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
    108    EventUtils.synthesizeKey("KEY_ArrowRight");
    109    evt = await caretMoved;
    110    is(textarea.caretOffset, 2, "Caret offset is 2 after ArrowRight");
    111    evt.QueryInterface(nsIAccessibleCaretMoveEvent);
    112    ok(!evt.isAtEndOfLine, "Caret is not at end of line");
    113    testTextAtOffset(
    114      kCaretOffset,
    115      BOUNDARY_CHAR,
    116      " ",
    117      2,
    118      3,
    119      textarea,
    120      kOk,
    121      kOk,
    122      kOk
    123    );
    124    testTextAtOffset(
    125      kCaretOffset,
    126      BOUNDARY_WORD_START,
    127      "ab ",
    128      0,
    129      3,
    130      textarea,
    131      kOk,
    132      kOk,
    133      kOk
    134    );
    135    testTextAtOffset(
    136      kCaretOffset,
    137      BOUNDARY_LINE_START,
    138      "ab cd ",
    139      0,
    140      6,
    141      textarea,
    142      kOk,
    143      kOk,
    144      kOk
    145    );
    146 
    147    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
    148    EventUtils.synthesizeKey("KEY_ArrowRight");
    149    evt = await caretMoved;
    150    is(textarea.caretOffset, 3, "Caret offset is 3 after ArrowRight");
    151    evt.QueryInterface(nsIAccessibleCaretMoveEvent);
    152    ok(!evt.isAtEndOfLine, "Caret is not at end of line");
    153    testTextAtOffset(
    154      kCaretOffset,
    155      BOUNDARY_CHAR,
    156      "c",
    157      3,
    158      4,
    159      textarea,
    160      kOk,
    161      kOk,
    162      kOk
    163    );
    164    testTextAtOffset(
    165      kCaretOffset,
    166      BOUNDARY_WORD_START,
    167      "cd ",
    168      3,
    169      6,
    170      textarea,
    171      kOk,
    172      kOk,
    173      kOk
    174    );
    175    testTextAtOffset(
    176      kCaretOffset,
    177      BOUNDARY_LINE_START,
    178      "ab cd ",
    179      0,
    180      6,
    181      textarea,
    182      kOk,
    183      kOk,
    184      kOk
    185    );
    186 
    187    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
    188    EventUtils.synthesizeKey("KEY_ArrowRight");
    189    evt = await caretMoved;
    190    is(textarea.caretOffset, 4, "Caret offset is 4 after ArrowRight");
    191    evt.QueryInterface(nsIAccessibleCaretMoveEvent);
    192    ok(!evt.isAtEndOfLine, "Caret is not at end of line");
    193    testTextAtOffset(
    194      kCaretOffset,
    195      BOUNDARY_CHAR,
    196      "d",
    197      4,
    198      5,
    199      textarea,
    200      kOk,
    201      kOk,
    202      kOk
    203    );
    204    testTextAtOffset(
    205      kCaretOffset,
    206      BOUNDARY_WORD_START,
    207      "cd ",
    208      3,
    209      6,
    210      textarea,
    211      kOk,
    212      kOk,
    213      kOk
    214    );
    215    testTextAtOffset(
    216      kCaretOffset,
    217      BOUNDARY_LINE_START,
    218      "ab cd ",
    219      0,
    220      6,
    221      textarea,
    222      kOk,
    223      kOk,
    224      kOk
    225    );
    226 
    227    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
    228    EventUtils.synthesizeKey("KEY_ArrowRight");
    229    evt = await caretMoved;
    230    is(textarea.caretOffset, 5, "Caret offset is 5 after ArrowRight");
    231    evt.QueryInterface(nsIAccessibleCaretMoveEvent);
    232    ok(!evt.isAtEndOfLine, "Caret is not at end of line");
    233    testTextAtOffset(
    234      kCaretOffset,
    235      BOUNDARY_CHAR,
    236      " ",
    237      5,
    238      6,
    239      textarea,
    240      kOk,
    241      kOk,
    242      kOk
    243    );
    244    testTextAtOffset(
    245      kCaretOffset,
    246      BOUNDARY_WORD_START,
    247      "cd ",
    248      3,
    249      6,
    250      textarea,
    251      kOk,
    252      kOk,
    253      kOk
    254    );
    255    testTextAtOffset(
    256      kCaretOffset,
    257      BOUNDARY_LINE_START,
    258      "ab cd ",
    259      0,
    260      6,
    261      textarea,
    262      kOk,
    263      kOk,
    264      kOk
    265    );
    266 
    267    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
    268    EventUtils.synthesizeKey("KEY_ArrowRight");
    269    evt = await caretMoved;
    270    is(textarea.caretOffset, 6, "Caret offset is 6 after ArrowRight");
    271    evt.QueryInterface(nsIAccessibleCaretMoveEvent);
    272    ok(evt.isAtEndOfLine, "Caret is at end of line");
    273    testTextAtOffset(
    274      kCaretOffset,
    275      BOUNDARY_CHAR,
    276      "",
    277      6,
    278      6,
    279      textarea,
    280      kOk,
    281      kOk,
    282      kOk
    283    );
    284    testTextAtOffset(
    285      kCaretOffset,
    286      BOUNDARY_CLUSTER,
    287      "",
    288      6,
    289      6,
    290      textarea,
    291      kOk,
    292      kOk,
    293      kOk
    294    );
    295    testTextAtOffset(
    296      kCaretOffset,
    297      BOUNDARY_WORD_START,
    298      "cd ",
    299      3,
    300      6,
    301      textarea,
    302      kOk,
    303      kOk,
    304      kOk
    305    );
    306    testTextAtOffset(
    307      kCaretOffset,
    308      BOUNDARY_LINE_START,
    309      "ab cd ",
    310      0,
    311      6,
    312      textarea,
    313      kOk,
    314      kOk,
    315      kOk
    316    );
    317 
    318    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
    319    EventUtils.synthesizeKey("KEY_ArrowRight");
    320    evt = await caretMoved;
    321    is(textarea.caretOffset, 6, "Caret offset remains 6 after ArrowRight");
    322    evt.QueryInterface(nsIAccessibleCaretMoveEvent);
    323    ok(!evt.isAtEndOfLine, "Caret is not at end of line");
    324    // Caret is at start of second line.
    325    testTextAtOffset(
    326      kCaretOffset,
    327      BOUNDARY_CHAR,
    328      "e",
    329      6,
    330      7,
    331      textarea,
    332      kOk,
    333      kOk,
    334      kOk
    335    );
    336    testTextAtOffset(
    337      kCaretOffset,
    338      BOUNDARY_WORD_START,
    339      "e",
    340      6,
    341      7,
    342      textarea,
    343      kOk,
    344      kOk,
    345      kOk
    346    );
    347    testTextAtOffset(
    348      kCaretOffset,
    349      BOUNDARY_LINE_START,
    350      "e",
    351      6,
    352      7,
    353      textarea,
    354      kOk,
    355      kOk,
    356      kOk
    357    );
    358 
    359    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
    360    EventUtils.synthesizeKey("KEY_ArrowRight");
    361    evt = await caretMoved;
    362    is(textarea.caretOffset, 7, "Caret offset is 7 after ArrowRight");
    363    evt.QueryInterface(nsIAccessibleCaretMoveEvent);
    364    ok(evt.isAtEndOfLine, "Caret is at end of line");
    365    // Caret is at end of textarea.
    366    testTextAtOffset(
    367      kCaretOffset,
    368      BOUNDARY_CHAR,
    369      "",
    370      7,
    371      7,
    372      textarea,
    373      kOk,
    374      kOk,
    375      kOk
    376    );
    377    testTextAtOffset(
    378      kCaretOffset,
    379      BOUNDARY_WORD_START,
    380      "e",
    381      6,
    382      7,
    383      textarea,
    384      kOk,
    385      kOk,
    386      kOk
    387    );
    388    testTextAtOffset(
    389      kCaretOffset,
    390      BOUNDARY_LINE_START,
    391      "e",
    392      6,
    393      7,
    394      textarea,
    395      kOk,
    396      kOk,
    397      kOk
    398    );
    399 
    400    // BrowserTestUtils.synthesizeMouseAtPoint takes coordinates relative to the document.
    401    const docX = {};
    402    const docY = {};
    403    docAcc.getBounds(docX, docY, {}, {});
    404    let charX = {};
    405    let charY = {};
    406    textarea.getCharacterExtents(
    407      0,
    408      charX,
    409      charY,
    410      {},
    411      {},
    412      COORDTYPE_SCREEN_RELATIVE
    413    );
    414    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
    415    await BrowserTestUtils.synthesizeMouseAtPoint(
    416      charX.value - docX.value,
    417      charY.value - docY.value,
    418      {},
    419      docAcc.browsingContext
    420    );
    421    evt = await caretMoved;
    422    is(textarea.caretOffset, 0, "Caret offset is 0 after click");
    423    evt.QueryInterface(nsIAccessibleCaretMoveEvent);
    424    ok(!evt.isAtEndOfLine, "Caret is not at end of line");
    425    testTextAtOffset(
    426      kCaretOffset,
    427      BOUNDARY_CHAR,
    428      "a",
    429      0,
    430      1,
    431      textarea,
    432      kOk,
    433      kOk,
    434      kOk
    435    );
    436    textarea.getCharacterExtents(
    437      1,
    438      charX,
    439      charY,
    440      {},
    441      {},
    442      COORDTYPE_SCREEN_RELATIVE
    443    );
    444    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
    445    await BrowserTestUtils.synthesizeMouseAtPoint(
    446      charX.value - docX.value,
    447      charY.value - docY.value,
    448      {},
    449      docAcc.browsingContext
    450    );
    451    evt = await caretMoved;
    452    is(textarea.caretOffset, 1, "Caret offset is 1 after click");
    453    evt.QueryInterface(nsIAccessibleCaretMoveEvent);
    454    ok(!evt.isAtEndOfLine, "Caret is not at end of line");
    455    testTextAtOffset(
    456      kCaretOffset,
    457      BOUNDARY_CHAR,
    458      "b",
    459      1,
    460      2,
    461      textarea,
    462      kOk,
    463      kOk,
    464      kOk
    465    );
    466 
    467    const empty = findAccessibleChildByID(docAcc, "empty", [nsIAccessibleText]);
    468    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, empty);
    469    empty.takeFocus();
    470    evt = await caretMoved;
    471    is(empty.caretOffset, 0, "Caret offset in empty textarea is 0");
    472    evt.QueryInterface(nsIAccessibleCaretMoveEvent);
    473    ok(!evt.isAtEndOfLine, "Caret is not at end of line");
    474 
    475    const contentEditable = findAccessibleChildByID(docAcc, "contentEditable", [
    476      nsIAccessibleText,
    477    ]);
    478    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, contentEditable);
    479    contentEditable.takeFocus();
    480    evt = await caretMoved;
    481    is(
    482      contentEditable.caretOffset,
    483      0,
    484      "Initial caret offset in contentEditable is 0"
    485    );
    486    evt.QueryInterface(nsIAccessibleCaretMoveEvent);
    487    ok(!evt.isAtEndOfLine, "Caret is not at end of line");
    488    testTextAtOffset(
    489      kCaretOffset,
    490      BOUNDARY_CHAR,
    491      "a",
    492      0,
    493      1,
    494      contentEditable,
    495      kOk,
    496      kOk,
    497      kOk
    498    );
    499    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, contentEditable);
    500    EventUtils.synthesizeKey("KEY_ArrowRight");
    501    evt = await caretMoved;
    502    is(contentEditable.caretOffset, 1, "Caret offset is 1 after ArrowRight");
    503    evt.QueryInterface(nsIAccessibleCaretMoveEvent);
    504    ok(!evt.isAtEndOfLine, "Caret is not at end of line");
    505    testTextAtOffset(
    506      kCaretOffset,
    507      BOUNDARY_CHAR,
    508      "b",
    509      1,
    510      2,
    511      contentEditable,
    512      kOk,
    513      kOk,
    514      kOk
    515    );
    516  },
    517  { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
    518 );
    519 
    520 /**
    521 * Test setting the caret.
    522 */
    523 addAccessibleTask(
    524  `
    525 <textarea id="textarea">ab\nc</textarea>
    526 <div id="editable" contenteditable>
    527  <p id="p">a<a id="link" href="https://example.com/">b</a></p>
    528 </div>
    529  `,
    530  async function (browser, docAcc) {
    531    const textarea = findAccessibleChildByID(docAcc, "textarea", [
    532      nsIAccessibleText,
    533    ]);
    534    info("textarea: Set caret offset to 0");
    535    let focused = waitForEvent(EVENT_FOCUS, textarea);
    536    let caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
    537    textarea.caretOffset = 0;
    538    await focused;
    539    await caretMoved;
    540    is(textarea.caretOffset, 0, "textarea caret correct");
    541    // Test setting caret to another line.
    542    info("textarea: Set caret offset to 3");
    543    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
    544    textarea.caretOffset = 3;
    545    await caretMoved;
    546    is(textarea.caretOffset, 3, "textarea caret correct");
    547    // Test setting caret to the end.
    548    info("textarea: Set caret offset to 4 (end)");
    549    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
    550    textarea.caretOffset = 4;
    551    await caretMoved;
    552    is(textarea.caretOffset, 4, "textarea caret correct");
    553 
    554    const editable = findAccessibleChildByID(docAcc, "editable", [
    555      nsIAccessibleText,
    556    ]);
    557    focused = waitForEvent(EVENT_FOCUS, editable);
    558    editable.takeFocus();
    559    await focused;
    560    const p = findAccessibleChildByID(docAcc, "p", [nsIAccessibleText]);
    561    info("p: Set caret offset to 0");
    562    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, p);
    563    p.caretOffset = 0;
    564    await focused;
    565    await caretMoved;
    566    is(p.caretOffset, 0, "p caret correct");
    567    const link = findAccessibleChildByID(docAcc, "link", [nsIAccessibleText]);
    568    info("link: Set caret offset to 0");
    569    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, link);
    570    link.caretOffset = 0;
    571    await caretMoved;
    572    is(link.caretOffset, 0, "link caret correct");
    573  },
    574  { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
    575 );
    576 
    577 /**
    578 * Test setting the caret in a contentEditable which is aria-hidden. Arguably,
    579 * we shouldn't fire caret events at all in this case, but really, this is just
    580 * bad authoring and shouldn't happen. We just need to make sure that the
    581 * offsets we do fire are at least valid so we don't trigger assertions or
    582 * confuse clients.
    583 */
    584 addAccessibleTask(
    585  `
    586 <div contenteditable id="editable" aria-hidden="true">abcd</div>
    587 <p></p>
    588  `,
    589  async function testSetCaretInAriaHidden(browser, docAcc) {
    590    info("Focusing editable");
    591    let moved = waitForEvent(EVENT_TEXT_CARET_MOVED, docAcc);
    592    await invokeContentTask(browser, [], () => {
    593      content.document.getElementById("editable").focus();
    594    });
    595    let evt = await moved;
    596    evt.QueryInterface(nsIAccessibleCaretMoveEvent);
    597    is(evt.caretOffset, 0, "Caret event is for offset 0");
    598 
    599    info("Setting caret in editable to c");
    600    moved = waitForEvent(EVENT_TEXT_CARET_MOVED, docAcc);
    601    await invokeContentTask(browser, [], () => {
    602      const text = content.document.getElementById("editable").firstChild;
    603      content.getSelection().setBaseAndExtent(text, 3, text, 3);
    604    });
    605    evt = await moved;
    606    evt.QueryInterface(nsIAccessibleCaretMoveEvent);
    607    is(evt.caretOffset, 0, "Caret event is for offset 0");
    608  }
    609 );
    610 
    611 /**
    612 * Test the caret when clicking in an empty area of a container immediately
    613 * before/after a text input.
    614 */
    615 addAccessibleTask(
    616  `
    617 <div id="inputThenEmpty" style="padding-bottom: 10vh;">
    618  <input id="inputBeforeEmpty" value="ab">
    619 </div>
    620 <div id="emptyThenInput" style="padding-top: 10vh;">
    621  <input id="inputAfterEmpty" value="cd">
    622 </div>
    623  `,
    624  async function testEmptyNearInput(browser, docAcc) {
    625    info("Focusing inputBeforeEmpty");
    626    let input = findAccessibleChildByID(docAcc, "inputBeforeEmpty", [
    627      nsIAccessibleText,
    628    ]);
    629    let moved = waitForEvents([
    630      [EVENT_FOCUS, input],
    631      [EVENT_TEXT_CARET_MOVED, input],
    632    ]);
    633    input.takeFocus();
    634    await moved;
    635    is(input.caretOffset, 0, "caretOffset 0");
    636 
    637    info("Clicking at bottom of inputThenEmpty");
    638    // BrowserTestUtils.synthesizeMouseAtPoint takes coordinates relative to the
    639    // document.
    640    const docX = {};
    641    const docY = {};
    642    docAcc.getBounds(docX, docY, {}, {});
    643    let container = findAccessibleChildByID(docAcc, "inputThenEmpty", [
    644      nsIAccessibleText,
    645    ]);
    646    const containerX = {};
    647    const containerY = {};
    648    const containerH = {};
    649    container.getBounds(containerX, containerY, {}, containerH);
    650    moved = waitForEvents([
    651      [EVENT_FOCUS, docAcc],
    652      [EVENT_TEXT_CARET_MOVED, container],
    653    ]);
    654    await BrowserTestUtils.synthesizeMouseAtPoint(
    655      containerX.value - docX.value,
    656      containerY.value + containerH.value - 1 - docY.value,
    657      {},
    658      docAcc.browsingContext
    659    );
    660    await moved;
    661    docAcc.QueryInterface(nsIAccessibleText);
    662    is(input.caretOffset, -1, "No caret in inputBeforeEmpty");
    663 
    664    info("Focusing inputAfterEmpty");
    665    input = findAccessibleChildByID(docAcc, "inputAfterEmpty", [
    666      nsIAccessibleText,
    667    ]);
    668    moved = waitForEvents([
    669      [EVENT_FOCUS, input],
    670      [EVENT_TEXT_CARET_MOVED, input],
    671    ]);
    672    input.takeFocus();
    673    await moved;
    674    is(input.caretOffset, 0, "caretOffset 0");
    675 
    676    info("Clicking at top of emptyThenInput");
    677    container = findAccessibleChildByID(docAcc, "emptyThenInput", [
    678      nsIAccessibleText,
    679    ]);
    680    container.getBounds(containerX, containerY, {}, {});
    681    // The caret event fires in the input instead of the container, but this
    682    // isn't really important.
    683    moved = waitForEvents([
    684      [EVENT_FOCUS, docAcc],
    685      [EVENT_TEXT_CARET_MOVED, input],
    686    ]);
    687    await BrowserTestUtils.synthesizeMouseAtPoint(
    688      containerX.value - docX.value,
    689      containerY.value - docY.value + 1,
    690      {},
    691      docAcc.browsingContext
    692    );
    693    await moved;
    694    is(input.caretOffset, -1, "No caret in inputAfterEmpty");
    695  },
    696  { chrome: true, topLevel: true }
    697 );
    698 
    699 /**
    700 * Test retrieving the caret line number.
    701 */
    702 addAccessibleTask(
    703  `
    704 ab
    705 <blockquote id="blockquote">
    706  cd<br>
    707  ef
    708  <p id="p">gh</p>
    709 </blockquote>
    710 ij
    711  `,
    712  async function testLineNumber(browser, docAcc) {
    713    docAcc.QueryInterface(nsIAccessibleText);
    714    testAttrs(docAcc, { "line-number": "1" }, true);
    715    info("Moving caret to b");
    716    let moved = waitForEvent(EVENT_TEXT_CARET_MOVED, docAcc);
    717    docAcc.caretOffset = 1;
    718    await moved;
    719    testAttrs(docAcc, { "line-number": "1" }, true);
    720    info("Moving caret to c");
    721    const blockquote = findAccessibleChildByID(docAcc, "blockquote", [
    722      nsIAccessibleText,
    723    ]);
    724    moved = waitForEvent(EVENT_TEXT_CARET_MOVED, blockquote);
    725    blockquote.caretOffset = 0;
    726    await moved;
    727    testAttrs(docAcc, { "line-number": "2" }, true);
    728    info("Moving caret to d");
    729    moved = waitForEvent(EVENT_TEXT_CARET_MOVED, blockquote);
    730    blockquote.caretOffset = 1;
    731    await moved;
    732    testAttrs(docAcc, { "line-number": "2" }, true);
    733    info("Moving caret to e");
    734    moved = waitForEvent(EVENT_TEXT_CARET_MOVED, blockquote);
    735    blockquote.caretOffset = 3;
    736    await moved;
    737    testAttrs(docAcc, { "line-number": "3" }, true);
    738    info("Moving caret to f");
    739    moved = waitForEvent(EVENT_TEXT_CARET_MOVED, blockquote);
    740    blockquote.caretOffset = 4;
    741    await moved;
    742    testAttrs(docAcc, { "line-number": "3" }, true);
    743    info("moving caret to g");
    744    const p = findAccessibleChildByID(docAcc, "p", [nsIAccessibleText]);
    745    moved = waitForEvent(EVENT_TEXT_CARET_MOVED, p);
    746    p.caretOffset = 0;
    747    await moved;
    748    testAttrs(docAcc, { "line-number": "4" }, true);
    749    info("moving caret to h");
    750    moved = waitForEvent(EVENT_TEXT_CARET_MOVED, p);
    751    p.caretOffset = 1;
    752    await moved;
    753    testAttrs(docAcc, { "line-number": "4" }, true);
    754    info("moving caret to i");
    755    moved = waitForEvent(EVENT_TEXT_CARET_MOVED, docAcc);
    756    docAcc.caretOffset = 4;
    757    await moved;
    758    testAttrs(docAcc, { "line-number": "5" }, true);
    759    info("moving caret to j");
    760    moved = waitForEvent(EVENT_TEXT_CARET_MOVED, docAcc);
    761    docAcc.caretOffset = 5;
    762    await moved;
    763    testAttrs(docAcc, { "line-number": "5" }, true);
    764    info("moving caret to end");
    765    moved = waitForEvent(EVENT_TEXT_CARET_MOVED, docAcc);
    766    // We end up with space at the end of the document, so use characterCount to
    767    // ensure we really move to the end.
    768    docAcc.caretOffset = docAcc.characterCount;
    769    await moved;
    770    testAttrs(docAcc, { "line-number": "5" }, true);
    771  },
    772  {
    773    // Bug 2007033: This is currently only supported for LocalAccessible.
    774    chrome: true,
    775    topLevel: false,
    776    contentSetup: async function contentSetup() {
    777      content.document.designMode = "on";
    778    },
    779  }
    780 );