tor-browser

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

commit b77ea8700fc2dda9e12eb4ae9b3034d9d2a716e7
parent ee38a798b31b620a1414e4f1d89d11f6db2d1b8d
Author: Sandor Molnar <smolnar@mozilla.com>
Date:   Wed,  7 Jan 2026 08:12:44 +0200

Revert "Bug 1998077 - part 7: Reimplement `HTMLEditUtils::GetInclusiveDeepestFirstChildWhichHasOneChild` with `LeafNodeOptions` r=m_kato" for causing build bustages @ WSRunScanner.cpp

This reverts commit 5ccf392dce86930b66101bc463f91fd0530915d1.

Revert "Bug 1998077 - part 6: Reimplement `HTMLEditUtils::Get(First|Last)Child()` and `HTMLEditUtils::Get(Next|Previous)Sibling()` with `LeafNodeOptions` r=m_kato"

This reverts commit 12eb928cb50025afa509f0c2a9ff85e6adc37c36.

Revert "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"

This reverts commit 58b4243d101d51384ac4713b5c9916328af95733.

Revert "Bug 1998077 - part 4: Replace some callers of `HTMLEditUtils::Get(Next|Previous)Content()` with `HTMLEditUtils::Get(Next|Previous)LeafContentOr(Next|Previous)BlockElement()` r=m_kato"

This reverts commit dd46967eb4b7cb89e09a713c664548c7123b3fb4.

Revert "Bug 1998077 - part 3: Add new options to ignore empty/invisible leaf nodes r=m_kato"

This reverts commit ca50a4a628a3ea7e8b7fcd8071a48881e3a0846a.

Revert "Bug 1998077 - part 2: Add `WSType::EmptyInlineContainerElement` r=m_kato"

This reverts commit 4a5d93d6315c2809b13c130cd43802ce6716b904.

Revert "Bug 1998077 - part 1: Make `HTMLEditUtils::IsVisibleElementEvenIfLeafNode` work better if primary frame is available r=m_kato"

This reverts commit b42dc9d35fc6d72432d156a76d4ee694e1577973.

Diffstat:
Meditor/libeditor/AutoClonedRangeArray.cpp | 85+++++++++++++++++++++++++++++++++----------------------------------------------
Meditor/libeditor/EditorBase.cpp | 42+++++++++++++++++-------------------------
Meditor/libeditor/HTMLEditSubActionHandler.cpp | 190++++++++++++++++++++++++++++++++++---------------------------------------------
Meditor/libeditor/HTMLEditUtils.cpp | 1021++++++++++++++++++++++++-------------------------------------------------------
Meditor/libeditor/HTMLEditUtils.h | 1076++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Meditor/libeditor/HTMLEditor.cpp | 92+++++++++++++++++++++++++++++++++++++++----------------------------------------
Meditor/libeditor/HTMLEditorDataTransfer.cpp | 17+++++------------
Meditor/libeditor/HTMLEditorDeleteHandler.cpp | 142++++++++++++++++++++++++++++++++++++-------------------------------------------
Meditor/libeditor/HTMLEditorInsertLineBreakHandler.cpp | 8++------
Meditor/libeditor/HTMLEditorInsertParagraphHandler.cpp | 36+++++++++++++++---------------------
Meditor/libeditor/HTMLEditorState.cpp | 8++++----
Meditor/libeditor/HTMLStyleEditor.cpp | 38++++++++++++++++----------------------
Meditor/libeditor/TextEditor.cpp | 5++++-
Meditor/libeditor/WSRunScanner.cpp | 18+-----------------
Meditor/libeditor/WSRunScanner.h | 185++++++-------------------------------------------------------------------------
Meditor/libeditor/WSRunScannerNestedClasses.cpp | 226++++++++++++++++++++++++++++++++++---------------------------------------------
Meditor/libeditor/WhiteSpaceVisibilityKeeper.cpp | 47++++++++++++++++++++++++-----------------------
Meditor/libeditor/gtest/TestHTMLEditUtils.cpp | 618++++---------------------------------------------------------------------------
Mtesting/web-platform/meta/editing/run/delete.html.ini | 3+++
19 files changed, 1528 insertions(+), 2329 deletions(-)

