commit ca50a4a628a3ea7e8b7fcd8071a48881e3a0846a
parent 4a5d93d6315c2809b13c130cd43802ce6716b904
Author: Masayuki Nakano <masayuki@d-toybox.com>
Date: Wed, 7 Jan 2026 01:10:20 +0000
Bug 1998077 - part 3: Add new options to ignore empty/invisible leaf nodes r=m_kato
This adds some options which ignore some types of inline elements.
For example, `IgnoreAnyEmptyInlineContainers` is useful to scan previous
or next editable point because Chrome ignores empty inline elements when
deleting from next to an inline element.
`IgnoreInvisibleInlineVoidElements` is useful to scan invisible line
break from end of a block or checking a line break is invisible because
visible inline element makes a line after the last line break. E.g.,
```html
<div>ABC<br><span></span></div>
```
This shows only one line. However,
```html
<div>ABC<br><span style="border:1px solid"></span></div>
```
This shows 2 lines in the `<div>`.
Additionally, renaming the existing `LeafNodeType` values for making
them easier to understand.
Differential Revision: https://phabricator.services.mozilla.com/D277520
Diffstat:
15 files changed, 1018 insertions(+), 740 deletions(-)
diff --git a/editor/libeditor/EditorBase.cpp b/editor/libeditor/EditorBase.cpp
@@ -144,8 +144,6 @@ using namespace dom;
using namespace widget;
using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
-using LeafNodeType = HTMLEditUtils::LeafNodeType;
-using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes;
using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
static LazyLogModule gEventLog("EditorEvent");
diff --git a/editor/libeditor/HTMLEditSubActionHandler.cpp b/editor/libeditor/HTMLEditSubActionHandler.cpp
@@ -78,8 +78,8 @@ extern LazyLogModule gTextInputLog; // Defined in EditorBase.cpp
using namespace dom;
using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
using EmptyCheckOptions = HTMLEditUtils::EmptyCheckOptions;
-using LeafNodeType = HTMLEditUtils::LeafNodeType;
-using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes;
+using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
+using LeafNodeOptions = HTMLEditUtils::LeafNodeOptions;
using WalkTextOption = HTMLEditUtils::WalkTextOption;
using WalkTreeDirection = HTMLEditUtils::WalkTreeDirection;
using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
@@ -926,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, {LeafNodeType::OnlyLeafNode});
+ nsIContent* firstLeafChild =
+ HTMLEditUtils::GetFirstLeafContent(*mRootElement, {});
if (firstLeafChild &&
EditorUtils::IsPaddingBRElementForEmptyEditor(*firstLeafChild)) {
mPaddingBRElementForEmptyEditor =
@@ -2566,7 +2566,8 @@ HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces(
// Try to put caret next to immediately after previous editable leaf.
nsIContent* previousContent =
HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
- newCaretPosition, {LeafNodeType::LeafNodeOrNonEditableNode},
+ newCaretPosition,
+ {LeafNodeOption::TreatNonEditableNodeAsLeafNode},
BlockInlineCheck::UseComputedDisplayStyle,
editableBlockElementOrInlineEditingHost);
if (previousContent &&
@@ -2584,7 +2585,7 @@ HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces(
else if (nsIContent* nextContent =
HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
newCaretPosition,
- {LeafNodeType::LeafNodeOrNonEditableNode},
+ {LeafNodeOption::TreatNonEditableNodeAsLeafNode},
BlockInlineCheck::UseComputedDisplayStyle,
editableBlockElementOrInlineEditingHost)) {
if (HTMLEditUtils::IsSimplyEditableNode(*nextContent) &&
@@ -7231,7 +7232,7 @@ HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction(
// endpoint is just after the close of a block.
if (nsIContent* child = HTMLEditUtils::GetLastLeafContent(
*prevVisibleThingOfEndPoint.ElementPtr(),
- {LeafNodeType::LeafNodeOrChildBlock},
+ {LeafNodeOption::TreatChildBlockAsLeafNode},
BlockInlineCheck::UseHTMLDefaultStyle)) {
newRange.SetEnd(EditorRawDOMPoint::After(*child));
}
@@ -7272,7 +7273,7 @@ HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction(
// startpoint is just before the start of a block.
if (nsIContent* child = HTMLEditUtils::GetFirstLeafContent(
*nextVisibleThingOfStartPoint.ElementPtr(),
- {LeafNodeType::LeafNodeOrChildBlock},
+ {LeafNodeOption::TreatChildBlockAsLeafNode},
BlockInlineCheck::UseHTMLDefaultStyle)) {
newRange.SetStart(EditorRawDOMPoint(child));
}
diff --git a/editor/libeditor/HTMLEditUtils.cpp b/editor/libeditor/HTMLEditUtils.cpp
@@ -57,6 +57,36 @@ namespace mozilla {
using namespace dom;
using EditorType = EditorBase::EditorType;
+template nsIContent* HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
+ const EditorDOMPoint&, const LeafNodeOptions&, BlockInlineCheck,
+ const Element*);
+template nsIContent* HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
+ const EditorRawDOMPoint&, const LeafNodeOptions&, BlockInlineCheck,
+ const Element*);
+template nsIContent* HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
+ const EditorDOMPointInText&, const LeafNodeOptions&, BlockInlineCheck,
+ const Element*);
+template nsIContent* HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
+ const EditorRawDOMPointInText&, const LeafNodeOptions&, BlockInlineCheck,
+ const Element*);
+
+template nsIContent*
+HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
+ const EditorDOMPoint&, const LeafNodeOptions&, BlockInlineCheck,
+ const Element*);
+template nsIContent*
+HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
+ const EditorRawDOMPoint&, const LeafNodeOptions&, BlockInlineCheck,
+ const Element*);
+template nsIContent*
+HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
+ const EditorDOMPointInText&, const LeafNodeOptions&, BlockInlineCheck,
+ const Element*);
+template nsIContent*
+HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
+ const EditorRawDOMPointInText&, const LeafNodeOptions&, BlockInlineCheck,
+ const Element*);
+
template nsIContent* HTMLEditUtils::GetPreviousContent(
const EditorDOMPoint& aPoint, const WalkTreeOptions& aOptions,
BlockInlineCheck aBlockInlineCheck, const Element* aAncestorLimiter);
@@ -924,9 +954,12 @@ EditorDOMPoint HTMLEditUtils::LineRequiresPaddingLineBreakToBeVisible(
// because we want to make it visible. Therefore, we cannot use
// WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary() here.
nsIContent* const previousVisibleLeafOrChildBlock =
- HTMLEditUtils::GetPreviousNonEmptyLeafContentOrPreviousBlockElement(
+ HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
preferredPaddingLineBreakPoint,
- {LeafNodeType::LeafNodeOrChildBlock}, BlockInlineCheck::Auto);
+ {LeafNodeOption::TreatChildBlockAsLeafNode,
+ LeafNodeOption::IgnoreInvisibleEmptyInlineContainers,
+ LeafNodeOption::IgnoreEmptyText},
+ BlockInlineCheck::Auto);
if (!previousVisibleLeafOrChildBlock) {
// Reached current block.
return true;
@@ -1093,6 +1126,533 @@ 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::GetNextLeafContentOrNextBlockElement(
+ const nsIContent& aStartContent, 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 ||
+ 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 (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::GetNextLeafContentOrNextBlockElement(
+ const EditorDOMPointBase<PT, CT>& aStartPoint,
+ 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::GetNextLeafContentOrNextBlockElement(
+ *aStartPoint.template ContainerAs<nsIContent>(), aOptions,
+ aBlockInlineCheck, aAncestorLimiter);
+ }
+ if (!HTMLEditUtils::IsContainerNode(
+ *aStartPoint.template ContainerAs<Element>()) ||
+ HTMLEditUtils::IsReplacedElement(
+ *aStartPoint.template ContainerAs<Element>())) {
+ return HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
+ *aStartPoint.template ContainerAs<nsIContent>(), aOptions,
+ aBlockInlineCheck, aAncestorLimiter);
+ }
+
+ for (nsIContent* nextContent = aStartPoint.GetChild();;) {
+ if (!nextContent) {
+ if (aStartPoint.GetContainer() == aAncestorLimiter ||
+ HTMLEditUtils::IsBlockElement(
+ *aStartPoint.template ContainerAs<Element>(),
+ UseComputedDisplayStyleIfAuto(aBlockInlineCheck))) {
+ // We are at end of the block.
+ return nullptr;
+ }
+
+ // We are at end of non-block container
+ return HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
+ *aStartPoint.template ContainerAs<Element>(), aOptions,
+ PreferDisplayOutsideIfUsingDisplay(aBlockInlineCheck),
+ aAncestorLimiter);
+ }
+
+ // We have a next node. If it's a block, return it.
+ if (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::GetPreviousLeafContentOrPreviousBlockElement(
+ const nsIContent& aStartContent, 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 ||
+ 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 (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::GetPreviousLeafContentOrPreviousBlockElement(
+ const EditorDOMPointBase<PT, CT>& aStartPoint,
+ 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::GetPreviousLeafContentOrPreviousBlockElement(
+ *aStartPoint.template ContainerAs<nsIContent>(), aOptions,
+ aBlockInlineCheck, aAncestorLimiter);
+ }
+ if (!HTMLEditUtils::IsContainerNode(
+ *aStartPoint.template ContainerAs<Element>()) ||
+ HTMLEditUtils::IsReplacedElement(
+ *aStartPoint.template ContainerAs<Element>())) {
+ return HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
+ *aStartPoint.template ContainerAs<Element>(), aOptions,
+ aBlockInlineCheck, aAncestorLimiter);
+ }
+
+ if (aStartPoint.IsStartOfContainer()) {
+ if (aStartPoint.GetContainer() == aAncestorLimiter ||
+ HTMLEditUtils::IsBlockElement(
+ *aStartPoint.template ContainerAs<Element>(),
+ UseComputedDisplayStyleIfAuto(aBlockInlineCheck))) {
+ // We are at start of the block.
+ return nullptr;
+ }
+
+ // We are at start of non-block container
+ return HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
+ *aStartPoint.template ContainerAs<Element>(), aOptions,
+ PreferDisplayOutsideIfUsingDisplay(aBlockInlineCheck),
+ aAncestorLimiter);
+ }
+
+ for (nsIContent* previousContent = aStartPoint.GetPreviousSiblingOfChild();
+ previousContent;
+ previousContent = previousContent->GetPreviousSibling()) {
+ // We have a prior node. If it's a block, return it.
+ if (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;
+}
+
template <typename EditorLineBreakType>
Maybe<EditorLineBreakType> HTMLEditUtils::GetUnnecessaryLineBreak(
const Element& aBlockElement, ScanLineBreak aScanLineBreak) {
@@ -1101,8 +1661,7 @@ Maybe<EditorLineBreakType> HTMLEditUtils::GetUnnecessaryLineBreak(
WalkTreeOption::StopAtBlockBoundary};
for (nsIContent* content =
aScanLineBreak == ScanLineBreak::AtEndOfBlock
- ? HTMLEditUtils::GetLastLeafContent(
- aBlockElement, {LeafNodeType::OnlyLeafNode})
+ ? HTMLEditUtils::GetLastLeafContent(aBlockElement, {})
: HTMLEditUtils::GetPreviousContent(
aBlockElement, onlyPrecedingLine,
BlockInlineCheck::UseComputedDisplayStyle,
@@ -1111,8 +1670,7 @@ Maybe<EditorLineBreakType> HTMLEditUtils::GetUnnecessaryLineBreak(
content =
aScanLineBreak == ScanLineBreak::AtEndOfBlock
? HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
- *content, {LeafNodeType::OnlyLeafNode},
- BlockInlineCheck::UseComputedDisplayStyle,
+ *content, {}, BlockInlineCheck::UseComputedDisplayStyle,
&aBlockElement)
: HTMLEditUtils::GetPreviousContent(
*content, onlyPrecedingLine,
@@ -1184,11 +1742,12 @@ Maybe<EditorLineBreakType> HTMLEditUtils::GetUnnecessaryLineBreak(
BlockInlineCheck::UseComputedDisplayStyle);
for (nsIContent* content =
HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
- *lastLineBreakContent, {LeafNodeType::LeafNodeOrChildBlock},
+ *lastLineBreakContent,
+ {LeafNodeOption::TreatChildBlockAsLeafNode},
BlockInlineCheck::UseComputedDisplayStyle, blockElement);
content;
content = HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
- *content, {LeafNodeType::LeafNodeOrChildBlock},
+ *content, {LeafNodeOption::TreatChildBlockAsLeafNode},
BlockInlineCheck::UseComputedDisplayStyle, blockElement)) {
if (HTMLEditUtils::IsBlockElement(
*content, BlockInlineCheck::UseComputedDisplayStyle) ||
@@ -2029,9 +2588,9 @@ nsIContent* HTMLEditUtils::GetPreviousContent(
// and want the deep-right child.
nsIContent* lastLeafContent = HTMLEditUtils::GetLastLeafContent(
*aPoint.GetContainer(),
- {aOptions.contains(WalkTreeOption::StopAtBlockBoundary)
- ? LeafNodeType::LeafNodeOrChildBlock
- : LeafNodeType::OnlyLeafNode},
+ aOptions.contains(WalkTreeOption::StopAtBlockBoundary)
+ ? LeafNodeOptions{LeafNodeOption::TreatChildBlockAsLeafNode}
+ : LeafNodeOptions{},
aBlockInlineCheck);
if (!lastLeafContent) {
return nullptr;
@@ -2078,9 +2637,9 @@ nsIContent* HTMLEditUtils::GetNextContent(
nsIContent* firstLeafContent = HTMLEditUtils::GetFirstLeafContent(
*point.GetChild(),
- {aOptions.contains(WalkTreeOption::StopAtBlockBoundary)
- ? LeafNodeType::LeafNodeOrChildBlock
- : LeafNodeType::OnlyLeafNode},
+ aOptions.contains(WalkTreeOption::StopAtBlockBoundary)
+ ? LeafNodeOptions{LeafNodeOption::TreatChildBlockAsLeafNode}
+ : LeafNodeOptions{},
aBlockInlineCheck);
if (!firstLeafContent) {
return point.GetChild();
@@ -2146,15 +2705,18 @@ nsIContent* HTMLEditUtils::GetAdjacentLeafContent(
// don't look inside previous sibling, since it is a block
return sibling;
}
- const LeafNodeTypes leafNodeTypes = {
- aOptions.contains(WalkTreeOption::StopAtBlockBoundary)
- ? LeafNodeType::LeafNodeOrChildBlock
- : LeafNodeType::OnlyLeafNode};
+ LeafNodeOptions leafNodeOptions;
+ if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary)) {
+ leafNodeOptions += LeafNodeOption::TreatChildBlockAsLeafNode;
+ }
+ if (aOptions.contains(WalkTreeOption::IgnoreNonEditableNode)) {
+ leafNodeOptions += LeafNodeOption::IgnoreNonEditableNode;
+ }
nsIContent* leafContent =
aWalkTreeDirection == WalkTreeDirection::Forward
- ? HTMLEditUtils::GetFirstLeafContent(*sibling, leafNodeTypes,
+ ? HTMLEditUtils::GetFirstLeafContent(*sibling, leafNodeOptions,
aBlockInlineCheck)
- : HTMLEditUtils::GetLastLeafContent(*sibling, leafNodeTypes,
+ : HTMLEditUtils::GetLastLeafContent(*sibling, leafNodeOptions,
aBlockInlineCheck);
return leafContent ? leafContent : sibling;
}
@@ -3341,6 +3903,7 @@ 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)];
}
@@ -3368,6 +3931,7 @@ 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)];
}
@@ -3396,6 +3960,7 @@ 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)];
}
@@ -3414,22 +3979,27 @@ std::ostream& operator<<(std::ostream& aStream,
}
std::ostream& operator<<(std::ostream& aStream,
- const HTMLEditUtils::LeafNodeType& aLeafNodeType) {
+ const HTMLEditUtils::LeafNodeOption& aOption) {
constexpr static const char* names[] = {
- "OnlyLeafNode",
- "LeafNodeOrChildBlock",
- "LeafNodeOrNonEditableNode",
- "OnlyEditableLeafNode",
+ "TreatChildBlockAsLeafNode",
+ "TreatNonEditableNodeAsLeafNode",
+ "IgnoreNonEditableNode",
"TreatCommentAsLeafNode",
+ "IgnoreEmptyText",
+ "IgnoreInvisibleText",
+ "IgnoreInvisibleInlineVoidElements",
+ "IgnoreAnyEmptyInlineContainers",
+ "IgnoreInvisibleEmptyInlineContainers",
};
- return aStream << names[static_cast<uint32_t>(aLeafNodeType)];
+ MOZ_ASSERT(static_cast<uint32_t>(aOption) < std::size(names));
+ return aStream << names[static_cast<uint32_t>(aOption)];
}
std::ostream& operator<<(std::ostream& aStream,
- const HTMLEditUtils::LeafNodeTypes& aLeafNodeTypes) {
+ const HTMLEditUtils::LeafNodeOptions& aOptions) {
aStream << "{";
bool first = true;
- for (const auto t : aLeafNodeTypes) {
+ for (const auto t : aOptions) {
if (!first) {
aStream << ", ";
}
diff --git a/editor/libeditor/HTMLEditUtils.h b/editor/libeditor/HTMLEditUtils.h
@@ -1422,221 +1422,86 @@ class HTMLEditUtils final {
return editableContent;
}
- 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.
+ 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.
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 LeafNodeTypes = EnumSet<LeafNodeType>;
+ using LeafNodeOptions = EnumSet<LeafNodeOption>;
friend std::ostream& operator<<(std::ostream& aStream,
- const LeafNodeType& aLeafNodeType);
+ const LeafNodeOption& aOption);
friend std::ostream& operator<<(std::ostream& aStream,
- const LeafNodeTypes& aLeafNodeTypes);
+ 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);
+ public:
/**
* GetLastLeafContent() returns rightmost leaf content in aNode. It depends
- * on aLeafNodeTypes whether this which types of nodes are treated as leaf
+ * on aOptions whether this which types of nodes are treated as leaf
* nodes.
*
- * @param aBlockInlineCheck Can be Unused if aLeafNodeTypes does not contain
- * LeafNodeOrCHildBlock.
+ * @param aBlockInlineCheck Can be Unused if aOptions does not contain
+ * TreatChildBlockAsLeafNode.
*/
- static nsIContent* GetLastLeafContent(
- const nsINode& aNode, const LeafNodeTypes& aLeafNodeTypes,
- BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused,
- const Element* aAncestorLimiter = nullptr) {
- 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;
- }
+ [[nodiscard]] static nsIContent* GetLastLeafContent(
+ const nsINode& aNode, const LeafNodeOptions& aOptions,
+ BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused);
/**
* GetFirstLeafContent() returns leftmost leaf content in aNode. It depends
- * on aLeafNodeTypes whether this scans into a block child or treat block as a
+ * on aOptions whether this scans into a block child or treat block as a
* leaf.
*
- * @param aBlockInlineCheck Can be Unused if aLeafNodeTypes does not contain
- * LeafNodeOrCHildBlock.
+ * @param aBlockInlineCheck Can be Unused if aOptions does not contain
+ * TreatChildBlockAsLeafNode.
*/
- static nsIContent* GetFirstLeafContent(
- const nsINode& aNode, const LeafNodeTypes& aLeafNodeTypes,
- BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused,
- const Element* aAncestorLimiter = nullptr) {
- 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;
- }
+ [[nodiscard]] static nsIContent* GetFirstLeafContent(
+ const nsINode& aNode, const LeafNodeOptions& aOptions,
+ BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused);
/**
* GetNextLeafContentOrNextBlockElement() returns next leaf content or
* next block element of aStartContent inside aAncestorLimiter.
*
* @param aStartContent The start content to scan next content.
- * @param aLeafNodeTypes See LeafNodeType.
+ * @param aOptions See LeafNodeOption.
* @param aAncestorLimiter Optional, if you set this, it must be an
* inclusive ancestor of aStartContent.
*/
static nsIContent* GetNextLeafContentOrNextBlockElement(
- const nsIContent& aStartContent, const LeafNodeTypes& aLeafNodeTypes,
+ const nsIContent& aStartContent, const LeafNodeOptions& aOptions,
BlockInlineCheck aBlockInlineCheck,
- const Element* aAncestorLimiter = nullptr) {
- 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");
- }
+ const Element* aAncestorLimiter = nullptr);
/**
* Similar to the above method, but take a DOM point to specify scan start
@@ -1645,77 +1510,8 @@ class HTMLEditUtils final {
template <typename PT, typename CT>
static nsIContent* GetNextLeafContentOrNextBlockElement(
const EditorDOMPointBase<PT, CT>& aStartPoint,
- const LeafNodeTypes& aLeafNodeTypes, BlockInlineCheck aBlockInlineCheck,
- const Element* aAncestorLimiter = nullptr) {
- 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");
- }
+ const LeafNodeOptions& aOptions, BlockInlineCheck aBlockInlineCheck,
+ const Element* aAncestorLimiter = nullptr);
/**
* GetPreviousLeafContentOrPreviousBlockElement() returns previous leaf
@@ -1723,88 +1519,14 @@ class HTMLEditUtils final {
* aAncestorLimiter.
*
* @param aStartContent The start content to scan previous content.
- * @param aLeafNodeTypes See LeafNodeType.
+ * @param aOptions See LeafNodeOption.
* @param aAncestorLimiter Optional, if you set this, it must be an
* inclusive ancestor of aStartContent.
*/
static nsIContent* GetPreviousLeafContentOrPreviousBlockElement(
- const nsIContent& aStartContent, const LeafNodeTypes& aLeafNodeTypes,
+ const nsIContent& aStartContent, const LeafNodeOptions& aOptions,
BlockInlineCheck aBlockInlineCheck,
- const Element* aAncestorLimiter = nullptr) {
- 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");
- }
+ const Element* aAncestorLimiter = nullptr);
/**
* Similar to the above method, but take a DOM point to specify scan start
@@ -1813,168 +1535,10 @@ class HTMLEditUtils final {
template <typename PT, typename CT>
static nsIContent* GetPreviousLeafContentOrPreviousBlockElement(
const EditorDOMPointBase<PT, CT>& aStartPoint,
- const LeafNodeTypes& aLeafNodeTypes, BlockInlineCheck aBlockInlineCheck,
- const Element* aAncestorLimiter = nullptr) {
- 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::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);
- }
-
- if (aStartPoint.IsStartOfContainer()) {
- if (aStartPoint.GetContainer() == aAncestorLimiter ||
- HTMLEditUtils::IsBlockElement(
- *aStartPoint.template ContainerAs<nsIContent>(),
- UseComputedDisplayStyleIfAuto(aBlockInlineCheck))) {
- // We are at start of the block.
- return nullptr;
- }
-
- // We are at start of non-block container
- return HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
- *aStartPoint.template ContainerAs<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 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.
- */
- [[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 nullptr;
- }
+ const LeafNodeOptions& aOptions, BlockInlineCheck aBlockInlineCheck,
+ const Element* aAncestorLimiter = nullptr);
/**
- * 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.
- */
- 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 nullptr;
- }
- /**
* Returns a content node whose inline styles should be preserved after
* deleting content in a range. Typically, you should set aPoint to start
* boundary of the range to delete.
@@ -2470,8 +2034,7 @@ class HTMLEditUtils final {
template <typename EditorLineBreakType>
static Maybe<EditorLineBreakType> GetFirstLineBreak(
const dom::Element& aElement) {
- for (nsIContent* content = HTMLEditUtils::GetFirstLeafContent(
- aElement, {LeafNodeType::OnlyLeafNode});
+ for (nsIContent* content = HTMLEditUtils::GetFirstLeafContent(aElement, {});
content; content = HTMLEditUtils::GetNextContent(
*content,
{WalkTreeOption::IgnoreDataNodeExceptText,
diff --git a/editor/libeditor/HTMLEditor.cpp b/editor/libeditor/HTMLEditor.cpp
@@ -100,8 +100,8 @@ using namespace widget;
LazyLogModule gHTMLEditorFocusLog("HTMLEditorFocus");
using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
-using LeafNodeType = HTMLEditUtils::LeafNodeType;
-using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes;
+using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
+using LeafNodeOptions = HTMLEditUtils::LeafNodeOptions;
using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
// Some utilities to handle overloading of "A" tag for link and named anchor.
@@ -1041,8 +1041,8 @@ nsresult HTMLEditor::CollapseSelectionToEndOfLastLeafNodeOfDocument() const {
}
auto pointToPutCaret = [&]() -> EditorRawDOMPoint {
- nsCOMPtr<nsIContent> lastLeafContent = HTMLEditUtils::GetLastLeafContent(
- *bodyOrDocumentElement, {LeafNodeType::OnlyLeafNode});
+ nsCOMPtr<nsIContent> lastLeafContent =
+ HTMLEditUtils::GetLastLeafContent(*bodyOrDocumentElement, {});
if (!lastLeafContent) {
return EditorRawDOMPoint::AtEndOf(*bodyOrDocumentElement);
}
@@ -1136,11 +1136,15 @@ 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,
- {LeafNodeType::LeafNodeOrNonEditableNode,
- LeafNodeType::LeafNodeOrChildBlock},
- BlockInlineCheck::UseComputedDisplayStyle, editingHost);
+ *editingHost, leafNodeOptions,
+ BlockInlineCheck::UseComputedDisplayStyle);
leafContent;) {
// If we meet a non-editable node first, we should move caret to start
// of the container block or editing host.
@@ -1177,9 +1181,7 @@ 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,
- {LeafNodeType::LeafNodeOrNonEditableNode,
- LeafNodeType::LeafNodeOrChildBlock},
+ *leafElement, leafNodeOptions,
BlockInlineCheck::UseComputedDisplayStyle, editingHost);
continue;
}
@@ -1203,9 +1205,7 @@ nsresult HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode(
}
// If it's an invisible text node, keep scanning next leaf.
leafContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
- *leafContent,
- {LeafNodeType::LeafNodeOrNonEditableNode,
- LeafNodeType::LeafNodeOrChildBlock},
+ *leafContent, leafNodeOptions,
BlockInlineCheck::UseComputedDisplayStyle, editingHost);
continue;
}
@@ -1240,19 +1240,15 @@ nsresult HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode(
EmptyCheckOption::TreatNonEditableContentAsInvisible}) &&
!HTMLEditUtils::IsNeverElementContentsEditableByUser(*leafContent)) {
leafContent = HTMLEditUtils::GetFirstLeafContent(
- *leafContent,
- {LeafNodeType::LeafNodeOrNonEditableNode,
- LeafNodeType::LeafNodeOrChildBlock},
- BlockInlineCheck::UseComputedDisplayStyle, editingHost);
+ *leafContent, leafNodeOptions,
+ BlockInlineCheck::UseComputedDisplayStyle);
continue;
}
// Otherwise, we must meet an empty block element or a data node like
// comment node. Let's ignore it.
leafContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
- *leafContent,
- {LeafNodeType::LeafNodeOrNonEditableNode,
- LeafNodeType::LeafNodeOrChildBlock},
+ *leafContent, leafNodeOptions,
BlockInlineCheck::UseComputedDisplayStyle, editingHost);
}
@@ -3306,7 +3302,9 @@ already_AddRefed<Element> HTMLEditor::GetSelectedElement(const nsAtom* aTagName,
return nullptr;
}
nsIContent* firstEditableLeaf = HTMLEditUtils::GetFirstLeafContent(
- *nextSibling, {LeafNodeType::OnlyLeafNode});
+ *nextSibling,
+ // XXX Should we ignore invisible inline elements and text nodes?
+ {});
if (firstEditableLeaf &&
firstEditableLeaf->IsHTMLElement(nsGkAtoms::br)) {
return nullptr;
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 LeafNodeType = HTMLEditUtils::LeafNodeType;
+using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
#define kInsertCookie "_moz_Insert Here_moz_"
@@ -506,9 +506,13 @@ HTMLEditor::HTMLWithContextInserter::GetNewCaretPointAfterInsertingHTML(
if (!aLastInsertedPoint.GetChild() ||
!aLastInsertedPoint.GetChild()->IsHTMLElement(nsGkAtoms::table)) {
containerContent = HTMLEditUtils::GetLastLeafContent(
- *aLastInsertedPoint.GetChild(), {LeafNodeType::OnlyEditableLeafNode},
- BlockInlineCheck::Unused,
- aLastInsertedPoint.GetChild()->GetAsElementOrParentElement());
+ *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.
+ });
if (containerContent) {
Element* mostDistantInclusiveAncestorTableElement = nullptr;
for (Element* maybeTableElement =
diff --git a/editor/libeditor/HTMLEditorDeleteHandler.cpp b/editor/libeditor/HTMLEditorDeleteHandler.cpp
@@ -60,7 +60,7 @@ using EditablePointOption = HTMLEditUtils::EditablePointOption;
using EditablePointOptions = HTMLEditUtils::EditablePointOptions;
using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
using InvisibleWhiteSpaces = HTMLEditUtils::InvisibleWhiteSpaces;
-using LeafNodeType = HTMLEditUtils::LeafNodeType;
+using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
using ScanLineBreak = HTMLEditUtils::ScanLineBreak;
using TableBoundary = HTMLEditUtils::TableBoundary;
using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
@@ -1958,11 +1958,10 @@ nsIContent* HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
MOZ_ASSERT(mOtherBlockElement);
return aDirectionAndAmount == nsIEditor::ePrevious
? HTMLEditUtils::GetLastLeafContent(
- *mOtherBlockElement, {LeafNodeType::OnlyEditableLeafNode},
- BlockInlineCheck::Unused, mOtherBlockElement)
+ *mOtherBlockElement, {LeafNodeOption::IgnoreNonEditableNode})
: HTMLEditUtils::GetFirstLeafContent(
- *mOtherBlockElement, {LeafNodeType::OnlyEditableLeafNode},
- BlockInlineCheck::Unused, mOtherBlockElement);
+ *mOtherBlockElement,
+ {LeafNodeOption::IgnoreNonEditableNode});
}
nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
@@ -2552,15 +2551,15 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
return false;
}
- auto ScanJoinTarget = [&]() -> nsIContent* {
+ auto ScanJoinTarget = [&]() MOZ_NEVER_INLINE_DEBUG -> nsIContent* {
nsIContent* targetContent =
aDirectionAndAmount == nsIEditor::ePrevious
? HTMLEditUtils::GetPreviousContent(
aCurrentBlockElement, {WalkTreeOption::IgnoreNonEditableNode},
- BlockInlineCheck::Unused, &aEditingHost)
+ BlockInlineCheck::Auto, &aEditingHost)
: HTMLEditUtils::GetNextContent(
aCurrentBlockElement, {WalkTreeOption::IgnoreNonEditableNode},
- BlockInlineCheck::Unused, &aEditingHost);
+ BlockInlineCheck::Auto, &aEditingHost);
// If found content is an invisible text node, let's scan visible things.
auto IsIgnorableDataNode = [](nsIContent* aContent) {
return aContent && HTMLEditUtils::IsRemovableNode(*aContent) &&
@@ -2606,9 +2605,10 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
nsIContent* leafContent =
aDirectionAndAmount == nsIEditor::ePrevious
? HTMLEditUtils::GetLastLeafContent(
- *adjacentContent, {LeafNodeType::OnlyEditableLeafNode})
+ *adjacentContent, {LeafNodeOption::IgnoreNonEditableNode})
: HTMLEditUtils::GetFirstLeafContent(
- *adjacentContent, {LeafNodeType::OnlyEditableLeafNode});
+ *adjacentContent,
+ {LeafNodeOption::IgnoreNonEditableNode});
mSkippedInvisibleContents.AppendElement(*targetContent);
return leafContent ? leafContent : adjacentContent;
}
@@ -4619,9 +4619,11 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
Element::FromNodeOrNull(moveFirstLineResult.DeleteRangeRef()
.GetClosestCommonInclusiveAncestor());
nsIContent* const previousVisibleLeafOrChildBlock =
- HTMLEditUtils::GetPreviousNonEmptyLeafContentOrPreviousBlockElement(
+ HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
moveFirstLineResult.DeleteRangeRef().EndRef(),
- {LeafNodeType::LeafNodeOrChildBlock},
+ {LeafNodeOption::TreatChildBlockAsLeafNode,
+ LeafNodeOption::IgnoreInvisibleEmptyInlineContainers,
+ LeafNodeOption::IgnoreInvisibleText},
BlockInlineCheck::UseComputedDisplayOutsideStyle, commonAncestor);
if (!previousVisibleLeafOrChildBlock) {
return false;
@@ -6371,7 +6373,7 @@ nsresult HTMLEditor::AutoMoveOneLineHandler::
mDestInclusiveAncestorBlock)
: HTMLEditUtils::GetLastLeafContent(
*mDestInclusiveAncestorBlock,
- {LeafNodeType::LeafNodeOrNonEditableNode}));
+ {LeafNodeOption::TreatNonEditableNodeAsLeafNode}));
if (!lastTextNode ||
!HTMLEditUtils::IsSimplyEditableNode(*lastTextNode)) {
return nullptr;
@@ -7211,7 +7213,7 @@ Result<CaretPoint, nsresult> HTMLEditor::AutoDeleteRangesHandler::
EditorRawDOMPoint::After(mEmptyInclusiveAncestorBlockElement);
scanStartPoint.IsInContentNode();) {
nsIContent* const nextContent = HTMLEditUtils::GetNextContent(
- scanStartPoint, {}, BlockInlineCheck::Unused, &aEditingHost);
+ scanStartPoint, {}, BlockInlineCheck::Auto, &aEditingHost);
// Let's ignore invisible `Text`.
if (nextContent && nextContent->IsText() &&
!HTMLEditUtils::IsVisibleTextNode(*nextContent->AsText())) {
@@ -7249,7 +7251,7 @@ Result<CaretPoint, nsresult> HTMLEditor::AutoDeleteRangesHandler::
scanStartPoint.IsInContentNode();) {
nsIContent* const previousContent = HTMLEditUtils::GetPreviousContent(
scanStartPoint, {WalkTreeOption::IgnoreNonEditableNode},
- BlockInlineCheck::Unused, &aEditingHost);
+ BlockInlineCheck::Auto, &aEditingHost);
// Let's ignore invisible `Text`.
if (previousContent && previousContent->IsText() &&
!HTMLEditUtils::IsVisibleTextNode(*previousContent->AsText())) {
diff --git a/editor/libeditor/HTMLEditorInsertParagraphHandler.cpp b/editor/libeditor/HTMLEditorInsertParagraphHandler.cpp
@@ -49,8 +49,8 @@ namespace mozilla {
using namespace dom;
using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
using EmptyCheckOptions = HTMLEditUtils::EmptyCheckOptions;
-using LeafNodeType = HTMLEditUtils::LeafNodeType;
-using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes;
+using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
+using LeafNodeOptions = HTMLEditUtils::LeafNodeOptions;
using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
Result<EditActionResult, nsresult>
@@ -1753,7 +1753,7 @@ HTMLEditor::AutoInsertParagraphHandler::SplitParagraphWithTransaction(
// Let's put caret at start of the first leaf container.
nsIContent* child = HTMLEditUtils::GetFirstLeafContent(
- *rightDivOrParagraphElement, {LeafNodeType::LeafNodeOrChildBlock},
+ *rightDivOrParagraphElement, {LeafNodeOption::TreatChildBlockAsLeafNode},
BlockInlineCheck::UseComputedDisplayStyle);
if (MOZ_UNLIKELY(!child)) {
return SplitNodeResult(std::move(splitDivOrPResult),
diff --git a/editor/libeditor/HTMLStyleEditor.cpp b/editor/libeditor/HTMLStyleEditor.cpp
@@ -64,8 +64,6 @@ using namespace dom;
using EditablePointOption = HTMLEditUtils::EditablePointOption;
using EditablePointOptions = HTMLEditUtils::EditablePointOptions;
using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
-using LeafNodeType = HTMLEditUtils::LeafNodeType;
-using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes;
using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
template nsresult HTMLEditor::SetInlinePropertiesAsSubAction(
@@ -2394,7 +2392,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(), {LeafNodeType::OnlyLeafNode});
+ *unwrappedSplitNodeResult.GetNextContent(), {});
EditorDOMPoint atStartOfNextNode(
firstLeafChildOfNextNode ? firstLeafChildOfNextNode
: unwrappedSplitNodeResult.GetNextContent(),
@@ -2484,8 +2482,7 @@ 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(),
- {LeafNodeType::OnlyLeafNode});
+ *unwrappedSplitResultAtStartOfNextNode.GetPreviousContent(), {});
pointToPutCaret.Set(
firstLeafChildOfPreviousNode
? firstLeafChildOfPreviousNode
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,9 +89,6 @@ 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
@@ -73,15 +73,17 @@ void WSScanResult::AssertIfInvalidData(const WSRunScanner& aScanner) const {
mContent->IsHTMLElement(nsGkAtoms::br));
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));
+ 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()) ||
@@ -90,14 +92,9 @@ 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)));
+ : BlockInlineCheck::UseComputedDisplayOutsideStyle)) &&
+ (!mContent->IsEditable() ||
+ !MaybeNonVoidEmptyInlineContainerElement()));
MOZ_ASSERT_IF(
mReason == WSType::OtherBlockBoundary,
HTMLEditUtils::IsBlockElement(
diff --git a/editor/libeditor/WSRunScanner.h b/editor/libeditor/WSRunScanner.h
@@ -526,6 +526,14 @@ 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>;
@@ -552,6 +560,32 @@ 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,
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 LeafNodeType = HTMLEditUtils::LeafNodeType;
-using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes;
+using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
+using LeafNodeOptions = HTMLEditUtils::LeafNodeOptions;
template WSRunScanner::TextFragmentData::TextFragmentData(Options,
const EditorDOMPoint&,
@@ -245,18 +245,10 @@ WSRunScanner::TextFragmentData::BoundaryData WSRunScanner::TextFragmentData::
? BlockInlineCheck::UseHTMLDefaultStyle
: BlockInlineCheck::Auto;
// Then, we need to check previous leaf node.
- 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;
- }();
+ const LeafNodeOptions leafNodeOptions = ToLeafNodeOptions(aOptions);
nsIContent* previousLeafContentOrBlock =
HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
- aPoint, leafNodeTypes, blockInlineCheck, &aAncestorLimiter);
+ aPoint, leafNodeOptions, blockInlineCheck, &aAncestorLimiter);
if (!previousLeafContentOrBlock) {
// No previous content means that we reached the aAncestorLimiter boundary.
return BoundaryData(
@@ -274,29 +266,35 @@ 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::IsEmptyInlineContainer(
+ HTMLEditUtils::IsInlineContent(
*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.
+ 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() ||
- (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.
+ if (!previousLeafContentOrBlock->IsText()) {
+ // Other elements like <img> or #comment.
return BoundaryData(aPoint, *previousLeafContentOrBlock,
- previousLeafContentOrBlock->IsHTMLElement(nsGkAtoms::br)
- ? WSType::BRElement
- : WSType::SpecialContent);
+ WSType::SpecialContent);
}
if (!previousLeafContentOrBlock->AsText()->TextLength()) {
@@ -420,18 +418,10 @@ WSRunScanner::TextFragmentData::BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
: BlockInlineCheck::Auto;
// Then, we need to check next leaf node.
- 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;
- }();
+ const LeafNodeOptions leafNodeOptions = ToLeafNodeOptions(aOptions);
nsIContent* nextLeafContentOrBlock =
HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
- aPoint, leafNodeTypes, blockInlineCheck, &aAncestorLimiter);
+ aPoint, leafNodeOptions, blockInlineCheck, &aAncestorLimiter);
if (!nextLeafContentOrBlock) {
// No next content means that we reached aAncestorLimiter boundary.
return BoundaryData(
@@ -451,30 +441,34 @@ 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::IsEmptyInlineContainer(
+ HTMLEditUtils::IsInlineContent(
*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.
+ 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() ||
- (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.
+ if (!nextLeafContentOrBlock->IsText()) {
+ // Other elements like <img>, etc.
return BoundaryData(aPoint, *nextLeafContentOrBlock,
- nextLeafContentOrBlock->IsHTMLElement(nsGkAtoms::br)
- ? WSType::BRElement
- : WSType::SpecialContent);
+ WSType::SpecialContent);
}
if (!nextLeafContentOrBlock->AsText()->DataBuffer().GetLength()) {
@@ -907,7 +901,10 @@ 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 {
+ // TODO: Use HTMLEditUtils::GetNextSibling().
nsIContent* const child = [&]() -> nsIContent* {
nsIContent* child =
aPoint.CanContainerHaveChildren() ? aPoint.GetChild() : nullptr;
@@ -921,7 +918,9 @@ EditorDOMPointType WSRunScanner::TextFragmentData::GetInclusiveNextCharPoint(
if (!child ||
HTMLEditUtils::IsBlockElement(
*child, UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)) ||
- HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*child)) {
+ ((!aOptions.contains(Option::IgnoreEmptyInlineContainers) ||
+ !HTMLEditUtils::IsContainerNode(*child)) &&
+ HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*child))) {
return aPoint.template To<EditorRawDOMPoint>();
}
if (!child->HasChildNodes()) {
@@ -935,14 +934,16 @@ 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, {LeafNodeType::LeafNodeOrChildBlock}, blockInlineCheck);
+ *child, leafNodeOptions, blockInlineCheck);
if (!leafContent) {
return EditorRawDOMPoint(child, 0);
}
if (HTMLEditUtils::IsBlockElement(
*leafContent,
UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)) ||
- HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*leafContent)) {
+ ((!aOptions.contains(Option::IgnoreEmptyInlineContainers) ||
+ !HTMLEditUtils::IsContainerNode(*leafContent)) &&
+ HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*leafContent))) {
return EditorRawDOMPoint();
}
return EditorRawDOMPoint(leafContent, 0);
@@ -979,24 +980,14 @@ 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>(), leafNodeTypes,
+ *point.ContainerAs<nsIContent>(), leafNodeOptions,
blockInlineCheck,
editableBlockElementOrInlineEditingHostOrNonEditableRootElement);
nextContent;
nextContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
- *nextContent, leafNodeTypes, blockInlineCheck,
+ *nextContent, leafNodeOptions, blockInlineCheck,
editableBlockElementOrInlineEditingHostOrNonEditableRootElement)) {
if (!nextContent->IsText() ||
(aIgnoreNonEditableNodes == IgnoreNonEditableNodes::Yes &&
@@ -1005,7 +996,9 @@ EditorDOMPointType WSRunScanner::TextFragmentData::GetInclusiveNextCharPoint(
HTMLEditUtils::IsBlockElement(
*nextContent,
UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)) ||
- HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*nextContent)) {
+ ((!aOptions.contains(Option::IgnoreEmptyInlineContainers) ||
+ !HTMLEditUtils::IsContainerNode(*nextContent)) &&
+ HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*nextContent))) {
break; // Reached end of current runs.
}
continue;
@@ -1032,7 +1025,10 @@ 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 {
+ // TODO: Use HTMLEditUtils::GetPreviousSibling().
nsIContent* const previousChild = [&]() -> nsIContent* {
nsIContent* previousChild = aPoint.CanContainerHaveChildren()
? aPoint.GetPreviousSiblingOfChild()
@@ -1048,7 +1044,9 @@ EditorDOMPointType WSRunScanner::TextFragmentData::GetPreviousCharPoint(
HTMLEditUtils::IsBlockElement(
*previousChild,
UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)) ||
- HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*previousChild)) {
+ ((!aOptions.contains(Option::IgnoreEmptyInlineContainers) ||
+ !HTMLEditUtils::IsContainerNode(*previousChild)) &&
+ HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*previousChild))) {
return aPoint.template To<EditorRawDOMPoint>();
}
if (!previousChild->HasChildren()) {
@@ -1063,14 +1061,16 @@ 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, {LeafNodeType::LeafNodeOrChildBlock}, blockInlineCheck);
+ *previousChild, leafNodeOptions, blockInlineCheck);
if (!leafContent) {
return EditorRawDOMPoint::AtEndOf(*previousChild);
}
if (HTMLEditUtils::IsBlockElement(
*leafContent,
UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)) ||
- HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*leafContent)) {
+ ((!aOptions.contains(Option::IgnoreEmptyInlineContainers) ||
+ !HTMLEditUtils::IsContainerNode(*leafContent)) &&
+ HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*leafContent))) {
return EditorRawDOMPoint();
}
return EditorRawDOMPoint::AtEndOf(*leafContent);
@@ -1108,25 +1108,16 @@ 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>(), leafNodeTypes, blockInlineCheck,
+ *point.ContainerAs<nsIContent>(), leafNodeOptions,
+ blockInlineCheck,
editableBlockElementOrInlineEditingHostOrNonEditableRootElement);
previousContent;
previousContent =
HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
- *previousContent, leafNodeTypes, blockInlineCheck,
+ *previousContent, leafNodeOptions, blockInlineCheck,
editableBlockElementOrInlineEditingHostOrNonEditableRootElement)) {
if (!previousContent->IsText() ||
(aIgnoreNonEditableNodes == IgnoreNonEditableNodes::Yes &&
@@ -1135,7 +1126,9 @@ EditorDOMPointType WSRunScanner::TextFragmentData::GetPreviousCharPoint(
HTMLEditUtils::IsBlockElement(
*previousContent,
UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)) ||
- HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*previousContent)) {
+ ((!aOptions.contains(Option::IgnoreEmptyInlineContainers) ||
+ !HTMLEditUtils::IsContainerNode(*previousContent)) &&
+ HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*previousContent))) {
break; // Reached start of current runs.
}
continue;
diff --git a/editor/libeditor/WhiteSpaceVisibilityKeeper.cpp b/editor/libeditor/WhiteSpaceVisibilityKeeper.cpp
@@ -32,7 +32,7 @@ namespace mozilla {
using namespace dom;
-using LeafNodeType = HTMLEditUtils::LeafNodeType;
+using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
Result<EditorDOMPoint, nsresult>
@@ -923,15 +923,14 @@ WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore(
aPoint.IsInTextNode() && aPoint.IsEndOfContainer()
? aPoint.ContainerAs<Text>()
: HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
- aPoint,
- {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock},
+ aPoint, {LeafNodeOption::TreatChildBlockAsLeafNode},
BlockInlineCheck::UseComputedDisplayStyle,
colsetBlockElement);
previousContent;
previousContent =
HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
EditorRawDOMPoint(previousContent),
- {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock},
+ {LeafNodeOption::TreatChildBlockAsLeafNode},
BlockInlineCheck::UseComputedDisplayStyle, colsetBlockElement)) {
if (!HTMLEditUtils::IsSimplyEditableNode(*previousContent)) {
// XXX Assume non-editable nodes are visible.
@@ -1042,14 +1041,13 @@ WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter(
aPoint.IsInTextNode() && aPoint.IsStartOfContainer()
? aPoint.ContainerAs<Text>()
: HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
- aPoint,
- {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock},
+ aPoint, {LeafNodeOption::TreatChildBlockAsLeafNode},
BlockInlineCheck::UseComputedDisplayStyle,
colsetBlockElement);
nextContent;
nextContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
EditorRawDOMPoint::After(*nextContent),
- {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock},
+ {LeafNodeOption::TreatChildBlockAsLeafNode},
BlockInlineCheck::UseComputedDisplayStyle, colsetBlockElement)) {
if (!HTMLEditUtils::IsSimplyEditableNode(*nextContent)) {
// XXX Assume non-editable nodes are visible.
@@ -1296,13 +1294,13 @@ WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt(
if (!pointToSplit.IsInTextNode() || pointToSplit.IsStartOfContainer()) {
for (nsCOMPtr<nsIContent> previousContent =
HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
- pointToSplit, {LeafNodeType::LeafNodeOrChildBlock},
+ pointToSplit, {LeafNodeOption::TreatChildBlockAsLeafNode},
BlockInlineCheck::UseComputedDisplayStyle,
closestBlockElement);
previousContent;
previousContent =
HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
- *previousContent, {LeafNodeType::LeafNodeOrChildBlock},
+ *previousContent, {LeafNodeOption::TreatChildBlockAsLeafNode},
BlockInlineCheck::UseComputedDisplayStyle,
closestBlockElement)) {
if (auto* const textNode = Text::FromNode(previousContent)) {
@@ -1337,12 +1335,12 @@ WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt(
if (!pointToSplit.IsInTextNode() || pointToSplit.IsEndOfContainer()) {
for (nsCOMPtr<nsIContent> nextContent =
HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
- pointToSplit, {LeafNodeType::LeafNodeOrChildBlock},
+ pointToSplit, {LeafNodeOption::TreatChildBlockAsLeafNode},
BlockInlineCheck::UseComputedDisplayStyle,
closestBlockElement);
nextContent;
nextContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
- *nextContent, {LeafNodeType::LeafNodeOrChildBlock},
+ *nextContent, {LeafNodeOption::TreatChildBlockAsLeafNode},
BlockInlineCheck::UseComputedDisplayStyle, closestBlockElement)) {
if (auto* const textNode = Text::FromNode(nextContent)) {
if (!HTMLEditUtils::IsSimplyEditableNode(*textNode) &&
@@ -1697,14 +1695,14 @@ nsresult WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesAfter(
for (nsIContent* nextContent =
HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
aPoint,
- {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock,
- HTMLEditUtils::LeafNodeType::TreatCommentAsLeafNode},
+ {LeafNodeOption::TreatChildBlockAsLeafNode,
+ LeafNodeOption::TreatCommentAsLeafNode},
BlockInlineCheck::UseComputedDisplayStyle, colsetBlockElement);
nextContent;
nextContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
EditorRawDOMPoint::After(*nextContent),
- {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock,
- HTMLEditUtils::LeafNodeType::TreatCommentAsLeafNode},
+ {LeafNodeOption::TreatChildBlockAsLeafNode,
+ LeafNodeOption::TreatCommentAsLeafNode},
BlockInlineCheck::UseComputedDisplayStyle, colsetBlockElement)) {
if (!HTMLEditUtils::IsSimplyEditableNode(*nextContent)) {
// XXX Assume non-editable nodes are visible.
@@ -1781,15 +1779,15 @@ nsresult WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesBefore(
for (nsIContent* previousContent =
HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
aPoint,
- {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock,
- HTMLEditUtils::LeafNodeType::TreatCommentAsLeafNode},
+ {LeafNodeOption::TreatChildBlockAsLeafNode,
+ LeafNodeOption::TreatCommentAsLeafNode},
BlockInlineCheck::UseComputedDisplayStyle, colsetBlockElement);
previousContent;
previousContent =
HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
EditorRawDOMPoint(previousContent),
- {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock,
- HTMLEditUtils::LeafNodeType::TreatCommentAsLeafNode},
+ {LeafNodeOption::TreatChildBlockAsLeafNode,
+ LeafNodeOption::TreatCommentAsLeafNode},
BlockInlineCheck::UseComputedDisplayStyle, colsetBlockElement)) {
if (!HTMLEditUtils::IsSimplyEditableNode(*previousContent)) {
// XXX Assume non-editable nodes are visible.
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::LeafNodeTypes mTypes;
+ const HTMLEditUtils::LeafNodeOptions mOptions;
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.mTypes).c_str()
+ << "\" with options=" << ToString(aTest.mOptions).c_str()
<< " in \"" << NS_ConvertUTF16toUTF8(aTest.mInnerHTML).get()
<< "\"";
}
@@ -1477,7 +1477,7 @@ struct MOZ_STACK_CLASS GetLeafNodeTest final {
TEST(HTMLEditUtilsTest, GetLastLeafContent)
{
- using LeafNodeType = HTMLEditUtils::LeafNodeType;
+ using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
const RefPtr<Document> doc = CreateHTMLDoc();
const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
MOZ_RELEASE_ASSERT(body);
@@ -1486,11 +1486,12 @@ 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",
- {LeafNodeType::LeafNodeOrChildBlock},
+ {LeafNodeOption::TreatChildBlockAsLeafNode},
"div > div"},
GetLeafNodeTest{u"<div><div><br></div><div><br></div></div>",
"div",
@@ -1498,19 +1499,20 @@ TEST(HTMLEditUtilsTest, GetLastLeafContent)
"div > div + div > br"},
GetLeafNodeTest{u"<div><div><br></div><div><br></div></div>",
"div",
- {LeafNodeType::LeafNodeOrChildBlock},
+ {LeafNodeOption::TreatChildBlockAsLeafNode},
"div > div + div"},
+
GetLeafNodeTest{u"<div><!--abc--></div>", "div", {}, nullptr},
GetLeafNodeTest{u"<div><!--abc--></div>",
"div",
- {LeafNodeType::TreatCommentAsLeafNode},
+ {LeafNodeOption::TreatCommentAsLeafNode},
nullptr,
"div",
0u},
GetLeafNodeTest{u"<div><br><!--abc--></div>", "div", {}, "div > br"},
GetLeafNodeTest{u"<div><br><!--abc--></div>",
"div",
- {LeafNodeType::TreatCommentAsLeafNode},
+ {LeafNodeOption::TreatCommentAsLeafNode},
nullptr,
"div",
1u},
@@ -1522,10 +1524,74 @@ TEST(HTMLEditUtilsTest, GetLastLeafContent)
GetLeafNodeTest{
u"<div><div><br></div><div><br></div><!--abc--></div>",
"div",
- {LeafNodeType::TreatCommentAsLeafNode},
+ {LeafNodeOption::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());
@@ -1533,7 +1599,7 @@ TEST(HTMLEditUtilsTest, GetLastLeafContent)
nsDependentCString(testData.mContentSelector), IgnoreErrors());
MOZ_RELEASE_ASSERT(target);
const nsIContent* result = HTMLEditUtils::GetLastLeafContent(
- *target, testData.mTypes,
+ *target, testData.mOptions,
BlockInlineCheck::UseComputedDisplayOutsideStyle);
EXPECT_EQ(result, testData.GetExpectedTarget(*body))
<< "GetLastLeafContent: " << testData
@@ -1543,7 +1609,7 @@ TEST(HTMLEditUtilsTest, GetLastLeafContent)
TEST(HTMLEditUtilsTest, GetFirstLeafContent)
{
- using LeafNodeType = HTMLEditUtils::LeafNodeType;
+ using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
const RefPtr<Document> doc = CreateHTMLDoc();
const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
MOZ_RELEASE_ASSERT(body);
@@ -1555,9 +1621,10 @@ 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",
- {LeafNodeType::LeafNodeOrChildBlock},
+ {LeafNodeOption::TreatChildBlockAsLeafNode},
"div > div"},
GetLeafNodeTest{u"<div><div><br></div><div><br></div></div>",
"div",
@@ -1565,19 +1632,20 @@ TEST(HTMLEditUtilsTest, GetFirstLeafContent)
"div > div > br"},
GetLeafNodeTest{u"<div><div><br></div><div><br></div></div>",
"div",
- {LeafNodeType::LeafNodeOrChildBlock},
+ {LeafNodeOption::TreatChildBlockAsLeafNode},
"div > div"},
+
GetLeafNodeTest{u"<div><!--abc--></div>", "div", {}, nullptr},
GetLeafNodeTest{u"<div><!--abc--></div>",
"div",
- {LeafNodeType::TreatCommentAsLeafNode},
+ {LeafNodeOption::TreatCommentAsLeafNode},
nullptr,
"div",
0u},
GetLeafNodeTest{u"<div><!--abc--><br></div>", "div", {}, "div > br"},
GetLeafNodeTest{u"<div><!--abc--><br></div>",
"div",
- {LeafNodeType::TreatCommentAsLeafNode},
+ {LeafNodeOption::TreatCommentAsLeafNode},
nullptr,
"div",
0u},
@@ -1589,10 +1657,72 @@ TEST(HTMLEditUtilsTest, GetFirstLeafContent)
GetLeafNodeTest{
u"<div><!--abc--><div><br></div><div><br></div></div>",
"div",
- {LeafNodeType::TreatCommentAsLeafNode},
+ {LeafNodeOption::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());
@@ -1600,7 +1730,7 @@ TEST(HTMLEditUtilsTest, GetFirstLeafContent)
nsDependentCString(testData.mContentSelector), IgnoreErrors());
MOZ_RELEASE_ASSERT(target);
const nsIContent* result = HTMLEditUtils::GetFirstLeafContent(
- *target, testData.mTypes,
+ *target, testData.mOptions,
BlockInlineCheck::UseComputedDisplayOutsideStyle);
EXPECT_EQ(result, testData.GetExpectedTarget(*body))
<< "GetFirstLeafContent: " << testData
@@ -1610,7 +1740,7 @@ TEST(HTMLEditUtilsTest, GetFirstLeafContent)
TEST(HTMLEditUtilsTest, GetNextLeafContentOrNextBlockElement_Content)
{
- using LeafNodeType = HTMLEditUtils::LeafNodeType;
+ using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
const RefPtr<Document> doc = CreateHTMLDoc();
const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
MOZ_RELEASE_ASSERT(body);
@@ -1620,7 +1750,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",
- {LeafNodeType::TreatCommentAsLeafNode},
+ {LeafNodeOption::TreatCommentAsLeafNode},
nullptr,
"body",
1u},
@@ -1632,7 +1762,7 @@ TEST(HTMLEditUtilsTest, GetNextLeafContentOrNextBlockElement_Content)
"span > br"},
GetLeafNodeTest{u"<div><br></div><span><!--abc--><br></span>",
"div",
- {LeafNodeType::TreatCommentAsLeafNode},
+ {LeafNodeOption::TreatCommentAsLeafNode},
nullptr,
"span",
0u},
@@ -1644,7 +1774,7 @@ TEST(HTMLEditUtilsTest, GetNextLeafContentOrNextBlockElement_Content)
MOZ_RELEASE_ASSERT(target);
const nsIContent* result =
HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
- *target, testData.mTypes,
+ *target, testData.mOptions,
BlockInlineCheck::UseComputedDisplayOutsideStyle);
EXPECT_EQ(result, testData.GetExpectedTarget(*body->GetParentNode()))
<< "GetNextLeafContentOrNextBlockElement: " << testData
@@ -1656,7 +1786,7 @@ TEST(HTMLEditUtilsTest, GetNextLeafContentOrNextBlockElement_Content)
TEST(HTMLEditUtilsTest, GetPreviousLeafContentOrPreviousBlockElement_Content)
{
- using LeafNodeType = HTMLEditUtils::LeafNodeType;
+ using LeafNodeOption = HTMLEditUtils::LeafNodeOption;
const RefPtr<Document> doc = CreateHTMLDoc();
const RefPtr<nsGenericHTMLElement> body = doc->GetBody();
MOZ_RELEASE_ASSERT(body);
@@ -1666,7 +1796,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",
- {LeafNodeType::TreatCommentAsLeafNode},
+ {LeafNodeOption::TreatCommentAsLeafNode},
nullptr,
"body",
1u},
@@ -1678,7 +1808,7 @@ TEST(HTMLEditUtilsTest, GetPreviousLeafContentOrPreviousBlockElement_Content)
"span > br"},
GetLeafNodeTest{u"<span><br><!--abc--></span><div><br></div>",
"div",
- {LeafNodeType::TreatCommentAsLeafNode},
+ {LeafNodeOption::TreatCommentAsLeafNode},
nullptr,
"span",
1u},
@@ -1690,7 +1820,7 @@ TEST(HTMLEditUtilsTest, GetPreviousLeafContentOrPreviousBlockElement_Content)
MOZ_RELEASE_ASSERT(target);
const nsIContent* result =
HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
- *target, testData.mTypes,
+ *target, testData.mOptions,
BlockInlineCheck::UseComputedDisplayOutsideStyle);
EXPECT_EQ(result, testData.GetExpectedTarget(*body->GetParentNode()))
<< "GetPreviousLeafContentOrPreviousBlockElement: " << testData
@@ -1738,11 +1868,7 @@ TEST(HTMLEditUtilsTest, GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem)
u"<div contenteditable><br> </div>", "div", Some(1), 2, true},
LineBreakBeforeBlockBoundaryTest{
u"<div contenteditable><br><!-- X --></div>", "div", Nothing{},
- 2,
- // FIXME: Currently, this fails with a bug of WSRunScanner
- // (actually, a bug of
- // HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement)
- false},
+ 2, true},
LineBreakBeforeBlockBoundaryTest{
u"<div contenteditable><br><br></div>", "div", Nothing{}, 1,
false},