tor-browser

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

TestHTMLEditUtils.cpp (76903B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "gtest/gtest.h"
      7 #include "mozilla/BasePrincipal.h"
      8 #include "mozilla/OriginAttributes.h"
      9 #include "mozilla/dom/CDATASection.h"
     10 #include "mozilla/dom/Comment.h"
     11 #include "mozilla/dom/Document.h"
     12 #include "mozilla/dom/Text.h"
     13 #include "EditorDOMPoint.h"
     14 #include "HTMLEditUtils.h"
     15 #include "nsCOMPtr.h"
     16 #include "nsGenericHTMLElement.h"
     17 #include "nsIURI.h"
     18 #include "nsNetUtil.h"
     19 #include "nsString.h"
     20 #include "nsTextNode.h"
     21 
     22 namespace mozilla {
     23 
     24 using namespace dom;
     25 
     26 using AncestorType = HTMLEditUtils::AncestorType;
     27 using AncestorTypes = HTMLEditUtils::AncestorTypes;
     28 using EditablePointOption = HTMLEditUtils::EditablePointOption;
     29 using EditablePointOptions = HTMLEditUtils::EditablePointOptions;
     30 
     31 static already_AddRefed<Document> CreateHTMLDoc() {
     32  nsCOMPtr<nsIURI> uri;
     33  NS_NewURI(getter_AddRefs(uri), "data:text/html,");
     34 
     35  RefPtr<BasePrincipal> principal =
     36      BasePrincipal::CreateContentPrincipal(uri, OriginAttributes());
     37  MOZ_RELEASE_ASSERT(principal);
     38 
     39  nsCOMPtr<Document> doc;
     40  MOZ_ALWAYS_SUCCEEDS(NS_NewDOMDocument(getter_AddRefs(doc),
     41                                        u""_ns,   // aNamespaceURI
     42                                        u""_ns,   // aQualifiedName
     43                                        nullptr,  // aDoctype
     44                                        uri, uri, principal,
     45                                        LoadedAsData::No,  // aLoadedAsData
     46                                        nullptr,           // aEventObject
     47                                        DocumentFlavor::HTML));
     48  MOZ_RELEASE_ASSERT(doc);
     49 
     50  RefPtr<Element> html = doc->CreateHTMLElement(nsGkAtoms::html);
     51  html->SetInnerHTMLTrusted(u"<html><head></head><body></body></html>"_ns,
     52                            principal, IgnoreErrors());
     53  doc->AppendChild(*html, IgnoreErrors());
     54 
     55  return doc.forget();
     56 }
     57 
     58 struct MOZ_STACK_CLASS DeepestEditablePointTest final {
     59  const char16_t* const mInnerHTML;
     60  const char* const mContentSelector;
     61  const EditablePointOptions mOptions;
     62  const char* const mExpectedContainerSelector;
     63  const char16_t* const mExpectedTextData;
     64  const uint32_t mExpectedOffset;
     65 
     66  friend std::ostream& operator<<(std::ostream& aStream,
     67                                  const DeepestEditablePointTest& aTest) {
     68    return aStream << "Scan \"" << aTest.mContentSelector
     69                   << "\" with options=" << ToString(aTest.mOptions).c_str()
     70                   << " in \"" << NS_ConvertUTF16toUTF8(aTest.mInnerHTML).get()
     71                   << "\"";
     72  }
     73 };
     74 
     75 TEST(HTMLEditUtilsTest, GetDeepestEditableStartPointOf)
     76 {
     77  const RefPtr<Document> doc = CreateHTMLDoc();
     78  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
     79  MOZ_RELEASE_ASSERT(body);
     80  for (const auto& testData : {
     81           DeepestEditablePointTest{
     82               u"<div contenteditable><div><br></div></div>",
     83               "div[contenteditable] > div",
     84               {},
     85               "div[contenteditable] > div",
     86               nullptr,
     87               0  // Find <br>
     88           },
     89           DeepestEditablePointTest{
     90               u"<div contenteditable><div><img></div></div>",
     91               "div[contenteditable] > div",
     92               {},
     93               "div[contenteditable] > div",
     94               nullptr,
     95               0  // Find <img>
     96           },
     97           DeepestEditablePointTest{
     98               u"<div contenteditable><div><hr></div></div>",
     99               "div[contenteditable] > div",
    100               {},
    101               "div[contenteditable] > div",
    102               nullptr,
    103               0  // Find <hr>
    104           },
    105           DeepestEditablePointTest{
    106               u"<div contenteditable><div>abc</div></div>",
    107               "div[contenteditable] > div",
    108               {},
    109               "div[contenteditable] > div",
    110               u"abc",
    111               0  // Find "a"
    112           },
    113           DeepestEditablePointTest{
    114               u"<div contenteditable><div><p>abc</p></div></div>",
    115               "div[contenteditable] > div",
    116               {},
    117               "div[contenteditable] > div > p",
    118               u"abc",
    119               0  // Find "a"
    120           },
    121           DeepestEditablePointTest{
    122               u"<div contenteditable><div><span>abc</span></div></div>",
    123               "div[contenteditable] > div",
    124               {},
    125               "div[contenteditable] > div > span",
    126               u"abc",
    127               0  // Find "a"
    128           },
    129           DeepestEditablePointTest{
    130               u"<div contenteditable><div>   abc</div></div>",
    131               "div[contenteditable] > div",
    132               {},
    133               "div[contenteditable] > div",
    134               u"   abc",
    135               3  // Find "a"
    136           },
    137           DeepestEditablePointTest{
    138               u"<div contenteditable><div><span>   abc</span></div></div>",
    139               "div[contenteditable] > div",
    140               {},
    141               "div[contenteditable] > div > span",
    142               u"   abc",
    143               3  // Find "a"
    144           },
    145           DeepestEditablePointTest{
    146               u"<div contenteditable><div>   abc</div></div>",
    147               "div[contenteditable] > div",
    148               {EditablePointOption::RecognizeInvisibleWhiteSpaces},
    149               "div[contenteditable] > div",
    150               u"   abc",
    151               0  // Find the first white-space
    152           },
    153           DeepestEditablePointTest{
    154               u"<div contenteditable><div><span>   abc</span></div></div>",
    155               "div[contenteditable] > div",
    156               {EditablePointOption::RecognizeInvisibleWhiteSpaces},
    157               "div[contenteditable] > div > span",
    158               u"   abc",
    159               0  // Find the first white-space
    160           },
    161           DeepestEditablePointTest{
    162               u"<div contenteditable><div><span></span>abc</div></div>",
    163               "div[contenteditable] > div",
    164               {},
    165               "div[contenteditable] > div > span",
    166               nullptr,
    167               0  // Find the empty <span>
    168           },
    169           DeepestEditablePointTest{
    170               u"<div contenteditable><div><!-- comment -->abc</div></div>",
    171               "div[contenteditable] > div",
    172               {},
    173               "div[contenteditable] > div",
    174               u"abc",
    175               0  // Find "a"
    176           },
    177           DeepestEditablePointTest{
    178               u"<div contenteditable><div><!-- comment -->abc</div></div>",
    179               "div[contenteditable] > div",
    180               {EditablePointOption::StopAtComment},
    181               "div[contenteditable] > div",
    182               nullptr,
    183               0  // Find the comment
    184           },
    185           // inline-block may have leading white-spaces.  Therefore, even if
    186           // the start container is an inline element which follows visible
    187           // characters, it should return the first visible character in the
    188           // inline-block.
    189           DeepestEditablePointTest{
    190               u"<div contenteditable><div>abc<b><span style=\"display: "
    191               u"inline-block\">   def</span></b></div></div>",
    192               "div[contenteditable] > div > b",
    193               {},
    194               "div[contenteditable] > div > b > span",
    195               u"   def",
    196               3  // Find "d"
    197           },
    198           // There is a child <table>
    199           DeepestEditablePointTest{
    200               u"<div contenteditable><div><table><td><br></table></div></div>",
    201               "div[contenteditable] > div",
    202               {},
    203               "div[contenteditable] td",
    204               nullptr,
    205               0  // Find <br>
    206           },
    207           DeepestEditablePointTest{
    208               u"<div contenteditable><div><table><td><br></table></div></div>",
    209               "div[contenteditable] > div",
    210               {EditablePointOption::StopAtTableElement},
    211               "div[contenteditable] > div",
    212               nullptr,
    213               0  // Find <table>
    214           },
    215           DeepestEditablePointTest{
    216               u"<div contenteditable><div><table><td><br></table></div></div>",
    217               "div[contenteditable] > div",
    218               {EditablePointOption::StopAtAnyTableElement},
    219               "div[contenteditable] > div",
    220               nullptr,
    221               0  // Find <table>
    222           },
    223           // In a table structure
    224           DeepestEditablePointTest{
    225               u"<div contenteditable><div><table><td><br></table></div></div>",
    226               "div[contenteditable] table",
    227               {},
    228               "div[contenteditable] td",
    229               nullptr,
    230               0  // Find <br>
    231           },
    232           DeepestEditablePointTest{
    233               u"<div contenteditable><div><table><td><br></table></div></div>",
    234               "div[contenteditable] table",
    235               {EditablePointOption::StopAtTableElement},
    236               "div[contenteditable] td",
    237               nullptr,
    238               0  // Find <br>
    239           },
    240           DeepestEditablePointTest{
    241               u"<div contenteditable><div><table><td><br></table></div></div>",
    242               "div[contenteditable] table",
    243               {EditablePointOption::StopAtAnyTableElement},
    244               "div[contenteditable] table",
    245               nullptr,
    246               0  // Find <td>
    247           },
    248           // <ul>
    249           DeepestEditablePointTest{
    250               u"<div contenteditable><div><ul><li><br></li></ul></div></div>",
    251               "div[contenteditable] > div",
    252               {},
    253               "div[contenteditable] li",
    254               nullptr,
    255               0  // Find <br>
    256           },
    257           DeepestEditablePointTest{
    258               u"<div contenteditable><div><ul><li><br></li></ul></div></div>",
    259               "div[contenteditable] > div",
    260               {EditablePointOption::StopAtListItemElement},
    261               "div[contenteditable] ul",
    262               nullptr,
    263               0  // Find <li>
    264           },
    265           DeepestEditablePointTest{
    266               u"<div contenteditable><div><ul><li><br></li></ul></div></div>",
    267               "div[contenteditable] > div",
    268               {EditablePointOption::StopAtListElement},
    269               "div[contenteditable] > div",
    270               nullptr,
    271               0  // Find <ul>
    272           },
    273           // <ol>
    274           DeepestEditablePointTest{
    275               u"<div contenteditable><div><ol><li><br></li></ol></div></div>",
    276               "div[contenteditable] > div",
    277               {},
    278               "div[contenteditable] li",
    279               nullptr,
    280               0  // Find <br>
    281           },
    282           DeepestEditablePointTest{
    283               u"<div contenteditable><div><ol><li><br></li></ol></div></div>",
    284               "div[contenteditable] > div",
    285               {EditablePointOption::StopAtListItemElement},
    286               "div[contenteditable] ol",
    287               nullptr,
    288               0  // Find <li>
    289           },
    290           DeepestEditablePointTest{
    291               u"<div contenteditable><div><ol><li><br></li></ol></div></div>",
    292               "div[contenteditable] > div",
    293               {EditablePointOption::StopAtListElement},
    294               "div[contenteditable] > div",
    295               nullptr,
    296               0  // Find <ol>
    297           },
    298           // <dl> and <dt>
    299           DeepestEditablePointTest{
    300               u"<div contenteditable><div><dl><dt><br></dt></dl></div></div>",
    301               "div[contenteditable] > div",
    302               {},
    303               "div[contenteditable] dt",
    304               nullptr,
    305               0  // Find <br>
    306           },
    307           DeepestEditablePointTest{
    308               u"<div contenteditable><div><dl><dt><br></dt></dl></div></div>",
    309               "div[contenteditable] > div",
    310               {EditablePointOption::StopAtListItemElement},
    311               "div[contenteditable] dl",
    312               nullptr,
    313               0  // Find <dt>
    314           },
    315           DeepestEditablePointTest{
    316               u"<div contenteditable><div><dl><dt><br></dt></dl></div></div>",
    317               "div[contenteditable] > div",
    318               {EditablePointOption::StopAtListElement},
    319               "div[contenteditable] > div",
    320               nullptr,
    321               0  // Find <dl>
    322           },
    323           // <dl> and <dd>
    324           DeepestEditablePointTest{
    325               u"<div contenteditable><div><dl><dd><br></dd></dl></div></div>",
    326               "div[contenteditable] > div",
    327               {},
    328               "div[contenteditable] dd",
    329               nullptr,
    330               0  // Find <br>
    331           },
    332           DeepestEditablePointTest{
    333               u"<div contenteditable><div><dl><dd><br></dd></dl></div></div>",
    334               "div[contenteditable] > div",
    335               {EditablePointOption::StopAtListItemElement},
    336               "div[contenteditable] dl",
    337               nullptr,
    338               0  // Find <dd>
    339           },
    340           DeepestEditablePointTest{
    341               u"<div contenteditable><div><dl><dd><br></dd></dl></div></div>",
    342               "div[contenteditable] > div",
    343               {EditablePointOption::StopAtListElement},
    344               "div[contenteditable] > div",
    345               nullptr,
    346               0  // Find <dl>
    347           },
    348       }) {
    349    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
    350                              doc->NodePrincipal(), IgnoreErrors());
    351    const Element* const content = body->QuerySelector(
    352        nsDependentCString(testData.mContentSelector), IgnoreErrors());
    353    MOZ_RELEASE_ASSERT(content);
    354    const nsIContent* const expectedContainer = [&]() -> const nsIContent* {
    355      const Element* const containerElement = body->QuerySelector(
    356          nsDependentCString(testData.mExpectedContainerSelector),
    357          IgnoreErrors());
    358      if (!testData.mExpectedTextData) {
    359        return containerElement;
    360      }
    361      for (const nsIContent* child = containerElement->GetFirstChild(); child;
    362           child = child->GetNextSibling()) {
    363        if (const auto* text = Text::FromNodeOrNull(child)) {
    364          nsAutoString data;
    365          text->GetData(data);
    366          if (data.Equals(testData.mExpectedTextData)) {
    367            return text;
    368          }
    369        }
    370      }
    371      return nullptr;
    372    }();
    373    MOZ_RELEASE_ASSERT(expectedContainer);
    374    const EditorRawDOMPoint result =
    375        HTMLEditUtils::GetDeepestEditableStartPointOf<EditorRawDOMPoint>(
    376            *content, testData.mOptions);
    377    EXPECT_EQ(result.GetContainer(), expectedContainer)
    378        << testData << "(Got: " << ToString(RefPtr{result.GetContainer()})
    379        << ")";
    380    EXPECT_EQ(result.Offset(), testData.mExpectedOffset) << testData;
    381  }
    382 }
    383 
    384 TEST(HTMLEditUtilsTest, GetDeepestEditableEndPointOf)
    385 {
    386  const RefPtr<Document> doc = CreateHTMLDoc();
    387  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
    388  MOZ_RELEASE_ASSERT(body);
    389  for (const auto& testData : {
    390           DeepestEditablePointTest{
    391               u"<div contenteditable><div><br></div></div>",
    392               "div[contenteditable] > div",
    393               {},
    394               "div[contenteditable] > div",
    395               nullptr,
    396               // XXX Should be 0 due to an invisible <br>?
    397               1  // Find <br>
    398           },
    399           DeepestEditablePointTest{
    400               u"<div contenteditable><div><img></div></div>",
    401               "div[contenteditable] > div",
    402               {},
    403               "div[contenteditable] > div",
    404               nullptr,
    405               1  // Find <img>
    406           },
    407           DeepestEditablePointTest{
    408               u"<div contenteditable><div><hr></div></div>",
    409               "div[contenteditable] > div",
    410               {},
    411               "div[contenteditable] > div",
    412               nullptr,
    413               1  // Find <hr>
    414           },
    415           DeepestEditablePointTest{
    416               u"<div contenteditable><div>abc</div></div>",
    417               "div[contenteditable] > div",
    418               {},
    419               "div[contenteditable] > div",
    420               u"abc",
    421               3  // Find "c"
    422           },
    423           DeepestEditablePointTest{
    424               u"<div contenteditable><div><p>abc</p></div></div>",
    425               "div[contenteditable] > div",
    426               {},
    427               "div[contenteditable] > div > p",
    428               u"abc",
    429               3  // Find "c"
    430           },
    431           DeepestEditablePointTest{
    432               u"<div contenteditable><div><span>abc</span></div></div>",
    433               "div[contenteditable] > div",
    434               {},
    435               "div[contenteditable] > div > span",
    436               u"abc",
    437               3  // Find "c"
    438           },
    439           DeepestEditablePointTest{
    440               u"<div contenteditable><div>abc   </div></div>",
    441               "div[contenteditable] > div",
    442               {},
    443               "div[contenteditable] > div",
    444               u"abc   ",
    445               3  // Find "c"
    446           },
    447           DeepestEditablePointTest{
    448               u"<div contenteditable><div><span>abc   </span></div></div>",
    449               "div[contenteditable] > div",
    450               {},
    451               "div[contenteditable] > div > span",
    452               u"abc   ",
    453               3  // Find "a"
    454           },
    455           DeepestEditablePointTest{
    456               u"<div contenteditable><div>abc   </div></div>",
    457               "div[contenteditable] > div",
    458               {EditablePointOption::RecognizeInvisibleWhiteSpaces},
    459               "div[contenteditable] > div",
    460               u"abc   ",
    461               6  // Find the last white-space
    462           },
    463           DeepestEditablePointTest{
    464               u"<div contenteditable><div><span>abc   </span></div></div>",
    465               "div[contenteditable] > div",
    466               {EditablePointOption::RecognizeInvisibleWhiteSpaces},
    467               "div[contenteditable] > div > span",
    468               u"abc   ",
    469               6  // Find the last white-space
    470           },
    471           DeepestEditablePointTest{
    472               u"<div contenteditable><div>abc<span></span></div></div>",
    473               "div[contenteditable] > div",
    474               {},
    475               "div[contenteditable] > div > span",
    476               nullptr,
    477               0  // Find the empty <span>
    478           },
    479           DeepestEditablePointTest{
    480               u"<div contenteditable><div>abc<!-- comment --></div></div>",
    481               "div[contenteditable] > div",
    482               {},
    483               "div[contenteditable] > div",
    484               u"abc",
    485               3  // Find "c"
    486           },
    487           DeepestEditablePointTest{
    488               u"<div contenteditable><div>abc<!-- comment --></div></div>",
    489               "div[contenteditable] > div",
    490               {EditablePointOption::StopAtComment},
    491               "div[contenteditable] > div",
    492               nullptr,
    493               2  // Find the comment
    494           },
    495           // inline-block may have leading white-spaces.  Therefore, even if
    496           // the start container is an inline element which is followed by
    497           // visible characters, it should return the last visible character
    498           // in the inline-block.
    499           DeepestEditablePointTest{
    500               u"<div contenteditable><div><b><span style=\"display: "
    501               u"inline-block\">abc   </span></b>def</div></div>",
    502               "div[contenteditable] > div > b",
    503               {},
    504               "div[contenteditable] > div > b > span",
    505               u"abc   ",
    506               3  // Find "c"
    507           },
    508           // There is a child <table>
    509           DeepestEditablePointTest{
    510               u"<div contenteditable><div><table><td><br></table></div></div>",
    511               "div[contenteditable] > div",
    512               {},
    513               "div[contenteditable] td",
    514               nullptr,
    515               1  // Find <br>
    516           },
    517           DeepestEditablePointTest{
    518               u"<div contenteditable><div><table><td><br></table></div></div>",
    519               "div[contenteditable] > div",
    520               {EditablePointOption::StopAtTableElement},
    521               "div[contenteditable] > div",
    522               nullptr,
    523               1  // Find <table>
    524           },
    525           DeepestEditablePointTest{
    526               u"<div contenteditable><div><table><td><br></table></div></div>",
    527               "div[contenteditable] > div",
    528               {EditablePointOption::StopAtAnyTableElement},
    529               "div[contenteditable] > div",
    530               nullptr,
    531               1  // Find <table>
    532           },
    533           // In a table structure
    534           DeepestEditablePointTest{
    535               u"<div contenteditable><div><table><td><br></table></div></div>",
    536               "div[contenteditable] table",
    537               {},
    538               "div[contenteditable] td",
    539               nullptr,
    540               1  // Find <br>
    541           },
    542           DeepestEditablePointTest{
    543               u"<div contenteditable><div><table><td><br></table></div></div>",
    544               "div[contenteditable] table",
    545               {EditablePointOption::StopAtTableElement},
    546               "div[contenteditable] td",
    547               nullptr,
    548               1  // Find <br>
    549           },
    550           DeepestEditablePointTest{
    551               u"<div contenteditable><div><table><td><br></table></div></div>",
    552               "div[contenteditable] table",
    553               {EditablePointOption::StopAtAnyTableElement},
    554               "div[contenteditable] table",
    555               nullptr,
    556               1  // Find <td>
    557           },
    558           // <ul>
    559           DeepestEditablePointTest{
    560               u"<div contenteditable><div><ul><li><br></li></ul></div></div>",
    561               "div[contenteditable] > div",
    562               {},
    563               "div[contenteditable] li",
    564               nullptr,
    565               1  // Find <br>
    566           },
    567           DeepestEditablePointTest{
    568               u"<div contenteditable><div><ul><li><br></li></ul></div></div>",
    569               "div[contenteditable] > div",
    570               {EditablePointOption::StopAtListItemElement},
    571               "div[contenteditable] ul",
    572               nullptr,
    573               1  // Find <li>
    574           },
    575           DeepestEditablePointTest{
    576               u"<div contenteditable><div><ul><li><br></li></ul></div></div>",
    577               "div[contenteditable] > div",
    578               {EditablePointOption::StopAtListElement},
    579               "div[contenteditable] > div",
    580               nullptr,
    581               1  // Find <ul>
    582           },
    583           // <ol>
    584           DeepestEditablePointTest{
    585               u"<div contenteditable><div><ol><li><br></li></ol></div></div>",
    586               "div[contenteditable] > div",
    587               {},
    588               "div[contenteditable] li",
    589               nullptr,
    590               1  // Find <br>
    591           },
    592           DeepestEditablePointTest{
    593               u"<div contenteditable><div><ol><li><br></li></ol></div></div>",
    594               "div[contenteditable] > div",
    595               {EditablePointOption::StopAtListItemElement},
    596               "div[contenteditable] ol",
    597               nullptr,
    598               1  // Find <li>
    599           },
    600           DeepestEditablePointTest{
    601               u"<div contenteditable><div><ol><li><br></li></ol></div></div>",
    602               "div[contenteditable] > div",
    603               {EditablePointOption::StopAtListElement},
    604               "div[contenteditable] > div",
    605               nullptr,
    606               1  // Find <ol>
    607           },
    608           // <dl> and <dt>
    609           DeepestEditablePointTest{
    610               u"<div contenteditable><div><dl><dt><br></dt></dl></div></div>",
    611               "div[contenteditable] > div",
    612               {},
    613               "div[contenteditable] dt",
    614               nullptr,
    615               1  // Find <br>
    616           },
    617           DeepestEditablePointTest{
    618               u"<div contenteditable><div><dl><dt><br></dt></dl></div></div>",
    619               "div[contenteditable] > div",
    620               {EditablePointOption::StopAtListItemElement},
    621               "div[contenteditable] dl",
    622               nullptr,
    623               1  // Find <dt>
    624           },
    625           DeepestEditablePointTest{
    626               u"<div contenteditable><div><dl><dt><br></dt></dl></div></div>",
    627               "div[contenteditable] > div",
    628               {EditablePointOption::StopAtListElement},
    629               "div[contenteditable] > div",
    630               nullptr,
    631               1  // Find <dl>
    632           },
    633           // <dl> and <dd>
    634           DeepestEditablePointTest{
    635               u"<div contenteditable><div><dl><dd><br></dd></dl></div></div>",
    636               "div[contenteditable] > div",
    637               {},
    638               "div[contenteditable] dd",
    639               nullptr,
    640               1  // Find <br>
    641           },
    642           DeepestEditablePointTest{
    643               u"<div contenteditable><div><dl><dd><br></dd></dl></div></div>",
    644               "div[contenteditable] > div",
    645               {EditablePointOption::StopAtListItemElement},
    646               "div[contenteditable] dl",
    647               nullptr,
    648               1  // Find <dd>
    649           },
    650           DeepestEditablePointTest{
    651               u"<div contenteditable><div><dl><dd><br></dd></dl></div></div>",
    652               "div[contenteditable] > div",
    653               {EditablePointOption::StopAtListElement},
    654               "div[contenteditable] > div",
    655               nullptr,
    656               1  // Find <dl>
    657           },
    658       }) {
    659    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
    660                              doc->NodePrincipal(), IgnoreErrors());
    661    const Element* const content = body->QuerySelector(
    662        nsDependentCString(testData.mContentSelector), IgnoreErrors());
    663    MOZ_RELEASE_ASSERT(content);
    664    const nsIContent* const expectedContainer = [&]() -> const nsIContent* {
    665      const Element* const containerElement = body->QuerySelector(
    666          nsDependentCString(testData.mExpectedContainerSelector),
    667          IgnoreErrors());
    668      if (!testData.mExpectedTextData) {
    669        return containerElement;
    670      }
    671      for (const nsIContent* child = containerElement->GetLastChild(); child;
    672           child = child->GetPreviousSibling()) {
    673        if (const auto* text = Text::FromNodeOrNull(child)) {
    674          nsAutoString data;
    675          text->GetData(data);
    676          if (data.Equals(testData.mExpectedTextData)) {
    677            return text;
    678          }
    679        }
    680      }
    681      return nullptr;
    682    }();
    683    MOZ_RELEASE_ASSERT(expectedContainer);
    684    const EditorRawDOMPoint result =
    685        HTMLEditUtils::GetDeepestEditableEndPointOf<EditorRawDOMPoint>(
    686            *content, testData.mOptions);
    687    EXPECT_EQ(result.GetContainer(), expectedContainer)
    688        << testData << "(Got: " << ToString(RefPtr{result.GetContainer()})
    689        << ")";
    690    EXPECT_EQ(result.Offset(), testData.mExpectedOffset) << testData;
    691  }
    692 }
    693 
    694 struct MOZ_STACK_CLASS AncestorElementTest final {
    695  const char16_t* const mInnerHTML;
    696  const char* const mContentSelector;
    697  const AncestorTypes mAncestorTypes;
    698  const char* const mAncestorLimiterSelector;
    699  const char* const mExpectedSelectorForAncestor;
    700  const char* const mExpectedSelectorForInclusiveAncestor;
    701 
    702  friend std::ostream& operator<<(std::ostream& aStream,
    703                                  const AncestorElementTest& aTest) {
    704    return aStream << "Scan from \"" << aTest.mContentSelector
    705                   << "\" with ancestor types="
    706                   << ToString(aTest.mAncestorTypes).c_str() << " in \""
    707                   << NS_ConvertUTF16toUTF8(aTest.mInnerHTML).get() << "\"";
    708  }
    709 };
    710 
    711 TEST(HTMLEditUtilsTest, GetAncestorElement_ClosestBlockElement)
    712 {
    713  using AncestorType = HTMLEditUtils::AncestorType;
    714  const RefPtr<Document> doc = CreateHTMLDoc();
    715  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
    716  MOZ_RELEASE_ASSERT(body);
    717  for (const auto& testData : {
    718           AncestorElementTest{
    719               u"<div contenteditable><div><span><div><span><br>"
    720               u"</span></div></span></div></div>",
    721               "[contenteditable] > div > span > div > span",
    722               {AncestorType::ClosestBlockElement},
    723               "[contenteditable]",
    724               "[contenteditable] > div > span > div",
    725               "[contenteditable] > div > span > div"},
    726           AncestorElementTest{
    727               u"<div contenteditable><div><span><div><span><br>"
    728               u"</span></div></span></div></div>",
    729               "[contenteditable] > div > span > div",
    730               {AncestorType::ClosestBlockElement},
    731               "[contenteditable]",
    732               "[contenteditable] > div",
    733               "[contenteditable] > div > span > div"},
    734           AncestorElementTest{
    735               u"<div contenteditable><div><span><br></span></div></div>",
    736               "[contenteditable] > div",
    737               {AncestorType::ClosestBlockElement},
    738               "[contenteditable]",
    739               // Should return the editing host because of the closest ancestor
    740               // of the deepest <div>.
    741               "[contenteditable]",
    742               "[contenteditable] > div"},
    743           AncestorElementTest{
    744               u"<div contenteditable><span><br></span></div>",
    745               "[contenteditable] > span",
    746               {AncestorType::ClosestBlockElement},
    747               "[contenteditable]",
    748               // Should return the editing host because of a block.
    749               "[contenteditable]",
    750               "[contenteditable]"},
    751           AncestorElementTest{
    752               u"<div contenteditable><span><br></span></div>",
    753               "[contenteditable] > span",
    754               {AncestorType::ClosestBlockElement,
    755                AncestorType::ReturnAncestorLimiterIfNoProperAncestor},
    756               "[contenteditable]",
    757               "[contenteditable]",
    758               "[contenteditable]"},
    759           AncestorElementTest{u"<div contenteditable><span><br></span></div>",
    760                               "[contenteditable] > span",
    761                               {AncestorType::ClosestBlockElement,
    762                                AncestorType::EditableElement},
    763                               "[contenteditable]",
    764                               "[contenteditable]",
    765                               "[contenteditable]"},
    766           AncestorElementTest{
    767               u"<div contenteditable><span><br></span></div>",
    768               "[contenteditable] > span",
    769               {AncestorType::ClosestBlockElement,
    770                AncestorType::EditableElement,
    771                AncestorType::ReturnAncestorLimiterIfNoProperAncestor},
    772               "[contenteditable]",
    773               "[contenteditable]",
    774               "[contenteditable]"},
    775           AncestorElementTest{
    776               u"<span contenteditable><span><br></span></span>",
    777               "[contenteditable] > span",
    778               {AncestorType::ClosestBlockElement,
    779                AncestorType::EditableElement,
    780                AncestorType::ReturnAncestorLimiterIfNoProperAncestor},
    781               "[contenteditable]",
    782               // Should return the inline editing host because of the ancestor
    783               // limiter.
    784               "[contenteditable]",
    785               "[contenteditable]"},
    786           AncestorElementTest{
    787               u"<span contenteditable><span><br></span></span>",
    788               "[contenteditable] > span",
    789               {AncestorType::ClosestBlockElement,
    790                AncestorType::EditableElement},
    791               "[contenteditable]",
    792               // Should not return the body because of not editable.
    793               nullptr,
    794               nullptr},
    795           AncestorElementTest{
    796               u"<div><span contenteditable><br></span>",
    797               "[contenteditable]",
    798               {AncestorType::ClosestBlockElement,
    799                AncestorType::EditableElement,
    800                AncestorType::ReturnAncestorLimiterIfNoProperAncestor},
    801               "div",  // parent of the editing host
    802               // nullptr because of starting to scan from non-editable element.
    803               nullptr,
    804               // the editing host.
    805               "[contenteditable]",
    806           },
    807       }) {
    808    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
    809                              doc->NodePrincipal(), IgnoreErrors());
    810    const Element* const content = body->QuerySelector(
    811        nsDependentCString(testData.mContentSelector), IgnoreErrors());
    812    MOZ_RELEASE_ASSERT(content);
    813    {
    814      const Element* const expectedElement =
    815          testData.mExpectedSelectorForAncestor
    816              ? body->QuerySelector(
    817                    nsDependentCString(testData.mExpectedSelectorForAncestor),
    818                    IgnoreErrors())
    819              : nullptr;
    820      const Element* const result = HTMLEditUtils::GetAncestorElement(
    821          *body->QuerySelector(nsDependentCString(testData.mContentSelector),
    822                               IgnoreErrors()),
    823          testData.mAncestorTypes,
    824          BlockInlineCheck::UseComputedDisplayOutsideStyle,
    825          testData.mAncestorLimiterSelector
    826              ? body->QuerySelector(
    827                    nsDependentCString(testData.mAncestorLimiterSelector),
    828                    IgnoreErrors())
    829              : nullptr);
    830      EXPECT_EQ(result, expectedElement)
    831          << "GetAncestorElement: " << testData
    832          << "(Got: " << ToString(RefPtr{result}) << ")";
    833    }
    834    {
    835      const Element* const expectedElement =
    836          testData.mExpectedSelectorForInclusiveAncestor
    837              ? body->QuerySelector(
    838                    nsDependentCString(
    839                        testData.mExpectedSelectorForInclusiveAncestor),
    840                    IgnoreErrors())
    841              : nullptr;
    842      const Element* const result = HTMLEditUtils::GetInclusiveAncestorElement(
    843          *body->QuerySelector(nsDependentCString(testData.mContentSelector),
    844                               IgnoreErrors()),
    845          testData.mAncestorTypes,
    846          BlockInlineCheck::UseComputedDisplayOutsideStyle,
    847          testData.mAncestorLimiterSelector
    848              ? body->QuerySelector(
    849                    nsDependentCString(testData.mAncestorLimiterSelector),
    850                    IgnoreErrors())
    851              : nullptr);
    852      EXPECT_EQ(result, expectedElement)
    853          << "GetInclusiveAncestorElement: " << testData
    854          << "(Got: " << ToString(RefPtr{result}) << ")";
    855    }
    856  }
    857 }
    858 
    859 TEST(HTMLEditUtilsTest, GetAncestorElement_MostDistantInlineElementInBlock)
    860 {
    861  using AncestorType = HTMLEditUtils::AncestorType;
    862  const RefPtr<Document> doc = CreateHTMLDoc();
    863  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
    864  MOZ_RELEASE_ASSERT(body);
    865  for (const auto& testData : {
    866           AncestorElementTest{
    867               u"<div contenteditable><span><br></span></div>",
    868               "[contenteditable] > span",
    869               {AncestorType::MostDistantInlineElementInBlock,
    870                AncestorType::EditableElement,
    871                AncestorType::ReturnAncestorLimiterIfNoProperAncestor},
    872               "[contenteditable]",
    873               // Should return the editing host because of editable.
    874               "[contenteditable]",
    875               "[contenteditable] > span"},
    876           AncestorElementTest{
    877               u"<div contenteditable><b><i><div><u><s><br>"
    878               u"</s></u></div></i></b></div>",
    879               "[contenteditable] s",
    880               {AncestorType::MostDistantInlineElementInBlock},
    881               "[contenteditable]",
    882               // Should return the <u> because of the deepest <div>.
    883               "[contenteditable] u",
    884               "[contenteditable] u"},
    885           AncestorElementTest{u"<div contenteditable><b><i><div><u><s><br>"
    886                               u"</s></u></div></i></b></div>",
    887                               "[contenteditable] u",
    888                               {AncestorType::MostDistantInlineElementInBlock},
    889                               "[contenteditable]",
    890                               // Should return nullptr because of no ancestor
    891                               // in the deepest <div>.
    892                               nullptr,
    893                               "[contenteditable] u"},
    894           AncestorElementTest{
    895               u"<div contenteditable><b><i><div><u><s><br>"
    896               u"</s></u></div></i></b></div>",
    897               "[contenteditable] div",
    898               {AncestorType::MostDistantInlineElementInBlock},
    899               "[contenteditable]",
    900               "[contenteditable] b",
    901               // Should return nullptr because of starting from the <div>.
    902               nullptr},
    903           AncestorElementTest{
    904               u",<s><span contenteditable><b><i><br></i></b></span></s>",
    905               "[contenteditable] i",
    906               {AncestorType::MostDistantInlineElementInBlock},
    907               "[contenteditable]",
    908               // Should return the editing host because of inline.
    909               "[contenteditable]",
    910               "[contenteditable]"},
    911           AncestorElementTest{
    912               u"<s><span contenteditable><b><i><br></i></b></span></s>",
    913               "[contenteditable] i",
    914               {AncestorType::MostDistantInlineElementInBlock,
    915                AncestorType::ReturnAncestorLimiterIfNoProperAncestor},
    916               "[contenteditable]",
    917               // Should return the editing host because of inline.
    918               "[contenteditable]",
    919               "[contenteditable]"},
    920           AncestorElementTest{
    921               u"<s><span contenteditable><b><i><br></i></b></span></s>",
    922               "[contenteditable] i",
    923               {AncestorType::MostDistantInlineElementInBlock,
    924                AncestorType::EditableElement},
    925               nullptr,
    926               // Should return the editing host because of the editable root.
    927               "[contenteditable]",
    928               "[contenteditable]"},
    929       }) {
    930    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
    931                              doc->NodePrincipal(), IgnoreErrors());
    932    const Element* const content = body->QuerySelector(
    933        nsDependentCString(testData.mContentSelector), IgnoreErrors());
    934    MOZ_RELEASE_ASSERT(content);
    935    {
    936      const Element* const expectedElement =
    937          testData.mExpectedSelectorForAncestor
    938              ? body->QuerySelector(
    939                    nsDependentCString(testData.mExpectedSelectorForAncestor),
    940                    IgnoreErrors())
    941              : nullptr;
    942      const Element* const result = HTMLEditUtils::GetAncestorElement(
    943          *body->QuerySelector(nsDependentCString(testData.mContentSelector),
    944                               IgnoreErrors()),
    945          testData.mAncestorTypes,
    946          BlockInlineCheck::UseComputedDisplayOutsideStyle,
    947          testData.mAncestorLimiterSelector
    948              ? body->QuerySelector(
    949                    nsDependentCString(testData.mAncestorLimiterSelector),
    950                    IgnoreErrors())
    951              : nullptr);
    952      EXPECT_EQ(result, expectedElement)
    953          << "GetAncestorElement: " << testData
    954          << "(Got: " << ToString(RefPtr{result}) << ")";
    955    }
    956    {
    957      const Element* const expectedElement =
    958          testData.mExpectedSelectorForInclusiveAncestor
    959              ? body->QuerySelector(
    960                    nsDependentCString(
    961                        testData.mExpectedSelectorForInclusiveAncestor),
    962                    IgnoreErrors())
    963              : nullptr;
    964      const Element* const result = HTMLEditUtils::GetInclusiveAncestorElement(
    965          *body->QuerySelector(nsDependentCString(testData.mContentSelector),
    966                               IgnoreErrors()),
    967          testData.mAncestorTypes,
    968          BlockInlineCheck::UseComputedDisplayOutsideStyle,
    969          testData.mAncestorLimiterSelector
    970              ? body->QuerySelector(
    971                    nsDependentCString(testData.mAncestorLimiterSelector),
    972                    IgnoreErrors())
    973              : nullptr);
    974      EXPECT_EQ(result, expectedElement)
    975          << "GetInclusiveAncestorElement: " << testData
    976          << "(Got: " << ToString(RefPtr{result}) << ")";
    977    }
    978  }
    979 }
    980 
    981 TEST(HTMLEditUtilsTest, GetAncestorElement_ButtonElement)
    982 {
    983  using AncestorType = HTMLEditUtils::AncestorType;
    984  const RefPtr<Document> doc = CreateHTMLDoc();
    985  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
    986  MOZ_RELEASE_ASSERT(body);
    987  for (const auto& testData : {
    988           AncestorElementTest{
    989               u"<div contenteditable><button><span><br></span></button></div>",
    990               "[contenteditable] > button > span",
    991               {AncestorType::ClosestBlockElement,
    992                AncestorType::ClosestButtonElement},
    993               "[contenteditable]",
    994               "[contenteditable] > button",
    995               "[contenteditable] > button"},
    996           AncestorElementTest{
    997               u"<div contenteditable><button><br></button></div>",
    998               "[contenteditable] > button",
    999               {AncestorType::ClosestBlockElement,
   1000                AncestorType::ClosestButtonElement},
   1001               "[contenteditable]",
   1002               "[contenteditable]",
   1003               "[contenteditable] > button"},
   1004           AncestorElementTest{
   1005               u"<div contenteditable><b><button><i><br>"
   1006               u"</i></button></b></div>",
   1007               "[contenteditable] button > i",
   1008               {AncestorType::MostDistantInlineElementInBlock,
   1009                AncestorType::StopAtClosestButtonElement},
   1010               "[contenteditable]",
   1011               // because of no inline elements between <button> and <i>.
   1012               nullptr,
   1013               "[contenteditable] button > i"},
   1014           AncestorElementTest{u"<div contenteditable><b><button><i><u><br>"
   1015                               u"</u></i></button></b></div>",
   1016                               "[contenteditable] button > i > u",
   1017                               {AncestorType::MostDistantInlineElementInBlock,
   1018                                AncestorType::StopAtClosestButtonElement},
   1019                               "[contenteditable]",
   1020                               // because of <i> is a child of <button>.
   1021                               "i",
   1022                               "i"},
   1023           AncestorElementTest{u"<div contenteditable><b><button><i><br>"
   1024                               u"</i></button></b></div>",
   1025                               "[contenteditable] button > i",
   1026                               {AncestorType::MostDistantInlineElementInBlock,
   1027                                AncestorType::ClosestButtonElement},
   1028                               "[contenteditable]",
   1029                               "[contenteditable] button",
   1030                               "[contenteditable] button"},
   1031           AncestorElementTest{
   1032               u"<div contenteditable><b><button><br></button></b></div>",
   1033               "[contenteditable] > b > button",
   1034               {AncestorType::MostDistantInlineElementInBlock,
   1035                AncestorType::ClosestButtonElement},
   1036               "[contenteditable]",
   1037               "[contenteditable] > b",
   1038               "[contenteditable] > b > button"},
   1039       }) {
   1040    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
   1041                              doc->NodePrincipal(), IgnoreErrors());
   1042    const Element* const content = body->QuerySelector(
   1043        nsDependentCString(testData.mContentSelector), IgnoreErrors());
   1044    MOZ_RELEASE_ASSERT(content);
   1045    {
   1046      const Element* const expectedElement =
   1047          testData.mExpectedSelectorForAncestor
   1048              ? body->QuerySelector(
   1049                    nsDependentCString(testData.mExpectedSelectorForAncestor),
   1050                    IgnoreErrors())
   1051              : nullptr;
   1052      const Element* const result = HTMLEditUtils::GetAncestorElement(
   1053          *body->QuerySelector(nsDependentCString(testData.mContentSelector),
   1054                               IgnoreErrors()),
   1055          testData.mAncestorTypes,
   1056          BlockInlineCheck::UseComputedDisplayOutsideStyle,
   1057          testData.mAncestorLimiterSelector
   1058              ? body->QuerySelector(
   1059                    nsDependentCString(testData.mAncestorLimiterSelector),
   1060                    IgnoreErrors())
   1061              : nullptr);
   1062      EXPECT_EQ(result, expectedElement)
   1063          << "GetAncestorElement: " << testData
   1064          << "(Got: " << ToString(RefPtr{result}) << ")";
   1065    }
   1066    {
   1067      const Element* const expectedElement =
   1068          testData.mExpectedSelectorForInclusiveAncestor
   1069              ? body->QuerySelector(
   1070                    nsDependentCString(
   1071                        testData.mExpectedSelectorForInclusiveAncestor),
   1072                    IgnoreErrors())
   1073              : nullptr;
   1074      const Element* const result = HTMLEditUtils::GetInclusiveAncestorElement(
   1075          *body->QuerySelector(nsDependentCString(testData.mContentSelector),
   1076                               IgnoreErrors()),
   1077          testData.mAncestorTypes,
   1078          BlockInlineCheck::UseComputedDisplayOutsideStyle,
   1079          testData.mAncestorLimiterSelector
   1080              ? body->QuerySelector(
   1081                    nsDependentCString(testData.mAncestorLimiterSelector),
   1082                    IgnoreErrors())
   1083              : nullptr);
   1084      EXPECT_EQ(result, expectedElement)
   1085          << "GetInclusiveAncestorElement: " << testData
   1086          << "(Got: " << ToString(RefPtr{result}) << ")";
   1087    }
   1088  }
   1089 }
   1090 
   1091 TEST(HTMLEditUtilsTest, GetAncestorElement_IgnoreHRElement)
   1092 {
   1093  using AncestorType = HTMLEditUtils::AncestorType;
   1094  const RefPtr<Document> doc = CreateHTMLDoc();
   1095  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
   1096  MOZ_RELEASE_ASSERT(body);
   1097  for (const auto& testData : {
   1098           AncestorElementTest{u"<div contenteditable><hr></div>",
   1099                               "[contenteditable] > hr",
   1100                               {AncestorType::ClosestBlockElement,
   1101                                AncestorType::IgnoreHRElement},
   1102                               "[contenteditable]",
   1103                               "[contenteditable]",
   1104                               "[contenteditable]"},
   1105           AncestorElementTest{
   1106               u"<div contenteditable><button><hr></button></div>",
   1107               "[contenteditable] > button > hr",
   1108               {AncestorType::ClosestBlockElement,
   1109                AncestorType::ClosestButtonElement,
   1110                AncestorType::IgnoreHRElement},
   1111               "[contenteditable]",
   1112               "[contenteditable] > button",
   1113               "[contenteditable] > button"},
   1114           AncestorElementTest{u"<div contenteditable><span><hr></span></div>",
   1115                               "[contenteditable] > span > hr",
   1116                               {AncestorType::MostDistantInlineElementInBlock,
   1117                                AncestorType::IgnoreHRElement},
   1118                               "[contenteditable]",
   1119                               "[contenteditable] > span",
   1120                               "[contenteditable] > span"},
   1121       }) {
   1122    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
   1123                              doc->NodePrincipal(), IgnoreErrors());
   1124    const Element* const content = body->QuerySelector(
   1125        nsDependentCString(testData.mContentSelector), IgnoreErrors());
   1126    MOZ_RELEASE_ASSERT(content);
   1127    {
   1128      const Element* const expectedElement =
   1129          testData.mExpectedSelectorForAncestor
   1130              ? body->QuerySelector(
   1131                    nsDependentCString(testData.mExpectedSelectorForAncestor),
   1132                    IgnoreErrors())
   1133              : nullptr;
   1134      const Element* const result = HTMLEditUtils::GetAncestorElement(
   1135          *body->QuerySelector(nsDependentCString(testData.mContentSelector),
   1136                               IgnoreErrors()),
   1137          testData.mAncestorTypes,
   1138          BlockInlineCheck::UseComputedDisplayOutsideStyle,
   1139          testData.mAncestorLimiterSelector
   1140              ? body->QuerySelector(
   1141                    nsDependentCString(testData.mAncestorLimiterSelector),
   1142                    IgnoreErrors())
   1143              : nullptr);
   1144      EXPECT_EQ(result, expectedElement)
   1145          << "GetAncestorElement: " << testData
   1146          << "(Got: " << ToString(RefPtr{result}) << ")";
   1147    }
   1148    {
   1149      const Element* const expectedElement =
   1150          testData.mExpectedSelectorForInclusiveAncestor
   1151              ? body->QuerySelector(
   1152                    nsDependentCString(
   1153                        testData.mExpectedSelectorForInclusiveAncestor),
   1154                    IgnoreErrors())
   1155              : nullptr;
   1156      const Element* const result = HTMLEditUtils::GetInclusiveAncestorElement(
   1157          *body->QuerySelector(nsDependentCString(testData.mContentSelector),
   1158                               IgnoreErrors()),
   1159          testData.mAncestorTypes,
   1160          BlockInlineCheck::UseComputedDisplayOutsideStyle,
   1161          testData.mAncestorLimiterSelector
   1162              ? body->QuerySelector(
   1163                    nsDependentCString(testData.mAncestorLimiterSelector),
   1164                    IgnoreErrors())
   1165              : nullptr);
   1166      EXPECT_EQ(result, expectedElement)
   1167          << "GetInclusiveAncestorElement: " << testData
   1168          << "(Got: " << ToString(RefPtr{result}) << ")";
   1169    }
   1170  }
   1171 }
   1172 
   1173 TEST(HTMLEditUtilsTest, GetAncestorElement_ClosestContainerElement)
   1174 {
   1175  using AncestorType = HTMLEditUtils::AncestorType;
   1176  const RefPtr<Document> doc = CreateHTMLDoc();
   1177  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
   1178  MOZ_RELEASE_ASSERT(body);
   1179  for (const auto& testData : {
   1180           AncestorElementTest{
   1181               u"<div contenteditable><div><span><br></span></div></div>",
   1182               "[contenteditable] > div > span > br",
   1183               {AncestorType::ClosestContainerElement},
   1184               "[contenteditable]",
   1185               "[contenteditable] > div > span",
   1186               "[contenteditable] > div > span"},
   1187           AncestorElementTest{
   1188               u"<div contenteditable><div><span><br></span></div></div>",
   1189               "[contenteditable] > div > span",
   1190               {AncestorType::ClosestContainerElement},
   1191               "[contenteditable]",
   1192               "[contenteditable] > div",
   1193               "[contenteditable] > div > span"},
   1194           AncestorElementTest{
   1195               u"<div contenteditable><div><span><br></span></div></div>",
   1196               "[contenteditable] > div",
   1197               {AncestorType::ClosestContainerElement},
   1198               "[contenteditable]",
   1199               "[contenteditable]",
   1200               "[contenteditable] > div"},
   1201           AncestorElementTest{u"<br contenteditable>",
   1202                               "br[contenteditable]",
   1203                               {AncestorType::ClosestContainerElement},
   1204                               "br[contenteditable]",
   1205                               // Should return nullptr because of scanning
   1206                               // start from the parent of ancestor limiter.
   1207                               nullptr,
   1208                               // <br> is not a container.
   1209                               nullptr},
   1210           AncestorElementTest{
   1211               u"<br contenteditable>",
   1212               "br[contenteditable]",
   1213               {AncestorType::ClosestContainerElement,
   1214                AncestorType::ReturnAncestorLimiterIfNoProperAncestor},
   1215               "br[contenteditable]",
   1216               // Should return nullptr because of scanning start from the
   1217               // parent of ancestor limiter.
   1218               nullptr,
   1219               // <br> is the ancestor limiter.
   1220               "br[contenteditable]"},
   1221       }) {
   1222    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
   1223                              doc->NodePrincipal(), IgnoreErrors());
   1224    const Element* const content = body->QuerySelector(
   1225        nsDependentCString(testData.mContentSelector), IgnoreErrors());
   1226    MOZ_RELEASE_ASSERT(content);
   1227    {
   1228      const Element* const expectedElement =
   1229          testData.mExpectedSelectorForAncestor
   1230              ? body->QuerySelector(
   1231                    nsDependentCString(testData.mExpectedSelectorForAncestor),
   1232                    IgnoreErrors())
   1233              : nullptr;
   1234      const Element* const result = HTMLEditUtils::GetAncestorElement(
   1235          *body->QuerySelector(nsDependentCString(testData.mContentSelector),
   1236                               IgnoreErrors()),
   1237          testData.mAncestorTypes,
   1238          BlockInlineCheck::UseComputedDisplayOutsideStyle,
   1239          testData.mAncestorLimiterSelector
   1240              ? body->QuerySelector(
   1241                    nsDependentCString(testData.mAncestorLimiterSelector),
   1242                    IgnoreErrors())
   1243              : nullptr);
   1244      EXPECT_EQ(result, expectedElement)
   1245          << "GetAncestorElement: " << testData
   1246          << "(Got: " << ToString(RefPtr{result}) << ")";
   1247    }
   1248    {
   1249      const Element* const expectedElement =
   1250          testData.mExpectedSelectorForInclusiveAncestor
   1251              ? body->QuerySelector(
   1252                    nsDependentCString(
   1253                        testData.mExpectedSelectorForInclusiveAncestor),
   1254                    IgnoreErrors())
   1255              : nullptr;
   1256      const Element* const result = HTMLEditUtils::GetInclusiveAncestorElement(
   1257          *body->QuerySelector(nsDependentCString(testData.mContentSelector),
   1258                               IgnoreErrors()),
   1259          testData.mAncestorTypes,
   1260          BlockInlineCheck::UseComputedDisplayOutsideStyle,
   1261          testData.mAncestorLimiterSelector
   1262              ? body->QuerySelector(
   1263                    nsDependentCString(testData.mAncestorLimiterSelector),
   1264                    IgnoreErrors())
   1265              : nullptr);
   1266      EXPECT_EQ(result, expectedElement)
   1267          << "GetInclusiveAncestorElement: " << testData
   1268          << "(Got: " << ToString(RefPtr{result}) << ")";
   1269    }
   1270  }
   1271 }
   1272 
   1273 TEST(HTMLEditUtilsTest, IsContainerNode)
   1274 {
   1275  const RefPtr<Document> doc = CreateHTMLDoc();
   1276  for (const char16_t* tagName :
   1277       {u"html", u"body", u"div", u"span", u"select", u"option", u"form"}) {
   1278    const RefPtr<nsAtom> tag = NS_Atomize(tagName);
   1279    MOZ_RELEASE_ASSERT(tag);
   1280    const RefPtr<Element> element = doc->CreateHTMLElement(tag);
   1281    MOZ_RELEASE_ASSERT(element);
   1282    EXPECT_EQ(true, HTMLEditUtils::IsContainerNode(*element))
   1283        << "IsContainerNode(<" << NS_ConvertUTF16toUTF8(tagName).get() << ">)";
   1284  }
   1285  for (const char16_t* tagName : {u"img", u"input", u"br", u"wbr"}) {
   1286    const RefPtr<nsAtom> tag = NS_Atomize(tagName);
   1287    MOZ_RELEASE_ASSERT(tag);
   1288    const RefPtr<Element> element = doc->CreateHTMLElement(tag);
   1289    MOZ_RELEASE_ASSERT(element);
   1290    EXPECT_EQ(false, HTMLEditUtils::IsContainerNode(*element))
   1291        << "IsContainerNode(<" << NS_ConvertUTF16toUTF8(tagName).get() << ">)";
   1292  }
   1293  {
   1294    const RefPtr<nsTextNode> text = doc->CreateEmptyTextNode();
   1295    MOZ_RELEASE_ASSERT(text);
   1296    EXPECT_EQ(false, HTMLEditUtils::IsContainerNode(*text))
   1297        << "IsContainerNode(Text)";
   1298  }
   1299  {
   1300    const RefPtr<Comment> comment =
   1301        doc->CreateComment(nsDependentString(u"abc"));
   1302    MOZ_RELEASE_ASSERT(comment);
   1303    EXPECT_EQ(false, HTMLEditUtils::IsContainerNode(*comment))
   1304        << "IsContainerNode(Comment)";
   1305  }
   1306 }
   1307 
   1308 struct MOZ_STACK_CLASS IsEmptyNodeTest final {
   1309  const char16_t* mInnerHTML;
   1310  const char* mTargetSelector;
   1311  const HTMLEditUtils::EmptyCheckOptions mOptions;
   1312  const bool mExpectedValue;
   1313  const bool mExpectedSeenBR;
   1314 
   1315  friend std::ostream& operator<<(std::ostream& aStream,
   1316                                  const IsEmptyNodeTest& aTest) {
   1317    return aStream << "Check \"" << aTest.mTargetSelector
   1318                   << "\" with options=" << ToString(aTest.mOptions).c_str()
   1319                   << " in \"" << NS_ConvertUTF16toUTF8(aTest.mInnerHTML).get()
   1320                   << "\"";
   1321  }
   1322 };
   1323 
   1324 TEST(HTMLEditUtilsTest, IsEmptyNode)
   1325 {
   1326  using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
   1327  const RefPtr<Document> doc = CreateHTMLDoc();
   1328  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
   1329  MOZ_RELEASE_ASSERT(body);
   1330  for (const auto& testData : {
   1331           IsEmptyNodeTest{u"<div></div>", "div", {}, true, false},
   1332           IsEmptyNodeTest{u"<div></div>",
   1333                           "div",
   1334                           {EmptyCheckOption::TreatBlockAsVisible},
   1335                           true,
   1336                           false},
   1337           IsEmptyNodeTest{u"<div><br></div>", "div", {}, true, true},
   1338           IsEmptyNodeTest{u"<div><br></div>",
   1339                           "div",
   1340                           {EmptyCheckOption::TreatBlockAsVisible},
   1341                           true,
   1342                           true},
   1343           IsEmptyNodeTest{u"<div><br></div>",
   1344                           "div",
   1345                           {EmptyCheckOption::TreatSingleBRElementAsVisible},
   1346                           false,
   1347                           false},
   1348           IsEmptyNodeTest{u"<div><!--abc--></div>", "div", {}, true, false},
   1349           IsEmptyNodeTest{u"<div><!--abc--></div>",
   1350                           "div",
   1351                           {EmptyCheckOption::TreatCommentAsVisible},
   1352                           false,
   1353                           false},
   1354           IsEmptyNodeTest{u"<ul><li><br></li></ul>", "ul", {}, true, true},
   1355           IsEmptyNodeTest{u"<ul><li><br></li></ul>",
   1356                           "ul",
   1357                           {EmptyCheckOption::TreatListItemAsVisible},
   1358                           false,
   1359                           false},
   1360           IsEmptyNodeTest{
   1361               u"<table><td><br></td></table>", "table", {}, true, true},
   1362           IsEmptyNodeTest{u"<table><td><br></td></table>",
   1363                           "table",
   1364                           {EmptyCheckOption::TreatTableCellAsVisible},
   1365                           false,
   1366                           false},
   1367           IsEmptyNodeTest{u"<div>abc</div>", "div", {}, false, false},
   1368           IsEmptyNodeTest{
   1369               u"<div><span><br></span></div>", "div", {}, true, true},
   1370           IsEmptyNodeTest{
   1371               u"<div><div><br></div></div>", "div", {}, true, true},
   1372           IsEmptyNodeTest{u"<div><div><br></div></div>",
   1373                           "div",
   1374                           {EmptyCheckOption::TreatBlockAsVisible},
   1375                           false,
   1376                           false},
   1377           IsEmptyNodeTest{u"<dl><dt><br></dt></dl>", "dl", {}, true, true},
   1378           IsEmptyNodeTest{u"<dl><dt><br</dt></dl>",
   1379                           "dl",
   1380                           {EmptyCheckOption::TreatListItemAsVisible},
   1381                           false,
   1382                           false},
   1383           IsEmptyNodeTest{u"<dl><dd><br></dd></dl>", "dl", {}, true, true},
   1384           IsEmptyNodeTest{u"<dl><dd><br</dd></dl>",
   1385                           "dl",
   1386                           {EmptyCheckOption::TreatListItemAsVisible},
   1387                           false,
   1388                           false},
   1389           // form controls should be always not empty.
   1390           IsEmptyNodeTest{u"<input>", "input", {}, false, false},
   1391           IsEmptyNodeTest{u"<select></select>", "select", {}, false, false},
   1392           IsEmptyNodeTest{u"<button></button>", "button", {}, false, false},
   1393           IsEmptyNodeTest{
   1394               u"<textarea></textarea>", "textarea", {}, false, false},
   1395           IsEmptyNodeTest{u"<output></output>", "output", {}, false, false},
   1396           IsEmptyNodeTest{
   1397               u"<progress></progress>", "progress", {}, false, false},
   1398           IsEmptyNodeTest{u"<meter></meter>", "meter", {}, false, false},
   1399           // void elements should be always not empty.
   1400           IsEmptyNodeTest{u"<br>", "br", {}, false, false},
   1401           IsEmptyNodeTest{u"<wbr>", "wbr", {}, false, false},
   1402           IsEmptyNodeTest{u"<img>", "img", {}, false, false},
   1403           // white-spaces should not be treated as visible in block
   1404           IsEmptyNodeTest{u"<div> </div>", "div", {}, true, false},
   1405           IsEmptyNodeTest{u"<span> </span>", "span", {}, true, false},
   1406           IsEmptyNodeTest{u"a<span> </span>b", "span", {}, false, false},
   1407           // sublist's list items and table cells should be treated as visible.
   1408           IsEmptyNodeTest{u"<ul><li><ol><li><br></li></ol></li></ul>",
   1409                           "ul",
   1410                           {},
   1411                           false,
   1412                           false},
   1413           IsEmptyNodeTest{u"<ul><li><table><td><br></td></table></li></ul>",
   1414                           "ul",
   1415                           {},
   1416                           false,
   1417                           false},
   1418           IsEmptyNodeTest{
   1419               u"<table><td><table><td><br></td></table></td></table>",
   1420               "table",
   1421               {},
   1422               false,
   1423               false},
   1424           IsEmptyNodeTest{u"<table><td><ul><li><br></li></ul></td></table>",
   1425                           "table",
   1426                           {},
   1427                           false,
   1428                           false},
   1429       }) {
   1430    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
   1431                              doc->NodePrincipal(), IgnoreErrors());
   1432    const Element* const target = body->QuerySelector(
   1433        nsDependentCString(testData.mTargetSelector), IgnoreErrors());
   1434    MOZ_RELEASE_ASSERT(target);
   1435    bool seenBR = false;
   1436    const bool ret =
   1437        HTMLEditUtils::IsEmptyNode(*target, testData.mOptions, &seenBR);
   1438    EXPECT_EQ(ret, testData.mExpectedValue)
   1439        << "IsEmptyNode(result): " << testData;
   1440    EXPECT_EQ(seenBR, testData.mExpectedSeenBR)
   1441        << "IsEmptyNode(seenBR): " << testData;
   1442  }
   1443 }
   1444 
   1445 struct MOZ_STACK_CLASS GetLeafNodeTest final {
   1446  const char16_t* mInnerHTML;
   1447  const char* mContentSelector;
   1448  const HTMLEditUtils::LeafNodeTypes mTypes;
   1449  const char* mExpectedTargetSelector;
   1450  const char* mExpectedTargetContainerSelector = nullptr;
   1451  const uint32_t mExpectedTargetOffset = 0u;
   1452 
   1453  nsIContent* GetExpectedTarget(nsINode& aNode) const {
   1454    if (mExpectedTargetSelector) {
   1455      return aNode.QuerySelector(nsDependentCString(mExpectedTargetSelector),
   1456                                 IgnoreErrors());
   1457    }
   1458    if (!mExpectedTargetContainerSelector) {
   1459      return nullptr;
   1460    }
   1461    Element* const container = aNode.QuerySelector(
   1462        nsDependentCString(mExpectedTargetContainerSelector), IgnoreErrors());
   1463    MOZ_RELEASE_ASSERT(container);
   1464    MOZ_RELEASE_ASSERT(!mExpectedTargetOffset ||
   1465                       mExpectedTargetOffset < container->Length());
   1466    return container->GetChildAt_Deprecated(mExpectedTargetOffset);
   1467  }
   1468 
   1469  friend std::ostream& operator<<(std::ostream& aStream,
   1470                                  const GetLeafNodeTest& aTest) {
   1471    return aStream << "Scan from \"" << aTest.mContentSelector
   1472                   << "\" with options=" << ToString(aTest.mTypes).c_str()
   1473                   << " in \"" << NS_ConvertUTF16toUTF8(aTest.mInnerHTML).get()
   1474                   << "\"";
   1475  }
   1476 };
   1477 
   1478 TEST(HTMLEditUtilsTest, GetLastLeafContent)
   1479 {
   1480  using LeafNodeType = HTMLEditUtils::LeafNodeType;
   1481  const RefPtr<Document> doc = CreateHTMLDoc();
   1482  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
   1483  MOZ_RELEASE_ASSERT(body);
   1484  for (const auto& testData : {
   1485           GetLeafNodeTest{u"<div></div>", "div", {}, nullptr},
   1486           GetLeafNodeTest{u"<div><br></div>", "div", {}, "div > br"},
   1487           GetLeafNodeTest{u"<div>abc<br></div>", "div", {}, "div > br"},
   1488           GetLeafNodeTest{u"<div>abc</div>", "div", {}, nullptr, "div", 0u},
   1489           GetLeafNodeTest{
   1490               u"<div><div><br></div></div>", "div", {}, "div > div > br"},
   1491           GetLeafNodeTest{u"<div><div><br></div></div>",
   1492                           "div",
   1493                           {LeafNodeType::LeafNodeOrChildBlock},
   1494                           "div > div"},
   1495           GetLeafNodeTest{u"<div><div><br></div><div><br></div></div>",
   1496                           "div",
   1497                           {},
   1498                           "div > div + div > br"},
   1499           GetLeafNodeTest{u"<div><div><br></div><div><br></div></div>",
   1500                           "div",
   1501                           {LeafNodeType::LeafNodeOrChildBlock},
   1502                           "div > div + div"},
   1503           GetLeafNodeTest{u"<div><!--abc--></div>", "div", {}, nullptr},
   1504           GetLeafNodeTest{u"<div><!--abc--></div>",
   1505                           "div",
   1506                           {LeafNodeType::TreatCommentAsLeafNode},
   1507                           nullptr,
   1508                           "div",
   1509                           0u},
   1510           GetLeafNodeTest{u"<div><br><!--abc--></div>", "div", {}, "div > br"},
   1511           GetLeafNodeTest{u"<div><br><!--abc--></div>",
   1512                           "div",
   1513                           {LeafNodeType::TreatCommentAsLeafNode},
   1514                           nullptr,
   1515                           "div",
   1516                           1u},
   1517           GetLeafNodeTest{
   1518               u"<div><div><br></div><div><br></div><!--abc--></div>",
   1519               "div",
   1520               {},
   1521               "div > div + div > br"},
   1522           GetLeafNodeTest{
   1523               u"<div><div><br></div><div><br></div><!--abc--></div>",
   1524               "div",
   1525               {LeafNodeType::TreatCommentAsLeafNode},
   1526               nullptr,
   1527               "div",
   1528               2u},
   1529       }) {
   1530    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
   1531                              doc->NodePrincipal(), IgnoreErrors());
   1532    const Element* const target = body->QuerySelector(
   1533        nsDependentCString(testData.mContentSelector), IgnoreErrors());
   1534    MOZ_RELEASE_ASSERT(target);
   1535    const nsIContent* result = HTMLEditUtils::GetLastLeafContent(
   1536        *target, testData.mTypes,
   1537        BlockInlineCheck::UseComputedDisplayOutsideStyle);
   1538    EXPECT_EQ(result, testData.GetExpectedTarget(*body))
   1539        << "GetLastLeafContent: " << testData
   1540        << "(Got: " << ToString(RefPtr{result}) << ")";
   1541  }
   1542 }
   1543 
   1544 TEST(HTMLEditUtilsTest, GetFirstLeafContent)
   1545 {
   1546  using LeafNodeType = HTMLEditUtils::LeafNodeType;
   1547  const RefPtr<Document> doc = CreateHTMLDoc();
   1548  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
   1549  MOZ_RELEASE_ASSERT(body);
   1550  for (const auto& testData : {
   1551           GetLeafNodeTest{u"<div></div>", "div", {}, nullptr},
   1552           GetLeafNodeTest{u"<div><br></div>", "div", {}, "div > br"},
   1553           GetLeafNodeTest{
   1554               u"<div>abc<br></div>", "div", {}, nullptr, "div", 0u},
   1555           GetLeafNodeTest{u"<div>abc</div>", "div", {}, nullptr, "div", 0u},
   1556           GetLeafNodeTest{
   1557               u"<div><div><br></div></div>", "div", {}, "div > div > br"},
   1558           GetLeafNodeTest{u"<div><div><br></div></div>",
   1559                           "div",
   1560                           {LeafNodeType::LeafNodeOrChildBlock},
   1561                           "div > div"},
   1562           GetLeafNodeTest{u"<div><div><br></div><div><br></div></div>",
   1563                           "div",
   1564                           {},
   1565                           "div > div > br"},
   1566           GetLeafNodeTest{u"<div><div><br></div><div><br></div></div>",
   1567                           "div",
   1568                           {LeafNodeType::LeafNodeOrChildBlock},
   1569                           "div > div"},
   1570           GetLeafNodeTest{u"<div><!--abc--></div>", "div", {}, nullptr},
   1571           GetLeafNodeTest{u"<div><!--abc--></div>",
   1572                           "div",
   1573                           {LeafNodeType::TreatCommentAsLeafNode},
   1574                           nullptr,
   1575                           "div",
   1576                           0u},
   1577           GetLeafNodeTest{u"<div><!--abc--><br></div>", "div", {}, "div > br"},
   1578           GetLeafNodeTest{u"<div><!--abc--><br></div>",
   1579                           "div",
   1580                           {LeafNodeType::TreatCommentAsLeafNode},
   1581                           nullptr,
   1582                           "div",
   1583                           0u},
   1584           GetLeafNodeTest{
   1585               u"<div><!--abc--><div><br></div><div><br></div></div>",
   1586               "div",
   1587               {},
   1588               "div > div > br"},
   1589           GetLeafNodeTest{
   1590               u"<div><!--abc--><div><br></div><div><br></div></div>",
   1591               "div",
   1592               {LeafNodeType::TreatCommentAsLeafNode},
   1593               nullptr,
   1594               "div",
   1595               0u},
   1596       }) {
   1597    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
   1598                              doc->NodePrincipal(), IgnoreErrors());
   1599    const Element* const target = body->QuerySelector(
   1600        nsDependentCString(testData.mContentSelector), IgnoreErrors());
   1601    MOZ_RELEASE_ASSERT(target);
   1602    const nsIContent* result = HTMLEditUtils::GetFirstLeafContent(
   1603        *target, testData.mTypes,
   1604        BlockInlineCheck::UseComputedDisplayOutsideStyle);
   1605    EXPECT_EQ(result, testData.GetExpectedTarget(*body))
   1606        << "GetFirstLeafContent: " << testData
   1607        << "(Got: " << ToString(RefPtr{result}) << ")";
   1608  }
   1609 }
   1610 
   1611 TEST(HTMLEditUtilsTest, GetNextLeafContentOrNextBlockElement_Content)
   1612 {
   1613  using LeafNodeType = HTMLEditUtils::LeafNodeType;
   1614  const RefPtr<Document> doc = CreateHTMLDoc();
   1615  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
   1616  MOZ_RELEASE_ASSERT(body);
   1617  for (const auto& testData : {
   1618           GetLeafNodeTest{u"<div><br></div><p><br></p>", "div", {}, "p"},
   1619           GetLeafNodeTest{
   1620               u"<div><br></div><!--abc--><p><br></p>", "div", {}, "p"},
   1621           GetLeafNodeTest{u"<div><br></div><!--abc--><p><br></p>",
   1622                           "div",
   1623                           {LeafNodeType::TreatCommentAsLeafNode},
   1624                           nullptr,
   1625                           "body",
   1626                           1u},
   1627           GetLeafNodeTest{
   1628               u"<div><br></div><span><br></span>", "div", {}, "span > br"},
   1629           GetLeafNodeTest{u"<div><br></div><span><!--abc--><br></span>",
   1630                           "div",
   1631                           {},
   1632                           "span > br"},
   1633           GetLeafNodeTest{u"<div><br></div><span><!--abc--><br></span>",
   1634                           "div",
   1635                           {LeafNodeType::TreatCommentAsLeafNode},
   1636                           nullptr,
   1637                           "span",
   1638                           0u},
   1639       }) {
   1640    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
   1641                              doc->NodePrincipal(), IgnoreErrors());
   1642    const Element* const target = body->QuerySelector(
   1643        nsDependentCString(testData.mContentSelector), IgnoreErrors());
   1644    MOZ_RELEASE_ASSERT(target);
   1645    const nsIContent* result =
   1646        HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
   1647            *target, testData.mTypes,
   1648            BlockInlineCheck::UseComputedDisplayOutsideStyle);
   1649    EXPECT_EQ(result, testData.GetExpectedTarget(*body->GetParentNode()))
   1650        << "GetNextLeafContentOrNextBlockElement: " << testData
   1651        << "(Got: " << ToString(RefPtr{result}) << ")";
   1652  }
   1653 }
   1654 
   1655 // TODO: Test GetNextLeafContentOrNextBlockElement() which takes EditorDOMPoint
   1656 
   1657 TEST(HTMLEditUtilsTest, GetPreviousLeafContentOrPreviousBlockElement_Content)
   1658 {
   1659  using LeafNodeType = HTMLEditUtils::LeafNodeType;
   1660  const RefPtr<Document> doc = CreateHTMLDoc();
   1661  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
   1662  MOZ_RELEASE_ASSERT(body);
   1663  for (const auto& testData : {
   1664           GetLeafNodeTest{u"<p><br></p><div><br></div>", "div", {}, "p"},
   1665           GetLeafNodeTest{
   1666               u"<p><br></p><!--abc--><div><br></div>", "div", {}, "p"},
   1667           GetLeafNodeTest{u"<p><br></p><!--abc--><div><br></div>",
   1668                           "div",
   1669                           {LeafNodeType::TreatCommentAsLeafNode},
   1670                           nullptr,
   1671                           "body",
   1672                           1u},
   1673           GetLeafNodeTest{
   1674               u"<span><br></span><div><br></div>", "div", {}, "span > br"},
   1675           GetLeafNodeTest{u"<span><br><!--abc--></span><div><br></div>",
   1676                           "div",
   1677                           {},
   1678                           "span > br"},
   1679           GetLeafNodeTest{u"<span><br><!--abc--></span><div><br></div>",
   1680                           "div",
   1681                           {LeafNodeType::TreatCommentAsLeafNode},
   1682                           nullptr,
   1683                           "span",
   1684                           1u},
   1685       }) {
   1686    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
   1687                              doc->NodePrincipal(), IgnoreErrors());
   1688    const Element* const target = body->QuerySelector(
   1689        nsDependentCString(testData.mContentSelector), IgnoreErrors());
   1690    MOZ_RELEASE_ASSERT(target);
   1691    const nsIContent* result =
   1692        HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
   1693            *target, testData.mTypes,
   1694            BlockInlineCheck::UseComputedDisplayOutsideStyle);
   1695    EXPECT_EQ(result, testData.GetExpectedTarget(*body->GetParentNode()))
   1696        << "GetPreviousLeafContentOrPreviousBlockElement: " << testData
   1697        << "(Got: " << ToString(RefPtr{result}) << ")";
   1698  }
   1699 }
   1700 
   1701 // TODO: Test GetPreviousLeafContentOrPreviousBlockElement() which takes
   1702 // EditorDOMPoint
   1703 
   1704 struct MOZ_STACK_CLASS LineBreakBeforeBlockBoundaryTest final {
   1705  const char16_t* const mInnerHTML;
   1706  const char* const mContainer;
   1707  const Maybe<uint32_t>
   1708      mContainerIndex;  // Set if need to use CharacterData in mContainer.
   1709  const uint32_t mOffset;
   1710  const bool mExpectedResult;  // true if the method return a line break
   1711 
   1712  friend std::ostream& operator<<(
   1713      std::ostream& aStream, const LineBreakBeforeBlockBoundaryTest& aTest) {
   1714    aStream << "Scan from { container: " << aTest.mContainer;
   1715    if (aTest.mContainerIndex) {
   1716      aStream << "'s " << aTest.mContainerIndex.value() + 1 << "th child";
   1717    }
   1718    return aStream << ", offset: " << aTest.mOffset << " } in "
   1719                   << NS_ConvertUTF16toUTF8(aTest.mInnerHTML).get() << "\"";
   1720  }
   1721 };
   1722 
   1723 TEST(HTMLEditUtilsTest, GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem)
   1724 {
   1725  const RefPtr<Document> doc = CreateHTMLDoc();
   1726  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
   1727  MOZ_RELEASE_ASSERT(body);
   1728  for (const auto& testData : {
   1729           LineBreakBeforeBlockBoundaryTest{u"<div contenteditable>abc</div>",
   1730                                            "div", Some(0), 3, false},
   1731           LineBreakBeforeBlockBoundaryTest{u"<div contenteditable>abc</div>",
   1732                                            "div", Nothing{}, 1, false},
   1733           LineBreakBeforeBlockBoundaryTest{u"<div contenteditable><br></div>",
   1734                                            "div", Nothing{}, 0, false},
   1735           LineBreakBeforeBlockBoundaryTest{u"<div contenteditable><br></div>",
   1736                                            "div", Nothing{}, 1, true},
   1737           LineBreakBeforeBlockBoundaryTest{
   1738               u"<div contenteditable><br>  </div>", "div", Some(1), 2, true},
   1739           LineBreakBeforeBlockBoundaryTest{
   1740               u"<div contenteditable><br><!-- X --></div>", "div", Nothing{},
   1741               2,
   1742               // FIXME: Currently, this fails with a bug of WSRunScanner
   1743               // (actually, a bug of
   1744               // HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement)
   1745               false},
   1746           LineBreakBeforeBlockBoundaryTest{
   1747               u"<div contenteditable><br><br></div>", "div", Nothing{}, 1,
   1748               false},
   1749           LineBreakBeforeBlockBoundaryTest{
   1750               u"<div contenteditable><br><p>abc</p></div>", "div", Nothing{},
   1751               1, true},
   1752       }) {
   1753    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
   1754                              doc->NodePrincipal(), IgnoreErrors());
   1755    const Element* const containerElement = body->QuerySelector(
   1756        nsDependentCString(testData.mContainer), IgnoreErrors());
   1757    MOZ_ASSERT(containerElement);
   1758    const Element* const editingHost =
   1759        body->QuerySelector("[contenteditable]"_ns, IgnoreErrors());
   1760    MOZ_ASSERT(editingHost);
   1761    const nsIContent* const container =
   1762        testData.mContainerIndex
   1763            ? containerElement->GetChildAt_Deprecated(*testData.mContainerIndex)
   1764            : containerElement;
   1765    MOZ_RELEASE_ASSERT(container);
   1766    const Maybe<EditorRawLineBreak> result =
   1767        HTMLEditUtils::GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem<
   1768            EditorRawLineBreak>(EditorRawDOMPoint(container, testData.mOffset),
   1769                                *editingHost);
   1770    EXPECT_EQ(result.isSome(), testData.mExpectedResult)
   1771        << "GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem: " << testData;
   1772  }
   1773 }
   1774 
   1775 }  // namespace mozilla