/* 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 CreateHTMLDoc() { nsCOMPtr uri; NS_NewURI(getter_AddRefs(uri), "data:text/html,"); RefPtr principal = BasePrincipal::CreateContentPrincipal(uri, OriginAttributes()); MOZ_RELEASE_ASSERT(principal); nsCOMPtr 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 html = doc->CreateHTMLElement(nsGkAtoms::html); html->SetInnerHTMLTrusted(u""_ns, principal, IgnoreErrors()); doc->AppendChild(*html, IgnoreErrors()); return doc.forget(); } struct MOZ_STACK_CLASS DeepestEditablePointTest final { const char16_t* const mInnerHTML; const char* const mContentSelector; const EditablePointOptions mOptions; const char* const 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 doc = CreateHTMLDoc(); const RefPtr body = doc->GetBody(); MOZ_RELEASE_ASSERT(body); for (const auto& testData : { DeepestEditablePointTest{ u"

", "div[contenteditable] > div", {}, "div[contenteditable] > div", nullptr, 0 // Find
}, DeepestEditablePointTest{ u"
", "div[contenteditable] > div", {}, "div[contenteditable] > div", nullptr, 0 // Find }, DeepestEditablePointTest{ u"

", "div[contenteditable] > div", {}, "div[contenteditable] > div", nullptr, 0 // Find
}, DeepestEditablePointTest{ u"
abc
", "div[contenteditable] > div", {}, "div[contenteditable] > div", u"abc", 0 // Find "a" }, DeepestEditablePointTest{ u"

abc

", "div[contenteditable] > div", {}, "div[contenteditable] > div > p", u"abc", 0 // Find "a" }, DeepestEditablePointTest{ u"
abc
", "div[contenteditable] > div", {}, "div[contenteditable] > div > span", u"abc", 0 // Find "a" }, DeepestEditablePointTest{ u"
abc
", "div[contenteditable] > div", {}, "div[contenteditable] > div", u" abc", 3 // Find "a" }, DeepestEditablePointTest{ u"
abc
", "div[contenteditable] > div", {}, "div[contenteditable] > div > span", u" abc", 3 // Find "a" }, DeepestEditablePointTest{ u"
abc
", "div[contenteditable] > div", {EditablePointOption::RecognizeInvisibleWhiteSpaces}, "div[contenteditable] > div", u" abc", 0 // Find the first white-space }, DeepestEditablePointTest{ u"
abc
", "div[contenteditable] > div", {EditablePointOption::RecognizeInvisibleWhiteSpaces}, "div[contenteditable] > div > span", u" abc", 0 // Find the first white-space }, DeepestEditablePointTest{ u"
abc
", "div[contenteditable] > div", {}, "div[contenteditable] > div > span", nullptr, 0 // Find the empty }, DeepestEditablePointTest{ u"
abc
", "div[contenteditable] > div", {}, "div[contenteditable] > div", u"abc", 0 // Find "a" }, DeepestEditablePointTest{ u"
abc
", "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"
abc def
", "div[contenteditable] > div > b", {}, "div[contenteditable] > div > b > span", u" def", 3 // Find "d" }, // There is a child DeepestEditablePointTest{ u"

", "div[contenteditable] > div", {}, "div[contenteditable] td", nullptr, 0 // Find
}, DeepestEditablePointTest{ u"

", "div[contenteditable] > div", {EditablePointOption::StopAtTableElement}, "div[contenteditable] > div", nullptr, 0 // Find }, DeepestEditablePointTest{ u"

", "div[contenteditable] > div", {EditablePointOption::StopAtAnyTableElement}, "div[contenteditable] > div", nullptr, 0 // Find }, // In a table structure DeepestEditablePointTest{ u"

", "div[contenteditable] table", {}, "div[contenteditable] td", nullptr, 0 // Find
}, DeepestEditablePointTest{ u"

", "div[contenteditable] table", {EditablePointOption::StopAtTableElement}, "div[contenteditable] td", nullptr, 0 // Find
}, DeepestEditablePointTest{ u"

", "div[contenteditable] table", {EditablePointOption::StopAtAnyTableElement}, "div[contenteditable] table", nullptr, 0 // Find }, //
    DeepestEditablePointTest{ u"

    ", "div[contenteditable] > div", {}, "div[contenteditable] li", nullptr, 0 // Find
    }, DeepestEditablePointTest{ u"

    ", "div[contenteditable] > div", {EditablePointOption::StopAtListItemElement}, "div[contenteditable] ul", nullptr, 0 // Find
  • }, DeepestEditablePointTest{ u"

    ", "div[contenteditable] > div", {EditablePointOption::StopAtListElement}, "div[contenteditable] > div", nullptr, 0 // Find
      }, //
        DeepestEditablePointTest{ u"

        ", "div[contenteditable] > div", {}, "div[contenteditable] li", nullptr, 0 // Find
        }, DeepestEditablePointTest{ u"

        ", "div[contenteditable] > div", {EditablePointOption::StopAtListItemElement}, "div[contenteditable] ol", nullptr, 0 // Find
      1. }, DeepestEditablePointTest{ u"

        ", "div[contenteditable] > div", {EditablePointOption::StopAtListElement}, "div[contenteditable] > div", nullptr, 0 // Find
          }, //
          and
          DeepestEditablePointTest{ u"

          ", "div[contenteditable] > div", {}, "div[contenteditable] dt", nullptr, 0 // Find
          }, DeepestEditablePointTest{ u"

          ", "div[contenteditable] > div", {EditablePointOption::StopAtListItemElement}, "div[contenteditable] dl", nullptr, 0 // Find
          }, DeepestEditablePointTest{ u"

          ", "div[contenteditable] > div", {EditablePointOption::StopAtListElement}, "div[contenteditable] > div", nullptr, 0 // Find
          }, //
          and
          DeepestEditablePointTest{ u"

          ", "div[contenteditable] > div", {}, "div[contenteditable] dd", nullptr, 0 // Find
          }, DeepestEditablePointTest{ u"

          ", "div[contenteditable] > div", {EditablePointOption::StopAtListItemElement}, "div[contenteditable] dl", nullptr, 0 // Find
          }, DeepestEditablePointTest{ u"

          ", "div[contenteditable] > div", {EditablePointOption::StopAtListElement}, "div[contenteditable] > div", nullptr, 0 // Find
          }, }) { 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( *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 doc = CreateHTMLDoc(); const RefPtr body = doc->GetBody(); MOZ_RELEASE_ASSERT(body); for (const auto& testData : { DeepestEditablePointTest{ u"

          ", "div[contenteditable] > div", {}, "div[contenteditable] > div", nullptr, // XXX Should be 0 due to an invisible
          ? 1 // Find
          }, DeepestEditablePointTest{ u"
          ", "div[contenteditable] > div", {}, "div[contenteditable] > div", nullptr, 1 // Find }, DeepestEditablePointTest{ u"

          ", "div[contenteditable] > div", {}, "div[contenteditable] > div", nullptr, 1 // Find
          }, DeepestEditablePointTest{ u"
          abc
          ", "div[contenteditable] > div", {}, "div[contenteditable] > div", u"abc", 3 // Find "c" }, DeepestEditablePointTest{ u"

          abc

          ", "div[contenteditable] > div", {}, "div[contenteditable] > div > p", u"abc", 3 // Find "c" }, DeepestEditablePointTest{ u"
          abc
          ", "div[contenteditable] > div", {}, "div[contenteditable] > div > span", u"abc", 3 // Find "c" }, DeepestEditablePointTest{ u"
          abc
          ", "div[contenteditable] > div", {}, "div[contenteditable] > div", u"abc ", 3 // Find "c" }, DeepestEditablePointTest{ u"
          abc
          ", "div[contenteditable] > div", {}, "div[contenteditable] > div > span", u"abc ", 3 // Find "a" }, DeepestEditablePointTest{ u"
          abc
          ", "div[contenteditable] > div", {EditablePointOption::RecognizeInvisibleWhiteSpaces}, "div[contenteditable] > div", u"abc ", 6 // Find the last white-space }, DeepestEditablePointTest{ u"
          abc
          ", "div[contenteditable] > div", {EditablePointOption::RecognizeInvisibleWhiteSpaces}, "div[contenteditable] > div > span", u"abc ", 6 // Find the last white-space }, DeepestEditablePointTest{ u"
          abc
          ", "div[contenteditable] > div", {}, "div[contenteditable] > div > span", nullptr, 0 // Find the empty }, DeepestEditablePointTest{ u"
          abc
          ", "div[contenteditable] > div", {}, "div[contenteditable] > div", u"abc", 3 // Find "c" }, DeepestEditablePointTest{ u"
          abc
          ", "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"
          abc def
          ", "div[contenteditable] > div > b", {}, "div[contenteditable] > div > b > span", u"abc ", 3 // Find "c" }, // There is a child DeepestEditablePointTest{ u"

          ", "div[contenteditable] > div", {}, "div[contenteditable] td", nullptr, 1 // Find
          }, DeepestEditablePointTest{ u"

          ", "div[contenteditable] > div", {EditablePointOption::StopAtTableElement}, "div[contenteditable] > div", nullptr, 1 // Find }, DeepestEditablePointTest{ u"

          ", "div[contenteditable] > div", {EditablePointOption::StopAtAnyTableElement}, "div[contenteditable] > div", nullptr, 1 // Find }, // In a table structure DeepestEditablePointTest{ u"

          ", "div[contenteditable] table", {}, "div[contenteditable] td", nullptr, 1 // Find
          }, DeepestEditablePointTest{ u"

          ", "div[contenteditable] table", {EditablePointOption::StopAtTableElement}, "div[contenteditable] td", nullptr, 1 // Find
          }, DeepestEditablePointTest{ u"

          ", "div[contenteditable] table", {EditablePointOption::StopAtAnyTableElement}, "div[contenteditable] table", nullptr, 1 // Find }, //
            DeepestEditablePointTest{ u"

            ", "div[contenteditable] > div", {}, "div[contenteditable] li", nullptr, 1 // Find
            }, DeepestEditablePointTest{ u"

            ", "div[contenteditable] > div", {EditablePointOption::StopAtListItemElement}, "div[contenteditable] ul", nullptr, 1 // Find
          • }, DeepestEditablePointTest{ u"

            ", "div[contenteditable] > div", {EditablePointOption::StopAtListElement}, "div[contenteditable] > div", nullptr, 1 // Find
              }, //
                DeepestEditablePointTest{ u"

                ", "div[contenteditable] > div", {}, "div[contenteditable] li", nullptr, 1 // Find
                }, DeepestEditablePointTest{ u"

                ", "div[contenteditable] > div", {EditablePointOption::StopAtListItemElement}, "div[contenteditable] ol", nullptr, 1 // Find
              1. }, DeepestEditablePointTest{ u"

                ", "div[contenteditable] > div", {EditablePointOption::StopAtListElement}, "div[contenteditable] > div", nullptr, 1 // Find
                  }, //
                  and
                  DeepestEditablePointTest{ u"

                  ", "div[contenteditable] > div", {}, "div[contenteditable] dt", nullptr, 1 // Find
                  }, DeepestEditablePointTest{ u"

                  ", "div[contenteditable] > div", {EditablePointOption::StopAtListItemElement}, "div[contenteditable] dl", nullptr, 1 // Find
                  }, DeepestEditablePointTest{ u"

                  ", "div[contenteditable] > div", {EditablePointOption::StopAtListElement}, "div[contenteditable] > div", nullptr, 1 // Find
                  }, //
                  and
                  DeepestEditablePointTest{ u"

                  ", "div[contenteditable] > div", {}, "div[contenteditable] dd", nullptr, 1 // Find
                  }, DeepestEditablePointTest{ u"

                  ", "div[contenteditable] > div", {EditablePointOption::StopAtListItemElement}, "div[contenteditable] dl", nullptr, 1 // Find
                  }, DeepestEditablePointTest{ u"

                  ", "div[contenteditable] > div", {EditablePointOption::StopAtListElement}, "div[contenteditable] > div", nullptr, 1 // Find
                  }, }) { 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( *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 char* const mContentSelector; const AncestorTypes mAncestorTypes; const char* const mAncestorLimiterSelector; const char* const mExpectedSelectorForAncestor; const char* const 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 doc = CreateHTMLDoc(); const RefPtr body = doc->GetBody(); MOZ_RELEASE_ASSERT(body); for (const auto& testData : { AncestorElementTest{ u"

                  " u"
                  ", "[contenteditable] > div > span > div > span", {AncestorType::ClosestBlockElement}, "[contenteditable]", "[contenteditable] > div > span > div", "[contenteditable] > div > span > div"}, AncestorElementTest{ u"

                  " u"
                  ", "[contenteditable] > div > span > div", {AncestorType::ClosestBlockElement}, "[contenteditable]", "[contenteditable] > div", "[contenteditable] > div > span > div"}, AncestorElementTest{ u"

                  ", "[contenteditable] > div", {AncestorType::ClosestBlockElement}, "[contenteditable]", // Should return the editing host because of the closest ancestor // of the deepest
                  . "[contenteditable]", "[contenteditable] > div"}, AncestorElementTest{ u"

                  ", "[contenteditable] > span", {AncestorType::ClosestBlockElement}, "[contenteditable]", // Should return the editing host because of a block. "[contenteditable]", "[contenteditable]"}, AncestorElementTest{ u"

                  ", "[contenteditable] > span", {AncestorType::ClosestBlockElement, AncestorType::ReturnAncestorLimiterIfNoProperAncestor}, "[contenteditable]", "[contenteditable]", "[contenteditable]"}, AncestorElementTest{u"

                  ", "[contenteditable] > span", {AncestorType::ClosestBlockElement, AncestorType::EditableElement}, "[contenteditable]", "[contenteditable]", "[contenteditable]"}, AncestorElementTest{ u"

                  ", "[contenteditable] > span", {AncestorType::ClosestBlockElement, AncestorType::EditableElement, AncestorType::ReturnAncestorLimiterIfNoProperAncestor}, "[contenteditable]", "[contenteditable]", "[contenteditable]"}, AncestorElementTest{ u"
                  ", "[contenteditable] > span", {AncestorType::ClosestBlockElement, AncestorType::EditableElement, AncestorType::ReturnAncestorLimiterIfNoProperAncestor}, "[contenteditable]", // Should return the inline editing host because of the ancestor // limiter. "[contenteditable]", "[contenteditable]"}, AncestorElementTest{ u"
                  ", "[contenteditable] > span", {AncestorType::ClosestBlockElement, AncestorType::EditableElement}, "[contenteditable]", // Should not return the body because of not editable. nullptr, nullptr}, AncestorElementTest{ u"

                  ", "[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 doc = CreateHTMLDoc(); const RefPtr body = doc->GetBody(); MOZ_RELEASE_ASSERT(body); for (const auto& testData : { AncestorElementTest{ u"

                  ", "[contenteditable] > span", {AncestorType::MostDistantInlineElementInBlock, AncestorType::EditableElement, AncestorType::ReturnAncestorLimiterIfNoProperAncestor}, "[contenteditable]", // Should return the editing host because of editable. "[contenteditable]", "[contenteditable] > span"}, AncestorElementTest{ u"

                  " u"
                  ", "[contenteditable] s", {AncestorType::MostDistantInlineElementInBlock}, "[contenteditable]", // Should return the because of the deepest
                  . "[contenteditable] u", "[contenteditable] u"}, AncestorElementTest{u"

                  " u"
                  ", "[contenteditable] u", {AncestorType::MostDistantInlineElementInBlock}, "[contenteditable]", // Should return nullptr because of no ancestor // in the deepest
                  . nullptr, "[contenteditable] u"}, AncestorElementTest{ u"

                  " u"
                  ", "[contenteditable] div", {AncestorType::MostDistantInlineElementInBlock}, "[contenteditable]", "[contenteditable] b", // Should return nullptr because of starting from the
                  . nullptr}, AncestorElementTest{ u",
                  ", "[contenteditable] i", {AncestorType::MostDistantInlineElementInBlock}, "[contenteditable]", // Should return the editing host because of inline. "[contenteditable]", "[contenteditable]"}, AncestorElementTest{ u"
                  ", "[contenteditable] i", {AncestorType::MostDistantInlineElementInBlock, AncestorType::ReturnAncestorLimiterIfNoProperAncestor}, "[contenteditable]", // Should return the editing host because of inline. "[contenteditable]", "[contenteditable]"}, AncestorElementTest{ u"
                  ", "[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 doc = CreateHTMLDoc(); const RefPtr body = doc->GetBody(); MOZ_RELEASE_ASSERT(body); for (const auto& testData : { AncestorElementTest{ u"
                  ", "[contenteditable] > button > span", {AncestorType::ClosestBlockElement, AncestorType::ClosestButtonElement}, "[contenteditable]", "[contenteditable] > button", "[contenteditable] > button"}, AncestorElementTest{ u"
                  ", "[contenteditable] > button", {AncestorType::ClosestBlockElement, AncestorType::ClosestButtonElement}, "[contenteditable]", "[contenteditable]", "[contenteditable] > button"}, AncestorElementTest{ u"
                  ", "[contenteditable] button > i", {AncestorType::MostDistantInlineElementInBlock, AncestorType::StopAtClosestButtonElement}, "[contenteditable]", // because of no inline elements between
                  ", "[contenteditable] button > i > u", {AncestorType::MostDistantInlineElementInBlock, AncestorType::StopAtClosestButtonElement}, "[contenteditable]", // because of is a child of
                  ", "[contenteditable] button > i", {AncestorType::MostDistantInlineElementInBlock, AncestorType::ClosestButtonElement}, "[contenteditable]", "[contenteditable] button", "[contenteditable] button"}, AncestorElementTest{ u"
                  ", "[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 doc = CreateHTMLDoc(); const RefPtr body = doc->GetBody(); MOZ_RELEASE_ASSERT(body); for (const auto& testData : { AncestorElementTest{u"

                  ", "[contenteditable] > hr", {AncestorType::ClosestBlockElement, AncestorType::IgnoreHRElement}, "[contenteditable]", "[contenteditable]", "[contenteditable]"}, AncestorElementTest{ u"
                  ", "[contenteditable] > button > hr", {AncestorType::ClosestBlockElement, AncestorType::ClosestButtonElement, AncestorType::IgnoreHRElement}, "[contenteditable]", "[contenteditable] > button", "[contenteditable] > button"}, AncestorElementTest{u"

                  ", "[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 doc = CreateHTMLDoc(); const RefPtr body = doc->GetBody(); MOZ_RELEASE_ASSERT(body); for (const auto& testData : { AncestorElementTest{ u"

                  ", "[contenteditable] > div > span > br", {AncestorType::ClosestContainerElement}, "[contenteditable]", "[contenteditable] > div > span", "[contenteditable] > div > span"}, AncestorElementTest{ u"

                  ", "[contenteditable] > div > span", {AncestorType::ClosestContainerElement}, "[contenteditable]", "[contenteditable] > div", "[contenteditable] > div > span"}, AncestorElementTest{ u"

                  ", "[contenteditable] > div", {AncestorType::ClosestContainerElement}, "[contenteditable]", "[contenteditable]", "[contenteditable] > div"}, AncestorElementTest{u"
                  ", "br[contenteditable]", {AncestorType::ClosestContainerElement}, "br[contenteditable]", // Should return nullptr because of scanning // start from the parent of ancestor limiter. nullptr, //
                  is not a container. nullptr}, AncestorElementTest{ u"
                  ", "br[contenteditable]", {AncestorType::ClosestContainerElement, AncestorType::ReturnAncestorLimiterIfNoProperAncestor}, "br[contenteditable]", // Should return nullptr because of scanning start from the // parent of ancestor limiter. nullptr, //
                  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 doc = CreateHTMLDoc(); for (const char16_t* tagName : {u"html", u"body", u"div", u"span", u"select", u"option", u"form"}) { const RefPtr tag = NS_Atomize(tagName); MOZ_RELEASE_ASSERT(tag); const RefPtr 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 tag = NS_Atomize(tagName); MOZ_RELEASE_ASSERT(tag); const RefPtr element = doc->CreateHTMLElement(tag); MOZ_RELEASE_ASSERT(element); EXPECT_EQ(false, HTMLEditUtils::IsContainerNode(*element)) << "IsContainerNode(<" << NS_ConvertUTF16toUTF8(tagName).get() << ">)"; } { const RefPtr text = doc->CreateEmptyTextNode(); MOZ_RELEASE_ASSERT(text); EXPECT_EQ(false, HTMLEditUtils::IsContainerNode(*text)) << "IsContainerNode(Text)"; } { const RefPtr 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 doc = CreateHTMLDoc(); const RefPtr body = doc->GetBody(); MOZ_RELEASE_ASSERT(body); for (const auto& testData : { IsEmptyNodeTest{u"
                  ", "div", {}, true, false}, IsEmptyNodeTest{u"
                  ", "div", {EmptyCheckOption::TreatBlockAsVisible}, true, false}, IsEmptyNodeTest{u"

                  ", "div", {}, true, true}, IsEmptyNodeTest{u"

                  ", "div", {EmptyCheckOption::TreatBlockAsVisible}, true, true}, IsEmptyNodeTest{u"

                  ", "div", {EmptyCheckOption::TreatSingleBRElementAsVisible}, false, false}, IsEmptyNodeTest{u"
                  ", "div", {}, true, false}, IsEmptyNodeTest{u"
                  ", "div", {EmptyCheckOption::TreatCommentAsVisible}, false, false}, IsEmptyNodeTest{u"

                  ", "ul", {}, true, true}, IsEmptyNodeTest{u"

                  ", "ul", {EmptyCheckOption::TreatListItemAsVisible}, false, false}, IsEmptyNodeTest{ u"

                  ", "table", {}, true, true}, IsEmptyNodeTest{u"

                  ", "table", {EmptyCheckOption::TreatTableCellAsVisible}, false, false}, IsEmptyNodeTest{u"
                  abc
                  ", "div", {}, false, false}, IsEmptyNodeTest{ u"

                  ", "div", {}, true, true}, IsEmptyNodeTest{ u"

                  ", "div", {}, true, true}, IsEmptyNodeTest{u"

                  ", "div", {EmptyCheckOption::TreatBlockAsVisible}, false, false}, IsEmptyNodeTest{u"

                  ", "dl", {}, true, true}, IsEmptyNodeTest{u"
                  ", "dl", {EmptyCheckOption::TreatListItemAsVisible}, false, false}, IsEmptyNodeTest{u"

                  ", "dl", {}, true, true}, IsEmptyNodeTest{u"
                  ", "dl", {EmptyCheckOption::TreatListItemAsVisible}, false, false}, // form controls should be always not empty. IsEmptyNodeTest{u"", "input", {}, false, false}, IsEmptyNodeTest{u"", "select", {}, false, false}, IsEmptyNodeTest{u"", "button", {}, false, false}, IsEmptyNodeTest{ u"", "textarea", {}, false, false}, IsEmptyNodeTest{u"", "output", {}, false, false}, IsEmptyNodeTest{ u"", "progress", {}, false, false}, IsEmptyNodeTest{u"", "meter", {}, false, false}, // void elements should be always not empty. IsEmptyNodeTest{u"
                  ", "br", {}, false, false}, IsEmptyNodeTest{u"", "wbr", {}, false, false}, IsEmptyNodeTest{u"", "img", {}, false, false}, // white-spaces should not be treated as visible in block IsEmptyNodeTest{u"
                  ", "div", {}, true, false}, IsEmptyNodeTest{u" ", "span", {}, true, false}, IsEmptyNodeTest{u"a b", "span", {}, false, false}, // sublist's list items and table cells should be treated as visible. IsEmptyNodeTest{u"

                  ", "ul", {}, false, false}, IsEmptyNodeTest{u"

                  ", "ul", {}, false, false}, IsEmptyNodeTest{ u"

                  ", "table", {}, false, false}, IsEmptyNodeTest{u"

                  ", "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 doc = CreateHTMLDoc(); const RefPtr body = doc->GetBody(); MOZ_RELEASE_ASSERT(body); for (const auto& testData : { GetLeafNodeTest{u"
                  ", "div", {}, nullptr}, GetLeafNodeTest{u"

                  ", "div", {}, "div > br"}, GetLeafNodeTest{u"
                  abc
                  ", "div", {}, "div > br"}, GetLeafNodeTest{u"
                  abc
                  ", "div", {}, nullptr, "div", 0u}, GetLeafNodeTest{ u"

                  ", "div", {}, "div > div > br"}, GetLeafNodeTest{u"

                  ", "div", {LeafNodeOption::TreatChildBlockAsLeafNode}, "div > div"}, GetLeafNodeTest{u"


                  ", "div", {}, "div > div + div > br"}, GetLeafNodeTest{u"


                  ", "div", {LeafNodeOption::TreatChildBlockAsLeafNode}, "div > div + div"}, GetLeafNodeTest{u"
                  ", "div", {}, nullptr}, GetLeafNodeTest{u"
                  ", "div", {LeafNodeOption::TreatCommentAsLeafNode}, nullptr, "div", 0u}, GetLeafNodeTest{u"

                  ", "div", {}, "div > br"}, GetLeafNodeTest{u"

                  ", "div", {LeafNodeOption::TreatCommentAsLeafNode}, nullptr, "div", 1u}, GetLeafNodeTest{ u"


                  ", "div", {}, "div > div + div > br"}, GetLeafNodeTest{ u"


                  ", "div", {LeafNodeOption::TreatCommentAsLeafNode}, nullptr, "div", 2u}, GetLeafNodeTest{ u"
                  ", "div", {}, "div > span"}, GetLeafNodeTest{u"
                  ", "div", {LeafNodeOption::IgnoreAnyEmptyInlineContainers}, nullptr}, GetLeafNodeTest{ u"

                  ", "div", {}, "div > span"}, GetLeafNodeTest{u"

                  ", "div", {LeafNodeOption::IgnoreAnyEmptyInlineContainers}, "div > br"}, GetLeafNodeTest{ u"


                  ", "div", {}, "div > span"}, GetLeafNodeTest{ u"


                  ", "div", {LeafNodeOption::IgnoreAnyEmptyInlineContainers}, "div > div + div > br"}, GetLeafNodeTest{u"
                  ", "div", {}, "div > span"}, GetLeafNodeTest{u"
                  ", "div", {LeafNodeOption::TreatCommentAsLeafNode}, nullptr, "div > span", 0u}, GetLeafNodeTest{u"
                  ", "div", {LeafNodeOption::IgnoreAnyEmptyInlineContainers}, nullptr}, GetLeafNodeTest{u"
                  ", "div", {LeafNodeOption::IgnoreAnyEmptyInlineContainers, LeafNodeOption::TreatCommentAsLeafNode}, nullptr, "div > span", 0u}, GetLeafNodeTest{u"

                  ", "div", {}, "div > wbr"}, GetLeafNodeTest{u"

                  ", "div", {LeafNodeOption::IgnoreAnyEmptyInlineContainers}, "div > wbr"}, GetLeafNodeTest{u"

                  ", "div", {LeafNodeOption::IgnoreInvisibleInlineVoidElements}, "div > br"}, GetLeafNodeTest{ u"
                  abc
                  ", "div", {}, nullptr, "div", 1u}, GetLeafNodeTest{u"
                  abc
                  ", "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 doc = CreateHTMLDoc(); const RefPtr body = doc->GetBody(); MOZ_RELEASE_ASSERT(body); for (const auto& testData : { GetLeafNodeTest{u"
                  ", "div", {}, nullptr}, GetLeafNodeTest{u"

                  ", "div", {}, "div > br"}, GetLeafNodeTest{ u"
                  abc
                  ", "div", {}, nullptr, "div", 0u}, GetLeafNodeTest{u"
                  abc
                  ", "div", {}, nullptr, "div", 0u}, GetLeafNodeTest{ u"

                  ", "div", {}, "div > div > br"}, GetLeafNodeTest{u"

                  ", "div", {LeafNodeOption::TreatChildBlockAsLeafNode}, "div > div"}, GetLeafNodeTest{u"


                  ", "div", {}, "div > div > br"}, GetLeafNodeTest{u"


                  ", "div", {LeafNodeOption::TreatChildBlockAsLeafNode}, "div > div"}, GetLeafNodeTest{u"
                  ", "div", {}, nullptr}, GetLeafNodeTest{u"
                  ", "div", {LeafNodeOption::TreatCommentAsLeafNode}, nullptr, "div", 0u}, GetLeafNodeTest{u"

                  ", "div", {}, "div > br"}, GetLeafNodeTest{u"

                  ", "div", {LeafNodeOption::TreatCommentAsLeafNode}, nullptr, "div", 0u}, GetLeafNodeTest{ u"


                  ", "div", {}, "div > div > br"}, GetLeafNodeTest{ u"


                  ", "div", {LeafNodeOption::TreatCommentAsLeafNode}, nullptr, "div", 0u}, GetLeafNodeTest{ u"
                  ", "div", {}, "div > span"}, GetLeafNodeTest{u"
                  ", "div", {LeafNodeOption::IgnoreAnyEmptyInlineContainers}, nullptr}, GetLeafNodeTest{u"

                  ", "div", {LeafNodeOption::IgnoreAnyEmptyInlineContainers}, "div > br"}, GetLeafNodeTest{ u"


                  ", "div", {}, "div > span"}, GetLeafNodeTest{ u"


                  ", "div", {LeafNodeOption::IgnoreAnyEmptyInlineContainers}, "div > div > br"}, GetLeafNodeTest{u"
                  ", "div", {}, "div > span"}, GetLeafNodeTest{u"
                  ", "div", {LeafNodeOption::TreatCommentAsLeafNode}, nullptr, "div > span", 0u}, GetLeafNodeTest{u"
                  ", "div", {LeafNodeOption::IgnoreAnyEmptyInlineContainers}, nullptr}, GetLeafNodeTest{u"
                  ", "div", {LeafNodeOption::IgnoreAnyEmptyInlineContainers, LeafNodeOption::TreatCommentAsLeafNode}, nullptr, "div > span", 0u}, GetLeafNodeTest{u"

                  ", "div", {}, "div > wbr"}, GetLeafNodeTest{u"

                  ", "div", {LeafNodeOption::IgnoreAnyEmptyInlineContainers}, "div > wbr"}, GetLeafNodeTest{u"

                  ", "div", {LeafNodeOption::IgnoreInvisibleInlineVoidElements}, "div > br"}, GetLeafNodeTest{ u"
                  abc
                  ", "div", {}, nullptr, "div", 0u}, GetLeafNodeTest{u"
                  abc
                  ", "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 doc = CreateHTMLDoc(); const RefPtr body = doc->GetBody(); MOZ_RELEASE_ASSERT(body); for (const auto& testData : { GetLeafNodeTest{u"


                  ", "div", {}, "p"}, GetLeafNodeTest{ u"


                  ", "div", {}, "p"}, GetLeafNodeTest{u"


                  ", "div", {LeafNodeOption::TreatCommentAsLeafNode}, nullptr, "body", 1u}, GetLeafNodeTest{ u"


                  ", "div", {}, "span > br"}, GetLeafNodeTest{u"


                  ", "div", {}, "span > br"}, GetLeafNodeTest{u"


                  ", "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 doc = CreateHTMLDoc(); const RefPtr body = doc->GetBody(); MOZ_RELEASE_ASSERT(body); for (const auto& testData : { GetLeafNodeTest{u"



                  ", "div", {}, "p"}, GetLeafNodeTest{ u"



                  ", "div", {}, "p"}, GetLeafNodeTest{u"



                  ", "div", {LeafNodeOption::TreatCommentAsLeafNode}, nullptr, "body", 1u}, GetLeafNodeTest{ u"

                  ", "div", {}, "span > br"}, GetLeafNodeTest{u"

                  ", "div", {}, "span > br"}, GetLeafNodeTest{u"

                  ", "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 doc = CreateHTMLDoc(); const RefPtr body = doc->GetBody(); MOZ_RELEASE_ASSERT(body); for (const auto& testData : { GetLeafNodeTest{u"


                  ", "div", {}, "p > br"}, GetLeafNodeTest{ u"


                  ", "div", {}, "p > br"}, GetLeafNodeTest{u"


                  ", "div", {LeafNodeOption::TreatCommentAsLeafNode}, nullptr, "body", 1u}, GetLeafNodeTest{ u"


                  ", "div", {}, "span > br"}, GetLeafNodeTest{u"


                  ", "div", {}, "span > br"}, GetLeafNodeTest{u"


                  ", "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 doc = CreateHTMLDoc(); const RefPtr body = doc->GetBody(); MOZ_RELEASE_ASSERT(body); for (const auto& testData : { GetLeafNodeTest{u"



                  ", "div", {}, "p > br"}, GetLeafNodeTest{ u"



                  ", "div", {}, "p > br"}, GetLeafNodeTest{u"



                  ", "div", {LeafNodeOption::TreatCommentAsLeafNode}, nullptr, "body", 1u}, GetLeafNodeTest{ u"

                  ", "div", {}, "span > br"}, GetLeafNodeTest{u"

                  ", "div", {}, "span > br"}, GetLeafNodeTest{u"

                  ", "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 doc = CreateHTMLDoc(); const RefPtr body = doc->GetBody(); MOZ_RELEASE_ASSERT(body); for (const auto& testData : { GetLeafNodeTest{u"


                  ", "div > p", {}, nullptr}, GetLeafNodeTest{u"



                  ", "div > p + p", {}, "div > p"}, GetLeafNodeTest{u"



                  ", "div p + p", {}, "div > p"}, GetLeafNodeTest{u"



                  ", "div > p + p", {LeafNodeOption::TreatCommentAsLeafNode}, nullptr, "div", 1u}, GetLeafNodeTest{u"



                  ", "div > p + p", {}, nullptr, "div", 1u}, GetLeafNodeTest{u"



                  ", "div > p + p", {LeafNodeOption::IgnoreInvisibleText}, "div > p"}, GetLeafNodeTest{ u"




                  ", "div > p + p + p", {}, "div > p + p"}, GetLeafNodeTest{ u"




                  ", "div > p + p + p", {LeafNodeOption::IgnoreNonEditableNode}, "div > p"}, GetLeafNodeTest{ u"
                  abcdef
                  ", "div > i", {}, "div > s"}, GetLeafNodeTest{ u"
                  abcdef
                  ", "div > i", {LeafNodeOption::IgnoreInvisibleEmptyInlineContainers}, "div > b"}, GetLeafNodeTest{ u"
                  abcdef
                  ", "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 doc = CreateHTMLDoc(); const RefPtr body = doc->GetBody(); MOZ_RELEASE_ASSERT(body); for (const auto& testData : { GetLeafNodeTest{u"


                  ", "div > p", {}, nullptr}, GetLeafNodeTest{u"



                  ", "div > p", {}, "div > p + p"}, GetLeafNodeTest{u"



                  ", "div > p", {}, "div > p + p"}, GetLeafNodeTest{u"



                  ", "div > p", {LeafNodeOption::TreatCommentAsLeafNode}, nullptr, "div", 1u}, GetLeafNodeTest{u"



                  ", "div > p", {}, nullptr, "div", 1u}, GetLeafNodeTest{u"



                  ", "div > p", {LeafNodeOption::IgnoreInvisibleText}, "div > p + p"}, GetLeafNodeTest{ u"




                  ", "div p + p", {}, "div > p + p + p"}, GetLeafNodeTest{ u"




                  ", "div p", {LeafNodeOption::IgnoreNonEditableNode}, "div > p + p + p"}, GetLeafNodeTest{ u"
                  abcdef
                  ", "div > b", {}, "div > s"}, GetLeafNodeTest{ u"
                  abcdef
                  ", "div > b", {LeafNodeOption::IgnoreInvisibleEmptyInlineContainers}, "div > i"}, GetLeafNodeTest{ u"
                  abcdef
                  ", "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 doc = CreateHTMLDoc(); const RefPtr body = doc->GetBody(); MOZ_RELEASE_ASSERT(body); for (const auto& testData : { GetLeafNodeTest{u"
                  ", "div", {}, nullptr}, GetLeafNodeTest{u"


                  ", "div", {}, "div > p"}, GetLeafNodeTest{ u"


                  ", "div", {}, "div > p"}, GetLeafNodeTest{u"


                  ", "div", {LeafNodeOption::TreatCommentAsLeafNode}, nullptr, "div", 0u}, GetLeafNodeTest{ u"


                  ", "div", {}, nullptr, "div", 0u}, GetLeafNodeTest{u"


                  ", "div", {LeafNodeOption::IgnoreInvisibleText}, "div > p"}, GetLeafNodeTest{ u"



                  ", "div", {}, "div > p"}, GetLeafNodeTest{ u"



                  ", "div", {LeafNodeOption::IgnoreNonEditableNode}, "div > p + p"}, GetLeafNodeTest{u"
                  def
                  ", "div", {}, "div > s"}, GetLeafNodeTest{ u"
                  def
                  ", "div", {LeafNodeOption::IgnoreInvisibleEmptyInlineContainers}, "div > i"}, GetLeafNodeTest{ u"
                  def
                  ", "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 doc = CreateHTMLDoc(); const RefPtr body = doc->GetBody(); MOZ_RELEASE_ASSERT(body); for (const auto& testData : { GetLeafNodeTest{u"
                  ", "div", {}, nullptr}, GetLeafNodeTest{u"


                  ", "div", {}, "div > p"}, GetLeafNodeTest{ u"


                  ", "div", {}, "div > p"}, GetLeafNodeTest{u"


                  ", "div", {LeafNodeOption::TreatCommentAsLeafNode}, nullptr, "div", 1u}, GetLeafNodeTest{ u"


                  ", "div", {}, nullptr, "div", 1u}, GetLeafNodeTest{u"


                  ", "div", {LeafNodeOption::IgnoreInvisibleText}, "div > p"}, GetLeafNodeTest{u"



                  ", "div", {}, "div > p + p"}, GetLeafNodeTest{u"



                  ", "div", {LeafNodeOption::IgnoreNonEditableNode}, "div > p"}, GetLeafNodeTest{u"
                  def
                  ", "div", {}, "div > s"}, GetLeafNodeTest{ u"
                  def
                  ", "div", {LeafNodeOption::IgnoreInvisibleEmptyInlineContainers}, "div > i"}, GetLeafNodeTest{ u"
                  def
                  ", "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 doc = CreateHTMLDoc(); const RefPtr body = doc->GetBody(); MOZ_RELEASE_ASSERT(body); for (const auto& testData : { GetLeafNodeTest{u"
                  ", "div", {}, "div"}, GetLeafNodeTest{u"

                  ", "div", {}, "div"}, GetLeafNodeTest{ u"

                  ", "div", {}, "div > div"}, GetLeafNodeTest{ u"

                  ", "div", {}, "div"}, GetLeafNodeTest{u"

                  ", "div", {LeafNodeOption::TreatCommentAsLeafNode}, "div"}, GetLeafNodeTest{u"

                  ", "div", {}, "div > div"}, GetLeafNodeTest{u"

                  ", "div", {LeafNodeOption::TreatCommentAsLeafNode}, "div > div"}, GetLeafNodeTest{u"

                  ", "div", {}, "div > div"}, GetLeafNodeTest{u"

                  ", "div", {LeafNodeOption::TreatCommentAsLeafNode}, "div"}, GetLeafNodeTest{u"

                  ", "div", {}, "div"}, GetLeafNodeTest{u"

                  ", "div", {LeafNodeOption::IgnoreInvisibleText}, "div"}, GetLeafNodeTest{ u"

                  ", "div", {}, "div > div"}, GetLeafNodeTest{u"

                  ", "div", {LeafNodeOption::IgnoreInvisibleText}, "div > div"}, GetLeafNodeTest{u"

                  ", "div", {}, "div"}, GetLeafNodeTest{u"

                  ", "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 char* const mContainer; const Maybe 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 doc = CreateHTMLDoc(); const RefPtr body = doc->GetBody(); MOZ_RELEASE_ASSERT(body); for (const auto& testData : { LineBreakBeforeBlockBoundaryTest{u"
                  abc
                  ", "div", Some(0), 3, false}, LineBreakBeforeBlockBoundaryTest{u"
                  abc
                  ", "div", Nothing{}, 1, false}, LineBreakBeforeBlockBoundaryTest{u"

                  ", "div", Nothing{}, 0, false}, LineBreakBeforeBlockBoundaryTest{u"

                  ", "div", Nothing{}, 1, true}, LineBreakBeforeBlockBoundaryTest{ u"

                  ", "div", Some(1), 2, true}, LineBreakBeforeBlockBoundaryTest{ u"

                  ", "div", Nothing{}, 2, true}, LineBreakBeforeBlockBoundaryTest{ u"


                  ", "div", Nothing{}, 1, false}, LineBreakBeforeBlockBoundaryTest{ u"

                  abc

                  ", "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 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; struct MOZ_STACK_CLASS BRElementPositionTest final { const char16_t* const mInnerHTML; const char* const mBRSelector; const LineBreakPosition mExpectedPosition; const bool mIsSignificant; const bool mIsEmptyBlockPadding; friend std::ostream& operator<<(std::ostream& aStream, const BRElementPositionTest& aTest) { return aStream << "
                  of \"" << aTest.mBRSelector << "\" in \"" << NS_ConvertUTF16toUTF8(aTest.mInnerHTML).get() << "\""; } }; TEST(HTMLEditUtilsTest, BRElementPosition) { const RefPtr doc = CreateHTMLDoc(); const RefPtr body = doc->GetBody(); MOZ_RELEASE_ASSERT(body); for (const auto& testData : { BRElementPositionTest{u"

                  ", "div > br", {LineBreakIs::FollowedByCurrentBlockBoundary, LineBreakIs::FollowingCurrentBlockBoundary}, true, true}, BRElementPositionTest{u"


                  ", "div > br", {LineBreakIs::FollowedByLineBreak, LineBreakIs::FollowingCurrentBlockBoundary}, true, false}, BRElementPositionTest{u"


                  ", "div > br + br", {LineBreakIs::FollowedByCurrentBlockBoundary, LineBreakIs::FollowingLineBreak}, true, false}, BRElementPositionTest{u"


                  ", "div > br", {LineBreakIs::FollowedByOtherBlockBoundary, LineBreakIs::FollowingCurrentBlockBoundary}, true, false}, BRElementPositionTest{u"

                  ", "div > div + br", {LineBreakIs::FollowedByCurrentBlockBoundary, LineBreakIs::FollowingOtherBlockBoundary}, true, false}, BRElementPositionTest{u"

                  ", "div > br", {LineBreakIs::FollowedByCurrentBlockBoundary, LineBreakIs::FollowingCurrentBlockBoundary}, true, true}, BRElementPositionTest{u"
                  abc
                  ", "div > br", {LineBreakIs::FollowedByCurrentBlockBoundary}, false, false}, BRElementPositionTest{u"

                  abc
                  ", "div > br", {LineBreakIs::FollowingCurrentBlockBoundary}, true, false}, BRElementPositionTest{u"

                  ", "div > br", {LineBreakIs::FollowedByCurrentBlockBoundary, LineBreakIs::FollowingCurrentBlockBoundary}, true, true}, BRElementPositionTest{u"

                  ", "div > br", {LineBreakIs::FollowedByCurrentBlockBoundary, LineBreakIs::FollowingCurrentBlockBoundary}, true, true}, BRElementPositionTest{ u"

                  ", "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 mContainerOffset; const char* const mStartContainer; const uint32_t mOffsetInContainer; struct MOZ_STACK_CLASS ExpectedData { const char* const mBlockElement; const char* const 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 doc = CreateHTMLDoc(); const RefPtr body = doc->GetBody(); MOZ_RELEASE_ASSERT(body); for (const auto& testData : { ScanVisibleThingTest{u"

                  ", Nothing{}, "div", 0u, {nullptr, "div > br"}, {"div", "div > br"}}, ScanVisibleThingTest{u"

                  ", Nothing{}, "div", 1u, {"div", nullptr}, {"div", nullptr}}, ScanVisibleThingTest{u"
                  ABC
                  ", Some(0u), "div", 0u, {nullptr, nullptr}, {nullptr, nullptr}}, ScanVisibleThingTest{u"
                  ABC
                  ", Some(0u), "div", 3u, {"div", nullptr}, {"div", nullptr}}, ScanVisibleThingTest{u"
                  ABC
                  ", Some(0u), "div", 0u, {nullptr, nullptr}, {nullptr, nullptr}}, ScanVisibleThingTest{u"
                  ABC
                  ", Some(0u), "div", 0u, {nullptr, nullptr}, {nullptr, nullptr}}, ScanVisibleThingTest{u"
                  ABC
                  DEF
                  ", Some(0u), "div", 3u, {nullptr, "div > br"}, {nullptr, "div > br"}}, ScanVisibleThingTest{u"
                  ABC
                  DEF
                  ", Nothing{}, "div", 1u, {nullptr, "div > br"}, {nullptr, "div > br"}}, ScanVisibleThingTest{u"


                  ", Nothing{}, "div", 0u, {nullptr, "div > br"}, {nullptr, "div > br"}}, ScanVisibleThingTest{u"


                  ", Nothing{}, "p", 0u, {nullptr, "p > br"}, {"p", "p > br"}}, ScanVisibleThingTest{u"


                  ", Nothing{}, "p", 1u, {"p", nullptr}, {"p", nullptr}}, ScanVisibleThingTest{u"


                  ", Nothing{}, "div", 0u, {nullptr, "div > br"}, {nullptr, "div > br"}}, ScanVisibleThingTest{u"


                  ", Nothing{}, "div", 1u, {nullptr, "div > br + br"}, {nullptr, "div > br + br"}}, ScanVisibleThingTest{u"

                  ", Nothing{}, "div", 0u, {nullptr, "div > br"}, {"div", "div > br"}}, ScanVisibleThingTest{u"

                  ", Nothing{}, "div", 3u, {"div", nullptr}, {"div", nullptr}}, ScanVisibleThingTest{ u"
                  ABC
                  ", Some(0u), "div", 3u, {"div", "div > br"}, {"div", "div > br"}}, ScanVisibleThingTest{ u"
                  AB
                  ", 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
                  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
                  (" << 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
                  skipped? (" << paddingForEmptyBlock << "): " << testData; const Element* const expectedBR = body->QuerySelector( nsDependentCString(expectedData.mBRElement), IgnoreErrors()); MOZ_ASSERT(expectedBR); EXPECT_EQ(nextThing.BRElementPtr(), expectedBR) << "Skipped
                  (" << paddingForEmptyBlock << "): " << testData; continue; } EXPECT_EQ(nextThing.ReachedBlockBoundary(), false) << "ReachedBlockBoundary(" << paddingForEmptyBlock << "): " << testData; EXPECT_EQ(nextThing.ReachedBRElement(), false) << "ReachedBRElement(" << paddingForEmptyBlock << "): " << testData; } } } } // namespace mozilla