Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/editor/libeditor/gtest/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 117 kB image not shown  

SSL TestHTMLEditUtils.cpp

  Interaktion und
PortierbarkeitC
 

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "gtest/gtest.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/OriginAttributes.h"
#include "mozilla/dom/CDATASection.h"
#include "mozilla/dom/Comment.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/Text.h"
#include "EditorDOMPoint.h"
#include "HTMLEditUtils.h"
#include "WSRunScanner.h"
#include "nsCOMPtr.h"
#include "nsGenericHTMLElement.h"
#include "nsIURI.h"
#include "nsNetUtil.h"
#include "nsString.h"
#include "nsTextNode.h"

namespace mozilla {

using namespace dom;

using AncestorType = HTMLEditUtils::AncestorType;
using AncestorTypes = HTMLEditUtils::AncestorTypes;
using EditablePointOption = HTMLEditUtils::EditablePointOption;
using EditablePointOptions = HTMLEditUtils::EditablePointOptions;

static already_AddRefed<Document> CreateHTMLDoc() {
  nsCOMPtr<nsIURI> uri;
  NS_NewURI(getter_AddRefs(uri), "data:text/html,");

  RefPtr<BasePrincipal> principal =
      BasePrincipal::CreateContentPrincipal(uri, OriginAttributes());
  MOZ_RELEASE_ASSERT(principal);

  nsCOMPtr<Document> doc;
  MOZ_ALWAYS_SUCCEEDS(NS_NewDOMDocument(getter_AddRefs(doc),
                                        u""_ns,   // aNamespaceURI
                                        u""_ns,   // aQualifiedName
                                        nullptr,  // aDoctype
                                        uri, uri, principal,
                                        LoadedAsData::No,  // aLoadedAsData
                                        nullptr,           // aEventObject
                                        DocumentFlavor::HTML));
  MOZ_RELEASE_ASSERT(doc);

  RefPtr<Element> html = doc->CreateHTMLElement(nsGkAtoms::html);
  html->SetInnerHTMLTrusted(u"<html><head></head><body></body></html>"_ns,
                            principal, IgnoreErrors());
  doc->AppendChild(*html, IgnoreErrors());

  return doc.forget();
}

struct MOZ_STACK_CLASS DeepestEditablePointTest final {
  const char16_t* const mInnerHTML;
  const charconst mContentSelector;
  const EditablePointOptions mOptions;
  const charconst mExpectedContainerSelector;
  const char16_t* const mExpectedTextData;
  const uint32_t mExpectedOffset;

  friend std::ostream& operator<<(std::ostream& aStream,
                                  const DeepestEditablePointTest& aTest) {
    return aStream << "Scan \"" << aTest.mContentSelector
                   << "\" with options=" << ToString(aTest.mOptions).c_str()
                   << " in \"" << NS_ConvertUTF16toUTF8(aTest.mInnerHTML).get()
                   << "\"";
  }
};

TEST(HTMLEditUtilsTest, GetDeepestEditableStartPointOf)
{
  const RefPtr<Document> doc = CreateHTMLDoc();
  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
  MOZ_RELEASE_ASSERT(body);
  for (const auto& testData : {
           DeepestEditablePointTest{
               u"<div contenteditable><div><br></div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] > div",
               nullptr,
               0  // Find <br>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><img></div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] > div",
               nullptr,
               0  // Find <img>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><hr></div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] > div",
               nullptr,
               0  // Find <hr>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div>abc</div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] > div",
               u"abc",
               0  // Find "a"
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><p>abc</p></div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] > div > p",
               u"abc",
               0  // Find "a"
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><span>abc</span></div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] > div > span",
               u"abc",
               0  // Find "a"
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div>   abc</div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] > div",
               u"   abc",
               3  // Find "a"
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><span>   abc</span></div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] > div > span",
               u"   abc",
               3  // Find "a"
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div>   abc</div></div>",
               "div[contenteditable] > div",
               {EditablePointOption::RecognizeInvisibleWhiteSpaces},
               "div[contenteditable] > div",
               u"   abc",
               0  // Find the first white-space
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><span>   abc</span></div></div>",
               "div[contenteditable] > div",
               {EditablePointOption::RecognizeInvisibleWhiteSpaces},
               "div[contenteditable] > div > span",
               u"   abc",
               0  // Find the first white-space
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><span></span>abc</div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] > div > span",
               nullptr,
               0  // Find the empty <span>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><!-- comment -->abc</div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] > div",
               u"abc",
               0  // Find "a"
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><!-- comment -->abc</div></div>",
               "div[contenteditable] > div",
               {EditablePointOption::StopAtComment},
               "div[contenteditable] > div",
               nullptr,
               0  // Find the comment
           },
           // inline-block may have leading white-spaces.  Therefore, even if
           // the start container is an inline element which follows visible
           // characters, it should return the first visible character in the
           // inline-block.
           DeepestEditablePointTest{
               u"<div contenteditable><div>abc<b><span style=\"display: "
               u"inline-block\">   def</span></b></div></div>",
               "div[contenteditable] > div > b",
               {},
               "div[contenteditable] > div > b > span",
               u"   def",
               3  // Find "d"
           },
           // There is a child <table>
           DeepestEditablePointTest{
               u"<div contenteditable><div><table><td><br></table></div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] td",
               nullptr,
               0  // Find <br>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><table><td><br></table></div></div>",
               "div[contenteditable] > div",
               {EditablePointOption::StopAtTableElement},
               "div[contenteditable] > div",
               nullptr,
               0  // Find <table>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><table><td><br></table></div></div>",
               "div[contenteditable] > div",
               {EditablePointOption::StopAtAnyTableElement},
               "div[contenteditable] > div",
               nullptr,
               0  // Find <table>
           },
           // In a table structure
           DeepestEditablePointTest{
               u"<div contenteditable><div><table><td><br></table></div></div>",
               "div[contenteditable] table",
               {},
               "div[contenteditable] td",
               nullptr,
               0  // Find <br>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><table><td><br></table></div></div>",
               "div[contenteditable] table",
               {EditablePointOption::StopAtTableElement},
               "div[contenteditable] td",
               nullptr,
               0  // Find <br>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><table><td><br></table></div></div>",
               "div[contenteditable] table",
               {EditablePointOption::StopAtAnyTableElement},
               "div[contenteditable] table",
               nullptr,
               0  // Find <td>
           },
           // <ul>
           DeepestEditablePointTest{
               u"<div contenteditable><div><ul><li><br></li></ul></div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] li",
               nullptr,
               0  // Find <br>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><ul><li><br></li></ul></div></div>",
               "div[contenteditable] > div",
               {EditablePointOption::StopAtListItemElement},
               "div[contenteditable] ul",
               nullptr,
               0  // Find <li>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><ul><li><br></li></ul></div></div>",
               "div[contenteditable] > div",
               {EditablePointOption::StopAtListElement},
               "div[contenteditable] > div",
               nullptr,
               0  // Find <ul>
           },
           // <ol>
           DeepestEditablePointTest{
               u"<div contenteditable><div><ol><li><br></li></ol></div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] li",
               nullptr,
               0  // Find <br>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><ol><li><br></li></ol></div></div>",
               "div[contenteditable] > div",
               {EditablePointOption::StopAtListItemElement},
               "div[contenteditable] ol",
               nullptr,
               0  // Find <li>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><ol><li><br></li></ol></div></div>",
               "div[contenteditable] > div",
               {EditablePointOption::StopAtListElement},
               "div[contenteditable] > div",
               nullptr,
               0  // Find <ol>
           },
           // <dl> and <dt>
           DeepestEditablePointTest{
               u"<div contenteditable><div><dl><dt><br></dt></dl></div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] dt",
               nullptr,
               0  // Find <br>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><dl><dt><br></dt></dl></div></div>",
               "div[contenteditable] > div",
               {EditablePointOption::StopAtListItemElement},
               "div[contenteditable] dl",
               nullptr,
               0  // Find <dt>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><dl><dt><br></dt></dl></div></div>",
               "div[contenteditable] > div",
               {EditablePointOption::StopAtListElement},
               "div[contenteditable] > div",
               nullptr,
               0  // Find <dl>
           },
           // <dl> and <dd>
           DeepestEditablePointTest{
               u"<div contenteditable><div><dl><dd><br></dd></dl></div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] dd",
               nullptr,
               0  // Find <br>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><dl><dd><br></dd></dl></div></div>",
               "div[contenteditable] > div",
               {EditablePointOption::StopAtListItemElement},
               "div[contenteditable] dl",
               nullptr,
               0  // Find <dd>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><dl><dd><br></dd></dl></div></div>",
               "div[contenteditable] > div",
               {EditablePointOption::StopAtListElement},
               "div[contenteditable] > div",
               nullptr,
               0  // Find <dl>
           },
       }) {
    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
                              doc->NodePrincipal(), IgnoreErrors());
    const Element* const content = body->QuerySelector(
        nsDependentCString(testData.mContentSelector), IgnoreErrors());
    MOZ_RELEASE_ASSERT(content);
    const nsIContent* const expectedContainer = [&]() -> const nsIContent* {
      const Element* const containerElement = body->QuerySelector(
          nsDependentCString(testData.mExpectedContainerSelector),
          IgnoreErrors());
      if (!testData.mExpectedTextData) {
        return containerElement;
      }
      for (const nsIContent* child = containerElement->GetFirstChild(); child;
           child = child->GetNextSibling()) {
        if (const auto* text = Text::FromNodeOrNull(child)) {
          nsAutoString data;
          text->GetData(data);
          if (data.Equals(testData.mExpectedTextData)) {
            return text;
          }
        }
      }
      return nullptr;
    }();
    MOZ_RELEASE_ASSERT(expectedContainer);
    const EditorRawDOMPoint result =
        HTMLEditUtils::GetDeepestEditableStartPointOf<EditorRawDOMPoint>(
            *content, testData.mOptions);
    EXPECT_EQ(result.GetContainer(), expectedContainer)
        << testData << "(Got: " << ToString(RefPtr{result.GetContainer()})
        << ")";
    EXPECT_EQ(result.Offset(), testData.mExpectedOffset) << testData;
  }
}

