tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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:
Meditor/libeditor/AutoClonedRangeArray.cpp | 7+++----
Meditor/libeditor/EditorBase.cpp | 1-
Meditor/libeditor/HTMLEditSubActionHandler.cpp | 5++---
Meditor/libeditor/HTMLEditUtils.h | 80++++++++++++++++++++++++++++---------------------------------------------------
Meditor/libeditor/HTMLEditor.cpp | 1-
Meditor/libeditor/HTMLEditorDeleteHandler.cpp | 1-
Meditor/libeditor/HTMLEditorInsertParagraphHandler.cpp | 1-
Meditor/libeditor/HTMLEditorState.cpp | 1-
Meditor/libeditor/HTMLStyleEditor.cpp | 1-
Meditor/libeditor/WhiteSpaceVisibilityKeeper.cpp | 1-
Meditor/libeditor/gtest/TestHTMLEditUtils.cpp | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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;