tor-browser

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

test_text-fragments-create-text-directive.html (36235B)


      1 <!DOCTYPE html>
      2 <html>
      3 <head>
      4  <title>Text Fragment Chrome-only API Test</title>
      5  <meta charset="UTF-8">
      6  <script src="/tests/SimpleTest/SimpleTest.js"></script>
      7  <script src="/tests/SimpleTest/GleanTest.js"></script>
      8  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
      9 </head>
     10 <body>
     11  <div id="abc">abc def ghi abc def ghi jkl abc. def <span>ghi</span></div>
     12  <div id="foo">foo</div>
     13  <p id="block">p<span id="inlinespan">sp<span id="nestedinlinespan">a</span>n</span>p</p><span id="afterblockboundary">afterblockboundary</span>
     14  <div id="image"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAQAAAAnOwc2AAAAEUlEQVR42mNk+M+AARiHsiAAcCIKAYwFoQ8AAAAASUVORK5CYII="></div>
     15  <p>placeholder with block boundary</p>
     16  <p id="surroundedByBlockBoundaries">abc def ghi</p>
     17  <p>placeholder with block boundary</p>
     18  <ul>
     19    <li>First element</li>
     20    <li>Second element</li>
     21  </ul>
     22  <ul>
     23    <li>First element, but different</li>
     24    <li id="secondListElement">Second Element</li>
     25  </ul>
     26  <p id="textWithEmoji"><span>😍😍</span> abc def 😍😍</p>
     27  <p id="textWithDifferentEmoji"><span>😍😏😍</span> abc def 😍😏</p>
     28  <!-- TODO: Add test case with `visibility: hidden` as soon as it's supported (bug 1950707) -->
     29  <p id="textWithInvisibleContent">Text with <span style="display: none">display:none </span>content</p>
     30  <p id="wordBoundaryAtInlineBoundary">WordEnd <span>should not be part of text directive</span></p>
     31  <p id="matchAtEndOfBlock">EndOfBlock</p>
     32  <p id="prevMatchAtEndOfBlock">EndOfBlock</p>
     33  <div id="emptyBlockAtBeginning"><div></div>AfterEmptyBlock</div>
     34  <div id="emptyBlockAtEnd">BeforeEmptyBlock<div></div></div>
     35  <div id="rangeBasedWithMultipleEndMatchesBegin">Begin and begin or begin with begin</div>
     36  <div id="rangeBasedWithMultipleEndMatchesEnd">end or end and end at end</div>
     37  <div>HereStartsATableAndThisIsHereToMakeSureTheTestResultsAreIndependentOfSurroundingContent</div>
     38  <table>
     39    <tr>
     40      <td id="firstRow">first row</td></tr> <tr><td id="secondRow">second row</td>
     41    </tr>
     42  </table>
     43  <div>HereEndsATableAndThisIsHereToMakeSureTheTestResultsAreIndependentOfSurroundingContent</div>
     44 
     45  <span id="longWord">1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111</span>
     46  <!-- Test case for Bug 1979588: Long URL with unbreakable segment at the end -->
     47  <div id="longUrlForBug1979588">https://example.com/test?foo=bar#verylongnonbreakableurlsegmentthatcannotbebrokenintowordsbecauseiturlsegmentsdonotcontainspacesorothertraditionwordbreakingcharacterswhichcausesproblemsintextdirectivecreation</div>
     48  <p id="loremIpsum">Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source.</p>
     49  <p>
     50    <span id="beforeEmptyTextNode">before empty text node</span>
     51    <!-- empty text node will be inserted right here -->
     52    <span id="afterEmptyTextNode">after empty text node</span>
     53  </p>
     54  <div id="nbsp">&nbsp;</div>
     55  <div id="punctuationStartsRangeBasedTextDirective">"</div>
     56  <div id="punctuationEndsRangeBasedTextDirective">text preceded by punctuation in a different block</div>
     57  <!-- Test case for Bug 1976869. -->
     58  <div>Foo Bar</div>
     59  <div id="rangeBasedWithPrefixAtBlockBoundaries">Some text Foo Bar more text</div>
     60  <div id="rangeBasedWithPrefixAtBlockBoundariesEnd">End of Range</div>
     61  <!-- Test case for Bug 1976480. -->
     62  <div id="quotationMark">"Quotation mark"</div>
     63  LongSpacerWord
     64  <!-- Test case for Bug 1979474 -->
     65  <table>
     66    <tr id="trWithEmptyTdInside">
     67      <td id="tdFollowedByEmptyTd">}</td>
     68      <td></td>
     69      <td></td>
     70    </tr>
     71    <tr>
     72      <td>foo</td>
     73    </tr>
     74  </table>
     75  <!-- (crash)Test cases for Bug 1987845; example paragraph found on https://en.wikipedia.org/wiki/Nuclear_weapon-->
     76  <p>Nuclear<!-- This is needed so that there is an additional match to consider --></p>
     77  <p id="rangeBasedWihSpaceInTheMiddle">Nuclear weapons have only twice been used in warfare, both times by the United States against Japan at the end of World War II. On August 6, 1945, the United States Army Air Forces (USAAF) detonated a uranium gun-type fission bomb nicknamed "Little Boy" over the Japanese city of Hiroshima; three days later, on August 9, the USAAF[4] detonated a plutonium implosion-type fission bomb nicknamed "Fat Man" over the Japanese city of Nagasaki. These bombings caused injuries that resulted in the deaths of approximately 200,000 civilians and military personnel.[5] The ethics of these bombings and their role in Japan's surrender are to this day, still subjects of debate</p>
     78  <!-- note that "text" must already exist in the page to trigger this. -->
     79  <p id="rangeBasedWithPunctuationAfterFirstWordStart">Text: the colon</p>
     80  <p id="rangeBasedWithPunctuationAfterFirstWordEnd">is important here</p>
     81  <!-- (crash)Test case for bug 1989891 -->
     82  <p>1 1111111111111
     83 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
     84 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
     85 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
     86 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
     87 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
     88 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
     89 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
     90 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
     91 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
     92 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
     93 <span id="prefixAndSuffixHitMaxLength">test</span>
     94 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
     95 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
     96 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
     97 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
     98 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
     99 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
    100 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
    101 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
    102 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
    103 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
    104 1111111111111 1
    105 </p>
    106  <script>
    107    document.addEventListener("DOMContentLoaded", () => {
    108      const empty = document.createTextNode("");
    109      const afterEmptyTextNode = document.getElementById("afterEmptyTextNode");
    110      afterEmptyTextNode.parentNode.insertBefore(empty, afterEmptyTextNode);
    111    });
    112 
    113    SimpleTest.waitForExplicitFinish();
    114    function rAF() {
    115      return new Promise(resolve => {
    116        requestAnimationFrame(resolve);
    117      });
    118    }
    119    /**
    120     * Helper function which is used by assertions below.
    121     * Returns true if the two ranges have the exact same start and end points.
    122     */
    123    function rangeBoundariesAreEqual(range1, range2) {
    124      const startContainerIsEqual = range1.startContainer.textContent === range2.startContainer.textContent;
    125      const endContainerIsEqual = range1.endContainer.textContent === range2.endContainer.textContent;
    126 
    127      const startOffsetIsEqual = range1.startOffset == range2.startOffset;
    128      const endOffsetIsEqual = range1.endOffset == range2.endOffset;
    129      return (
    130        startContainerIsEqual
    131        && endContainerIsEqual
    132        && startOffsetIsEqual
    133        && endOffsetIsEqual
    134      );
    135    }
    136 
    137    /**
    138     * Test for the `rangeBoundariesAreEqual()` helper function.
    139     */
    140    function testRangeBoundariesAreEqual() {
    141      const range1 = document.createRange();
    142      range1.setStart(abc.firstChild, 0);
    143      range1.setEnd(abc.firstChild, 3);
    144      const range2 = document.createRange();
    145      range2.setStart(abc.firstChild, 0);
    146      range2.setEnd(abc.firstChild, 3);
    147 
    148      ok(
    149        rangeBoundariesAreEqual(range1, range2),
    150        "Ranges should have the same boundary points when containers are text nodes"
    151      );
    152 
    153      const range3 = document.createRange();
    154      range3.selectNode(abc);
    155      const range4 = document.createRange();
    156      range4.selectNode(abc);
    157      ok(
    158        rangeBoundariesAreEqual(range3, range4),
    159        "Ranges should have the same boundary points when containers are nodes"
    160      );
    161    }
    162 
    163    async function basicTests() {
    164      const createDirectiveTimeStart = await GleanTest.domTextfragment.createDirective.testGetValue() ?? {count: 0};
    165      let lastCreateTimeCount = createDirectiveTimeStart.count;
    166      for(let testCase of [
    167        {
    168          name: "Text directive is first string in the document",
    169          startContainer: abc.firstChild,
    170          startOffset: 0,
    171          endContainer: abc.firstChild,
    172          endOffset: 3,
    173          content: "abc",
    174          textDirective: "text=abc"
    175        },
    176        {
    177          name: "Text directive is the second word in the document",
    178          startContainer: abc.firstChild,
    179          startOffset: 4,
    180          endContainer: abc.firstChild,
    181          endOffset: 7,
    182          content: "def",
    183          textDirective: "text=def"
    184        },
    185        {
    186          name: "Text directive spans two words",
    187          startContainer: abc.firstChild,
    188          startOffset: 0,
    189          endContainer: abc.firstChild,
    190          endOffset: 7,
    191          content: "abc def",
    192          textDirective: "text=abc%20def"
    193        },
    194        {
    195          name: "Text directive is second occurrence of content",
    196          startContainer: abc.firstChild,
    197          startOffset: 12,
    198          endContainer: abc.firstChild,
    199          endOffset: 15,
    200          content: "abc",
    201          textDirective: "text=ghi-,abc"
    202        },
    203        {
    204          name: "Text directive is second occurrence, suffix is shorter",
    205          startContainer: abc.firstChild,
    206          startOffset: 20,
    207          endContainer: abc.firstChild,
    208          endOffset: 23,
    209          content: "ghi",
    210          textDirective: "text=ghi,-jkl"
    211        },
    212        {
    213          name: "Shortest text directive crosses block boundary",
    214          startContainer: abc.lastChild.firstChild,
    215          startOffset: 0,
    216          endContainer: abc.lastChild.firstChild,
    217          endOffset: 3,
    218          content: "ghi",
    219          textDirective: "text=ghi,-foo"
    220        },
    221        {
    222          name: "Text directive crosses block boundary",
    223          startContainer: block.firstChild,
    224          startOffset: 0,
    225          endContainer: afterblockboundary.firstChild,
    226          endOffset: 18,
    227          content: "pspanpafterblockboundary",
    228          textDirective: "text=pspanp,afterblockboundary",
    229        },
    230        {
    231          name: "Text directive crosses several block boundaries",
    232          startContainer: foo.firstChild,
    233          startOffset: 0,
    234          endContainer: afterblockboundary.firstChild,
    235          endOffset: 18,
    236          content: "foo\n  pspanpafterblockboundary",
    237          textDirective: "text=foo,afterblockboundary",
    238        },
    239        {
    240          name: "Text directive needs to check before previous block boundary",
    241          startContainer: secondListElement.firstChild,
    242          startOffset: 0,
    243          endContainer: secondListElement.firstChild,
    244          endOffset: 6,
    245          content: "Second",
    246          textDirective: "text=different-,Second"
    247        },
    248        {
    249          name: "Text directive contains emoji as start",
    250          startContainer: textWithEmoji.firstChild.firstChild,
    251          startOffset: 0,
    252          endContainer: textWithEmoji.firstChild.firstChild,
    253          endOffset: 2,
    254          content: "😍",
    255          textDirective: "text=%F0%9F%98%8D",
    256        },
    257        {
    258          name: "Text directive contains emoji as start and prefix",
    259          startContainer: textWithEmoji.firstChild.firstChild,
    260          startOffset: 2,
    261          endContainer: textWithEmoji.firstChild.firstChild,
    262          endOffset: 4,
    263          content: "😍",
    264          textDirective: "text=%F0%9F%98%8D-,%F0%9F%98%8D",
    265        },
    266        {
    267          name: "Text directive contains emoji as prefix",
    268          startContainer: textWithEmoji.firstChild.nextSibling,
    269          startOffset: 1,
    270          endContainer: textWithEmoji.firstChild.nextSibling,
    271          endOffset: 4,
    272          content: "abc",
    273          textDirective: "text=%F0%9F%98%8D-,abc",
    274        },
    275        {
    276          name: "Text directive contains slightly different emoji as prefix",
    277          startContainer: textWithDifferentEmoji.firstChild.nextSibling,
    278          startOffset: 1,
    279          endContainer: textWithDifferentEmoji.firstChild.nextSibling,
    280          endOffset: 4,
    281          content: "abc",
    282          textDirective: "text=%F0%9F%98%8F%F0%9F%98%8D-,abc",
    283        },
    284        {
    285          name: "Text directive needs to compare emojis for suffix",
    286          startContainer: textWithDifferentEmoji.firstChild.nextSibling,
    287          startOffset: 5,
    288          endContainer: textWithDifferentEmoji.firstChild.nextSibling,
    289          endOffset: 8,
    290          content: "def",
    291          textDirective: "text=def,-%F0%9F%98%8D%F0%9F%98%8F"
    292        },
    293        {
    294          name: "Text directive contains search-invisible content",
    295          startContainer: textWithInvisibleContent.firstChild,
    296          startOffset: 0,
    297          endContainer: textWithInvisibleContent.lastChild,
    298          endOffset: textWithInvisibleContent.lastChild.length,
    299          content: "Text with display:none content",
    300          textDirective: "text=Text%20with%20content"
    301        },
    302        {
    303          name: "Range-based text directive needs prefix",
    304          startContainer: abc.firstChild,
    305          startOffset: 12,
    306          endContainer: foo.firstChild,
    307          endOffset: 3,
    308          content: "abc def ghi jkl abc. def ghi\n  foo",
    309          textDirective: "text=ghi-,abc,foo"
    310        },
    311        {
    312          name: "Range-based text directive, end term is at inline boundary",
    313          startContainer: textWithInvisibleContent.firstChild,
    314          startOffset: 0,
    315          endContainer: wordBoundaryAtInlineBoundary.firstChild,
    316          endOffset: 7,
    317          content: "Text with display:none content\n  WordEnd",
    318          textDirective: "text=Text,WordEnd"
    319        },
    320        {
    321          name: "Finding text directive matches must correctly deal with block boundaries",
    322          startContainer: prevMatchAtEndOfBlock.firstChild,
    323          startOffset: 0,
    324          endContainer: prevMatchAtEndOfBlock.firstChild,
    325          endOffset: 10,
    326          content: "EndOfBlock",
    327          textDirective: "text=EndOfBlock-,EndOfBlock"
    328        },
    329        {
    330          name: "Finding text directive across table rows",
    331          startContainer: firstRow.firstChild,
    332          startOffset: 0,
    333          endContainer: secondRow.firstChild,
    334          endOffset: secondRow.firstChild.length,
    335          content: "first row second row",
    336          textDirective: "text=first%20row,second%20row",
    337        },
    338        {
    339          name: "Finding prefix in the correct order",
    340          startContainer: loremIpsum.firstChild,
    341          startOffset: 308,
    342          endContainer: loremIpsum.firstChild,
    343          endOffset: 313,
    344          content: "Lorem",
    345          textDirective: "text=a-,Lorem"
    346        },
    347        {
    348          name: "Range-based text directive with multiple end matches 1 (first match)",
    349          startContainer: rangeBasedWithMultipleEndMatchesBegin.firstChild,
    350          startOffset: 0,
    351          endContainer: rangeBasedWithMultipleEndMatchesEnd.firstChild,
    352          endOffset: 3,
    353          content: "Begin and begin or begin with begin\n  end",
    354          textDirective: "text=Begin,end"
    355        },
    356        {
    357          name: "Range-based text directive with multiple end matches 2 (use end because it's shorter)",
    358          startContainer: rangeBasedWithMultipleEndMatchesBegin.firstChild,
    359          startOffset: 0,
    360          endContainer: rangeBasedWithMultipleEndMatchesEnd.firstChild,
    361          endOffset: 10,
    362          content: "Begin and begin or begin with begin\n  end or end",
    363          textDirective: "text=Begin,or%20end"
    364        },
    365        {
    366          name: "Range-based text directive with multiple end matches 3 (use suffix because it's shorter)",
    367          startContainer: rangeBasedWithMultipleEndMatchesBegin.firstChild,
    368          startOffset: 0,
    369          endContainer: rangeBasedWithMultipleEndMatchesEnd.firstChild,
    370          endOffset: 18,
    371          content: "Begin and begin or begin with begin\n  end or end and end",
    372          textDirective: "text=Begin,end,-at"
    373        },
    374        {
    375          name: "Range-based text directive with multiple start matches 1 (use start because it's shorter)",
    376          startContainer: rangeBasedWithMultipleEndMatchesBegin.firstChild,
    377          startOffset: 10,
    378          endContainer: rangeBasedWithMultipleEndMatchesEnd.firstChild,
    379          endOffset: 18,
    380          content: "begin or begin with begin\n  end or end and end",
    381          textDirective: "text=begin%20or,end,-at"
    382        },
    383        {
    384          name: "Range-based text directive with multiple start matches 2 (use prefix because it's shorter)",
    385          startContainer: rangeBasedWithMultipleEndMatchesBegin.firstChild,
    386          startOffset: 19,
    387          endContainer: rangeBasedWithMultipleEndMatchesEnd.firstChild,
    388          endOffset: 18,
    389          content: "begin with begin\n  end or end and end",
    390          textDirective: "text=or-,begin,end,-at"
    391        },
    392        {
    393          name: "Empty text nodes need to be handled correctly when finding word boundary looking left",
    394          startContainer: afterEmptyTextNode.firstChild,
    395          startOffset: 0,
    396          endContainer: afterEmptyTextNode.firstChild,
    397          endOffset: 5,
    398          content: "after",
    399          textDirective: "text=after"
    400        },
    401        {
    402          name: "Empty text nodes need to be handled correctly when finding word boundary looking right",
    403          startContainer: beforeEmptyTextNode.firstChild,
    404          startOffset: 18,
    405          endContainer: beforeEmptyTextNode.firstChild,
    406          endOffset: 22,
    407          content: "node",
    408          textDirective: "text=node"
    409        },
    410        {
    411          name: "Range-based text directive with punctuation as start",
    412          startContainer: punctuationStartsRangeBasedTextDirective.firstChild,
    413          startOffset: 0,
    414          endContainer: punctuationEndsRangeBasedTextDirective.firstChild,
    415          endOffset: 4,
    416          content: '"\n  text',
    417          textDirective: "text=%22,text"
    418        },
    419        {
    420          name: "Test for Bug 1976869 (edge cases for common string length computation)",
    421          startContainer: rangeBasedWithPrefixAtBlockBoundaries.firstChild,
    422          startOffset: 14,
    423          endContainer: rangeBasedWithPrefixAtBlockBoundariesEnd.firstChild,
    424          endOffset: 3,
    425          content: "Bar more text\n  End",
    426          textDirective: "text=Bar%20more,End"
    427        },
    428        {
    429          name: "Test for Bug 1976480: Selected quotation mark should not be extended to the next word",
    430          startContainer: quotationMark.firstChild,
    431          startOffset: 0,
    432          endContainer: quotationMark.firstChild,
    433          endOffset: 1,
    434          content: "\"",
    435          textDirective: "text=Range-,%22"
    436        },
    437        {
    438          name: "Test for Bug 1976480: Selected quotation mark should not be extended to the previous word",
    439          startContainer: quotationMark.firstChild,
    440          startOffset: 15,
    441          endContainer: quotationMark.firstChild,
    442          endOffset: 16,
    443          content: "\"",
    444          textDirective: "text=mark-,%22"
    445        },
    446        {
    447          name: "Test for Bug 1976480: Text inside quotation marks should not have the selection extended to the quotation marks",
    448          startContainer: quotationMark.firstChild,
    449          startOffset: 1,
    450          endContainer: quotationMark.firstChild,
    451          endOffset: 15,
    452          content: "Quotation mark",
    453          textDirective: "text=Quotation%20mark"
    454        },
    455        {
    456          name: "Test for Bug 1979474: Text directive with empty table cell",
    457          startContainer: tdFollowedByEmptyTd.firstChild,
    458          startOffset: 0,
    459          endContainer: tdFollowedByEmptyTd.nextSibling,
    460          endOffset: 0,
    461          content: "}",
    462          textDirective: "text=%7D",
    463          dontCompareRangeBoundaries: true,
    464        },
    465        {
    466          name: "Test for Bug 1979588: Long URL with unbreakable segment should not crash",
    467          startContainer: longUrlForBug1979588.firstChild,
    468          startOffset: 0,
    469          endContainer: longUrlForBug1979588.firstChild,
    470          endOffset: longUrlForBug1979588.firstChild.length,
    471          content: "https://example.com/test?foo=bar#verylongnonbreakableurlsegmentthatcannotbebrokenintowordsbecauseiturlsegmentsdonotcontainspacesorothertraditionwordbreakingcharacterswhichcausesproblemsintextdirectivecreation",
    472          textDirective: "text=https,verylongnonbreakableurlsegmentthatcannotbebrokenintowordsbecauseiturlsegmentsdonotcontainspacesorothertraditionwordbreakingcharacterswhichcausesproblemsintextdirectivecreation",
    473        },
    474        {
    475          name: "Test for Bug 1987845: Long text with spaces in the middle",
    476          startContainer: rangeBasedWihSpaceInTheMiddle.firstChild,
    477          startOffset: 0,
    478          endContainer: rangeBasedWihSpaceInTheMiddle.firstChild,
    479          endOffset: rangeBasedWihSpaceInTheMiddle.firstChild.length,
    480          content: "Nuclear weapons have only twice been used in warfare, both times by the United States against Japan at the end of World War II. On August 6, 1945, the United States Army Air Forces (USAAF) detonated a uranium gun-type fission bomb nicknamed \"Little Boy\" over the Japanese city of Hiroshima; three days later, on August 9, the USAAF[4] detonated a plutonium implosion-type fission bomb nicknamed \"Fat Man\" over the Japanese city of Nagasaki. These bombings caused injuries that resulted in the deaths of approximately 200,000 civilians and military personnel.[5] The ethics of these bombings and their role in Japan's surrender are to this day, still subjects of debate",
    481          textDirective: "text=Nuclear%20weapons,debate",
    482        },
    483        {
    484          name: "Test for Bug 1987845: Punctuation after first word of range",
    485          startContainer: rangeBasedWithPunctuationAfterFirstWordStart.firstChild,
    486          startOffset: 0,
    487          endContainer: rangeBasedWithPunctuationAfterFirstWordEnd.firstChild,
    488          endOffset: rangeBasedWithPunctuationAfterFirstWordEnd.firstChild.length,
    489          content: "Text: the colon\n  is important here",
    490          textDirective: "text=Text%3A%20the,here",
    491        },
    492        {
    493          name: "Prefix and suffix hit max length at whitespace",
    494          startContainer: prefixAndSuffixHitMaxLength.firstChild,
    495          startOffset: 0,
    496          endContainer: prefixAndSuffixHitMaxLength.firstChild,
    497          endOffset: prefixAndSuffixHitMaxLength.firstChild.length,
    498          content: "test",
    499          textDirective: "text=test,-1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111",
    500        },
    501      ]) {
    502        const range = document.createRange();
    503        range.setStart(testCase.startContainer, testCase.startOffset);
    504        range.setEnd(testCase.endContainer, testCase.endOffset);
    505        is(
    506          range.toString(), testCase.content,
    507          `${testCase.name}: Precondition - Range has expected value`
    508        );
    509 
    510        const textDirective = await SpecialPowers.wrap(document).fragmentDirective.createTextDirectiveForRanges([range]);
    511        is(
    512          textDirective, testCase.textDirective,
    513          `${testCase.name}: text directive has expected value '${testCase.textDirective}'`
    514        );
    515        // load the page with the given text directive
    516        location.hash = `#:~:${textDirective}`;
    517        await rAF();
    518 
    519        // access the range from the loaded text directive and compare the boundary points
    520        ranges = SpecialPowers.wrap(document).fragmentDirective.getTextDirectiveRanges();
    521        is(
    522          ranges.length, 1,
    523          `${testCase.name}: There is one text fragment range on the document`
    524        );
    525 
    526        is(
    527          ranges[0].toString(), range.toString(),
    528          `${testCase.name}: Ranges have the same content`
    529        );
    530        if(!testCase.dontCompareRangeBoundaries) {
    531          // For some test cases it's necessary that the range boundaries in the test case
    532          // and the range boundaries of the created match are _not_ the same,
    533          // e.g. if the test requires that the range contains empty nodes.
    534          ok(
    535            rangeBoundariesAreEqual(range, ranges[0]),
    536            `${testCase.name}: Ranges have the same boundary points`
    537          );
    538        }
    539 
    540        // finally, remove all text directives to clean up for the next test.
    541        SpecialPowers.wrap(document).fragmentDirective.removeAllTextDirectives();
    542 
    543        const createDirectiveTime = await GleanTest.domTextfragment.createDirective.testGetValue() ?? {count: 0};
    544        is(createDirectiveTime.count, lastCreateTimeCount + 1, `${testCase.name}: Glean should have recorded the time it took to create the text directive`);
    545        lastCreateTimeCount = createDirectiveTime.count;
    546      }
    547    }
    548 
    549    /**
    550     * Calling the API with an empty / collapsed range should
    551     * return an empty string, not an error.
    552     */
    553    async function testEmptyRange() {
    554 
    555      let textDirective = await SpecialPowers.wrap(document).fragmentDirective.createTextDirectiveForRanges([]);
    556      is(
    557        textDirective, null,
    558        "Empty input range: Produces empty text directive"
    559      );
    560      const range = document.createRange();
    561      range.selectNode(abc);
    562      range.collapse(true);
    563      textDirective = await SpecialPowers.wrap(document).fragmentDirective.createTextDirectiveForRanges([range]);
    564      is(
    565        textDirective, null,
    566        "Collapsed input range: Produces empty text directive"
    567      );
    568      range.selectNode(image);
    569      textDirective = await SpecialPowers.wrap(document).fragmentDirective.createTextDirectiveForRanges([range]);
    570      is(
    571        textDirective, null,
    572        "Input range contains image only: Produces empty text directive"
    573      );
    574 
    575      range.selectNode(nbsp);
    576      textDirective = await SpecialPowers.wrap(document).fragmentDirective.createTextDirectiveForRanges([range]);
    577      is(
    578        textDirective, null,
    579        "Input range contains nbsp only: Produces empty text directive"
    580      );
    581    }
    582 
    583    async function testExpandRangeToWordBoundaries() {
    584      const createDirectiveTimeStart = await GleanTest.domTextfragment.createDirective.testGetValue() ?? {count: 0};
    585      let lastCreateTimeCount = createDirectiveTimeStart.count;
    586 
    587      for (testCase of [
    588        {
    589          name: "Expanding single-word range to word boundaries (input is inside word)",
    590          startContainer: abc.firstChild,
    591          startOffset: 5,
    592          endContainer: abc.firstChild,
    593          endOffset: 6,
    594          outputStartContainer: abc.firstChild,
    595          outputStartOffset: 4,
    596          outputEndContainer: abc.firstChild,
    597          outputEndOffset: 7,
    598          content: "e",
    599          outputContent: "def",
    600          textDirective: "text=def"
    601        },
    602        {
    603          name: "Expanding single-word range to word boundaries (input is start of word)",
    604          startContainer: abc.firstChild,
    605          startOffset: 4,
    606          endContainer: abc.firstChild,
    607          endOffset: 5,
    608          outputStartContainer: abc.firstChild,
    609          outputStartOffset: 4,
    610          outputEndContainer: abc.firstChild,
    611          outputEndOffset: 7,
    612          content: "d",
    613          outputContent: "def",
    614          textDirective: "text=def"
    615        },
    616        {
    617          name: "Expanding single-word range to word boundaries (input is end of word)",
    618          startContainer: abc.firstChild,
    619          startOffset: 6,
    620          endContainer: abc.firstChild,
    621          endOffset: 7,
    622          outputStartContainer: abc.firstChild,
    623          outputStartOffset: 4,
    624          outputEndContainer: abc.firstChild,
    625          outputEndOffset: 7,
    626          content: "f",
    627          outputContent: "def",
    628          textDirective: "text=def"
    629        },
    630        {
    631          name: "Expanding multi-word range to word boundaries",
    632          startContainer: abc.firstChild,
    633          startOffset: 5,
    634          endContainer: abc.firstChild,
    635          endOffset: 9,
    636          outputStartContainer: abc.firstChild,
    637          outputStartOffset: 4,
    638          outputEndContainer: abc.firstChild,
    639          outputEndOffset: 11,
    640          content: "ef g",
    641          outputContent: "def ghi",
    642          textDirective: "text=def%20ghi"
    643        },
    644        {
    645          name: "Expanding inline-boundary word range to word boundaries",
    646          startContainer: nestedinlinespan.firstChild,
    647          startOffset: 0,
    648          endContainer: nestedinlinespan.firstChild,
    649          endOffset: 1,
    650          outputStartContainer: block.firstChild,
    651          outputStartOffset: 0,
    652          outputEndContainer: block.lastChild,
    653          outputEndOffset: 1,
    654          content: "a",
    655          outputContent: "pspanp",
    656          textDirective: "text=pspanp"
    657        },
    658      ]) {
    659 
    660        const range = document.createRange();
    661        range.setStart(testCase.startContainer, testCase.startOffset);
    662        range.setEnd(testCase.endContainer, testCase.endOffset);
    663        is(
    664          range.toString().trim(), testCase.content,
    665          `${testCase.name}: Precondition - Range has expected value`
    666        );
    667 
    668        const textDirective = await SpecialPowers.wrap(document).fragmentDirective.createTextDirectiveForRanges([range]);
    669        is(
    670          textDirective, testCase.textDirective,
    671          `${testCase.name}: text directive has expected value '${textDirective}'`
    672        );
    673        // load the page with the given text directive
    674        location.hash = `#:~:${textDirective}`;
    675        await rAF();
    676 
    677        // access the range from the loaded text directive and compare the boundary points
    678        ranges = SpecialPowers.wrap(document).fragmentDirective.getTextDirectiveRanges();
    679        const expectedRange = document.createRange();
    680        expectedRange.setStart(testCase.outputStartContainer, testCase.outputStartOffset);
    681        expectedRange.setEnd(testCase.outputEndContainer, testCase.outputEndOffset);
    682        is(
    683          expectedRange.toString(), ranges[0].toString(),
    684          `${testCase.name}: Ranges have the same content '${ranges[0].toString()}'`
    685        );
    686        ok(
    687          rangeBoundariesAreEqual(expectedRange, ranges[0]),
    688          `${testCase.name}: Ranges have the same boundary points`
    689        );
    690 
    691        // finally, remove all text directives to clean up for the next test.
    692        SpecialPowers.wrap(document).fragmentDirective.removeAllTextDirectives();
    693        location.hash = "";
    694 
    695        const createDirectiveTime = await GleanTest.domTextfragment.createDirective.testGetValue() ?? {count: 0};
    696        is(createDirectiveTime.count, lastCreateTimeCount + 1, `${testCase.name}: Glean should have recorded the time it took to create the text directive`);
    697        lastCreateTimeCount = createDirectiveTime.count;
    698 
    699      }
    700    }
    701 
    702    async function testNonUniqueTestSurroundedByBlockBoundaries() {
    703      const createDirectiveTimeStart = await GleanTest.domTextfragment.createDirective.testGetValue() ?? {count: 0};
    704 
    705      const testCase = "Creating a text directive which cannot be found"
    706      const range = document.createRange();
    707      range.setStart(surroundedByBlockBoundaries.firstChild, 4);
    708      range.setEnd(surroundedByBlockBoundaries.firstChild, 7);
    709      is(range.toString(), "def", `${testCase}: Range has expected value`);
    710      const textDirective = await SpecialPowers.wrap(document).fragmentDirective.createTextDirectiveForRanges([range]);
    711      is(textDirective, null, `${testCase}: It's not possible to create a unique text directive`);
    712 
    713      const createDirectiveTime = await GleanTest.domTextfragment.createDirective.testGetValue() ?? {count: 0};
    714      is(createDirectiveTime.count, createDirectiveTimeStart.count + 1, `${testCase}: Glean should have recorded the time it took to create the text directive`);
    715    }
    716 
    717    async function testRangeBasedWithEmptySurroundingBlocks() {
    718      const testCase = "Empty blocks at beginning or end of range must be ignored";
    719      const range = document.createRange();
    720      range.setStart(emptyBlockAtBeginning, 0);
    721      range.setEnd(emptyBlockAtEnd.nextSibling ,0);
    722      const content = "AfterEmptyBlock\n  BeforeEmptyBlock";
    723      const textDirectiveString = "text=AfterEmptyBlock,BeforeEmptyBlock";
    724      is(range.toString(), content, `${testCase}: Range has expected value`);
    725      const textDirective = await SpecialPowers.wrap(document).fragmentDirective.createTextDirectiveForRanges([range]);
    726      is(textDirective, textDirectiveString, `${testCase}: text directive has expected value '${textDirectiveString}'`);
    727      location.hash = `:~:${textDirective}`;
    728      await rAF();
    729      ranges = SpecialPowers.wrap(document).fragmentDirective.getTextDirectiveRanges();
    730      is(ranges.length, 1, `${testCase}: There is one text fragment range on the document`);
    731      is(ranges[0].toString(), range.toString(), `${testCase}: Ranges have the same content`);
    732    }
    733 
    734    async function testRangeBasedWithNonBreakableWord() {
    735      const testCase = "Range-based text directive with non-breakable word";
    736      const range = document.createRange();
    737      range.setStart(longWord.firstChild, 0);
    738      range.setEnd(longWord.firstChild, longWord.firstChild.length);
    739      const textDirective = await SpecialPowers.wrap(document).fragmentDirective.createTextDirectiveForRanges([range]);
    740      is(textDirective, null, `${testCase}: This test case should not produce a text directive`);
    741    }
    742 
    743    async function testMultipleRanges() {
    744      let testCase = "Two single-word text directives";
    745      const range1 = document.createRange();
    746      range1.setStart(abc.firstChild, 0);
    747      range1.setEnd(abc.firstChild, 3);
    748      const range2 = document.createRange();
    749      range2.setStart(foo.firstChild, 0);
    750      range2.setEnd(foo.firstChild, 3);
    751      const textDirective = await SpecialPowers.wrap(document).fragmentDirective.createTextDirectiveForRanges([range1, range2]);
    752      is(textDirective, "text=abc&text=foo", `${testCase}: The text fragment has the expected value`);
    753    }
    754 
    755    async function runTests() {
    756      try {
    757        testRangeBoundariesAreEqual();
    758        await basicTests();
    759        await testEmptyRange();
    760        await testExpandRangeToWordBoundaries();
    761        await testNonUniqueTestSurroundedByBlockBoundaries();
    762        await testRangeBasedWithEmptySurroundingBlocks();
    763        await testRangeBasedWithNonBreakableWord();
    764        await testMultipleRanges();
    765 
    766    }
    767    finally {
    768      SimpleTest.finish();
    769    }
    770  }
    771 
    772  document.body.onload = runTests;
    773  </script>
    774 </body>
    775 </html>