commit 5ccf392dce86930b66101bc463f91fd0530915d1
parent 12eb928cb50025afa509f0c2a9ff85e6adc37c36
Author: Masayuki Nakano <masayuki@d-toybox.com>
Date: Wed, 7 Jan 2026 01:10:21 +0000
Bug 1998077 - part 7: Reimplement `HTMLEditUtils::GetInclusiveDeepestFirstChildWhichHasOneChild` with `LeafNodeOptions` r=m_kato
Similarly, we can implement it with `LeafNodeOptions` to ignore
unnecessary nodes. Then, we can get rid of `WalkTreeOption` and
`WalkTreeOptions` completely.
Differential Revision: https://phabricator.services.mozilla.com/D277524
Diffstat:
11 files changed, 99 insertions(+), 66 deletions(-)
diff --git a/editor/libeditor/AutoClonedRangeArray.cpp b/editor/libeditor/AutoClonedRangeArray.cpp
@@ -1061,11 +1061,10 @@ nsresult AutoClonedRangeArray::CollectEditTargetNodes(
if (aOutArrayOfContents.Length() != 1) {
break;
}
- Element* deepestDivBlockquoteOrListElement =
+ Element* const deepestDivBlockquoteOrListElement =
HTMLEditUtils::GetInclusiveDeepestFirstChildWhichHasOneChild(
- aOutArrayOfContents[0],
- {HTMLEditUtils::WalkTreeOption::IgnoreNonEditableNode},
- BlockInlineCheck::Unused, nsGkAtoms::div, nsGkAtoms::blockquote,
+ aOutArrayOfContents[0], {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::Auto, nsGkAtoms::div, nsGkAtoms::blockquote,
nsGkAtoms::ul, nsGkAtoms::ol, nsGkAtoms::dl);
if (!deepestDivBlockquoteOrListElement) {
break;
diff --git a/editor/libeditor/EditorBase.cpp b/editor/libeditor/EditorBase.cpp
@@ -145,7 +145,6 @@ using namespace widget;
using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
-using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
static LazyLogModule gEventLog("EditorEvent");
static LazyLogModule gHTMLEditorEditActionStartLog("HTMLEditorEditActionStart");
diff --git a/editor/libeditor/HTMLEditSubActionHandler.cpp b/editor/libeditor/HTMLEditSubActionHandler.cpp
@@ -82,7 +82,6 @@ using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
using LeafNodeOptions = HTMLEditUtils::LeafNodeOptions;
using WalkTextOption = HTMLEditUtils::WalkTextOption;
using WalkTreeDirection = HTMLEditUtils::WalkTreeDirection;
-using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
/********************************************************
* first some helpful functors we will use
@@ -3213,9 +3212,9 @@ HTMLEditor::AutoListElementCreator::WrapContentNodesIntoNewListElements(
// if there is only one node in the array, and it is a list, div, or
// blockquote, then look inside of it until we find inner list or content.
if (aArrayOfContents.Length() == 1) {
- if (Element* deepestDivBlockquoteOrListElement =
+ if (Element* const deepestDivBlockquoteOrListElement =
HTMLEditUtils::GetInclusiveDeepestFirstChildWhichHasOneChild(
- aArrayOfContents[0], {WalkTreeOption::IgnoreNonEditableNode},
+ aArrayOfContents[0], {LeafNodeOption::IgnoreNonEditableNode},
BlockInlineCheck::UseHTMLDefaultStyle, nsGkAtoms::div,
nsGkAtoms::blockquote, nsGkAtoms::ul, nsGkAtoms::ol,
nsGkAtoms::dl)) {
diff --git a/editor/libeditor/HTMLEditUtils.h b/editor/libeditor/HTMLEditUtils.h
@@ -1990,14 +1990,30 @@ class HTMLEditUtils final {
return GetTableCellElementIfOnlyOneSelected(*firstRange);
}
- enum class WalkTreeOption {
- IgnoreNonEditableNode, // Ignore non-editable nodes and their children.
- IgnoreDataNodeExceptText, // Ignore data nodes which are not text node.
- IgnoreWhiteSpaceOnlyText, // Ignore text nodes having only white-spaces.
- StopAtBlockBoundary, // Stop waking the tree at a block boundary.
- };
- using WalkTreeOptions = EnumSet<WalkTreeOption>;
+ private:
+ static uint32_t CountMeaningfulChildren(const nsINode& aNode,
+ const LeafNodeOptions& aOptions,
+ BlockInlineCheck aBlockInlineCheck) {
+ uint32_t count = 0;
+ for (nsIContent* child = aNode.GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ const LeafNodeType leafNodeType = HTMLEditUtils::GetLeafNodeType(
+ *child, aOptions, aBlockInlineCheck, IgnoreChildren::No);
+ if (leafNodeType == LeafNodeType::Ignore) {
+ continue;
+ }
+ if (leafNodeType == LeafNodeType::NonEmptyContainer) {
+ if (!HTMLEditUtils::GetFirstLeafContent(*child, aOptions,
+ aBlockInlineCheck)) {
+ continue;
+ }
+ }
+ ++count;
+ }
+ return count;
+ }
+ public:
/**
* GetInclusiveFirstChildWhichHasOneChild() returns the deepest element whose
* tag name is one of `aFirstElementName` and `aOtherElementNames...` if and
@@ -2012,7 +2028,7 @@ class HTMLEditUtils final {
*/
template <typename FirstElementName, typename... OtherElementNames>
static Element* GetInclusiveDeepestFirstChildWhichHasOneChild(
- const nsINode& aNode, const WalkTreeOptions& aOptions,
+ const nsINode& aNode, const LeafNodeOptions& aOptions,
BlockInlineCheck aBlockInlineCheck, FirstElementName aFirstElementName,
OtherElementNames... aOtherElementNames) {
if (!aNode.IsElement()) {
@@ -2022,11 +2038,10 @@ class HTMLEditUtils final {
for (nsIContent* content = const_cast<nsIContent*>(aNode.AsContent());
content && content->IsElement() &&
content->IsAnyOfHTMLElements(aFirstElementName, aOtherElementNames...);
- // XXX Why do we scan only the first child of every element? If it's
- // not editable, why do we ignore it when aOptions specifies so.
- content = content->GetFirstChild()) {
- if (HTMLEditUtils::CountChildren(*content, aOptions, aBlockInlineCheck) !=
- 1) {
+ content = HTMLEditUtils::GetFirstChild(*content, aOptions,
+ aBlockInlineCheck)) {
+ if (HTMLEditUtils::CountMeaningfulChildren(*content, aOptions,
+ aBlockInlineCheck) != 1) {
return content->AsElement();
}
parentElement = content->AsElement();
@@ -2839,45 +2854,6 @@ class HTMLEditUtils final {
return !cannotCrossBoundary;
}
- static bool IsContentIgnored(const nsIContent& aContent,
- const WalkTreeOptions& aOptions) {
- if (aOptions.contains(WalkTreeOption::IgnoreNonEditableNode) &&
- !EditorUtils::IsEditableContent(aContent,
- EditorUtils::EditorType::HTML)) {
- return true;
- }
- if (aOptions.contains(WalkTreeOption::IgnoreDataNodeExceptText) &&
- !EditorUtils::IsElementOrText(aContent)) {
- return true;
- }
- if (aOptions.contains(WalkTreeOption::IgnoreWhiteSpaceOnlyText) &&
- aContent.IsText() &&
- const_cast<Text*>(aContent.AsText())->TextIsOnlyWhitespace()) {
- return true;
- }
- return false;
- }
-
- static uint32_t CountChildren(const nsINode& aNode,
- const WalkTreeOptions& aOptions,
- BlockInlineCheck aBlockInlineCheck) {
- uint32_t count = 0;
- for (nsIContent* child = aNode.GetFirstChild(); child;
- child = child->GetNextSibling()) {
- if (HTMLEditUtils::IsContentIgnored(*child, aOptions)) {
- continue;
- }
- if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
- HTMLEditUtils::IsBlockElement(
- *child,
- UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck))) {
- break;
- }
- ++count;
- }
- return count;
- }
-
/**
* GetElementOfImmediateBlockBoundary() returns a block element if its
* block boundary and aContent may be first visible thing before/after the
diff --git a/editor/libeditor/HTMLEditor.cpp b/editor/libeditor/HTMLEditor.cpp
@@ -102,7 +102,6 @@ LazyLogModule gHTMLEditorFocusLog("HTMLEditorFocus");
using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
using LeafNodeOptions = HTMLEditUtils::LeafNodeOptions;
-using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
// Some utilities to handle overloading of "A" tag for link and named anchor.
static bool IsLinkTag(const nsAtom& aTagName) {
diff --git a/editor/libeditor/HTMLEditorDeleteHandler.cpp b/editor/libeditor/HTMLEditorDeleteHandler.cpp
@@ -63,7 +63,6 @@ using InvisibleWhiteSpaces = HTMLEditUtils::InvisibleWhiteSpaces;
using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
using ScanLineBreak = HTMLEditUtils::ScanLineBreak;
using TableBoundary = HTMLEditUtils::TableBoundary;
-using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
static LazyLogModule gOneLineMoverLog("AutoMoveOneLineHandler");
diff --git a/editor/libeditor/HTMLEditorInsertParagraphHandler.cpp b/editor/libeditor/HTMLEditorInsertParagraphHandler.cpp
@@ -51,7 +51,6 @@ using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
using EmptyCheckOptions = HTMLEditUtils::EmptyCheckOptions;
using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
using LeafNodeOptions = HTMLEditUtils::LeafNodeOptions;
-using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
Result<EditActionResult, nsresult>
HTMLEditor::InsertParagraphSeparatorAsSubAction(const Element& aEditingHost) {
diff --git a/editor/libeditor/HTMLEditorState.cpp b/editor/libeditor/HTMLEditorState.cpp
@@ -39,7 +39,6 @@ using namespace dom;
using EditorType = EditorUtils::EditorType;
using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
-using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
/*****************************************************************************
* ListElementSelectionState
diff --git a/editor/libeditor/HTMLStyleEditor.cpp b/editor/libeditor/HTMLStyleEditor.cpp
@@ -65,7 +65,6 @@ using EditablePointOption = HTMLEditUtils::EditablePointOption;
using EditablePointOptions = HTMLEditUtils::EditablePointOptions;
using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
-using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
template nsresult HTMLEditor::SetInlinePropertiesAsSubAction(
const AutoTArray<EditorInlineStyleAndValue, 1>& aStylesToSet,
diff --git a/editor/libeditor/WhiteSpaceVisibilityKeeper.cpp b/editor/libeditor/WhiteSpaceVisibilityKeeper.cpp
@@ -33,7 +33,6 @@ namespace mozilla {
using namespace dom;
using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
-using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
Result<EditorDOMPoint, nsresult>
WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement(
diff --git a/editor/libeditor/gtest/TestHTMLEditUtils.cpp b/editor/libeditor/gtest/TestHTMLEditUtils.cpp
@@ -2199,6 +2199,72 @@ TEST(HTMLEditUtilsTest, GetLastChild)
}
}
+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;