TEST(HTMLEditUtilsTest, GetDeepestEditableEndPointOf)
{
  const RefPtr<Document> doc = CreateHTMLDoc();
  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
  MOZ_RELEASE_ASSERT(body);
  for (const auto& testData : {
           DeepestEditablePointTest{
               u"<div contenteditable><div><br></div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] > div",
               nullptr,
               // XXX Should be 0 due to an invisible <br>?
               1  // Find <br>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><img></div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] > div",
               nullptr,
               1  // Find <img>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><hr></div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] > div",
               nullptr,
               1  // Find <hr>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div>abc</div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] > div",
               u"abc",
               3  // Find "c"
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><p>abc</p></div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] > div > p",
               u"abc",
               3  // Find "c"
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><span>abc</span></div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] > div > span",
               u"abc",
               3  // Find "c"
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div>abc   </div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] > div",
               u"abc   ",
               3  // Find "c"
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><span>abc   </span></div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] > div > span",
               u"abc   ",
               3  // Find "a"
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div>abc   </div></div>",
               "div[contenteditable] > div",
               {EditablePointOption::RecognizeInvisibleWhiteSpaces},
               "div[contenteditable] > div",
               u"abc   ",
               6  // Find the last white-space
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><span>abc   </span></div></div>",
               "div[contenteditable] > div",
               {EditablePointOption::RecognizeInvisibleWhiteSpaces},
               "div[contenteditable] > div > span",
               u"abc   ",
               6  // Find the last white-space
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div>abc<span></span></div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] > div > span",
               nullptr,
               0  // Find the empty <span>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div>abc<!-- comment --></div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] > div",
               u"abc",
               3  // Find "c"
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div>abc<!-- comment --></div></div>",
               "div[contenteditable] > div",
               {EditablePointOption::StopAtComment},
               "div[contenteditable] > div",
               nullptr,
               2  // Find the comment
           },
           // inline-block may have leading white-spaces.  Therefore, even if
           // the start container is an inline element which is followed by
           // visible characters, it should return the last visible character
           // in the inline-block.
           DeepestEditablePointTest{
               u"<div contenteditable><div><b><span style=\"display: "
               u"inline-block\">abc   </span></b>def</div></div>",
               "div[contenteditable] > div > b",
               {},
               "div[contenteditable] > div > b > span",
               u"abc   ",
               3  // Find "c"
           },
           // There is a child <table>
           DeepestEditablePointTest{
               u"<div contenteditable><div><table><td><br></table></div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] td",
               nullptr,
               1  // Find <br>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><table><td><br></table></div></div>",
               "div[contenteditable] > div",
               {EditablePointOption::StopAtTableElement},
               "div[contenteditable] > div",
               nullptr,
               1  // Find <table>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><table><td><br></table></div></div>",
               "div[contenteditable] > div",
               {EditablePointOption::StopAtAnyTableElement},
               "div[contenteditable] > div",
               nullptr,
               1  // Find <table>
           },
           // In a table structure
           DeepestEditablePointTest{
               u"<div contenteditable><div><table><td><br></table></div></div>",
               "div[contenteditable] table",
               {},
               "div[contenteditable] td",
               nullptr,
               1  // Find <br>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><table><td><br></table></div></div>",
               "div[contenteditable] table",
               {EditablePointOption::StopAtTableElement},
               "div[contenteditable] td",
               nullptr,
               1  // Find <br>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><table><td><br></table></div></div>",
               "div[contenteditable] table",
               {EditablePointOption::StopAtAnyTableElement},
               "div[contenteditable] table",
               nullptr,
               1  // Find <td>
           },
           // <ul>
           DeepestEditablePointTest{
               u"<div contenteditable><div><ul><li><br></li></ul></div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] li",
               nullptr,
               1  // Find <br>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><ul><li><br></li></ul></div></div>",
               "div[contenteditable] > div",
               {EditablePointOption::StopAtListItemElement},
               "div[contenteditable] ul",
               nullptr,
               1  // Find <li>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><ul><li><br></li></ul></div></div>",
               "div[contenteditable] > div",
               {EditablePointOption::StopAtListElement},
               "div[contenteditable] > div",
               nullptr,
               1  // Find <ul>
           },
           // <ol>
           DeepestEditablePointTest{
               u"<div contenteditable><div><ol><li><br></li></ol></div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] li",
               nullptr,
               1  // Find <br>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><ol><li><br></li></ol></div></div>",
               "div[contenteditable] > div",
               {EditablePointOption::StopAtListItemElement},
               "div[contenteditable] ol",
               nullptr,
               1  // Find <li>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><ol><li><br></li></ol></div></div>",
               "div[contenteditable] > div",
               {EditablePointOption::StopAtListElement},
               "div[contenteditable] > div",
               nullptr,
               1  // Find <ol>
           },
           // <dl> and <dt>
           DeepestEditablePointTest{
               u"<div contenteditable><div><dl><dt><br></dt></dl></div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] dt",
               nullptr,
               1  // Find <br>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><dl><dt><br></dt></dl></div></div>",
               "div[contenteditable] > div",
               {EditablePointOption::StopAtListItemElement},
               "div[contenteditable] dl",
               nullptr,
               1  // Find <dt>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><dl><dt><br></dt></dl></div></div>",
               "div[contenteditable] > div",
               {EditablePointOption::StopAtListElement},
               "div[contenteditable] > div",
               nullptr,
               1  // Find <dl>
           },
           // <dl> and <dd>
           DeepestEditablePointTest{
               u"<div contenteditable><div><dl><dd><br></dd></dl></div></div>",
               "div[contenteditable] > div",
               {},
               "div[contenteditable] dd",
               nullptr,
               1  // Find <br>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><dl><dd><br></dd></dl></div></div>",
               "div[contenteditable] > div",
               {EditablePointOption::StopAtListItemElement},
               "div[contenteditable] dl",
               nullptr,
               1  // Find <dd>
           },
           DeepestEditablePointTest{
               u"<div contenteditable><div><dl><dd><br></dd></dl></div></div>",
               "div[contenteditable] > div",
               {EditablePointOption::StopAtListElement},
               "div[contenteditable] > div",
               nullptr,
               1  // Find <dl>
           },
       }) {
    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
                              doc->NodePrincipal(), IgnoreErrors());
    const Element* const content = body->QuerySelector(
        nsDependentCString(testData.mContentSelector), IgnoreErrors());
    MOZ_RELEASE_ASSERT(content);
    const nsIContent* const expectedContainer = [&]() -> const nsIContent* {
      const Element* const containerElement = body->QuerySelector(
          nsDependentCString(testData.mExpectedContainerSelector),
          IgnoreErrors());
      if (!testData.mExpectedTextData) {
        return containerElement;
      }
      for (const nsIContent* child = containerElement->GetLastChild(); child;
           child = child->GetPreviousSibling()) {
        if (const auto* text = Text::FromNodeOrNull(child)) {
          nsAutoString data;
          text->GetData(data);
          if (data.Equals(testData.mExpectedTextData)) {
            return text;
          }
        }
      }
      return nullptr;
    }();
    MOZ_RELEASE_ASSERT(expectedContainer);
    const EditorRawDOMPoint result =
        HTMLEditUtils::GetDeepestEditableEndPointOf<EditorRawDOMPoint>(
            *content, testData.mOptions);
    EXPECT_EQ(result.GetContainer(), expectedContainer)
        << testData << "(Got: " << ToString(RefPtr{result.GetContainer()})
        << ")";
    EXPECT_EQ(result.Offset(), testData.mExpectedOffset) << testData;
  }
}

struct MOZ_STACK_CLASS AncestorElementTest final {
  const char16_t* const mInnerHTML;
  const charconst mContentSelector;
  const AncestorTypes mAncestorTypes;
  const charconst mAncestorLimiterSelector;
  const charconst mExpectedSelectorForAncestor;
  const charconst mExpectedSelectorForInclusiveAncestor;

  friend std::ostream& operator<<(std::ostream& aStream,
                                  const AncestorElementTest& aTest) {
    return aStream << "Scan from \"" << aTest.mContentSelector
                   << "\" with ancestor types="
                   << ToString(aTest.mAncestorTypes).c_str() << " in \""
                   << NS_ConvertUTF16toUTF8(aTest.mInnerHTML).get() << "\"";
  }
};

TEST(HTMLEditUtilsTest, GetAncestorElement_ClosestBlockElement)
{
  using AncestorType = HTMLEditUtils::AncestorType;
  const RefPtr<Document> doc = CreateHTMLDoc();
  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
  MOZ_RELEASE_ASSERT(body);
  for (const auto& testData : {
           AncestorElementTest{
               u"<div contenteditable><div><span><div><span><br>"
               u"</span></div></span></div></div>",
               "[contenteditable] > div > span > div > span",
               {AncestorType::ClosestBlockElement},
               "[contenteditable]",
               "[contenteditable] > div > span > div",
               "[contenteditable] > div > span > div"},
           AncestorElementTest{
               u"<div contenteditable><div><span><div><span><br>"
               u"</span></div></span></div></div>",
               "[contenteditable] > div > span > div",
               {AncestorType::ClosestBlockElement},
               "[contenteditable]",
               "[contenteditable] > div",
               "[contenteditable] > div > span > div"},
           AncestorElementTest{
               u"<div contenteditable><div><span><br></span></div></div>",
               "[contenteditable] > div",
               {AncestorType::ClosestBlockElement},
               "[contenteditable]",
               // Should return the editing host because of the closest ancestor
               // of the deepest <div>.
               "[contenteditable]",
               "[contenteditable] > div"},
           AncestorElementTest{
               u"<div contenteditable><span><br></span></div>",
               "[contenteditable] > span",
               {AncestorType::ClosestBlockElement},
               "[contenteditable]",
               // Should return the editing host because of a block.
               "[contenteditable]",
               "[contenteditable]"},
           AncestorElementTest{
               u"<div contenteditable><span><br></span></div>",
               "[contenteditable] > span",
               {AncestorType::ClosestBlockElement,
                AncestorType::ReturnAncestorLimiterIfNoProperAncestor},
               "[contenteditable]",
               "[contenteditable]",
               "[contenteditable]"},
           AncestorElementTest{u"<div contenteditable><span><br></span></div>",
                               "[contenteditable] > span",
                               {AncestorType::ClosestBlockElement,
                                AncestorType::EditableElement},
                               "[contenteditable]",
                               "[contenteditable]",
                               "[contenteditable]"},
           AncestorElementTest{
               u"<div contenteditable><span><br></span></div>",
               "[contenteditable] > span",
               {AncestorType::ClosestBlockElement,
                AncestorType::EditableElement,
                AncestorType::ReturnAncestorLimiterIfNoProperAncestor},
               "[contenteditable]",
               "[contenteditable]",
               "[contenteditable]"},
           AncestorElementTest{
               u"<span contenteditable><span><br></span></span>",
               "[contenteditable] > span",
               {AncestorType::ClosestBlockElement,
                AncestorType::EditableElement,
                AncestorType::ReturnAncestorLimiterIfNoProperAncestor},
               "[contenteditable]",
               // Should return the inline editing host because of the ancestor
               // limiter.
               "[contenteditable]",
               "[contenteditable]"},
           AncestorElementTest{
               u"<span contenteditable><span><br></span></span>",
               "[contenteditable] > span",
               {AncestorType::ClosestBlockElement,
                AncestorType::EditableElement},
               "[contenteditable]",
               // Should not return the body because of not editable.
               nullptr,
               nullptr},
           AncestorElementTest{
               u"<div><span contenteditable><br></span>",
               "[contenteditable]",
               {AncestorType::ClosestBlockElement,
                AncestorType::EditableElement,
                AncestorType::ReturnAncestorLimiterIfNoProperAncestor},
               "div",  // parent of the editing host
               // nullptr because of starting to scan from non-editable element.
               nullptr,
               // the editing host.
               "[contenteditable]",
           },
       }) {
    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
                              doc->NodePrincipal(), IgnoreErrors());
    const Element* const content = body->QuerySelector(
        nsDependentCString(testData.mContentSelector), IgnoreErrors());
    MOZ_RELEASE_ASSERT(content);
    {
      const Element* const expectedElement =
          testData.mExpectedSelectorForAncestor
              ? body->QuerySelector(
                    nsDependentCString(testData.mExpectedSelectorForAncestor),
                    IgnoreErrors())
              : nullptr;
      const Element* const result = HTMLEditUtils::GetAncestorElement(
          *body->QuerySelector(nsDependentCString(testData.mContentSelector),
                               IgnoreErrors()),
          testData.mAncestorTypes,
          BlockInlineCheck::UseComputedDisplayOutsideStyle,
          testData.mAncestorLimiterSelector
              ? body->QuerySelector(
                    nsDependentCString(testData.mAncestorLimiterSelector),
                    IgnoreErrors())
              : nullptr);
      EXPECT_EQ(result, expectedElement)
          << "GetAncestorElement: " << testData
          << "(Got: " << ToString(RefPtr{result}) << ")";
    }
    {
      const Element* const expectedElement =
          testData.mExpectedSelectorForInclusiveAncestor
              ? body->QuerySelector(
                    nsDependentCString(
                        testData.mExpectedSelectorForInclusiveAncestor),
                    IgnoreErrors())
              : nullptr;
      const Element* const result = HTMLEditUtils::GetInclusiveAncestorElement(
          *body->QuerySelector(nsDependentCString(testData.mContentSelector),
                               IgnoreErrors()),
          testData.mAncestorTypes,
          BlockInlineCheck::UseComputedDisplayOutsideStyle,
          testData.mAncestorLimiterSelector
              ? body->QuerySelector(
                    nsDependentCString(testData.mAncestorLimiterSelector),
                    IgnoreErrors())
              : nullptr);
      EXPECT_EQ(result, expectedElement)
          << "GetInclusiveAncestorElement: " << testData
          << "(Got: " << ToString(RefPtr{result}) << ")";
    }
  }
}

