tor-browser

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

commit 58b4243d101d51384ac4713b5c9916328af95733
parent dd46967eb4b7cb89e09a713c664548c7123b3fb4
Author: Masayuki Nakano <masayuki@d-toybox.com>
Date:   Wed,  7 Jan 2026 01:10:21 +0000

Bug 1998077 - part 5: Replace the remaining `HTMLEditUtils::Get(Next|Previous)Content()` with new ones based on `Get(Next|Previous)LeafNodeOr(Next|Previous)BlockElement()` r=m_kato

`HTMLEditUtils::GetNextContent()` and
`HTMLEditUtils::GetPreviousContent()` are wrappers of
`HTMLEditUtils::GetAdjacentContent()` and
`HTMLEditUtils::GetAdjacentLeafContent()`.  The behavior is similar to
`HTMLEditUtils::GetNextLeafContentOrNextBlockElement()` or
`HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement()` but
they won't stop at block inclusive ancestor or a block sibling of an
inclusive ancestor.  Therefore, we can get rid of them and use the new
and better implementation.

Differential Revision: https://phabricator.services.mozilla.com/D277522

Diffstat:
Meditor/libeditor/EditorBase.cpp | 39+++++++++++++++++++++++++--------------
Meditor/libeditor/HTMLEditSubActionHandler.cpp | 18+++++++++---------
Meditor/libeditor/HTMLEditUtils.cpp | 387++++++++++++++++---------------------------------------------------------------
Meditor/libeditor/HTMLEditUtils.h | 233+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Meditor/libeditor/HTMLEditor.cpp | 4++--
Meditor/libeditor/HTMLEditorDeleteHandler.cpp | 59+++++++++++++++++++++++++++++++----------------------------
Meditor/libeditor/HTMLEditorInsertParagraphHandler.cpp | 12++++++------
Meditor/libeditor/HTMLEditorState.cpp | 7++++---
Meditor/libeditor/gtest/TestHTMLEditUtils.cpp | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
9 files changed, 371 insertions(+), 478 deletions(-)