diff --git a/editor/libeditor/AutoClonedRangeArray.cpp b/editor/libeditor/AutoClonedRangeArray.cpp @@ -35,7 +35,6 @@ namespace mozilla { using namespace dom; using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption; -using LeafNodeOption = HTMLEditUtils::LeafNodeOption; using ReplaceOrVoidElementOption = HTMLEditUtils::ReplaceOrVoidElementOption; /****************************************************************************** @@ -438,22 +437,20 @@ GetPointAtFirstContentOfLineOrParentHTMLBlockIfFirstContentOfBlock( // Look back through any further inline nodes that aren't across a <br> // from us, and that are enclosed in the same block. // I.e., looking for start of current hard line. - for (nsIContent* previousEditableContent = - HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( - point, - {LeafNodeOption::IgnoreNonEditableNode, - LeafNodeOption::TreatChildBlockAsLeafNode}, - aBlockInlineCheck, &aAncestorLimiter); + constexpr HTMLEditUtils::WalkTreeOptions + ignoreNonEditableNodeAndStopAtBlockBoundary{ + HTMLEditUtils::WalkTreeOption::IgnoreNonEditableNode, + HTMLEditUtils::WalkTreeOption::StopAtBlockBoundary}; + for (nsIContent* previousEditableContent = HTMLEditUtils::GetPreviousContent( + point, ignoreNonEditableNodeAndStopAtBlockBoundary, + aBlockInlineCheck, &aAncestorLimiter); previousEditableContent && previousEditableContent->GetParentNode() && !HTMLEditUtils::IsVisibleBRElement(*previousEditableContent) && !HTMLEditUtils::IsBlockElement(*previousEditableContent, aBlockInlineCheck); - previousEditableContent = - HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( - point, - {LeafNodeOption::IgnoreNonEditableNode, - LeafNodeOption::TreatChildBlockAsLeafNode}, - aBlockInlineCheck, &aAncestorLimiter)) { + previousEditableContent = HTMLEditUtils::GetPreviousContent( + point, ignoreNonEditableNodeAndStopAtBlockBoundary, + aBlockInlineCheck, &aAncestorLimiter)) { EditorDOMPoint atLastPreformattedNewLine = HTMLEditUtils::GetPreviousPreformattedNewLineInTextNode<EditorDOMPoint>( EditorRawDOMPoint::AtEndOf(*previousEditableContent)); @@ -467,20 +464,14 @@ GetPointAtFirstContentOfLineOrParentHTMLBlockIfFirstContentOfBlock( // <br> element. Look up the tree for as long as we are the first node in // the container (typically, start of nearest block ancestor), and as long // as we haven't hit the body node. - for (nsIContent* nearContent = - HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( - point, - {LeafNodeOption::IgnoreNonEditableNode, - LeafNodeOption::TreatChildBlockAsLeafNode}, - aBlockInlineCheck, &aAncestorLimiter); + for (nsIContent* nearContent = HTMLEditUtils::GetPreviousContent( + point, ignoreNonEditableNodeAndStopAtBlockBoundary, + aBlockInlineCheck, &aAncestorLimiter); !nearContent && !point.IsContainerHTMLElement(nsGkAtoms::body) && point.GetContainerParent(); - nearContent = - HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( - point, - {LeafNodeOption::IgnoreNonEditableNode, - LeafNodeOption::TreatChildBlockAsLeafNode}, - aBlockInlineCheck, &aAncestorLimiter)) { + nearContent = HTMLEditUtils::GetPreviousContent( + point, ignoreNonEditableNodeAndStopAtBlockBoundary, + aBlockInlineCheck, &aAncestorLimiter)) { // Don't keep looking up if we have found a blockquote element to act on // when we handle outdent. // XXX Sounds like this is hacky. If possible, it should be check in @@ -610,22 +601,20 @@ GetPointAfterFollowingLineBreakOrAtFollowingHTMLBlock( // * <div contenteditable>foo[]<b contenteditable="false">bar</b>baz</div> // Only in the first case, after the caret position isn't wrapped with // new <div> element. - for (nsIContent* nextEditableContent = - HTMLEditUtils::GetNextLeafContentOrNextBlockElement( - point, - {LeafNodeOption::IgnoreNonEditableNode, - LeafNodeOption::TreatChildBlockAsLeafNode}, - aBlockInlineCheck, &aAncestorLimiter); + constexpr HTMLEditUtils::WalkTreeOptions + ignoreNonEditableNodeAndStopAtBlockBoundary{ + HTMLEditUtils::WalkTreeOption::IgnoreNonEditableNode, + HTMLEditUtils::WalkTreeOption::StopAtBlockBoundary}; + for (nsIContent* nextEditableContent = HTMLEditUtils::GetNextContent( + point, ignoreNonEditableNodeAndStopAtBlockBoundary, + aBlockInlineCheck, &aAncestorLimiter); nextEditableContent && !HTMLEditUtils::IsBlockElement(*nextEditableContent, aBlockInlineCheck) && nextEditableContent->GetParent(); - nextEditableContent = - HTMLEditUtils::GetNextLeafContentOrNextBlockElement( - point, - {LeafNodeOption::IgnoreNonEditableNode, - LeafNodeOption::TreatChildBlockAsLeafNode}, - aBlockInlineCheck, &aAncestorLimiter)) { + nextEditableContent = HTMLEditUtils::GetNextContent( + point, ignoreNonEditableNodeAndStopAtBlockBoundary, + aBlockInlineCheck, &aAncestorLimiter)) { EditorDOMPoint atFirstPreformattedNewLine = HTMLEditUtils::GetInclusiveNextPreformattedNewLineInTextNode< EditorDOMPoint>(EditorRawDOMPoint(nextEditableContent, 0)); @@ -669,18 +658,13 @@ GetPointAfterFollowingLineBreakOrAtFollowingHTMLBlock( // element. Look up the tree for as long as we are the last node in the // container (typically, block node), and as long as we haven't hit the body // node. - for (nsIContent* nearContent = - HTMLEditUtils::GetNextLeafContentOrNextBlockElement( - point, - {LeafNodeOption::IgnoreNonEditableNode, - LeafNodeOption::TreatChildBlockAsLeafNode}, - aBlockInlineCheck, &aAncestorLimiter); + for (nsIContent* nearContent = HTMLEditUtils::GetNextContent( + point, ignoreNonEditableNodeAndStopAtBlockBoundary, + aBlockInlineCheck, &aAncestorLimiter); !nearContent && !point.IsContainerHTMLElement(nsGkAtoms::body) && point.GetContainerParent(); - nearContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement( - point, - {LeafNodeOption::IgnoreNonEditableNode, - LeafNodeOption::TreatChildBlockAsLeafNode}, + nearContent = HTMLEditUtils::GetNextContent( + point, ignoreNonEditableNodeAndStopAtBlockBoundary, aBlockInlineCheck, &aAncestorLimiter)) { // Don't walk past the editable section. Note that we need to check before // walking up to a parent because we need to return the parent object, so @@ -1061,10 +1045,11 @@ nsresult AutoClonedRangeArray::CollectEditTargetNodes( if (aOutArrayOfContents.Length() != 1) { break; } - Element* const deepestDivBlockquoteOrListElement = + Element* deepestDivBlockquoteOrListElement = HTMLEditUtils::GetInclusiveDeepestFirstChildWhichHasOneChild( - aOutArrayOfContents[0], {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::Auto, nsGkAtoms::div, nsGkAtoms::blockquote, + aOutArrayOfContents[0], + {HTMLEditUtils::WalkTreeOption::IgnoreNonEditableNode}, + BlockInlineCheck::Unused, 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 @@ -144,7 +144,9 @@ using namespace dom; using namespace widget; using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption; -using LeafNodeOption = HTMLEditUtils::LeafNodeOption; +using LeafNodeType = HTMLEditUtils::LeafNodeType; +using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes; +using WalkTreeOption = HTMLEditUtils::WalkTreeOption; static LazyLogModule gEventLog("EditorEvent"); static LazyLogModule gHTMLEditorEditActionStartLog("HTMLEditorEditActionStart"); @@ -4417,19 +4419,14 @@ 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::GetPreviousLeafContent( - *point.ContainerAs<nsIContent>(), - {LeafNodeOption::IgnoreNonEditableNode}, + nsIContent* previousEditableContent = HTMLEditUtils::GetPreviousContent( + *point.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode}, IsTextEditor() ? BlockInlineCheck::UseHTMLDefaultStyle : BlockInlineCheck::UseComputedDisplayOutsideStyle, anonymousDivOrEditingHost); - if (MOZ_UNLIKELY(!previousEditableContent)) { + if (!previousEditableContent) { NS_WARNING("There was no editable content before the collapsed range"); return nullptr; } @@ -4472,19 +4469,14 @@ 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::GetNextLeafContent( - *point.ContainerAs<nsIContent>(), - {LeafNodeOption::IgnoreNonEditableNode}, + nsIContent* nextEditableContent = HTMLEditUtils::GetNextContent( + *point.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode}, IsTextEditor() ? BlockInlineCheck::UseHTMLDefaultStyle : BlockInlineCheck::UseComputedDisplayOutsideStyle, anonymousDivOrEditingHost); - if (MOZ_UNLIKELY(!nextEditableContent)) { + if (!nextEditableContent) { NS_WARNING("There was no editable content after the collapsed range"); return nullptr; } @@ -4548,12 +4540,12 @@ EditorBase::CreateTransactionForCollapsedRange( if (IsHTMLEditor()) { editableContent = aHowToHandleCollapsedRange == HowToHandleCollapsedRange::ExtendBackward - ? HTMLEditUtils::GetPreviousLeafContent( - point, {LeafNodeOption::IgnoreNonEditableNode}, + ? HTMLEditUtils::GetPreviousContent( + point, {WalkTreeOption::IgnoreNonEditableNode}, BlockInlineCheck::UseComputedDisplayOutsideStyle, anonymousDivOrEditingHost) - : HTMLEditUtils::GetNextLeafContent( - point, {LeafNodeOption::IgnoreNonEditableNode}, + : HTMLEditUtils::GetNextContent( + point, {WalkTreeOption::IgnoreNonEditableNode}, BlockInlineCheck::UseComputedDisplayOutsideStyle, anonymousDivOrEditingHost); if (!editableContent) { @@ -4566,12 +4558,12 @@ EditorBase::CreateTransactionForCollapsedRange( editableContent = aHowToHandleCollapsedRange == HowToHandleCollapsedRange::ExtendBackward - ? HTMLEditUtils::GetPreviousLeafContent( - *editableContent, {LeafNodeOption::IgnoreNonEditableNode}, + ? HTMLEditUtils::GetPreviousContent( + *editableContent, {WalkTreeOption::IgnoreNonEditableNode}, BlockInlineCheck::UseComputedDisplayOutsideStyle, anonymousDivOrEditingHost) - : HTMLEditUtils::GetNextLeafContent( - *editableContent, {LeafNodeOption::IgnoreNonEditableNode}, + : HTMLEditUtils::GetNextContent( + *editableContent, {WalkTreeOption::IgnoreNonEditableNode}, BlockInlineCheck::UseComputedDisplayOutsideStyle, anonymousDivOrEditingHost); } diff --git a/editor/libeditor/HTMLEditSubActionHandler.cpp b/editor/libeditor/HTMLEditSubActionHandler.cpp @@ -78,10 +78,11 @@ extern LazyLogModule gTextInputLog; // Defined in EditorBase.cpp using namespace dom; using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption; using EmptyCheckOptions = HTMLEditUtils::EmptyCheckOptions; -using LeafNodeOption = HTMLEditUtils::LeafNodeOption; -using LeafNodeOptions = HTMLEditUtils::LeafNodeOptions; +using LeafNodeType = HTMLEditUtils::LeafNodeType; +using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes; using WalkTextOption = HTMLEditUtils::WalkTextOption; using WalkTreeDirection = HTMLEditUtils::WalkTreeDirection; +using WalkTreeOption = HTMLEditUtils::WalkTreeOption; /******************************************************** * first some helpful functors we will use @@ -786,7 +787,7 @@ nsresult HTMLEditor::EnsureCaretNotAfterInvisibleBRElement( return NS_OK; } - nsIContent* previousBRElement = HTMLEditUtils::GetPreviousLeafContent( + nsIContent* previousBRElement = HTMLEditUtils::GetPreviousContent( atSelectionStart, {}, BlockInlineCheck::UseComputedDisplayStyle, &aEditingHost); if (!previousBRElement || !previousBRElement->IsHTMLElement(nsGkAtoms::br) || @@ -925,8 +926,8 @@ nsresult HTMLEditor::ReflectPaddingBRElementForEmptyEditor() { // here and at redo, or doing it everywhere else that might care. Since undo // and redo are relatively rare, it makes sense to take the (small) // performance hit here. - nsIContent* firstLeafChild = - HTMLEditUtils::GetFirstLeafContent(*mRootElement, {}); + nsIContent* firstLeafChild = HTMLEditUtils::GetFirstLeafContent( + *mRootElement, {LeafNodeType::OnlyLeafNode}); if (firstLeafChild && EditorUtils::IsPaddingBRElementForEmptyEditor(*firstLeafChild)) { mPaddingBRElementForEmptyEditor = @@ -2565,8 +2566,7 @@ HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces( // Try to put caret next to immediately after previous editable leaf. nsIContent* previousContent = HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( - newCaretPosition, - {LeafNodeOption::TreatNonEditableNodeAsLeafNode}, + newCaretPosition, {LeafNodeType::LeafNodeOrNonEditableNode}, BlockInlineCheck::UseComputedDisplayStyle, editableBlockElementOrInlineEditingHost); if (previousContent && @@ -2584,7 +2584,7 @@ HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces( else if (nsIContent* nextContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement( newCaretPosition, - {LeafNodeOption::TreatNonEditableNodeAsLeafNode}, + {LeafNodeType::LeafNodeOrNonEditableNode}, BlockInlineCheck::UseComputedDisplayStyle, editableBlockElementOrInlineEditingHost)) { if (HTMLEditUtils::IsSimplyEditableNode(*nextContent) && @@ -3212,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* const deepestDivBlockquoteOrListElement = + if (Element* deepestDivBlockquoteOrListElement = HTMLEditUtils::GetInclusiveDeepestFirstChildWhichHasOneChild( - aArrayOfContents[0], {LeafNodeOption::IgnoreNonEditableNode}, + aArrayOfContents[0], {WalkTreeOption::IgnoreNonEditableNode}, BlockInlineCheck::UseHTMLDefaultStyle, nsGkAtoms::div, nsGkAtoms::blockquote, nsGkAtoms::ul, nsGkAtoms::ol, nsGkAtoms::dl)) { @@ -4184,8 +4184,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::GetNextLeafContent( - pointToInsertBlock, {LeafNodeOption::IgnoreNonEditableNode}, + if (nsCOMPtr<nsIContent> brContent = HTMLEditUtils::GetNextContent( + pointToInsertBlock, {WalkTreeOption::IgnoreNonEditableNode}, BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost)) { if (brContent && brContent->IsHTMLElement(nsGkAtoms::br)) { @@ -4233,13 +4233,11 @@ HTMLEditor::FormatBlockContainerWithTransaction( } // We are making a block. Consume a br, if needed. - if (nsCOMPtr<nsIContent> maybeBRContent = - HTMLEditUtils::GetNextLeafContentOrNextBlockElement( - pointToInsertBlock, - {LeafNodeOption::IgnoreNonEditableNode, - LeafNodeOption::TreatChildBlockAsLeafNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle, - &aEditingHost)) { + if (nsCOMPtr<nsIContent> maybeBRContent = HTMLEditUtils::GetNextContent( + pointToInsertBlock, + {WalkTreeOption::IgnoreNonEditableNode, + WalkTreeOption::StopAtBlockBoundary}, + BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost)) { if (maybeBRContent->IsHTMLElement(nsGkAtoms::br)) { AutoEditorDOMPointChildInvalidator lockOffset(pointToInsertBlock); nsresult rv = DeleteNodeWithTransaction(*maybeBRContent); @@ -4429,10 +4427,8 @@ Result<EditorDOMPoint, nsresult> HTMLEditor::IndentListChildWithTransaction( // same as the parent list element's tag, we can move it to start of the // sub-list. if (nsIContent* const nextEditableSibling = HTMLEditUtils::GetNextSibling( - aContentMovingToSubList, - {LeafNodeOption::IgnoreInvisibleText, - LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle)) { + aContentMovingToSubList, {WalkTreeOption::IgnoreWhiteSpaceOnlyText, + WalkTreeOption::IgnoreNonEditableNode})) { if (HTMLEditUtils::IsListElement(*nextEditableSibling) && aPointInListElement.GetContainer()->NodeInfo()->NameAtom() == nextEditableSibling->NodeInfo()->NameAtom() && @@ -4454,9 +4450,8 @@ Result<EditorDOMPoint, nsresult> HTMLEditor::IndentListChildWithTransaction( if (const nsCOMPtr<nsIContent> previousEditableSibling = HTMLEditUtils::GetPreviousSibling( aContentMovingToSubList, - {LeafNodeOption::IgnoreInvisibleText, - LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle)) { + {WalkTreeOption::IgnoreWhiteSpaceOnlyText, + WalkTreeOption::IgnoreNonEditableNode})) { if (HTMLEditUtils::IsListElement(*previousEditableSibling) && aPointInListElement.GetContainer()->NodeInfo()->NameAtom() == previousEditableSibling->NodeInfo()->NameAtom() && @@ -4479,9 +4474,8 @@ Result<EditorDOMPoint, nsresult> HTMLEditor::IndentListChildWithTransaction( nsIContent* previousEditableSibling = *aSubListElement ? HTMLEditUtils::GetPreviousSibling( aContentMovingToSubList, - {LeafNodeOption::IgnoreInvisibleText, - LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle) + {WalkTreeOption::IgnoreWhiteSpaceOnlyText, + WalkTreeOption::IgnoreNonEditableNode}) : nullptr; if (!*aSubListElement || (previousEditableSibling && previousEditableSibling != *aSubListElement)) { @@ -5187,11 +5181,10 @@ nsresult HTMLEditor::HandleHTMLIndentAroundRanges( } // check to see if subListElement is still appropriate. Which it is if // content is still right after it in the same list. - nsIContent* const previousEditableSibling = + nsIContent* previousEditableSibling = subListElement ? HTMLEditUtils::GetPreviousSibling( - *listItem, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle) + *listItem, {WalkTreeOption::IgnoreNonEditableNode}) : nullptr; if (!subListElement || (previousEditableSibling && previousEditableSibling != subListElement)) { @@ -7105,9 +7098,8 @@ Result<EditorDOMPoint, nsresult> HTMLEditor::AlignBlockContentsWithDivElement( // XXX I don't understand why we should NOT align non-editable children // with modifying EDITABLE `<div>` element. const nsCOMPtr<nsIContent> firstEditableContent = - HTMLEditUtils::GetFirstChild( - aBlockElement, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle); + HTMLEditUtils::GetFirstChild(aBlockElement, + {WalkTreeOption::IgnoreNonEditableNode}); if (!firstEditableContent) { // This block has no editable content, nothing to align. return EditorDOMPoint(); @@ -7116,8 +7108,7 @@ Result<EditorDOMPoint, nsresult> HTMLEditor::AlignBlockContentsWithDivElement( // If there is only one editable content and it's a `<div>` element, // just set `align` attribute of it. const nsCOMPtr<nsIContent> lastEditableContent = HTMLEditUtils::GetLastChild( - aBlockElement, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle); + aBlockElement, {WalkTreeOption::IgnoreNonEditableNode}); if (firstEditableContent == lastEditableContent && firstEditableContent->IsHTMLElement(nsGkAtoms::div)) { // XXX Chrome uses `style="text-align: foo"` instead of the legacy `align` @@ -7240,7 +7231,7 @@ HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction( // endpoint is just after the close of a block. if (nsIContent* child = HTMLEditUtils::GetLastLeafContent( *prevVisibleThingOfEndPoint.ElementPtr(), - {LeafNodeOption::TreatChildBlockAsLeafNode}, + {LeafNodeType::LeafNodeOrChildBlock}, BlockInlineCheck::UseHTMLDefaultStyle)) { newRange.SetEnd(EditorRawDOMPoint::After(*child)); } @@ -7249,8 +7240,8 @@ HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction( prevVisibleThingOfEndPoint .ReachedInlineEditingHostBoundary()) { // endpoint is just after start of this block - if (nsIContent* const child = HTMLEditUtils::GetPreviousLeafContent( - endPoint, {LeafNodeOption::IgnoreNonEditableNode}, + if (nsIContent* child = HTMLEditUtils::GetPreviousContent( + endPoint, {WalkTreeOption::IgnoreNonEditableNode}, BlockInlineCheck::UseHTMLDefaultStyle, &aEditingHost)) { newRange.SetEnd(EditorRawDOMPoint::After(*child)); } @@ -7281,7 +7272,7 @@ HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction( // startpoint is just before the start of a block. if (nsIContent* child = HTMLEditUtils::GetFirstLeafContent( *nextVisibleThingOfStartPoint.ElementPtr(), - {LeafNodeOption::TreatChildBlockAsLeafNode}, + {LeafNodeType::LeafNodeOrChildBlock}, BlockInlineCheck::UseHTMLDefaultStyle)) { newRange.SetStart(EditorRawDOMPoint(child)); } @@ -7290,8 +7281,8 @@ HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction( nextVisibleThingOfStartPoint .ReachedInlineEditingHostBoundary()) { // startpoint is just before end of this block - if (nsIContent* const child = HTMLEditUtils::GetNextLeafContent( - startPoint, {LeafNodeOption::IgnoreNonEditableNode}, + if (nsIContent* child = HTMLEditUtils::GetNextContent( + startPoint, {WalkTreeOption::IgnoreNonEditableNode}, BlockInlineCheck::UseHTMLDefaultStyle, &aEditingHost)) { newRange.SetStart(EditorRawDOMPoint(child)); } @@ -8325,23 +8316,19 @@ HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction( if (aBRElementNextToSplitPoint == BRElementNextToSplitPoint::Delete) { // Consume a trailing br, if any. This is to keep an alignment from // creating extra lines, if possible. - if (nsCOMPtr<nsIContent> maybeBRContent = - HTMLEditUtils::GetNextLeafContentOrNextBlockElement( - splitPoint, - {LeafNodeOption::IgnoreNonEditableNode, - LeafNodeOption::TreatChildBlockAsLeafNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle, - &aEditingHost)) { + if (nsCOMPtr<nsIContent> maybeBRContent = HTMLEditUtils::GetNextContent( + splitPoint, + {WalkTreeOption::IgnoreNonEditableNode, + WalkTreeOption::StopAtBlockBoundary}, + BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost)) { if (maybeBRContent->IsHTMLElement(nsGkAtoms::br) && splitPoint.GetChild()) { // Making use of html structure... if next node after where we are // putting our div is not a block, then the br we found is in same // block we are, so it's safe to consume it. - if (nsIContent* const nextEditableSibling = - HTMLEditUtils::GetNextSibling( - *splitPoint.GetChild(), - {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle)) { + if (nsIContent* nextEditableSibling = HTMLEditUtils::GetNextSibling( + *splitPoint.GetChild(), + {WalkTreeOption::IgnoreNonEditableNode})) { if (!HTMLEditUtils::IsBlockElement( *nextEditableSibling, BlockInlineCheck::UseComputedDisplayOutsideStyle)) { @@ -8428,18 +8415,16 @@ nsresult HTMLEditor::JoinNearestEditableNodesWithTransaction( } // Remember the last left child, and first right child - const nsCOMPtr<nsIContent> lastEditableChildOfLeftContent = - HTMLEditUtils::GetLastChild( - aNodeLeft, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle); + nsCOMPtr<nsIContent> lastEditableChildOfLeftContent = + HTMLEditUtils::GetLastChild(aNodeLeft, + {WalkTreeOption::IgnoreNonEditableNode}); if (MOZ_UNLIKELY(NS_WARN_IF(!lastEditableChildOfLeftContent))) { return NS_ERROR_FAILURE; } - const nsCOMPtr<nsIContent> firstEditableChildOfRightContent = - HTMLEditUtils::GetFirstChild( - aNodeRight, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle); + nsCOMPtr<nsIContent> firstEditableChildOfRightContent = + HTMLEditUtils::GetFirstChild(aNodeRight, + {WalkTreeOption::IgnoreNonEditableNode}); if (NS_WARN_IF(!firstEditableChildOfRightContent)) { return NS_ERROR_FAILURE; } @@ -8814,10 +8799,10 @@ void HTMLEditor::SetSelectionInterlinePosition() { // content is `<br>`, does this do right thing? if (Element* editingHost = ComputeEditingHost()) { if (nsIContent* previousEditableContentInBlock = - HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( + HTMLEditUtils::GetPreviousContent( atCaret, - {LeafNodeOption::IgnoreNonEditableNode, - LeafNodeOption::TreatChildBlockAsLeafNode}, + {WalkTreeOption::IgnoreNonEditableNode, + WalkTreeOption::StopAtBlockBoundary}, BlockInlineCheck::UseComputedDisplayStyle, editingHost)) { if (previousEditableContentInBlock->IsHTMLElement(nsGkAtoms::br)) { DebugOnly<nsresult> rvIgnored = SelectionRef().SetInterlinePosition( @@ -8839,10 +8824,9 @@ void HTMLEditor::SetSelectionInterlinePosition() { // XXX Although I don't understand "interline position", if caret is // immediately after non-editable contents, but previous editable // content is a block, does this do right thing? - if (nsIContent* const previousEditableContentInBlockAtCaret = + if (nsIContent* previousEditableContentInBlockAtCaret = HTMLEditUtils::GetPreviousSibling( - *atCaret.GetChild(), {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle)) { + *atCaret.GetChild(), {WalkTreeOption::IgnoreNonEditableNode})) { if (HTMLEditUtils::IsBlockElement( *previousEditableContentInBlockAtCaret, BlockInlineCheck::UseComputedDisplayStyle)) { @@ -8859,10 +8843,9 @@ void HTMLEditor::SetSelectionInterlinePosition() { // XXX Although I don't understand "interline position", if caret is // immediately before non-editable contents, but next editable // content is a block, does this do right thing? - if (nsIContent* const nextEditableContentInBlockAtCaret = + if (nsIContent* nextEditableContentInBlockAtCaret = HTMLEditUtils::GetNextSibling( - *atCaret.GetChild(), {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle)) { + *atCaret.GetChild(), {WalkTreeOption::IgnoreNonEditableNode})) { if (HTMLEditUtils::IsBlockElement( *nextEditableContentInBlockAtCaret, BlockInlineCheck::UseComputedDisplayStyle)) { @@ -8962,8 +8945,8 @@ nsresult HTMLEditor::AdjustCaretPositionAndEnsurePaddingBRElement( } if (nsCOMPtr<nsIContent> previousEditableContent = - HTMLEditUtils::GetPreviousLeafContent( - point, {LeafNodeOption::IgnoreNonEditableNode}, + HTMLEditUtils::GetPreviousContent( + point, {WalkTreeOption::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> @@ -9012,10 +8995,10 @@ nsresult HTMLEditor::AdjustCaretPositionAndEnsurePaddingBRElement( // If it's a visible `<br>` element and next editable content is a // padding `<br>` element, we need to set interline position. else if (nsIContent* nextEditableContentInBlock = - HTMLEditUtils::GetNextLeafContentOrNextBlockElement( + HTMLEditUtils::GetNextContent( *previousEditableContent, - {LeafNodeOption::IgnoreNonEditableNode, - LeafNodeOption::TreatChildBlockAsLeafNode}, + {WalkTreeOption::IgnoreNonEditableNode, + WalkTreeOption::StopAtBlockBoundary}, BlockInlineCheck::UseComputedDisplayStyle, editingHost)) { if (EditorUtils::IsPaddingBRElementForEmptyLastLine( @@ -9036,10 +9019,10 @@ nsresult HTMLEditor::AdjustCaretPositionAndEnsurePaddingBRElement( // If previous editable content in same block is `<br>`, text node, `<img>` // or `<hr>`, current caret position is fine. if (nsIContent* const previousEditableContentInBlock = - HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( + HTMLEditUtils::GetPreviousContent( point, - {LeafNodeOption::IgnoreNonEditableNode, - LeafNodeOption::TreatChildBlockAsLeafNode}, + {WalkTreeOption::IgnoreNonEditableNode, + WalkTreeOption::StopAtBlockBoundary}, BlockInlineCheck::UseComputedDisplayStyle, editingHost)) { if (previousEditableContentInBlock->IsHTMLElement(nsGkAtoms::br) || previousEditableContentInBlock->IsText() || @@ -9051,12 +9034,11 @@ nsresult HTMLEditor::AdjustCaretPositionAndEnsurePaddingBRElement( // If next editable content in same block is `<br>`, text node, `<img>` or // `<hr>`, current caret position is fine. - if (nsIContent* nextEditableContentInBlock = - HTMLEditUtils::GetNextLeafContentOrNextBlockElement( - point, - {LeafNodeOption::IgnoreNonEditableNode, - LeafNodeOption::TreatChildBlockAsLeafNode}, - BlockInlineCheck::UseComputedDisplayStyle, editingHost)) { + if (nsIContent* nextEditableContentInBlock = HTMLEditUtils::GetNextContent( + point, + {WalkTreeOption::IgnoreNonEditableNode, + WalkTreeOption::StopAtBlockBoundary}, + BlockInlineCheck::UseComputedDisplayStyle, editingHost)) { if (nextEditableContentInBlock->IsText() || nextEditableContentInBlock->IsAnyOfHTMLElements( nsGkAtoms::br, nsGkAtoms::img, nsGkAtoms::hr)) { @@ -9353,11 +9335,9 @@ nsresult HTMLEditor::LiftUpListItemElement( // if it's first or last list item, don't need to split the list // otherwise we do. const bool isFirstListItem = HTMLEditUtils::IsFirstChild( - aListItemElement, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle); + aListItemElement, {WalkTreeOption::IgnoreNonEditableNode}); const bool isLastListItem = HTMLEditUtils::IsLastChild( - aListItemElement, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle); + aListItemElement, {WalkTreeOption::IgnoreNonEditableNode}); Element* leftListElement = aListItemElement.GetParentElement(); if (NS_WARN_IF(!leftListElement)) { @@ -9939,9 +9919,8 @@ HTMLEditor::EnsureHardLineBeginsWithFirstChildOf( Element& aRemovingContainerElement) { MOZ_ASSERT(IsEditActionDataAvailable()); - nsIContent* const firstEditableChild = HTMLEditUtils::GetFirstChild( - aRemovingContainerElement, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle); + nsIContent* firstEditableChild = HTMLEditUtils::GetFirstChild( + aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode}); if (!firstEditableChild) { return CreateElementResult::NotHandled(); } @@ -9952,9 +9931,8 @@ HTMLEditor::EnsureHardLineBeginsWithFirstChildOf( return CreateElementResult::NotHandled(); } - nsIContent* const previousEditableContent = HTMLEditUtils::GetPreviousSibling( - aRemovingContainerElement, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle); + nsIContent* previousEditableContent = HTMLEditUtils::GetPreviousSibling( + aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode}); if (!previousEditableContent) { return CreateElementResult::NotHandled(); } @@ -9986,9 +9964,8 @@ HTMLEditor::EnsureHardLineEndsWithLastChildOf( Element& aRemovingContainerElement) { MOZ_ASSERT(IsEditActionDataAvailable()); - nsIContent* const firstEditableContent = HTMLEditUtils::GetLastChild( - aRemovingContainerElement, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle); + nsIContent* firstEditableContent = HTMLEditUtils::GetLastChild( + aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode}); if (!firstEditableContent) { return CreateElementResult::NotHandled(); } @@ -9999,9 +9976,8 @@ HTMLEditor::EnsureHardLineEndsWithLastChildOf( return CreateElementResult::NotHandled(); } - nsIContent* const nextEditableContent = HTMLEditUtils::GetPreviousSibling( - aRemovingContainerElement, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle); + nsIContent* nextEditableContent = HTMLEditUtils::GetPreviousSibling( + aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode}); if (!nextEditableContent) { return CreateElementResult::NotHandled(); } @@ -10499,11 +10475,10 @@ nsresult HTMLEditor::MoveSelectedContentsToDivElementToMakeItAbsolutePosition( // list element in the target `<div>` element for the destination. // Therefore, duplicate same list element into the target `<div>` // element. - nsIContent* const previousEditableContent = + nsIContent* previousEditableContent = createdListElement ? HTMLEditUtils::GetPreviousSibling( - content, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle) + content, {WalkTreeOption::IgnoreNonEditableNode}) : nullptr; if (!createdListElement || (previousEditableContent && @@ -10607,11 +10582,10 @@ nsresult HTMLEditor::MoveSelectedContentsToDivElementToMakeItAbsolutePosition( } // If we cannot move the list item element into created list element, // we need another list element in the target `<div>` element. - nsIContent* const previousEditableContent = + nsIContent* previousEditableContent = createdListElement ? HTMLEditUtils::GetPreviousSibling( - *listItemElement, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle) + *listItemElement, {WalkTreeOption::IgnoreNonEditableNode}) : nullptr; if (!createdListElement || (previousEditableContent && diff --git a/editor/libeditor/HTMLEditUtils.cpp b/editor/libeditor/HTMLEditUtils.cpp @@ -57,35 +57,30 @@ namespace mozilla { using namespace dom; using EditorType = EditorBase::EditorType; -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::GetPreviousLeafContentOrPreviousBlockElementImpl( - const EditorDOMPoint&, StopAtBlockSibling, const LeafNodeOptions&, - BlockInlineCheck, const Element*); -template nsIContent* -HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElementImpl( - const EditorRawDOMPoint&, StopAtBlockSibling, const LeafNodeOptions&, - BlockInlineCheck, const Element*); -template nsIContent* -HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElementImpl( - const EditorDOMPointInText&, StopAtBlockSibling, const LeafNodeOptions&, - BlockInlineCheck, const Element*); -template nsIContent* -HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElementImpl( - const EditorRawDOMPointInText&, StopAtBlockSibling, 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); template EditorDOMPoint HTMLEditUtils::GetPreviousEditablePoint( nsIContent& aContent, const Element* aAncestorLimiter, @@ -471,8 +466,7 @@ bool HTMLEditUtils::IsFlexOrGridItem(const nsIContent& aContent) { } bool HTMLEditUtils::IsInclusiveAncestorCSSDisplayNone( - const nsIContent& aContent, - const nsIContent* aAncestorLimiter /* = nullptr */) { + const nsIContent& aContent) { if (NS_WARN_IF(!aContent.IsInComposedDoc())) { return true; } @@ -480,14 +474,12 @@ bool HTMLEditUtils::IsInclusiveAncestorCSSDisplayNone( aContent.InclusiveFlatTreeAncestorsOfType<Element>()) { RefPtr<const ComputedStyle> elementStyle = nsComputedDOMStyle::GetComputedStyleNoFlush(element); - if (MOZ_LIKELY(elementStyle)) { - const nsStyleDisplay* styleDisplay = elementStyle->StyleDisplay(); - if (MOZ_UNLIKELY(styleDisplay->mDisplay == StyleDisplay::None)) { - return true; - } + if (NS_WARN_IF(!elementStyle)) { + continue; } - if (element == aAncestorLimiter) { - break; + const nsStyleDisplay* styleDisplay = elementStyle->StyleDisplay(); + if (MOZ_UNLIKELY(styleDisplay->mDisplay == StyleDisplay::None)) { + return true; } } return false; @@ -501,11 +493,7 @@ bool HTMLEditUtils::IsVisibleElementEvenIfLeafNode(const nsIContent& aContent) { if (!aContent.IsHTMLElement()) { return true; } - nsIFrame* const primaryFrame = aContent.GetPrimaryFrame(); - if (primaryFrame && aContent.IsInComposedDoc() && - HTMLEditUtils::IsInclusiveAncestorCSSDisplayNone(aContent)) { - return false; - } + // XXX Should we return false if the element is display:none? if (HTMLEditUtils::IsBlockElement( aContent, BlockInlineCheck::UseComputedDisplayStyle)) { return true; @@ -523,19 +511,18 @@ bool HTMLEditUtils::IsVisibleElementEvenIfLeafNode(const nsIContent& aContent) { HTMLInputElement::FromNode(&aContent)) { return inputElement->ControlType() != FormControlType::InputHidden; } - if (primaryFrame) { - // If the frame is not dirty or non-inline container frame, we can trust - // whether the frame is empty or not. - if (!primaryFrame->IsSubtreeDirty() || !primaryFrame->IsInlineFrame()) { - return !primaryFrame->GetSize().IsEmpty(); - } - // Otherwise, the inner content may have been changed by the editor or JS. - // Let's treat it's visible only when it has non-zero border or padding. - return !primaryFrame->IsSelfEmpty(); + // If the element has a primary frame and it's not empty, the element is + // visible. + // XXX This method does not guarantee that the layout has already been + // updated. Therefore, this check might be wrong in the edge cases. + // However, basically, editor apps should not depend on this path, this + // is required if last <br> before a block boundary becomes visible because + // of followed by empty but styled frame like <span style=padding:1px></span>. + if (aContent.GetPrimaryFrame() && + !aContent.GetPrimaryFrame()->GetSize().IsEmpty()) { + return true; } - // If aContent does not have a primary frame, it may be inserted to the - // document and has not been flushed the pending notifications. Then, we - // cannot know the actual style so that let's assume it's invisible. + // Maybe, empty inline element such as <span>. return false; } @@ -929,12 +916,9 @@ EditorDOMPoint HTMLEditUtils::LineRequiresPaddingLineBreakToBeVisible( // because we want to make it visible. Therefore, we cannot use // WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary() here. nsIContent* const previousVisibleLeafOrChildBlock = - HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( + HTMLEditUtils::GetPreviousNonEmptyLeafContentOrPreviousBlockElement( preferredPaddingLineBreakPoint, - {LeafNodeOption::TreatChildBlockAsLeafNode, - LeafNodeOption::IgnoreInvisibleEmptyInlineContainers, - LeafNodeOption::IgnoreEmptyText}, - BlockInlineCheck::Auto); + {LeafNodeType::LeafNodeOrChildBlock}, BlockInlineCheck::Auto); if (!previousVisibleLeafOrChildBlock) { // Reached current block. return true; @@ -967,12 +951,16 @@ Element* HTMLEditUtils::GetElementOfImmediateBlockBoundary( auto getNextContent = [&aDirection, &maybeNonEditableAncestorBlock]( const nsIContent& aContent) -> nsIContent* { return aDirection == WalkTreeDirection::Forward - ? HTMLEditUtils::GetNextLeafContentOrNextBlockElement( - aContent, {LeafNodeOption::TreatChildBlockAsLeafNode}, + ? HTMLEditUtils::GetNextContent( + aContent, + {WalkTreeOption::IgnoreDataNodeExceptText, + WalkTreeOption::StopAtBlockBoundary}, BlockInlineCheck::UseComputedDisplayStyle, maybeNonEditableAncestorBlock) - : HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( - aContent, {LeafNodeOption::TreatChildBlockAsLeafNode}, + : HTMLEditUtils::GetPreviousContent( + aContent, + {WalkTreeOption::IgnoreDataNodeExceptText, + WalkTreeOption::StopAtBlockBoundary}, BlockInlineCheck::UseComputedDisplayStyle, maybeNonEditableAncestorBlock); }; @@ -1097,636 +1085,31 @@ bool HTMLEditUtils::PointIsImmediatelyBeforeCurrentBlockBoundary( return false; } -// static -HTMLEditUtils::LeafNodeType HTMLEditUtils::GetLeafNodeType( - const nsIContent& aContent, const LeafNodeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck, IgnoreChildren aIgnoreChildren) { - if (!HTMLEditUtils::IsSimplyEditableNode(aContent)) { - if (aOptions.contains(LeafNodeOption::TreatNonEditableNodeAsLeafNode)) { - return LeafNodeType::Leaf; - } - if (aOptions.contains(LeafNodeOption::IgnoreNonEditableNode)) { - return LeafNodeType::Ignore; - } - } - if (const Element* const element = Element::FromNode(&aContent)) { - // If the element is a replaced element, it should be treated as a leaf. - if (HTMLEditUtils::IsReplacedElement(*element)) { - return LeafNodeType::Leaf; - } - // We're looking for a child block, check the display-outside style. - if (aOptions.contains(LeafNodeOption::TreatChildBlockAsLeafNode) && - HTMLEditUtils::IsBlockElement( - *element, - UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck))) { - return LeafNodeType::Leaf; - } - // Let's handle invisible void elements even if it has some children. - if (!HTMLEditUtils::IsContainerNode(*element)) { - return aOptions.contains( - LeafNodeOption::IgnoreInvisibleInlineVoidElements) && - !HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*element) - ? LeafNodeType::Ignore - : LeafNodeType::Leaf; - } - if (aIgnoreChildren == IgnoreChildren::No && aContent.HasChildNodes()) { - return LeafNodeType::NonEmptyContainer; - } - // if the element is a flow root, it's meaningful and must be visible. - if (HTMLEditUtils::IsBlockElement( - *element, aBlockInlineCheck == BlockInlineCheck::UseHTMLDefaultStyle - ? BlockInlineCheck::UseHTMLDefaultStyle - : BlockInlineCheck::UseComputedDisplayStyle)) { - return LeafNodeType::Leaf; - } - if (!HTMLEditUtils::IsContainerNode(*element)) { - return LeafNodeType::Ignore; - } - // Now the element is an empty inline container like <span></span>. - if (aOptions.contains(LeafNodeOption::IgnoreAnyEmptyInlineContainers)) { - return LeafNodeType::Ignore; - } - if (aOptions.contains( - LeafNodeOption::IgnoreInvisibleEmptyInlineContainers) && - !HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*element)) { - return LeafNodeType::Ignore; - } - return LeafNodeType::Leaf; - } - if (const Text* const text = Text::FromNode(aContent)) { - if (!text->TextDataLength()) { - return aOptions.contains(LeafNodeOption::IgnoreEmptyText) || - aOptions.contains(LeafNodeOption::IgnoreInvisibleText) - ? LeafNodeType::Ignore - : LeafNodeType::Leaf; - } - return !aOptions.contains(LeafNodeOption::IgnoreInvisibleText) || - IsVisibleTextNode(*text) - ? LeafNodeType::Leaf - : LeafNodeType::Ignore; - } - if (aContent.IsComment()) { - return aOptions.contains(LeafNodeOption::TreatCommentAsLeafNode) - ? LeafNodeType::Leaf - : LeafNodeType::Ignore; - } - return LeafNodeType::Ignore; -} - -// static -nsIContent* HTMLEditUtils::GetLastLeafContent( - const nsINode& aNode, const LeafNodeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck /* = BlockInlineCheck::Unused */) { - MOZ_ASSERT_IF( - aOptions.contains(LeafNodeOption::IgnoreNonEditableNode), - !aOptions.contains(LeafNodeOption::TreatNonEditableNodeAsLeafNode)); - MOZ_ASSERT_IF(aOptions.contains(LeafNodeOption::TreatChildBlockAsLeafNode), - aBlockInlineCheck != BlockInlineCheck::Unused); - // editor shouldn't touch child nodes which are replaced with native - // anonymous nodes. - if (aNode.IsElement() && - HTMLEditUtils::IsNeverElementContentsEditableByUser(*aNode.AsElement())) { - return nullptr; - } - for (nsIContent* content = aNode.GetLastChild(); content;) { - const LeafNodeType type = HTMLEditUtils::GetLeafNodeType( - *content, aOptions, aBlockInlineCheck, IgnoreChildren::No); - if (type == LeafNodeType::Leaf) { - return content; - } - if (type == LeafNodeType::NonEmptyContainer) { - content = content->GetLastChild(); - MOZ_ASSERT(content); - continue; - } - MOZ_ASSERT(type == LeafNodeType::Ignore); - nsIContent* const prevSibling = content->GetPreviousSibling(); - if (prevSibling) { - content = prevSibling; - continue; - } - // Okay, content is the first sibling but no meaningful content is not in - // current container. So, the container can be treated as an empty - // container. - nsIContent* const parent = content->GetParent(); - if (!parent || parent == &aNode) { - return nullptr; - } - content = nullptr; - for (nsIContent* const ancestor : - parent->InclusiveAncestorsOfType<nsIContent>()) { - if (ancestor == &aNode) { - return nullptr; // No meaningful leaf in aNode. - } - // All children of current content is ignorable. So, the parent - // should be treated as empty. - const LeafNodeType type = HTMLEditUtils::GetLeafNodeType( - *ancestor, aOptions, aBlockInlineCheck, IgnoreChildren::Yes); - if (type == LeafNodeType::Leaf) { - return ancestor; - } - MOZ_ASSERT(type == LeafNodeType::Ignore); - // If the ancestor has a previous sibling, check it. - if ((content = ancestor->GetPreviousSibling())) { - break; - } - // Otherwise, check the parent of the ancestor. - } - } - return nullptr; -} - -// static -nsIContent* HTMLEditUtils::GetFirstLeafContent( - const nsINode& aNode, const LeafNodeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck /* = BlockInlineCheck::Unused */) { - MOZ_ASSERT_IF( - aOptions.contains(LeafNodeOption::IgnoreNonEditableNode), - !aOptions.contains(LeafNodeOption::TreatNonEditableNodeAsLeafNode)); - MOZ_ASSERT_IF(aOptions.contains(LeafNodeOption::TreatChildBlockAsLeafNode), - aBlockInlineCheck != BlockInlineCheck::Unused); - // editor shouldn't touch child nodes which are replaced with native - // anonymous nodes. - if (aNode.IsElement() && - HTMLEditUtils::IsNeverElementContentsEditableByUser(*aNode.AsElement())) { - return nullptr; - } - for (nsIContent* content = aNode.GetFirstChild(); content;) { - const LeafNodeType type = HTMLEditUtils::GetLeafNodeType( - *content, aOptions, aBlockInlineCheck, IgnoreChildren::No); - if (type == LeafNodeType::Leaf) { - return content; - } - if (type == LeafNodeType::NonEmptyContainer) { - content = content->GetFirstChild(); - MOZ_ASSERT(content); - continue; - } - MOZ_ASSERT(type == LeafNodeType::Ignore); - nsIContent* const nextSibling = content->GetNextSibling(); - if (nextSibling) { - content = nextSibling; - continue; - } - // Okay, content is the last sibling but no meaningful content is not in - // current container. So, the container can be treated as an empty - // container. - nsIContent* const parent = content->GetParent(); - if (!parent || parent == &aNode) { - return nullptr; // No meaningful leaf in aNode. - } - content = nullptr; - for (nsIContent* const ancestor : - parent->InclusiveAncestorsOfType<nsIContent>()) { - if (ancestor == &aNode) { - return nullptr; - } - // All children of current content is ignorable. So, the parent - // should be treated as empty. - const LeafNodeType type = HTMLEditUtils::GetLeafNodeType( - *ancestor, aOptions, aBlockInlineCheck, IgnoreChildren::Yes); - if (type == LeafNodeType::Leaf) { - return ancestor; - } - MOZ_ASSERT(type == LeafNodeType::Ignore); - // If the ancestor has a next sibling, check it. - if ((content = ancestor->GetNextSibling())) { - break; - } - // Otherwise, check the parent of the ancestor. - } - } - return nullptr; -} - -// static -nsIContent* HTMLEditUtils::GetNextLeafContentOrNextBlockElementImpl( - const nsIContent& aStartContent, StopAtBlockSibling aStopAtBlockSibling, - const LeafNodeOptions& aOptions, BlockInlineCheck aBlockInlineCheck, - const Element* aAncestorLimiter /* = nullptr */) { - MOZ_ASSERT_IF( - aOptions.contains(LeafNodeOption::IgnoreNonEditableNode), - !aOptions.contains(LeafNodeOption::TreatNonEditableNodeAsLeafNode)); - - if (&aStartContent == aAncestorLimiter) { - return nullptr; - } - - Element* container = aStartContent.GetParentElement(); - for (nsIContent* nextContent = aStartContent.GetNextSibling();;) { - if (!nextContent) { - if (!container) { - NS_WARNING("Reached orphan node while climbing up the DOM tree"); - return nullptr; - } - for (Element* const parentElement : - container->InclusiveAncestorsOfType<Element>()) { - if (parentElement == aAncestorLimiter || - (static_cast<bool>(aStopAtBlockSibling) && - HTMLEditUtils::IsBlockElement( - *parentElement, - UseComputedDisplayStyleIfAuto(aBlockInlineCheck)))) { - return nullptr; - } - if (aOptions.contains(LeafNodeOption::TreatNonEditableNodeAsLeafNode) && - !parentElement->IsEditable()) { - return nullptr; - } - nextContent = parentElement->GetNextSibling(); - if (nextContent) { - container = nextContent->GetParentElement(); - break; - } - if (!parentElement->GetParentElement()) { - NS_WARNING("Reached orphan node while climbing up the DOM tree"); - return nullptr; - } - } - MOZ_ASSERT(nextContent); - } - - // We have a next content. If it's a block, return it. - if (static_cast<bool>(aStopAtBlockSibling) && - HTMLEditUtils::IsBlockElement( - *nextContent, - PreferDisplayOutsideIfUsingDisplay( - UseComputedDisplayStyleIfAuto(aBlockInlineCheck)))) { - return nextContent; - } - LeafNodeType type = HTMLEditUtils::GetLeafNodeType( - *nextContent, aOptions, aBlockInlineCheck, IgnoreChildren::No); - if (type == LeafNodeType::Leaf) { - return nextContent; - } - if (type == LeafNodeType::Ignore) { - nextContent = nextContent->GetNextSibling(); - MOZ_ASSERT_IF(nextContent, container == nextContent->GetParentElement()); - continue; - } - MOZ_ASSERT(type == LeafNodeType::NonEmptyContainer); - if (nsIContent* const lastLeaf = HTMLEditUtils::GetFirstLeafContent( - *nextContent, aOptions, - PreferDisplayOutsideIfUsingDisplay(aBlockInlineCheck))) { - return lastLeaf; - } - // nextContent has some nodes, but does not have meaningful nodes. - // Therefore, we can treat it as empty. - type = HTMLEditUtils::GetLeafNodeType( - *nextContent, aOptions, aBlockInlineCheck, IgnoreChildren::Yes); - if (type == LeafNodeType::Leaf) { - return nextContent; - } - MOZ_ASSERT(type == LeafNodeType::Ignore); - nextContent = nextContent->GetNextSibling(); - MOZ_ASSERT_IF(nextContent, container == nextContent->GetParentElement()); - } - MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE( - "Must return from the preceding for-loop"); -} - -// static -template <typename PT, typename CT> -nsIContent* HTMLEditUtils::GetNextLeafContentOrNextBlockElementImpl( - const EditorDOMPointBase<PT, CT>& aStartPoint, - StopAtBlockSibling aStopAtBlockSibling, const LeafNodeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck, - const Element* aAncestorLimiter /* = nullptr */) { - MOZ_ASSERT(aStartPoint.IsSet()); - MOZ_ASSERT_IF( - aOptions.contains(LeafNodeOption::IgnoreNonEditableNode), - !aOptions.contains(LeafNodeOption::TreatNonEditableNodeAsLeafNode)); - - if (!aStartPoint.IsInContentNode()) { - return nullptr; - } - if (!aStartPoint.GetContainer()->IsElement()) { - 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::GetNextLeafContentOrNextBlockElementImpl( - *aStartPoint.template ContainerAs<nsIContent>(), aStopAtBlockSibling, - aOptions, aBlockInlineCheck, aAncestorLimiter); - } - - for (nsIContent* nextContent = aStartPoint.GetChild();;) { - if (!nextContent) { - if (aStartPoint.GetContainer() == aAncestorLimiter || - (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::GetNextLeafContentOrNextBlockElementImpl( - *aStartPoint.template ContainerAs<Element>(), aStopAtBlockSibling, - aOptions, PreferDisplayOutsideIfUsingDisplay(aBlockInlineCheck), - aAncestorLimiter); - } - - // We have a next node. If it's a block, return it. - if (static_cast<bool>(aStopAtBlockSibling) && - HTMLEditUtils::IsBlockElement( - *nextContent, - UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck))) { - return nextContent; - } - LeafNodeType type = HTMLEditUtils::GetLeafNodeType( - *nextContent, aOptions, aBlockInlineCheck, IgnoreChildren::No); - if (type == LeafNodeType::Leaf) { - return nextContent; - } - if (type == LeafNodeType::Ignore) { - nextContent = nextContent->GetNextSibling(); - continue; - } - MOZ_ASSERT(type == LeafNodeType::NonEmptyContainer); - if (nsIContent* const firstLeaf = HTMLEditUtils::GetFirstLeafContent( - *nextContent, aOptions, - PreferDisplayOutsideIfUsingDisplay(aBlockInlineCheck))) { - return firstLeaf; - } - // nextContent has some nodes, but does not have meaningful nodes. - // Therefore, we can treat it as empty. - type = HTMLEditUtils::GetLeafNodeType( - *nextContent, aOptions, aBlockInlineCheck, IgnoreChildren::Yes); - if (type == LeafNodeType::Leaf) { - return nextContent; - } - MOZ_ASSERT(type == LeafNodeType::Ignore); - nextContent = nextContent->GetNextSibling(); - } - MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE( - "Must return from the preceding for-loop"); -} - -// static -nsIContent* HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElementImpl( - const nsIContent& aStartContent, StopAtBlockSibling aStopAtBlockSibling, - const LeafNodeOptions& aOptions, BlockInlineCheck aBlockInlineCheck, - const Element* aAncestorLimiter /* = nullptr */) { - MOZ_ASSERT_IF( - aOptions.contains(LeafNodeOption::IgnoreNonEditableNode), - !aOptions.contains(LeafNodeOption::TreatNonEditableNodeAsLeafNode)); - - if (&aStartContent == aAncestorLimiter) { - return nullptr; - } - - Element* container = aStartContent.GetParentElement(); - for (nsIContent* previousContent = aStartContent.GetPreviousSibling();;) { - if (!previousContent) { - if (!container) { - NS_WARNING("Reached orphan node while climbing up the DOM tree"); - return nullptr; - } - for (Element* parentElement : - container->InclusiveAncestorsOfType<Element>()) { - if (parentElement == aAncestorLimiter || - (static_cast<bool>(aStopAtBlockSibling) && - HTMLEditUtils::IsBlockElement( - *parentElement, - UseComputedDisplayStyleIfAuto(aBlockInlineCheck)))) { - return nullptr; - } - if (aOptions.contains(LeafNodeOption::TreatNonEditableNodeAsLeafNode) && - !parentElement->IsEditable()) { - return nullptr; - } - previousContent = parentElement->GetPreviousSibling(); - if (previousContent) { - container = previousContent->GetParentElement(); - break; - } - if (!parentElement->GetParentElement()) { - NS_WARNING("Reached orphan node while climbing up the DOM tree"); - return nullptr; - } - } - MOZ_ASSERT(previousContent); - } - // We have a next content. If it's a block, return it. - if (static_cast<bool>(aStopAtBlockSibling) && - HTMLEditUtils::IsBlockElement( - *previousContent, - PreferDisplayOutsideIfUsingDisplay( - UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck)))) { - return previousContent; - } - LeafNodeType type = HTMLEditUtils::GetLeafNodeType( - *previousContent, aOptions, aBlockInlineCheck, IgnoreChildren::No); - if (type == LeafNodeType::Leaf) { - return previousContent; - } - if (type == LeafNodeType::Ignore) { - previousContent = previousContent->GetPreviousSibling(); - MOZ_ASSERT_IF(previousContent, - container == previousContent->GetParentElement()); - continue; - } - if (nsIContent* const lastLeaf = HTMLEditUtils::GetLastLeafContent( - *previousContent, aOptions, - PreferDisplayOutsideIfUsingDisplay(aBlockInlineCheck))) { - return lastLeaf; - } - // previousContent has some nodes, but does not have meaningful nodes. - // Therefore, we can treat it as empty. - type = HTMLEditUtils::GetLeafNodeType( - *previousContent, aOptions, aBlockInlineCheck, IgnoreChildren::Yes); - if (type == LeafNodeType::Leaf) { - return previousContent; - } - MOZ_ASSERT(type == LeafNodeType::Ignore); - previousContent = previousContent->GetPreviousSibling(); - MOZ_ASSERT_IF(previousContent, - container == previousContent->GetParentElement()); - return previousContent; - } - MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE( - "Must return from the preceding for-loop"); -} - -// static -template <typename PT, typename CT> -nsIContent* HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElementImpl( - const EditorDOMPointBase<PT, CT>& aStartPoint, - StopAtBlockSibling aStopAtBlockSibling, const LeafNodeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck, - const Element* aAncestorLimiter /* = nullptr */) { - MOZ_ASSERT(aStartPoint.IsSet()); - MOZ_ASSERT_IF( - aOptions.contains(LeafNodeOption::IgnoreNonEditableNode), - !aOptions.contains(LeafNodeOption::TreatNonEditableNodeAsLeafNode)); - - if (!aStartPoint.IsInContentNode()) { - return nullptr; - } - if (!aStartPoint.GetContainer()->IsElement()) { - 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::GetPreviousLeafContentOrPreviousBlockElementImpl( - *aStartPoint.template ContainerAs<Element>(), aStopAtBlockSibling, - aOptions, aBlockInlineCheck, aAncestorLimiter); - } - - if (aStartPoint.IsStartOfContainer()) { - if (aStartPoint.GetContainer() == aAncestorLimiter || - (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::GetPreviousLeafContentOrPreviousBlockElementImpl( - *aStartPoint.template ContainerAs<Element>(), aStopAtBlockSibling, - aOptions, PreferDisplayOutsideIfUsingDisplay(aBlockInlineCheck), - aAncestorLimiter); - } - - for (nsIContent* previousContent = aStartPoint.GetPreviousSiblingOfChild(); - previousContent; - previousContent = previousContent->GetPreviousSibling()) { - // We have a prior node. If it's a block, return it. - if (static_cast<bool>(aStopAtBlockSibling) && - HTMLEditUtils::IsBlockElement( - *previousContent, - UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck))) { - return previousContent; - } - LeafNodeType type = HTMLEditUtils::GetLeafNodeType( - *previousContent, aOptions, aBlockInlineCheck, IgnoreChildren::No); - if (type == LeafNodeType::Leaf) { - return previousContent; - } - if (type == LeafNodeType::Ignore) { - continue; - } - if (nsIContent* const lastLeaf = HTMLEditUtils::GetLastLeafContent( - *previousContent, aOptions, - PreferDisplayOutsideIfUsingDisplay(aBlockInlineCheck))) { - return lastLeaf; - } - // previousContent has some nodes, but does not have meaningful nodes. - // Therefore, we can treat it as empty. - type = HTMLEditUtils::GetLeafNodeType( - *previousContent, aOptions, aBlockInlineCheck, IgnoreChildren::Yes); - if (type == LeafNodeType::Leaf) { - return previousContent; - } - MOZ_ASSERT(type == LeafNodeType::Ignore); - } - return nullptr; -} - -nsIContent* HTMLEditUtils::GetSibling(const nsIContent& aContent, - WalkTreeDirection aDirection, - const LeafNodeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck) { - MOZ_ASSERT(aBlockInlineCheck != BlockInlineCheck::Unused); - aBlockInlineCheck = UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck); - for (nsIContent* sibling = aDirection == WalkTreeDirection::Backward - ? aContent.GetPreviousSibling() - : aContent.GetNextSibling(); - sibling; sibling = aDirection == WalkTreeDirection::Backward - ? sibling->GetPreviousSibling() - : sibling->GetNextSibling()) { - const LeafNodeType leafNodeType = HTMLEditUtils::GetLeafNodeType( - *sibling, aOptions, aBlockInlineCheck, IgnoreChildren::No); - if (leafNodeType == LeafNodeType::Ignore) { - continue; - } - if (HTMLEditUtils::IsBlockElement(*sibling, aBlockInlineCheck)) { - return sibling; - } - if (leafNodeType == LeafNodeType::NonEmptyContainer) { - if (HTMLEditUtils::GetFirstLeafContent(*sibling, aOptions, - aBlockInlineCheck)) { - return sibling; // Has meaningful child so that it's meaningful. - } - if (HTMLEditUtils::GetLeafNodeType(*sibling, aOptions, aBlockInlineCheck, - IgnoreChildren::Yes) == - LeafNodeType::Ignore) { - continue; // The sibling itself can be ignored. - } - } - return sibling; - } - return nullptr; -} - -nsIContent* HTMLEditUtils::GetFirstOrLastChild( - const nsINode& aNode, FirstOrLastChild aFirstOrLastChild, - const LeafNodeOptions& aOptions, BlockInlineCheck aBlockInlineCheck) { - MOZ_ASSERT(aBlockInlineCheck != BlockInlineCheck::Unused); - aBlockInlineCheck = UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck); - for (nsIContent* child = aFirstOrLastChild == FirstOrLastChild::First - ? aNode.GetFirstChild() - : aNode.GetLastChild(); - child; child = aFirstOrLastChild == FirstOrLastChild::First - ? child->GetNextSibling() - : child->GetPreviousSibling()) { - const LeafNodeType leafNodeType = HTMLEditUtils::GetLeafNodeType( - *child, aOptions, aBlockInlineCheck, IgnoreChildren::No); - if (leafNodeType == LeafNodeType::Ignore) { - continue; - } - if (HTMLEditUtils::IsBlockElement(*child, aBlockInlineCheck)) { - return child; - } - if (leafNodeType == LeafNodeType::NonEmptyContainer) { - if (HTMLEditUtils::GetFirstLeafContent(*child, aOptions, - aBlockInlineCheck)) { - return child; // Has meaningful child so that it's meaningful. - } - if (HTMLEditUtils::GetLeafNodeType(*child, aOptions, aBlockInlineCheck, - IgnoreChildren::Yes) == - LeafNodeType::Ignore) { - continue; // The child itself can be ignored. - } - } - return child; - } - return nullptr; -} - template <typename EditorLineBreakType> Maybe<EditorLineBreakType> HTMLEditUtils::GetUnnecessaryLineBreak( const Element& aBlockElement, ScanLineBreak aScanLineBreak) { auto* lastLineBreakContent = [&]() -> nsIContent* { + const WalkTreeOptions onlyPrecedingLine{ + WalkTreeOption::StopAtBlockBoundary}; for (nsIContent* content = aScanLineBreak == ScanLineBreak::AtEndOfBlock - ? HTMLEditUtils::GetLastLeafContent(aBlockElement, {}) - : HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( - aBlockElement, - {LeafNodeOption::TreatChildBlockAsLeafNode}, + ? HTMLEditUtils::GetLastLeafContent( + aBlockElement, {LeafNodeType::OnlyLeafNode}) + : HTMLEditUtils::GetPreviousContent( + aBlockElement, onlyPrecedingLine, BlockInlineCheck::UseComputedDisplayStyle, aBlockElement.GetParentElement()); content; - content = HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( - *content, - aScanLineBreak == ScanLineBreak::AtEndOfBlock - ? LeafNodeOptions{} - : LeafNodeOptions{LeafNodeOption::TreatChildBlockAsLeafNode}, - BlockInlineCheck::UseComputedDisplayStyle, + content = aScanLineBreak == ScanLineBreak::AtEndOfBlock - ? &aBlockElement - : aBlockElement.GetParentElement())) { + ? HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( + *content, {LeafNodeType::OnlyLeafNode}, + BlockInlineCheck::UseComputedDisplayStyle, + &aBlockElement) + : HTMLEditUtils::GetPreviousContent( + *content, onlyPrecedingLine, + BlockInlineCheck::UseComputedDisplayStyle, + aBlockElement.GetParentElement())) { // If we're scanning preceding <br> element of aBlockElement, we don't // need to look for a line break in another block because the caller // needs to handle only preceding <br> element of aBlockElement. @@ -1793,12 +1176,11 @@ Maybe<EditorLineBreakType> HTMLEditUtils::GetUnnecessaryLineBreak( BlockInlineCheck::UseComputedDisplayStyle); for (nsIContent* content = HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( - *lastLineBreakContent, - {LeafNodeOption::TreatChildBlockAsLeafNode}, + *lastLineBreakContent, {LeafNodeType::LeafNodeOrChildBlock}, BlockInlineCheck::UseComputedDisplayStyle, blockElement); content; content = HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( - *content, {LeafNodeOption::TreatChildBlockAsLeafNode}, + *content, {LeafNodeType::LeafNodeOrChildBlock}, BlockInlineCheck::UseComputedDisplayStyle, blockElement)) { if (HTMLEditUtils::IsBlockElement( *content, BlockInlineCheck::UseComputedDisplayStyle) || @@ -2155,10 +1537,6 @@ bool HTMLEditUtils::IsEmptyNode(nsPresContext* aPresContext, continue; } - if (childContent->IsComment()) { - continue; - } - MOZ_ASSERT(childContent != &aNode); if (!aOptions.contains(EmptyCheckOption::TreatSingleBRElementAsVisible) && @@ -2603,6 +1981,219 @@ 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) + ? LeafNodeType::LeafNodeOrChildBlock + : LeafNodeType::OnlyLeafNode}, + 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) + ? LeafNodeType::LeafNodeOrChildBlock + : LeafNodeType::OnlyLeafNode}, + 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; + } + const LeafNodeTypes leafNodeTypes = { + aOptions.contains(WalkTreeOption::StopAtBlockBoundary) + ? LeafNodeType::LeafNodeOrChildBlock + : LeafNodeType::OnlyLeafNode}; + nsIContent* leafContent = + aWalkTreeDirection == WalkTreeDirection::Forward + ? HTMLEditUtils::GetFirstLeafContent(*sibling, leafNodeTypes, + aBlockInlineCheck) + : HTMLEditUtils::GetLastLeafContent(*sibling, leafNodeTypes, + 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, @@ -3228,14 +2819,17 @@ nsIContent* HTMLEditUtils::GetContentToPreserveInlineStyles( if (nextVisibleThing.InVisibleOrCollapsibleCharacters()) { return nextVisibleThing.TextPtr(); } - if (nextVisibleThing.ContentIsEditableRoot()) { + if (nextVisibleThing.IsContentEditableRoot()) { break; } - // Ignore invisible empty inline container elements because it's not visible - // for users so that using the style will appear suddenly from point of view - // of users. - if (nextVisibleThing.ReachedEditableInvisibleEmptyInlineContainerElement( - &aEditingHost)) { + // Ignore empty inline container elements because it's not visible for + // users so that using the style will appear suddenly from point of + // view of users. + if (nextVisibleThing.ReachedSpecialContent() && + nextVisibleThing.IsContentEditable() && + nextVisibleThing.ContentIsElement() && + !nextVisibleThing.ElementPtr()->HasChildNodes() && + HTMLEditUtils::IsContainerNode(*nextVisibleThing.ElementPtr())) { point.SetAfter(nextVisibleThing.ElementPtr()); continue; } @@ -3372,8 +2966,7 @@ HTMLEditUtils::ComputePointToPutCaretInElementIfOutside( if (nodeBefore) { // selection is after block. put at end of block. const nsIContent* lastEditableContent = HTMLEditUtils::GetLastChild( - aElement, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle); + aElement, {WalkTreeOption::IgnoreNonEditableNode}); if (!lastEditableContent) { lastEditableContent = &aElement; } @@ -3387,8 +2980,7 @@ HTMLEditUtils::ComputePointToPutCaretInElementIfOutside( // selection is before block. put at start of block. const nsIContent* firstEditableContent = HTMLEditUtils::GetFirstChild( - aElement, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle); + aElement, {WalkTreeOption::IgnoreNonEditableNode}); if (!firstEditableContent) { firstEditableContent = &aElement; } @@ -3467,8 +3059,7 @@ size_t HTMLEditUtils::CollectChildren( size_t numberOfFoundChildren = 0; for (nsIContent* content = - GetFirstChild(aNode, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle); + GetFirstChild(aNode, {WalkTreeOption::IgnoreNonEditableNode}); content; content = content->GetNextSibling()) { if ((aOptions.contains(CollectChildrenOption::CollectListChildren) && (HTMLEditUtils::IsListElement(*content) || @@ -3741,7 +3332,6 @@ std::ostream& operator<<(std::ostream& aStream, "ReturnAncestorLimiterIfNoProperAncestor", "EditableElement", }; - MOZ_ASSERT(static_cast<uint32_t>(aType) < std::size(names)); return aStream << names[static_cast<uint32_t>(aType)]; } @@ -3769,7 +3359,6 @@ std::ostream& operator<<(std::ostream& aStream, "StopAtTableElement", "StopAtAnyTableElement", }; - MOZ_ASSERT(static_cast<uint32_t>(aOption) < std::size(names)); return aStream << names[static_cast<uint32_t>(aOption)]; } @@ -3798,7 +3387,6 @@ std::ostream& operator<<(std::ostream& aStream, "TreatCommentAsVisible", "SafeToAskLayout", }; - MOZ_ASSERT(static_cast<uint32_t>(aOption) < std::size(names)); return aStream << names[static_cast<uint32_t>(aOption)]; } @@ -3817,27 +3405,22 @@ std::ostream& operator<<(std::ostream& aStream, } std::ostream& operator<<(std::ostream& aStream, - const HTMLEditUtils::LeafNodeOption& aOption) { + const HTMLEditUtils::LeafNodeType& aLeafNodeType) { constexpr static const char* names[] = { - "TreatChildBlockAsLeafNode", - "TreatNonEditableNodeAsLeafNode", - "IgnoreNonEditableNode", + "OnlyLeafNode", + "LeafNodeOrChildBlock", + "LeafNodeOrNonEditableNode", + "OnlyEditableLeafNode", "TreatCommentAsLeafNode", - "IgnoreEmptyText", - "IgnoreInvisibleText", - "IgnoreInvisibleInlineVoidElements", - "IgnoreAnyEmptyInlineContainers", - "IgnoreInvisibleEmptyInlineContainers", }; - MOZ_ASSERT(static_cast<uint32_t>(aOption) < std::size(names)); - return aStream << names[static_cast<uint32_t>(aOption)]; + return aStream << names[static_cast<uint32_t>(aLeafNodeType)]; } std::ostream& operator<<(std::ostream& aStream, - const HTMLEditUtils::LeafNodeOptions& aOptions) { + const HTMLEditUtils::LeafNodeTypes& aLeafNodeTypes) { aStream << "{"; bool first = true; - for (const auto t : aOptions) { + for (const auto t : aLeafNodeTypes) { if (!first) { aStream << ", "; } diff --git a/editor/libeditor/HTMLEditUtils.h b/editor/libeditor/HTMLEditUtils.h @@ -236,13 +236,11 @@ class HTMLEditUtils final { BlockInlineCheck aBlockInlineCheck); /** - * IsVisibleElementEvenIfLeafNode() returns true if aContent is a visible - * element when aContent is empty. If aContent has a primary frame (even if - * dirty), this checks whether aContent is actually visible. Otherwise, this - * guesses it from the element type. + * IsVisibleElementEvenIfLeafNode() returns true if aContent is an empty block + * element, a visible replaced element such as a form control. This does not + * check the layout information. */ - [[nodiscard]] static bool IsVisibleElementEvenIfLeafNode( - const nsIContent& aContent); + static bool IsVisibleElementEvenIfLeafNode(const nsIContent& aContent); /** * Return true if aContent is an inline element which formats the content @@ -783,8 +781,7 @@ class HTMLEditUtils final { /** * Return true if `display` of inclusive ancestor of aContent is `none`. */ - [[nodiscard]] static bool IsInclusiveAncestorCSSDisplayNone( - const nsIContent& aContent, const nsIContent* aAncestorLimiter = nullptr); + static bool IsInclusiveAncestorCSSDisplayNone(const nsIContent& aContent); /** * IsVisiblePreformattedNewLine() and IsInvisiblePreformattedNewLine() return @@ -1130,6 +1127,225 @@ class HTMLEditUtils final { } /** + * 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. + IgnoreWhiteSpaceOnlyText, // Ignore text nodes having only white-spaces. + 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. + * + * @param aBlockInlineCheck Can be Unused if aOptions does not contain + * StopAtBlockBoundary. + */ + static nsIContent* GetPreviousSibling( + const nsIContent& aContent, const WalkTreeOptions& aOptions, + BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused) { + aBlockInlineCheck = UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck); + for (nsIContent* sibling = aContent.GetPreviousSibling(); sibling; + sibling = sibling->GetPreviousSibling()) { + if (HTMLEditUtils::IsContentIgnored(*sibling, aOptions)) { + continue; + } + if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) && + HTMLEditUtils::IsBlockElement(*sibling, aBlockInlineCheck)) { + return nullptr; + } + return sibling; + } + return nullptr; + } + + /** + * GetNextSibling() return the following sibling of aContent which matches + * with aOption. + * + * @param aBlockInlineCheck Can be Unused if aOptions does not contain + * StopAtBlockBoundary. + */ + static nsIContent* GetNextSibling( + const nsIContent& aContent, const WalkTreeOptions& aOptions, + BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused) { + aBlockInlineCheck = UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck); + for (nsIContent* sibling = aContent.GetNextSibling(); sibling; + sibling = sibling->GetNextSibling()) { + if (HTMLEditUtils::IsContentIgnored(*sibling, aOptions)) { + continue; + } + if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) && + HTMLEditUtils::IsBlockElement(*sibling, aBlockInlineCheck)) { + return nullptr; + } + return sibling; + } + return nullptr; + } + + /** + * Return the last child of aNode which matches with aOption. + * + * @param aBlockInlineCheck Can be unused if aOptions does not contain + * StopAtBlockBoundary. + */ + static nsIContent* GetLastChild( + const nsINode& aNode, const WalkTreeOptions& aOptions, + BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused) { + aBlockInlineCheck = UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck); + for (nsIContent* child = aNode.GetLastChild(); child; + child = child->GetPreviousSibling()) { + if (HTMLEditUtils::IsContentIgnored(*child, aOptions)) { + continue; + } + if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) && + HTMLEditUtils::IsBlockElement(*child, aBlockInlineCheck)) { + return nullptr; + } + return child; + } + return nullptr; + } + + /** + * Return the first child of aNode which matches with aOption. + * + * @param aBlockInlineCheck Can be unused if aOptions does not contain + * StopAtBlockBoundary. + */ + static nsIContent* GetFirstChild( + const nsINode& aNode, const WalkTreeOptions& aOptions, + BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused) { + aBlockInlineCheck = UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck); + for (nsIContent* child = aNode.GetFirstChild(); child; + child = child->GetNextSibling()) { + if (HTMLEditUtils::IsContentIgnored(*child, aOptions)) { + continue; + } + if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) && + HTMLEditUtils::IsBlockElement(*child, aBlockInlineCheck)) { + return nullptr; + } + return child; + } + return nullptr; + } + + /** + * Return true if aContent is the last child of aNode with ignoring all + * children which do not match with aOption. + * + * @param aBlockInlineCheck Can be unused if aOptions does not contain + * StopAtBlockBoundary. + */ + static bool IsLastChild( + const nsIContent& aContent, const WalkTreeOptions& aOptions, + BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused) { + nsINode* parentNode = aContent.GetParentNode(); + if (!parentNode) { + return false; + } + return HTMLEditUtils::GetLastChild(*parentNode, aOptions, + aBlockInlineCheck) == &aContent; + } + + /** + * Return true if aContent is the first child of aNode with ignoring all + * children which do not match with aOption. + * + * @param aBlockInlineCheck Can be unused if aOptions does not contain + * StopAtBlockBoundary. + */ + static bool IsFirstChild( + const nsIContent& aContent, const WalkTreeOptions& aOptions, + BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused) { + nsINode* parentNode = aContent.GetParentNode(); + if (!parentNode) { + return false; + } + return HTMLEditUtils::GetFirstChild(*parentNode, aOptions, + aBlockInlineCheck) == &aContent; + } + + /** * GetAdjacentContentToPutCaret() walks the DOM tree to find an editable node * near aPoint where may be a good point to put caret and keep typing or * deleting. @@ -1148,15 +1364,15 @@ class HTMLEditUtils final { nsIContent* editableContent = nullptr; if (aWalkTreeDirection == WalkTreeDirection::Backward) { - editableContent = HTMLEditUtils::GetPreviousLeafContent( - aPoint, {LeafNodeOption::IgnoreNonEditableNode}, + editableContent = HTMLEditUtils::GetPreviousContent( + aPoint, {WalkTreeOption::IgnoreNonEditableNode}, BlockInlineCheck::Auto, &aEditingHost); if (!editableContent) { return nullptr; // Not illegal. } } else { - editableContent = HTMLEditUtils::GetNextLeafContent( - aPoint, {LeafNodeOption::IgnoreNonEditableNode}, + editableContent = HTMLEditUtils::GetNextContent( + aPoint, {WalkTreeOption::IgnoreNonEditableNode}, BlockInlineCheck::Auto, &aEditingHost); if (NS_WARN_IF(!editableContent)) { // Perhaps, illegal because the node pointed by aPoint isn't editable @@ -1173,15 +1389,15 @@ class HTMLEditUtils final { !editableContent->IsHTMLElement(nsGkAtoms::br) && !HTMLEditUtils::IsImageElement(*editableContent)) { if (aWalkTreeDirection == WalkTreeDirection::Backward) { - editableContent = HTMLEditUtils::GetPreviousLeafContent( - *editableContent, {LeafNodeOption::IgnoreNonEditableNode}, + editableContent = HTMLEditUtils::GetPreviousContent( + *editableContent, {WalkTreeOption::IgnoreNonEditableNode}, BlockInlineCheck::Auto, &aEditingHost); if (NS_WARN_IF(!editableContent)) { return nullptr; } } else { - editableContent = HTMLEditUtils::GetNextLeafContent( - *editableContent, {LeafNodeOption::IgnoreNonEditableNode}, + editableContent = HTMLEditUtils::GetNextContent( + *editableContent, {WalkTreeOption::IgnoreNonEditableNode}, BlockInlineCheck::Auto, &aEditingHost); if (NS_WARN_IF(!editableContent)) { return nullptr; @@ -1203,167 +1419,135 @@ class HTMLEditUtils final { return editableContent; } - enum class LeafNodeOption { - // Treat a block element as a leaf node. - TreatChildBlockAsLeafNode, - // Treat a non-editable node as a leaf node. - TreatNonEditableNodeAsLeafNode, - // Ignore non-editable content. - IgnoreNonEditableNode, - // Treat a `Comment` node as a significant leaf node. + enum class LeafNodeType { + // Even if there is a child block, keep scanning a leaf content in it. + OnlyLeafNode, + // If there is a child block, return it too. Note that this does not + // mean that block siblings are not treated as leaf nodes. + LeafNodeOrChildBlock, + // If there is a non-editable element if and only if scanning from editable + // node, return it too. + LeafNodeOrNonEditableNode, + // Ignore non-editable content at walking the tree. + OnlyEditableLeafNode, + // Treat `Comment` nodes are empty leaf nodes. TreatCommentAsLeafNode, - // Ignore empty `Text` node. - IgnoreEmptyText, - // Ignore invisible `Text` node such as empty node or all data is collapsed. - IgnoreInvisibleText, - // Ignore invisible void elements such as <wbr> and <input type="hidden">. - IgnoreInvisibleInlineVoidElements, - // If set, ignore empty inline containers such as <span></span>. - IgnoreAnyEmptyInlineContainers, - // If set, ignore empty inline containers which is not visible. E.g., - // <span></span> is not ignored but <span style="border:1px solid"></span> - // and <span style="border:padding 1px"></span> are not ignored. - // XXX Currently, this does not work well if the inline container has only - // `::before` and/or `::after` content and the frame is dirty. - IgnoreInvisibleEmptyInlineContainers, }; - using LeafNodeOptions = EnumSet<LeafNodeOption>; + using LeafNodeTypes = EnumSet<LeafNodeType>; friend std::ostream& operator<<(std::ostream& aStream, - const LeafNodeOption& aOption); + const LeafNodeType& aLeafNodeType); friend std::ostream& operator<<(std::ostream& aStream, - const LeafNodeOptions& aOptions); - - private: - enum class IgnoreChildren : bool { No, Yes }; - enum class LeafNodeType { - NonEmptyContainer, - Leaf, - Ignore, - }; - [[nodiscard]] static LeafNodeType GetLeafNodeType( - const nsIContent& aContent, const LeafNodeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck, IgnoreChildren aIgnoreChildren); + const LeafNodeTypes& aLeafNodeTypes); - public: /** * GetLastLeafContent() returns rightmost leaf content in aNode. It depends - * on aOptions whether this which types of nodes are treated as leaf + * on aLeafNodeTypes whether this which types of nodes are treated as leaf * nodes. * - * @param aBlockInlineCheck Can be Unused if aOptions does not contain - * TreatChildBlockAsLeafNode. - */ - [[nodiscard]] static nsIContent* GetLastLeafContent( - const nsINode& aNode, const LeafNodeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused); - - /** - * GetFirstLeafContent() returns leftmost leaf content in aNode. It depends - * on aOptions whether this scans into a block child or treat block as a - * leaf. - * - * @param aBlockInlineCheck Can be Unused if aOptions does not contain - * TreatChildBlockAsLeafNode. - */ - [[nodiscard]] static nsIContent* GetFirstLeafContent( - 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. + * @param aBlockInlineCheck Can be Unused if aLeafNodeTypes does not contain + * LeafNodeOrCHildBlock. */ - template <typename PT, typename CT> - static nsIContent* GetNextLeafContent( - const EditorDOMPointBase<PT, CT>& aStartPoint, - const LeafNodeOptions& aOptions, BlockInlineCheck aBlockInlineCheck, + static nsIContent* GetLastLeafContent( + const nsINode& aNode, const LeafNodeTypes& aLeafNodeTypes, + BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused, const Element* aAncestorLimiter = nullptr) { - return GetNextLeafContentOrNextBlockElementImpl( - aStartPoint, StopAtBlockSibling::No, aOptions, aBlockInlineCheck, - aAncestorLimiter); + MOZ_ASSERT_IF( + aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode), + !aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode)); + // editor shouldn't touch child nodes which are replaced with native + // anonymous nodes. + if (aNode.IsElement() && + HTMLEditUtils::IsNeverElementContentsEditableByUser( + *aNode.AsElement())) { + return nullptr; + } + for (nsIContent* content = aNode.GetLastChild(); content;) { + if (aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode) && + !EditorUtils::IsEditableContent(*content, + EditorUtils::EditorType::HTML)) { + content = HTMLEditUtils::GetPreviousContent( + *content, {WalkTreeOption::IgnoreNonEditableNode}, + aBlockInlineCheck, aAncestorLimiter); + continue; + } + if (!aLeafNodeTypes.contains(LeafNodeType::TreatCommentAsLeafNode) && + content->IsComment()) { + content = content->GetPreviousSibling(); + continue; + } + if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrChildBlock) && + HTMLEditUtils::IsBlockElement( + *content, + UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck))) { + return content; + } + if (!content->HasChildren() || + HTMLEditUtils::IsNeverElementContentsEditableByUser(*content)) { + return content; + } + if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) && + !HTMLEditUtils::IsSimplyEditableNode(*content)) { + return content; + } + content = content->GetLastChild(); + } + return nullptr; } /** - * 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. + * GetFirstLeafContent() returns leftmost leaf content in aNode. It depends + * on aLeafNodeTypes whether this scans into a block child or treat block as a + * leaf. * - * @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. + * @param aBlockInlineCheck Can be Unused if aLeafNodeTypes does not contain + * LeafNodeOrCHildBlock. */ - template <typename PT, typename CT> - static nsIContent* GetPreviousLeafContent( - const EditorDOMPointBase<PT, CT>& aStartPoint, - const LeafNodeOptions& aOptions, BlockInlineCheck aBlockInlineCheck, + static nsIContent* GetFirstLeafContent( + const nsINode& aNode, const LeafNodeTypes& aLeafNodeTypes, + BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused, const Element* aAncestorLimiter = nullptr) { - return GetPreviousLeafContentOrPreviousBlockElementImpl( - aStartPoint, StopAtBlockSibling::No, aOptions, aBlockInlineCheck, - aAncestorLimiter); + MOZ_ASSERT_IF( + aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode), + !aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode)); + // editor shouldn't touch child nodes which are replaced with native + // anonymous nodes. + if (aNode.IsElement() && + HTMLEditUtils::IsNeverElementContentsEditableByUser( + *aNode.AsElement())) { + return nullptr; + } + for (nsIContent* content = aNode.GetFirstChild(); content;) { + if (aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode) && + !EditorUtils::IsEditableContent(*content, + EditorUtils::EditorType::HTML)) { + content = HTMLEditUtils::GetNextContent( + *content, {WalkTreeOption::IgnoreNonEditableNode}, + aBlockInlineCheck, aAncestorLimiter); + continue; + } + if (!aLeafNodeTypes.contains(LeafNodeType::TreatCommentAsLeafNode) && + content->IsComment()) { + content = content->GetNextSibling(); + continue; + } + if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrChildBlock) && + HTMLEditUtils::IsBlockElement( + *content, + UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck))) { + return content; + } + if (!content->HasChildren() || + HTMLEditUtils::IsNeverElementContentsEditableByUser(*content)) { + return content; + } + if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) && + !HTMLEditUtils::IsSimplyEditableNode(*content)) { + return content; + } + content = content->GetFirstChild(); + } + return nullptr; } /** @@ -1371,17 +1555,84 @@ class HTMLEditUtils final { * next block element of aStartContent inside aAncestorLimiter. * * @param aStartContent The start content to scan next content. - * @param aOptions See LeafNodeOption. + * @param aLeafNodeTypes See LeafNodeType. * @param aAncestorLimiter Optional, if you set this, it must be an * inclusive ancestor of aStartContent. */ static nsIContent* GetNextLeafContentOrNextBlockElement( - const nsIContent& aStartContent, const LeafNodeOptions& aOptions, + const nsIContent& aStartContent, const LeafNodeTypes& aLeafNodeTypes, BlockInlineCheck aBlockInlineCheck, const Element* aAncestorLimiter = nullptr) { - return GetNextLeafContentOrNextBlockElementImpl( - aStartContent, StopAtBlockSibling::Yes, aOptions, aBlockInlineCheck, - aAncestorLimiter); + MOZ_ASSERT_IF( + aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode), + !aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode)); + + if (&aStartContent == aAncestorLimiter) { + return nullptr; + } + + Element* container = aStartContent.GetParentElement(); + for (nsIContent* nextContent = aStartContent.GetNextSibling();;) { + if (!nextContent) { + if (!container) { + NS_WARNING("Reached orphan node while climbing up the DOM tree"); + return nullptr; + } + for (Element* parentElement : + container->InclusiveAncestorsOfType<Element>()) { + if (parentElement == aAncestorLimiter || + HTMLEditUtils::IsBlockElement( + *parentElement, + UseComputedDisplayStyleIfAuto(aBlockInlineCheck))) { + return nullptr; + } + if (aLeafNodeTypes.contains( + LeafNodeType::LeafNodeOrNonEditableNode) && + !parentElement->IsEditable()) { + return nullptr; + } + nextContent = parentElement->GetNextSibling(); + if (nextContent) { + container = nextContent->GetParentElement(); + break; + } + if (!parentElement->GetParentElement()) { + NS_WARNING("Reached orphan node while climbing up the DOM tree"); + return nullptr; + } + } + MOZ_ASSERT(nextContent); + } + + if (!aLeafNodeTypes.contains(LeafNodeType::TreatCommentAsLeafNode) && + nextContent->IsComment()) { + nextContent = nextContent->GetNextSibling(); + continue; + } + + // We have a next content. If it's a block, return it. + if (HTMLEditUtils::IsBlockElement( + *nextContent, + PreferDisplayOutsideIfUsingDisplay( + UseComputedDisplayStyleIfAuto(aBlockInlineCheck)))) { + return nextContent; + } + if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) && + !nextContent->IsEditable()) { + return nextContent; + } + if (HTMLEditUtils::IsContainerNode(*nextContent)) { + // Else if it's a container, get deep leftmost child + if (nsIContent* child = HTMLEditUtils::GetFirstLeafContent( + *nextContent, aLeafNodeTypes, aBlockInlineCheck)) { + return child; + } + } + // Else return the next content itself. + return nextContent; + } + MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE( + "Must return from the preceding for-loop"); } /** @@ -1391,11 +1642,76 @@ class HTMLEditUtils final { template <typename PT, typename CT> static nsIContent* GetNextLeafContentOrNextBlockElement( const EditorDOMPointBase<PT, CT>& aStartPoint, - const LeafNodeOptions& aOptions, BlockInlineCheck aBlockInlineCheck, + const LeafNodeTypes& aLeafNodeTypes, BlockInlineCheck aBlockInlineCheck, const Element* aAncestorLimiter = nullptr) { - return GetNextLeafContentOrNextBlockElementImpl( - aStartPoint, StopAtBlockSibling::Yes, aOptions, aBlockInlineCheck, - aAncestorLimiter); + MOZ_ASSERT(aStartPoint.IsSet()); + MOZ_ASSERT_IF( + aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode), + !aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode)); + NS_ASSERTION(!aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode), + "Not implemented yet"); + + if (!aStartPoint.IsInContentNode()) { + return nullptr; + } + if (aStartPoint.IsInTextNode()) { + return HTMLEditUtils::GetNextLeafContentOrNextBlockElement( + *aStartPoint.template ContainerAs<Text>(), aLeafNodeTypes, + aBlockInlineCheck, aAncestorLimiter); + } + if (!HTMLEditUtils::IsContainerNode( + *aStartPoint.template ContainerAs<nsIContent>())) { + return HTMLEditUtils::GetNextLeafContentOrNextBlockElement( + *aStartPoint.template ContainerAs<nsIContent>(), aLeafNodeTypes, + aBlockInlineCheck, aAncestorLimiter); + } + + for (nsIContent* nextContent = aStartPoint.GetChild();;) { + if (!nextContent) { + if (aStartPoint.GetContainer() == aAncestorLimiter || + HTMLEditUtils::IsBlockElement( + *aStartPoint.template ContainerAs<nsIContent>(), + 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<nsIContent>(), aLeafNodeTypes, + PreferDisplayOutsideIfUsingDisplay(aBlockInlineCheck), + aAncestorLimiter); + } + + if (!aLeafNodeTypes.contains(LeafNodeType::TreatCommentAsLeafNode) && + nextContent->IsComment()) { + nextContent = nextContent->GetNextSibling(); + continue; + } + + // We have a next node. If it's a block, return it. + if (HTMLEditUtils::IsBlockElement( + *nextContent, + UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck))) { + return nextContent; + } + if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) && + !HTMLEditUtils::IsSimplyEditableNode(*nextContent)) { + return nextContent; + } + if (HTMLEditUtils::IsContainerNode(*nextContent)) { + // else if it's a container, get deep leftmost child + if (nsIContent* child = HTMLEditUtils::GetFirstLeafContent( + *nextContent, aLeafNodeTypes, + PreferDisplayOutsideIfUsingDisplay(aBlockInlineCheck))) { + return child; + } + } + // Else return the node itself + return nextContent; + } + MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE( + "Must return from the preceding for-loop"); } /** @@ -1404,17 +1720,87 @@ class HTMLEditUtils final { * aAncestorLimiter. * * @param aStartContent The start content to scan previous content. - * @param aOptions See LeafNodeOption. + * @param aLeafNodeTypes See LeafNodeType. * @param aAncestorLimiter Optional, if you set this, it must be an * inclusive ancestor of aStartContent. */ static nsIContent* GetPreviousLeafContentOrPreviousBlockElement( - const nsIContent& aStartContent, const LeafNodeOptions& aOptions, + const nsIContent& aStartContent, const LeafNodeTypes& aLeafNodeTypes, BlockInlineCheck aBlockInlineCheck, const Element* aAncestorLimiter = nullptr) { - return GetPreviousLeafContentOrPreviousBlockElementImpl( - aStartContent, StopAtBlockSibling::Yes, aOptions, aBlockInlineCheck, - aAncestorLimiter); + MOZ_ASSERT_IF( + aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode), + !aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode)); + NS_ASSERTION(!aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode), + "Not implemented yet"); + + if (&aStartContent == aAncestorLimiter) { + return nullptr; + } + + Element* container = aStartContent.GetParentElement(); + for (nsIContent* previousContent = aStartContent.GetPreviousSibling();;) { + if (!previousContent) { + if (!container) { + NS_WARNING("Reached orphan node while climbing up the DOM tree"); + return nullptr; + } + for (Element* parentElement : + container->InclusiveAncestorsOfType<Element>()) { + if (parentElement == aAncestorLimiter || + HTMLEditUtils::IsBlockElement( + *parentElement, + UseComputedDisplayStyleIfAuto(aBlockInlineCheck))) { + return nullptr; + } + if (aLeafNodeTypes.contains( + LeafNodeType::LeafNodeOrNonEditableNode) && + !parentElement->IsEditable()) { + return nullptr; + } + previousContent = parentElement->GetPreviousSibling(); + if (previousContent) { + container = previousContent->GetParentElement(); + break; + } + if (!parentElement->GetParentElement()) { + NS_WARNING("Reached orphan node while climbing up the DOM tree"); + return nullptr; + } + } + MOZ_ASSERT(previousContent); + } + + if (!aLeafNodeTypes.contains(LeafNodeType::TreatCommentAsLeafNode) && + previousContent->IsComment()) { + previousContent = previousContent->GetPreviousSibling(); + continue; + } + + // We have a next content. If it's a block, return it. + if (HTMLEditUtils::IsBlockElement( + *previousContent, + PreferDisplayOutsideIfUsingDisplay( + UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck)))) { + return previousContent; + } + if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) && + !HTMLEditUtils::IsSimplyEditableNode(*previousContent)) { + return previousContent; + } + if (HTMLEditUtils::IsContainerNode(*previousContent)) { + // Else if it's a container, get deep rightmost child + if (nsIContent* child = HTMLEditUtils::GetLastLeafContent( + *previousContent, aLeafNodeTypes, + PreferDisplayOutsideIfUsingDisplay(aBlockInlineCheck))) { + return child; + } + } + // Else return the next content itself. + return previousContent; + } + MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE( + "Must return from the preceding for-loop"); } /** @@ -1424,122 +1810,167 @@ class HTMLEditUtils final { template <typename PT, typename CT> static nsIContent* GetPreviousLeafContentOrPreviousBlockElement( const EditorDOMPointBase<PT, CT>& aStartPoint, - const LeafNodeOptions& aOptions, BlockInlineCheck aBlockInlineCheck, + const LeafNodeTypes& aLeafNodeTypes, BlockInlineCheck aBlockInlineCheck, const Element* aAncestorLimiter = nullptr) { - return GetPreviousLeafContentOrPreviousBlockElementImpl( - aStartPoint, StopAtBlockSibling::Yes, aOptions, aBlockInlineCheck, - aAncestorLimiter); - } - - private: - static nsIContent* GetSibling(const nsIContent& aContent, - WalkTreeDirection aDirection, - const LeafNodeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck); - - public: - /** - * Return the preceding sibling of aContent with ignoring leaf nodes which are - * specified by aOptions. - * - * @param aOptions If a sibling is a leaf node or does not have meaningful - * children against aOptions, the sibling is ignored. - * NOTE: LeafNodeOption::TreatChildBlockAsLeafNode is - * ignored. - */ - static nsIContent* GetPreviousSibling(const nsIContent& aContent, - const LeafNodeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck) { - return GetSibling(aContent, WalkTreeDirection::Backward, aOptions, - aBlockInlineCheck); - } - - /** - * Return the following sibling of aContent with ignoring leaf nodes which are - * specified by aOptions. - * - * @param aOptions If a sibling is a leaf node or does not have meaningful - * children against aOptions, the sibling is ignored. - * NOTE: LeafNodeOption::TreatChildBlockAsLeafNode is - * ignored. - */ - static nsIContent* GetNextSibling(const nsIContent& aContent, - const LeafNodeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck) { - return GetSibling(aContent, WalkTreeDirection::Forward, aOptions, - aBlockInlineCheck); - } + MOZ_ASSERT(aStartPoint.IsSet()); + MOZ_ASSERT_IF( + aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode), + !aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode)); + NS_ASSERTION(!aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode), + "Not implemented yet"); - private: - enum class FirstOrLastChild { First, Last }; - static nsIContent* GetFirstOrLastChild(const nsINode& aNode, - FirstOrLastChild aFirstOrLastChild, - const LeafNodeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck); + if (!aStartPoint.IsInContentNode()) { + return nullptr; + } + if (aStartPoint.IsInTextNode()) { + return HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( + *aStartPoint.template ContainerAs<Text>(), aLeafNodeTypes, + aBlockInlineCheck, aAncestorLimiter); + } + if (!HTMLEditUtils::IsContainerNode( + *aStartPoint.template ContainerAs<nsIContent>())) { + return HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( + *aStartPoint.template ContainerAs<nsIContent>(), aLeafNodeTypes, + aBlockInlineCheck, aAncestorLimiter); + } - public: - /** - * Return the last child of aNode with ignoring leaf nodes which are specified - * by aOptions. - * - * @param aOptions If a child is a leaf node or does not have meaningful - * children against aOptions, the child is ignored. - * NOTE: LeafNodeOption::TreatChildBlockAsLeafNode is - * ignored. - */ - static nsIContent* GetLastChild(const nsINode& aNode, - const LeafNodeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck) { - return GetFirstOrLastChild(aNode, FirstOrLastChild::Last, aOptions, - aBlockInlineCheck); - } + if (aStartPoint.IsStartOfContainer()) { + if (aStartPoint.GetContainer() == aAncestorLimiter || + HTMLEditUtils::IsBlockElement( + *aStartPoint.template ContainerAs<nsIContent>(), + UseComputedDisplayStyleIfAuto(aBlockInlineCheck))) { + // We are at start of the block. + return nullptr; + } - /** - * Return the first child of aNode with ignoring leaf nodes which are - * specified by aOptions. - * - * @param aOptions If a child is a leaf node or does not have meaningful - * children against aOptions, the child is ignored. - * NOTE: LeafNodeOption::TreatChildBlockAsLeafNode is - * ignored. - */ - static nsIContent* GetFirstChild(const nsINode& aNode, - const LeafNodeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck) { - return GetFirstOrLastChild(aNode, FirstOrLastChild::First, aOptions, - aBlockInlineCheck); + // We are at start of non-block container + return HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( + *aStartPoint.template ContainerAs<nsIContent>(), aLeafNodeTypes, + PreferDisplayOutsideIfUsingDisplay(aBlockInlineCheck), + aAncestorLimiter); + } + + for (nsIContent* previousContent = aStartPoint.GetPreviousSiblingOfChild(); + previousContent && + (aLeafNodeTypes.contains(LeafNodeType::TreatCommentAsLeafNode) || + !previousContent->IsComment()); + previousContent = previousContent->GetPreviousSibling()) { + // We have a prior node. If it's a block, return it. + if (HTMLEditUtils::IsBlockElement( + *previousContent, + UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck))) { + return previousContent; + } + if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) && + !HTMLEditUtils::IsSimplyEditableNode(*previousContent)) { + return previousContent; + } + if (HTMLEditUtils::IsContainerNode(*previousContent)) { + // Else if it's a container, get deep rightmost child + if (nsIContent* child = HTMLEditUtils::GetLastLeafContent( + *previousContent, aLeafNodeTypes, + PreferDisplayOutsideIfUsingDisplay(aBlockInlineCheck))) { + return child; + } + } + // Else return the node itself + return previousContent; + } + return nullptr; } /** - * Return true if aContent is the last child of aNode with ignoring all - * children which are specified by aOptions. + * Return previous non-empty leaf content or child block or non-editable + * content (depending on aLeafNodeTypes). This ignores invisible inline leaf + * element like `<b></b>` and empty `Text` nodes. So, this may return + * invisible `Text` node, but it may be useful to consider whether we need to + * insert a padding <br> element. */ - static bool IsLastChild(const nsIContent& aContent, - const LeafNodeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck) { - nsINode* const parentNode = aContent.GetParentNode(); - if (MOZ_UNLIKELY(!parentNode)) { - return false; + [[nodiscard]] static nsIContent* + GetPreviousNonEmptyLeafContentOrPreviousBlockElement( + const nsIContent& aContent, const LeafNodeTypes& aLeafNodeTypes, + BlockInlineCheck aBlockInlineCheck, + const Element* aAncestorLimiter = nullptr) { + for (nsIContent* previousContent = + HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( + aContent, aLeafNodeTypes, aBlockInlineCheck, aAncestorLimiter); + previousContent; + previousContent = + HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( + *previousContent, aLeafNodeTypes, aBlockInlineCheck, + aAncestorLimiter)) { + if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrChildBlock) && + HTMLEditUtils::IsBlockElement( + *previousContent, + PreferDisplayOutsideIfUsingDisplay( + UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck)))) { + return previousContent; // Reached block element + } + if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) && + HTMLEditUtils::IsSimplyEditableNode(*previousContent)) { + return previousContent; // Reached non-editable content + } + Text* const previousText = Text::FromNode(previousContent); + if (!previousText) { + if (!HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*previousContent)) { + continue; // Ignore invisible inline elements + } + return previousContent; // Reached visible inline element + } + if (!previousText->TextDataLength()) { + continue; // Ignore empty Text nodes. + } + return previousText; // Reached non-empty text } - return HTMLEditUtils::GetLastChild(*parentNode, aOptions, - aBlockInlineCheck) == &aContent; + return nullptr; } /** - * Return true if aContent is the first child of aNode with ignoring all - * children which are specified by aOptions. + * Return previous visible leaf content or child block or non-editable content + * (depending on aLeafNodeTypes). This ignores invisible inline leaf element + * like `<b></b>` and empty `Text` nodes. So, this may return invisible + * `Text` node, but it may be useful to consider whether we need to insert a + * padding <br> element. */ - static bool IsFirstChild(const nsIContent& aContent, - const LeafNodeOptions& aOptions, - BlockInlineCheck aBlockInlineCheck) { - nsINode* const parentNode = aContent.GetParentNode(); - if (MOZ_UNLIKELY(!parentNode)) { - return false; + template <typename PT, typename CT> + [[nodiscard]] static nsIContent* + GetPreviousNonEmptyLeafContentOrPreviousBlockElement( + const EditorDOMPointBase<PT, CT>& aPoint, + const LeafNodeTypes& aLeafNodeTypes, BlockInlineCheck aBlockInlineCheck, + const Element* aAncestorLimiter = nullptr) { + for (nsIContent* previousContent = + HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( + aPoint, aLeafNodeTypes, aBlockInlineCheck, aAncestorLimiter); + previousContent; + previousContent = + HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( + *previousContent, aLeafNodeTypes, aBlockInlineCheck, + aAncestorLimiter)) { + if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrChildBlock) && + HTMLEditUtils::IsBlockElement( + *previousContent, + PreferDisplayOutsideIfUsingDisplay( + UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck)))) { + return previousContent; // Reached block element + } + if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) && + HTMLEditUtils::IsSimplyEditableNode(*previousContent)) { + return previousContent; // Reached non-editable content + } + Text* const previousText = Text::FromNode(previousContent); + if (!previousText) { + if (!HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*previousContent)) { + continue; // Ignore invisible inline elements + } + return previousContent; // Reached visible inline element + } + if (!previousText->TextDataLength()) { + continue; // Ignore empty Text nodes. + } + return previousText; // Reached non-empty text } - return HTMLEditUtils::GetFirstChild(*parentNode, aOptions, - aBlockInlineCheck) == &aContent; + return nullptr; } - /** * Returns a content node whose inline styles should be preserved after * deleting content in a range. Typically, you should set aPoint to start @@ -1990,30 +2421,6 @@ class HTMLEditUtils final { return GetTableCellElementIfOnlyOneSelected(*firstRange); } - 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 @@ -2028,7 +2435,7 @@ class HTMLEditUtils final { */ template <typename FirstElementName, typename... OtherElementNames> static Element* GetInclusiveDeepestFirstChildWhichHasOneChild( - const nsINode& aNode, const LeafNodeOptions& aOptions, + const nsINode& aNode, const WalkTreeOptions& aOptions, BlockInlineCheck aBlockInlineCheck, FirstElementName aFirstElementName, OtherElementNames... aOtherElementNames) { if (!aNode.IsElement()) { @@ -2038,10 +2445,11 @@ class HTMLEditUtils final { for (nsIContent* content = const_cast<nsIContent*>(aNode.AsContent()); content && content->IsElement() && content->IsAnyOfHTMLElements(aFirstElementName, aOtherElementNames...); - content = HTMLEditUtils::GetFirstChild(*content, aOptions, - aBlockInlineCheck)) { - if (HTMLEditUtils::CountMeaningfulChildren(*content, aOptions, - aBlockInlineCheck) != 1) { + // 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) { return content->AsElement(); } parentElement = content->AsElement(); @@ -2059,10 +2467,13 @@ class HTMLEditUtils final { template <typename EditorLineBreakType> static Maybe<EditorLineBreakType> GetFirstLineBreak( const dom::Element& aElement) { - for (nsIContent* content = HTMLEditUtils::GetFirstLeafContent(aElement, {}); - content; content = HTMLEditUtils::GetNextLeafContent( - *content, {LeafNodeOption::IgnoreInvisibleText}, - BlockInlineCheck::Auto, &aElement)) { + for (nsIContent* content = HTMLEditUtils::GetFirstLeafContent( + aElement, {LeafNodeType::OnlyLeafNode}); + content; content = HTMLEditUtils::GetNextContent( + *content, + {WalkTreeOption::IgnoreDataNodeExceptText, + WalkTreeOption::IgnoreWhiteSpaceOnlyText}, + BlockInlineCheck::Unused, &aElement)) { if (auto* brElement = dom::HTMLBRElement::FromNode(*content)) { return Some(EditorLineBreakType(*brElement)); } @@ -2854,6 +3265,57 @@ 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; + } + + /** + * 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 diff --git a/editor/libeditor/HTMLEditor.cpp b/editor/libeditor/HTMLEditor.cpp @@ -100,8 +100,9 @@ using namespace widget; LazyLogModule gHTMLEditorFocusLog("HTMLEditorFocus"); using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption; -using LeafNodeOption = HTMLEditUtils::LeafNodeOption; -using LeafNodeOptions = HTMLEditUtils::LeafNodeOptions; +using LeafNodeType = HTMLEditUtils::LeafNodeType; +using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes; +using WalkTreeOption = HTMLEditUtils::WalkTreeOption; // Some utilities to handle overloading of "A" tag for link and named anchor. static bool IsLinkTag(const nsAtom& aTagName) { @@ -1040,8 +1041,8 @@ nsresult HTMLEditor::CollapseSelectionToEndOfLastLeafNodeOfDocument() const { } auto pointToPutCaret = [&]() -> EditorRawDOMPoint { - nsCOMPtr<nsIContent> lastLeafContent = - HTMLEditUtils::GetLastLeafContent(*bodyOrDocumentElement, {}); + nsCOMPtr<nsIContent> lastLeafContent = HTMLEditUtils::GetLastLeafContent( + *bodyOrDocumentElement, {LeafNodeType::OnlyLeafNode}); if (!lastLeafContent) { return EditorRawDOMPoint::AtEndOf(*bodyOrDocumentElement); } @@ -1135,15 +1136,11 @@ nsresult HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode( } } - constexpr LeafNodeOptions leafNodeOptions = { - LeafNodeOption::TreatNonEditableNodeAsLeafNode, - LeafNodeOption::TreatChildBlockAsLeafNode, - // FIXME: Ignore empty inline containers such as <span></span> because we - // cannot visually put caret into it. - }; for (nsIContent* leafContent = HTMLEditUtils::GetFirstLeafContent( - *editingHost, leafNodeOptions, - BlockInlineCheck::UseComputedDisplayStyle); + *editingHost, + {LeafNodeType::LeafNodeOrNonEditableNode, + LeafNodeType::LeafNodeOrChildBlock}, + BlockInlineCheck::UseComputedDisplayStyle, editingHost); leafContent;) { // If we meet a non-editable node first, we should move caret to start // of the container block or editing host. @@ -1180,7 +1177,9 @@ nsresult HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode( // Chromium collapses selection to start of the editing host when this // is the last leaf content. So, we don't need special handling here. leafContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement( - *leafElement, leafNodeOptions, + *leafElement, + {LeafNodeType::LeafNodeOrNonEditableNode, + LeafNodeType::LeafNodeOrChildBlock}, BlockInlineCheck::UseComputedDisplayStyle, editingHost); continue; } @@ -1204,7 +1203,9 @@ nsresult HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode( } // If it's an invisible text node, keep scanning next leaf. leafContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement( - *leafContent, leafNodeOptions, + *leafContent, + {LeafNodeType::LeafNodeOrNonEditableNode, + LeafNodeType::LeafNodeOrChildBlock}, BlockInlineCheck::UseComputedDisplayStyle, editingHost); continue; } @@ -1239,15 +1240,19 @@ nsresult HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode( EmptyCheckOption::TreatNonEditableContentAsInvisible}) && !HTMLEditUtils::IsNeverElementContentsEditableByUser(*leafContent)) { leafContent = HTMLEditUtils::GetFirstLeafContent( - *leafContent, leafNodeOptions, - BlockInlineCheck::UseComputedDisplayStyle); + *leafContent, + {LeafNodeType::LeafNodeOrNonEditableNode, + LeafNodeType::LeafNodeOrChildBlock}, + BlockInlineCheck::UseComputedDisplayStyle, editingHost); continue; } // Otherwise, we must meet an empty block element or a data node like // comment node. Let's ignore it. leafContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement( - *leafContent, leafNodeOptions, + *leafContent, + {LeafNodeType::LeafNodeOrNonEditableNode, + LeafNodeType::LeafNodeOrChildBlock}, BlockInlineCheck::UseComputedDisplayStyle, editingHost); } @@ -1987,9 +1992,8 @@ nsresult HTMLEditor::InsertElementAtSelectionAsAction( // check for inserting a whole table at the end of a block. If so insert // a br after it. if (!aElement->IsHTMLElement(nsGkAtoms::table) || - !HTMLEditUtils::IsLastChild( - *aElement, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle)) { + !HTMLEditUtils::IsLastChild(*aElement, + {WalkTreeOption::IgnoreNonEditableNode})) { return NS_OK; } @@ -3302,9 +3306,7 @@ already_AddRefed<Element> HTMLEditor::GetSelectedElement(const nsAtom* aTagName, return nullptr; } nsIContent* firstEditableLeaf = HTMLEditUtils::GetFirstLeafContent( - *nextSibling, - // XXX Should we ignore invisible inline elements and text nodes? - {}); + *nextSibling, {LeafNodeType::OnlyLeafNode}); if (firstEditableLeaf && firstEditableLeaf->IsHTMLElement(nsGkAtoms::br)) { return nullptr; @@ -4932,18 +4934,16 @@ HTMLEditor::RemoveBlockContainerWithTransaction(Element& aElement) { } EditorDOMPoint pointToPutCaret; if (HTMLEditUtils::CanNodeContain(*parentElement, *nsGkAtoms::br)) { - if (const nsCOMPtr<nsIContent> child = HTMLEditUtils::GetFirstChild( - aElement, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle)) { + if (nsCOMPtr<nsIContent> child = HTMLEditUtils::GetFirstChild( + aElement, {WalkTreeOption::IgnoreNonEditableNode})) { // The case of aNode not being empty. We need a br at start unless: // 1) previous sibling of aNode is a block, OR // 2) previous sibling of aNode is a br, OR // 3) first child of aNode is a block OR // 4) either is null - if (nsIContent* const previousSibling = HTMLEditUtils::GetPreviousSibling( - aElement, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle)) { + if (nsIContent* previousSibling = HTMLEditUtils::GetPreviousSibling( + aElement, {WalkTreeOption::IgnoreNonEditableNode})) { if (!HTMLEditUtils::IsBlockElement( *previousSibling, BlockInlineCheck::UseComputedDisplayOutsideStyle) && @@ -4973,15 +4973,14 @@ HTMLEditor::RemoveBlockContainerWithTransaction(Element& aElement) { // 3) last child of aNode is a br OR // 4) either is null - if (nsIContent* const nextSibling = HTMLEditUtils::GetNextSibling( - aElement, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle)) { + if (nsIContent* nextSibling = HTMLEditUtils::GetNextSibling( + aElement, {WalkTreeOption::IgnoreNonEditableNode})) { if (nextSibling && !HTMLEditUtils::IsBlockElement( *nextSibling, BlockInlineCheck::UseComputedDisplayStyle)) { - if (nsIContent* const lastChild = HTMLEditUtils::GetLastChild( - aElement, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle)) { + if (nsIContent* lastChild = HTMLEditUtils::GetLastChild( + aElement, {WalkTreeOption::IgnoreNonEditableNode}, + BlockInlineCheck::Unused)) { if (!HTMLEditUtils::IsBlockElement( *lastChild, BlockInlineCheck::UseComputedDisplayStyle) && !lastChild->IsHTMLElement(nsGkAtoms::br)) { @@ -5004,10 +5003,8 @@ HTMLEditor::RemoveBlockContainerWithTransaction(Element& aElement) { } } } - } else if (nsIContent* const previousSibling = - HTMLEditUtils::GetPreviousSibling( - aElement, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle)) { + } else if (nsIContent* previousSibling = HTMLEditUtils::GetPreviousSibling( + aElement, {WalkTreeOption::IgnoreNonEditableNode})) { // The case of aNode being empty. We need a br at start unless: // 1) previous sibling of aNode is a block, OR // 2) previous sibling of aNode is a br, OR @@ -5018,8 +5015,7 @@ HTMLEditor::RemoveBlockContainerWithTransaction(Element& aElement) { *previousSibling, BlockInlineCheck::UseComputedDisplayStyle) && !previousSibling->IsHTMLElement(nsGkAtoms::br)) { if (nsIContent* nextSibling = HTMLEditUtils::GetNextSibling( - aElement, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle)) { + aElement, {WalkTreeOption::IgnoreNonEditableNode})) { if (!HTMLEditUtils::IsBlockElement( *nextSibling, BlockInlineCheck::UseComputedDisplayStyle) && !nextSibling->IsHTMLElement(nsGkAtoms::br)) { @@ -6684,14 +6680,16 @@ HTMLEditor::CopyLastEditableChildStylesWithTransaction( // Look for the deepest last editable leaf node in aPreviousBlock. // Then, if found one is a <br> element, look for non-<br> element. - nsIContent* deepestEditableContent = HTMLEditUtils::GetPreviousLeafContent( - EditorRawDOMPoint::AtEndOf(aPreviousBlock), - {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle); + nsIContent* deepestEditableContent = nullptr; + for (nsCOMPtr<nsIContent> child = &aPreviousBlock; child; + child = HTMLEditUtils::GetLastChild( + *child, {WalkTreeOption::IgnoreNonEditableNode})) { + deepestEditableContent = child; + } while (deepestEditableContent && deepestEditableContent->IsHTMLElement(nsGkAtoms::br)) { - deepestEditableContent = HTMLEditUtils::GetPreviousLeafContent( - *deepestEditableContent, {LeafNodeOption::IgnoreNonEditableNode}, + deepestEditableContent = HTMLEditUtils::GetPreviousContent( + *deepestEditableContent, {WalkTreeOption::IgnoreNonEditableNode}, BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost); } if (!deepestEditableContent) { diff --git a/editor/libeditor/HTMLEditorDataTransfer.cpp b/editor/libeditor/HTMLEditorDataTransfer.cpp @@ -97,7 +97,7 @@ namespace mozilla { using namespace dom; using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption; -using LeafNodeOption = HTMLEditUtils::LeafNodeOption; +using LeafNodeType = HTMLEditUtils::LeafNodeType; #define kInsertCookie "_moz_Insert Here_moz_" @@ -506,13 +506,9 @@ HTMLEditor::HTMLWithContextInserter::GetNewCaretPointAfterInsertingHTML( if (!aLastInsertedPoint.GetChild() || !aLastInsertedPoint.GetChild()->IsHTMLElement(nsGkAtoms::table)) { containerContent = HTMLEditUtils::GetLastLeafContent( - *aLastInsertedPoint.GetChild(), - { - LeafNodeOption::IgnoreNonEditableNode, - LeafNodeOption::IgnoreInvisibleText, - // FIXME: We cannot visually put caret into empty inline containers - // like <span></span> so that let's ignore them. - }); + *aLastInsertedPoint.GetChild(), {LeafNodeType::OnlyEditableLeafNode}, + BlockInlineCheck::Unused, + aLastInsertedPoint.GetChild()->GetAsElementOrParentElement()); if (containerContent) { Element* mostDistantInclusiveAncestorTableElement = nullptr; for (Element* maybeTableElement = @@ -563,12 +559,9 @@ HTMLEditor::HTMLWithContextInserter::GetNewCaretPointAfterInsertingHTML( {WSRunScanner::Option::OnlyEditableNodes}, EditorRawDOMPoint(prevVisibleThing.BRElementPtr())); if (prevVisibleThingOfBRElement.InVisibleOrCollapsibleCharacters()) { - // XXX Why not end of the text node? pointToPutCaret = prevVisibleThingOfBRElement .PointAfterReachedContent<EditorDOMPoint>(); - } else if (prevVisibleThingOfBRElement.ReachedSpecialContent() || - prevVisibleThingOfBRElement - .ReachedEmptyInlineContainerElement()) { + } else if (prevVisibleThingOfBRElement.ReachedSpecialContent()) { pointToPutCaret = prevVisibleThingOfBRElement .PointAfterReachedContentNode<EditorDOMPoint>(); } diff --git a/editor/libeditor/HTMLEditorDeleteHandler.cpp b/editor/libeditor/HTMLEditorDeleteHandler.cpp @@ -60,9 +60,10 @@ using EditablePointOption = HTMLEditUtils::EditablePointOption; using EditablePointOptions = HTMLEditUtils::EditablePointOptions; using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption; using InvisibleWhiteSpaces = HTMLEditUtils::InvisibleWhiteSpaces; -using LeafNodeOption = HTMLEditUtils::LeafNodeOption; +using LeafNodeType = HTMLEditUtils::LeafNodeType; using ScanLineBreak = HTMLEditUtils::ScanLineBreak; using TableBoundary = HTMLEditUtils::TableBoundary; +using WalkTreeOption = HTMLEditUtils::WalkTreeOption; static LazyLogModule gOneLineMoverLog("AutoMoveOneLineHandler"); @@ -865,7 +866,7 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDelete( if (scanFromCaretPointResult.BRElementPtr() == &aEditingHost) { return NS_OK; } - if (!scanFromCaretPointResult.ContentIsEditable()) { + if (!scanFromCaretPointResult.IsContentEditable()) { return NS_SUCCESS_DOM_NO_OPERATION; } if (scanFromCaretPointResult.ReachedInvisibleBRElement()) { @@ -1161,7 +1162,7 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::Run( if (scanFromCaretPointResult.BRElementPtr() == &aEditingHost) { return EditActionResult::HandledResult(); } - if (!scanFromCaretPointResult.ContentIsEditable()) { + if (!scanFromCaretPointResult.IsContentEditable()) { return EditActionResult::CanceledResult(); } if (scanFromCaretPointResult.ReachedInvisibleBRElement()) { @@ -1275,7 +1276,6 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAroundCollapsedRanges( } if (aScanFromCaretPointResult.ReachedSpecialContent() || - aScanFromCaretPointResult.ReachedEmptyInlineContainerElement() || aScanFromCaretPointResult.ReachedBRElement() || aScanFromCaretPointResult.ReachedHRElement() || aScanFromCaretPointResult.ReachedNonEditableOtherBlockElement()) { @@ -1408,7 +1408,6 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAroundCollapsedRanges( } if (aScanFromCaretPointResult.ReachedSpecialContent() || - aScanFromCaretPointResult.ReachedEmptyInlineContainerElement() || aScanFromCaretPointResult.ReachedBRElement() || aScanFromCaretPointResult.ReachedHRElement() || aScanFromCaretPointResult.ReachedNonEditableOtherBlockElement()) { @@ -1658,8 +1657,7 @@ nsIContent* HTMLEditor::AutoDeleteRangesHandler::GetAtomicContentToDelete( const WSScanResult& aScanFromCaretPointResult) { MOZ_ASSERT(aScanFromCaretPointResult.GetContent()); - if (!aScanFromCaretPointResult.ReachedSpecialContent() && - !aScanFromCaretPointResult.ReachedEmptyInlineContainerElement()) { + if (!aScanFromCaretPointResult.ReachedSpecialContent()) { return aScanFromCaretPointResult.GetContent(); } @@ -1957,10 +1955,11 @@ nsIContent* HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: MOZ_ASSERT(mOtherBlockElement); return aDirectionAndAmount == nsIEditor::ePrevious ? HTMLEditUtils::GetLastLeafContent( - *mOtherBlockElement, {LeafNodeOption::IgnoreNonEditableNode}) + *mOtherBlockElement, {LeafNodeType::OnlyEditableLeafNode}, + BlockInlineCheck::Unused, mOtherBlockElement) : HTMLEditUtils::GetFirstLeafContent( - *mOtherBlockElement, - {LeafNodeOption::IgnoreNonEditableNode}); + *mOtherBlockElement, {LeafNodeType::OnlyEditableLeafNode}, + BlockInlineCheck::Unused, mOtherBlockElement); } nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: @@ -2083,7 +2082,7 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler:: const WSScanResult maybePreviousText = scanner.ScanPreviousVisibleNodeOrBlockBoundaryFrom( EditorRawDOMPoint(mBRElement)); - if (maybePreviousText.ContentIsEditable() && + if (maybePreviousText.IsContentEditable() && maybePreviousText.InVisibleOrCollapsibleCharacters() && !HTMLEditor::GetLinkElement(maybePreviousText.TextPtr())) { return maybePreviousText.PointAfterReachedContent<EditorDOMPoint>(); @@ -2091,7 +2090,7 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler:: const WSScanResult maybeNextText = scanner.ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom( EditorRawDOMPoint::After(*mBRElement)); - if (maybeNextText.ContentIsEditable() && + if (maybeNextText.IsContentEditable() && maybeNextText.InVisibleOrCollapsibleCharacters()) { return maybeNextText.PointAtReachedContent<EditorDOMPoint>(); } @@ -2550,15 +2549,15 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: return false; } - auto ScanJoinTarget = [&]() MOZ_NEVER_INLINE_DEBUG -> nsIContent* { + auto ScanJoinTarget = [&]() -> nsIContent* { nsIContent* targetContent = aDirectionAndAmount == nsIEditor::ePrevious - ? HTMLEditUtils::GetPreviousLeafContent( - aCurrentBlockElement, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::Auto, &aEditingHost) - : HTMLEditUtils::GetNextLeafContent( - aCurrentBlockElement, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::Auto, &aEditingHost); + ? HTMLEditUtils::GetPreviousContent( + aCurrentBlockElement, {WalkTreeOption::IgnoreNonEditableNode}, + BlockInlineCheck::Unused, &aEditingHost) + : HTMLEditUtils::GetNextContent( + aCurrentBlockElement, {WalkTreeOption::IgnoreNonEditableNode}, + BlockInlineCheck::Unused, &aEditingHost); // If found content is an invisible text node, let's scan visible things. auto IsIgnorableDataNode = [](nsIContent* aContent) { return aContent && HTMLEditUtils::IsRemovableNode(*aContent) && @@ -2573,27 +2572,23 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: MOZ_ASSERT(mSkippedInvisibleContents.IsEmpty()); for (nsIContent* adjacentContent = aDirectionAndAmount == nsIEditor::ePrevious - ? HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( - *targetContent, - {LeafNodeOption::TreatChildBlockAsLeafNode}, + ? HTMLEditUtils::GetPreviousContent( + *targetContent, {WalkTreeOption::StopAtBlockBoundary}, BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost) - : HTMLEditUtils::GetNextLeafContentOrNextBlockElement( - *targetContent, - {LeafNodeOption::TreatChildBlockAsLeafNode}, + : HTMLEditUtils::GetNextContent( + *targetContent, {WalkTreeOption::StopAtBlockBoundary}, BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost); adjacentContent; adjacentContent = aDirectionAndAmount == nsIEditor::ePrevious - ? HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( - *adjacentContent, - {LeafNodeOption::TreatChildBlockAsLeafNode}, + ? HTMLEditUtils::GetPreviousContent( + *adjacentContent, {WalkTreeOption::StopAtBlockBoundary}, BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost) - : HTMLEditUtils::GetNextLeafContentOrNextBlockElement( - *adjacentContent, - {LeafNodeOption::TreatChildBlockAsLeafNode}, + : HTMLEditUtils::GetNextContent( + *adjacentContent, {WalkTreeOption::StopAtBlockBoundary}, BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost)) { // If non-editable element is found, we should not skip it to avoid @@ -2608,10 +2603,9 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: nsIContent* leafContent = aDirectionAndAmount == nsIEditor::ePrevious ? HTMLEditUtils::GetLastLeafContent( - *adjacentContent, {LeafNodeOption::IgnoreNonEditableNode}) + *adjacentContent, {LeafNodeType::OnlyEditableLeafNode}) : HTMLEditUtils::GetFirstLeafContent( - *adjacentContent, - {LeafNodeOption::IgnoreNonEditableNode}); + *adjacentContent, {LeafNodeType::OnlyEditableLeafNode}); mSkippedInvisibleContents.AppendElement(*targetContent); return leafContent ? leafContent : adjacentContent; } @@ -3711,7 +3705,7 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler:: const WSScanResult maybePreviousText = WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( {}, startOfRightContent, &aEditingHost); - if (maybePreviousText.ContentIsEditable() && + if (maybePreviousText.IsContentEditable() && maybePreviousText.InVisibleOrCollapsibleCharacters()) { nsresult rv = aHTMLEditor.CollapseSelectionTo( maybePreviousText.PointAfterReachedContent<EditorRawDOMPoint>()); @@ -4622,11 +4616,9 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler:: Element::FromNodeOrNull(moveFirstLineResult.DeleteRangeRef() .GetClosestCommonInclusiveAncestor()); nsIContent* const previousVisibleLeafOrChildBlock = - HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( + HTMLEditUtils::GetPreviousNonEmptyLeafContentOrPreviousBlockElement( moveFirstLineResult.DeleteRangeRef().EndRef(), - {LeafNodeOption::TreatChildBlockAsLeafNode, - LeafNodeOption::IgnoreInvisibleEmptyInlineContainers, - LeafNodeOption::IgnoreInvisibleText}, + {LeafNodeType::LeafNodeOrChildBlock}, BlockInlineCheck::UseComputedDisplayOutsideStyle, commonAncestor); if (!previousVisibleLeafOrChildBlock) { return false; @@ -5049,11 +5041,10 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangeToDeleteRangeWithTransaction( EditorRawDOMPoint caretPoint(aRangeToDelete.StartRef()); if (howToHandleCollapsedRange == EditorBase::HowToHandleCollapsedRange::ExtendBackward && - caretPoint.IsStartOfContainer() && caretPoint.IsInContentNode()) { - nsIContent* previousEditableContent = HTMLEditUtils::GetPreviousLeafContent( - *caretPoint.ContainerAs<nsIContent>(), - {LeafNodeOption::IgnoreNonEditableNode}, BlockInlineCheck::Auto, - &aEditingHost); + caretPoint.IsStartOfContainer()) { + nsIContent* previousEditableContent = HTMLEditUtils::GetPreviousContent( + *caretPoint.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode}, + BlockInlineCheck::Unused, &aEditingHost); if (!previousEditableContent) { return NS_OK; } @@ -5073,11 +5064,10 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangeToDeleteRangeWithTransaction( if (howToHandleCollapsedRange == EditorBase::HowToHandleCollapsedRange::ExtendForward && - caretPoint.IsEndOfContainer() && caretPoint.IsInContentNode()) { - nsIContent* nextEditableContent = HTMLEditUtils::GetNextLeafContent( - *caretPoint.ContainerAs<nsIContent>(), - {LeafNodeOption::IgnoreNonEditableNode}, BlockInlineCheck::Auto, - &aEditingHost); + caretPoint.IsEndOfContainer()) { + nsIContent* nextEditableContent = HTMLEditUtils::GetNextContent( + *caretPoint.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode}, + BlockInlineCheck::Unused, &aEditingHost); if (!nextEditableContent) { return NS_OK; } @@ -5114,12 +5104,12 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangeToDeleteRangeWithTransaction( nsIContent* editableContent = howToHandleCollapsedRange == EditorBase::HowToHandleCollapsedRange::ExtendBackward - ? HTMLEditUtils::GetPreviousLeafContent( - caretPoint, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::Auto, &aEditingHost) - : HTMLEditUtils::GetNextLeafContent( - caretPoint, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::Auto, &aEditingHost); + ? HTMLEditUtils::GetPreviousContent( + caretPoint, {WalkTreeOption::IgnoreNonEditableNode}, + BlockInlineCheck::Unused, &aEditingHost) + : HTMLEditUtils::GetNextContent( + caretPoint, {WalkTreeOption::IgnoreNonEditableNode}, + BlockInlineCheck::Unused, &aEditingHost); if (!editableContent) { return NS_OK; } @@ -5128,12 +5118,12 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangeToDeleteRangeWithTransaction( editableContent = howToHandleCollapsedRange == EditorBase::HowToHandleCollapsedRange::ExtendBackward - ? HTMLEditUtils::GetPreviousLeafContent( - *editableContent, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::Auto, &aEditingHost) - : HTMLEditUtils::GetNextLeafContent( - *editableContent, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::Auto, &aEditingHost); + ? HTMLEditUtils::GetPreviousContent( + *editableContent, {WalkTreeOption::IgnoreNonEditableNode}, + BlockInlineCheck::Unused, &aEditingHost) + : HTMLEditUtils::GetNextContent( + *editableContent, {WalkTreeOption::IgnoreNonEditableNode}, + BlockInlineCheck::Unused, &aEditingHost); } if (!editableContent) { return NS_OK; @@ -5594,9 +5584,10 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: atStart.IsEndOfContainer() && range.StartRef().GetChild() && HTMLEditUtils::IsInvisibleBRElement( *range.StartRef().GetChild()) - ? HTMLEditUtils::GetNextLeafContentOrNextBlockElement( + ? HTMLEditUtils::GetNextContent( *atStart.ContainerAs<nsIContent>(), - {LeafNodeOption::TreatChildBlockAsLeafNode}, + {WalkTreeOption::IgnoreDataNodeExceptText, + WalkTreeOption::StopAtBlockBoundary}, BlockInlineCheck::UseComputedDisplayOutsideStyle, editingHost) : nullptr; @@ -5666,7 +5657,7 @@ Result<DeleteRangeResult, nsresult> HTMLEditor::AutoDeleteRangesHandler:: const WSScanResult maybePreviousText = WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( {}, maybeDeepStartOfRightContent, &aEditingHost); - if (maybePreviousText.ContentIsEditable() && + if (maybePreviousText.IsContentEditable() && maybePreviousText.InVisibleOrCollapsibleCharacters()) { return maybePreviousText.PointAfterReachedContent<EditorDOMPoint>(); } @@ -6370,14 +6361,14 @@ nsresult HTMLEditor::AutoMoveOneLineHandler:: const RefPtr<Text> textNodeEndingWithUnnecessaryLineBreak = [&]() -> Text* { Text* lastTextNode = Text::FromNodeOrNull( mMovingToParentBlock - ? HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( + ? HTMLEditUtils::GetPreviousContent( *mTopmostSrcAncestorBlockInDestBlock, - {LeafNodeOption::TreatChildBlockAsLeafNode}, + {WalkTreeOption::StopAtBlockBoundary}, BlockInlineCheck::UseComputedDisplayOutsideStyle, mDestInclusiveAncestorBlock) : HTMLEditUtils::GetLastLeafContent( *mDestInclusiveAncestorBlock, - {LeafNodeOption::TreatNonEditableNodeAsLeafNode})); + {LeafNodeType::LeafNodeOrNonEditableNode})); if (!lastTextNode || !HTMLEditUtils::IsSimplyEditableNode(*lastTextNode)) { return nullptr; @@ -7159,10 +7150,8 @@ HTMLEditor::AutoDeleteRangesHandler::AutoEmptyBlockAncestorDeleter:: // last list item is deleted. We should follow it since current // behavior is annoying when you type new list item with selecting // all list items. - if (!HTMLEditUtils::IsFirstChild( - *mEmptyInclusiveAncestorBlockElement, - {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle)) { + if (!HTMLEditUtils::IsFirstChild(*mEmptyInclusiveAncestorBlockElement, + {WalkTreeOption::IgnoreNonEditableNode})) { return CreateLineBreakResult::NotHandled(); } @@ -7218,8 +7207,8 @@ Result<CaretPoint, nsresult> HTMLEditor::AutoDeleteRangesHandler:: for (EditorRawDOMPoint scanStartPoint = EditorRawDOMPoint::After(mEmptyInclusiveAncestorBlockElement); scanStartPoint.IsInContentNode();) { - nsIContent* const nextContent = HTMLEditUtils::GetNextLeafContent( - scanStartPoint, {}, BlockInlineCheck::Auto, &aEditingHost); + nsIContent* const nextContent = HTMLEditUtils::GetNextContent( + scanStartPoint, {}, BlockInlineCheck::Unused, &aEditingHost); // Let's ignore invisible `Text`. if (nextContent && nextContent->IsText() && !HTMLEditUtils::IsVisibleTextNode(*nextContent->AsText())) { @@ -7255,10 +7244,9 @@ Result<CaretPoint, nsresult> HTMLEditor::AutoDeleteRangesHandler:: for (EditorRawDOMPoint scanStartPoint = EditorRawDOMPoint(mEmptyInclusiveAncestorBlockElement); scanStartPoint.IsInContentNode();) { - nsIContent* const previousContent = - HTMLEditUtils::GetPreviousLeafContent( - scanStartPoint, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::Auto, &aEditingHost); + nsIContent* const previousContent = HTMLEditUtils::GetPreviousContent( + scanStartPoint, {WalkTreeOption::IgnoreNonEditableNode}, + BlockInlineCheck::Unused, &aEditingHost); // Let's ignore invisible `Text`. if (previousContent && previousContent->IsText() && !HTMLEditUtils::IsVisibleTextNode(*previousContent->AsText())) { diff --git a/editor/libeditor/HTMLEditorInsertLineBreakHandler.cpp b/editor/libeditor/HTMLEditorInsertLineBreakHandler.cpp @@ -235,14 +235,10 @@ nsresult HTMLEditor::AutoInsertLineBreakHandler::HandleInsertBRElement() { pointToPutCaret = forwardScanFromAfterBRElementResult .PointAtReachedContent<EditorDOMPoint>(); } else if (forwardScanFromAfterBRElementResult.ReachedSpecialContent()) { - pointToPutCaret = forwardScanFromAfterBRElementResult - .PointAtReachedContent<EditorDOMPoint>(); - } else if (forwardScanFromAfterBRElementResult - .ReachedEmptyInlineContainerElement()) { // Next inserting text should be inserted into styled inline elements if // they have first visible thing in the new line. - pointToPutCaret = - EditorDOMPoint(forwardScanFromAfterBRElementResult.ElementPtr(), 0); + pointToPutCaret = forwardScanFromAfterBRElementResult + .PointAtReachedContent<EditorDOMPoint>(); } nsresult rv = mHTMLEditor.CollapseSelectionTo(pointToPutCaret); diff --git a/editor/libeditor/HTMLEditorInsertParagraphHandler.cpp b/editor/libeditor/HTMLEditorInsertParagraphHandler.cpp @@ -49,8 +49,9 @@ namespace mozilla { using namespace dom; using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption; using EmptyCheckOptions = HTMLEditUtils::EmptyCheckOptions; -using LeafNodeOption = HTMLEditUtils::LeafNodeOption; -using LeafNodeOptions = HTMLEditUtils::LeafNodeOptions; +using LeafNodeType = HTMLEditUtils::LeafNodeType; +using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes; +using WalkTreeOption = HTMLEditUtils::WalkTreeOption; Result<EditActionResult, nsresult> HTMLEditor::InsertParagraphSeparatorAsSubAction(const Element& aEditingHost) { @@ -1059,9 +1060,7 @@ nsresult HTMLEditor::AutoInsertParagraphHandler:: if (!backwardScanFromPointToCreateNewBRElementResult .InVisibleOrCollapsibleCharacters() && !backwardScanFromPointToCreateNewBRElementResult - .ReachedSpecialContent() && - !backwardScanFromPointToCreateNewBRElementResult - .ReachedEmptyInlineContainerElement()) { + .ReachedSpecialContent()) { return NS_SUCCESS_DOM_NO_OPERATION; } const WSScanResult forwardScanFromPointAfterNewBRElementResult = @@ -1076,8 +1075,6 @@ nsresult HTMLEditor::AutoInsertParagraphHandler:: if (!forwardScanFromPointAfterNewBRElementResult .InVisibleOrCollapsibleCharacters() && !forwardScanFromPointAfterNewBRElementResult.ReachedSpecialContent() && - !forwardScanFromPointAfterNewBRElementResult - .ReachedEmptyInlineContainerElement() && // In case we're at the very end. !forwardScanFromPointAfterNewBRElementResult .ReachedCurrentBlockBoundary()) { @@ -1219,8 +1216,7 @@ bool HTMLEditor::AutoInsertParagraphHandler::ShouldCreateNewParagraph( const auto* const precedingBRElement = HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetPreviousSibling( *aPointToSplit.ContainerAs<Text>(), - {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle)); + {WalkTreeOption::IgnoreNonEditableNode})); return !IsNullOrInvisibleBRElementOrPaddingOneForEmptyLastLine( precedingBRElement); } @@ -1232,8 +1228,7 @@ bool HTMLEditor::AutoInsertParagraphHandler::ShouldCreateNewParagraph( const auto* const followingBRElement = HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetNextSibling( *aPointToSplit.ContainerAs<Text>(), - {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle)); + {WalkTreeOption::IgnoreNonEditableNode})); return !IsNullOrInvisibleBRElementOrPaddingOneForEmptyLastLine( followingBRElement); } @@ -1251,9 +1246,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::GetPreviousLeafContent( - aPointToSplit, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::Auto, &mEditingHost)); + HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetPreviousContent( + aPointToSplit, {WalkTreeOption::IgnoreNonEditableNode}, + BlockInlineCheck::Unused, &mEditingHost)); if (!IsNullOrInvisibleBRElementOrPaddingOneForEmptyLastLine( precedingBRElement)) { return true; @@ -1262,9 +1257,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::GetNextLeafContent( - aPointToSplit, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::Auto, &mEditingHost)); + HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetNextContent( + aPointToSplit, {WalkTreeOption::IgnoreNonEditableNode}, + BlockInlineCheck::Unused, &mEditingHost)); return !IsNullOrInvisibleBRElementOrPaddingOneForEmptyLastLine( followingBRElement); } @@ -1754,7 +1749,7 @@ HTMLEditor::AutoInsertParagraphHandler::SplitParagraphWithTransaction( // Let's put caret at start of the first leaf container. nsIContent* child = HTMLEditUtils::GetFirstLeafContent( - *rightDivOrParagraphElement, {LeafNodeOption::TreatChildBlockAsLeafNode}, + *rightDivOrParagraphElement, {LeafNodeType::LeafNodeOrChildBlock}, BlockInlineCheck::UseComputedDisplayStyle); if (MOZ_UNLIKELY(!child)) { return SplitNodeResult(std::move(splitDivOrPResult), @@ -1835,9 +1830,8 @@ HTMLEditor::AutoInsertParagraphHandler::HandleInListItemElement( // If the given list item element is not the last list item element of // its parent nor not followed by sub list elements, split the parent // before it. - if (!HTMLEditUtils::IsLastChild( - aListItemElement, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle)) { + if (!HTMLEditUtils::IsLastChild(aListItemElement, + {WalkTreeOption::IgnoreNonEditableNode})) { Result<SplitNodeResult, nsresult> splitListItemParentResult = mHTMLEditor.SplitNodeWithTransaction( EditorDOMPoint(&aListItemElement)); diff --git a/editor/libeditor/HTMLEditorState.cpp b/editor/libeditor/HTMLEditorState.cpp @@ -38,7 +38,7 @@ namespace mozilla { using namespace dom; using EditorType = EditorUtils::EditorType; -using LeafNodeOption = HTMLEditUtils::LeafNodeOption; +using WalkTreeOption = HTMLEditUtils::WalkTreeOption; /***************************************************************************** * ListElementSelectionState @@ -282,9 +282,9 @@ AlignStateAtSelection::AlignStateAtSelection(HTMLEditor& aHTMLEditor, else if (atStartOfSelection.IsContainerHTMLElement(nsGkAtoms::html) && atBodyOrDocumentElement.IsSet() && atStartOfSelection.Offset() == atBodyOrDocumentElement.Offset()) { - editTargetContent = HTMLEditUtils::GetNextLeafContent( - atStartOfSelection, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::Auto, aHTMLEditor.ComputeEditingHost()); + editTargetContent = HTMLEditUtils::GetNextContent( + atStartOfSelection, {WalkTreeOption::IgnoreNonEditableNode}, + BlockInlineCheck::Unused, aHTMLEditor.ComputeEditingHost()); if (NS_WARN_IF(!editTargetContent)) { aRv.Throw(NS_ERROR_FAILURE); return; diff --git a/editor/libeditor/HTMLStyleEditor.cpp b/editor/libeditor/HTMLStyleEditor.cpp @@ -64,7 +64,9 @@ using namespace dom; using EditablePointOption = HTMLEditUtils::EditablePointOption; using EditablePointOptions = HTMLEditUtils::EditablePointOptions; using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption; -using LeafNodeOption = HTMLEditUtils::LeafNodeOption; +using LeafNodeType = HTMLEditUtils::LeafNodeType; +using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes; +using WalkTreeOption = HTMLEditUtils::WalkTreeOption; template nsresult HTMLEditor::SetInlinePropertiesAsSubAction( const AutoTArray<EditorInlineStyleAndValue, 1>& aStylesToSet, @@ -950,8 +952,7 @@ HTMLEditor::AutoInlineStyleSetter::SplitTextNodeAndApplyStyleToMiddleNode( if (mAttribute) { // Look for siblings that are correct type of node nsIContent* sibling = HTMLEditUtils::GetPreviousSibling( - *middleTextNode, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle); + *middleTextNode, {WalkTreeOption::IgnoreNonEditableNode}); if (sibling && sibling->IsElement()) { OwningNonNull<Element> element(*sibling->AsElement()); Result<bool, nsresult> result = @@ -980,8 +981,7 @@ HTMLEditor::AutoInlineStyleSetter::SplitTextNodeAndApplyStyleToMiddleNode( } } sibling = HTMLEditUtils::GetNextSibling( - *middleTextNode, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle); + *middleTextNode, {WalkTreeOption::IgnoreNonEditableNode}); if (sibling && sibling->IsElement()) { OwningNonNull<Element> element(*sibling->AsElement()); Result<bool, nsresult> result = @@ -1062,13 +1062,10 @@ Result<CaretPoint, nsresult> HTMLEditor::AutoInlineStyleSetter::ApplyStyle( } // First check if there's an adjacent sibling we can put our node into. - const nsCOMPtr<nsIContent> previousSibling = - HTMLEditUtils::GetPreviousSibling( - aContent, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle); - const nsCOMPtr<nsIContent> nextSibling = HTMLEditUtils::GetNextSibling( - aContent, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle); + nsCOMPtr<nsIContent> previousSibling = HTMLEditUtils::GetPreviousSibling( + aContent, {WalkTreeOption::IgnoreNonEditableNode}); + nsCOMPtr<nsIContent> nextSibling = HTMLEditUtils::GetNextSibling( + aContent, {WalkTreeOption::IgnoreNonEditableNode}); if (RefPtr<Element> previousElement = Element::FromNodeOrNull(previousSibling)) { Result<bool, nsresult> canMoveIntoPreviousSibling = @@ -2397,7 +2394,7 @@ Result<EditorDOMPoint, nsresult> HTMLEditor::ClearStyleAt( // `<p><b><i>a</i></b><b><i></i></b><b><i>bc</i></b></p>`. // ^^^^^^^^^^^^^^ nsIContent* firstLeafChildOfNextNode = HTMLEditUtils::GetFirstLeafContent( - *unwrappedSplitNodeResult.GetNextContent(), {}); + *unwrappedSplitNodeResult.GetNextContent(), {LeafNodeType::OnlyLeafNode}); EditorDOMPoint atStartOfNextNode( firstLeafChildOfNextNode ? firstLeafChildOfNextNode : unwrappedSplitNodeResult.GetNextContent(), @@ -2487,7 +2484,8 @@ Result<EditorDOMPoint, nsresult> HTMLEditor::ClearStyleAt( // it was in next node of the first split. // E.g., `<p><b><i>a</i></b><b><i><br></i></b><b><i>bc</i></b></p>` nsIContent* firstLeafChildOfPreviousNode = HTMLEditUtils::GetFirstLeafContent( - *unwrappedSplitResultAtStartOfNextNode.GetPreviousContent(), {}); + *unwrappedSplitResultAtStartOfNextNode.GetPreviousContent(), + {LeafNodeType::OnlyLeafNode}); pointToPutCaret.Set( firstLeafChildOfPreviousNode ? firstLeafChildOfPreviousNode @@ -4177,8 +4175,7 @@ Result<CreateElementResult, nsresult> HTMLEditor::SetFontSizeOnTextNode( aIncrementOrDecrement == FontSize::incr ? nsGkAtoms::big : nsGkAtoms::small; nsCOMPtr<nsIContent> sibling = HTMLEditUtils::GetPreviousSibling( - *textNodeForTheRange, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle); + *textNodeForTheRange, {WalkTreeOption::IgnoreNonEditableNode}); if (sibling && sibling->IsHTMLElement(bigOrSmallTagName)) { // Previous sib is already right kind of inline node; slide this over Result<MoveNodeResult, nsresult> moveTextNodeResult = @@ -4194,8 +4191,7 @@ Result<CreateElementResult, nsresult> HTMLEditor::SetFontSizeOnTextNode( return CreateElementResult::NotHandled(std::move(pointToPutCaret)); } sibling = HTMLEditUtils::GetNextSibling( - *textNodeForTheRange, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle); + *textNodeForTheRange, {WalkTreeOption::IgnoreNonEditableNode}); if (sibling && sibling->IsHTMLElement(bigOrSmallTagName)) { // Following sib is already right kind of inline node; slide this over Result<MoveNodeResult, nsresult> moveTextNodeResult = @@ -4329,8 +4325,7 @@ Result<EditorDOMPoint, nsresult> HTMLEditor::SetFontSizeWithBigOrSmallElement( // Next, if next or previous is <big> or <small>, move aContent into it. nsCOMPtr<nsIContent> sibling = HTMLEditUtils::GetPreviousSibling( - aContent, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle); + aContent, {WalkTreeOption::IgnoreNonEditableNode}); if (sibling && sibling->IsHTMLElement(bigOrSmallTagName)) { Result<MoveNodeResult, nsresult> moveNodeResult = MoveNodeToEndWithTransaction(aContent, *sibling); @@ -4345,8 +4340,7 @@ Result<EditorDOMPoint, nsresult> HTMLEditor::SetFontSizeWithBigOrSmallElement( } sibling = HTMLEditUtils::GetNextSibling( - aContent, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle); + aContent, {WalkTreeOption::IgnoreNonEditableNode}); if (sibling && sibling->IsHTMLElement(bigOrSmallTagName)) { Result<MoveNodeResult, nsresult> moveNodeResult = MoveNodeWithTransaction(aContent, EditorDOMPoint(sibling, 0u)); diff --git a/editor/libeditor/TextEditor.cpp b/editor/libeditor/TextEditor.cpp @@ -10,8 +10,8 @@ #include "EditAction.h" #include "EditAggregateTransaction.h" #include "EditorDOMPoint.h" -#include "EditorUtils.h" #include "HTMLEditor.h" +#include "HTMLEditUtils.h" #include "InternetCiter.h" #include "PlaceholderTransaction.h" #include "gfxFontUtils.h" @@ -89,6 +89,9 @@ static void LogOrWarn(const TextEditor* aTextEditor, LazyLogModule& aLog, using namespace dom; +using LeafNodeType = HTMLEditUtils::LeafNodeType; +using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes; + template EditorDOMPoint TextEditor::FindBetterInsertionPoint( const EditorDOMPoint& aPoint) const; template EditorRawDOMPoint TextEditor::FindBetterInsertionPoint( diff --git a/editor/libeditor/WSRunScanner.cpp b/editor/libeditor/WSRunScanner.cpp @@ -36,7 +36,6 @@ void WSScanResult::AssertIfInvalidData(const WSRunScanner& aScanner) const { mReason == WSType::CollapsibleWhiteSpaces || mReason == WSType::BRElement || mReason == WSType::PreformattedLineBreak || - mReason == WSType::EmptyInlineContainerElement || mReason == WSType::SpecialContent || mReason == WSType::CurrentBlockBoundary || mReason == WSType::OtherBlockBoundary || @@ -73,17 +72,6 @@ void WSScanResult::AssertIfInvalidData(const WSRunScanner& aScanner) const { mContent->IsHTMLElement(nsGkAtoms::br)); MOZ_ASSERT_IF(mReason == WSType::PreformattedLineBreak, EditorUtils::IsNewLinePreformatted(*mContent)); - auto MaybeNonVoidEmptyInlineContainerElement = [&]() { - return HTMLEditUtils::IsInlineContent( - *mContent, - aScanner.ReferredHTMLDefaultStyle() - ? BlockInlineCheck::UseHTMLDefaultStyle - : BlockInlineCheck::UseComputedDisplayOutsideStyle) && - HTMLEditUtils::IsContainerNode(*mContent) && - !HTMLEditUtils::IsReplacedElement(*mContent->AsElement()); - }; - MOZ_ASSERT_IF(mReason == WSType::EmptyInlineContainerElement, - MaybeNonVoidEmptyInlineContainerElement()); MOZ_ASSERT_IF( mReason == WSType::SpecialContent, (mContent->IsText() && !mContent->IsEditable()) || @@ -92,9 +80,7 @@ void WSScanResult::AssertIfInvalidData(const WSRunScanner& aScanner) const { *mContent, aScanner.ReferredHTMLDefaultStyle() ? BlockInlineCheck::UseHTMLDefaultStyle - : BlockInlineCheck::UseComputedDisplayOutsideStyle)) && - (!mContent->IsEditable() || - !MaybeNonVoidEmptyInlineContainerElement())); + : BlockInlineCheck::UseComputedDisplayOutsideStyle))); MOZ_ASSERT_IF( mReason == WSType::OtherBlockBoundary, HTMLEditUtils::IsBlockElement( @@ -1044,7 +1030,6 @@ WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent( if (textFragmentDataAtStart.EndsByVisibleBRElement()) { startContent = textFragmentDataAtStart.EndReasonBRElementPtr(); } else if (textFragmentDataAtStart.EndsBySpecialContent() || - textFragmentDataAtStart.EndsByEmptyInlineContainerElement() || (textFragmentDataAtStart.EndsByOtherBlockElement() && !HTMLEditUtils::IsContainerNode( *textFragmentDataAtStart @@ -1067,7 +1052,6 @@ WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent( if (textFragmentDataAtEnd.StartsFromVisibleBRElement()) { endContent = textFragmentDataAtEnd.StartReasonBRElementPtr(); } else if (textFragmentDataAtEnd.StartsFromSpecialContent() || - textFragmentDataAtEnd.EndsByEmptyInlineContainerElement() || (textFragmentDataAtEnd.StartsFromOtherBlockElement() && !HTMLEditUtils::IsContainerNode( *textFragmentDataAtEnd diff --git a/editor/libeditor/WSRunScanner.h b/editor/libeditor/WSRunScanner.h @@ -51,11 +51,6 @@ class MOZ_STACK_CLASS WSScanResult final { CollapsibleWhiteSpaces, // Visible characters except collapsible white-spaces. NonCollapsibleCharacters, - // Empty inline container elemnet such as `<span></span>`. Note that it may - // be visible if its border/padding is not 0 for example. - // NOTE: This won't be used if it's the inline editing host at the scan - // start point. - EmptyInlineContainerElement, // Special content such as `<img>`, etc. SpecialContent, // <br> element. @@ -86,8 +81,6 @@ class MOZ_STACK_CLASS WSScanResult final { return aStream << "WSType::CollapsibleWhiteSpaces"; case WSType::NonCollapsibleCharacters: return aStream << "WSType::NonCollapsibleCharacters"; - case WSType::EmptyInlineContainerElement: - return aStream << "WSType::EmptyInlineContainerElement"; case WSType::SpecialContent: return aStream << "WSType::SpecialContent"; case WSType::BRElement: @@ -187,22 +180,13 @@ class MOZ_STACK_CLASS WSScanResult final { } /** - * Return true if found or reached content is editable. + * Returns true if found or reached content is editable. */ - [[nodiscard]] bool ContentIsEditable() const { - return mContent && HTMLEditUtils::IsSimplyEditableNode(*mContent); - } + bool IsContentEditable() const { return mContent && mContent->IsEditable(); } - /** - * Return true if found or reached content is removable node. - */ - [[nodiscard]] bool ContentIsRemovable() const { - return mContent && HTMLEditUtils::IsRemovableNode(*mContent); - } - - [[nodiscard]] bool ContentIsEditableRoot() const { - return ContentIsElement() && - HTMLEditUtils::ElementIsEditableRoot(*ElementPtr()); + [[nodiscard]] bool IsContentEditableRoot() const { + return mContent && mContent->IsElement() && + HTMLEditUtils::ElementIsEditableRoot(*mContent->AsElement()); } /** @@ -274,97 +258,10 @@ class MOZ_STACK_CLASS WSScanResult final { } /** - * The scanner reached an empty inline container element such as - * <span></span>. Note that the element may be visible, e.g., may have - * non-zero border/padding. - */ - [[nodiscard]] constexpr bool ReachedEmptyInlineContainerElement() const { - return mReason == WSType::EmptyInlineContainerElement; - } - /** - * The scanner reached an editable empty inline container element such as - * <span></span>. Note that the element may be visible, e.g., may have - * non-zero border/padding. - */ - [[nodiscard]] bool ReachedEditableEmptyInlineContainerElement() const { - return ReachedEmptyInlineContainerElement() && ContentIsEditable(); - } - /** - * The scanner reached a removable empty inline container element such as - * <span></span>. Note that the element may be visible, e.g., may have - * non-zero border/padding. - */ - [[nodiscard]] bool ReachedRemovableEmptyInlineContainerElement() const { - return ReachedEmptyInlineContainerElement() && ContentIsRemovable(); - } - - /** - * The scanner reached an empty inline container element which is visible. - */ - [[nodiscard]] bool ReachedVisibleEmptyInlineContainerElement( - const nsIContent* aAncestorLimiterToCheckDisplayNone = nullptr) const { - return ReachedEmptyInlineContainerElement() && - HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*ElementPtr()) && - !HTMLEditUtils::IsInclusiveAncestorCSSDisplayNone( - *ElementPtr(), aAncestorLimiterToCheckDisplayNone); - } - /** - * The scanner reached an editable empty inline container element which is - * visible. - */ - [[nodiscard]] bool ReachedEditableEmptyInlineContainerElement( - const nsIContent* aAncestorLimiterToCheckDisplayNone = nullptr) const { - return ReachedVisibleEmptyInlineContainerElement( - aAncestorLimiterToCheckDisplayNone) && - ContentIsEditable(); - } - /** - * The scanner reached a removable empty inline container element which is - * visible. - */ - [[nodiscard]] bool ReachedRemovableEmptyInlineContainerElement( - const nsIContent* aAncestorLimiterToCheckDisplayNone = nullptr) const { - return ReachedVisibleEmptyInlineContainerElement( - aAncestorLimiterToCheckDisplayNone) && - ContentIsRemovable(); - } - - /** - * The scanner reached an empty inline container element which is invisible. - */ - [[nodiscard]] bool ReachedInvisibleEmptyInlineContainerElement( - const nsIContent* aAncestorLimiterToCheckDisplayNone = nullptr) const { - return ReachedEmptyInlineContainerElement() && - (!HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*ElementPtr()) || - HTMLEditUtils::IsInclusiveAncestorCSSDisplayNone( - *ElementPtr(), aAncestorLimiterToCheckDisplayNone)); - } - /** - * The scanner reached an editable empty inline container element which is - * invisible. - */ - [[nodiscard]] bool ReachedEditableInvisibleEmptyInlineContainerElement( - const nsIContent* aAncestorLimiterToCheckDisplayNone = nullptr) const { - return ReachedInvisibleEmptyInlineContainerElement( - aAncestorLimiterToCheckDisplayNone) && - ContentIsEditable(); - } - /** - * The scanner reached a removable empty inline container element which is - * invisible. - */ - [[nodiscard]] bool ReachedRemovableInvisibleEmptyInlineContainerElement( - const nsIContent* aAncestorLimiterToCheckDisplayNone = nullptr) const { - return ReachedEditableInvisibleEmptyInlineContainerElement( - aAncestorLimiterToCheckDisplayNone) && - ContentIsRemovable(); - } - - /** * The scanner reached <img> or something which is inline and is not a * container. */ - [[nodiscard]] constexpr bool ReachedSpecialContent() const { + bool ReachedSpecialContent() const { return mReason == WSType::SpecialContent; } @@ -500,7 +397,7 @@ class MOZ_STACK_CLASS WSScanResult final { private: nsCOMPtr<nsIContent> mContent; Maybe<uint32_t> mOffset; - WSType mReason = WSType::NotInitialized; + WSType mReason; ScanDirection mDirection = ScanDirection::Backward; }; @@ -526,14 +423,6 @@ class MOZ_STACK_CLASS WSRunScanner final { ReferHTMLDefaultStyle, // If set, stop scanning the DOM when it reaches a `Comment` node. StopAtComment, - // If set, ignore empty inline containers such as <span></span>. - IgnoreEmptyInlineContainers, - // If set, ignore empty inline containers which is not visible. E.g., - // <span></span> is ignored but <span style="border:1px solid"></span> - // and <span style="border:padding 1px"></span> are not ignored. - // XXX Currently, this does not work well if the inline container has only - // `::before` and/or `::after` content and the frame is dirty. - IgnoreInvisibleInlines, }; using Options = EnumSet<Option>; @@ -560,32 +449,6 @@ class MOZ_STACK_CLASS WSRunScanner final { aOptions.contains(Option::ReferHTMLDefaultStyle)); } - private: - [[nodiscard]] static HTMLEditUtils::LeafNodeOptions ToLeafNodeOptions( - const Options& aOptions) { - using LeafNodeOption = HTMLEditUtils::LeafNodeOption; - using LeafNodeOptions = HTMLEditUtils::LeafNodeOptions; - auto types = - aOptions.contains(Option::OnlyEditableNodes) - ? LeafNodeOptions{LeafNodeOption::TreatNonEditableNodeAsLeafNode} - : LeafNodeOptions{}; - if (aOptions.contains(Option::StopAtComment)) { - types += LeafNodeOption::TreatCommentAsLeafNode; - } - if (aOptions.contains(Option::IgnoreInvisibleInlines)) { - types += - LeafNodeOptions{LeafNodeOption::IgnoreInvisibleEmptyInlineContainers, - LeafNodeOption::IgnoreInvisibleInlineVoidElements, - LeafNodeOption::IgnoreInvisibleText}; - } - if (aOptions.contains(Option::IgnoreEmptyInlineContainers)) { - types += LeafNodeOptions{LeafNodeOption::IgnoreAnyEmptyInlineContainers, - LeafNodeOption::IgnoreEmptyText}; - } - return types; - } - - public: template <typename EditorDOMPointType> WSRunScanner(Options aOptions, // NOLINT(performance-unnecessary-value-param) const EditorDOMPointType& aScanStartPoint, @@ -818,12 +681,9 @@ class MOZ_STACK_CLASS WSRunScanner final { bool StartsFromNonCollapsibleCharacters() const { return mLeftWSType == WSType::NonCollapsibleCharacters; } - [[nodiscard]] constexpr bool StartsFromSpecialContent() const { + bool StartsFromSpecialContent() const { return mLeftWSType == WSType::SpecialContent; } - [[nodiscard]] constexpr bool StartsFromEmptyInlineContainerElement() const { - return mLeftWSType == WSType::EmptyInlineContainerElement; - } bool StartsFromPreformattedLineBreak() const { return mLeftWSType == WSType::PreformattedLineBreak; } @@ -838,12 +698,9 @@ class MOZ_STACK_CLASS WSRunScanner final { bool EndsByTrailingWhiteSpaces() const { return mRightWSType == WSType::TrailingWhiteSpaces; } - [[nodiscard]] constexpr bool EndsBySpecialContent() const { + bool EndsBySpecialContent() const { return mRightWSType == WSType::SpecialContent; } - [[nodiscard]] constexpr bool EndsByEmptyInlineContainerElement() const { - return mRightWSType == WSType::EmptyInlineContainerElement; - } bool EndsByBRElement() const { return mRightWSType == WSType::BRElement; } bool EndsByPreformattedLineBreak() const { return mRightWSType == WSType::PreformattedLineBreak; @@ -1051,12 +908,9 @@ class MOZ_STACK_CLASS WSRunScanner final { bool IsNonCollapsibleCharacters() const { return mReason == WSType::NonCollapsibleCharacters; } - [[nodiscard]] constexpr bool IsSpecialContent() const { + bool IsSpecialContent() const { return mReason == WSType::SpecialContent; } - [[nodiscard]] constexpr bool IsEmptyInlineContainerElement() const { - return mReason == WSType::EmptyInlineContainerElement; - } bool IsBRElement() const { return mReason == WSType::BRElement; } bool IsPreformattedLineBreak() const { return mReason == WSType::PreformattedLineBreak; @@ -1106,9 +960,8 @@ class MOZ_STACK_CLASS WSRunScanner final { EditorDOMPoint mPoint; // Must be one of WSType::NotInitialized, // WSType::NonCollapsibleCharacters, WSType::SpecialContent, - // WSType::EmptyInlineContainerElement, WSType::BRElement, - // WSType::CurrentBlockBoundary, WSType::OtherBlockBoundary or - // WSType::InlineEditingHostBoundary. + // WSType::BRElement, WSType::CurrentBlockBoundary, + // WSType::OtherBlockBoundary or WSType::InlineEditingHostBoundary. WSType mReason = WSType::NotInitialized; }; @@ -1173,12 +1026,7 @@ class MOZ_STACK_CLASS WSRunScanner final { bool StartsFromNonCollapsibleCharacters() const { return mStart.IsNonCollapsibleCharacters(); } - [[nodiscard]] bool StartsFromSpecialContent() const { - return mStart.IsSpecialContent(); - } - [[nodiscard]] bool StartsFromEmptyInlineContainerElement() const { - return mStart.IsEmptyInlineContainerElement(); - } + bool StartsFromSpecialContent() const { return mStart.IsSpecialContent(); } bool StartsFromBRElement() const { return mStart.IsBRElement(); } bool StartsFromVisibleBRElement() const { return StartsFromBRElement() && @@ -1205,12 +1053,7 @@ class MOZ_STACK_CLASS WSRunScanner final { bool EndsByNonCollapsibleCharacters() const { return mEnd.IsNonCollapsibleCharacters(); } - [[nodiscard]] bool EndsBySpecialContent() const { - return mEnd.IsSpecialContent(); - } - [[nodiscard]] bool EndsByEmptyInlineContainerElement() const { - return mEnd.IsEmptyInlineContainerElement(); - } + bool EndsBySpecialContent() const { return mEnd.IsSpecialContent(); } bool EndsByBRElement() const { return mEnd.IsBRElement(); } bool EndsByVisibleBRElement() const { return EndsByBRElement() && diff --git a/editor/libeditor/WSRunScannerNestedClasses.cpp b/editor/libeditor/WSRunScannerNestedClasses.cpp @@ -24,8 +24,8 @@ using namespace dom; using AncestorType = HTMLEditUtils::AncestorType; using AncestorTypes = HTMLEditUtils::AncestorTypes; -using LeafNodeOption = HTMLEditUtils::LeafNodeOption; -using LeafNodeOptions = HTMLEditUtils::LeafNodeOptions; +using LeafNodeType = HTMLEditUtils::LeafNodeType; +using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes; template WSRunScanner::TextFragmentData::TextFragmentData(Options, const EditorDOMPoint&, @@ -245,10 +245,18 @@ WSRunScanner::TextFragmentData::BoundaryData WSRunScanner::TextFragmentData:: ? BlockInlineCheck::UseHTMLDefaultStyle : BlockInlineCheck::Auto; // Then, we need to check previous leaf node. - const LeafNodeOptions leafNodeOptions = ToLeafNodeOptions(aOptions); + const auto leafNodeTypes = [&]() -> LeafNodeTypes { + auto types = aOptions.contains(Option::OnlyEditableNodes) + ? LeafNodeTypes{LeafNodeType::LeafNodeOrNonEditableNode} + : LeafNodeTypes{LeafNodeType::OnlyLeafNode}; + if (aOptions.contains(Option::StopAtComment)) { + types += LeafNodeType::TreatCommentAsLeafNode; + } + return types; + }(); nsIContent* previousLeafContentOrBlock = HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( - aPoint, leafNodeOptions, blockInlineCheck, &aAncestorLimiter); + aPoint, leafNodeTypes, blockInlineCheck, &aAncestorLimiter); if (!previousLeafContentOrBlock) { // No previous content means that we reached the aAncestorLimiter boundary. return BoundaryData( @@ -266,35 +274,16 @@ WSRunScanner::TextFragmentData::BoundaryData WSRunScanner::TextFragmentData:: WSType::OtherBlockBoundary); } - if (previousLeafContentOrBlock->IsHTMLElement(nsGkAtoms::br)) { - // <br> - return BoundaryData(aPoint, *previousLeafContentOrBlock, WSType::BRElement); - } - - if (aOptions.contains(Option::OnlyEditableNodes) && - HTMLEditUtils::IsSimplyEditableNode(*previousLeafContentOrBlock) != - HTMLEditUtils::IsSimplyEditableNode(aAncestorLimiter)) { - // Non-editable nodes (assuming the start content is editable). - return BoundaryData(aPoint, *previousLeafContentOrBlock, - WSType::SpecialContent); - } - - if (previousLeafContentOrBlock->IsElement() && - HTMLEditUtils::IsInlineContent( - *previousLeafContentOrBlock, - UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)) && - HTMLEditUtils::IsContainerNode(*previousLeafContentOrBlock) && - !HTMLEditUtils::IsReplacedElement( - *previousLeafContentOrBlock->AsElement())) { - // Empty inline containers such as <span></span> - return BoundaryData(aPoint, *previousLeafContentOrBlock, - WSType::EmptyInlineContainerElement); - } - - if (!previousLeafContentOrBlock->IsText()) { - // Other elements like <img> or #comment. + if (!previousLeafContentOrBlock->IsText() || + (aOptions.contains(Option::OnlyEditableNodes) && + HTMLEditUtils::IsSimplyEditableNode(*previousLeafContentOrBlock) != + HTMLEditUtils::IsSimplyEditableNode(aAncestorLimiter))) { + // it's a break or a special node, like <img>, that is not a block and + // not a break but still serves as a terminator to ws runs. return BoundaryData(aPoint, *previousLeafContentOrBlock, - WSType::SpecialContent); + previousLeafContentOrBlock->IsHTMLElement(nsGkAtoms::br) + ? WSType::BRElement + : WSType::SpecialContent); } if (!previousLeafContentOrBlock->AsText()->TextLength()) { @@ -418,10 +407,18 @@ WSRunScanner::TextFragmentData::BoundaryData::ScanCollapsibleWhiteSpaceEndFrom( : BlockInlineCheck::Auto; // Then, we need to check next leaf node. - const LeafNodeOptions leafNodeOptions = ToLeafNodeOptions(aOptions); + const auto leafNodeTypes = [&]() -> LeafNodeTypes { + auto types = aOptions.contains(Option::OnlyEditableNodes) + ? LeafNodeTypes{LeafNodeType::LeafNodeOrNonEditableNode} + : LeafNodeTypes{LeafNodeType::OnlyLeafNode}; + if (aOptions.contains(Option::StopAtComment)) { + types += LeafNodeType::TreatCommentAsLeafNode; + } + return types; + }(); nsIContent* nextLeafContentOrBlock = HTMLEditUtils::GetNextLeafContentOrNextBlockElement( - aPoint, leafNodeOptions, blockInlineCheck, &aAncestorLimiter); + aPoint, leafNodeTypes, blockInlineCheck, &aAncestorLimiter); if (!nextLeafContentOrBlock) { // No next content means that we reached aAncestorLimiter boundary. return BoundaryData( @@ -441,34 +438,17 @@ WSRunScanner::TextFragmentData::BoundaryData::ScanCollapsibleWhiteSpaceEndFrom( WSType::OtherBlockBoundary); } - if (nextLeafContentOrBlock->IsHTMLElement(nsGkAtoms::br)) { - // <br> - return BoundaryData(aPoint, *nextLeafContentOrBlock, WSType::BRElement); - } - - if (aOptions.contains(Option::OnlyEditableNodes) && - HTMLEditUtils::IsSimplyEditableNode(*nextLeafContentOrBlock) != - HTMLEditUtils::IsSimplyEditableNode(aAncestorLimiter)) { - // Non-editable nodes (assuming the start content is editable). - return BoundaryData(aPoint, *nextLeafContentOrBlock, - WSType::SpecialContent); - } - - if (nextLeafContentOrBlock->IsElement() && - HTMLEditUtils::IsInlineContent( - *nextLeafContentOrBlock, - UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)) && - HTMLEditUtils::IsContainerNode(*nextLeafContentOrBlock) && - !HTMLEditUtils::IsReplacedElement(*nextLeafContentOrBlock->AsElement())) { - // Empty inline containers such as <span></span> - return BoundaryData(aPoint, *nextLeafContentOrBlock, - WSType::EmptyInlineContainerElement); - } - - if (!nextLeafContentOrBlock->IsText()) { - // Other elements like <img>, etc. + if (!nextLeafContentOrBlock->IsText() || + (aOptions.contains(Option::OnlyEditableNodes) && + HTMLEditUtils::IsSimplyEditableNode(*nextLeafContentOrBlock) != + HTMLEditUtils::IsSimplyEditableNode(aAncestorLimiter))) { + // we encountered a break or a special node, like <img>, + // that is not a block and not a break but still + // serves as a terminator to ws runs. return BoundaryData(aPoint, *nextLeafContentOrBlock, - WSType::SpecialContent); + nextLeafContentOrBlock->IsHTMLElement(nsGkAtoms::br) + ? WSType::BRElement + : WSType::SpecialContent); } if (!nextLeafContentOrBlock->AsText()->DataBuffer().GetLength()) { @@ -619,19 +599,9 @@ WSRunScanner::TextFragmentData::VisibleWhiteSpacesDataRef() const { // If all things are obviously visible, we can return range for all of the // things quickly. const bool mayHaveInvisibleLeadingSpace = - !StartsFromNonCollapsibleCharacters() && !StartsFromSpecialContent() && - !(StartsFromEmptyInlineContainerElement() && - // XXX I think we don't need to check display:none here for now - // because in the other cases, we don't do that. - HTMLEditUtils::IsVisibleElementEvenIfLeafNode( - *GetStartReasonContent())); + !StartsFromNonCollapsibleCharacters() && !StartsFromSpecialContent(); const bool mayHaveInvisibleTrailingWhiteSpace = !EndsByNonCollapsibleCharacters() && !EndsBySpecialContent() && - !(EndsByEmptyInlineContainerElement() && - // XXX I think we don't need to check display:none here for now - // because in the other cases, we don't do that. - HTMLEditUtils::IsVisibleElementEvenIfLeafNode( - *GetEndReasonContent())) && !EndsByBRElement() && !EndsByInvisiblePreformattedLineBreak(); if (!mayHaveInvisibleLeadingSpace && !mayHaveInvisibleTrailingWhiteSpace) { @@ -901,26 +871,21 @@ EditorDOMPointType WSRunScanner::TextFragmentData::GetInclusiveNextCharPoint( aOptions.contains(Option::ReferHTMLDefaultStyle) ? BlockInlineCheck::UseHTMLDefaultStyle : BlockInlineCheck::Auto; - const LeafNodeOptions leafNodeOptions = - ToLeafNodeOptions(aOptions) + LeafNodeOption::TreatChildBlockAsLeafNode; const EditorRawDOMPoint point = [&]() MOZ_NEVER_INLINE_DEBUG { - if (!aPoint.CanContainerHaveChildren()) { - return aPoint.template To<EditorRawDOMPoint>(); - } - nsIContent* const child = - aPoint.GetPreviousSiblingOfChild() - ? HTMLEditUtils::GetNextSibling( - *aPoint.GetPreviousSiblingOfChild(), leafNodeOptions, - UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)) - : HTMLEditUtils::GetFirstChild( - *aPoint.GetContainer(), leafNodeOptions, - UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)); + nsIContent* const child = [&]() -> nsIContent* { + nsIContent* child = + aPoint.CanContainerHaveChildren() ? aPoint.GetChild() : nullptr; + // XXX Why don't we skip non-editable nodes here? + while (child && child->IsComment() && + !aOptions.contains(Option::StopAtComment)) { + child = child->GetNextSibling(); + } + return child; + }(); if (!child || HTMLEditUtils::IsBlockElement( *child, UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)) || - ((!aOptions.contains(Option::IgnoreEmptyInlineContainers) || - !HTMLEditUtils::IsContainerNode(*child)) && - HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*child))) { + HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*child)) { return aPoint.template To<EditorRawDOMPoint>(); } if (!child->HasChildNodes()) { @@ -934,16 +899,14 @@ EditorDOMPointType WSRunScanner::TextFragmentData::GetInclusiveNextCharPoint( // block because end reason content should not be the other side of the // following block boundary. nsIContent* const leafContent = HTMLEditUtils::GetFirstLeafContent( - *child, leafNodeOptions, blockInlineCheck); + *child, {LeafNodeType::LeafNodeOrChildBlock}, blockInlineCheck); if (!leafContent) { return EditorRawDOMPoint(child, 0); } if (HTMLEditUtils::IsBlockElement( *leafContent, UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)) || - ((!aOptions.contains(Option::IgnoreEmptyInlineContainers) || - !HTMLEditUtils::IsContainerNode(*leafContent)) && - HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*leafContent))) { + HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*leafContent)) { return EditorRawDOMPoint(); } return EditorRawDOMPoint(leafContent, 0); @@ -980,14 +943,24 @@ EditorDOMPointType WSRunScanner::TextFragmentData::GetInclusiveNextCharPoint( return EditorDOMPointType(); } + const auto leafNodeTypes = [&]() -> LeafNodeTypes { + auto types = aIgnoreNonEditableNodes == IgnoreNonEditableNodes::Yes + ? LeafNodeTypes(LeafNodeType::LeafNodeOrNonEditableNode, + LeafNodeType::LeafNodeOrChildBlock) + : LeafNodeTypes(LeafNodeType::LeafNodeOrChildBlock); + if (aOptions.contains(Option::StopAtComment)) { + types += LeafNodeType::TreatCommentAsLeafNode; + } + return types; + }(); for (nsIContent* nextContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement( - *point.ContainerAs<nsIContent>(), leafNodeOptions, + *point.ContainerAs<nsIContent>(), leafNodeTypes, blockInlineCheck, editableBlockElementOrInlineEditingHostOrNonEditableRootElement); nextContent; nextContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement( - *nextContent, leafNodeOptions, blockInlineCheck, + *nextContent, leafNodeTypes, blockInlineCheck, editableBlockElementOrInlineEditingHostOrNonEditableRootElement)) { if (!nextContent->IsText() || (aIgnoreNonEditableNodes == IgnoreNonEditableNodes::Yes && @@ -996,9 +969,7 @@ EditorDOMPointType WSRunScanner::TextFragmentData::GetInclusiveNextCharPoint( HTMLEditUtils::IsBlockElement( *nextContent, UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)) || - ((!aOptions.contains(Option::IgnoreEmptyInlineContainers) || - !HTMLEditUtils::IsContainerNode(*nextContent)) && - HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*nextContent))) { + HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*nextContent)) { break; // Reached end of current runs. } continue; @@ -1025,27 +996,23 @@ EditorDOMPointType WSRunScanner::TextFragmentData::GetPreviousCharPoint( aOptions.contains(Option::ReferHTMLDefaultStyle) ? BlockInlineCheck::UseHTMLDefaultStyle : BlockInlineCheck::Auto; - const LeafNodeOptions leafNodeOptions = - ToLeafNodeOptions(aOptions) + LeafNodeOption::TreatChildBlockAsLeafNode; const EditorRawDOMPoint point = [&]() MOZ_NEVER_INLINE_DEBUG { - if (!aPoint.CanContainerHaveChildren()) { - return aPoint.template To<EditorRawDOMPoint>(); - } - nsIContent* const previousChild = - aPoint.GetChild() - ? HTMLEditUtils::GetPreviousSibling( - *aPoint.GetChild(), leafNodeOptions, - UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)) - : HTMLEditUtils::GetLastChild( - *aPoint.GetContainer(), leafNodeOptions, - UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)); + nsIContent* const previousChild = [&]() -> nsIContent* { + nsIContent* previousChild = aPoint.CanContainerHaveChildren() + ? aPoint.GetPreviousSiblingOfChild() + : nullptr; + // XXX Why don't we skip non-editable nodes here? + while (previousChild && previousChild->IsComment() && + !aOptions.contains(Option::StopAtComment)) { + previousChild = previousChild->GetPreviousSibling(); + } + return previousChild; + }(); if (!previousChild || HTMLEditUtils::IsBlockElement( *previousChild, UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)) || - ((!aOptions.contains(Option::IgnoreEmptyInlineContainers) || - !HTMLEditUtils::IsContainerNode(*previousChild)) && - HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*previousChild))) { + HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*previousChild)) { return aPoint.template To<EditorRawDOMPoint>(); } if (!previousChild->HasChildren()) { @@ -1060,16 +1027,14 @@ EditorDOMPointType WSRunScanner::TextFragmentData::GetPreviousCharPoint( // block because end reason content should not be the other side of the // following block boundary. nsIContent* const leafContent = HTMLEditUtils::GetLastLeafContent( - *previousChild, leafNodeOptions, blockInlineCheck); + *previousChild, {LeafNodeType::LeafNodeOrChildBlock}, blockInlineCheck); if (!leafContent) { return EditorRawDOMPoint::AtEndOf(*previousChild); } if (HTMLEditUtils::IsBlockElement( *leafContent, UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)) || - ((!aOptions.contains(Option::IgnoreEmptyInlineContainers) || - !HTMLEditUtils::IsContainerNode(*leafContent)) && - HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*leafContent))) { + HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*leafContent)) { return EditorRawDOMPoint(); } return EditorRawDOMPoint::AtEndOf(*leafContent); @@ -1107,16 +1072,25 @@ EditorDOMPointType WSRunScanner::TextFragmentData::GetPreviousCharPoint( return EditorDOMPointType(); } + const auto leafNodeTypes = [&]() -> LeafNodeTypes { + auto types = aIgnoreNonEditableNodes == IgnoreNonEditableNodes::Yes + ? LeafNodeTypes(LeafNodeType::LeafNodeOrNonEditableNode, + LeafNodeType::LeafNodeOrChildBlock) + : LeafNodeTypes(LeafNodeType::LeafNodeOrChildBlock); + if (aOptions.contains(Option::StopAtComment)) { + types += LeafNodeType::TreatCommentAsLeafNode; + } + return types; + }(); for ( nsIContent* previousContent = HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( - *point.ContainerAs<nsIContent>(), leafNodeOptions, - blockInlineCheck, + *point.ContainerAs<nsIContent>(), leafNodeTypes, blockInlineCheck, editableBlockElementOrInlineEditingHostOrNonEditableRootElement); previousContent; previousContent = HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( - *previousContent, leafNodeOptions, blockInlineCheck, + *previousContent, leafNodeTypes, blockInlineCheck, editableBlockElementOrInlineEditingHostOrNonEditableRootElement)) { if (!previousContent->IsText() || (aIgnoreNonEditableNodes == IgnoreNonEditableNodes::Yes && @@ -1125,9 +1099,7 @@ EditorDOMPointType WSRunScanner::TextFragmentData::GetPreviousCharPoint( HTMLEditUtils::IsBlockElement( *previousContent, UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)) || - ((!aOptions.contains(Option::IgnoreEmptyInlineContainers) || - !HTMLEditUtils::IsContainerNode(*previousContent)) && - HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*previousContent))) { + HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*previousContent)) { break; // Reached start of current runs. } continue; @@ -1426,10 +1398,7 @@ EditorDOMPointInText WSRunScanner::TextFragmentData:: const VisibleWhiteSpacesData& visibleWhiteSpaces = VisibleWhiteSpacesDataRef(); if (!visibleWhiteSpaces.StartsFromNonCollapsibleCharacters() && - !visibleWhiteSpaces.StartsFromSpecialContent() && - !(visibleWhiteSpaces.StartsFromEmptyInlineContainerElement() && - HTMLEditUtils::IsVisibleElementEvenIfLeafNode( - *GetStartReasonContent()))) { + !visibleWhiteSpaces.StartsFromSpecialContent()) { return EditorDOMPointInText(); } return atPreviousChar; @@ -1487,9 +1456,6 @@ EditorDOMPointInText WSRunScanner::TextFragmentData:: VisibleWhiteSpacesDataRef(); if (!visibleWhiteSpaces.EndsByNonCollapsibleCharacters() && !visibleWhiteSpaces.EndsBySpecialContent() && - !(visibleWhiteSpaces.EndsByEmptyInlineContainerElement() && - HTMLEditUtils::IsVisibleElementEvenIfLeafNode( - *GetEndReasonContent())) && !visibleWhiteSpaces.EndsByBRElement()) { return EditorDOMPointInText(); } diff --git a/editor/libeditor/WhiteSpaceVisibilityKeeper.cpp b/editor/libeditor/WhiteSpaceVisibilityKeeper.cpp @@ -32,7 +32,8 @@ namespace mozilla { using namespace dom; -using LeafNodeOption = HTMLEditUtils::LeafNodeOption; +using LeafNodeType = HTMLEditUtils::LeafNodeType; +using WalkTreeOption = HTMLEditUtils::WalkTreeOption; Result<EditorDOMPoint, nsresult> WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement( @@ -922,14 +923,15 @@ WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore( aPoint.IsInTextNode() && aPoint.IsEndOfContainer() ? aPoint.ContainerAs<Text>() : HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( - aPoint, {LeafNodeOption::TreatChildBlockAsLeafNode}, + aPoint, + {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock}, BlockInlineCheck::UseComputedDisplayStyle, colsetBlockElement); previousContent; previousContent = HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( EditorRawDOMPoint(previousContent), - {LeafNodeOption::TreatChildBlockAsLeafNode}, + {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock}, BlockInlineCheck::UseComputedDisplayStyle, colsetBlockElement)) { if (!HTMLEditUtils::IsSimplyEditableNode(*previousContent)) { // XXX Assume non-editable nodes are visible. @@ -1040,13 +1042,14 @@ WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter( aPoint.IsInTextNode() && aPoint.IsStartOfContainer() ? aPoint.ContainerAs<Text>() : HTMLEditUtils::GetNextLeafContentOrNextBlockElement( - aPoint, {LeafNodeOption::TreatChildBlockAsLeafNode}, + aPoint, + {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock}, BlockInlineCheck::UseComputedDisplayStyle, colsetBlockElement); nextContent; nextContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement( EditorRawDOMPoint::After(*nextContent), - {LeafNodeOption::TreatChildBlockAsLeafNode}, + {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock}, BlockInlineCheck::UseComputedDisplayStyle, colsetBlockElement)) { if (!HTMLEditUtils::IsSimplyEditableNode(*nextContent)) { // XXX Assume non-editable nodes are visible. @@ -1293,13 +1296,13 @@ WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt( if (!pointToSplit.IsInTextNode() || pointToSplit.IsStartOfContainer()) { for (nsCOMPtr<nsIContent> previousContent = HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( - pointToSplit, {LeafNodeOption::TreatChildBlockAsLeafNode}, + pointToSplit, {LeafNodeType::LeafNodeOrChildBlock}, BlockInlineCheck::UseComputedDisplayStyle, closestBlockElement); previousContent; previousContent = HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( - *previousContent, {LeafNodeOption::TreatChildBlockAsLeafNode}, + *previousContent, {LeafNodeType::LeafNodeOrChildBlock}, BlockInlineCheck::UseComputedDisplayStyle, closestBlockElement)) { if (auto* const textNode = Text::FromNode(previousContent)) { @@ -1334,12 +1337,12 @@ WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt( if (!pointToSplit.IsInTextNode() || pointToSplit.IsEndOfContainer()) { for (nsCOMPtr<nsIContent> nextContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement( - pointToSplit, {LeafNodeOption::TreatChildBlockAsLeafNode}, + pointToSplit, {LeafNodeType::LeafNodeOrChildBlock}, BlockInlineCheck::UseComputedDisplayStyle, closestBlockElement); nextContent; nextContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement( - *nextContent, {LeafNodeOption::TreatChildBlockAsLeafNode}, + *nextContent, {LeafNodeType::LeafNodeOrChildBlock}, BlockInlineCheck::UseComputedDisplayStyle, closestBlockElement)) { if (auto* const textNode = Text::FromNode(nextContent)) { if (!HTMLEditUtils::IsSimplyEditableNode(*textNode) && @@ -1694,14 +1697,14 @@ nsresult WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesAfter( for (nsIContent* nextContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement( aPoint, - {LeafNodeOption::TreatChildBlockAsLeafNode, - LeafNodeOption::TreatCommentAsLeafNode}, + {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock, + HTMLEditUtils::LeafNodeType::TreatCommentAsLeafNode}, BlockInlineCheck::UseComputedDisplayStyle, colsetBlockElement); nextContent; nextContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement( EditorRawDOMPoint::After(*nextContent), - {LeafNodeOption::TreatChildBlockAsLeafNode, - LeafNodeOption::TreatCommentAsLeafNode}, + {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock, + HTMLEditUtils::LeafNodeType::TreatCommentAsLeafNode}, BlockInlineCheck::UseComputedDisplayStyle, colsetBlockElement)) { if (!HTMLEditUtils::IsSimplyEditableNode(*nextContent)) { // XXX Assume non-editable nodes are visible. @@ -1778,15 +1781,15 @@ nsresult WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesBefore( for (nsIContent* previousContent = HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( aPoint, - {LeafNodeOption::TreatChildBlockAsLeafNode, - LeafNodeOption::TreatCommentAsLeafNode}, + {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock, + HTMLEditUtils::LeafNodeType::TreatCommentAsLeafNode}, BlockInlineCheck::UseComputedDisplayStyle, colsetBlockElement); previousContent; previousContent = HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( EditorRawDOMPoint(previousContent), - {LeafNodeOption::TreatChildBlockAsLeafNode, - LeafNodeOption::TreatCommentAsLeafNode}, + {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock, + HTMLEditUtils::LeafNodeType::TreatCommentAsLeafNode}, BlockInlineCheck::UseComputedDisplayStyle, colsetBlockElement)) { if (!HTMLEditUtils::IsSimplyEditableNode(*previousContent)) { // XXX Assume non-editable nodes are visible. @@ -2522,10 +2525,9 @@ WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt( } } - const nsCOMPtr<nsIContent> previousEditableSibling = + nsCOMPtr<nsIContent> previousEditableSibling = HTMLEditUtils::GetPreviousSibling( - aContentToDelete, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle); + aContentToDelete, {WalkTreeOption::IgnoreNonEditableNode}); // Delete the node, and join like nodes if appropriate nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(aContentToDelete); if (NS_FAILED(rv)) { @@ -2548,9 +2550,8 @@ WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt( return CaretPoint(std::move(pointToPutCaret)); } - nsIContent* const nextEditableSibling = HTMLEditUtils::GetNextSibling( - *previousEditableSibling, {LeafNodeOption::IgnoreNonEditableNode}, - BlockInlineCheck::UseComputedDisplayOutsideStyle); + nsIContent* nextEditableSibling = HTMLEditUtils::GetNextSibling( + *previousEditableSibling, {WalkTreeOption::IgnoreNonEditableNode}); if (aCaretPoint.GetContainer() != nextEditableSibling) { return CaretPoint(std::move(pointToPutCaret)); } diff --git a/editor/libeditor/gtest/TestHTMLEditUtils.cpp b/editor/libeditor/gtest/TestHTMLEditUtils.cpp @@ -1445,7 +1445,7 @@ TEST(HTMLEditUtilsTest, IsEmptyNode) struct MOZ_STACK_CLASS GetLeafNodeTest final { const char16_t* mInnerHTML; const char* mContentSelector; - const HTMLEditUtils::LeafNodeOptions mOptions; + const HTMLEditUtils::LeafNodeTypes mTypes; const char* mExpectedTargetSelector; const char* mExpectedTargetContainerSelector = nullptr; const uint32_t mExpectedTargetOffset = 0u; @@ -1469,7 +1469,7 @@ struct MOZ_STACK_CLASS GetLeafNodeTest final { friend std::ostream& operator<<(std::ostream& aStream, const GetLeafNodeTest& aTest) { return aStream << "Scan from \"" << aTest.mContentSelector - << "\" with options=" << ToString(aTest.mOptions).c_str() + << "\" with options=" << ToString(aTest.mTypes).c_str() << " in \"" << NS_ConvertUTF16toUTF8(aTest.mInnerHTML).get() << "\""; } @@ -1477,7 +1477,7 @@ struct MOZ_STACK_CLASS GetLeafNodeTest final { TEST(HTMLEditUtilsTest, GetLastLeafContent) { - using LeafNodeOption = HTMLEditUtils::LeafNodeOption; + using LeafNodeType = HTMLEditUtils::LeafNodeType; const RefPtr<Document> doc = CreateHTMLDoc(); const RefPtr<nsGenericHTMLElement> body = doc->GetBody(); MOZ_RELEASE_ASSERT(body); @@ -1486,12 +1486,11 @@ TEST(HTMLEditUtilsTest, GetLastLeafContent) 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}, + {LeafNodeType::LeafNodeOrChildBlock}, "div > div"}, GetLeafNodeTest{u"<div><div><br></div><div><br></div></div>", "div", @@ -1499,20 +1498,19 @@ TEST(HTMLEditUtilsTest, GetLastLeafContent) "div > div + div > br"}, GetLeafNodeTest{u"<div><div><br></div><div><br></div></div>", "div", - {LeafNodeOption::TreatChildBlockAsLeafNode}, + {LeafNodeType::LeafNodeOrChildBlock}, "div > div + div"}, - GetLeafNodeTest{u"<div><!--abc--></div>", "div", {}, nullptr}, GetLeafNodeTest{u"<div><!--abc--></div>", "div", - {LeafNodeOption::TreatCommentAsLeafNode}, + {LeafNodeType::TreatCommentAsLeafNode}, nullptr, "div", 0u}, GetLeafNodeTest{u"<div><br><!--abc--></div>", "div", {}, "div > br"}, GetLeafNodeTest{u"<div><br><!--abc--></div>", "div", - {LeafNodeOption::TreatCommentAsLeafNode}, + {LeafNodeType::TreatCommentAsLeafNode}, nullptr, "div", 1u}, @@ -1524,74 +1522,10 @@ TEST(HTMLEditUtilsTest, GetLastLeafContent) GetLeafNodeTest{ u"<div><div><br></div><div><br></div><!--abc--></div>", "div", - {LeafNodeOption::TreatCommentAsLeafNode}, + {LeafNodeType::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()); @@ -1599,7 +1533,7 @@ TEST(HTMLEditUtilsTest, GetLastLeafContent) nsDependentCString(testData.mContentSelector), IgnoreErrors()); MOZ_RELEASE_ASSERT(target); const nsIContent* result = HTMLEditUtils::GetLastLeafContent( - *target, testData.mOptions, + *target, testData.mTypes, BlockInlineCheck::UseComputedDisplayOutsideStyle); EXPECT_EQ(result, testData.GetExpectedTarget(*body)) << "GetLastLeafContent: " << testData @@ -1609,7 +1543,7 @@ TEST(HTMLEditUtilsTest, GetLastLeafContent) TEST(HTMLEditUtilsTest, GetFirstLeafContent) { - using LeafNodeOption = HTMLEditUtils::LeafNodeOption; + using LeafNodeType = HTMLEditUtils::LeafNodeType; const RefPtr<Document> doc = CreateHTMLDoc(); const RefPtr<nsGenericHTMLElement> body = doc->GetBody(); MOZ_RELEASE_ASSERT(body); @@ -1621,10 +1555,9 @@ TEST(HTMLEditUtilsTest, GetFirstLeafContent) 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}, + {LeafNodeType::LeafNodeOrChildBlock}, "div > div"}, GetLeafNodeTest{u"<div><div><br></div><div><br></div></div>", "div", @@ -1632,20 +1565,19 @@ TEST(HTMLEditUtilsTest, GetFirstLeafContent) "div > div > br"}, GetLeafNodeTest{u"<div><div><br></div><div><br></div></div>", "div", - {LeafNodeOption::TreatChildBlockAsLeafNode}, + {LeafNodeType::LeafNodeOrChildBlock}, "div > div"}, - GetLeafNodeTest{u"<div><!--abc--></div>", "div", {}, nullptr}, GetLeafNodeTest{u"<div><!--abc--></div>", "div", - {LeafNodeOption::TreatCommentAsLeafNode}, + {LeafNodeType::TreatCommentAsLeafNode}, nullptr, "div", 0u}, GetLeafNodeTest{u"<div><!--abc--><br></div>", "div", {}, "div > br"}, GetLeafNodeTest{u"<div><!--abc--><br></div>", "div", - {LeafNodeOption::TreatCommentAsLeafNode}, + {LeafNodeType::TreatCommentAsLeafNode}, nullptr, "div", 0u}, @@ -1657,72 +1589,10 @@ TEST(HTMLEditUtilsTest, GetFirstLeafContent) GetLeafNodeTest{ u"<div><!--abc--><div><br></div><div><br></div></div>", "div", - {LeafNodeOption::TreatCommentAsLeafNode}, + {LeafNodeType::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()); @@ -1730,7 +1600,7 @@ TEST(HTMLEditUtilsTest, GetFirstLeafContent) nsDependentCString(testData.mContentSelector), IgnoreErrors()); MOZ_RELEASE_ASSERT(target); const nsIContent* result = HTMLEditUtils::GetFirstLeafContent( - *target, testData.mOptions, + *target, testData.mTypes, BlockInlineCheck::UseComputedDisplayOutsideStyle); EXPECT_EQ(result, testData.GetExpectedTarget(*body)) << "GetFirstLeafContent: " << testData @@ -1740,7 +1610,7 @@ TEST(HTMLEditUtilsTest, GetFirstLeafContent) TEST(HTMLEditUtilsTest, GetNextLeafContentOrNextBlockElement_Content) { - using LeafNodeOption = HTMLEditUtils::LeafNodeOption; + using LeafNodeType = HTMLEditUtils::LeafNodeType; const RefPtr<Document> doc = CreateHTMLDoc(); const RefPtr<nsGenericHTMLElement> body = doc->GetBody(); MOZ_RELEASE_ASSERT(body); @@ -1750,7 +1620,7 @@ TEST(HTMLEditUtilsTest, GetNextLeafContentOrNextBlockElement_Content) u"<div><br></div><!--abc--><p><br></p>", "div", {}, "p"}, GetLeafNodeTest{u"<div><br></div><!--abc--><p><br></p>", "div", - {LeafNodeOption::TreatCommentAsLeafNode}, + {LeafNodeType::TreatCommentAsLeafNode}, nullptr, "body", 1u}, @@ -1762,7 +1632,7 @@ TEST(HTMLEditUtilsTest, GetNextLeafContentOrNextBlockElement_Content) "span > br"}, GetLeafNodeTest{u"<div><br></div><span><!--abc--><br></span>", "div", - {LeafNodeOption::TreatCommentAsLeafNode}, + {LeafNodeType::TreatCommentAsLeafNode}, nullptr, "span", 0u}, @@ -1774,7 +1644,7 @@ TEST(HTMLEditUtilsTest, GetNextLeafContentOrNextBlockElement_Content) MOZ_RELEASE_ASSERT(target); const nsIContent* result = HTMLEditUtils::GetNextLeafContentOrNextBlockElement( - *target, testData.mOptions, + *target, testData.mTypes, BlockInlineCheck::UseComputedDisplayOutsideStyle); EXPECT_EQ(result, testData.GetExpectedTarget(*body->GetParentNode())) << "GetNextLeafContentOrNextBlockElement: " << testData @@ -1786,7 +1656,7 @@ TEST(HTMLEditUtilsTest, GetNextLeafContentOrNextBlockElement_Content) TEST(HTMLEditUtilsTest, GetPreviousLeafContentOrPreviousBlockElement_Content) { - using LeafNodeOption = HTMLEditUtils::LeafNodeOption; + using LeafNodeType = HTMLEditUtils::LeafNodeType; const RefPtr<Document> doc = CreateHTMLDoc(); const RefPtr<nsGenericHTMLElement> body = doc->GetBody(); MOZ_RELEASE_ASSERT(body); @@ -1796,7 +1666,7 @@ TEST(HTMLEditUtilsTest, GetPreviousLeafContentOrPreviousBlockElement_Content) u"<p><br></p><!--abc--><div><br></div>", "div", {}, "p"}, GetLeafNodeTest{u"<p><br></p><!--abc--><div><br></div>", "div", - {LeafNodeOption::TreatCommentAsLeafNode}, + {LeafNodeType::TreatCommentAsLeafNode}, nullptr, "body", 1u}, @@ -1808,7 +1678,7 @@ TEST(HTMLEditUtilsTest, GetPreviousLeafContentOrPreviousBlockElement_Content) "span > br"}, GetLeafNodeTest{u"<span><br><!--abc--></span><div><br></div>", "div", - {LeafNodeOption::TreatCommentAsLeafNode}, + {LeafNodeType::TreatCommentAsLeafNode}, nullptr, "span", 1u}, @@ -1820,7 +1690,7 @@ TEST(HTMLEditUtilsTest, GetPreviousLeafContentOrPreviousBlockElement_Content) MOZ_RELEASE_ASSERT(target); const nsIContent* result = HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( - *target, testData.mOptions, + *target, testData.mTypes, BlockInlineCheck::UseComputedDisplayOutsideStyle); EXPECT_EQ(result, testData.GetExpectedTarget(*body->GetParentNode())) << "GetPreviousLeafContentOrPreviousBlockElement: " << testData @@ -1831,440 +1701,6 @@ 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 - -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; @@ -2302,7 +1738,11 @@ TEST(HTMLEditUtilsTest, GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem) u"<div contenteditable><br> </div>", "div", Some(1), 2, true}, LineBreakBeforeBlockBoundaryTest{ u"<div contenteditable><br><!-- X --></div>", "div", Nothing{}, - 2, true}, + 2, + // FIXME: Currently, this fails with a bug of WSRunScanner + // (actually, a bug of + // HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement) + false}, LineBreakBeforeBlockBoundaryTest{ u"<div contenteditable><br><br></div>", "div", Nothing{}, 1, false}, diff --git a/testing/web-platform/meta/editing/run/delete.html.ini b/testing/web-platform/meta/editing/run/delete.html.ini @@ -571,6 +571,9 @@ [[["delete",""\]\] "<p>abc</p><ul><li contenteditable=\\"false\\">def</li></ul><p>[\]ghi</p>" compare innerHTML] expected: FAIL + [[["delete",""\]\] "<p>abc</p><ul><li contenteditable=\\"false\\">def</li><li>[\]ghi</li></ul>": execCommand("delete", false, "") return value] + expected: FAIL + [[["delete",""\]\] "<p>abc</p><ul><li contenteditable=\\"false\\">def</li><li>[\]ghi</li></ul>" compare innerHTML] expected: FAIL