TEST(HTMLEditUtilsTest, GetAncestorElement_MostDistantInlineElementInBlock)
{
  using AncestorType = HTMLEditUtils::AncestorType;
  const RefPtr<Document> doc = CreateHTMLDoc();
  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
  MOZ_RELEASE_ASSERT(body);
  for (const auto& testData : {
           AncestorElementTest{
               u"<div contenteditable><span><br></span></div>",
               "[contenteditable] > span",
               {AncestorType::MostDistantInlineElementInBlock,
                AncestorType::EditableElement,
                AncestorType::ReturnAncestorLimiterIfNoProperAncestor},
               "[contenteditable]",
               // Should return the editing host because of editable.
               "[contenteditable]",
               "[contenteditable] > span"},
           AncestorElementTest{
               u"<div contenteditable><b><i><div><u><s><br>"
               u"</s></u></div></i></b></div>",
               "[contenteditable] s",
               {AncestorType::MostDistantInlineElementInBlock},
               "[contenteditable]",
               // Should return the <u> because of the deepest <div>.
               "[contenteditable] u",
               "[contenteditable] u"},
           AncestorElementTest{u"<div contenteditable><b><i><div><u><s><br>"
                               u"</s></u></div></i></b></div>",
                               "[contenteditable] u",
                               {AncestorType::MostDistantInlineElementInBlock},
                               "[contenteditable]",
                               // Should return nullptr because of no ancestor
                               // in the deepest <div>.
                               nullptr,
                               "[contenteditable] u"},
           AncestorElementTest{
               u"<div contenteditable><b><i><div><u><s><br>"
               u"</s></u></div></i></b></div>",
               "[contenteditable] div",
               {AncestorType::MostDistantInlineElementInBlock},
               "[contenteditable]",
               "[contenteditable] b",
               // Should return nullptr because of starting from the <div>.
               nullptr},
           AncestorElementTest{
               u",<s><span contenteditable><b><i><br></i></b></span></s>",
               "[contenteditable] i",
               {AncestorType::MostDistantInlineElementInBlock},
               "[contenteditable]",
               // Should return the editing host because of inline.
               "[contenteditable]",
               "[contenteditable]"},
           AncestorElementTest{
               u"<s><span contenteditable><b><i><br></i></b></span></s>",
               "[contenteditable] i",
               {AncestorType::MostDistantInlineElementInBlock,
                AncestorType::ReturnAncestorLimiterIfNoProperAncestor},
               "[contenteditable]",
               // Should return the editing host because of inline.
               "[contenteditable]",
               "[contenteditable]"},
           AncestorElementTest{
               u"<s><span contenteditable><b><i><br></i></b></span></s>",
               "[contenteditable] i",
               {AncestorType::MostDistantInlineElementInBlock,
                AncestorType::EditableElement},
               nullptr,
               // Should return the editing host because of the editable root.
               "[contenteditable]",
               "[contenteditable]"},
       }) {
    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
                              doc->NodePrincipal(), IgnoreErrors());
    const Element* const content = body->QuerySelector(
        nsDependentCString(testData.mContentSelector), IgnoreErrors());
    MOZ_RELEASE_ASSERT(content);
    {
      const Element* const expectedElement =
          testData.mExpectedSelectorForAncestor
              ? body->QuerySelector(
                    nsDependentCString(testData.mExpectedSelectorForAncestor),
                    IgnoreErrors())
              : nullptr;
      const Element* const result = HTMLEditUtils::GetAncestorElement(
          *body->QuerySelector(nsDependentCString(testData.mContentSelector),
                               IgnoreErrors()),
          testData.mAncestorTypes,
          BlockInlineCheck::UseComputedDisplayOutsideStyle,
          testData.mAncestorLimiterSelector
              ? body->QuerySelector(
                    nsDependentCString(testData.mAncestorLimiterSelector),
                    IgnoreErrors())
              : nullptr);
      EXPECT_EQ(result, expectedElement)
          << "GetAncestorElement: " << testData
          << "(Got: " << ToString(RefPtr{result}) << ")";
    }
    {
      const Element* const expectedElement =
          testData.mExpectedSelectorForInclusiveAncestor
              ? body->QuerySelector(
                    nsDependentCString(
                        testData.mExpectedSelectorForInclusiveAncestor),
                    IgnoreErrors())
              : nullptr;
      const Element* const result = HTMLEditUtils::GetInclusiveAncestorElement(
          *body->QuerySelector(nsDependentCString(testData.mContentSelector),
                               IgnoreErrors()),
          testData.mAncestorTypes,
          BlockInlineCheck::UseComputedDisplayOutsideStyle,
          testData.mAncestorLimiterSelector
              ? body->QuerySelector(
                    nsDependentCString(testData.mAncestorLimiterSelector),
                    IgnoreErrors())
              : nullptr);
      EXPECT_EQ(result, expectedElement)
          << "GetInclusiveAncestorElement: " << testData
          << "(Got: " << ToString(RefPtr{result}) << ")";
    }
  }
}

TEST(HTMLEditUtilsTest, GetAncestorElement_ButtonElement)
{
  using AncestorType = HTMLEditUtils::AncestorType;
  const RefPtr<Document> doc = CreateHTMLDoc();
  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
  MOZ_RELEASE_ASSERT(body);
  for (const auto& testData : {
           AncestorElementTest{
               u"<div contenteditable><button><span><br></span></button></div>",
               "[contenteditable] > button > span",
               {AncestorType::ClosestBlockElement,
                AncestorType::ClosestButtonElement},
               "[contenteditable]",
               "[contenteditable] > button",
               "[contenteditable] > button"},
           AncestorElementTest{
               u"<div contenteditable><button><br></button></div>",
               "[contenteditable] > button",
               {AncestorType::ClosestBlockElement,
                AncestorType::ClosestButtonElement},
               "[contenteditable]",
               "[contenteditable]",
               "[contenteditable] > button"},
           AncestorElementTest{
               u"<div contenteditable><b><button><i><br>"
               u"</i></button></b></div>",
               "[contenteditable] button > i",
               {AncestorType::MostDistantInlineElementInBlock,
                AncestorType::StopAtClosestButtonElement},
               "[contenteditable]",
               // because of no inline elements between <button> and <i>.
               nullptr,
               "[contenteditable] button > i"},
           AncestorElementTest{u"<div contenteditable><b><button><i><u><br>"
                               u"</u></i></button></b></div>",
                               "[contenteditable] button > i > u",
                               {AncestorType::MostDistantInlineElementInBlock,
                                AncestorType::StopAtClosestButtonElement},
                               "[contenteditable]",
                               // because of <i> is a child of <button>.
                               "i",
                               "i"},
           AncestorElementTest{u"<div contenteditable><b><button><i><br>"
                               u"</i></button></b></div>",
                               "[contenteditable] button > i",
                               {AncestorType::MostDistantInlineElementInBlock,
                                AncestorType::ClosestButtonElement},
                               "[contenteditable]",
                               "[contenteditable] button",
                               "[contenteditable] button"},
           AncestorElementTest{
               u"<div contenteditable><b><button><br></button></b></div>",
               "[contenteditable] > b > button",
               {AncestorType::MostDistantInlineElementInBlock,
                AncestorType::ClosestButtonElement},
               "[contenteditable]",
               "[contenteditable] > b",
               "[contenteditable] > b > button"},
       }) {
    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
                              doc->NodePrincipal(), IgnoreErrors());
    const Element* const content = body->QuerySelector(
        nsDependentCString(testData.mContentSelector), IgnoreErrors());
    MOZ_RELEASE_ASSERT(content);
    {
      const Element* const expectedElement =
          testData.mExpectedSelectorForAncestor
              ? body->QuerySelector(
                    nsDependentCString(testData.mExpectedSelectorForAncestor),
                    IgnoreErrors())
              : nullptr;
      const Element* const result = HTMLEditUtils::GetAncestorElement(
          *body->QuerySelector(nsDependentCString(testData.mContentSelector),
                               IgnoreErrors()),
          testData.mAncestorTypes,
          BlockInlineCheck::UseComputedDisplayOutsideStyle,
          testData.mAncestorLimiterSelector
              ? body->QuerySelector(
                    nsDependentCString(testData.mAncestorLimiterSelector),
                    IgnoreErrors())
              : nullptr);
      EXPECT_EQ(result, expectedElement)
          << "GetAncestorElement: " << testData
          << "(Got: " << ToString(RefPtr{result}) << ")";
    }
    {
      const Element* const expectedElement =
          testData.mExpectedSelectorForInclusiveAncestor
              ? body->QuerySelector(
                    nsDependentCString(
                        testData.mExpectedSelectorForInclusiveAncestor),
                    IgnoreErrors())
              : nullptr;
      const Element* const result = HTMLEditUtils::GetInclusiveAncestorElement(
          *body->QuerySelector(nsDependentCString(testData.mContentSelector),
                               IgnoreErrors()),
          testData.mAncestorTypes,
          BlockInlineCheck::UseComputedDisplayOutsideStyle,
          testData.mAncestorLimiterSelector
              ? body->QuerySelector(
                    nsDependentCString(testData.mAncestorLimiterSelector),
                    IgnoreErrors())
              : nullptr);
      EXPECT_EQ(result, expectedElement)
          << "GetInclusiveAncestorElement: " << testData
          << "(Got: " << ToString(RefPtr{result}) << ")";
    }
  }
}

TEST(HTMLEditUtilsTest, GetAncestorElement_IgnoreHRElement)
{
  using AncestorType = HTMLEditUtils::AncestorType;
  const RefPtr<Document> doc = CreateHTMLDoc();
  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
  MOZ_RELEASE_ASSERT(body);
  for (const auto& testData : {
           AncestorElementTest{u"<div contenteditable><hr></div>",
                               "[contenteditable] > hr",
                               {AncestorType::ClosestBlockElement,
                                AncestorType::IgnoreHRElement},
                               "[contenteditable]",
                               "[contenteditable]",
                               "[contenteditable]"},
           AncestorElementTest{
               u"<div contenteditable><button><hr></button></div>",
               "[contenteditable] > button > hr",
               {AncestorType::ClosestBlockElement,
                AncestorType::ClosestButtonElement,
                AncestorType::IgnoreHRElement},
               "[contenteditable]",
               "[contenteditable] > button",
               "[contenteditable] > button"},
           AncestorElementTest{u"<div contenteditable><span><hr></span></div>",
                               "[contenteditable] > span > hr",
                               {AncestorType::MostDistantInlineElementInBlock,
                                AncestorType::IgnoreHRElement},
                               "[contenteditable]",
                               "[contenteditable] > span",
                               "[contenteditable] > span"},
       }) {
    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
                              doc->NodePrincipal(), IgnoreErrors());
    const Element* const content = body->QuerySelector(
        nsDependentCString(testData.mContentSelector), IgnoreErrors());
    MOZ_RELEASE_ASSERT(content);
    {
      const Element* const expectedElement =
          testData.mExpectedSelectorForAncestor
              ? body->QuerySelector(
                    nsDependentCString(testData.mExpectedSelectorForAncestor),
                    IgnoreErrors())
              : nullptr;
      const Element* const result = HTMLEditUtils::GetAncestorElement(
          *body->QuerySelector(nsDependentCString(testData.mContentSelector),
                               IgnoreErrors()),
          testData.mAncestorTypes,
          BlockInlineCheck::UseComputedDisplayOutsideStyle,
          testData.mAncestorLimiterSelector
              ? body->QuerySelector(
                    nsDependentCString(testData.mAncestorLimiterSelector),
                    IgnoreErrors())
              : nullptr);
      EXPECT_EQ(result, expectedElement)
          << "GetAncestorElement: " << testData
          << "(Got: " << ToString(RefPtr{result}) << ")";
    }
    {
      const Element* const expectedElement =
          testData.mExpectedSelectorForInclusiveAncestor
              ? body->QuerySelector(
                    nsDependentCString(
                        testData.mExpectedSelectorForInclusiveAncestor),
                    IgnoreErrors())
              : nullptr;
      const Element* const result = HTMLEditUtils::GetInclusiveAncestorElement(
          *body->QuerySelector(nsDependentCString(testData.mContentSelector),
                               IgnoreErrors()),
          testData.mAncestorTypes,
          BlockInlineCheck::UseComputedDisplayOutsideStyle,
          testData.mAncestorLimiterSelector
              ? body->QuerySelector(
                    nsDependentCString(testData.mAncestorLimiterSelector),
                    IgnoreErrors())
              : nullptr);
      EXPECT_EQ(result, expectedElement)
          << "GetInclusiveAncestorElement: " << testData
          << "(Got: " << ToString(RefPtr{result}) << ")";
    }
  }
}

