<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=202251
https://bugzilla.mozilla.org/show_bug.cgi?id=450048
https://bugzilla.mozilla.org/show_bug.cgi?id=812837
https://bugzilla.mozilla.org/show_bug.cgi?id=969980
https://bugzilla.mozilla.org/show_bug.cgi?id=1589786
https://bugzilla.mozilla.org/show_bug.cgi?id=1611568
https://bugzilla.mozilla.org/show_bug.cgi?id=1649187
https://bugzilla.mozilla.org/show_bug.cgi?id=1699599
-->
<head>
  <meta charset="UTF-8">
  <title>Test for nsFind::Find()</title>
  <script src="/tests/SimpleTest/SimpleTest.js"></script>
  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>

<script type="application/javascript">

SimpleTest.waitForExplicitFinish();

async function runTests() {
  // Check nsFind class and its nsIFind interface.

  // Inject some text that we'll search for later.
  const NULL_CHARACTER = "\0";
  const INJECTED_NULL_TEXT = "injected null\0";
  const nullcharsinjected = document.getElementById("nullcharsinjected");
  const nulltextnode = document.createTextNode(INJECTED_NULL_TEXT);
  nullcharsinjected.appendChild(nulltextnode);

  // Take steps to ensure that the frame is created for the newly added
  // nulltextnode. Our find code is dependent upon finding visible frames.
  // One way to ensure the frame exists is to ask for its bounds.
  const injectionBounds = nullcharsinjected.getBoundingClientRect();
  ok(injectionBounds, "Got a bounding rect for the injected text container.");

  var rf = SpecialPowers.Cc["@mozilla.org/embedcomp/rangefind;1"]
                        .getService(SpecialPowers.Ci.nsIFind);

  var display = window.document.getElementById("display");
  var searchRange = window.document.createRange();
  searchRange.setStart(display, 0);
  searchRange.setEnd(display, display.childNodes.length);
  var startPt = searchRange;
  var endPt = searchRange;

  var searchValue, retRange;

  rf.findBackwards = false;

  rf.caseSensitive = false;
  rf.matchDiacritics = false;
  rf.entireWord = false;

  searchValue = "TexT";
  retRange = rf.Find(searchValue, searchRange, startPt, endPt);
  ok(retRange, "\"" + searchValue + "\" not found (not caseSensitive)");

  searchValue = "λογος";
  retRange = rf.Find(searchValue, searchRange, startPt, endPt);
  ok(retRange, "\"" + searchValue + "\" not found (not caseSensitive)");

  searchValue = "유";
  retRange = rf.Find(searchValue, searchRange, startPt, endPt);
  ok(!retRange, "\"" + searchValue + "\" found (not caseSensitive)");

  searchValue = "కె";
  retRange = rf.Find(searchValue, searchRange, startPt, endPt);
  ok(!retRange, "\"" + searchValue + "\" found (not caseSensitive)");

  searchValue = "𑂥";
  retRange = rf.Find(searchValue, searchRange, startPt, endPt);
  ok(retRange, "\"" + searchValue + "\" not found (not caseSensitive)");

  searchValue = "istanbul";
  retRange = rf.Find(searchValue, searchRange, startPt, endPt);
  ok(retRange, "\"" + searchValue + "\" not found (not caseSensitive)");

  searchValue = "wroclaw";
  retRange = rf.Find(searchValue, searchRange, startPt, endPt);
  ok(retRange, "\"" + searchValue + "\" not found (not caseSensitive)");

  searchValue = "goteborg";
  retRange = rf.Find(searchValue, searchRange, startPt, endPt);
  ok(retRange, "\"" + searchValue + "\" not found (not caseSensitive)");

  searchValue = "degrees k";
  retRange = rf.Find(searchValue, searchRange, startPt, endPt);
  ok(retRange, "\"" + searchValue + "\" not found (not caseSensitive)");

  searchValue = "≠";
  retRange = rf.Find(searchValue, searchRange, startPt, endPt);
  ok(!retRange, "\"" + searchValue + "\" found (not caseSensitive)");

  searchValue = "guahe";
  retRange = rf.Find(searchValue, searchRange, startPt, endPt);
  ok(retRange, "\"" + searchValue + "\" not found (not caseSensitive)");

  searchValue = "g̃uah̰e";
  retRange = rf.Find(searchValue, searchRange, startPt, endPt);
  ok(retRange, "\"" + searchValue + "\" not found (not caseSensitive)");

  searchValue = "𐐸𐐯𐑊𐐬";
  retRange = rf.Find(searchValue, searchRange, startPt, endPt);
  ok(retRange, "\"" + searchValue + "\" not found (not caseSensitive)");

  searchValue = "東京駅"
  retRange = rf.Find(searchValue, searchRange, startPt, endPt);
  ok(retRange, "\"" + searchValue + "\" not found");

  rf.matchDiacritics = true;

  searchValue = "λογος";
  retRange = rf.Find(searchValue, searchRange, startPt, endPt);
  ok(!retRange, "\"" + searchValue + "\" found (matchDiacritics on)");

  searchValue = "g̃uahe";
  retRange = rf.Find(searchValue, searchRange, startPt, endPt);
  ok(retRange, "\"" + searchValue + "\" not found (matchDiacritics on)");

  searchValue = "guahe";
  retRange = rf.Find(searchValue, searchRange, startPt, endPt);
  ok(!retRange, "\"" + searchValue + "\" found (matchDiacritics on)");

  rf.caseSensitive = true;

  searchValue = "TexT";
  retRange = rf.Find(searchValue, searchRange, startPt, endPt);
  ok(!retRange, "\"" + searchValue + "\" found (caseSensitive)");

  searchValue = "text";
  retRange = rf.Find(searchValue, searchRange, startPt, endPt);
  ok(retRange, "\"" + searchValue + "\" not found");

  // Matches |i<b>n&shy;t</b>o|.
  searchValue = "into";
  retRange = rf.Find(searchValue, searchRange, startPt, endPt);
  ok(retRange, "\"" + searchValue + "\" not found");

  // Matches inside |search|.
  searchValue = "ear";
  retRange = rf.Find(searchValue, searchRange, startPt, endPt);
  ok(retRange, "\"" + searchValue + "\" not found");

  // Set new start point (to end of last search).
  startPt = retRange.endContainer.ownerDocument.createRange();
  startPt.setStart(retRange.endContainer, retRange.endOffset);
  startPt.setEnd(retRange.endContainer, retRange.endOffset);

  searchValue = "t";
  retRange = rf.Find(searchValue, searchRange, startPt, endPt);
  ok(retRange, "\"" + searchValue + "\" not found (forward)");

  searchValue = "the";
  retRange = rf.Find(searchValue, searchRange, startPt, endPt);
  ok(!retRange, "\"" + searchValue + "\" found (forward)");

  rf.findBackwards = true;

  // searchValue = "the";
  retRange = rf.Find(searchValue, searchRange, startPt, endPt);
  ok(retRange, "\"" + searchValue + "\" not found (backward)");

  // Curly quotes and straight quotes should match.

  rf.matchDiacritics = false;
  rf.findBackwards = false;

  function find(node, value) {
    var range = document.createRange();
    range.setStart(node, 0);
    range.setEnd(node, node.childNodes.length);
    return rf.Find(value, range, range, range);
  }

  function assertFound(node, value) {
    ok(find(node, value), "\"" + value + "\" should be found");
  }

  function assertNotFound(node, value) {
    ok(!find(node, value), "\"" + value + "\" should not be found");
  }

  var quotes = document.getElementById("quotes");

  assertFound(quotes, "\"straight\"");
  assertFound(quotes, "\u201Cstraight\u201D");

  assertNotFound(quotes, "'straight'");
  assertNotFound(quotes, "\u2018straight\u2019");
  assertNotFound(quotes, "\u2019straight\u2018");
  assertNotFound(quotes, ".straight.");

  assertFound(quotes, "\"curly\"");
  assertFound(quotes, "\u201Ccurly\u201D");

  assertNotFound(quotes, "'curly'");
  assertNotFound(quotes, "\u2018curly\u2019");
  assertNotFound(quotes, ".curly.");

  assertFound(quotes, "didn't");
  assertFound(quotes, "didn\u2018t");
  assertFound(quotes, "didn\u2019t");

  assertNotFound(quotes, "didnt");
  assertNotFound(quotes, "didn t");
  assertNotFound(quotes, "didn.t");

  assertFound(quotes, "'didn't'");
  assertFound(quotes, "'didn\u2018t'");
  assertFound(quotes, "'didn\u2019t'");
  assertFound(quotes, "\u2018didn't\u2019");
  assertFound(quotes, "\u2019didn't\u2018");
  assertFound(quotes, "\u2018didn't\u2018");
  assertFound(quotes, "\u2019didn't\u2019");
  assertFound(quotes, "\u2018didn\u2019t\u2019");
  assertFound(quotes, "\u2019didn\u2018t\u2019");
  assertFound(quotes, "\u2018didn\u2019t\u2018");

  assertNotFound(quotes, "\"didn't\"");
  assertNotFound(quotes, "\u201Cdidn't\u201D");

  assertFound(quotes, "doesn't");
  assertFound(quotes, "doesn\u2018t");
  assertFound(quotes, "doesn\u2019t");

  assertNotFound(quotes, "doesnt");
  assertNotFound(quotes, "doesn t");
  assertNotFound(quotes, "doesn.t");

  assertFound(quotes, "'doesn't'");
  assertFound(quotes, "'doesn\u2018t'");
  assertFound(quotes, "'doesn\u2019t'");
  assertFound(quotes, "\u2018doesn't\u2019");
  assertFound(quotes, "\u2019doesn't\u2018");
  assertFound(quotes, "\u2018doesn't\u2018");
  assertFound(quotes, "\u2019doesn't\u2019");
  assertFound(quotes, "\u2018doesn\u2019t\u2019");
  assertFound(quotes, "\u2019doesn\u2018t\u2019");
  assertFound(quotes, "\u2018doesn\u2019t\u2018");

  assertNotFound(quotes, "\"doesn't\"");
  assertNotFound(quotes, "\u201Cdoesn't\u201D");

  // Curly quotes and straight quotes should not match.
  rf.matchDiacritics = true;

  assertFound(quotes, "\"straight\"");
  assertNotFound(quotes, "\u201Cstraight\u201D");

  assertNotFound(quotes, "\"curly\"");
  assertFound(quotes, "\u201Ccurly\u201D");

  assertFound(quotes, "\u2018didn't\u2019");
  assertNotFound(quotes, "'didn't'");

  assertFound(quotes, "'doesn\u2019t'");
  assertNotFound(quotes, "'doesn\u2018t'");
  assertNotFound(quotes, "'doesn't'");

  // Embedded strings containing null characters can't be found, because
  // the HTML parser converts them to \ufffd, which is the replacement
  // character.
  const nullcharsnative = document.getElementById("nullcharsnative");
  assertFound(nullcharsnative, "native null\ufffd");

  // Injected strings containing null characters can be found.
  assertFound(nullcharsinjected, NULL_CHARACTER);
  assertFound(nullcharsinjected, INJECTED_NULL_TEXT);

  // Content skipped via content-visibility can't be found.
  assertNotFound(quotes, "Tardigrade");
  assertNotFound(quotes, "Amoeba");

  // Simple checks for behavior of the entireWord option.
  var entireWord = document.getElementById("entireWord");
  rf.entireWord = true;
  assertFound(entireWord, "one");
  assertNotFound(entireWord, "on");
  assertFound(entireWord, "(one)");
  assertFound(entireWord, "two");
  assertFound(entireWord, "[two]");
  assertFound(entireWord, "[three]");
  assertFound(entireWord, "foo");
  assertFound(entireWord, "-foo");
  assertFound(entireWord, "bar");
  assertFound(entireWord, "-bar");
  assertNotFound(entireWord, "-fo");
  assertNotFound(entireWord, "-ba");

  rf.entireWord = false;
  assertFound(entireWord, "on");
  assertFound(entireWord, "-fo");
  assertFound(entireWord, "-ba");

  // Searching in elements with display: table-*, bug 1645990
  var table = document.getElementById("tabular");
  assertFound(table, "One");
  assertFound(table, "TwoThree");  // across adjacent cells
  assertNotFound(table, "wordsanother");  // not across rows
  rf.entireWord = true;
  assertNotFound(table, "One");  // because nothing separates it from next cell
  assertFound(table, "several");
  assertFound(table, "whole");
  assertFound(table, "words");
  rf.entireWord = false;

  // Do these test at the end since they trigger failure screenshots in
  // the test harness, and we want as much information as possible about
  // any OTHER tests that may have already failed.

  // Check |null| detection on |aSearchRange| parameter.
  try {
    rf.Find("", null, startPt, endPt);

    ok(false, "Missing NS_ERROR_ILLEGAL_VALUE exception");
  } catch (e) {
    let wrappedError = SpecialPowers.wrap(e);
    if (wrappedError.result == SpecialPowers.Cr.NS_ERROR_ILLEGAL_VALUE) {
      ok(true, null);
    } else {
      throw wrappedError;
    }
  }

  // Check |null| detection on |aStartPoint| parameter.
  try {
    rf.Find("", searchRange, null, endPt);

    ok(false, "Missing NS_ERROR_ILLEGAL_VALUE exception");
  } catch (e) {
    let wrappedError = SpecialPowers.wrap(e);
    if (wrappedError.result == SpecialPowers.Cr.NS_ERROR_ILLEGAL_VALUE) {
      ok(true, null);
    } else {
      throw wrappedError;
    }
  }

  // Check |null| detection on |aEndPoint| parameter.
  try {
    rf.Find("", searchRange, startPt, null);

    ok(false, "Missing NS_ERROR_ILLEGAL_VALUE exception");
  } catch (e) {
    let wrappedError = SpecialPowers.wrap(e);
    if (wrappedError.result == SpecialPowers.Cr.NS_ERROR_ILLEGAL_VALUE) {
      ok(true, null);
    } else {
      throw wrappedError;
    }
  }

  SimpleTest.finish();
}
</script>
<style>
#tabular { display: table; }
#tabular div { display: table-row; }
#tabular div div { display: table-cell; }
</style>
</head>
<body onload="runTests()">
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=450048">Mozilla Bug 450048</a>
<p id="display">This is the text to search i<b>n&shy;t</b>o</p>
<p id="quotes">"straight" and &ldquo;curly&rdquo; and &lsquo;didn't&rsquo; and 'doesn&rsquo;t'</p>
<p id="nullcharsnative">native null&#0;</p>
<p id="nullcharsinjected"></p>
<p id="greek">ΛΌΓΟΣ</p>
<p id="korean">위</p>
<p id="telugu">కై</p>
<p id="kaithi">𑂫</p>
<p id="turkish">İstanbul</p>
<p id="polish">Wrocław</p>
<p id="norwegian">Gøteborg</p>
<p id="kelvin">degrees &#x212A;</p>
<p id="math">=</p>
<p id="guarani">G̃uahe</p>
<p id="deseret">𐐐𐐯𐑊𐐬 𐐶𐐯𐑉𐑊𐐼!</p>
<p id="ruby"><ruby>東<rt>とう</rt></ruby><ruby>京<rt>きょう</ruby>駅</p>
<div id="content-visibility-hidden" style="content-visibility: hidden">
    Tardigrade
    <div>Amoeba</div>
</div>
<div id="entireWord"><p>(one)</p><p>[two] [three]</p><p>-foo -bar</p></div>
<div id="content" style="display: none">

</div>
<div id="tabular">
  <div><div>One</div><div>Two</div><div>Three</div></div>
  <div><div></div><div></div><div>several whole words</div></div>
  <div><div>one</div><div>more</div><div>row</div></div>
</div>
<pre id="test">
</pre>
</body>
</html>
