commit 4a5d93d6315c2809b13c130cd43802ce6716b904
parent b42dc9d35fc6d72432d156a76d4ee694e1577973
Author: Masayuki Nakano <masayuki@d-toybox.com>
Date: Wed, 7 Jan 2026 01:10:19 +0000
Bug 1998077 - part 2: Add `WSType::EmptyInlineContainerElement` r=m_kato
An empty inline container element such as `<span></span>` has 2
meanings, if it's visible with specified style, e.g., has non-zero
padding, it should be treated like a replaced element like `<img>`.
However, if it's invisible, it should be treated as `Comment` or
empty `Text`. Therefore, the scanner users need a special handling for
them in some cases. So, it should be identified with the new `WSType`.
Unfortunately, neither
`HTMLEditUtils::IsInclusiveAncestorCSSDisplayNone` nor
`HTMLEditUtils::IsVisibleElementEvenIfLeafNode` is testable within
GTest because it does not have layout information.
Differential Revision: https://phabricator.services.mozilla.com/D277519
Diffstat:
9 files changed, 246 insertions(+), 43 deletions(-)
diff --git a/editor/libeditor/HTMLEditUtils.cpp b/editor/libeditor/HTMLEditUtils.cpp
@@ -466,7 +466,8 @@ bool HTMLEditUtils::IsFlexOrGridItem(const nsIContent& aContent) {
}
bool HTMLEditUtils::IsInclusiveAncestorCSSDisplayNone(
- const nsIContent& aContent) {
+ const nsIContent& aContent,
+ const nsIContent* aAncestorLimiter /* = nullptr */) {
if (NS_WARN_IF(!aContent.IsInComposedDoc())) {
return true;
}
@@ -474,12 +475,14 @@ bool HTMLEditUtils::IsInclusiveAncestorCSSDisplayNone(
aContent.InclusiveFlatTreeAncestorsOfType<Element>()) {
RefPtr<const ComputedStyle> elementStyle =
nsComputedDOMStyle::GetComputedStyleNoFlush(element);
- if (NS_WARN_IF(!elementStyle)) {
- continue;
+ if (MOZ_LIKELY(elementStyle)) {
+ const nsStyleDisplay* styleDisplay = elementStyle->StyleDisplay();
+ if (MOZ_UNLIKELY(styleDisplay->mDisplay == StyleDisplay::None)) {
+ return true;
+ }
}
- const nsStyleDisplay* styleDisplay = elementStyle->StyleDisplay();
- if (MOZ_UNLIKELY(styleDisplay->mDisplay == StyleDisplay::None)) {
- return true;
+ if (element == aAncestorLimiter) {
+ break;
}
}
return false;
@@ -1542,6 +1545,10 @@ bool HTMLEditUtils::IsEmptyNode(nsPresContext* aPresContext,
continue;
}
+ if (childContent->IsComment()) {
+ continue;
+ }
+
MOZ_ASSERT(childContent != &aNode);
if (!aOptions.contains(EmptyCheckOption::TreatSingleBRElementAsVisible) &&
@@ -2824,17 +2831,14 @@ nsIContent* HTMLEditUtils::GetContentToPreserveInlineStyles(
if (nextVisibleThing.InVisibleOrCollapsibleCharacters()) {
return nextVisibleThing.TextPtr();
}
- if (nextVisibleThing.IsContentEditableRoot()) {
+ if (nextVisibleThing.ContentIsEditableRoot()) {
break;
}
- // 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())) {
+ // 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)) {
point.SetAfter(nextVisibleThing.ElementPtr());
continue;
}
diff --git a/editor/libeditor/HTMLEditUtils.h b/editor/libeditor/HTMLEditUtils.h
@@ -783,7 +783,8 @@ class HTMLEditUtils final {
/**
* Return true if `display` of inclusive ancestor of aContent is `none`.
*/
- static bool IsInclusiveAncestorCSSDisplayNone(const nsIContent& aContent);
+ [[nodiscard]] static bool IsInclusiveAncestorCSSDisplayNone(
+ const nsIContent& aContent, const nsIContent* aAncestorLimiter = nullptr);
/**
* IsVisiblePreformattedNewLine() and IsInvisiblePreformattedNewLine() return
diff --git a/editor/libeditor/HTMLEditorDataTransfer.cpp b/editor/libeditor/HTMLEditorDataTransfer.cpp
@@ -559,9 +559,12 @@ 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()) {
+ } else if (prevVisibleThingOfBRElement.ReachedSpecialContent() ||
+ prevVisibleThingOfBRElement
+ .ReachedEmptyInlineContainerElement()) {
pointToPutCaret = prevVisibleThingOfBRElement
.PointAfterReachedContentNode<EditorDOMPoint>();
}
diff --git a/editor/libeditor/HTMLEditorDeleteHandler.cpp b/editor/libeditor/HTMLEditorDeleteHandler.cpp
@@ -866,7 +866,7 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDelete(
if (scanFromCaretPointResult.BRElementPtr() == &aEditingHost) {
return NS_OK;
}
- if (!scanFromCaretPointResult.IsContentEditable()) {
+ if (!scanFromCaretPointResult.ContentIsEditable()) {
return NS_SUCCESS_DOM_NO_OPERATION;
}
if (scanFromCaretPointResult.ReachedInvisibleBRElement()) {
@@ -1162,7 +1162,7 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::Run(
if (scanFromCaretPointResult.BRElementPtr() == &aEditingHost) {
return EditActionResult::HandledResult();
}
- if (!scanFromCaretPointResult.IsContentEditable()) {
+ if (!scanFromCaretPointResult.ContentIsEditable()) {
return EditActionResult::CanceledResult();
}
if (scanFromCaretPointResult.ReachedInvisibleBRElement()) {
@@ -1276,6 +1276,7 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAroundCollapsedRanges(
}
if (aScanFromCaretPointResult.ReachedSpecialContent() ||
+ aScanFromCaretPointResult.ReachedEmptyInlineContainerElement() ||
aScanFromCaretPointResult.ReachedBRElement() ||
aScanFromCaretPointResult.ReachedHRElement() ||
aScanFromCaretPointResult.ReachedNonEditableOtherBlockElement()) {
@@ -1408,6 +1409,7 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAroundCollapsedRanges(
}
if (aScanFromCaretPointResult.ReachedSpecialContent() ||
+ aScanFromCaretPointResult.ReachedEmptyInlineContainerElement() ||
aScanFromCaretPointResult.ReachedBRElement() ||
aScanFromCaretPointResult.ReachedHRElement() ||
aScanFromCaretPointResult.ReachedNonEditableOtherBlockElement()) {
@@ -1657,7 +1659,8 @@ nsIContent* HTMLEditor::AutoDeleteRangesHandler::GetAtomicContentToDelete(
const WSScanResult& aScanFromCaretPointResult) {
MOZ_ASSERT(aScanFromCaretPointResult.GetContent());
- if (!aScanFromCaretPointResult.ReachedSpecialContent()) {
+ if (!aScanFromCaretPointResult.ReachedSpecialContent() &&
+ !aScanFromCaretPointResult.ReachedEmptyInlineContainerElement()) {
return aScanFromCaretPointResult.GetContent();
}
@@ -2082,7 +2085,7 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
const WSScanResult maybePreviousText =
scanner.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
EditorRawDOMPoint(mBRElement));
- if (maybePreviousText.IsContentEditable() &&
+ if (maybePreviousText.ContentIsEditable() &&
maybePreviousText.InVisibleOrCollapsibleCharacters() &&
!HTMLEditor::GetLinkElement(maybePreviousText.TextPtr())) {
return maybePreviousText.PointAfterReachedContent<EditorDOMPoint>();
@@ -2090,7 +2093,7 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
const WSScanResult maybeNextText =
scanner.ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
EditorRawDOMPoint::After(*mBRElement));
- if (maybeNextText.IsContentEditable() &&
+ if (maybeNextText.ContentIsEditable() &&
maybeNextText.InVisibleOrCollapsibleCharacters()) {
return maybeNextText.PointAtReachedContent<EditorDOMPoint>();
}
@@ -3705,7 +3708,7 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
const WSScanResult maybePreviousText =
WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
{}, startOfRightContent, &aEditingHost);
- if (maybePreviousText.IsContentEditable() &&
+ if (maybePreviousText.ContentIsEditable() &&
maybePreviousText.InVisibleOrCollapsibleCharacters()) {
nsresult rv = aHTMLEditor.CollapseSelectionTo(
maybePreviousText.PointAfterReachedContent<EditorRawDOMPoint>());
@@ -5657,7 +5660,7 @@ Result<DeleteRangeResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
const WSScanResult maybePreviousText =
WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
{}, maybeDeepStartOfRightContent, &aEditingHost);
- if (maybePreviousText.IsContentEditable() &&
+ if (maybePreviousText.ContentIsEditable() &&
maybePreviousText.InVisibleOrCollapsibleCharacters()) {
return maybePreviousText.PointAfterReachedContent<EditorDOMPoint>();
}
diff --git a/editor/libeditor/HTMLEditorInsertLineBreakHandler.cpp b/editor/libeditor/HTMLEditorInsertLineBreakHandler.cpp
@@ -235,10 +235,14 @@ nsresult HTMLEditor::AutoInsertLineBreakHandler::HandleInsertBRElement() {
pointToPutCaret = forwardScanFromAfterBRElementResult
.PointAtReachedContent<EditorDOMPoint>();
} else if (forwardScanFromAfterBRElementResult.ReachedSpecialContent()) {
- // Next inserting text should be inserted into styled inline elements if
- // they have first visible thing in the new line.
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);
}
nsresult rv = mHTMLEditor.CollapseSelectionTo(pointToPutCaret);
diff --git a/editor/libeditor/HTMLEditorInsertParagraphHandler.cpp b/editor/libeditor/HTMLEditorInsertParagraphHandler.cpp
@@ -1060,7 +1060,9 @@ nsresult HTMLEditor::AutoInsertParagraphHandler::
if (!backwardScanFromPointToCreateNewBRElementResult
.InVisibleOrCollapsibleCharacters() &&
!backwardScanFromPointToCreateNewBRElementResult
- .ReachedSpecialContent()) {
+ .ReachedSpecialContent() &&
+ !backwardScanFromPointToCreateNewBRElementResult
+ .ReachedEmptyInlineContainerElement()) {
return NS_SUCCESS_DOM_NO_OPERATION;
}
const WSScanResult forwardScanFromPointAfterNewBRElementResult =
@@ -1075,6 +1077,8 @@ nsresult HTMLEditor::AutoInsertParagraphHandler::
if (!forwardScanFromPointAfterNewBRElementResult
.InVisibleOrCollapsibleCharacters() &&
!forwardScanFromPointAfterNewBRElementResult.ReachedSpecialContent() &&
+ !forwardScanFromPointAfterNewBRElementResult
+ .ReachedEmptyInlineContainerElement() &&
// In case we're at the very end.
!forwardScanFromPointAfterNewBRElementResult
.ReachedCurrentBlockBoundary()) {
diff --git a/editor/libeditor/WSRunScanner.cpp b/editor/libeditor/WSRunScanner.cpp
@@ -36,6 +36,7 @@ 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,6 +74,15 @@ void WSScanResult::AssertIfInvalidData(const WSRunScanner& aScanner) const {
MOZ_ASSERT_IF(mReason == WSType::PreformattedLineBreak,
EditorUtils::IsNewLinePreformatted(*mContent));
MOZ_ASSERT_IF(
+ mReason == WSType::EmptyInlineContainerElement,
+ HTMLEditUtils::IsEmptyInlineContainer(
+ *mContent,
+ {HTMLEditUtils::EmptyCheckOption::TreatSingleBRElementAsVisible,
+ HTMLEditUtils::EmptyCheckOption::TreatBlockAsVisible},
+ aScanner.ReferredHTMLDefaultStyle()
+ ? BlockInlineCheck::UseHTMLDefaultStyle
+ : BlockInlineCheck::UseComputedDisplayOutsideStyle));
+ MOZ_ASSERT_IF(
mReason == WSType::SpecialContent,
(mContent->IsText() && !mContent->IsEditable()) ||
(!mContent->IsHTMLElement(nsGkAtoms::br) &&
@@ -80,6 +90,13 @@ void WSScanResult::AssertIfInvalidData(const WSRunScanner& aScanner) const {
*mContent,
aScanner.ReferredHTMLDefaultStyle()
? BlockInlineCheck::UseHTMLDefaultStyle
+ : BlockInlineCheck::UseComputedDisplayOutsideStyle) &&
+ !HTMLEditUtils::IsEmptyInlineContainer(
+ *mContent,
+ {HTMLEditUtils::EmptyCheckOption::TreatSingleBRElementAsVisible,
+ HTMLEditUtils::EmptyCheckOption::TreatBlockAsVisible},
+ aScanner.ReferredHTMLDefaultStyle()
+ ? BlockInlineCheck::UseHTMLDefaultStyle
: BlockInlineCheck::UseComputedDisplayOutsideStyle)));
MOZ_ASSERT_IF(
mReason == WSType::OtherBlockBoundary,
@@ -1030,6 +1047,7 @@ WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent(
if (textFragmentDataAtStart.EndsByVisibleBRElement()) {
startContent = textFragmentDataAtStart.EndReasonBRElementPtr();
} else if (textFragmentDataAtStart.EndsBySpecialContent() ||
+ textFragmentDataAtStart.EndsByEmptyInlineContainerElement() ||
(textFragmentDataAtStart.EndsByOtherBlockElement() &&
!HTMLEditUtils::IsContainerNode(
*textFragmentDataAtStart
@@ -1052,6 +1070,7 @@ 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,6 +51,11 @@ 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.
@@ -81,6 +86,8 @@ 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:
@@ -180,13 +187,22 @@ class MOZ_STACK_CLASS WSScanResult final {
}
/**
- * Returns true if found or reached content is editable.
+ * Return true if found or reached content is editable.
*/
- bool IsContentEditable() const { return mContent && mContent->IsEditable(); }
+ [[nodiscard]] bool ContentIsEditable() const {
+ return mContent && HTMLEditUtils::IsSimplyEditableNode(*mContent);
+ }
+
+ /**
+ * Return true if found or reached content is removable node.
+ */
+ [[nodiscard]] bool ContentIsRemovable() const {
+ return mContent && HTMLEditUtils::IsRemovableNode(*mContent);
+ }
- [[nodiscard]] bool IsContentEditableRoot() const {
- return mContent && mContent->IsElement() &&
- HTMLEditUtils::ElementIsEditableRoot(*mContent->AsElement());
+ [[nodiscard]] bool ContentIsEditableRoot() const {
+ return ContentIsElement() &&
+ HTMLEditUtils::ElementIsEditableRoot(*ElementPtr());
}
/**
@@ -258,10 +274,97 @@ 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.
*/
- bool ReachedSpecialContent() const {
+ [[nodiscard]] constexpr bool ReachedSpecialContent() const {
return mReason == WSType::SpecialContent;
}
@@ -397,7 +500,7 @@ class MOZ_STACK_CLASS WSScanResult final {
private:
nsCOMPtr<nsIContent> mContent;
Maybe<uint32_t> mOffset;
- WSType mReason;
+ WSType mReason = WSType::NotInitialized;
ScanDirection mDirection = ScanDirection::Backward;
};
@@ -681,9 +784,12 @@ class MOZ_STACK_CLASS WSRunScanner final {
bool StartsFromNonCollapsibleCharacters() const {
return mLeftWSType == WSType::NonCollapsibleCharacters;
}
- bool StartsFromSpecialContent() const {
+ [[nodiscard]] constexpr bool StartsFromSpecialContent() const {
return mLeftWSType == WSType::SpecialContent;
}
+ [[nodiscard]] constexpr bool StartsFromEmptyInlineContainerElement() const {
+ return mLeftWSType == WSType::EmptyInlineContainerElement;
+ }
bool StartsFromPreformattedLineBreak() const {
return mLeftWSType == WSType::PreformattedLineBreak;
}
@@ -698,9 +804,12 @@ class MOZ_STACK_CLASS WSRunScanner final {
bool EndsByTrailingWhiteSpaces() const {
return mRightWSType == WSType::TrailingWhiteSpaces;
}
- bool EndsBySpecialContent() const {
+ [[nodiscard]] constexpr 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;
@@ -908,9 +1017,12 @@ class MOZ_STACK_CLASS WSRunScanner final {
bool IsNonCollapsibleCharacters() const {
return mReason == WSType::NonCollapsibleCharacters;
}
- bool IsSpecialContent() const {
+ [[nodiscard]] constexpr 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;
@@ -960,8 +1072,9 @@ class MOZ_STACK_CLASS WSRunScanner final {
EditorDOMPoint mPoint;
// Must be one of WSType::NotInitialized,
// WSType::NonCollapsibleCharacters, WSType::SpecialContent,
- // WSType::BRElement, WSType::CurrentBlockBoundary,
- // WSType::OtherBlockBoundary or WSType::InlineEditingHostBoundary.
+ // WSType::EmptyInlineContainerElement, WSType::BRElement,
+ // WSType::CurrentBlockBoundary, WSType::OtherBlockBoundary or
+ // WSType::InlineEditingHostBoundary.
WSType mReason = WSType::NotInitialized;
};
@@ -1026,7 +1139,12 @@ class MOZ_STACK_CLASS WSRunScanner final {
bool StartsFromNonCollapsibleCharacters() const {
return mStart.IsNonCollapsibleCharacters();
}
- bool StartsFromSpecialContent() const { return mStart.IsSpecialContent(); }
+ [[nodiscard]] bool StartsFromSpecialContent() const {
+ return mStart.IsSpecialContent();
+ }
+ [[nodiscard]] bool StartsFromEmptyInlineContainerElement() const {
+ return mStart.IsEmptyInlineContainerElement();
+ }
bool StartsFromBRElement() const { return mStart.IsBRElement(); }
bool StartsFromVisibleBRElement() const {
return StartsFromBRElement() &&
@@ -1053,7 +1171,12 @@ class MOZ_STACK_CLASS WSRunScanner final {
bool EndsByNonCollapsibleCharacters() const {
return mEnd.IsNonCollapsibleCharacters();
}
- bool EndsBySpecialContent() const { return mEnd.IsSpecialContent(); }
+ [[nodiscard]] bool EndsBySpecialContent() const {
+ return mEnd.IsSpecialContent();
+ }
+ [[nodiscard]] bool EndsByEmptyInlineContainerElement() const {
+ return mEnd.IsEmptyInlineContainerElement();
+ }
bool EndsByBRElement() const { return mEnd.IsBRElement(); }
bool EndsByVisibleBRElement() const {
return EndsByBRElement() &&
diff --git a/editor/libeditor/WSRunScannerNestedClasses.cpp b/editor/libeditor/WSRunScannerNestedClasses.cpp
@@ -274,6 +274,19 @@ WSRunScanner::TextFragmentData::BoundaryData WSRunScanner::TextFragmentData::
WSType::OtherBlockBoundary);
}
+ if (previousLeafContentOrBlock->IsElement() &&
+ HTMLEditUtils::IsEmptyInlineContainer(
+ *previousLeafContentOrBlock,
+ {HTMLEditUtils::EmptyCheckOption::TreatSingleBRElementAsVisible,
+ HTMLEditUtils::EmptyCheckOption::TreatBlockAsVisible},
+ UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck))) {
+ // Okay, it's an empty inline container. Basically, it's invisible but may
+ // be visible if it has non-zero padding/margin, etc. So, the scanner user
+ // may need to additional check.
+ return BoundaryData(aPoint, *previousLeafContentOrBlock,
+ WSType::EmptyInlineContainerElement);
+ }
+
if (!previousLeafContentOrBlock->IsText() ||
(aOptions.contains(Option::OnlyEditableNodes) &&
HTMLEditUtils::IsSimplyEditableNode(*previousLeafContentOrBlock) !=
@@ -438,6 +451,19 @@ WSRunScanner::TextFragmentData::BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
WSType::OtherBlockBoundary);
}
+ if (nextLeafContentOrBlock->IsElement() &&
+ HTMLEditUtils::IsEmptyInlineContainer(
+ *nextLeafContentOrBlock,
+ {HTMLEditUtils::EmptyCheckOption::TreatSingleBRElementAsVisible,
+ HTMLEditUtils::EmptyCheckOption::TreatBlockAsVisible},
+ UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck))) {
+ // Okay, it's an empty inline container. Basically, it's invisible but may
+ // be visible if it has non-zero padding/margin, etc. So, the scanner user
+ // may need to additional check.
+ return BoundaryData(aPoint, *nextLeafContentOrBlock,
+ WSType::EmptyInlineContainerElement);
+ }
+
if (!nextLeafContentOrBlock->IsText() ||
(aOptions.contains(Option::OnlyEditableNodes) &&
HTMLEditUtils::IsSimplyEditableNode(*nextLeafContentOrBlock) !=
@@ -599,9 +625,19 @@ 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();
+ !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()));
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) {
@@ -1398,7 +1434,10 @@ EditorDOMPointInText WSRunScanner::TextFragmentData::
const VisibleWhiteSpacesData& visibleWhiteSpaces =
VisibleWhiteSpacesDataRef();
if (!visibleWhiteSpaces.StartsFromNonCollapsibleCharacters() &&
- !visibleWhiteSpaces.StartsFromSpecialContent()) {
+ !visibleWhiteSpaces.StartsFromSpecialContent() &&
+ !(visibleWhiteSpaces.StartsFromEmptyInlineContainerElement() &&
+ HTMLEditUtils::IsVisibleElementEvenIfLeafNode(
+ *GetStartReasonContent()))) {
return EditorDOMPointInText();
}
return atPreviousChar;
@@ -1456,6 +1495,9 @@ EditorDOMPointInText WSRunScanner::TextFragmentData::
VisibleWhiteSpacesDataRef();
if (!visibleWhiteSpaces.EndsByNonCollapsibleCharacters() &&
!visibleWhiteSpaces.EndsBySpecialContent() &&
+ !(visibleWhiteSpaces.EndsByEmptyInlineContainerElement() &&
+ HTMLEditUtils::IsVisibleElementEvenIfLeafNode(
+ *GetEndReasonContent())) &&
!visibleWhiteSpaces.EndsByBRElement()) {
return EditorDOMPointInText();
}