TEST(HTMLEditUtilsTest, GetAncestorElement_ClosestContainerElement)
{
  using AncestorType = HTMLEditUtils::AncestorType;
  const RefPtr<Document> doc = CreateHTMLDoc();
  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
  MOZ_RELEASE_ASSERT(body);
  for (const auto& testData : {
           AncestorElementTest{
               u"<div contenteditable><div><span><br></span></div></div>",
               "[contenteditable] > div > span > br",
               {AncestorType::ClosestContainerElement},
               "[contenteditable]",
               "[contenteditable] > div > span",
               "[contenteditable] > div > span"},
           AncestorElementTest{
               u"<div contenteditable><div><span><br></span></div></div>",
               "[contenteditable] > div > span",
               {AncestorType::ClosestContainerElement},
               "[contenteditable]",
               "[contenteditable] > div",
               "[contenteditable] > div > span"},
           AncestorElementTest{
               u"<div contenteditable><div><span><br></span></div></div>",
               "[contenteditable] > div",
               {AncestorType::ClosestContainerElement},
               "[contenteditable]",
               "[contenteditable]",
               "[contenteditable] > div"},
           AncestorElementTest{u"<br contenteditable>",
                               "br[contenteditable]",
                               {AncestorType::ClosestContainerElement},
                               "br[contenteditable]",
                               // Should return nullptr because of scanning
                               // start from the parent of ancestor limiter.
                               nullptr,
                               // <br> is not a container.
                               nullptr},
           AncestorElementTest{
               u"<br contenteditable>",
               "br[contenteditable]",
               {AncestorType::ClosestContainerElement,
                AncestorType::ReturnAncestorLimiterIfNoProperAncestor},
               "br[contenteditable]",
               // Should return nullptr because of scanning start from the
               // parent of ancestor limiter.
               nullptr,
               // <br> is the ancestor limiter.
               "br[contenteditable]"},
       }) {
    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
                              doc->NodePrincipal(), IgnoreErrors());
    const Element* const content = body->QuerySelector(
        nsDependentCString(testData.mContentSelector), IgnoreErrors());
    MOZ_RELEASE_ASSERT(content);
    {
      const Element* const expectedElement =
          testData.mExpectedSelectorForAncestor
              ? body->QuerySelector(
                    nsDependentCString(testData.mExpectedSelectorForAncestor),
                    IgnoreErrors())
              : nullptr;
      const Element* const result = HTMLEditUtils::GetAncestorElement(
          *body->QuerySelector(nsDependentCString(testData.mContentSelector),
                               IgnoreErrors()),
          testData.mAncestorTypes,
          BlockInlineCheck::UseComputedDisplayOutsideStyle,
          testData.mAncestorLimiterSelector
              ? body->QuerySelector(
                    nsDependentCString(testData.mAncestorLimiterSelector),
                    IgnoreErrors())
              : nullptr);
      EXPECT_EQ(result, expectedElement)
          << "GetAncestorElement: " << testData
          << "(Got: " << ToString(RefPtr{result}) << ")";
    }
    {
      const Element* const expectedElement =
          testData.mExpectedSelectorForInclusiveAncestor
              ? body->QuerySelector(
                    nsDependentCString(
                        testData.mExpectedSelectorForInclusiveAncestor),
                    IgnoreErrors())
              : nullptr;
      const Element* const result = HTMLEditUtils::GetInclusiveAncestorElement(
          *body->QuerySelector(nsDependentCString(testData.mContentSelector),
                               IgnoreErrors()),
          testData.mAncestorTypes,
          BlockInlineCheck::UseComputedDisplayOutsideStyle,
          testData.mAncestorLimiterSelector
              ? body->QuerySelector(
                    nsDependentCString(testData.mAncestorLimiterSelector),
                    IgnoreErrors())
              : nullptr);
      EXPECT_EQ(result, expectedElement)
          << "GetInclusiveAncestorElement: " << testData
          << "(Got: " << ToString(RefPtr{result}) << ")";
    }
  }
}

TEST(HTMLEditUtilsTest, IsContainerNode)
{
  const RefPtr<Document> doc = CreateHTMLDoc();
  for (const char16_t* tagName :
       {u"html", u"body", u"div", u"span", u"select", u"option", u"form"}) {
    const RefPtr<nsAtom> tag = NS_Atomize(tagName);
    MOZ_RELEASE_ASSERT(tag);
    const RefPtr<Element> element = doc->CreateHTMLElement(tag);
    MOZ_RELEASE_ASSERT(element);
    EXPECT_EQ(true, HTMLEditUtils::IsContainerNode(*element))
        << "IsContainerNode(<" << NS_ConvertUTF16toUTF8(tagName).get() << ">)";
  }
  for (const char16_t* tagName : {u"img", u"input", u"br", u"wbr"}) {
    const RefPtr<nsAtom> tag = NS_Atomize(tagName);
    MOZ_RELEASE_ASSERT(tag);
    const RefPtr<Element> element = doc->CreateHTMLElement(tag);
    MOZ_RELEASE_ASSERT(element);
    EXPECT_EQ(false, HTMLEditUtils::IsContainerNode(*element))
        << "IsContainerNode(<" << NS_ConvertUTF16toUTF8(tagName).get() << ">)";
  }
  {
    const RefPtr<nsTextNode> text = doc->CreateEmptyTextNode();
    MOZ_RELEASE_ASSERT(text);
    EXPECT_EQ(false, HTMLEditUtils::IsContainerNode(*text))
        << "IsContainerNode(Text)";
  }
  {
    const RefPtr<Comment> comment =
        doc->CreateComment(nsDependentString(u"abc"));
    MOZ_RELEASE_ASSERT(comment);
    EXPECT_EQ(false, HTMLEditUtils::IsContainerNode(*comment))
        << "IsContainerNode(Comment)";
  }
}

struct MOZ_STACK_CLASS IsEmptyNodeTest final {
  const char16_t* mInnerHTML;
  const char* mTargetSelector;
  const HTMLEditUtils::EmptyCheckOptions mOptions;
  const bool mExpectedValue;
  const bool mExpectedSeenBR;

  friend std::ostream& operator<<(std::ostream& aStream,
                                  const IsEmptyNodeTest& aTest) {
    return aStream << "Check \"" << aTest.mTargetSelector
                   << "\" with options=" << ToString(aTest.mOptions).c_str()
                   << " in \"" << NS_ConvertUTF16toUTF8(aTest.mInnerHTML).get()
                   << "\"";
  }
};

TEST(HTMLEditUtilsTest, IsEmptyNode)
{
  using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
  const RefPtr<Document> doc = CreateHTMLDoc();
  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
  MOZ_RELEASE_ASSERT(body);
  for (const auto& testData : {
           IsEmptyNodeTest{u"<div></div>""div", {}, true, false},
           IsEmptyNodeTest{u"<div></div>",
                           "div",
                           {EmptyCheckOption::TreatBlockAsVisible},
                           true,
                           false},
           IsEmptyNodeTest{u"<div><br></div>""div", {}, true, true},
           IsEmptyNodeTest{u"<div><br></div>",
                           "div",
                           {EmptyCheckOption::TreatBlockAsVisible},
                           true,
                           true},
           IsEmptyNodeTest{u"<div><br></div>",
                           "div",
                           {EmptyCheckOption::TreatSingleBRElementAsVisible},
                           false,
                           false},
           IsEmptyNodeTest{u"<div><!--abc--></div>""div", {}, true, false},
           IsEmptyNodeTest{u"<div><!--abc--></div>",
                           "div",
                           {EmptyCheckOption::TreatCommentAsVisible},
                           false,
                           false},
           IsEmptyNodeTest{u"<ul><li><br></li></ul>""ul", {}, true, true},
           IsEmptyNodeTest{u"<ul><li><br></li></ul>",
                           "ul",
                           {EmptyCheckOption::TreatListItemAsVisible},
                           false,
                           false},
           IsEmptyNodeTest{
               u"<table><td><br></td></table>""table", {}, true, true},
           IsEmptyNodeTest{u"<table><td><br></td></table>",
                           "table",
                           {EmptyCheckOption::TreatTableCellAsVisible},
                           false,
                           false},
           IsEmptyNodeTest{u"<div>abc</div>""div", {}, falsefalse},
           IsEmptyNodeTest{
               u"<div><span><br></span></div>""div", {}, true, true},
           IsEmptyNodeTest{
               u"<div><div><br></div></div>""div", {}, true, true},
           IsEmptyNodeTest{u"<div><div><br></div></div>",
                           "div",
                           {EmptyCheckOption::TreatBlockAsVisible},
                           false,
                           false},
           IsEmptyNodeTest{u"<dl><dt><br></dt></dl>""dl", {}, true, true},
           IsEmptyNodeTest{u"<dl><dt><br</dt></dl>",
                           "dl",
                           {EmptyCheckOption::TreatListItemAsVisible},
                           false,
                           false},
           IsEmptyNodeTest{u"<dl><dd><br></dd></dl>""dl", {}, true, true},
           IsEmptyNodeTest{u"<dl><dd><br</dd></dl>",
                           "dl",
                           {EmptyCheckOption::TreatListItemAsVisible},
                           false,
                           false},
           // form controls should be always not empty.
           IsEmptyNodeTest{u"<input>""input", {}, falsefalse},
           IsEmptyNodeTest{u"<select></select>""select", {}, falsefalse},
           IsEmptyNodeTest{u"<button></button>""button", {}, falsefalse},
           IsEmptyNodeTest{
               u"<textarea></textarea>""textarea", {}, falsefalse},
           IsEmptyNodeTest{u"<output></output>""output", {}, true, false},
           IsEmptyNodeTest{
               u"<progress></progress>""progress", {}, falsefalse},
           IsEmptyNodeTest{u"<meter></meter>""meter", {}, falsefalse},
           // void elements should be always not empty.
           IsEmptyNodeTest{u"<br>""br", {}, falsefalse},
           IsEmptyNodeTest{u"<wbr>""wbr", {}, falsefalse},
           IsEmptyNodeTest{u"<img>""img", {}, falsefalse},
           // white-spaces should not be treated as visible in block
           IsEmptyNodeTest{u"<div> </div>""div", {}, true, false},
           IsEmptyNodeTest{u"<span> </span>""span", {}, true, false},
           IsEmptyNodeTest{u"a<span> </span>b""span", {}, falsefalse},
           // sublist's list items and table cells should be treated as visible.
           IsEmptyNodeTest{u"<ul><li><ol><li><br></li></ol></li></ul>",
                           "ul",
                           {},
                           false,
                           false},
           IsEmptyNodeTest{u"<ul><li><table><td><br></td></table></li></ul>",
                           "ul",
                           {},
                           false,
                           false},
           IsEmptyNodeTest{
               u"<table><td><table><td><br></td></table></td></table>",
               "table",
               {},
               false,
               false},
           IsEmptyNodeTest{u"<table><td><ul><li><br></li></ul></td></table>",
                           "table",
                           {},
                           false,
                           false},
       }) {
    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
                              doc->NodePrincipal(), IgnoreErrors());
    const Element* const target = body->QuerySelector(
        nsDependentCString(testData.mTargetSelector), IgnoreErrors());
    MOZ_RELEASE_ASSERT(target);
    bool seenBR = false;
    const bool ret =
        HTMLEditUtils::IsEmptyNode(*target, testData.mOptions, &seenBR);
    EXPECT_EQ(ret, testData.mExpectedValue)
        << "IsEmptyNode(result): " << testData;
    EXPECT_EQ(seenBR, testData.mExpectedSeenBR)
        << "IsEmptyNode(seenBR): " << testData;
  }
}

struct MOZ_STACK_CLASS GetLeafNodeTest final {
  const char16_t* mInnerHTML;
  const char* mContentSelector;
  const HTMLEditUtils::LeafNodeOptions mOptions;
  const char* mExpectedTargetSelector;
  const char* mExpectedTargetContainerSelector = nullptr;
  const uint32_t mExpectedTargetOffset = 0u;

  nsIContent* GetExpectedTarget(nsINode& aNode) const {
    if (mExpectedTargetSelector) {
      return aNode.QuerySelector(nsDependentCString(mExpectedTargetSelector),
                                 IgnoreErrors());
    }
    if (!mExpectedTargetContainerSelector) {
      return nullptr;
    }
    Element* const container = aNode.QuerySelector(
        nsDependentCString(mExpectedTargetContainerSelector), IgnoreErrors());
    MOZ_RELEASE_ASSERT(container);
    MOZ_RELEASE_ASSERT(!mExpectedTargetOffset ||
                       mExpectedTargetOffset < container->Length());
    return container->GetChildAt_Deprecated(mExpectedTargetOffset);
  }

  friend std::ostream& operator<<(std::ostream& aStream,
                                  const GetLeafNodeTest& aTest) {
    return aStream << "Scan from \"" << aTest.mContentSelector
                   << "\" with options=" << ToString(aTest.mOptions).c_str()
                   << " in \"" << NS_ConvertUTF16toUTF8(aTest.mInnerHTML).get()
                   << "\"";
  }
};

