commit 12eb928cb50025afa509f0c2a9ff85e6adc37c36
parent 58b4243d101d51384ac4713b5c9916328af95733
Author: Masayuki Nakano <masayuki@d-toybox.com>
Date: Wed, 7 Jan 2026 01:10:21 +0000
Bug 1998077 - part 6: Reimplement `HTMLEditUtils::Get(First|Last)Child()` and `HTMLEditUtils::Get(Next|Previous)Sibling()` with `LeafNodeOptions` r=m_kato
`WalkTreeOptions` is similar to `LeafNodeOptions` and it's hard to
maintain both of them. Additionally, `LeafNodeOptions` can specify
which type of leaf nodes should be ignored at scanning. Therefore,
`LeafNodeOptions` can be used for them.
Differential Revision: https://phabricator.services.mozilla.com/D277523
Diffstat:
10 files changed, 616 insertions(+), 245 deletions(-)
diff --git a/editor/libeditor/HTMLEditSubActionHandler.cpp b/editor/libeditor/HTMLEditSubActionHandler.cpp
@@ -4430,8 +4430,10 @@ 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, {WalkTreeOption::IgnoreWhiteSpaceOnlyText,
- WalkTreeOption::IgnoreNonEditableNode})) {
+ aContentMovingToSubList,
+ {LeafNodeOption::IgnoreInvisibleText,
+ LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
if (HTMLEditUtils::IsListElement(*nextEditableSibling) &&
aPointInListElement.GetContainer()->NodeInfo()->NameAtom() ==
nextEditableSibling->NodeInfo()->NameAtom() &&
@@ -4453,8 +4455,9 @@ Result<EditorDOMPoint, nsresult> HTMLEditor::IndentListChildWithTransaction(
if (const nsCOMPtr<nsIContent> previousEditableSibling =
HTMLEditUtils::GetPreviousSibling(
aContentMovingToSubList,
- {WalkTreeOption::IgnoreWhiteSpaceOnlyText,
- WalkTreeOption::IgnoreNonEditableNode})) {
+ {LeafNodeOption::IgnoreInvisibleText,
+ LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
if (HTMLEditUtils::IsListElement(*previousEditableSibling) &&
aPointInListElement.GetContainer()->NodeInfo()->NameAtom() ==
previousEditableSibling->NodeInfo()->NameAtom() &&
@@ -4477,8 +4480,9 @@ Result<EditorDOMPoint, nsresult> HTMLEditor::IndentListChildWithTransaction(
nsIContent* previousEditableSibling =
*aSubListElement ? HTMLEditUtils::GetPreviousSibling(
aContentMovingToSubList,
- {WalkTreeOption::IgnoreWhiteSpaceOnlyText,
- WalkTreeOption::IgnoreNonEditableNode})
+ {LeafNodeOption::IgnoreInvisibleText,
+ LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle)
: nullptr;
if (!*aSubListElement || (previousEditableSibling &&
previousEditableSibling != *aSubListElement)) {
@@ -5184,10 +5188,11 @@ 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* previousEditableSibling =
+ nsIContent* const previousEditableSibling =
subListElement
? HTMLEditUtils::GetPreviousSibling(
- *listItem, {WalkTreeOption::IgnoreNonEditableNode})
+ *listItem, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle)
: nullptr;
if (!subListElement || (previousEditableSibling &&
previousEditableSibling != subListElement)) {
@@ -7101,8 +7106,9 @@ 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,
- {WalkTreeOption::IgnoreNonEditableNode});
+ HTMLEditUtils::GetFirstChild(
+ aBlockElement, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
if (!firstEditableContent) {
// This block has no editable content, nothing to align.
return EditorDOMPoint();
@@ -7111,7 +7117,8 @@ 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, {WalkTreeOption::IgnoreNonEditableNode});
+ aBlockElement, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
if (firstEditableContent == lastEditableContent &&
firstEditableContent->IsHTMLElement(nsGkAtoms::div)) {
// XXX Chrome uses `style="text-align: foo"` instead of the legacy `align`
@@ -8331,9 +8338,11 @@ HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction(
// 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* nextEditableSibling = HTMLEditUtils::GetNextSibling(
- *splitPoint.GetChild(),
- {WalkTreeOption::IgnoreNonEditableNode})) {
+ if (nsIContent* const nextEditableSibling =
+ HTMLEditUtils::GetNextSibling(
+ *splitPoint.GetChild(),
+ {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
if (!HTMLEditUtils::IsBlockElement(
*nextEditableSibling,
BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
@@ -8420,16 +8429,18 @@ nsresult HTMLEditor::JoinNearestEditableNodesWithTransaction(
}
// Remember the last left child, and first right child
- nsCOMPtr<nsIContent> lastEditableChildOfLeftContent =
- HTMLEditUtils::GetLastChild(aNodeLeft,
- {WalkTreeOption::IgnoreNonEditableNode});
+ const nsCOMPtr<nsIContent> lastEditableChildOfLeftContent =
+ HTMLEditUtils::GetLastChild(
+ aNodeLeft, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
if (MOZ_UNLIKELY(NS_WARN_IF(!lastEditableChildOfLeftContent))) {
return NS_ERROR_FAILURE;
}
- nsCOMPtr<nsIContent> firstEditableChildOfRightContent =
- HTMLEditUtils::GetFirstChild(aNodeRight,
- {WalkTreeOption::IgnoreNonEditableNode});
+ const nsCOMPtr<nsIContent> firstEditableChildOfRightContent =
+ HTMLEditUtils::GetFirstChild(
+ aNodeRight, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
if (NS_WARN_IF(!firstEditableChildOfRightContent)) {
return NS_ERROR_FAILURE;
}
@@ -8829,9 +8840,10 @@ 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* previousEditableContentInBlockAtCaret =
+ if (nsIContent* const previousEditableContentInBlockAtCaret =
HTMLEditUtils::GetPreviousSibling(
- *atCaret.GetChild(), {WalkTreeOption::IgnoreNonEditableNode})) {
+ *atCaret.GetChild(), {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
if (HTMLEditUtils::IsBlockElement(
*previousEditableContentInBlockAtCaret,
BlockInlineCheck::UseComputedDisplayStyle)) {
@@ -8848,9 +8860,10 @@ 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* nextEditableContentInBlockAtCaret =
+ if (nsIContent* const nextEditableContentInBlockAtCaret =
HTMLEditUtils::GetNextSibling(
- *atCaret.GetChild(), {WalkTreeOption::IgnoreNonEditableNode})) {
+ *atCaret.GetChild(), {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
if (HTMLEditUtils::IsBlockElement(
*nextEditableContentInBlockAtCaret,
BlockInlineCheck::UseComputedDisplayStyle)) {
@@ -9341,9 +9354,11 @@ 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, {WalkTreeOption::IgnoreNonEditableNode});
+ aListItemElement, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
const bool isLastListItem = HTMLEditUtils::IsLastChild(
- aListItemElement, {WalkTreeOption::IgnoreNonEditableNode});
+ aListItemElement, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
Element* leftListElement = aListItemElement.GetParentElement();
if (NS_WARN_IF(!leftListElement)) {
@@ -9925,8 +9940,9 @@ HTMLEditor::EnsureHardLineBeginsWithFirstChildOf(
Element& aRemovingContainerElement) {
MOZ_ASSERT(IsEditActionDataAvailable());
- nsIContent* firstEditableChild = HTMLEditUtils::GetFirstChild(
- aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode});
+ nsIContent* const firstEditableChild = HTMLEditUtils::GetFirstChild(
+ aRemovingContainerElement, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
if (!firstEditableChild) {
return CreateElementResult::NotHandled();
}
@@ -9937,8 +9953,9 @@ HTMLEditor::EnsureHardLineBeginsWithFirstChildOf(
return CreateElementResult::NotHandled();
}
- nsIContent* previousEditableContent = HTMLEditUtils::GetPreviousSibling(
- aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode});
+ nsIContent* const previousEditableContent = HTMLEditUtils::GetPreviousSibling(
+ aRemovingContainerElement, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
if (!previousEditableContent) {
return CreateElementResult::NotHandled();
}
@@ -9970,8 +9987,9 @@ HTMLEditor::EnsureHardLineEndsWithLastChildOf(
Element& aRemovingContainerElement) {
MOZ_ASSERT(IsEditActionDataAvailable());
- nsIContent* firstEditableContent = HTMLEditUtils::GetLastChild(
- aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode});
+ nsIContent* const firstEditableContent = HTMLEditUtils::GetLastChild(
+ aRemovingContainerElement, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
if (!firstEditableContent) {
return CreateElementResult::NotHandled();
}
@@ -9982,8 +10000,9 @@ HTMLEditor::EnsureHardLineEndsWithLastChildOf(
return CreateElementResult::NotHandled();
}
- nsIContent* nextEditableContent = HTMLEditUtils::GetPreviousSibling(
- aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode});
+ nsIContent* const nextEditableContent = HTMLEditUtils::GetPreviousSibling(
+ aRemovingContainerElement, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
if (!nextEditableContent) {
return CreateElementResult::NotHandled();
}
@@ -10481,10 +10500,11 @@ nsresult HTMLEditor::MoveSelectedContentsToDivElementToMakeItAbsolutePosition(
// list element in the target `<div>` element for the destination.
// Therefore, duplicate same list element into the target `<div>`
// element.
- nsIContent* previousEditableContent =
+ nsIContent* const previousEditableContent =
createdListElement
? HTMLEditUtils::GetPreviousSibling(
- content, {WalkTreeOption::IgnoreNonEditableNode})
+ content, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle)
: nullptr;
if (!createdListElement ||
(previousEditableContent &&
@@ -10588,10 +10608,11 @@ 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* previousEditableContent =
+ nsIContent* const previousEditableContent =
createdListElement
? HTMLEditUtils::GetPreviousSibling(
- *listItemElement, {WalkTreeOption::IgnoreNonEditableNode})
+ *listItemElement, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle)
: nullptr;
if (!createdListElement ||
(previousEditableContent &&
diff --git a/editor/libeditor/HTMLEditUtils.cpp b/editor/libeditor/HTMLEditUtils.cpp
@@ -1634,6 +1634,77 @@ nsIContent* HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElementImpl(
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) {
@@ -3301,7 +3372,8 @@ HTMLEditUtils::ComputePointToPutCaretInElementIfOutside(
if (nodeBefore) {
// selection is after block. put at end of block.
const nsIContent* lastEditableContent = HTMLEditUtils::GetLastChild(
- aElement, {WalkTreeOption::IgnoreNonEditableNode});
+ aElement, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
if (!lastEditableContent) {
lastEditableContent = &aElement;
}
@@ -3315,7 +3387,8 @@ HTMLEditUtils::ComputePointToPutCaretInElementIfOutside(
// selection is before block. put at start of block.
const nsIContent* firstEditableContent = HTMLEditUtils::GetFirstChild(
- aElement, {WalkTreeOption::IgnoreNonEditableNode});
+ aElement, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
if (!firstEditableContent) {
firstEditableContent = &aElement;
}
@@ -3394,7 +3467,8 @@ size_t HTMLEditUtils::CollectChildren(
size_t numberOfFoundChildren = 0;
for (nsIContent* content =
- GetFirstChild(aNode, {WalkTreeOption::IgnoreNonEditableNode});
+ GetFirstChild(aNode, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
content; content = content->GetNextSibling()) {
if ((aOptions.contains(CollectChildrenOption::CollectListChildren) &&
(HTMLEditUtils::IsListElement(*content) ||
diff --git a/editor/libeditor/HTMLEditUtils.h b/editor/libeditor/HTMLEditUtils.h
@@ -1129,147 +1129,6 @@ class HTMLEditUtils final {
aFoundLinkElement);
}
- 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>;
- /**
- * 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
@@ -1413,6 +1272,7 @@ class HTMLEditUtils final {
private:
enum class StopAtBlockSibling : bool { No, Yes };
+
static nsIContent* GetNextLeafContentOrNextBlockElementImpl(
const nsIContent& aStartContent, StopAtBlockSibling aStopAtBlockSibling,
const LeafNodeOptions& aOptions, BlockInlineCheck aBlockInlineCheck,
@@ -1571,6 +1431,115 @@ class HTMLEditUtils final {
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);
+ }
+
+ private:
+ enum class FirstOrLastChild { First, Last };
+ static nsIContent* GetFirstOrLastChild(const nsINode& aNode,
+ FirstOrLastChild aFirstOrLastChild,
+ const LeafNodeOptions& aOptions,
+ BlockInlineCheck aBlockInlineCheck);
+
+ 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);
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Return true if aContent is the last child of aNode with ignoring all
+ * children which are specified by aOptions.
+ */
+ static bool IsLastChild(const nsIContent& aContent,
+ const LeafNodeOptions& aOptions,
+ BlockInlineCheck aBlockInlineCheck) {
+ nsINode* const parentNode = aContent.GetParentNode();
+ if (MOZ_UNLIKELY(!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 are specified by aOptions.
+ */
+ static bool IsFirstChild(const nsIContent& aContent,
+ const LeafNodeOptions& aOptions,
+ BlockInlineCheck aBlockInlineCheck) {
+ nsINode* const parentNode = aContent.GetParentNode();
+ if (MOZ_UNLIKELY(!parentNode)) {
+ return false;
+ }
+ return HTMLEditUtils::GetFirstChild(*parentNode, aOptions,
+ aBlockInlineCheck) == &aContent;
+ }
+
/**
* Returns a content node whose inline styles should be preserved after
* deleting content in a range. Typically, you should set aPoint to start
@@ -2021,6 +1990,14 @@ class HTMLEditUtils final {
return GetTableCellElementIfOnlyOneSelected(*firstRange);
}
+ enum class WalkTreeOption {
+ IgnoreNonEditableNode, // Ignore non-editable nodes and their children.
+ IgnoreDataNodeExceptText, // Ignore data nodes which are not text node.
+ IgnoreWhiteSpaceOnlyText, // Ignore text nodes having only white-spaces.
+ StopAtBlockBoundary, // Stop waking the tree at a block boundary.
+ };
+ using WalkTreeOptions = EnumSet<WalkTreeOption>;
+
/**
* GetInclusiveFirstChildWhichHasOneChild() returns the deepest element whose
* tag name is one of `aFirstElementName` and `aOtherElementNames...` if and
diff --git a/editor/libeditor/HTMLEditor.cpp b/editor/libeditor/HTMLEditor.cpp
@@ -1988,8 +1988,9 @@ 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,
- {WalkTreeOption::IgnoreNonEditableNode})) {
+ !HTMLEditUtils::IsLastChild(
+ *aElement, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
return NS_OK;
}
@@ -4932,16 +4933,18 @@ HTMLEditor::RemoveBlockContainerWithTransaction(Element& aElement) {
}
EditorDOMPoint pointToPutCaret;
if (HTMLEditUtils::CanNodeContain(*parentElement, *nsGkAtoms::br)) {
- if (nsCOMPtr<nsIContent> child = HTMLEditUtils::GetFirstChild(
- aElement, {WalkTreeOption::IgnoreNonEditableNode})) {
+ if (const nsCOMPtr<nsIContent> child = HTMLEditUtils::GetFirstChild(
+ aElement, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
// 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* previousSibling = HTMLEditUtils::GetPreviousSibling(
- aElement, {WalkTreeOption::IgnoreNonEditableNode})) {
+ if (nsIContent* const previousSibling = HTMLEditUtils::GetPreviousSibling(
+ aElement, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
if (!HTMLEditUtils::IsBlockElement(
*previousSibling,
BlockInlineCheck::UseComputedDisplayOutsideStyle) &&
@@ -4971,14 +4974,15 @@ HTMLEditor::RemoveBlockContainerWithTransaction(Element& aElement) {
// 3) last child of aNode is a br OR
// 4) either is null
- if (nsIContent* nextSibling = HTMLEditUtils::GetNextSibling(
- aElement, {WalkTreeOption::IgnoreNonEditableNode})) {
+ if (nsIContent* const nextSibling = HTMLEditUtils::GetNextSibling(
+ aElement, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
if (nextSibling &&
!HTMLEditUtils::IsBlockElement(
*nextSibling, BlockInlineCheck::UseComputedDisplayStyle)) {
- if (nsIContent* lastChild = HTMLEditUtils::GetLastChild(
- aElement, {WalkTreeOption::IgnoreNonEditableNode},
- BlockInlineCheck::Unused)) {
+ if (nsIContent* const lastChild = HTMLEditUtils::GetLastChild(
+ aElement, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
if (!HTMLEditUtils::IsBlockElement(
*lastChild, BlockInlineCheck::UseComputedDisplayStyle) &&
!lastChild->IsHTMLElement(nsGkAtoms::br)) {
@@ -5001,8 +5005,10 @@ HTMLEditor::RemoveBlockContainerWithTransaction(Element& aElement) {
}
}
}
- } else if (nsIContent* previousSibling = HTMLEditUtils::GetPreviousSibling(
- aElement, {WalkTreeOption::IgnoreNonEditableNode})) {
+ } else if (nsIContent* const previousSibling =
+ HTMLEditUtils::GetPreviousSibling(
+ aElement, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
// 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
@@ -5013,7 +5019,8 @@ HTMLEditor::RemoveBlockContainerWithTransaction(Element& aElement) {
*previousSibling, BlockInlineCheck::UseComputedDisplayStyle) &&
!previousSibling->IsHTMLElement(nsGkAtoms::br)) {
if (nsIContent* nextSibling = HTMLEditUtils::GetNextSibling(
- aElement, {WalkTreeOption::IgnoreNonEditableNode})) {
+ aElement, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
if (!HTMLEditUtils::IsBlockElement(
*nextSibling, BlockInlineCheck::UseComputedDisplayStyle) &&
!nextSibling->IsHTMLElement(nsGkAtoms::br)) {
@@ -6678,12 +6685,10 @@ 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 = nullptr;
- for (nsCOMPtr<nsIContent> child = &aPreviousBlock; child;
- child = HTMLEditUtils::GetLastChild(
- *child, {WalkTreeOption::IgnoreNonEditableNode})) {
- deepestEditableContent = child;
- }
+ nsIContent* deepestEditableContent = HTMLEditUtils::GetPreviousLeafContent(
+ EditorRawDOMPoint::AtEndOf(aPreviousBlock),
+ {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
while (deepestEditableContent &&
deepestEditableContent->IsHTMLElement(nsGkAtoms::br)) {
deepestEditableContent = HTMLEditUtils::GetPreviousLeafContent(
diff --git a/editor/libeditor/HTMLEditorDeleteHandler.cpp b/editor/libeditor/HTMLEditorDeleteHandler.cpp
@@ -7160,8 +7160,10 @@ 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,
- {WalkTreeOption::IgnoreNonEditableNode})) {
+ if (!HTMLEditUtils::IsFirstChild(
+ *mEmptyInclusiveAncestorBlockElement,
+ {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
return CreateLineBreakResult::NotHandled();
}
diff --git a/editor/libeditor/HTMLEditorInsertParagraphHandler.cpp b/editor/libeditor/HTMLEditorInsertParagraphHandler.cpp
@@ -1220,7 +1220,8 @@ bool HTMLEditor::AutoInsertParagraphHandler::ShouldCreateNewParagraph(
const auto* const precedingBRElement =
HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetPreviousSibling(
*aPointToSplit.ContainerAs<Text>(),
- {WalkTreeOption::IgnoreNonEditableNode}));
+ {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle));
return !IsNullOrInvisibleBRElementOrPaddingOneForEmptyLastLine(
precedingBRElement);
}
@@ -1232,7 +1233,8 @@ bool HTMLEditor::AutoInsertParagraphHandler::ShouldCreateNewParagraph(
const auto* const followingBRElement =
HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetNextSibling(
*aPointToSplit.ContainerAs<Text>(),
- {WalkTreeOption::IgnoreNonEditableNode}));
+ {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle));
return !IsNullOrInvisibleBRElementOrPaddingOneForEmptyLastLine(
followingBRElement);
}
@@ -1834,8 +1836,9 @@ 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,
- {WalkTreeOption::IgnoreNonEditableNode})) {
+ if (!HTMLEditUtils::IsLastChild(
+ aListItemElement, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
Result<SplitNodeResult, nsresult> splitListItemParentResult =
mHTMLEditor.SplitNodeWithTransaction(
EditorDOMPoint(&aListItemElement));
diff --git a/editor/libeditor/HTMLStyleEditor.cpp b/editor/libeditor/HTMLStyleEditor.cpp
@@ -64,6 +64,7 @@ using namespace dom;
using EditablePointOption = HTMLEditUtils::EditablePointOption;
using EditablePointOptions = HTMLEditUtils::EditablePointOptions;
using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
+using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
template nsresult HTMLEditor::SetInlinePropertiesAsSubAction(
@@ -950,7 +951,8 @@ HTMLEditor::AutoInlineStyleSetter::SplitTextNodeAndApplyStyleToMiddleNode(
if (mAttribute) {
// Look for siblings that are correct type of node
nsIContent* sibling = HTMLEditUtils::GetPreviousSibling(
- *middleTextNode, {WalkTreeOption::IgnoreNonEditableNode});
+ *middleTextNode, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
if (sibling && sibling->IsElement()) {
OwningNonNull<Element> element(*sibling->AsElement());
Result<bool, nsresult> result =
@@ -979,7 +981,8 @@ HTMLEditor::AutoInlineStyleSetter::SplitTextNodeAndApplyStyleToMiddleNode(
}
}
sibling = HTMLEditUtils::GetNextSibling(
- *middleTextNode, {WalkTreeOption::IgnoreNonEditableNode});
+ *middleTextNode, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
if (sibling && sibling->IsElement()) {
OwningNonNull<Element> element(*sibling->AsElement());
Result<bool, nsresult> result =
@@ -1060,10 +1063,13 @@ Result<CaretPoint, nsresult> HTMLEditor::AutoInlineStyleSetter::ApplyStyle(
}
// First check if there's an adjacent sibling we can put our node into.
- nsCOMPtr<nsIContent> previousSibling = HTMLEditUtils::GetPreviousSibling(
- aContent, {WalkTreeOption::IgnoreNonEditableNode});
- nsCOMPtr<nsIContent> nextSibling = HTMLEditUtils::GetNextSibling(
- aContent, {WalkTreeOption::IgnoreNonEditableNode});
+ const nsCOMPtr<nsIContent> previousSibling =
+ HTMLEditUtils::GetPreviousSibling(
+ aContent, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ const nsCOMPtr<nsIContent> nextSibling = HTMLEditUtils::GetNextSibling(
+ aContent, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
if (RefPtr<Element> previousElement =
Element::FromNodeOrNull(previousSibling)) {
Result<bool, nsresult> canMoveIntoPreviousSibling =
@@ -4172,7 +4178,8 @@ Result<CreateElementResult, nsresult> HTMLEditor::SetFontSizeOnTextNode(
aIncrementOrDecrement == FontSize::incr ? nsGkAtoms::big
: nsGkAtoms::small;
nsCOMPtr<nsIContent> sibling = HTMLEditUtils::GetPreviousSibling(
- *textNodeForTheRange, {WalkTreeOption::IgnoreNonEditableNode});
+ *textNodeForTheRange, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
if (sibling && sibling->IsHTMLElement(bigOrSmallTagName)) {
// Previous sib is already right kind of inline node; slide this over
Result<MoveNodeResult, nsresult> moveTextNodeResult =
@@ -4188,7 +4195,8 @@ Result<CreateElementResult, nsresult> HTMLEditor::SetFontSizeOnTextNode(
return CreateElementResult::NotHandled(std::move(pointToPutCaret));
}
sibling = HTMLEditUtils::GetNextSibling(
- *textNodeForTheRange, {WalkTreeOption::IgnoreNonEditableNode});
+ *textNodeForTheRange, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
if (sibling && sibling->IsHTMLElement(bigOrSmallTagName)) {
// Following sib is already right kind of inline node; slide this over
Result<MoveNodeResult, nsresult> moveTextNodeResult =
@@ -4322,7 +4330,8 @@ Result<EditorDOMPoint, nsresult> HTMLEditor::SetFontSizeWithBigOrSmallElement(
// Next, if next or previous is <big> or <small>, move aContent into it.
nsCOMPtr<nsIContent> sibling = HTMLEditUtils::GetPreviousSibling(
- aContent, {WalkTreeOption::IgnoreNonEditableNode});
+ aContent, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
if (sibling && sibling->IsHTMLElement(bigOrSmallTagName)) {
Result<MoveNodeResult, nsresult> moveNodeResult =
MoveNodeToEndWithTransaction(aContent, *sibling);
@@ -4337,7 +4346,8 @@ Result<EditorDOMPoint, nsresult> HTMLEditor::SetFontSizeWithBigOrSmallElement(
}
sibling = HTMLEditUtils::GetNextSibling(
- aContent, {WalkTreeOption::IgnoreNonEditableNode});
+ aContent, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
if (sibling && sibling->IsHTMLElement(bigOrSmallTagName)) {
Result<MoveNodeResult, nsresult> moveNodeResult =
MoveNodeWithTransaction(aContent, EditorDOMPoint(sibling, 0u));
diff --git a/editor/libeditor/WSRunScannerNestedClasses.cpp b/editor/libeditor/WSRunScannerNestedClasses.cpp
@@ -904,17 +904,17 @@ EditorDOMPointType WSRunScanner::TextFragmentData::GetInclusiveNextCharPoint(
const LeafNodeOptions leafNodeOptions =
ToLeafNodeOptions(aOptions) + LeafNodeOption::TreatChildBlockAsLeafNode;
const EditorRawDOMPoint point = [&]() MOZ_NEVER_INLINE_DEBUG {
- // TODO: Use HTMLEditUtils::GetNextSibling().
- 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 (!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));
if (!child ||
HTMLEditUtils::IsBlockElement(
*child, UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)) ||
@@ -1028,18 +1028,17 @@ EditorDOMPointType WSRunScanner::TextFragmentData::GetPreviousCharPoint(
const LeafNodeOptions leafNodeOptions =
ToLeafNodeOptions(aOptions) + LeafNodeOption::TreatChildBlockAsLeafNode;
const EditorRawDOMPoint point = [&]() MOZ_NEVER_INLINE_DEBUG {
- // TODO: Use HTMLEditUtils::GetPreviousSibling().
- 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 (!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));
if (!previousChild ||
HTMLEditUtils::IsBlockElement(
*previousChild,
diff --git a/editor/libeditor/WhiteSpaceVisibilityKeeper.cpp b/editor/libeditor/WhiteSpaceVisibilityKeeper.cpp
@@ -2523,9 +2523,10 @@ WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt(
}
}
- nsCOMPtr<nsIContent> previousEditableSibling =
+ const nsCOMPtr<nsIContent> previousEditableSibling =
HTMLEditUtils::GetPreviousSibling(
- aContentToDelete, {WalkTreeOption::IgnoreNonEditableNode});
+ aContentToDelete, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
// Delete the node, and join like nodes if appropriate
nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(aContentToDelete);
if (NS_FAILED(rv)) {
@@ -2548,8 +2549,9 @@ WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt(
return CaretPoint(std::move(pointToPutCaret));
}
- nsIContent* nextEditableSibling = HTMLEditUtils::GetNextSibling(
- *previousEditableSibling, {WalkTreeOption::IgnoreNonEditableNode});
+ nsIContent* const nextEditableSibling = HTMLEditUtils::GetNextSibling(
+ *previousEditableSibling, {LeafNodeOption::IgnoreNonEditableNode},
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
if (aCaretPoint.GetContainer() != nextEditableSibling) {
return CaretPoint(std::move(pointToPutCaret));
}
diff --git a/editor/libeditor/gtest/TestHTMLEditUtils.cpp b/editor/libeditor/gtest/TestHTMLEditUtils.cpp
@@ -1921,6 +1921,284 @@ TEST(HTMLEditUtilsTest, GetPreviousLeafContent_Content)
// 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})
+ << ")";
+ }
+}
+
struct MOZ_STACK_CLASS LineBreakBeforeBlockBoundaryTest final {
const char16_t* const mInnerHTML;
const char* const mContainer;