A form widget element does not mean it's a replaced element. So, it should check each tag name instead. Additionally, it's the last user of `HTMLEditUtils::IsFormWidgetElement()` so that we can get rid of it. Differential Revision: https://phabricator.services.mozilla.com/D295731
2712 lines
117 KiB
C++
2712 lines
117 KiB
C++
/* 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", {}, true, 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
|