TEST(HTMLEditUtilsTest, GetLastLeafContent)
{
  using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
  const RefPtr<Document> doc = CreateHTMLDoc();
  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
  MOZ_RELEASE_ASSERT(body);
  for (const auto& testData : {
           GetLeafNodeTest{u"<div></div>""div", {}, nullptr},
           GetLeafNodeTest{u"<div><br></div>""div", {}, "div > br"},
           GetLeafNodeTest{u"<div>abc<br></div>""div", {}, "div > br"},
           GetLeafNodeTest{u"<div>abc</div>""div", {}, nullptr, "div"0u},

           GetLeafNodeTest{
               u"<div><div><br></div></div>""div", {}, "div > div > br"},
           GetLeafNodeTest{u"<div><div><br></div></div>",
                           "div",
                           {LeafNodeOption::TreatChildBlockAsLeafNode},
                           "div > div"},
           GetLeafNodeTest{u"<div><div><br></div><div><br></div></div>",
                           "div",
                           {},
                           "div > div + div > br"},
           GetLeafNodeTest{u"<div><div><br></div><div><br></div></div>",
                           "div",
                           {LeafNodeOption::TreatChildBlockAsLeafNode},
                           "div > div + div"},

           GetLeafNodeTest{u"<div><!--abc--></div>""div", {}, nullptr},
           GetLeafNodeTest{u"<div><!--abc--></div>",
                           "div",
                           {LeafNodeOption::TreatCommentAsLeafNode},
                           nullptr,
                           "div",
                           0u},
           GetLeafNodeTest{u"<div><br><!--abc--></div>""div", {}, "div > br"},
           GetLeafNodeTest{u"<div><br><!--abc--></div>",
                           "div",
                           {LeafNodeOption::TreatCommentAsLeafNode},
                           nullptr,
                           "div",
                           1u},
           GetLeafNodeTest{
               u"<div><div><br></div><div><br></div><!--abc--></div>",
               "div",
               {},
               "div > div + div > br"},
           GetLeafNodeTest{
               u"<div><div><br></div><div><br></div><!--abc--></div>",
               "div",
               {LeafNodeOption::TreatCommentAsLeafNode},
               nullptr,
               "div",
               2u},

           GetLeafNodeTest{
               u"<div><span></span></div>""div", {}, "div > span"},
           GetLeafNodeTest{u"<div><span></span></div>",
                           "div",
                           {LeafNodeOption::IgnoreAnyEmptyInlineContainers},
                           nullptr},
           GetLeafNodeTest{
               u"<div><br><span></span></div>""div", {}, "div > span"},
           GetLeafNodeTest{u"<div><br><span></span></div>",
                           "div",
                           {LeafNodeOption::IgnoreAnyEmptyInlineContainers},
                           "div > br"},
           GetLeafNodeTest{
               u"<div><div><br></div><div><br></div><span></span></div>",
               "div",
               {},
               "div > span"},
           GetLeafNodeTest{
               u"<div><div><br></div><div><br></div><span></span></div>",
               "div",
               {LeafNodeOption::IgnoreAnyEmptyInlineContainers},
               "div > div + div > br"},

           GetLeafNodeTest{u"<div><span><!-- abc --></span></div>",
                           "div",
                           {},
                           "div > span"},
           GetLeafNodeTest{u"<div><span><!-- abc --></span></div>",
                           "div",
                           {LeafNodeOption::TreatCommentAsLeafNode},
                           nullptr,
                           "div > span",
                           0u},
           GetLeafNodeTest{u"<div><span><!-- abc --></span></div>",
                           "div",
                           {LeafNodeOption::IgnoreAnyEmptyInlineContainers},
                           nullptr},
           GetLeafNodeTest{u"<div><span><!-- abc --></span></div>",
                           "div",
                           {LeafNodeOption::IgnoreAnyEmptyInlineContainers,
                            LeafNodeOption::TreatCommentAsLeafNode},
                           nullptr,
                           "div > span",
                           0u},

           GetLeafNodeTest{u"<div><br><wbr></div>""div", {}, "div > wbr"},
           GetLeafNodeTest{u"<div><br><wbr></div>",
                           "div",
                           {LeafNodeOption::IgnoreAnyEmptyInlineContainers},
                           "div > wbr"},
           GetLeafNodeTest{u"<div><br><wbr></div>",
                           "div",
                           {LeafNodeOption::IgnoreInvisibleInlineVoidElements},
                           "div > br"},

           GetLeafNodeTest{
               u"<div><span>abc</span> </div>""div", {}, nullptr, "div"1u},
           GetLeafNodeTest{u"<div><span>abc</span> </div>",
                           "div",
                           {LeafNodeOption::IgnoreInvisibleText},
                           nullptr,
                           "div > span",
                           0u},
       }) {
    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
                              doc->NodePrincipal(), IgnoreErrors());
    const Element* const target = body->QuerySelector(
        nsDependentCString(testData.mContentSelector), IgnoreErrors());
    MOZ_RELEASE_ASSERT(target);
    const nsIContent* result = HTMLEditUtils::GetLastLeafContent(
        *target, testData.mOptions,
        BlockInlineCheck::UseComputedDisplayOutsideStyle);
    EXPECT_EQ(result, testData.GetExpectedTarget(*body))
        << "GetLastLeafContent: " << testData
        << "(Got: " << ToString(RefPtr{result}) << ")";
  }
}

TEST(HTMLEditUtilsTest, GetFirstLeafContent)
{
  using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
  const RefPtr<Document> doc = CreateHTMLDoc();
  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
  MOZ_RELEASE_ASSERT(body);
  for (const auto& testData : {
           GetLeafNodeTest{u"<div></div>""div", {}, nullptr},
           GetLeafNodeTest{u"<div><br></div>""div", {}, "div > br"},
           GetLeafNodeTest{
               u"<div>abc<br></div>""div", {}, nullptr, "div"0u},
           GetLeafNodeTest{u"<div>abc</div>""div", {}, nullptr, "div"0u},
           GetLeafNodeTest{
               u"<div><div><br></div></div>""div", {}, "div > div > br"},

           GetLeafNodeTest{u"<div><div><br></div></div>",
                           "div",
                           {LeafNodeOption::TreatChildBlockAsLeafNode},
                           "div > div"},
           GetLeafNodeTest{u"<div><div><br></div><div><br></div></div>",
                           "div",
                           {},
                           "div > div > br"},
           GetLeafNodeTest{u"<div><div><br></div><div><br></div></div>",
                           "div",
                           {LeafNodeOption::TreatChildBlockAsLeafNode},
                           "div > div"},

           GetLeafNodeTest{u"<div><!--abc--></div>""div", {}, nullptr},
           GetLeafNodeTest{u"<div><!--abc--></div>",
                           "div",
                           {LeafNodeOption::TreatCommentAsLeafNode},
                           nullptr,
                           "div",
                           0u},
           GetLeafNodeTest{u"<div><!--abc--><br></div>""div", {}, "div > br"},
           GetLeafNodeTest{u"<div><!--abc--><br></div>",
                           "div",
                           {LeafNodeOption::TreatCommentAsLeafNode},
                           nullptr,
                           "div",
                           0u},
           GetLeafNodeTest{
               u"<div><!--abc--><div><br></div><div><br></div></div>",
               "div",
               {},
               "div > div > br"},
           GetLeafNodeTest{
               u"<div><!--abc--><div><br></div><div><br></div></div>",
               "div",
               {LeafNodeOption::TreatCommentAsLeafNode},
               nullptr,
               "div",
               0u},

           GetLeafNodeTest{
               u"<div><span></span></div>""div", {}, "div > span"},
           GetLeafNodeTest{u"<div><span></span></div>",
                           "div",
                           {LeafNodeOption::IgnoreAnyEmptyInlineContainers},
                           nullptr},
           GetLeafNodeTest{u"<div><span></span><br></div>",
                           "div",
                           {LeafNodeOption::IgnoreAnyEmptyInlineContainers},
                           "div > br"},
           GetLeafNodeTest{
               u"<div><span></span><div><br></div><div><br></div></div>",
               "div",
               {},
               "div > span"},
           GetLeafNodeTest{
               u"<div><span></span><div><br></div><div><br></div></div>",
               "div",
               {LeafNodeOption::IgnoreAnyEmptyInlineContainers},
               "div > div > br"},

           GetLeafNodeTest{u"<div><span><!-- abc --></span></div>",
                           "div",
                           {},
                           "div > span"},
           GetLeafNodeTest{u"<div><span><!-- abc --></span></div>",
                           "div",
                           {LeafNodeOption::TreatCommentAsLeafNode},
                           nullptr,
                           "div > span",
                           0u},
           GetLeafNodeTest{u"<div><span><!-- abc --></span></div>",
                           "div",
                           {LeafNodeOption::IgnoreAnyEmptyInlineContainers},
                           nullptr},
           GetLeafNodeTest{u"<div><span><!-- abc --></span></div>",
                           "div",
                           {LeafNodeOption::IgnoreAnyEmptyInlineContainers,
                            LeafNodeOption::TreatCommentAsLeafNode},
                           nullptr,
                           "div > span",
                           0u},

           GetLeafNodeTest{u"<div><wbr><br></div>""div", {}, "div > wbr"},
           GetLeafNodeTest{u"<div><wbr><br></div>",
                           "div",
                           {LeafNodeOption::IgnoreAnyEmptyInlineContainers},
                           "div > wbr"},
           GetLeafNodeTest{u"<div><wbr><br></div>",
                           "div",
                           {LeafNodeOption::IgnoreInvisibleInlineVoidElements},
                           "div > br"},

           GetLeafNodeTest{
               u"<div> <span>abc</span></div>""div", {}, nullptr, "div"0u},
           GetLeafNodeTest{u"<div> <span>abc</span></div>",
                           "div",
                           {LeafNodeOption::IgnoreInvisibleText},
                           nullptr,
                           "div > span",
                           0u},
       }) {
    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
                              doc->NodePrincipal(), IgnoreErrors());
    const Element* const target = body->QuerySelector(
        nsDependentCString(testData.mContentSelector), IgnoreErrors());
    MOZ_RELEASE_ASSERT(target);
    const nsIContent* result = HTMLEditUtils::GetFirstLeafContent(
        *target, testData.mOptions,
        BlockInlineCheck::UseComputedDisplayOutsideStyle);
    EXPECT_EQ(result, testData.GetExpectedTarget(*body))
        << "GetFirstLeafContent: " << testData
        << "(Got: " << ToString(RefPtr{result}) << ")";
  }
}

TEST(HTMLEditUtilsTest, GetNextLeafContentOrNextBlockElement_Content)
{
  using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
  const RefPtr<Document> doc = CreateHTMLDoc();
  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
  MOZ_RELEASE_ASSERT(body);
  for (const auto& testData : {
           GetLeafNodeTest{u"<div><br></div><p><br></p>""div", {}, "p"},
           GetLeafNodeTest{
               u"<div><br></div><!--abc--><p><br></p>""div", {}, "p"},
           GetLeafNodeTest{u"<div><br></div><!--abc--><p><br></p>",
                           "div",
                           {LeafNodeOption::TreatCommentAsLeafNode},
                           nullptr,
                           "body",
                           1u},
           GetLeafNodeTest{
               u"<div><br></div><span><br></span>""div", {}, "span > br"},
           GetLeafNodeTest{u"<div><br></div><span><!--abc--><br></span>",
                           "div",
                           {},
                           "span > br"},
           GetLeafNodeTest{u"<div><br></div><span><!--abc--><br></span>",
                           "div",
                           {LeafNodeOption::TreatCommentAsLeafNode},
                           nullptr,
                           "span",
                           0u},
       }) {
    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
                              doc->NodePrincipal(), IgnoreErrors());
    const Element* const target = body->QuerySelector(
        nsDependentCString(testData.mContentSelector), IgnoreErrors());
    MOZ_RELEASE_ASSERT(target);
    const nsIContent* result =
        HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
            *target, testData.mOptions,
            BlockInlineCheck::UseComputedDisplayOutsideStyle);
    EXPECT_EQ(result, testData.GetExpectedTarget(*body->GetParentNode()))
        << "GetNextLeafContentOrNextBlockElement: " << testData
        << "(Got: " << ToString(RefPtr{result}) << ")";
  }
}

// TODO: Test GetNextLeafContentOrNextBlockElement() which takes EditorDOMPoint

TEST(HTMLEditUtilsTest, GetPreviousLeafContentOrPreviousBlockElement_Content)
{
  using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
  const RefPtr<Document> doc = CreateHTMLDoc();
  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
  MOZ_RELEASE_ASSERT(body);
  for (const auto& testData : {
           GetLeafNodeTest{u"<p><br></p><div><br></div>""div", {}, "p"},
           GetLeafNodeTest{
               u"<p><br></p><!--abc--><div><br></div>""div", {}, "p"},
           GetLeafNodeTest{u"<p><br></p><!--abc--><div><br></div>",
                           "div",
                           {LeafNodeOption::TreatCommentAsLeafNode},
                           nullptr,
                           "body",
                           1u},
           GetLeafNodeTest{
               u"<span><br></span><div><br></div>""div", {}, "span > br"},
           GetLeafNodeTest{u"<span><br><!--abc--></span><div><br></div>",
                           "div",
                           {},
                           "span > br"},
           GetLeafNodeTest{u"<span><br><!--abc--></span><div><br></div>",
                           "div",
                           {LeafNodeOption::TreatCommentAsLeafNode},
                           nullptr,
                           "span",
                           1u},
       }) {
    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
                              doc->NodePrincipal(), IgnoreErrors());
    const Element* const target = body->QuerySelector(
        nsDependentCString(testData.mContentSelector), IgnoreErrors());
    MOZ_RELEASE_ASSERT(target);
    const nsIContent* result =
        HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
            *target, testData.mOptions,
            BlockInlineCheck::UseComputedDisplayOutsideStyle);
    EXPECT_EQ(result, testData.GetExpectedTarget(*body->GetParentNode()))
        << "GetPreviousLeafContentOrPreviousBlockElement: " << testData
        << "(Got: " << ToString(RefPtr{result}) << ")";
  }
}

