tor-browser

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

browser_text.js (13013B)


      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 /* import-globals-from ../../mochitest/text.js */
      9 loadScripts({ name: "attributes.js", dir: MOCHITESTS_DIR });
     10 
     11 /**
     12 * Test line and word offsets for various cases for both local and remote
     13 * Accessibles. There is more extensive coverage in ../../mochitest/text. These
     14 * tests don't need to duplicate all of that, since much of the underlying code
     15 * is unified. They should ensure that the cache works as expected and that
     16 * there is consistency between local and remote.
     17 */
     18 addAccessibleTask(
     19  `
     20 <p id="br">ab cd<br>ef gh</p>
     21 <pre id="pre">ab cd
     22 ef gh</pre>
     23 <p id="linksStartEnd"><a href="https://example.com/">a</a>b<a href="https://example.com/">c</a></p>
     24 <p id="linksBreaking">a<a href="https://example.com/">b<br>c</a>d</p>
     25 <p id="p">a<br role="presentation">b</p>
     26 <p id="leafThenWrap" style="font-family: monospace; width: 2ch; word-break: break-word;"><span>a</span>bc</p>
     27 <p id="bidi" style="font-family: monospace; width: 3ch; word-break: break-word">אb גד eו זח טj</p>
     28  `,
     29  async function (browser, docAcc) {
     30    for (const id of ["br", "pre"]) {
     31      const acc = findAccessibleChildByID(docAcc, id);
     32      testCharacterCount([acc], 11);
     33      testTextAtOffset(acc, BOUNDARY_LINE_START, [
     34        [0, 5, "ab cd\n", 0, 6],
     35        [6, 11, "ef gh", 6, 11],
     36      ]);
     37      testTextBeforeOffset(acc, BOUNDARY_LINE_START, [
     38        [0, 5, "", 0, 0],
     39        [6, 11, "ab cd\n", 0, 6],
     40      ]);
     41      testTextAfterOffset(acc, BOUNDARY_LINE_START, [
     42        [0, 5, "ef gh", 6, 11],
     43        [6, 11, "", 11, 11],
     44      ]);
     45      testTextAtOffset(acc, BOUNDARY_LINE_END, [
     46        [0, 5, "ab cd", 0, 5],
     47        [6, 11, "\nef gh", 5, 11],
     48      ]);
     49      testTextBeforeOffset(acc, BOUNDARY_LINE_END, [
     50        [0, 5, "", 0, 0],
     51        [6, 11, "ab cd", 0, 5],
     52      ]);
     53      testTextAfterOffset(acc, BOUNDARY_LINE_END, [
     54        [0, 5, "\nef gh", 5, 11],
     55        [6, 11, "", 11, 11],
     56      ]);
     57      testTextAtOffset(acc, BOUNDARY_WORD_START, [
     58        [0, 2, "ab ", 0, 3],
     59        [3, 5, "cd\n", 3, 6],
     60        [6, 8, "ef ", 6, 9],
     61        [9, 11, "gh", 9, 11],
     62      ]);
     63      testTextBeforeOffset(acc, BOUNDARY_WORD_START, [
     64        [0, 2, "", 0, 0],
     65        [3, 5, "ab ", 0, 3],
     66        [6, 8, "cd\n", 3, 6],
     67        [9, 11, "ef ", 6, 9],
     68      ]);
     69      testTextAfterOffset(acc, BOUNDARY_WORD_START, [
     70        [0, 2, "cd\n", 3, 6],
     71        [3, 5, "ef ", 6, 9],
     72        [6, 8, "gh", 9, 11],
     73        [9, 11, "", 11, 11],
     74      ]);
     75      testTextAtOffset(acc, BOUNDARY_WORD_END, [
     76        [0, 1, "ab", 0, 2],
     77        [2, 4, " cd", 2, 5],
     78        [5, 7, "\nef", 5, 8],
     79        [8, 11, " gh", 8, 11],
     80      ]);
     81      testTextBeforeOffset(acc, BOUNDARY_WORD_END, [
     82        [0, 2, "", 0, 0],
     83        [3, 5, "ab", 0, 2],
     84        // See below for offset 6.
     85        [7, 8, " cd", 2, 5],
     86        [9, 11, "\nef", 5, 8],
     87      ]);
     88      testTextBeforeOffset(acc, BOUNDARY_WORD_END, [[6, 6, " cd", 2, 5]]);
     89      testTextAfterOffset(acc, BOUNDARY_WORD_END, [
     90        [0, 2, " cd", 2, 5],
     91        [3, 5, "\nef", 5, 8],
     92        [6, 8, " gh", 8, 11],
     93        [9, 11, "", 11, 11],
     94      ]);
     95      testTextAtOffset(acc, BOUNDARY_PARAGRAPH, [
     96        [0, 5, "ab cd\n", 0, 6],
     97        [6, 11, "ef gh", 6, 11],
     98      ]);
     99    }
    100    const linksStartEnd = findAccessibleChildByID(docAcc, "linksStartEnd");
    101    testTextAtOffset(linksStartEnd, BOUNDARY_LINE_START, [
    102      [0, 3, `${kEmbedChar}b${kEmbedChar}`, 0, 3],
    103    ]);
    104    testTextAtOffset(linksStartEnd, BOUNDARY_WORD_START, [
    105      [0, 3, `${kEmbedChar}b${kEmbedChar}`, 0, 3],
    106    ]);
    107    const linksBreaking = findAccessibleChildByID(docAcc, "linksBreaking");
    108    testTextAtOffset(linksBreaking, BOUNDARY_LINE_START, [
    109      [0, 0, `a${kEmbedChar}`, 0, 2],
    110      [1, 1, `a${kEmbedChar}d`, 0, 3],
    111      [2, 3, `${kEmbedChar}d`, 1, 3],
    112    ]);
    113    testTextAtOffset(linksBreaking, BOUNDARY_WORD_START, [
    114      [0, 0, `a${kEmbedChar}`, 0, 2],
    115      [1, 1, `a${kEmbedChar}d`, 0, 3],
    116      [2, 3, `${kEmbedChar}d`, 1, 3],
    117    ]);
    118    const p = findAccessibleChildByID(docAcc, "p");
    119    testTextAtOffset(p, BOUNDARY_LINE_START, [
    120      [0, 0, "a", 0, 1],
    121      [1, 2, "b", 1, 2],
    122    ]);
    123    testTextAtOffset(p, BOUNDARY_PARAGRAPH, [[0, 2, "ab", 0, 2]]);
    124    const leafThenWrap = findAccessibleChildByID(docAcc, "leafThenWrap");
    125    testTextAtOffset(leafThenWrap, BOUNDARY_LINE_START, [
    126      [0, 1, "ab", 0, 2],
    127      [2, 3, "c", 2, 3],
    128    ]);
    129    const bidi = findAccessibleChildByID(docAcc, "bidi");
    130    testTextAtOffset(bidi, BOUNDARY_LINE_START, [
    131      [0, 2, "אb ", 0, 3],
    132      [3, 5, "גד ", 3, 6],
    133      [6, 8, "eו ", 6, 9],
    134      [9, 11, "זח ", 9, 12],
    135      [12, 14, "טj", 12, 14],
    136    ]);
    137  },
    138  { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
    139 );
    140 
    141 /**
    142 * Test line offsets after text mutation.
    143 */
    144 addAccessibleTask(
    145  `
    146 <p id="initBr"><br></p>
    147 <p id="rewrap" style="font-family: monospace; width: 2ch; word-break: break-word;"><span id="rewrap1">ac</span>def</p>
    148  `,
    149  async function (browser, docAcc) {
    150    const initBr = findAccessibleChildByID(docAcc, "initBr");
    151    testTextAtOffset(initBr, BOUNDARY_LINE_START, [
    152      [0, 0, "\n", 0, 1],
    153      [1, 1, "", 1, 1],
    154    ]);
    155    info("initBr: Inserting text before br");
    156    let reordered = waitForEvent(EVENT_REORDER, initBr);
    157    await invokeContentTask(browser, [], () => {
    158      const initBrNode = content.document.getElementById("initBr");
    159      initBrNode.insertBefore(
    160        content.document.createTextNode("a"),
    161        initBrNode.firstElementChild
    162      );
    163    });
    164    await reordered;
    165    testTextAtOffset(initBr, BOUNDARY_LINE_START, [
    166      [0, 1, "a\n", 0, 2],
    167      [2, 2, "", 2, 2],
    168    ]);
    169 
    170    const rewrap = findAccessibleChildByID(docAcc, "rewrap");
    171    testTextAtOffset(rewrap, BOUNDARY_LINE_START, [
    172      [0, 1, "ac", 0, 2],
    173      [2, 3, "de", 2, 4],
    174      [4, 5, "f", 4, 5],
    175    ]);
    176    info("rewrap: Changing ac to abc");
    177    reordered = waitForEvent(EVENT_REORDER, rewrap);
    178    await invokeContentTask(browser, [], () => {
    179      const rewrap1 = content.document.getElementById("rewrap1");
    180      rewrap1.textContent = "abc";
    181    });
    182    await reordered;
    183    testTextAtOffset(rewrap, BOUNDARY_LINE_START, [
    184      [0, 1, "ab", 0, 2],
    185      [2, 3, "cd", 2, 4],
    186      [4, 6, "ef", 4, 6],
    187    ]);
    188  },
    189  { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
    190 );
    191 
    192 /**
    193 * Test retrieval of text offsets when an invalid offset is given.
    194 */
    195 addAccessibleTask(
    196  `<p id="p">test</p>`,
    197  async function (browser, docAcc) {
    198    const p = findAccessibleChildByID(docAcc, "p");
    199    testTextAtOffset(p, BOUNDARY_LINE_START, [[5, 5, "", 0, 0]]);
    200    testTextBeforeOffset(p, BOUNDARY_LINE_START, [[5, 5, "", 0, 0]]);
    201    testTextAfterOffset(p, BOUNDARY_LINE_START, [[5, 5, "", 0, 0]]);
    202  },
    203  {
    204    // The old HyperTextAccessible implementation doesn't crash, but it returns
    205    // different offsets. This doesn't matter because they're invalid either
    206    // way. Since the new HyperTextAccessibleBase implementation is all we will
    207    // have soon, just test that.
    208    chrome: true,
    209    topLevel: true,
    210    iframe: true,
    211    remoteIframe: true,
    212  }
    213 );
    214 
    215 /**
    216 * Test HyperText embedded object methods.
    217 */
    218 addAccessibleTask(
    219  `<div id="container">a<a id="link" href="https://example.com/">b</a>c</div>`,
    220  async function (browser, docAcc) {
    221    const container = findAccessibleChildByID(docAcc, "container", [
    222      nsIAccessibleHyperText,
    223    ]);
    224    is(container.linkCount, 1, "container linkCount is 1");
    225    let link = container.getLinkAt(0);
    226    queryInterfaces(link, [nsIAccessible, nsIAccessibleHyperText]);
    227    is(getAccessibleDOMNodeID(link), "link", "LinkAt 0 is the link");
    228    is(container.getLinkIndex(link), 0, "getLinkIndex for link is 0");
    229    is(link.startIndex, 1, "link's startIndex is 1");
    230    is(link.endIndex, 2, "link's endIndex is 2");
    231    is(container.getLinkIndexAtOffset(1), 0, "getLinkIndexAtOffset(1) is 0");
    232    is(container.getLinkIndexAtOffset(0), -1, "getLinkIndexAtOffset(0) is -1");
    233    is(link.linkCount, 0, "link linkCount is 0");
    234  },
    235  {
    236    chrome: true,
    237    topLevel: true,
    238    iframe: true,
    239    remoteIframe: true,
    240  }
    241 );
    242 
    243 /**
    244 * Test HyperText embedded object methods near a list bullet.
    245 */
    246 addAccessibleTask(
    247  `<ul><li id="li"><a id="link" href="https://example.com/">a</a></li></ul>`,
    248  async function (browser, docAcc) {
    249    const li = findAccessibleChildByID(docAcc, "li", [nsIAccessibleHyperText]);
    250    let link = li.getLinkAt(0);
    251    queryInterfaces(link, [nsIAccessible]);
    252    is(getAccessibleDOMNodeID(link), "link", "LinkAt 0 is the link");
    253    is(li.getLinkIndex(link), 0, "getLinkIndex for link is 0");
    254    is(link.startIndex, 2, "link's startIndex is 2");
    255    is(li.getLinkIndexAtOffset(2), 0, "getLinkIndexAtOffset(2) is 0");
    256    is(li.getLinkIndexAtOffset(0), -1, "getLinkIndexAtOffset(0) is -1");
    257  },
    258  {
    259    chrome: true,
    260    topLevel: true,
    261    iframe: true,
    262    remoteIframe: true,
    263  }
    264 );
    265 
    266 const boldAttrs = { "font-weight": "700" };
    267 
    268 /**
    269 * Test text attribute methods.
    270 */
    271 addAccessibleTask(
    272  `
    273 <p id="plain">ab</p>
    274 <p id="bold" style="font-weight: bold;">ab</p>
    275 <p id="partialBold">ab<b>cd</b>ef</p>
    276 <p id="consecutiveBold">ab<b>cd</b><b>ef</b>gh</p>
    277 <p id="embeddedObjs">ab<a href="https://example.com/">cd</a><a href="https://example.com/">ef</a><a href="https://example.com/">gh</a>ij</p>
    278 <p id="empty"></p>
    279 <p id="fontFamilies" style="font-family: sans-serif;">ab<span style="font-family: monospace;">cd</span><span style="font-family: monospace;">ef</span>gh</p>
    280  `,
    281  async function (browser, docAcc) {
    282    let defAttrs = {
    283      "text-position": "baseline",
    284      "font-style": "normal",
    285      "font-weight": "400",
    286    };
    287 
    288    const plain = findAccessibleChildByID(docAcc, "plain");
    289    testDefaultTextAttrs(plain, defAttrs, true);
    290    for (let offset = 0; offset <= 2; ++offset) {
    291      testTextAttrs(plain, offset, {}, defAttrs, 0, 2, true);
    292    }
    293 
    294    const bold = findAccessibleChildByID(docAcc, "bold");
    295    defAttrs["font-weight"] = "700";
    296    testDefaultTextAttrs(bold, defAttrs, true);
    297    testTextAttrs(bold, 0, {}, defAttrs, 0, 2, true);
    298 
    299    const partialBold = findAccessibleChildByID(docAcc, "partialBold");
    300    defAttrs["font-weight"] = "400";
    301    testDefaultTextAttrs(partialBold, defAttrs, true);
    302    testTextAttrs(partialBold, 0, {}, defAttrs, 0, 2, true);
    303    testTextAttrs(partialBold, 2, boldAttrs, defAttrs, 2, 4, true);
    304    testTextAttrs(partialBold, 4, {}, defAttrs, 4, 6, true);
    305 
    306    const consecutiveBold = findAccessibleChildByID(docAcc, "consecutiveBold");
    307    testDefaultTextAttrs(consecutiveBold, defAttrs, true);
    308    testTextAttrs(consecutiveBold, 0, {}, defAttrs, 0, 2, true);
    309    testTextAttrs(consecutiveBold, 2, boldAttrs, defAttrs, 2, 6, true);
    310    testTextAttrs(consecutiveBold, 6, {}, defAttrs, 6, 8, true);
    311 
    312    const embeddedObjs = findAccessibleChildByID(docAcc, "embeddedObjs");
    313    testDefaultTextAttrs(embeddedObjs, defAttrs, true);
    314    testTextAttrs(embeddedObjs, 0, {}, defAttrs, 0, 2, true);
    315    for (let offset = 2; offset <= 4; ++offset) {
    316      // attrs and defAttrs should be completely empty, so we pass
    317      // false for aSkipUnexpectedAttrs.
    318      testTextAttrs(embeddedObjs, offset, {}, {}, 2, 5, false);
    319    }
    320    testTextAttrs(embeddedObjs, 5, {}, defAttrs, 5, 7, true);
    321 
    322    const empty = findAccessibleChildByID(docAcc, "empty");
    323    testDefaultTextAttrs(empty, defAttrs, true);
    324    testTextAttrs(empty, 0, {}, defAttrs, 0, 0, true);
    325 
    326    const fontFamilies = findAccessibleChildByID(docAcc, "fontFamilies", [
    327      nsIAccessibleHyperText,
    328    ]);
    329    testDefaultTextAttrs(fontFamilies, defAttrs, true);
    330    testTextAttrs(fontFamilies, 0, {}, defAttrs, 0, 2, true);
    331    testTextAttrs(fontFamilies, 2, {}, defAttrs, 2, 6, true);
    332    testTextAttrs(fontFamilies, 6, {}, defAttrs, 6, 8, true);
    333  },
    334  {
    335    chrome: true,
    336    topLevel: true,
    337    iframe: true,
    338    remoteIframe: true,
    339  }
    340 );
    341 
    342 /**
    343 * Test cluster offsets.
    344 */
    345 addAccessibleTask(
    346  `<p id="clusters">À2🤦‍♂️🤦🏼‍♂️5x͇͕̦̍͂͒7È</p>`,
    347  async function testCluster(browser, docAcc) {
    348    const clusters = findAccessibleChildByID(docAcc, "clusters");
    349    testCharacterCount(clusters, 26);
    350    testTextAtOffset(clusters, BOUNDARY_CLUSTER, [
    351      [0, 1, "À", 0, 2],
    352      [2, 2, "2", 2, 3],
    353      [3, 7, "🤦‍♂️", 3, 8],
    354      [8, 14, "🤦🏼‍♂️", 8, 15],
    355      [15, 15, "5", 15, 16],
    356      [16, 22, "x͇͕̦̍͂͒", 16, 23],
    357      [23, 23, "7", 23, 24],
    358      [24, 25, "È", 24, 26],
    359      [26, 26, "", 26, 26],
    360    ]);
    361    // Ensure that BOUNDARY_CHAR returns single Unicode characters.
    362    testTextAtOffset(clusters, BOUNDARY_CHAR, [
    363      [0, 0, "A", 0, 1],
    364      [1, 1, "̀", 1, 2],
    365    ]);
    366  },
    367  { chrome: true, topLevel: true }
    368 );