- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 93 %
- : 0 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 0 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 0 %
- : 93 %
- : 0 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 0 %
- : 93 %
- : 93 %
- : 93 %
- : 0 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 92 %
- : 92 %
- : 92 %
- : 92 %
- : 92 %
- : 92 %
- : 92 %
- : 92 %
- : 92 %
- : 92 %
- : 0 %
- : 0 %
- : 81 %
- : 81 %
- : 81 %
- : 0 %
- : 81 %
- : 0 %
- : 0 %
- : 0 %
- : 81 %
- : 0 %
- : 81 %
- : 0 %
- : 81 %
- : 0 %
- : 0 %
- : 81 %
- : 0 %
- : 0 %
- : 81 %
- : 0 %
- : 0 %
- : 0 %
- : 81 %
- : 0 %
- : 0 %
- : 81 %
- : 81 %
- : 0 %
- : 81 %
- : 81 %
- : 81 %
- : 0 %
- : 81 %
- : 81 %
- : 81 %
- : 81 %
- : 81 %
- : 81 %
- : 0 %
- : 81 %
- : 81 %
- : 0 %
- : 81 %
- : 0 %
- : 81 %
- : 81 %
- : 0 %
- : 0 %
- : 78 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 78 %
- : 78 %
- : 0 %
- : 78 %
- : 78 %
- : 78 %
- : 0 %
- : 0 %
- : 83 %
- : 0 %
- : 0 %
- : 83 %
- : 0 %
- : 83 %
- : 83 %
- : 83 %
- : 83 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 83 %
- : 83 %
- : 83 %
- : 0 %
- : 83 %
- : 0 %
- : 0 %
- : 83 %
- : 83 %
- : 83 %
- : 83 %
- : 0 %
- : 83 %
- : 91 %
- : 91 %
- : 0 %
- : 0 %
- : 0 %
- : 91 %
- : 0 %
- : 0 %
- : 0 %
- : 91 %
- : 91 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 91 %
- : 0 %
- : 0 %
- : 91 %
- : 91 %
Source code
Revision control
Copy as Markdown
Other Tools
/* 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 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<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 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<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", {}, false, false},
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", {}, false, false},
IsEmptyNodeTest{u"<select></select>", "select", {}, false, false},
IsEmptyNodeTest{u"<button></button>", "button", {}, false, false},
IsEmptyNodeTest{
u"<textarea></textarea>", "textarea", {}, false, false},
IsEmptyNodeTest{u"<output></output>", "output", {}, false, false},
IsEmptyNodeTest{
u"<progress></progress>", "progress", {}, false, false},
IsEmptyNodeTest{u"<meter></meter>", "meter", {}, false, false},
// void elements should be always not empty.
IsEmptyNodeTest{u"<br>", "br", {}, false, false},
IsEmptyNodeTest{u"<wbr>", "wbr", {}, false, false},
IsEmptyNodeTest{u"<img>", "img", {}, false, false},
// 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", {}, false, false},
// 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 char* const 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), 3, false},
LineBreakBeforeBlockBoundaryTest{u"<div contenteditable>abc</div>",
"div", Nothing{}, 1, false},
LineBreakBeforeBlockBoundaryTest{u"<div contenteditable><br></div>",
"div", Nothing{}, 0, false},
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 char* const 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 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<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