// TODO: Test GetPreviousLeafContentOrPreviousBlockElement() which takes
// EditorDOMPoint

TEST(HTMLEditUtilsTest, GetNextLeafContent_Content)
{
  using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
  const RefPtr<Document> doc = CreateHTMLDoc();
  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
  MOZ_RELEASE_ASSERT(body);
  for (const auto& testData : {
           GetLeafNodeTest{u"<div><br></div><p><br></p>""div", {}, "p > br"},
           GetLeafNodeTest{
               u"<div><br></div><!--abc--><p><br></p>""div", {}, "p > br"},
           GetLeafNodeTest{u"<div><br></div><!--abc--><p><br></p>",
                           "div",
                           {LeafNodeOption::TreatCommentAsLeafNode},
                           nullptr,
                           "body",
                           1u},
           GetLeafNodeTest{
               u"<div><br></div><span><br></span>""div", {}, "span > br"},
           GetLeafNodeTest{u"<div><br></div><span><!--abc--><br></span>",
                           "div",
                           {},
                           "span > br"},
           GetLeafNodeTest{u"<div><br></div><span><!--abc--><br></span>",
                           "div",
                           {LeafNodeOption::TreatCommentAsLeafNode},
                           nullptr,
                           "span",
                           0u},
       }) {
    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
                              doc->NodePrincipal(), IgnoreErrors());
    const Element* const target = body->QuerySelector(
        nsDependentCString(testData.mContentSelector), IgnoreErrors());
    MOZ_RELEASE_ASSERT(target);
    const nsIContent* result = HTMLEditUtils::GetNextLeafContent(
        *target, testData.mOptions,
        BlockInlineCheck::UseComputedDisplayOutsideStyle);
    EXPECT_EQ(result, testData.GetExpectedTarget(*body->GetParentNode()))
        << "GetNextLeafContent: " << testData
        << "(Got: " << ToString(RefPtr{result}) << ")";
  }
}

// TODO: Test GetNextLeafContent() which takes EditorDOMPoint

TEST(HTMLEditUtilsTest, GetPreviousLeafContent_Content)
{
  using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
  const RefPtr<Document> doc = CreateHTMLDoc();
  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
  MOZ_RELEASE_ASSERT(body);
  for (const auto& testData : {
           GetLeafNodeTest{u"<p><br></p><div><br></div>""div", {}, "p > br"},
           GetLeafNodeTest{
               u"<p><br></p><!--abc--><div><br></div>""div", {}, "p > br"},
           GetLeafNodeTest{u"<p><br></p><!--abc--><div><br></div>",
                           "div",
                           {LeafNodeOption::TreatCommentAsLeafNode},
                           nullptr,
                           "body",
                           1u},
           GetLeafNodeTest{
               u"<span><br></span><div><br></div>""div", {}, "span > br"},
           GetLeafNodeTest{u"<span><br><!--abc--></span><div><br></div>",
                           "div",
                           {},
                           "span > br"},
           GetLeafNodeTest{u"<span><br><!--abc--></span><div><br></div>",
                           "div",
                           {LeafNodeOption::TreatCommentAsLeafNode},
                           nullptr,
                           "span",
                           1u},
       }) {
    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
                              doc->NodePrincipal(), IgnoreErrors());
    const Element* const target = body->QuerySelector(
        nsDependentCString(testData.mContentSelector), IgnoreErrors());
    MOZ_RELEASE_ASSERT(target);
    const nsIContent* result = HTMLEditUtils::GetPreviousLeafContent(
        *target, testData.mOptions,
        BlockInlineCheck::UseComputedDisplayOutsideStyle);
    EXPECT_EQ(result, testData.GetExpectedTarget(*body->GetParentNode()))
        << "GetPreviousLeafContent: " << testData
        << "(Got: " << ToString(RefPtr{result}) << ")";
  }
}

// TODO: Test GetPreviousLeafContent() which takes EditorDOMPoint

TEST(HTMLEditUtilsTest, GetPreviousSibling)
{
  using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
  const RefPtr<Document> doc = CreateHTMLDoc();
  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
  MOZ_RELEASE_ASSERT(body);
  for (const auto& testData : {
           GetLeafNodeTest{u"<div><p><br></p></div>""div > p", {}, nullptr},
           GetLeafNodeTest{u"<div><p><br></p><p><br></p></div>",
                           "div > p + p",
                           {},
                           "div > p"},
           GetLeafNodeTest{u"<div><p><br></p><!-- comment --><p><br></p></div>",
                           "div p + p",
                           {},
                           "div > p"},
           GetLeafNodeTest{u"<div><p><br></p><!-- comment --><p><br></p></div>",
                           "div > p + p",
                           {LeafNodeOption::TreatCommentAsLeafNode},
                           nullptr,
                           "div",
                           1u},
           GetLeafNodeTest{u"<div><p><br></p> <p><br></p></div>",
                           "div > p + p",
                           {},
                           nullptr,
                           "div",
                           1u},
           GetLeafNodeTest{u"<div><p><br></p> <p><br></p></div>",
                           "div > p + p",
                           {LeafNodeOption::IgnoreInvisibleText},
                           "div > p"},
           GetLeafNodeTest{
               u"<div contenteditable><p><br></p><p "
               u"contenteditable=\"false\"><br></p><p><br></p></div>",
               "div > p + p + p",
               {},
               "div > p + p"},
           GetLeafNodeTest{
               u"<div contenteditable><p><br></p><p "
               u"contenteditable=\"false\"><br></p><p><br></p></div>",
               "div > p + p + p",
               {LeafNodeOption::IgnoreNonEditableNode},
               "div > p"},
           GetLeafNodeTest{
               u"<div><b>abc</b><s><!-- comment --></s><i>def</i></div>",
               "div > i",
               {},
               "div > s"},
           GetLeafNodeTest{
               u"<div><b>abc</b><s><!-- comment --></s><i>def</i></div>",
               "div > i",
               {LeafNodeOption::IgnoreInvisibleEmptyInlineContainers},
               "div > b"},
           GetLeafNodeTest{
               u"<div><b>abc</b><s><!-- comment --></s><i>def</i></div>",
               "div > i",
               {LeafNodeOption::IgnoreInvisibleEmptyInlineContainers,
                LeafNodeOption::TreatCommentAsLeafNode},
               "div > s"},
       }) {
    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
                              doc->NodePrincipal(), IgnoreErrors());
    const Element* const target = body->QuerySelector(
        nsDependentCString(testData.mContentSelector), IgnoreErrors());
    MOZ_RELEASE_ASSERT(target);
    const nsIContent* result = HTMLEditUtils::GetPreviousSibling(
        *target, testData.mOptions,
        BlockInlineCheck::UseComputedDisplayOutsideStyle);
    EXPECT_EQ(result, testData.GetExpectedTarget(*body->GetParentNode()))
        << "GetPreviousSibling: " << testData
        << "(Got: " << ToString(RefPtr{result}) << ")";
  }
}

TEST(HTMLEditUtilsTest, GetNextSibling)
{
  using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
  const RefPtr<Document> doc = CreateHTMLDoc();
  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
  MOZ_RELEASE_ASSERT(body);
  for (const auto& testData : {
           GetLeafNodeTest{u"<div><p><br></p></div>""div > p", {}, nullptr},
           GetLeafNodeTest{u"<div><p><br></p><p><br></p></div>",
                           "div > p",
                           {},
                           "div > p + p"},
           GetLeafNodeTest{u"<div><p><br></p><!-- comment --><p><br></p></div>",
                           "div > p",
                           {},
                           "div > p + p"},
           GetLeafNodeTest{u"<div><p><br></p><!-- comment --><p><br></p></div>",
                           "div > p",
                           {LeafNodeOption::TreatCommentAsLeafNode},
                           nullptr,
                           "div",
                           1u},
           GetLeafNodeTest{u"<div><p><br></p> <p><br></p></div>",
                           "div > p",
                           {},
                           nullptr,
                           "div",
                           1u},
           GetLeafNodeTest{u"<div><p><br></p> <p><br></p></div>",
                           "div > p",
                           {LeafNodeOption::IgnoreInvisibleText},
                           "div > p + p"},
           GetLeafNodeTest{
               u"<div contenteditable><p><br></p><p "
               u"contenteditable=\"false\"><br></p><p><br></p></div>",
               "div p + p",
               {},
               "div > p + p + p"},
           GetLeafNodeTest{
               u"<div contenteditable><p><br></p><p "
               u"contenteditable=\"false\"><br></p><p><br></p></div>",
               "div p",
               {LeafNodeOption::IgnoreNonEditableNode},
               "div > p  + p + p"},
           GetLeafNodeTest{
               u"<div><b>abc</b><s><!-- comment --></s><i>def</i></div>",
               "div > b",
               {},
               "div > s"},
           GetLeafNodeTest{
               u"<div><b>abc</b><s><!-- comment --></s><i>def</i></div>",
               "div > b",
               {LeafNodeOption::IgnoreInvisibleEmptyInlineContainers},
               "div > i"},
           GetLeafNodeTest{
               u"<div><b>abc</b><s><!-- comment --></s><i>def</i></div>",
               "div > b",
               {LeafNodeOption::IgnoreInvisibleEmptyInlineContainers,
                LeafNodeOption::TreatCommentAsLeafNode},
               "div > s"},
       }) {
    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
                              doc->NodePrincipal(), IgnoreErrors());
    const Element* const target = body->QuerySelector(
        nsDependentCString(testData.mContentSelector), IgnoreErrors());
    MOZ_RELEASE_ASSERT(target);
    const nsIContent* result = HTMLEditUtils::GetNextSibling(
        *target, testData.mOptions,
        BlockInlineCheck::UseComputedDisplayOutsideStyle);
    EXPECT_EQ(result, testData.GetExpectedTarget(*body->GetParentNode()))
        << "GetNextSibling: " << testData
        << "(Got: " << ToString(RefPtr{result}) << ")";
  }
}

TEST(HTMLEditUtilsTest, GetFirstChild)
{
  using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
  const RefPtr<Document> doc = CreateHTMLDoc();
  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
  MOZ_RELEASE_ASSERT(body);
  for (const auto& testData : {
           GetLeafNodeTest{u"<div></div>""div", {}, nullptr},
           GetLeafNodeTest{u"<div><p><br></p></div>""div", {}, "div > p"},
           GetLeafNodeTest{
               u"<div><!-- comment --><p><br></p></div>""div", {}, "div > p"},
           GetLeafNodeTest{u"<div><!-- comment --><p><br></p></div>",
                           "div",
                           {LeafNodeOption::TreatCommentAsLeafNode},
                           nullptr,
                           "div",
                           0u},
           GetLeafNodeTest{
               u"<div> <p><br></p></div>""div", {}, nullptr, "div"0u},
           GetLeafNodeTest{u"<div> <p><br></p></div>",
                           "div",
                           {LeafNodeOption::IgnoreInvisibleText},
                           "div > p"},
           GetLeafNodeTest{
               u"<div contenteditable><p "
               u"contenteditable=\"false\"><br></p><p><br></p></div>",
               "div",
               {},
               "div > p"},
           GetLeafNodeTest{
               u"<div contenteditable><p "
               u"contenteditable=\"false\"><br></p><p><br></p></div>",
               "div",
               {LeafNodeOption::IgnoreNonEditableNode},
               "div > p + p"},
           GetLeafNodeTest{u"<div><s><!-- comment --></s><i>def</i></div>",
                           "div",
                           {},
                           "div > s"},
           GetLeafNodeTest{
               u"<div><s><!-- comment --></s><i>def</i></div>",
               "div",
               {LeafNodeOption::IgnoreInvisibleEmptyInlineContainers},
               "div > i"},
           GetLeafNodeTest{
               u"<div><s><!-- comment --></s><i>def</i></div>",
               "div",
               {LeafNodeOption::IgnoreInvisibleEmptyInlineContainers,
                LeafNodeOption::TreatCommentAsLeafNode},
               "div > s"},
       }) {
    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
                              doc->NodePrincipal(), IgnoreErrors());
    const Element* const target = body->QuerySelector(
        nsDependentCString(testData.mContentSelector), IgnoreErrors());
    MOZ_RELEASE_ASSERT(target);
    const nsIContent* result = HTMLEditUtils::GetFirstChild(
        *target, testData.mOptions,
        BlockInlineCheck::UseComputedDisplayOutsideStyle);
    EXPECT_EQ(result, testData.GetExpectedTarget(*body->GetParentNode()))
        << "GetFirstChild: " << testData << "(Got: " << ToString(RefPtr{result})
        << ")";
  }
}