diff --git a/editor/libeditor/EditorBase.cpp b/editor/libeditor/EditorBase.cpp @@ -144,6 +144,7 @@ using namespace dom; using namespace widget; using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption; +using LeafNodeOption = HTMLEditUtils::LeafNodeOption; using WalkTreeOption = HTMLEditUtils::WalkTreeOption; static LazyLogModule gEventLog("EditorEvent"); @@ -4417,14 +4418,19 @@ EditorBase::CreateTransactionForCollapsedRange( if (aHowToHandleCollapsedRange == HowToHandleCollapsedRange::ExtendBackward && point.IsStartOfContainer()) { MOZ_ASSERT(IsHTMLEditor()); + if (MOZ_UNLIKELY(!point.IsInContentNode())) { + NS_WARNING("There was no editable content before the collapsed range"); + return nullptr; + } // We're backspacing from the beginning of a node. Delete the last thing // of previous editable content. - nsIContent* previousEditableContent = HTMLEditUtils::GetPreviousContent( - *point.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode}, + nsIContent* previousEditableContent = HTMLEditUtils::GetPreviousLeafContent( + *point.ContainerAs<nsIContent>(), + {LeafNodeOption::IgnoreNonEditableNode}, IsTextEditor() ? BlockInlineCheck::UseHTMLDefaultStyle : BlockInlineCheck::UseComputedDisplayOutsideStyle, anonymousDivOrEditingHost); - if (!previousEditableContent) { + if (MOZ_UNLIKELY(!previousEditableContent)) { NS_WARNING("There was no editable content before the collapsed range"); return nullptr; } @@ -4467,14 +4473,19 @@ EditorBase::CreateTransactionForCollapsedRange( if (aHowToHandleCollapsedRange == HowToHandleCollapsedRange::ExtendForward && point.IsEndOfContainer()) { MOZ_ASSERT(IsHTMLEditor()); + if (MOZ_UNLIKELY(!point.IsInContentNode())) { + NS_WARNING("There was no editable content after the collapsed range"); + return nullptr; + } // We're deleting from the end of a node. Delete the first thing of // next editable content. - nsIContent* nextEditableContent = HTMLEditUtils::GetNextContent( - *point.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode}, + nsIContent* nextEditableContent = HTMLEditUtils::GetNextLeafContent( + *point.ContainerAs<nsIContent>(), + {LeafNodeOption::IgnoreNonEditableNode}, IsTextEditor() ? BlockInlineCheck::UseHTMLDefaultStyle : BlockInlineCheck::UseComputedDisplayOutsideStyle, anonymousDivOrEditingHost); - if (!nextEditableContent) { + if (MOZ_UNLIKELY(!nextEditableContent)) { NS_WARNING("There was no editable content after the collapsed range"); return nullptr; } @@ -4538,12 +4549,12 @@ EditorBase::CreateTransactionForCollapsedRange( if (IsHTMLEditor()) { editableContent = aHowToHandleCollapsedRange == HowToHandleCollapsedRange::ExtendBackward - ? HTMLEditUtils::GetPreviousContent( - point, {WalkTreeOption::IgnoreNonEditableNode}, + ? HTMLEditUtils::GetPreviousLeafContent( + point, {LeafNodeOption::IgnoreNonEditableNode}, BlockInlineCheck::UseComputedDisplayOutsideStyle, anonymousDivOrEditingHost) - : HTMLEditUtils::GetNextContent( - point, {WalkTreeOption::IgnoreNonEditableNode}, + : HTMLEditUtils::GetNextLeafContent( + point, {LeafNodeOption::IgnoreNonEditableNode}, BlockInlineCheck::UseComputedDisplayOutsideStyle, anonymousDivOrEditingHost); if (!editableContent) { @@ -4556,12 +4567,12 @@ EditorBase::CreateTransactionForCollapsedRange( editableContent = aHowToHandleCollapsedRange == HowToHandleCollapsedRange::ExtendBackward - ? HTMLEditUtils::GetPreviousContent( - *editableContent, {WalkTreeOption::IgnoreNonEditableNode}, + ? HTMLEditUtils::GetPreviousLeafContent( + *editableContent, {LeafNodeOption::IgnoreNonEditableNode}, BlockInlineCheck::UseComputedDisplayOutsideStyle, anonymousDivOrEditingHost) - : HTMLEditUtils::GetNextContent( - *editableContent, {WalkTreeOption::IgnoreNonEditableNode}, + : HTMLEditUtils::GetNextLeafContent( + *editableContent, {LeafNodeOption::IgnoreNonEditableNode}, BlockInlineCheck::UseComputedDisplayOutsideStyle, anonymousDivOrEditingHost); } diff --git a/editor/libeditor/HTMLEditSubActionHandler.cpp b/editor/libeditor/HTMLEditSubActionHandler.cpp @@ -787,7 +787,7 @@ nsresult HTMLEditor::EnsureCaretNotAfterInvisibleBRElement( return NS_OK; } - nsIContent* previousBRElement = HTMLEditUtils::GetPreviousContent( + nsIContent* previousBRElement = HTMLEditUtils::GetPreviousLeafContent( atSelectionStart, {}, BlockInlineCheck::UseComputedDisplayStyle, &aEditingHost); if (!previousBRElement || !previousBRElement->IsHTMLElement(nsGkAtoms::br) || @@ -4185,8 +4185,8 @@ HTMLEditor::FormatBlockContainerWithTransaction( // If the first editable node after selection is a br, consume it. // Otherwise it gets pushed into a following block after the split, // which is visually bad. - if (nsCOMPtr<nsIContent> brContent = HTMLEditUtils::GetNextContent( - pointToInsertBlock, {WalkTreeOption::IgnoreNonEditableNode}, + if (nsCOMPtr<nsIContent> brContent = HTMLEditUtils::GetNextLeafContent( + pointToInsertBlock, {LeafNodeOption::IgnoreNonEditableNode}, BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost)) { if (brContent && brContent->IsHTMLElement(nsGkAtoms::br)) { @@ -7243,8 +7243,8 @@ HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction( prevVisibleThingOfEndPoint .ReachedInlineEditingHostBoundary()) { // endpoint is just after start of this block - if (nsIContent* child = HTMLEditUtils::GetPreviousContent( - endPoint, {WalkTreeOption::IgnoreNonEditableNode}, + if (nsIContent* const child = HTMLEditUtils::GetPreviousLeafContent( + endPoint, {LeafNodeOption::IgnoreNonEditableNode}, BlockInlineCheck::UseHTMLDefaultStyle, &aEditingHost)) { newRange.SetEnd(EditorRawDOMPoint::After(*child)); } @@ -7284,8 +7284,8 @@ HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction( nextVisibleThingOfStartPoint .ReachedInlineEditingHostBoundary()) { // startpoint is just before end of this block - if (nsIContent* child = HTMLEditUtils::GetNextContent( - startPoint, {WalkTreeOption::IgnoreNonEditableNode}, + if (nsIContent* const child = HTMLEditUtils::GetNextLeafContent( + startPoint, {LeafNodeOption::IgnoreNonEditableNode}, BlockInlineCheck::UseHTMLDefaultStyle, &aEditingHost)) { newRange.SetStart(EditorRawDOMPoint(child)); } @@ -8950,8 +8950,8 @@ nsresult HTMLEditor::AdjustCaretPositionAndEnsurePaddingBRElement( } if (nsCOMPtr<nsIContent> previousEditableContent = - HTMLEditUtils::GetPreviousContent( - point, {WalkTreeOption::IgnoreNonEditableNode}, + HTMLEditUtils::GetPreviousLeafContent( + point, {LeafNodeOption::IgnoreNonEditableNode}, BlockInlineCheck::UseComputedDisplayStyle, editingHost)) { // If caret and previous editable content are in same block element // (even if it's a non-editable element), we should put a padding <br> diff --git a/editor/libeditor/HTMLEditUtils.cpp b/editor/libeditor/HTMLEditUtils.cpp @@ -57,60 +57,35 @@ namespace mozilla { using namespace dom; using EditorType = EditorBase::EditorType; -template nsIContent* HTMLEditUtils::GetNextLeafContentOrNextBlockElement( - const EditorDOMPoint&, const LeafNodeOptions&, BlockInlineCheck, - const Element*); -template nsIContent* HTMLEditUtils::GetNextLeafContentOrNextBlockElement( - const EditorRawDOMPoint&, const LeafNodeOptions&, BlockInlineCheck, - const Element*); -template nsIContent* HTMLEditUtils::GetNextLeafContentOrNextBlockElement( - const EditorDOMPointInText&, const LeafNodeOptions&, BlockInlineCheck, - const Element*); -template nsIContent* HTMLEditUtils::GetNextLeafContentOrNextBlockElement( - const EditorRawDOMPointInText&, const LeafNodeOptions&, BlockInlineCheck, - const Element*); +template nsIContent* HTMLEditUtils::GetNextLeafContentOrNextBlockElementImpl( + const EditorDOMPoint&, StopAtBlockSibling, const LeafNodeOptions&, + BlockInlineCheck, const Element*); +template nsIContent* HTMLEditUtils::GetNextLeafContentOrNextBlockElementImpl( + const EditorRawDOMPoint&, StopAtBlockSibling, const LeafNodeOptions&, + BlockInlineCheck, const Element*); +template nsIContent* HTMLEditUtils::GetNextLeafContentOrNextBlockElementImpl( + const EditorDOMPointInText&, StopAtBlockSibling, const LeafNodeOptions&, + BlockInlineCheck, const Element*); +template nsIContent* HTMLEditUtils::GetNextLeafContentOrNextBlockElementImpl( + const EditorRawDOMPointInText&, StopAtBlockSibling, const LeafNodeOptions&, + BlockInlineCheck, const Element*); template nsIContent* -HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( - const EditorDOMPoint&, const LeafNodeOptions&, BlockInlineCheck, - const Element*); +HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElementImpl( + const EditorDOMPoint&, StopAtBlockSibling, const LeafNodeOptions&, + BlockInlineCheck, const Element*); template nsIContent* -HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( - const EditorRawDOMPoint&, const LeafNodeOptions&, BlockInlineCheck, - const Element*); +HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElementImpl( + const EditorRawDOMPoint&, StopAtBlockSibling, const LeafNodeOptions&, + BlockInlineCheck, const Element*); template nsIContent* -HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( - const EditorDOMPointInText&, const LeafNodeOptions&, BlockInlineCheck, - const Element*); +HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElementImpl( + const EditorDOMPointInText&, StopAtBlockSibling, const LeafNodeOptions&, + BlockInlineCheck, const Element*); template nsIContent* -HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( - const EditorRawDOMPointInText&, const LeafNodeOptions&, BlockInlineCheck, - const Element*); - -template nsIContent* HTMLEditUtils::GetPreviousContent( - const EditorDOMPoint& aPoint, const WalkTreeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck, const Element* aAncestorLimiter); -template nsIContent* HTMLEditUtils::GetPreviousContent( - const EditorRawDOMPoint& aPoint, const WalkTreeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck, const Element* aAncestorLimiter); -template nsIContent* HTMLEditUtils::GetPreviousContent( - const EditorDOMPointInText& aPoint, const WalkTreeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck, const Element* aAncestorLimiter); -template nsIContent* HTMLEditUtils::GetPreviousContent( - const EditorRawDOMPointInText& aPoint, const WalkTreeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck, const Element* aAncestorLimiter); -template nsIContent* HTMLEditUtils::GetNextContent( - const EditorDOMPoint& aPoint, const WalkTreeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck, const Element* aAncestorLimiter); -template nsIContent* HTMLEditUtils::GetNextContent( - const EditorRawDOMPoint& aPoint, const WalkTreeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck, const Element* aAncestorLimiter); -template nsIContent* HTMLEditUtils::GetNextContent( - const EditorDOMPointInText& aPoint, const WalkTreeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck, const Element* aAncestorLimiter); -template nsIContent* HTMLEditUtils::GetNextContent( - const EditorRawDOMPointInText& aPoint, const WalkTreeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck, const Element* aAncestorLimiter); +HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElementImpl( + const EditorRawDOMPointInText&, StopAtBlockSibling, const LeafNodeOptions&, + BlockInlineCheck, const Element*); template EditorDOMPoint HTMLEditUtils::GetPreviousEditablePoint( nsIContent& aContent, const Element* aAncestorLimiter, @@ -1325,9 +1300,9 @@ nsIContent* HTMLEditUtils::GetFirstLeafContent( } // static -nsIContent* HTMLEditUtils::GetNextLeafContentOrNextBlockElement( - const nsIContent& aStartContent, const LeafNodeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck, +nsIContent* HTMLEditUtils::GetNextLeafContentOrNextBlockElementImpl( + const nsIContent& aStartContent, StopAtBlockSibling aStopAtBlockSibling, + const LeafNodeOptions& aOptions, BlockInlineCheck aBlockInlineCheck, const Element* aAncestorLimiter /* = nullptr */) { MOZ_ASSERT_IF( aOptions.contains(LeafNodeOption::IgnoreNonEditableNode), @@ -1347,9 +1322,10 @@ nsIContent* HTMLEditUtils::GetNextLeafContentOrNextBlockElement( for (Element* const parentElement : container->InclusiveAncestorsOfType<Element>()) { if (parentElement == aAncestorLimiter || - HTMLEditUtils::IsBlockElement( - *parentElement, - UseComputedDisplayStyleIfAuto(aBlockInlineCheck))) { + (static_cast<bool>(aStopAtBlockSibling) && + HTMLEditUtils::IsBlockElement( + *parentElement, + UseComputedDisplayStyleIfAuto(aBlockInlineCheck)))) { return nullptr; } if (aOptions.contains(LeafNodeOption::TreatNonEditableNodeAsLeafNode) && @@ -1370,7 +1346,8 @@ nsIContent* HTMLEditUtils::GetNextLeafContentOrNextBlockElement( } // We have a next content. If it's a block, return it. - if (HTMLEditUtils::IsBlockElement( + if (static_cast<bool>(aStopAtBlockSibling) && + HTMLEditUtils::IsBlockElement( *nextContent, PreferDisplayOutsideIfUsingDisplay( UseComputedDisplayStyleIfAuto(aBlockInlineCheck)))) { @@ -1409,9 +1386,10 @@ nsIContent* HTMLEditUtils::GetNextLeafContentOrNextBlockElement( // static template <typename PT, typename CT> -nsIContent* HTMLEditUtils::GetNextLeafContentOrNextBlockElement( +nsIContent* HTMLEditUtils::GetNextLeafContentOrNextBlockElementImpl( const EditorDOMPointBase<PT, CT>& aStartPoint, - const LeafNodeOptions& aOptions, BlockInlineCheck aBlockInlineCheck, + StopAtBlockSibling aStopAtBlockSibling, const LeafNodeOptions& aOptions, + BlockInlineCheck aBlockInlineCheck, const Element* aAncestorLimiter /* = nullptr */) { MOZ_ASSERT(aStartPoint.IsSet()); MOZ_ASSERT_IF( @@ -1422,38 +1400,40 @@ nsIContent* HTMLEditUtils::GetNextLeafContentOrNextBlockElement( return nullptr; } if (!aStartPoint.GetContainer()->IsElement()) { - return HTMLEditUtils::GetNextLeafContentOrNextBlockElement( - *aStartPoint.template ContainerAs<nsIContent>(), aOptions, - aBlockInlineCheck, aAncestorLimiter); + return HTMLEditUtils::GetNextLeafContentOrNextBlockElementImpl( + *aStartPoint.template ContainerAs<nsIContent>(), aStopAtBlockSibling, + aOptions, aBlockInlineCheck, aAncestorLimiter); } if (!HTMLEditUtils::IsContainerNode( *aStartPoint.template ContainerAs<Element>()) || HTMLEditUtils::IsReplacedElement( *aStartPoint.template ContainerAs<Element>())) { - return HTMLEditUtils::GetNextLeafContentOrNextBlockElement( - *aStartPoint.template ContainerAs<nsIContent>(), aOptions, - aBlockInlineCheck, aAncestorLimiter); + return HTMLEditUtils::GetNextLeafContentOrNextBlockElementImpl( + *aStartPoint.template ContainerAs<nsIContent>(), aStopAtBlockSibling, + aOptions, aBlockInlineCheck, aAncestorLimiter); } for (nsIContent* nextContent = aStartPoint.GetChild();;) { if (!nextContent) { if (aStartPoint.GetContainer() == aAncestorLimiter || - HTMLEditUtils::IsBlockElement( - *aStartPoint.template ContainerAs<Element>(), - UseComputedDisplayStyleIfAuto(aBlockInlineCheck))) { + (static_cast<bool>(aStopAtBlockSibling) && + HTMLEditUtils::IsBlockElement( + *aStartPoint.template ContainerAs<Element>(), + UseComputedDisplayStyleIfAuto(aBlockInlineCheck)))) { // We are at end of the block. return nullptr; } // We are at end of non-block container - return HTMLEditUtils::GetNextLeafContentOrNextBlockElement( - *aStartPoint.template ContainerAs<Element>(), aOptions, - PreferDisplayOutsideIfUsingDisplay(aBlockInlineCheck), + return HTMLEditUtils::GetNextLeafContentOrNextBlockElementImpl( + *aStartPoint.template ContainerAs<Element>(), aStopAtBlockSibling, + aOptions, PreferDisplayOutsideIfUsingDisplay(aBlockInlineCheck), aAncestorLimiter); } // We have a next node. If it's a block, return it. - if (HTMLEditUtils::IsBlockElement( + if (static_cast<bool>(aStopAtBlockSibling) && + HTMLEditUtils::IsBlockElement( *nextContent, UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck))) { return nextContent; @@ -1488,9 +1468,9 @@ nsIContent* HTMLEditUtils::GetNextLeafContentOrNextBlockElement( } // static -nsIContent* HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( - const nsIContent& aStartContent, const LeafNodeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck, +nsIContent* HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElementImpl( + const nsIContent& aStartContent, StopAtBlockSibling aStopAtBlockSibling, + const LeafNodeOptions& aOptions, BlockInlineCheck aBlockInlineCheck, const Element* aAncestorLimiter /* = nullptr */) { MOZ_ASSERT_IF( aOptions.contains(LeafNodeOption::IgnoreNonEditableNode), @@ -1510,9 +1490,10 @@ nsIContent* HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( for (Element* parentElement : container->InclusiveAncestorsOfType<Element>()) { if (parentElement == aAncestorLimiter || - HTMLEditUtils::IsBlockElement( - *parentElement, - UseComputedDisplayStyleIfAuto(aBlockInlineCheck))) { + (static_cast<bool>(aStopAtBlockSibling) && + HTMLEditUtils::IsBlockElement( + *parentElement, + UseComputedDisplayStyleIfAuto(aBlockInlineCheck)))) { return nullptr; } if (aOptions.contains(LeafNodeOption::TreatNonEditableNodeAsLeafNode) && @@ -1532,7 +1513,8 @@ nsIContent* HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( MOZ_ASSERT(previousContent); } // We have a next content. If it's a block, return it. - if (HTMLEditUtils::IsBlockElement( + if (static_cast<bool>(aStopAtBlockSibling) && + HTMLEditUtils::IsBlockElement( *previousContent, PreferDisplayOutsideIfUsingDisplay( UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck)))) { @@ -1573,9 +1555,10 @@ nsIContent* HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( // static template <typename PT, typename CT> -nsIContent* HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( +nsIContent* HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElementImpl( const EditorDOMPointBase<PT, CT>& aStartPoint, - const LeafNodeOptions& aOptions, BlockInlineCheck aBlockInlineCheck, + StopAtBlockSibling aStopAtBlockSibling, const LeafNodeOptions& aOptions, + BlockInlineCheck aBlockInlineCheck, const Element* aAncestorLimiter /* = nullptr */) { MOZ_ASSERT(aStartPoint.IsSet()); MOZ_ASSERT_IF( @@ -1586,32 +1569,33 @@ nsIContent* HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( return nullptr; } if (!aStartPoint.GetContainer()->IsElement()) { - return HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( - *aStartPoint.template ContainerAs<nsIContent>(), aOptions, - aBlockInlineCheck, aAncestorLimiter); + return HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElementImpl( + *aStartPoint.template ContainerAs<nsIContent>(), aStopAtBlockSibling, + aOptions, aBlockInlineCheck, aAncestorLimiter); } if (!HTMLEditUtils::IsContainerNode( *aStartPoint.template ContainerAs<Element>()) || HTMLEditUtils::IsReplacedElement( *aStartPoint.template ContainerAs<Element>())) { - return HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( - *aStartPoint.template ContainerAs<Element>(), aOptions, - aBlockInlineCheck, aAncestorLimiter); + return HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElementImpl( + *aStartPoint.template ContainerAs<Element>(), aStopAtBlockSibling, + aOptions, aBlockInlineCheck, aAncestorLimiter); } if (aStartPoint.IsStartOfContainer()) { if (aStartPoint.GetContainer() == aAncestorLimiter || - HTMLEditUtils::IsBlockElement( - *aStartPoint.template ContainerAs<Element>(), - UseComputedDisplayStyleIfAuto(aBlockInlineCheck))) { + (static_cast<bool>(aStopAtBlockSibling) && + HTMLEditUtils::IsBlockElement( + *aStartPoint.template ContainerAs<Element>(), + UseComputedDisplayStyleIfAuto(aBlockInlineCheck)))) { // We are at start of the block. return nullptr; } // We are at start of non-block container - return HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( - *aStartPoint.template ContainerAs<Element>(), aOptions, - PreferDisplayOutsideIfUsingDisplay(aBlockInlineCheck), + return HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElementImpl( + *aStartPoint.template ContainerAs<Element>(), aStopAtBlockSibling, + aOptions, PreferDisplayOutsideIfUsingDisplay(aBlockInlineCheck), aAncestorLimiter); } @@ -1619,7 +1603,8 @@ nsIContent* HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( previousContent; previousContent = previousContent->GetPreviousSibling()) { // We have a prior node. If it's a block, return it. - if (HTMLEditUtils::IsBlockElement( + if (static_cast<bool>(aStopAtBlockSibling) && + HTMLEditUtils::IsBlockElement( *previousContent, UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck))) { return previousContent; @@ -2547,222 +2532,6 @@ bool HTMLEditUtils::IsSingleLineContainer(const nsIContent& aContent) { } // static -template <typename PT, typename CT> -nsIContent* HTMLEditUtils::GetPreviousContent( - const EditorDOMPointBase<PT, CT>& aPoint, const WalkTreeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck, - const Element* aAncestorLimiter /* = nullptr */) { - MOZ_ASSERT(aPoint.IsSetAndValid()); - NS_WARNING_ASSERTION( - !aPoint.IsInDataNode() || aPoint.IsInTextNode(), - "GetPreviousContent() doesn't assume that the start point is a " - "data node except text node"); - - // If we are at the beginning of the node, or it is a text node, then just - // look before it. - if (aPoint.IsStartOfContainer() || aPoint.IsInTextNode()) { - if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) && - aPoint.IsInContentNode() && - HTMLEditUtils::IsBlockElement( - *aPoint.template ContainerAs<nsIContent>(), - UseComputedDisplayStyleIfAuto(aBlockInlineCheck))) { - // If we aren't allowed to cross blocks, don't look before this block. - return nullptr; - } - return HTMLEditUtils::GetPreviousContent( - *aPoint.GetContainer(), aOptions, aBlockInlineCheck, aAncestorLimiter); - } - - // else look before the child at 'aOffset' - if (aPoint.GetChild()) { - return HTMLEditUtils::GetPreviousContent( - *aPoint.GetChild(), aOptions, aBlockInlineCheck, aAncestorLimiter); - } - - // unless there isn't one, in which case we are at the end of the node - // and want the deep-right child. - nsIContent* lastLeafContent = HTMLEditUtils::GetLastLeafContent( - *aPoint.GetContainer(), - aOptions.contains(WalkTreeOption::StopAtBlockBoundary) - ? LeafNodeOptions{LeafNodeOption::TreatChildBlockAsLeafNode} - : LeafNodeOptions{}, - aBlockInlineCheck); - if (!lastLeafContent) { - return nullptr; - } - - if (!HTMLEditUtils::IsContentIgnored(*lastLeafContent, aOptions)) { - return lastLeafContent; - } - - // restart the search from the non-editable node we just found - return HTMLEditUtils::GetPreviousContent(*lastLeafContent, aOptions, - aBlockInlineCheck, aAncestorLimiter); -} - -// static -template <typename PT, typename CT> -nsIContent* HTMLEditUtils::GetNextContent( - const EditorDOMPointBase<PT, CT>& aPoint, const WalkTreeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck, - const Element* aAncestorLimiter /* = nullptr */) { - MOZ_ASSERT(aPoint.IsSetAndValid()); - NS_WARNING_ASSERTION( - !aPoint.IsInDataNode() || aPoint.IsInTextNode(), - "GetNextContent() doesn't assume that the start point is a " - "data node except text node"); - - auto point = aPoint.template To<EditorRawDOMPoint>(); - - // if the container is a text node, use its location instead - if (point.IsInTextNode()) { - point.SetAfter(point.GetContainer()); - if (NS_WARN_IF(!point.IsSet())) { - return nullptr; - } - } - - if (point.GetChild()) { - if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) && - HTMLEditUtils::IsBlockElement( - *point.GetChild(), - UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck))) { - return point.GetChild(); - } - - nsIContent* firstLeafContent = HTMLEditUtils::GetFirstLeafContent( - *point.GetChild(), - aOptions.contains(WalkTreeOption::StopAtBlockBoundary) - ? LeafNodeOptions{LeafNodeOption::TreatChildBlockAsLeafNode} - : LeafNodeOptions{}, - aBlockInlineCheck); - if (!firstLeafContent) { - return point.GetChild(); - } - - // XXX Why do we need to do this check? The leaf node must be a descendant - // of `point.GetChild()`. - if (aAncestorLimiter && - (firstLeafContent == aAncestorLimiter || - !firstLeafContent->IsInclusiveDescendantOf(aAncestorLimiter))) { - return nullptr; - } - - if (!HTMLEditUtils::IsContentIgnored(*firstLeafContent, aOptions)) { - return firstLeafContent; - } - - // restart the search from the non-editable node we just found - return HTMLEditUtils::GetNextContent(*firstLeafContent, aOptions, - aBlockInlineCheck, aAncestorLimiter); - } - - // unless there isn't one, in which case we are at the end of the node - // and want the next one. - if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) && - point.IsInContentNode() && - HTMLEditUtils::IsBlockElement( - *point.template ContainerAs<nsIContent>(), - UseComputedDisplayStyleIfAuto(aBlockInlineCheck))) { - // don't cross out of parent block - return nullptr; - } - - return HTMLEditUtils::GetNextContent(*point.GetContainer(), aOptions, - aBlockInlineCheck, aAncestorLimiter); -} - -// static -nsIContent* HTMLEditUtils::GetAdjacentLeafContent( - const nsINode& aNode, WalkTreeDirection aWalkTreeDirection, - const WalkTreeOptions& aOptions, BlockInlineCheck aBlockInlineCheck, - const Element* aAncestorLimiter /* = nullptr */) { - // called only by GetPriorNode so we don't need to check params. - MOZ_ASSERT(&aNode != aAncestorLimiter); - MOZ_ASSERT_IF(aAncestorLimiter, - aAncestorLimiter->IsInclusiveDescendantOf(aAncestorLimiter)); - - const nsINode* node = &aNode; - for (;;) { - // if aNode has a sibling in the right direction, return - // that sibling's closest child (or itself if it has no children) - nsIContent* sibling = aWalkTreeDirection == WalkTreeDirection::Forward - ? node->GetNextSibling() - : node->GetPreviousSibling(); - if (sibling) { - // XXX If `sibling` belongs to siblings of inclusive ancestors of aNode, - // perhaps, we need to use - // PreferDisplayOutsideIfUsingDisplay(aBlockInlineCheck) here. - if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) && - HTMLEditUtils::IsBlockElement( - *sibling, - UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck))) { - // don't look inside previous sibling, since it is a block - return sibling; - } - LeafNodeOptions leafNodeOptions; - if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary)) { - leafNodeOptions += LeafNodeOption::TreatChildBlockAsLeafNode; - } - if (aOptions.contains(WalkTreeOption::IgnoreNonEditableNode)) { - leafNodeOptions += LeafNodeOption::IgnoreNonEditableNode; - } - nsIContent* leafContent = - aWalkTreeDirection == WalkTreeDirection::Forward - ? HTMLEditUtils::GetFirstLeafContent(*sibling, leafNodeOptions, - aBlockInlineCheck) - : HTMLEditUtils::GetLastLeafContent(*sibling, leafNodeOptions, - aBlockInlineCheck); - return leafContent ? leafContent : sibling; - } - - nsIContent* parent = node->GetParent(); - if (!parent) { - return nullptr; - } - - if (parent == aAncestorLimiter || - (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) && - HTMLEditUtils::IsBlockElement( - *parent, UseComputedDisplayStyleIfAuto(aBlockInlineCheck)))) { - return nullptr; - } - - node = parent; - } - - MOZ_ASSERT_UNREACHABLE("What part of for(;;) do you not understand?"); - return nullptr; -} - -// static -nsIContent* HTMLEditUtils::GetAdjacentContent( - const nsINode& aNode, WalkTreeDirection aWalkTreeDirection, - const WalkTreeOptions& aOptions, BlockInlineCheck aBlockInlineCheck, - const Element* aAncestorLimiter /* = nullptr */) { - if (&aNode == aAncestorLimiter) { - // Don't allow traversal above the root node! This helps - // prevent us from accidentally editing browser content - // when the editor is in a text widget. - return nullptr; - } - - nsIContent* leafContent = HTMLEditUtils::GetAdjacentLeafContent( - aNode, aWalkTreeDirection, aOptions, aBlockInlineCheck, aAncestorLimiter); - if (!leafContent) { - return nullptr; - } - - if (!HTMLEditUtils::IsContentIgnored(*leafContent, aOptions)) { - return leafContent; - } - - return HTMLEditUtils::GetAdjacentContent(*leafContent, aWalkTreeDirection, - aOptions, aBlockInlineCheck, - aAncestorLimiter); -} - -// static template <typename EditorDOMPointType> EditorDOMPointType HTMLEditUtils::GetPreviousEditablePoint( nsIContent& aContent, const Element* aAncestorLimiter, diff --git a/editor/libeditor/HTMLEditUtils.h b/editor/libeditor/HTMLEditUtils.h @@ -1129,19 +1129,6 @@ class HTMLEditUtils final { aFoundLinkElement); } - /** - * Get adjacent content node of aNode if there is (even if one is in different - * parent element). - * - * @param aNode The node from which we start to walk the DOM - * tree. - * @param aOptions See WalkTreeOption for the detail. - * @param aBlockInlineCheck Whether considering block vs. inline with the - * computed style or the HTML default style. - * @param aAncestorLimiter Ancestor limiter element which these methods - * never cross its boundary. This is typically - * the editing host. - */ enum class WalkTreeOption { IgnoreNonEditableNode, // Ignore non-editable nodes and their children. IgnoreDataNodeExceptText, // Ignore data nodes which are not text node. @@ -1149,71 +1136,6 @@ class HTMLEditUtils final { StopAtBlockBoundary, // Stop waking the tree at a block boundary. }; using WalkTreeOptions = EnumSet<WalkTreeOption>; - static nsIContent* GetPreviousContent( - const nsINode& aNode, const WalkTreeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck, - const Element* aAncestorLimiter = nullptr) { - if (&aNode == aAncestorLimiter || - (aAncestorLimiter && - !aNode.IsInclusiveDescendantOf(aAncestorLimiter))) { - return nullptr; - } - return HTMLEditUtils::GetAdjacentContent(aNode, WalkTreeDirection::Backward, - aOptions, aBlockInlineCheck, - aAncestorLimiter); - } - static nsIContent* GetNextContent(const nsINode& aNode, - const WalkTreeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck, - const Element* aAncestorLimiter = nullptr) { - if (&aNode == aAncestorLimiter || - (aAncestorLimiter && - !aNode.IsInclusiveDescendantOf(aAncestorLimiter))) { - return nullptr; - } - return HTMLEditUtils::GetAdjacentContent(aNode, WalkTreeDirection::Forward, - aOptions, aBlockInlineCheck, - aAncestorLimiter); - } - - /** - * And another version that takes a point in DOM tree rather than a node. - */ - template <typename PT, typename CT> - static nsIContent* GetPreviousContent( - const EditorDOMPointBase<PT, CT>& aPoint, const WalkTreeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck, - const Element* aAncestorLimiter = nullptr); - - /** - * And another version that takes a point in DOM tree rather than a node. - * - * Note that this may return the child at the offset. E.g., following code - * causes infinite loop. - * - * EditorRawDOMPoint point(aEditableNode); - * while (nsIContent* content = - * GetNextContent(point, {WalkTreeOption::IgnoreNonEditableNode})) { - * // Do something... - * point.Set(content); - * } - * - * Following code must be you expected: - * - * while (nsIContent* content = - * GetNextContent(point, {WalkTreeOption::IgnoreNonEditableNode}) { - * // Do something... - * DebugOnly<bool> advanced = point.Advanced(); - * MOZ_ASSERT(advanced); - * point.Set(point.GetChild()); - * } - */ - template <typename PT, typename CT> - static nsIContent* GetNextContent(const EditorDOMPointBase<PT, CT>& aPoint, - const WalkTreeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck, - const Element* aAncestorLimiter = nullptr); - /** * GetPreviousSibling() return the preceding sibling of aContent which matches * with aOption. @@ -1367,15 +1289,15 @@ class HTMLEditUtils final { nsIContent* editableContent = nullptr; if (aWalkTreeDirection == WalkTreeDirection::Backward) { - editableContent = HTMLEditUtils::GetPreviousContent( - aPoint, {WalkTreeOption::IgnoreNonEditableNode}, + editableContent = HTMLEditUtils::GetPreviousLeafContent( + aPoint, {LeafNodeOption::IgnoreNonEditableNode}, BlockInlineCheck::Auto, &aEditingHost); if (!editableContent) { return nullptr; // Not illegal. } } else { - editableContent = HTMLEditUtils::GetNextContent( - aPoint, {WalkTreeOption::IgnoreNonEditableNode}, + editableContent = HTMLEditUtils::GetNextLeafContent( + aPoint, {LeafNodeOption::IgnoreNonEditableNode}, BlockInlineCheck::Auto, &aEditingHost); if (NS_WARN_IF(!editableContent)) { // Perhaps, illegal because the node pointed by aPoint isn't editable @@ -1392,15 +1314,15 @@ class HTMLEditUtils final { !editableContent->IsHTMLElement(nsGkAtoms::br) && !HTMLEditUtils::IsImageElement(*editableContent)) { if (aWalkTreeDirection == WalkTreeDirection::Backward) { - editableContent = HTMLEditUtils::GetPreviousContent( - *editableContent, {WalkTreeOption::IgnoreNonEditableNode}, + editableContent = HTMLEditUtils::GetPreviousLeafContent( + *editableContent, {LeafNodeOption::IgnoreNonEditableNode}, BlockInlineCheck::Auto, &aEditingHost); if (NS_WARN_IF(!editableContent)) { return nullptr; } } else { - editableContent = HTMLEditUtils::GetNextContent( - *editableContent, {WalkTreeOption::IgnoreNonEditableNode}, + editableContent = HTMLEditUtils::GetNextLeafContent( + *editableContent, {LeafNodeOption::IgnoreNonEditableNode}, BlockInlineCheck::Auto, &aEditingHost); if (NS_WARN_IF(!editableContent)) { return nullptr; @@ -1489,6 +1411,101 @@ class HTMLEditUtils final { const nsINode& aNode, const LeafNodeOptions& aOptions, BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused); + private: + enum class StopAtBlockSibling : bool { No, Yes }; + static nsIContent* GetNextLeafContentOrNextBlockElementImpl( + const nsIContent& aStartContent, StopAtBlockSibling aStopAtBlockSibling, + const LeafNodeOptions& aOptions, BlockInlineCheck aBlockInlineCheck, + const Element* aAncestorLimiter); + template <typename PT, typename CT> + static nsIContent* GetNextLeafContentOrNextBlockElementImpl( + const EditorDOMPointBase<PT, CT>& aStartPoint, + StopAtBlockSibling aStopAtBlockSibling, const LeafNodeOptions& aOptions, + BlockInlineCheck aBlockInlineCheck, const Element* aAncestorLimiter); + static nsIContent* GetPreviousLeafContentOrPreviousBlockElementImpl( + const nsIContent& aStartContent, StopAtBlockSibling aStopAtBlockSibling, + const LeafNodeOptions& aOptions, BlockInlineCheck aBlockInlineCheck, + const Element* aAncestorLimiter); + template <typename PT, typename CT> + static nsIContent* GetPreviousLeafContentOrPreviousBlockElementImpl( + const EditorDOMPointBase<PT, CT>& aStartPoint, + StopAtBlockSibling aStopAtBlockSibling, const LeafNodeOptions& aOptions, + BlockInlineCheck aBlockInlineCheck, const Element* aAncestorLimiter); + + public: + /** + * Return next leaf content of aStartContent inside aAncestorLimiter. + * This does not stop at a block inclusive ancestor nor a block sibling of an + * inclusive ancestor different from GetNextLeafContentOrNextBlockElement(). + * However, if you specify LeafNodeOption::TreatChildBlockAsLeafNode, this + * stops at a child block boundary. So, the behavior becomes complicated so + * that you need to be careful if you specify that. + * + * @param aStartContent The start content to scan next content. + * @param aOptions See LeafNodeOption. + * @param aAncestorLimiter Optional, if you set this, it must be an + * inclusive ancestor of aStartContent. + */ + static nsIContent* GetNextLeafContent( + const nsIContent& aStartContent, const LeafNodeOptions& aOptions, + BlockInlineCheck aBlockInlineCheck, + const Element* aAncestorLimiter = nullptr) { + return GetNextLeafContentOrNextBlockElementImpl( + aStartContent, StopAtBlockSibling::No, aOptions, aBlockInlineCheck, + aAncestorLimiter); + } + + /** + * Similar to the above method, but take a DOM point to specify scan start + * point. + */ + template <typename PT, typename CT> + static nsIContent* GetNextLeafContent( + const EditorDOMPointBase<PT, CT>& aStartPoint, + const LeafNodeOptions& aOptions, BlockInlineCheck aBlockInlineCheck, + const Element* aAncestorLimiter = nullptr) { + return GetNextLeafContentOrNextBlockElementImpl( + aStartPoint, StopAtBlockSibling::No, aOptions, aBlockInlineCheck, + aAncestorLimiter); + } + + /** + * Return previous leaf content of aStartContent inside aAncestorLimiter. + * This does not stop at a block inclusive ancestor nor a block sibling of an + * inclusive ancestor different from + * GetPreviousLeafContentOrPreviousBlockElement(). However, if you specify + * LeafNodeOption::TreatChildBlockAsLeafNode, this stops at a child block + * boundary. So, the behavior becomes complicated so that you need to be + * careful if you specify that. + * + * @param aStartContent The start content to scan previous content. + * @param aOptions See LeafNodeOption. + * @param aAncestorLimiter Optional, if you set this, it must be an + * inclusive ancestor of aStartContent. + */ + static nsIContent* GetPreviousLeafContent( + const nsIContent& aStartContent, const LeafNodeOptions& aOptions, + BlockInlineCheck aBlockInlineCheck, + const Element* aAncestorLimiter = nullptr) { + return GetPreviousLeafContentOrPreviousBlockElementImpl( + aStartContent, StopAtBlockSibling::No, aOptions, aBlockInlineCheck, + aAncestorLimiter); + } + + /** + * Similar to the above method, but take a DOM point to specify scan start + * point. + */ + template <typename PT, typename CT> + static nsIContent* GetPreviousLeafContent( + const EditorDOMPointBase<PT, CT>& aStartPoint, + const LeafNodeOptions& aOptions, BlockInlineCheck aBlockInlineCheck, + const Element* aAncestorLimiter = nullptr) { + return GetPreviousLeafContentOrPreviousBlockElementImpl( + aStartPoint, StopAtBlockSibling::No, aOptions, aBlockInlineCheck, + aAncestorLimiter); + } + /** * GetNextLeafContentOrNextBlockElement() returns next leaf content or * next block element of aStartContent inside aAncestorLimiter. @@ -1501,7 +1518,11 @@ class HTMLEditUtils final { static nsIContent* GetNextLeafContentOrNextBlockElement( const nsIContent& aStartContent, const LeafNodeOptions& aOptions, BlockInlineCheck aBlockInlineCheck, - const Element* aAncestorLimiter = nullptr); + const Element* aAncestorLimiter = nullptr) { + return GetNextLeafContentOrNextBlockElementImpl( + aStartContent, StopAtBlockSibling::Yes, aOptions, aBlockInlineCheck, + aAncestorLimiter); + } /** * Similar to the above method, but take a DOM point to specify scan start @@ -1511,7 +1532,11 @@ class HTMLEditUtils final { static nsIContent* GetNextLeafContentOrNextBlockElement( const EditorDOMPointBase<PT, CT>& aStartPoint, const LeafNodeOptions& aOptions, BlockInlineCheck aBlockInlineCheck, - const Element* aAncestorLimiter = nullptr); + const Element* aAncestorLimiter = nullptr) { + return GetNextLeafContentOrNextBlockElementImpl( + aStartPoint, StopAtBlockSibling::Yes, aOptions, aBlockInlineCheck, + aAncestorLimiter); + } /** * GetPreviousLeafContentOrPreviousBlockElement() returns previous leaf @@ -1526,7 +1551,11 @@ class HTMLEditUtils final { static nsIContent* GetPreviousLeafContentOrPreviousBlockElement( const nsIContent& aStartContent, const LeafNodeOptions& aOptions, BlockInlineCheck aBlockInlineCheck, - const Element* aAncestorLimiter = nullptr); + const Element* aAncestorLimiter = nullptr) { + return GetPreviousLeafContentOrPreviousBlockElementImpl( + aStartContent, StopAtBlockSibling::Yes, aOptions, aBlockInlineCheck, + aAncestorLimiter); + } /** * Similar to the above method, but take a DOM point to specify scan start @@ -1536,7 +1565,11 @@ class HTMLEditUtils final { static nsIContent* GetPreviousLeafContentOrPreviousBlockElement( const EditorDOMPointBase<PT, CT>& aStartPoint, const LeafNodeOptions& aOptions, BlockInlineCheck aBlockInlineCheck, - const Element* aAncestorLimiter = nullptr); + const Element* aAncestorLimiter = nullptr) { + return GetPreviousLeafContentOrPreviousBlockElementImpl( + aStartPoint, StopAtBlockSibling::Yes, aOptions, aBlockInlineCheck, + aAncestorLimiter); + } /** * Returns a content node whose inline styles should be preserved after @@ -2035,11 +2068,9 @@ class HTMLEditUtils final { static Maybe<EditorLineBreakType> GetFirstLineBreak( const dom::Element& aElement) { for (nsIContent* content = HTMLEditUtils::GetFirstLeafContent(aElement, {}); - content; content = HTMLEditUtils::GetNextContent( - *content, - {WalkTreeOption::IgnoreDataNodeExceptText, - WalkTreeOption::IgnoreWhiteSpaceOnlyText}, - BlockInlineCheck::Unused, &aElement)) { + content; content = HTMLEditUtils::GetNextLeafContent( + *content, {LeafNodeOption::IgnoreInvisibleText}, + BlockInlineCheck::Auto, &aElement)) { if (auto* brElement = dom::HTMLBRElement::FromNode(*content)) { return Some(EditorLineBreakType(*brElement)); } @@ -2871,18 +2902,6 @@ class HTMLEditUtils final { } /** - * Helper for GetPreviousContent() and GetNextContent(). - */ - static nsIContent* GetAdjacentLeafContent( - const nsINode& aNode, WalkTreeDirection aWalkTreeDirection, - const WalkTreeOptions& aOptions, BlockInlineCheck aBlockInlineCheck, - const Element* aAncestorLimiter = nullptr); - static nsIContent* GetAdjacentContent( - const nsINode& aNode, WalkTreeDirection aWalkTreeDirection, - const WalkTreeOptions& aOptions, BlockInlineCheck aBlockInlineCheck, - const Element* aAncestorLimiter = nullptr); - - /** * GetElementOfImmediateBlockBoundary() returns a block element if its * block boundary and aContent may be first visible thing before/after the * boundary. And it may return a <br> element only when aContent is a diff --git a/editor/libeditor/HTMLEditor.cpp b/editor/libeditor/HTMLEditor.cpp @@ -6686,8 +6686,8 @@ HTMLEditor::CopyLastEditableChildStylesWithTransaction( } while (deepestEditableContent && deepestEditableContent->IsHTMLElement(nsGkAtoms::br)) { - deepestEditableContent = HTMLEditUtils::GetPreviousContent( - *deepestEditableContent, {WalkTreeOption::IgnoreNonEditableNode}, + deepestEditableContent = HTMLEditUtils::GetPreviousLeafContent( + *deepestEditableContent, {LeafNodeOption::IgnoreNonEditableNode}, BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost); } if (!deepestEditableContent) { diff --git a/editor/libeditor/HTMLEditorDeleteHandler.cpp b/editor/libeditor/HTMLEditorDeleteHandler.cpp @@ -2554,11 +2554,11 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: auto ScanJoinTarget = [&]() MOZ_NEVER_INLINE_DEBUG -> nsIContent* { nsIContent* targetContent = aDirectionAndAmount == nsIEditor::ePrevious - ? HTMLEditUtils::GetPreviousContent( - aCurrentBlockElement, {WalkTreeOption::IgnoreNonEditableNode}, + ? HTMLEditUtils::GetPreviousLeafContent( + aCurrentBlockElement, {LeafNodeOption::IgnoreNonEditableNode}, BlockInlineCheck::Auto, &aEditingHost) - : HTMLEditUtils::GetNextContent( - aCurrentBlockElement, {WalkTreeOption::IgnoreNonEditableNode}, + : HTMLEditUtils::GetNextLeafContent( + aCurrentBlockElement, {LeafNodeOption::IgnoreNonEditableNode}, BlockInlineCheck::Auto, &aEditingHost); // If found content is an invisible text node, let's scan visible things. auto IsIgnorableDataNode = [](nsIContent* aContent) { @@ -5050,10 +5050,11 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangeToDeleteRangeWithTransaction( EditorRawDOMPoint caretPoint(aRangeToDelete.StartRef()); if (howToHandleCollapsedRange == EditorBase::HowToHandleCollapsedRange::ExtendBackward && - caretPoint.IsStartOfContainer()) { - nsIContent* previousEditableContent = HTMLEditUtils::GetPreviousContent( - *caretPoint.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode}, - BlockInlineCheck::Unused, &aEditingHost); + caretPoint.IsStartOfContainer() && caretPoint.IsInContentNode()) { + nsIContent* previousEditableContent = HTMLEditUtils::GetPreviousLeafContent( + *caretPoint.ContainerAs<nsIContent>(), + {LeafNodeOption::IgnoreNonEditableNode}, BlockInlineCheck::Auto, + &aEditingHost); if (!previousEditableContent) { return NS_OK; } @@ -5073,10 +5074,11 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangeToDeleteRangeWithTransaction( if (howToHandleCollapsedRange == EditorBase::HowToHandleCollapsedRange::ExtendForward && - caretPoint.IsEndOfContainer()) { - nsIContent* nextEditableContent = HTMLEditUtils::GetNextContent( - *caretPoint.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode}, - BlockInlineCheck::Unused, &aEditingHost); + caretPoint.IsEndOfContainer() && caretPoint.IsInContentNode()) { + nsIContent* nextEditableContent = HTMLEditUtils::GetNextLeafContent( + *caretPoint.ContainerAs<nsIContent>(), + {LeafNodeOption::IgnoreNonEditableNode}, BlockInlineCheck::Auto, + &aEditingHost); if (!nextEditableContent) { return NS_OK; } @@ -5113,12 +5115,12 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangeToDeleteRangeWithTransaction( nsIContent* editableContent = howToHandleCollapsedRange == EditorBase::HowToHandleCollapsedRange::ExtendBackward - ? HTMLEditUtils::GetPreviousContent( - caretPoint, {WalkTreeOption::IgnoreNonEditableNode}, - BlockInlineCheck::Unused, &aEditingHost) - : HTMLEditUtils::GetNextContent( - caretPoint, {WalkTreeOption::IgnoreNonEditableNode}, - BlockInlineCheck::Unused, &aEditingHost); + ? HTMLEditUtils::GetPreviousLeafContent( + caretPoint, {LeafNodeOption::IgnoreNonEditableNode}, + BlockInlineCheck::Auto, &aEditingHost) + : HTMLEditUtils::GetNextLeafContent( + caretPoint, {LeafNodeOption::IgnoreNonEditableNode}, + BlockInlineCheck::Auto, &aEditingHost); if (!editableContent) { return NS_OK; } @@ -5127,12 +5129,12 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangeToDeleteRangeWithTransaction( editableContent = howToHandleCollapsedRange == EditorBase::HowToHandleCollapsedRange::ExtendBackward - ? HTMLEditUtils::GetPreviousContent( - *editableContent, {WalkTreeOption::IgnoreNonEditableNode}, - BlockInlineCheck::Unused, &aEditingHost) - : HTMLEditUtils::GetNextContent( - *editableContent, {WalkTreeOption::IgnoreNonEditableNode}, - BlockInlineCheck::Unused, &aEditingHost); + ? HTMLEditUtils::GetPreviousLeafContent( + *editableContent, {LeafNodeOption::IgnoreNonEditableNode}, + BlockInlineCheck::Auto, &aEditingHost) + : HTMLEditUtils::GetNextLeafContent( + *editableContent, {LeafNodeOption::IgnoreNonEditableNode}, + BlockInlineCheck::Auto, &aEditingHost); } if (!editableContent) { return NS_OK; @@ -7215,7 +7217,7 @@ Result<CaretPoint, nsresult> HTMLEditor::AutoDeleteRangesHandler:: for (EditorRawDOMPoint scanStartPoint = EditorRawDOMPoint::After(mEmptyInclusiveAncestorBlockElement); scanStartPoint.IsInContentNode();) { - nsIContent* const nextContent = HTMLEditUtils::GetNextContent( + nsIContent* const nextContent = HTMLEditUtils::GetNextLeafContent( scanStartPoint, {}, BlockInlineCheck::Auto, &aEditingHost); // Let's ignore invisible `Text`. if (nextContent && nextContent->IsText() && @@ -7252,9 +7254,10 @@ Result<CaretPoint, nsresult> HTMLEditor::AutoDeleteRangesHandler:: for (EditorRawDOMPoint scanStartPoint = EditorRawDOMPoint(mEmptyInclusiveAncestorBlockElement); scanStartPoint.IsInContentNode();) { - nsIContent* const previousContent = HTMLEditUtils::GetPreviousContent( - scanStartPoint, {WalkTreeOption::IgnoreNonEditableNode}, - BlockInlineCheck::Auto, &aEditingHost); + nsIContent* const previousContent = + HTMLEditUtils::GetPreviousLeafContent( + scanStartPoint, {LeafNodeOption::IgnoreNonEditableNode}, + BlockInlineCheck::Auto, &aEditingHost); // Let's ignore invisible `Text`. if (previousContent && previousContent->IsText() && !HTMLEditUtils::IsVisibleTextNode(*previousContent->AsText())) { diff --git a/editor/libeditor/HTMLEditorInsertParagraphHandler.cpp b/editor/libeditor/HTMLEditorInsertParagraphHandler.cpp @@ -1250,9 +1250,9 @@ bool HTMLEditor::AutoInsertParagraphHandler::ShouldCreateNewParagraph( // moving to the caret, but I think that this could be handled in fewer // cases than this. const auto* const precedingBRElement = - HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetPreviousContent( - aPointToSplit, {WalkTreeOption::IgnoreNonEditableNode}, - BlockInlineCheck::Unused, &mEditingHost)); + HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetPreviousLeafContent( + aPointToSplit, {LeafNodeOption::IgnoreNonEditableNode}, + BlockInlineCheck::Auto, &mEditingHost)); if (!IsNullOrInvisibleBRElementOrPaddingOneForEmptyLastLine( precedingBRElement)) { return true; @@ -1261,9 +1261,9 @@ bool HTMLEditor::AutoInsertParagraphHandler::ShouldCreateNewParagraph( // followed by a <br> or followed by an invisible <br>, we should not create a // new paragraph. const auto* followingBRElement = - HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetNextContent( - aPointToSplit, {WalkTreeOption::IgnoreNonEditableNode}, - BlockInlineCheck::Unused, &mEditingHost)); + HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetNextLeafContent( + aPointToSplit, {LeafNodeOption::IgnoreNonEditableNode}, + BlockInlineCheck::Auto, &mEditingHost)); return !IsNullOrInvisibleBRElementOrPaddingOneForEmptyLastLine( followingBRElement); } diff --git a/editor/libeditor/HTMLEditorState.cpp b/editor/libeditor/HTMLEditorState.cpp @@ -38,6 +38,7 @@ namespace mozilla { using namespace dom; using EditorType = EditorUtils::EditorType; +using LeafNodeOption = HTMLEditUtils::LeafNodeOption; using WalkTreeOption = HTMLEditUtils::WalkTreeOption; /***************************************************************************** @@ -282,9 +283,9 @@ AlignStateAtSelection::AlignStateAtSelection(HTMLEditor& aHTMLEditor, else if (atStartOfSelection.IsContainerHTMLElement(nsGkAtoms::html) && atBodyOrDocumentElement.IsSet() && atStartOfSelection.Offset() == atBodyOrDocumentElement.Offset()) { - editTargetContent = HTMLEditUtils::GetNextContent( - atStartOfSelection, {WalkTreeOption::IgnoreNonEditableNode}, - BlockInlineCheck::Unused, aHTMLEditor.ComputeEditingHost()); + editTargetContent = HTMLEditUtils::GetNextLeafContent( + atStartOfSelection, {LeafNodeOption::IgnoreNonEditableNode}, + BlockInlineCheck::Auto, aHTMLEditor.ComputeEditingHost()); if (NS_WARN_IF(!editTargetContent)) { aRv.Throw(NS_ERROR_FAILURE); return; diff --git a/editor/libeditor/gtest/TestHTMLEditUtils.cpp b/editor/libeditor/gtest/TestHTMLEditUtils.cpp @@ -1831,6 +1831,96 @@ TEST(HTMLEditUtilsTest, GetPreviousLeafContentOrPreviousBlockElement_Content) // 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 + struct MOZ_STACK_CLASS LineBreakBeforeBlockBoundaryTest final { const char16_t* const mInnerHTML; const char* const mContainer;