TEST(HTMLEditUtilsTest, GetLastChild)
{
  using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
  const RefPtr<Document> doc = CreateHTMLDoc();
  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
  MOZ_RELEASE_ASSERT(body);
  for (const auto& testData : {
           GetLeafNodeTest{u"<div></div>""div", {}, nullptr},
           GetLeafNodeTest{u"<div><p><br></p></div>""div", {}, "div > p"},
           GetLeafNodeTest{
               u"<div><p><br></p><!-- comment --></div>""div", {}, "div > p"},
           GetLeafNodeTest{u"<div><p><br></p><!-- comment --></div>",
                           "div",
                           {LeafNodeOption::TreatCommentAsLeafNode},
                           nullptr,
                           "div",
                           1u},
           GetLeafNodeTest{
               u"<div><p><br></p> </div>""div", {}, nullptr, "div"1u},
           GetLeafNodeTest{u"<div><p><br></p> </div>",
                           "div",
                           {LeafNodeOption::IgnoreInvisibleText},
                           "div > p"},
           GetLeafNodeTest{u"<div contenteditable><p><br></p><p "
                           u"contenteditable=\"false\"><br></p></div>",
                           "div",
                           {},
                           "div > p + p"},
           GetLeafNodeTest{u"<div contenteditable><p><br></p><p "
                           u"contenteditable=\"false\"><br></p></div>",
                           "div",
                           {LeafNodeOption::IgnoreNonEditableNode},
                           "div > p"},
           GetLeafNodeTest{u"<div><i>def</i><s><!-- comment --></s></div>",
                           "div",
                           {},
                           "div > s"},
           GetLeafNodeTest{
               u"<div><i>def</i><s><!-- comment --></s></div>",
               "div",
               {LeafNodeOption::IgnoreInvisibleEmptyInlineContainers},
               "div > i"},
           GetLeafNodeTest{
               u"<div><i>def</i><s><!-- comment --></s></div>",
               "div",
               {LeafNodeOption::IgnoreInvisibleEmptyInlineContainers,
                LeafNodeOption::TreatCommentAsLeafNode},
               "div > s"},
       }) {
    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
                              doc->NodePrincipal(), IgnoreErrors());
    const Element* const target = body->QuerySelector(
        nsDependentCString(testData.mContentSelector), IgnoreErrors());
    MOZ_RELEASE_ASSERT(target);
    const nsIContent* result = HTMLEditUtils::GetLastChild(
        *target, testData.mOptions,
        BlockInlineCheck::UseComputedDisplayOutsideStyle);
    EXPECT_EQ(result, testData.GetExpectedTarget(*body->GetParentNode()))
        << "GetLastChild: " << testData << "(Got: " << ToString(RefPtr{result})
        << ")";
  }
}

TEST(HTMLEditUtilsTest, GetInclusiveDeepestFirstChildWhichHasOneChild)
{
  using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
  const RefPtr<Document> doc = CreateHTMLDoc();
  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
  MOZ_RELEASE_ASSERT(body);
  for (const auto& testData : {
           GetLeafNodeTest{u"<div></div>""div", {}, "div"},
           GetLeafNodeTest{u"<div><br></div>""div", {}, "div"},
           GetLeafNodeTest{
               u"<div><div><br></div></div>""div", {}, "div > div"},
           GetLeafNodeTest{
               u"<div><!-- comment --><br></div>""div", {}, "div"},
           GetLeafNodeTest{u"<div><!-- comment --><br></div>",
                           "div",
                           {LeafNodeOption::TreatCommentAsLeafNode},
                           "div"},
           GetLeafNodeTest{u"<div><div><!-- comment --><br></div></div>",
                           "div",
                           {},
                           "div > div"},
           GetLeafNodeTest{u"<div><div><!-- comment --><br></div></div>",
                           "div",
                           {LeafNodeOption::TreatCommentAsLeafNode},
                           "div > div"},
           GetLeafNodeTest{u"<div><!-- comment --><div><br></div></div>",
                           "div",
                           {},
                           "div > div"},
           GetLeafNodeTest{u"<div><!-- comment --><div><br></div></div>",
                           "div",
                           {LeafNodeOption::TreatCommentAsLeafNode},
                           "div"},
           GetLeafNodeTest{u"<div> <br></div>""div", {}, "div"},
           GetLeafNodeTest{u"<div> <br></div>",
                           "div",
                           {LeafNodeOption::IgnoreInvisibleText},
                           "div"},
           GetLeafNodeTest{
               u"<div><div> <br></div></div>""div", {}, "div > div"},
           GetLeafNodeTest{u"<div><div> <br></div></div>",
                           "div",
                           {LeafNodeOption::IgnoreInvisibleText},
                           "div > div"},
           GetLeafNodeTest{u"<div> <div><br></div></div>""div", {}, "div"},
           GetLeafNodeTest{u"<div> <div><br></div></div>",
                           "div",
                           {LeafNodeOption::IgnoreInvisibleText},
                           "div > div"},
       }) {
    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
                              doc->NodePrincipal(), IgnoreErrors());
    const Element* const target = body->QuerySelector(
        nsDependentCString(testData.mContentSelector), IgnoreErrors());
    MOZ_RELEASE_ASSERT(target);
    const nsIContent* result =
        HTMLEditUtils::GetInclusiveDeepestFirstChildWhichHasOneChild(
            *target, testData.mOptions,
            BlockInlineCheck::UseComputedDisplayOutsideStyle, nsGkAtoms::div,
            nsGkAtoms::blockquote, nsGkAtoms::ul, nsGkAtoms::ol, nsGkAtoms::dl);
    EXPECT_EQ(result, testData.GetExpectedTarget(*body->GetParentNode()))
        << "GetInclusiveDeepestFirstChildWhichHasOneChild: " << testData
        << "(Got: " << ToString(RefPtr{result}) << ")";
  }
}

struct MOZ_STACK_CLASS LineBreakBeforeBlockBoundaryTest final {
  const char16_t* const mInnerHTML;
  const charconst mContainer;
  const Maybe<uint32_t>
      mContainerIndex;  // Set if need to use CharacterData in mContainer.
  const uint32_t mOffset;
  const bool mExpectedResult;  // true if the method return a line break

  friend std::ostream& operator<<(
      std::ostream& aStream, const LineBreakBeforeBlockBoundaryTest& aTest) {
    aStream << "Scan from { container: " << aTest.mContainer;
    if (aTest.mContainerIndex) {
      aStream << "'s " << aTest.mContainerIndex.value() + 1 << "th child";
    }
    return aStream << ", offset: " << aTest.mOffset << " } in "
                   << NS_ConvertUTF16toUTF8(aTest.mInnerHTML).get() << "\"";
  }
};

TEST(HTMLEditUtilsTest, GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem)
{
  const RefPtr<Document> doc = CreateHTMLDoc();
  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
  MOZ_RELEASE_ASSERT(body);
  for (const auto& testData : {
           LineBreakBeforeBlockBoundaryTest{u"<div contenteditable>abc</div>",
                                            "div", Some(0), 3false},
           LineBreakBeforeBlockBoundaryTest{u"<div contenteditable>abc</div>",
                                            "div", Nothing{}, 1false},
           LineBreakBeforeBlockBoundaryTest{u"<div contenteditable><br></div>",
                                            "div", Nothing{}, 0false},
           LineBreakBeforeBlockBoundaryTest{u"<div contenteditable><br></div>",
                                            "div", Nothing{}, 1, true},
           LineBreakBeforeBlockBoundaryTest{
               u"<div contenteditable><br>  </div>""div", Some(1), 2, true},
           LineBreakBeforeBlockBoundaryTest{
               u"<div contenteditable><br><!-- X --></div>""div", Nothing{},
               2, true},
           LineBreakBeforeBlockBoundaryTest{
               u"<div contenteditable><br><br></div>""div", Nothing{}, 1,
               false},
           LineBreakBeforeBlockBoundaryTest{
               u"<div contenteditable><br><p>abc</p></div>""div", Nothing{},
               1, true},
       }) {
    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
                              doc->NodePrincipal(), IgnoreErrors());
    const Element* const containerElement = body->QuerySelector(
        nsDependentCString(testData.mContainer), IgnoreErrors());
    MOZ_ASSERT(containerElement);
    const Element* const editingHost =
        body->QuerySelector("[contenteditable]"_ns, IgnoreErrors());
    MOZ_ASSERT(editingHost);
    const nsIContent* const container =
        testData.mContainerIndex
            ? containerElement->GetChildAt_Deprecated(*testData.mContainerIndex)
            : containerElement;
    MOZ_RELEASE_ASSERT(container);
    const Maybe<EditorRawLineBreak> result =
        HTMLEditUtils::GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem<
            EditorRawLineBreak>(EditorRawDOMPoint(container, testData.mOffset),
                                *editingHost);
    EXPECT_EQ(result.isSome(), testData.mExpectedResult)
        << "GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem: " << testData;
  }
}

enum class LineBreakIs {
  FollowedByCurrentBlockBoundary,
  FollowedByOtherBlockBoundary,
  FollowedByLineBreak,
  FollowingLineBreak,
  FollowingCurrentBlockBoundary,
  FollowingOtherBlockBoundary,
};
using LineBreakPosition = EnumSet<LineBreakIs>;

struct MOZ_STACK_CLASS BRElementPositionTest final {
  const char16_t* const mInnerHTML;
  const charconst mBRSelector;
  const LineBreakPosition mExpectedPosition;
  const bool mIsSignificant;
  const bool mIsEmptyBlockPadding;

  friend std::ostream& operator<<(std::ostream& aStream,
                                  const BRElementPositionTest& aTest) {
    return aStream << "<br> of \"" << aTest.mBRSelector << "\" in \""
                   << NS_ConvertUTF16toUTF8(aTest.mInnerHTML).get() << "\"";
  }
};

TEST(HTMLEditUtilsTest, BRElementPosition)
{
  const RefPtr<Document> doc = CreateHTMLDoc();
  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
  MOZ_RELEASE_ASSERT(body);
  for (const auto& testData : {
           BRElementPositionTest{u"<div><br></div>",
                                 "div > br",
                                 {LineBreakIs::FollowedByCurrentBlockBoundary,
                                  LineBreakIs::FollowingCurrentBlockBoundary},
                                 true,
                                 true},
           BRElementPositionTest{u"<div><br><br></div>",
                                 "div > br",
                                 {LineBreakIs::FollowedByLineBreak,
                                  LineBreakIs::FollowingCurrentBlockBoundary},
                                 true,
                                 false},
           BRElementPositionTest{u"<div><br><br></div>",
                                 "div > br + br",
                                 {LineBreakIs::FollowedByCurrentBlockBoundary,
                                  LineBreakIs::FollowingLineBreak},
                                 true,
                                 false},
           BRElementPositionTest{u"<div><br><div><br></div></div>",
                                 "div > br",
                                 {LineBreakIs::FollowedByOtherBlockBoundary,
                                  LineBreakIs::FollowingCurrentBlockBoundary},
                                 true,
                                 false},
           BRElementPositionTest{u"<div><div></div><br></div>",
                                 "div > div + br",
                                 {LineBreakIs::FollowedByCurrentBlockBoundary,
                                  LineBreakIs::FollowingOtherBlockBoundary},
                                 true,
                                 false},
           BRElementPositionTest{u"<div> <br> </div>",
                                 "div > br",
                                 {LineBreakIs::FollowedByCurrentBlockBoundary,
                                  LineBreakIs::FollowingCurrentBlockBoundary},
                                 true,
                                 true},
           BRElementPositionTest{u"<div>abc<br></div>",
                                 "div > br",
                                 {LineBreakIs::FollowedByCurrentBlockBoundary},
                                 false,
                                 false},
           BRElementPositionTest{u"<div><br>abc</div>",
                                 "div > br",
                                 {LineBreakIs::FollowingCurrentBlockBoundary},
                                 true,
                                 false},
           BRElementPositionTest{u"<div><span></span><br><span></span></div>",
                                 "div > br",
                                 {LineBreakIs::FollowedByCurrentBlockBoundary,
                                  LineBreakIs::FollowingCurrentBlockBoundary},
                                 true,
                                 true},
           BRElementPositionTest{u"<div><!-- --><br><!-- --></div>",
                                 "div > br",
                                 {LineBreakIs::FollowedByCurrentBlockBoundary,
                                  LineBreakIs::FollowingCurrentBlockBoundary},
                                 true,
                                 true},
           BRElementPositionTest{
               u"<div><!-- --><span><!-- --><br><!-- --></span><!-- --></div>",
               "div > span > br",
               {LineBreakIs::FollowedByCurrentBlockBoundary,
                LineBreakIs::FollowingCurrentBlockBoundary},
               true,
               true},
       }) {
    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
                              doc->NodePrincipal(), IgnoreErrors());
    const Element* const brElement = body->QuerySelector(
        nsDependentCString(testData.mBRSelector), IgnoreErrors());
    MOZ_ASSERT(brElement);
    EXPECT_EQ(HTMLEditUtils::IsBRElementFollowedByBlockBoundary(*brElement),
              testData.mExpectedPosition.contains(
                  LineBreakIs::FollowedByCurrentBlockBoundary) ||
                  testData.mExpectedPosition.contains(
                      LineBreakIs::FollowedByOtherBlockBoundary))
        << "IsBRElementFollowedByBlockBoundary: " << testData;
    EXPECT_EQ(
        HTMLEditUtils::IsBRElementFollowedByCurrentBlockBoundary(*brElement),
        testData.mExpectedPosition.contains(
            LineBreakIs::FollowedByCurrentBlockBoundary))
        << "IsBRElementFollowedByCurrentBlockBoundary: " << testData;
    EXPECT_EQ(
        HTMLEditUtils::IsBRElementFollowingCurrentBlockBoundary(*brElement),
        testData.mExpectedPosition.contains(
            LineBreakIs::FollowingCurrentBlockBoundary))
        << "IsBRElementFollowingCurrentBlockBoundary: " << testData;
    EXPECT_EQ(
        HTMLEditUtils::IsBRElementFollowedByOtherBlockBoundary(*brElement),
        testData.mExpectedPosition.contains(
            LineBreakIs::FollowedByOtherBlockBoundary))
        << "IsBRElementFollowedByOtherBlockBoundary: " << testData;
    EXPECT_EQ(HTMLEditUtils::IsBRElementFollowedByLineBoundary(*brElement),
              testData.mExpectedPosition.contains(
                  LineBreakIs::FollowedByCurrentBlockBoundary) ||
                  testData.mExpectedPosition.contains(
                      LineBreakIs::FollowedByOtherBlockBoundary) ||
                  testData.mExpectedPosition.contains(
                      LineBreakIs::FollowedByLineBreak))
        << "IsBRElementFollowedByLineBoundary: " << testData;
    EXPECT_EQ(HTMLEditUtils::IsBRElementFollowingLineBoundary(*brElement),
              testData.mExpectedPosition.contains(
                  LineBreakIs::FollowingCurrentBlockBoundary) ||
                  testData.mExpectedPosition.contains(
                      LineBreakIs::FollowingOtherBlockBoundary) ||
                  testData.mExpectedPosition.contains(
                      LineBreakIs::FollowingLineBreak))
        << "IsBRElementFollowingLineBoundary: " << testData;
    EXPECT_EQ(
        HTMLEditUtils::IsBRElementFollowingLineBreak(*brElement),
        testData.mExpectedPosition.contains(LineBreakIs::FollowingLineBreak))
        << "IsBRElementFollowingLineBoundary: " << testData;
    EXPECT_EQ(HTMLEditUtils::IsUnnecessaryBRElement(
                  *brElement, PaddingForEmptyBlock::Unnecessary),
              !testData.mIsSignificant || testData.mIsEmptyBlockPadding)
        << "IsUnnecessaryBRElement(PaddingForEmptyBlock::Unnecessary): "
        << testData;
    EXPECT_EQ(HTMLEditUtils::IsUnnecessaryBRElement(
                  *brElement, PaddingForEmptyBlock::Significant),
              !testData.mIsSignificant)
        << "IsUnnecessaryBRElement(PaddingForEmptyBlock::Significant): "
        << testData;
    EXPECT_EQ(HTMLEditUtils::IsSignificantBRElement(
                  *brElement, PaddingForEmptyBlock::Unnecessary),
              testData.mIsSignificant && !testData.mIsEmptyBlockPadding)
        << "IsSignificantBRElement(PaddingForEmptyBlock::Unnecessary): "
        << testData;
    EXPECT_EQ(HTMLEditUtils::IsSignificantBRElement(
                  *brElement, PaddingForEmptyBlock::Significant),
              testData.mIsSignificant)
        << "IsSignificantBRElement(PaddingForEmptyBlock::Significant): "
        << testData;
  }
}

struct MOZ_STACK_CLASS ScanVisibleThingTest final {
  const char16_t* const mInnerHTML;
  const Maybe<uint32_t> mContainerOffset;
  const charconst mStartContainer;
  const uint32_t mOffsetInContainer;
  struct MOZ_STACK_CLASS ExpectedData {
    const charconst mBlockElement;
    const charconst mBRElement;
  };
  const ExpectedData mExpectedIfPaddingForEmptyBlockSignificant;
  const ExpectedData mExpectedIfPaddingForEmptyBlockUnnecessary;

  friend std::ostream& operator<<(std::ostream& aStream,
                                  const ScanVisibleThingTest& aTest) {
    aStream << "{";
    if (aTest.mContainerOffset) {
      aStream << *aTest.mContainerOffset << "th child of ";
    }
    return aStream << "\"" << aTest.mStartContainer << "\"" << "-"
                   << aTest.mOffsetInContainer << "} in \""
                   << NS_ConvertUTF16toUTF8(aTest.mInnerHTML).get() << "\"";
  }
};

TEST(HTMLEditUtilsTest, ScanInclusiveNextThingWithIgnoringUnnecessaryLineBreak)
{
  const RefPtr<Document> doc = CreateHTMLDoc();
  const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
  MOZ_RELEASE_ASSERT(body);
  for (const auto& testData : {
           ScanVisibleThingTest{u"<div><br></div>",
                                Nothing{},
                                "div",
                                0u,
                                {nullptr, "div > br"},
                                {"div""div > br"}},
           ScanVisibleThingTest{u"<div><br></div>",
                                Nothing{},
                                "div",
                                1u,
                                {"div", nullptr},
                                {"div", nullptr}},
           ScanVisibleThingTest{u"<div>ABC</div>",
                                Some(0u),
                                "div",
                                0u,
                                {nullptr, nullptr},
                                {nullptr, nullptr}},
           ScanVisibleThingTest{u"<div>ABC</div>",
                                Some(0u),
                                "div",
                                3u,
                                {"div", nullptr},
                                {"div", nullptr}},
           ScanVisibleThingTest{u"<div>ABC<br></div>",
                                Some(0u),
                                "div",
                                0u,
                                {nullptr, nullptr},
                                {nullptr, nullptr}},
           ScanVisibleThingTest{u"<div>ABC<br></div>",
                                Some(0u),
                                "div",
                                0u,
                                {nullptr, nullptr},
                                {nullptr, nullptr}},
           ScanVisibleThingTest{u"<div>ABC<br>DEF</div>",
                                Some(0u),
                                "div",
                                3u,
                                {nullptr, "div > br"},
                                {nullptr, "div > br"}},
           ScanVisibleThingTest{u"<div>ABC<br>DEF</div>",
                                Nothing{},
                                "div",
                                1u,
                                {nullptr, "div > br"},
                                {nullptr, "div > br"}},
           ScanVisibleThingTest{u"<div><br><p><br></p></div>",
                                Nothing{},
                                "div",
                                0u,
                                {nullptr, "div > br"},
                                {nullptr, "div > br"}},
           ScanVisibleThingTest{u"<div><br><p><br></p></div>",
                                Nothing{},
                                "p",
                                0u,
                                {nullptr, "p > br"},
                                {"p""p > br"}},
           ScanVisibleThingTest{u"<div><br><p><br></p></div>",
                                Nothing{},
                                "p",
                                1u,
                                {"p", nullptr},
                                {"p", nullptr}},
           ScanVisibleThingTest{u"<div><br><br></div>",
                                Nothing{},
                                "div",
                                0u,
                                {nullptr, "div > br"},
                                {nullptr, "div > br"}},
           ScanVisibleThingTest{u"<div><br><br></div>",
                                Nothing{},
                                "div",
                                1u,
                                {nullptr, "div > br + br"},
                                {nullptr, "div > br + br"}},
           ScanVisibleThingTest{u"<div><span></span><!-- comment "
                                u"--><br><span></span><!-- comment --></div>",
                                Nothing{},
                                "div",
                                0u,
                                {nullptr, "div > br"},
                                {"div""div > br"}},
           ScanVisibleThingTest{u"<div><span></span><!-- comment "
                                u"--><br><span></span><!-- comment --></div>",
                                Nothing{},
                                "div",
                                3u,
                                {"div", nullptr},
                                {"div", nullptr}},
           ScanVisibleThingTest{
               u"<div>ABC<br><span></span><!-- comment --></div>",
               Some(0u),
               "div",
               3u,
               {"div""div > br"},
               {"div""div > br"}},
           ScanVisibleThingTest{
               u"<div>AB <br><span></span><!-- comment --></div>",
               Some(0u),
               "div",
               3u,
               {"div""div > br"},
               {"div""div > br"}},
       }) {
    body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML),
                              doc->NodePrincipal(), IgnoreErrors());
    const nsIContent* const container = [&]() -> nsIContent* {
      Element* const startContainer = body->QuerySelector(
          nsDependentCString(testData.mStartContainer), IgnoreErrors());
      if (!testData.mContainerOffset) {
        return startContainer;
      }
      return startContainer->GetChildAt_Deprecated(*testData.mContainerOffset);
    }();
    MOZ_ASSERT(container);
    EditorRawDOMPoint scanStart(container, testData.mOffsetInContainer);
    MOZ_ASSERT(scanStart.IsSetAndValid());
    for (const auto paddingForEmptyBlock :
         {PaddingForEmptyBlock::Significant,
          PaddingForEmptyBlock::Unnecessary}) {
      const WSScanResult nextThing =
          HTMLEditUtils::ScanInclusiveNextThingWithIgnoringUnnecessaryLineBreak(
              scanStart, paddingForEmptyBlock, *body);
      const ScanVisibleThingTest::ExpectedData& expectedData =
          paddingForEmptyBlock == PaddingForEmptyBlock::Significant
              ? testData.mExpectedIfPaddingForEmptyBlockSignificant
              : testData.mExpectedIfPaddingForEmptyBlockUnnecessary;
      if (expectedData.mBlockElement) {
        EXPECT_EQ(nextThing.ReachedBlockBoundary(), true)
            << "ReachedBlockBoundary(" << paddingForEmptyBlock
            << "): " << testData;
        if (!nextThing.ReachedBlockBoundary()) {
          continue;
        }
        const Element* const expectedBlock = body->QuerySelector(
            nsDependentCString(expectedData.mBlockElement), IgnoreErrors());
        MOZ_ASSERT(expectedBlock);
        EXPECT_EQ(nextThing.ElementPtr(), expectedBlock)
            << "BlockElement(" << paddingForEmptyBlock << "): " << testData;
        EXPECT_EQ(nextThing.MaybeIgnoredLineBreak().isSome(),
                  !!expectedData.mBRElement)
            << "Is <br> skipped? (" << paddingForEmptyBlock
            << "): " << testData;
        if (!expectedData.mBRElement) {
          continue;
        }
        const Element* const expectedBR = body->QuerySelector(
            nsDependentCString(expectedData.mBRElement), IgnoreErrors());
        MOZ_ASSERT(expectedBR);
        EXPECT_EQ(nextThing.MaybeIgnoredLineBreak()->GetBRElement(), expectedBR)
            << "Skipped <br> (" << paddingForEmptyBlock << "): " << testData;
        continue;
      }
      if (expectedData.mBRElement) {
        EXPECT_EQ(nextThing.ReachedBRElement(), true)
            << "ReachedBRElement(" << paddingForEmptyBlock << "): " << testData;
        if (!nextThing.ReachedBRElement()) {
          continue;
        }
        EXPECT_EQ(nextThing.MaybeIgnoredLineBreak().isNothing(), true)
            << "Is <br> skipped? (" << paddingForEmptyBlock
            << "): " << testData;
        const Element* const expectedBR = body->QuerySelector(
            nsDependentCString(expectedData.mBRElement), IgnoreErrors());
        MOZ_ASSERT(expectedBR);
        EXPECT_EQ(nextThing.BRElementPtr(), expectedBR)
            << "Skipped <br> (" << paddingForEmptyBlock << "): " << testData;
        continue;
      }
      EXPECT_EQ(nextThing.ReachedBlockBoundary(), false)
          << "ReachedBlockBoundary(" << paddingForEmptyBlock
          << "): " << testData;
      EXPECT_EQ(nextThing.ReachedBRElement(), false)
          << "ReachedBRElement(" << paddingForEmptyBlock << "): " << testData;
    }
  }
}

}  // namespace mozilla

Messung V0.5 in Prozent
C=96 H=90 G=93

¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.70Angebot  ¤

*Eine klare Vorstellung vom Zielzustand






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.