summaryrefslogtreecommitdiffstats
path: root/editor
diff options
context:
space:
mode:
Diffstat (limited to 'editor')
-rw-r--r--editor/libeditor/EditAction.h1
-rw-r--r--editor/libeditor/EditorBase.cpp73
-rw-r--r--editor/libeditor/HTMLEditSubActionHandler.cpp119
-rw-r--r--editor/libeditor/HTMLEditUtils.cpp24
-rw-r--r--editor/libeditor/HTMLEditUtils.h7
-rw-r--r--editor/libeditor/HTMLEditor.cpp105
-rw-r--r--editor/libeditor/HTMLEditor.h5
-rw-r--r--editor/libeditor/HTMLEditorDataTransfer.cpp6
-rw-r--r--editor/libeditor/HTMLEditorDeleteHandler.cpp1104
-rw-r--r--editor/libeditor/HTMLStyleEditor.cpp6
-rw-r--r--editor/libeditor/WSRunObject.cpp181
-rw-r--r--editor/libeditor/WSRunObject.h237
12 files changed, 1445 insertions, 423 deletions
diff --git a/editor/libeditor/EditAction.h b/editor/libeditor/EditAction.h
index f74d1c6949..6133d82f4a 100644
--- a/editor/libeditor/EditAction.h
+++ b/editor/libeditor/EditAction.h
@@ -642,6 +642,7 @@ inline EditorInputType ToInputType(EditAction aEditAction) {
inline bool MayEditActionDeleteAroundCollapsedSelection(
const EditAction aEditAction) {
switch (aEditAction) {
+ case EditAction::eCut:
case EditAction::eDeleteSelection:
case EditAction::eDeleteBackward:
case EditAction::eDeleteForward:
diff --git a/editor/libeditor/EditorBase.cpp b/editor/libeditor/EditorBase.cpp
index cd1439e4c0..d63086e78e 100644
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -66,17 +66,18 @@
#include "mozilla/TextInputListener.h" // for TextInputListener
#include "mozilla/TextServicesDocument.h" // for TextServicesDocument
#include "mozilla/TextEvents.h"
-#include "mozilla/TransactionManager.h" // for TransactionManager
-#include "mozilla/dom/AbstractRange.h" // for AbstractRange
-#include "mozilla/dom/Attr.h" // for Attr
-#include "mozilla/dom/BrowsingContext.h" // for BrowsingContext
-#include "mozilla/dom/CharacterData.h" // for CharacterData
-#include "mozilla/dom/DataTransfer.h" // for DataTransfer
-#include "mozilla/dom/Document.h" // for Document
-#include "mozilla/dom/DocumentInlines.h" // for GetObservingPresShell
-#include "mozilla/dom/DragEvent.h" // for DragEvent
-#include "mozilla/dom/Element.h" // for Element, nsINode::AsElement
-#include "mozilla/dom/EventTarget.h" // for EventTarget
+#include "mozilla/TransactionManager.h" // for TransactionManager
+#include "mozilla/dom/AbstractRange.h" // for AbstractRange
+#include "mozilla/dom/Attr.h" // for Attr
+#include "mozilla/dom/BorrowedAttrInfo.h" // for BorrowedAttrInfo
+#include "mozilla/dom/BrowsingContext.h" // for BrowsingContext
+#include "mozilla/dom/CharacterData.h" // for CharacterData
+#include "mozilla/dom/DataTransfer.h" // for DataTransfer
+#include "mozilla/dom/Document.h" // for Document
+#include "mozilla/dom/DocumentInlines.h" // for GetObservingPresShell
+#include "mozilla/dom/DragEvent.h" // for DragEvent
+#include "mozilla/dom/Element.h" // for Element, nsINode::AsElement
+#include "mozilla/dom/EventTarget.h" // for EventTarget
#include "mozilla/dom/HTMLBodyElement.h"
#include "mozilla/dom/HTMLBRElement.h"
#include "mozilla/dom/Selection.h" // for Selection, etc.
@@ -2940,33 +2941,52 @@ void EditorBase::CloneAttributesWithTransaction(Element& aDestElement,
bool isDestElementInBody = rootElement->Contains(destElement);
// Clear existing attributes
- RefPtr<nsDOMAttributeMap> destAttributes = destElement->Attributes();
- while (RefPtr<Attr> attr = destAttributes->Item(0)) {
+ AutoTArray<OwningNonNull<nsAtom>, 16> destElementAttributes;
+ if (const uint32_t attrCount = destElement->GetAttrCount()) {
+ destElementAttributes.SetCapacity(attrCount);
+ for (const uint32_t i : IntegerRange(attrCount)) {
+ if (const nsAttrName* attrName = destElement->GetUnsafeAttrNameAt(i)) {
+ MOZ_ASSERT(attrName->LocalName());
+ destElementAttributes.AppendElement(*attrName->LocalName());
+ }
+ }
+ }
+ for (const OwningNonNull<nsAtom>& attr : destElementAttributes) {
if (isDestElementInBody) {
- DebugOnly<nsresult> rvIgnored = RemoveAttributeWithTransaction(
- destElement, MOZ_KnownLive(*attr->NodeInfo()->NameAtom()));
+ DebugOnly<nsresult> rvIgnored =
+ RemoveAttributeWithTransaction(destElement, MOZ_KnownLive(*attr));
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored),
"EditorBase::RemoveAttributeWithTransaction() failed, but ignored");
} else {
- DebugOnly<nsresult> rvIgnored = destElement->UnsetAttr(
- kNameSpaceID_None, attr->NodeInfo()->NameAtom(), true);
+ DebugOnly<nsresult> rvIgnored =
+ destElement->UnsetAttr(kNameSpaceID_None, attr, true);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
"Element::UnsetAttr() failed, but ignored");
}
}
// Set just the attributes that the source element has
- RefPtr<nsDOMAttributeMap> sourceAttributes = sourceElement->Attributes();
- uint32_t sourceCount = sourceAttributes->Length();
- for (uint32_t i = 0; i < sourceCount; i++) {
- RefPtr<Attr> attr = sourceAttributes->Item(i);
- nsAutoString value;
- attr->GetValue(value);
+ AutoTArray<std::pair<OwningNonNull<nsAtom>, nsString>, 16>
+ sourceElementAttributes;
+ if (const uint32_t attrCount = sourceElement->GetAttrCount()) {
+ sourceElementAttributes.SetCapacity(attrCount);
+ for (const uint32_t i : IntegerRange(attrCount)) {
+ const BorrowedAttrInfo attrInfo = sourceElement->GetAttrInfoAt(i);
+ if (const nsAttrName* attrName = attrInfo.mName) {
+ MOZ_ASSERT(attrName->LocalName());
+ MOZ_ASSERT(attrInfo.mValue);
+ nsString value;
+ attrInfo.mValue->ToString(value);
+ sourceElementAttributes.AppendElement(std::make_pair(
+ OwningNonNull<nsAtom>(*attrName->LocalName()), std::move(value)));
+ }
+ }
+ }
+ for (const auto& attr : sourceElementAttributes) {
if (isDestElementInBody) {
DebugOnly<nsresult> rvIgnored = SetAttributeOrEquivalent(
- destElement, MOZ_KnownLive(attr->NodeInfo()->NameAtom()), value,
- false);
+ destElement, MOZ_KnownLive(attr.first), attr.second, false);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored),
"EditorBase::SetAttributeOrEquivalent() failed, but ignored");
@@ -2974,8 +2994,7 @@ void EditorBase::CloneAttributesWithTransaction(Element& aDestElement,
// The element is not inserted in the document yet, we don't want to put
// a transaction on the UndoStack
DebugOnly<nsresult> rvIgnored = SetAttributeOrEquivalent(
- destElement, MOZ_KnownLive(attr->NodeInfo()->NameAtom()), value,
- true);
+ destElement, MOZ_KnownLive(attr.first), attr.second, true);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored),
"EditorBase::SetAttributeOrEquivalent() failed, but ignored");
diff --git a/editor/libeditor/HTMLEditSubActionHandler.cpp b/editor/libeditor/HTMLEditSubActionHandler.cpp
index 28e0ad1595..50c35c04ed 100644
--- a/editor/libeditor/HTMLEditSubActionHandler.cpp
+++ b/editor/libeditor/HTMLEditSubActionHandler.cpp
@@ -1604,7 +1604,7 @@ nsresult HTMLEditor::InsertLineBreakAsSubAction() {
NS_WARNING("Inserted <br> was unexpectedly removed");
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
- WSScanResult backwardScanFromBeforeBRElementResult =
+ const WSScanResult backwardScanFromBeforeBRElementResult =
WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
editingHost,
EditorDOMPoint(unwrappedInsertBRElementResult.GetNewNode()),
@@ -1615,8 +1615,8 @@ nsresult HTMLEditor::InsertLineBreakAsSubAction() {
return Err(NS_ERROR_FAILURE);
}
- WSScanResult forwardScanFromAfterBRElementResult =
- WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
+ const WSScanResult forwardScanFromAfterBRElementResult =
+ WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
editingHost, pointToPutCaret,
BlockInlineCheck::UseComputedDisplayStyle);
if (MOZ_UNLIKELY(forwardScanFromAfterBRElementResult.Failed())) {
@@ -1624,9 +1624,18 @@ nsresult HTMLEditor::InsertLineBreakAsSubAction() {
return Err(NS_ERROR_FAILURE);
}
const bool brElementIsAfterBlock =
- backwardScanFromBeforeBRElementResult.ReachedBlockBoundary();
+ backwardScanFromBeforeBRElementResult.ReachedBlockBoundary() ||
+ // FIXME: This is wrong considering because the inline editing host may
+ // be surrounded by visible inline content. However, WSRunScanner is
+ // not aware of block boundary around it and stopping this change causes
+ // starting to fail some WPT. Therefore, we need to keep doing this for
+ // now.
+ backwardScanFromBeforeBRElementResult
+ .ReachedInlineEditingHostBoundary();
const bool brElementIsBeforeBlock =
- forwardScanFromAfterBRElementResult.ReachedBlockBoundary();
+ forwardScanFromAfterBRElementResult.ReachedBlockBoundary() ||
+ // FIXME: See above comment.
+ forwardScanFromAfterBRElementResult.ReachedInlineEditingHostBoundary();
const bool isEmptyEditingHost = HTMLEditUtils::IsEmptyNode(
*editingHost, {EmptyCheckOption::TreatNonEditableContentAsInvisible});
if (brElementIsBeforeBlock &&
@@ -1647,13 +1656,13 @@ nsresult HTMLEditor::InsertLineBreakAsSubAction() {
unwrappedInvisibleAdditionalBRElement.IgnoreCaretPointSuggestion();
} else if (forwardScanFromAfterBRElementResult
.InVisibleOrCollapsibleCharacters()) {
- pointToPutCaret =
- forwardScanFromAfterBRElementResult.Point<EditorDOMPoint>();
+ pointToPutCaret = forwardScanFromAfterBRElementResult
+ .PointAtReachedContent<EditorDOMPoint>();
} else if (forwardScanFromAfterBRElementResult.ReachedSpecialContent()) {
// Next inserting text should be inserted into styled inline elements if
// they have first visible thing in the new line.
- pointToPutCaret =
- forwardScanFromAfterBRElementResult.PointAtContent<EditorDOMPoint>();
+ pointToPutCaret = forwardScanFromAfterBRElementResult
+ .PointAtReachedContent<EditorDOMPoint>();
}
nsresult rv = CollapseSelectionTo(pointToPutCaret);
@@ -2254,21 +2263,32 @@ Result<CreateElementResult, nsresult> HTMLEditor::HandleInsertBRElement(
aEditingHost, {EmptyCheckOption::TreatNonEditableContentAsInvisible});
WSRunScanner wsRunScanner(&aEditingHost, aPointToBreak,
BlockInlineCheck::UseComputedDisplayStyle);
- WSScanResult backwardScanResult =
+ const WSScanResult backwardScanResult =
wsRunScanner.ScanPreviousVisibleNodeOrBlockBoundaryFrom(aPointToBreak);
if (MOZ_UNLIKELY(backwardScanResult.Failed())) {
NS_WARNING(
"WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom() failed");
return Err(NS_ERROR_FAILURE);
}
- const bool brElementIsAfterBlock = backwardScanResult.ReachedBlockBoundary();
- WSScanResult forwardScanResult =
- wsRunScanner.ScanNextVisibleNodeOrBlockBoundaryFrom(aPointToBreak);
+ const bool brElementIsAfterBlock =
+ backwardScanResult.ReachedBlockBoundary() ||
+ // FIXME: This is wrong considering because the inline editing host may
+ // be surrounded by visible inline content. However, WSRunScanner is
+ // not aware of block boundary around it and stopping this change causes
+ // starting to fail some WPT. Therefore, we need to keep doing this for
+ // now.
+ backwardScanResult.ReachedInlineEditingHostBoundary();
+ const WSScanResult forwardScanResult =
+ wsRunScanner.ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
+ aPointToBreak);
if (MOZ_UNLIKELY(forwardScanResult.Failed())) {
NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom() failed");
return Err(NS_ERROR_FAILURE);
}
- const bool brElementIsBeforeBlock = forwardScanResult.ReachedBlockBoundary();
+ const bool brElementIsBeforeBlock =
+ forwardScanResult.ReachedBlockBoundary() ||
+ // FIXME: See above comment
+ forwardScanResult.ReachedInlineEditingHostBoundary();
// First, insert a <br> element.
RefPtr<Element> brElement;
@@ -2383,8 +2403,8 @@ Result<CreateElementResult, nsresult> HTMLEditor::HandleInsertBRElement(
std::move(pointToPutCaret));
}
- WSScanResult forwardScanFromAfterBRElementResult =
- WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
+ const WSScanResult forwardScanFromAfterBRElementResult =
+ WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
&aEditingHost, afterBRElement,
BlockInlineCheck::UseComputedDisplayStyle);
if (MOZ_UNLIKELY(forwardScanFromAfterBRElementResult.Failed())) {
@@ -2422,7 +2442,14 @@ Result<CreateElementResult, nsresult> HTMLEditor::HandleInsertBRElement(
rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
"MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
}
- } else if (forwardScanFromAfterBRElementResult.ReachedBlockBoundary() &&
+ } else if ((forwardScanFromAfterBRElementResult.ReachedBlockBoundary() ||
+ // FIXME: This is wrong considering because the inline editing
+ // host may be surrounded by visible inline content. However,
+ // WSRunScanner is not aware of block boundary around it and
+ // stopping this change causes starting to fail some WPT.
+ // Therefore, we need to keep doing this for now.
+ forwardScanFromAfterBRElementResult
+ .ReachedInlineEditingHostBoundary()) &&
!brElementIsAfterBlock) {
Result<CreateElementResult, nsresult> invisibleAdditionalBRElementResult =
InsertAdditionalInvisibleLineBreak();
@@ -2530,7 +2557,8 @@ Result<EditorDOMPoint, nsresult> HTMLEditor::HandleInsertLinefeed(
WSRunScanner wsScannerAtCaret(&aEditingHost, pointToPutCaret,
BlockInlineCheck::UseComputedDisplayStyle);
if (wsScannerAtCaret.StartsFromPreformattedLineBreak() &&
- wsScannerAtCaret.EndsByBlockBoundary() &&
+ (wsScannerAtCaret.EndsByBlockBoundary() ||
+ wsScannerAtCaret.EndsByInlineEditingHostBoundary()) &&
HTMLEditUtils::CanNodeContain(*wsScannerAtCaret.GetEndReasonContent(),
*nsGkAtoms::br)) {
AutoTrackDOMPoint trackingInsertedPosition(RangeUpdaterRef(),
@@ -2612,8 +2640,8 @@ HTMLEditor::HandleInsertParagraphInMailCiteElement(
// mailquote (in either inline or block case). The latter can confuse a
// user if they click there and start typing, because being in the
// mailquote may affect wrapping behavior, or font color, etc.
- WSScanResult forwardScanFromPointToSplitResult =
- WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
+ const WSScanResult forwardScanFromPointToSplitResult =
+ WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
&aEditingHost, pointToSplit, BlockInlineCheck::UseHTMLDefaultStyle);
if (forwardScanFromPointToSplitResult.Failed()) {
return Err(NS_ERROR_FAILURE);
@@ -2624,8 +2652,8 @@ HTMLEditor::HandleInsertParagraphInMailCiteElement(
forwardScanFromPointToSplitResult.BRElementPtr() != &aMailCiteElement &&
aMailCiteElement.Contains(
forwardScanFromPointToSplitResult.BRElementPtr())) {
- pointToSplit =
- forwardScanFromPointToSplitResult.PointAfterContent<EditorDOMPoint>();
+ pointToSplit = forwardScanFromPointToSplitResult
+ .PointAfterReachedContent<EditorDOMPoint>();
}
if (NS_WARN_IF(!pointToSplit.IsInContentNode())) {
@@ -2726,7 +2754,7 @@ HTMLEditor::HandleInsertParagraphInMailCiteElement(
// XXX Cannot we replace this complicated check with just a call of
// HTMLEditUtils::IsVisibleBRElement with
// resultOfInsertingBRElement.inspect()?
- WSScanResult backwardScanFromPointToCreateNewBRElementResult =
+ const WSScanResult backwardScanFromPointToCreateNewBRElementResult =
WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
&aEditingHost, pointToCreateNewBRElement,
BlockInlineCheck::UseComputedDisplayStyle);
@@ -2743,8 +2771,8 @@ HTMLEditor::HandleInsertParagraphInMailCiteElement(
.ReachedSpecialContent()) {
return NS_SUCCESS_DOM_NO_OPERATION;
}
- WSScanResult forwardScanFromPointAfterNewBRElementResult =
- WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
+ const WSScanResult forwardScanFromPointAfterNewBRElementResult =
+ WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
&aEditingHost,
EditorRawDOMPoint::After(pointToCreateNewBRElement),
BlockInlineCheck::UseComputedDisplayStyle);
@@ -3343,12 +3371,14 @@ HTMLEditor::InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary(
BlockInlineCheck::UseComputedDisplayStyle);
// If the point is not start of a hard line, we don't need to put a `<br>`
// element here.
- if (!wsRunScanner.StartsFromHardLineBreak()) {
+ if (!wsRunScanner.StartsFromHardLineBreak() &&
+ !wsRunScanner.StartsFromInlineEditingHostBoundary()) {
return CaretPoint(EditorDOMPoint());
}
// If the point is not end of a hard line or the hard line does not end with
// block boundary, we don't need to put a `<br>` element here.
- if (!wsRunScanner.EndsByBlockBoundary()) {
+ if (!wsRunScanner.EndsByBlockBoundary() &&
+ !wsRunScanner.EndsByInlineEditingHostBoundary()) {
return CaretPoint(EditorDOMPoint());
}
@@ -7654,7 +7684,7 @@ HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction(
// element even if selected in a blocked phrase element or
// non-HTMLelement.
BlockInlineCheck::UseHTMLDefaultStyle);
- WSScanResult scanResultAtEnd =
+ const WSScanResult scanResultAtEnd =
wsScannerAtEnd.ScanPreviousVisibleNodeOrBlockBoundaryFrom(endPoint);
if (scanResultAtEnd.Failed()) {
NS_WARNING(
@@ -7673,7 +7703,8 @@ HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction(
newRange.SetEnd(EditorRawDOMPoint::After(*child));
}
// else block is empty - we can leave selection alone here, i think.
- } else if (wsScannerAtEnd.StartsFromCurrentBlockBoundary()) {
+ } else if (wsScannerAtEnd.StartsFromCurrentBlockBoundary() ||
+ wsScannerAtEnd.StartsFromInlineEditingHostBoundary()) {
// endpoint is just after start of this block
if (nsIContent* child = HTMLEditUtils::GetPreviousContent(
endPoint, {WalkTreeOption::IgnoreNonEditableNode},
@@ -7692,8 +7723,9 @@ HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction(
// selection past that, it would visibly change meaning of users selection.
WSRunScanner wsScannerAtStart(&aEditingHost, startPoint,
BlockInlineCheck::UseHTMLDefaultStyle);
- WSScanResult scanResultAtStart =
- wsScannerAtStart.ScanNextVisibleNodeOrBlockBoundaryFrom(startPoint);
+ const WSScanResult scanResultAtStart =
+ wsScannerAtStart.ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
+ startPoint);
if (scanResultAtStart.Failed()) {
NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom() failed");
return Err(NS_ERROR_FAILURE);
@@ -7710,7 +7742,8 @@ HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction(
newRange.SetStart(EditorRawDOMPoint(child));
}
// else block is empty - we can leave selection alone here, i think.
- } else if (wsScannerAtStart.EndsByCurrentBlockBoundary()) {
+ } else if (wsScannerAtStart.EndsByCurrentBlockBoundary() ||
+ wsScannerAtStart.EndsByInlineEditingHostBoundary()) {
// startpoint is just before end of this block
if (nsIContent* child = HTMLEditUtils::GetNextContent(
startPoint, {WalkTreeOption::IgnoreNonEditableNode},
@@ -8944,8 +8977,8 @@ HTMLEditor::HandleInsertParagraphInListItemElement(
// If the right list item element is not empty, we need to consider where to
// put caret in it. If it has non-container inline elements, <br> or <hr>, at
// the element is proper position.
- WSScanResult forwardScanFromStartOfListItemResult =
- WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
+ const WSScanResult forwardScanFromStartOfListItemResult =
+ WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
&aEditingHost, EditorRawDOMPoint(&rightListItemElement, 0u),
BlockInlineCheck::UseComputedDisplayStyle);
if (MOZ_UNLIKELY(forwardScanFromStartOfListItemResult.Failed())) {
@@ -8955,8 +8988,8 @@ HTMLEditor::HandleInsertParagraphInListItemElement(
if (forwardScanFromStartOfListItemResult.ReachedSpecialContent() ||
forwardScanFromStartOfListItemResult.ReachedBRElement() ||
forwardScanFromStartOfListItemResult.ReachedHRElement()) {
- auto atFoundElement =
- forwardScanFromStartOfListItemResult.PointAtContent<EditorDOMPoint>();
+ auto atFoundElement = forwardScanFromStartOfListItemResult
+ .PointAtReachedContent<EditorDOMPoint>();
if (NS_WARN_IF(!atFoundElement.IsSetAndValid())) {
return Err(NS_ERROR_FAILURE);
}
@@ -8966,7 +8999,13 @@ HTMLEditor::HandleInsertParagraphInListItemElement(
// If we reached a block boundary (end of the list item or a child block),
// let's put deepest start of the list item or the child block.
- if (forwardScanFromStartOfListItemResult.ReachedBlockBoundary()) {
+ if (forwardScanFromStartOfListItemResult.ReachedBlockBoundary() ||
+ // FIXME: This is wrong considering because the inline editing host may
+ // be surrounded by visible inline content. However, WSRunScanner is
+ // not aware of block boundary around it and stopping this change causes
+ // starting to fail some WPT. Therefore, we need to keep doing this for
+ // now.
+ forwardScanFromStartOfListItemResult.ReachedInlineEditingHostBoundary()) {
return InsertParagraphResult(
&rightListItemElement,
HTMLEditUtils::GetDeepestEditableStartPointOf<EditorDOMPoint>(
@@ -8978,9 +9017,9 @@ HTMLEditor::HandleInsertParagraphInListItemElement(
// Otherwise, return the point at first visible thing.
// XXX This may be not meaningful position if it reached block element
// in aListItemElement.
- return InsertParagraphResult(
- &rightListItemElement,
- forwardScanFromStartOfListItemResult.Point<EditorDOMPoint>());
+ return InsertParagraphResult(&rightListItemElement,
+ forwardScanFromStartOfListItemResult
+ .PointAtReachedContent<EditorDOMPoint>());
}
Result<CreateElementResult, nsresult>
diff --git a/editor/libeditor/HTMLEditUtils.cpp b/editor/libeditor/HTMLEditUtils.cpp
index c2cbbe6157..cf0f4fd04a 100644
--- a/editor/libeditor/HTMLEditUtils.cpp
+++ b/editor/libeditor/HTMLEditUtils.cpp
@@ -2203,8 +2203,8 @@ nsIContent* HTMLEditUtils::GetContentToPreserveInlineStyles(
return aPoint.template ContainerAs<nsIContent>();
}
for (auto point = aPoint.template To<EditorRawDOMPoint>(); point.IsSet();) {
- WSScanResult nextVisibleThing =
- WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
+ const WSScanResult nextVisibleThing =
+ WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
&aEditingHost, point,
BlockInlineCheck::UseComputedDisplayOutsideStyle);
if (nextVisibleThing.InVisibleOrCollapsibleCharacters()) {
@@ -2215,8 +2215,8 @@ nsIContent* HTMLEditUtils::GetContentToPreserveInlineStyles(
// view of users.
if (nextVisibleThing.ReachedSpecialContent() &&
nextVisibleThing.IsContentEditable() &&
- nextVisibleThing.GetContent()->IsElement() &&
- !nextVisibleThing.GetContent()->HasChildNodes() &&
+ nextVisibleThing.ContentIsElement() &&
+ !nextVisibleThing.ElementPtr()->HasChildNodes() &&
HTMLEditUtils::IsContainerNode(*nextVisibleThing.ElementPtr())) {
point.SetAfter(nextVisibleThing.ElementPtr());
continue;
@@ -2260,13 +2260,12 @@ EditorDOMPointType HTMLEditUtils::GetBetterInsertionPointFor(
// If the insertion position is after the last visible item in a line,
// i.e., the insertion position is just before a visible line break <br>,
// we want to skip to the position just after the line break (see bug 68767).
- WSScanResult forwardScanFromPointToInsertResult =
- wsScannerForPointToInsert.ScanNextVisibleNodeOrBlockBoundaryFrom(
+ const WSScanResult forwardScanFromPointToInsertResult =
+ wsScannerForPointToInsert.ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
pointToInsert);
// So, if the next visible node isn't a <br> element, we can insert the block
// level element to the point.
- if (!forwardScanFromPointToInsertResult.GetContent() ||
- !forwardScanFromPointToInsertResult.ReachedBRElement()) {
+ if (!forwardScanFromPointToInsertResult.ReachedBRElement()) {
return pointToInsert;
}
@@ -2274,7 +2273,7 @@ EditorDOMPointType HTMLEditUtils::GetBetterInsertionPointFor(
// positioned at the beginning of a block, in that case skipping the <br>
// would not insert the <br> at the caret position, but after the current
// empty line.
- WSScanResult backwardScanFromPointToInsertResult =
+ const WSScanResult backwardScanFromPointToInsertResult =
wsScannerForPointToInsert.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
pointToInsert);
// So, if there is no previous visible node,
@@ -2282,14 +2281,15 @@ EditorDOMPointType HTMLEditUtils::GetBetterInsertionPointFor(
// or, if the previous visible node is different block,
// we need to skip the following <br>. So, otherwise, we can insert the
// block at the insertion point.
- if (!backwardScanFromPointToInsertResult.GetContent() ||
+ if (NS_WARN_IF(backwardScanFromPointToInsertResult.Failed()) ||
+ backwardScanFromPointToInsertResult.ReachedInlineEditingHostBoundary() ||
backwardScanFromPointToInsertResult.ReachedBRElement() ||
backwardScanFromPointToInsertResult.ReachedCurrentBlockBoundary()) {
return pointToInsert;
}
return forwardScanFromPointToInsertResult
- .template PointAfterContent<EditorDOMPointType>();
+ .template PointAfterReachedContent<EditorDOMPointType>();
}
// static
@@ -2310,7 +2310,7 @@ EditorDOMPointType HTMLEditUtils::GetBetterCaretPositionToInsertText(
if (aPoint.IsEndOfContainer()) {
WSRunScanner scanner(&aEditingHost, aPoint,
BlockInlineCheck::UseComputedDisplayStyle);
- WSScanResult previousThing =
+ const WSScanResult previousThing =
scanner.ScanPreviousVisibleNodeOrBlockBoundaryFrom(aPoint);
if (previousThing.InVisibleOrCollapsibleCharacters()) {
return EditorDOMPointType::AtEndOf(*previousThing.TextPtr());
diff --git a/editor/libeditor/HTMLEditUtils.h b/editor/libeditor/HTMLEditUtils.h
index 115a329247..01e41c147d 100644
--- a/editor/libeditor/HTMLEditUtils.h
+++ b/editor/libeditor/HTMLEditUtils.h
@@ -1853,14 +1853,15 @@ class HTMLEditUtils final {
continue;
}
return lastEmptyContent != &aEmptyContent
- ? lastEmptyContent->AsElement()
+ ? Element::FromNode(lastEmptyContent)
: nullptr;
}
}
lastEmptyContent = element;
}
- return lastEmptyContent != &aEmptyContent ? lastEmptyContent->AsElement()
- : nullptr;
+ return lastEmptyContent != &aEmptyContent
+ ? Element::FromNode(lastEmptyContent)
+ : nullptr;
}
/**
diff --git a/editor/libeditor/HTMLEditor.cpp b/editor/libeditor/HTMLEditor.cpp
index fc88c79477..11d021e70b 100644
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -46,6 +46,7 @@
#include "mozilla/css/Loader.h"
#include "mozilla/dom/AncestorIterator.h"
#include "mozilla/dom/Attr.h"
+#include "mozilla/dom/BorrowedAttrInfo.h"
#include "mozilla/dom/DocumentFragment.h"
#include "mozilla/dom/DocumentInlines.h"
#include "mozilla/dom/Element.h"
@@ -63,7 +64,6 @@
#include "nsContentUtils.h"
#include "nsCRT.h"
#include "nsDebug.h"
-#include "nsDOMAttributeMap.h"
#include "nsElementTable.h"
#include "nsFocusManager.h"
#include "nsGenericHTMLElement.h"
@@ -215,27 +215,23 @@ HTMLEditor::AppendNewElementWithBRToInsertingElement(
}
HTMLEditor::AttributeFilter HTMLEditor::CopyAllAttributes =
- [](HTMLEditor&, const Element&, const Element&, const Attr&, nsString&) {
- return true;
- };
+ [](HTMLEditor&, const Element&, const Element&, int32_t, const nsAtom&,
+ nsString&) { return true; };
HTMLEditor::AttributeFilter HTMLEditor::CopyAllAttributesExceptId =
- [](HTMLEditor&, const Element&, const Element&, const Attr& aAttr,
- nsString&) {
- return aAttr.NodeInfo()->NamespaceID() != kNameSpaceID_None ||
- aAttr.NodeInfo()->NameAtom() != nsGkAtoms::id;
+ [](HTMLEditor&, const Element&, const Element&, int32_t aNamespaceID,
+ const nsAtom& aAttrName, nsString&) {
+ return aNamespaceID != kNameSpaceID_None || &aAttrName != nsGkAtoms::id;
};
HTMLEditor::AttributeFilter HTMLEditor::CopyAllAttributesExceptDir =
- [](HTMLEditor&, const Element&, const Element&, const Attr& aAttr,
- nsString&) {
- return aAttr.NodeInfo()->NamespaceID() != kNameSpaceID_None ||
- aAttr.NodeInfo()->NameAtom() != nsGkAtoms::dir;
+ [](HTMLEditor&, const Element&, const Element&, int32_t aNamespaceID,
+ const nsAtom& aAttrName, nsString&) {
+ return aNamespaceID != kNameSpaceID_None || &aAttrName != nsGkAtoms::dir;
};
HTMLEditor::AttributeFilter HTMLEditor::CopyAllAttributesExceptIdAndDir =
- [](HTMLEditor&, const Element&, const Element&, const Attr& aAttr,
- nsString&) {
- return !(aAttr.NodeInfo()->NamespaceID() == kNameSpaceID_None &&
- (aAttr.NodeInfo()->NameAtom() == nsGkAtoms::id ||
- aAttr.NodeInfo()->NameAtom() == nsGkAtoms::dir));
+ [](HTMLEditor&, const Element&, const Element&, int32_t aNamespaceID,
+ const nsAtom& aAttrName, nsString&) {
+ return !(aNamespaceID == kNameSpaceID_None &&
+ (&aAttrName == nsGkAtoms::id || &aAttrName == nsGkAtoms::dir));
};
HTMLEditor::HTMLEditor(const Document& aDocument)
@@ -1150,15 +1146,15 @@ nsresult HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode(
if (Text* text = leafContent->GetAsText()) {
// If there is editable and visible text node, move caret at first of
// the visible character.
- WSScanResult scanResultInTextNode =
- WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
+ const WSScanResult scanResultInTextNode =
+ WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
editingHost, EditorRawDOMPoint(text, 0),
BlockInlineCheck::UseComputedDisplayStyle);
if ((scanResultInTextNode.InVisibleOrCollapsibleCharacters() ||
scanResultInTextNode.ReachedPreformattedLineBreak()) &&
scanResultInTextNode.TextPtr() == text) {
nsresult rv = CollapseSelectionTo(
- scanResultInTextNode.Point<EditorRawDOMPoint>());
+ scanResultInTextNode.PointAtReachedContent<EditorRawDOMPoint>());
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"EditorBase::CollapseSelectionTo() failed");
return rv;
@@ -3603,29 +3599,35 @@ Result<CreateElementResult, nsresult> HTMLEditor::CreateAndInsertElement(
nsresult HTMLEditor::CopyAttributes(WithTransaction aWithTransaction,
Element& aDestElement, Element& aSrcElement,
const AttributeFilter& aFilterFunc) {
- RefPtr<nsDOMAttributeMap> srcAttributes = aSrcElement.Attributes();
- if (!srcAttributes->Length()) {
+ if (!aSrcElement.GetAttrCount()) {
return NS_OK;
}
- AutoTArray<OwningNonNull<Attr>, 16> srcAttrs;
- srcAttrs.SetCapacity(srcAttributes->Length());
- for (uint32_t i = 0; i < srcAttributes->Length(); i++) {
- RefPtr<Attr> attr = srcAttributes->Item(i);
- if (!attr) {
- break;
+ struct MOZ_STACK_CLASS AttrCache {
+ int32_t mNamespaceID;
+ OwningNonNull<nsAtom> mName;
+ nsString mValue;
+ };
+ AutoTArray<AttrCache, 16> srcAttrs;
+ srcAttrs.SetCapacity(aSrcElement.GetAttrCount());
+ for (const uint32_t i : IntegerRange(aSrcElement.GetAttrCount())) {
+ const BorrowedAttrInfo attrInfo = aSrcElement.GetAttrInfoAt(i);
+ if (const nsAttrName* attrName = attrInfo.mName) {
+ MOZ_ASSERT(attrName->LocalName());
+ MOZ_ASSERT(attrInfo.mValue);
+ nsString attrValue;
+ attrInfo.mValue->ToString(attrValue);
+ srcAttrs.AppendElement(AttrCache{attrInfo.mName->NamespaceID(),
+ *attrName->LocalName(), attrValue});
}
- srcAttrs.AppendElement(std::move(attr));
}
if (aWithTransaction == WithTransaction::No) {
- for (const OwningNonNull<Attr>& attr : srcAttrs) {
- nsString value;
- attr->GetValue(value);
- if (!aFilterFunc(*this, aSrcElement, aDestElement, attr, value)) {
+ for (auto& attr : srcAttrs) {
+ if (!aFilterFunc(*this, aSrcElement, aDestElement, attr.mNamespaceID,
+ attr.mName, attr.mValue)) {
continue;
}
- DebugOnly<nsresult> rvIgnored =
- aDestElement.SetAttr(attr->NodeInfo()->NamespaceID(),
- attr->NodeInfo()->NameAtom(), value, false);
+ DebugOnly<nsresult> rvIgnored = aDestElement.SetAttr(
+ attr.mNamespaceID, attr.mName, attr.mValue, false);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
"Element::SetAttr() failed, but ignored");
}
@@ -3787,31 +3789,24 @@ nsresult HTMLEditor::InsertLinkAroundSelectionAsAction(
*this, ScrollSelectionIntoView::Yes, __FUNCTION__);
// Set all attributes found on the supplied anchor element
- RefPtr<nsDOMAttributeMap> attributeMap = anchor->Attributes();
- if (NS_WARN_IF(!attributeMap)) {
- return NS_ERROR_FAILURE;
- }
-
// TODO: We should stop using this loop for adding attributes to newly created
// `<a href="...">` elements. Then, we can avoid to increate the ref-
// counter of attribute names since we can use nsStaticAtom if we don't
// need to support unknown attributes.
AutoTArray<EditorInlineStyleAndValue, 32> stylesToSet;
- stylesToSet.SetCapacity(attributeMap->Length());
- nsString value;
- for (uint32_t i : IntegerRange(attributeMap->Length())) {
- RefPtr<Attr> attribute = attributeMap->Item(i);
- if (!attribute) {
- continue;
+ if (const uint32_t attrCount = anchor->GetAttrCount()) {
+ stylesToSet.SetCapacity(attrCount);
+ for (const uint32_t i : IntegerRange(attrCount)) {
+ const BorrowedAttrInfo attrInfo = anchor->GetAttrInfoAt(i);
+ if (const nsAttrName* attrName = attrInfo.mName) {
+ RefPtr<nsAtom> attributeName = attrName->LocalName();
+ MOZ_ASSERT(attrInfo.mValue);
+ nsString attrValue;
+ attrInfo.mValue->ToString(attrValue);
+ stylesToSet.AppendElement(EditorInlineStyleAndValue(
+ *nsGkAtoms::a, std::move(attributeName), std::move(attrValue)));
+ }
}
-
- RefPtr<nsAtom> attributeName = attribute->NodeInfo()->NameAtom();
-
- MOZ_ASSERT(value.IsEmpty());
- attribute->GetValue(value);
-
- stylesToSet.AppendElement(EditorInlineStyleAndValue(
- *nsGkAtoms::a, std::move(attributeName), std::move(value)));
}
rv = SetInlinePropertiesAsSubAction(stylesToSet);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
diff --git a/editor/libeditor/HTMLEditor.h b/editor/libeditor/HTMLEditor.h
index 2a31ead29d..e56ae0fe33 100644
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -1333,7 +1333,8 @@ class HTMLEditor final : public EditorBase,
* @param aHTMLEditor The HTML editor.
* @param aSrcElement The element which have the attribute.
* @param aDestElement The element which will have the attribute.
- * @param aAttr [in] The attribute which will be copied.
+ * @param aNamespaceID [in] The namespace ID of aAttrName.
+ * @param aAttrName [in] The attribute name which will be copied.
* @param aValue [in/out] The attribute value which will be copied.
* Once updated, the new value is used.
* @return true if the attribute should be copied, otherwise,
@@ -1341,7 +1342,7 @@ class HTMLEditor final : public EditorBase,
*/
using AttributeFilter = std::function<bool(
HTMLEditor& aHTMLEditor, Element& aSrcElement, Element& aDestElement,
- const dom::Attr& aAttr, nsString& aValue)>;
+ int32_t aNamespaceID, const nsAtom& aAttrName, nsString& aValue)>;
static AttributeFilter CopyAllAttributes;
static AttributeFilter CopyAllAttributesExceptId;
static AttributeFilter CopyAllAttributesExceptDir;
diff --git a/editor/libeditor/HTMLEditorDataTransfer.cpp b/editor/libeditor/HTMLEditorDataTransfer.cpp
index 72aeb7eee8..f7b785722a 100644
--- a/editor/libeditor/HTMLEditorDataTransfer.cpp
+++ b/editor/libeditor/HTMLEditorDataTransfer.cpp
@@ -505,12 +505,12 @@ HTMLEditor::HTMLWithContextInserter::GetNewCaretPointAfterInsertingHTML(
editingHost,
EditorDOMPoint(wsRunScannerAtCaret.GetStartReasonContent()),
BlockInlineCheck::UseComputedDisplayStyle);
- WSScanResult backwardScanFromPointToCaretResult =
+ const WSScanResult backwardScanFromPointToCaretResult =
wsRunScannerAtStartReason.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
pointToPutCaret);
if (backwardScanFromPointToCaretResult.InVisibleOrCollapsibleCharacters()) {
- pointToPutCaret =
- backwardScanFromPointToCaretResult.Point<EditorDOMPoint>();
+ pointToPutCaret = backwardScanFromPointToCaretResult
+ .PointAfterReachedContent<EditorDOMPoint>();
} else if (backwardScanFromPointToCaretResult.ReachedSpecialContent()) {
// XXX In my understanding, this is odd. The end reason may not be
// same as the reached special content because the equality is
diff --git a/editor/libeditor/HTMLEditorDeleteHandler.cpp b/editor/libeditor/HTMLEditorDeleteHandler.cpp
index 39bb95151e..8c58979001 100644
--- a/editor/libeditor/HTMLEditorDeleteHandler.cpp
+++ b/editor/libeditor/HTMLEditorDeleteHandler.cpp
@@ -353,6 +353,28 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
const EditorDOMRangeType& aRangeToDelete) const;
/**
+ * Extend the start boundary of aRangeToDelete to contain ancestor inline
+ * elements which will be empty once the content in aRangeToDelete is removed
+ * from the tree.
+ *
+ * NOTE: This is designed for deleting inline elements which become empty if
+ * aRangeToDelete which crosses a block boundary of right block child.
+ * Therefore, you may need to improve this method if you want to use this in
+ * the other cases.
+ *
+ * @param aRangeToDelete [in/out] The range to delete. This start
+ * boundary may be modified.
+ * @param aEditingHost The editing host.
+ * @return true if aRangeToDelete is modified.
+ * false if aRangeToDelete is not modified.
+ * error if aRangeToDelete gets unexpected
+ * situation.
+ */
+ static Result<bool, nsresult>
+ ExtendRangeToContainAncestorInlineElementsAtStart(
+ nsRange& aRangeToDelete, const Element& aEditingHost);
+
+ /**
* A helper method for ExtendOrShrinkRangeToDelete(). This returns shrunken
* range if aRangeToDelete selects all over list elements which have some list
* item elements to avoid to delete all list items from the list element.
@@ -529,13 +551,15 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
* @param aCurrentBlockElement The current block element.
* @param aCaretPoint The caret point (i.e., selection start
* or end).
+ * @param aEditingHost The editing host.
* @return true if can continue to handle the
* deletion.
*/
bool PrepareToDeleteAtCurrentBlockBoundary(
const HTMLEditor& aHTMLEditor,
nsIEditor::EDirection aDirectionAndAmount,
- Element& aCurrentBlockElement, const EditorDOMPoint& aCaretPoint);
+ Element& aCurrentBlockElement, const EditorDOMPoint& aCaretPoint,
+ const Element& aEditingHost);
/**
* PrepareToDeleteAtOtherBlockBoundary() considers left content and right
@@ -567,11 +591,13 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
* @param aHTMLEditor The HTML editor.
* @param aRangeToDelete The range to delete. Must not be
* collapsed.
+ * @param aEditingHost The editing host.
* @return true if can continue to handle the
* deletion.
*/
bool PrepareToDeleteNonCollapsedRange(const HTMLEditor& aHTMLEditor,
- const nsRange& aRangeToDelete);
+ const nsRange& aRangeToDelete,
+ const Element& aEditingHost);
/**
* Run() executes the joining.
@@ -609,17 +635,20 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
"HandleDeleteAtOtherBlockBoundary() failed");
return result;
}
- case Mode::DeleteBRElement: {
- Result<EditActionResult, nsresult> result =
- DeleteBRElement(aHTMLEditor, aDirectionAndAmount, aEditingHost);
+ case Mode::DeleteBRElement:
+ case Mode::DeletePrecedingBRElementOfBlock:
+ case Mode::DeletePrecedingPreformattedLineBreak: {
+ Result<EditActionResult, nsresult> result = HandleDeleteLineBreak(
+ aHTMLEditor, aDirectionAndAmount, aCaretPoint, aEditingHost);
NS_WARNING_ASSERTION(
result.isOk(),
- "AutoBlockElementsJoiner::DeleteBRElement() failed");
+ "AutoBlockElementsJoiner::HandleDeleteLineBreak() failed");
return result;
}
case Mode::JoinBlocksInSameParent:
case Mode::DeleteContentInRange:
case Mode::DeleteNonCollapsedRange:
+ case Mode::DeletePrecedingLinesAndContentInRange:
MOZ_ASSERT_UNREACHABLE(
"This mode should be handled in the other Run()");
return Err(NS_ERROR_UNEXPECTED);
@@ -654,16 +683,21 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
"ComputeRangeToDeleteAtOtherBlockBoundary() failed");
return rv;
}
- case Mode::DeleteBRElement: {
- nsresult rv = ComputeRangeToDeleteBRElement(aRangeToDelete);
+ case Mode::DeleteBRElement:
+ case Mode::DeletePrecedingBRElementOfBlock:
+ case Mode::DeletePrecedingPreformattedLineBreak: {
+ nsresult rv = ComputeRangeToDeleteLineBreak(
+ aHTMLEditor, aRangeToDelete, aEditingHost,
+ ComputeRangeFor::GetTargetRanges);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"AutoBlockElementsJoiner::"
- "ComputeRangeToDeleteBRElement() failed");
+ "ComputeRangeToDeleteLineBreak() failed");
return rv;
}
case Mode::JoinBlocksInSameParent:
case Mode::DeleteContentInRange:
case Mode::DeleteNonCollapsedRange:
+ case Mode::DeletePrecedingLinesAndContentInRange:
MOZ_ASSERT_UNREACHABLE(
"This mode should be handled in the other "
"ComputeRangesToDelete()");
@@ -696,6 +730,8 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
case Mode::JoinCurrentBlock:
case Mode::JoinOtherBlock:
case Mode::DeleteBRElement:
+ case Mode::DeletePrecedingBRElementOfBlock:
+ case Mode::DeletePrecedingPreformattedLineBreak:
MOZ_ASSERT_UNREACHABLE(
"This mode should be handled in the other Run()");
return Err(NS_ERROR_UNEXPECTED);
@@ -717,7 +753,8 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
"AutoBlockElementsJoiner::DeleteContentInRange() failed");
return result;
}
- case Mode::DeleteNonCollapsedRange: {
+ case Mode::DeleteNonCollapsedRange:
+ case Mode::DeletePrecedingLinesAndContentInRange: {
Result<EditActionResult, nsresult> result =
HandleDeleteNonCollapsedRange(
aHTMLEditor, aDirectionAndAmount, aStripWrappers,
@@ -744,6 +781,8 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
case Mode::JoinCurrentBlock:
case Mode::JoinOtherBlock:
case Mode::DeleteBRElement:
+ case Mode::DeletePrecedingBRElementOfBlock:
+ case Mode::DeletePrecedingPreformattedLineBreak:
MOZ_ASSERT_UNREACHABLE(
"This mode should be handled in the other "
"ComputeRangesToDelete()");
@@ -765,7 +804,8 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
"ComputeRangesToDeleteContentInRanges() failed");
return rv;
}
- case Mode::DeleteNonCollapsedRange: {
+ case Mode::DeleteNonCollapsedRange:
+ case Mode::DeletePrecedingLinesAndContentInRange: {
nsresult rv = ComputeRangeToDeleteNonCollapsedRange(
aHTMLEditor, aDirectionAndAmount, aRangeToDelete,
aSelectionWasCollapsed, aEditingHost);
@@ -825,10 +865,14 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
nsIEditor::EDirection aDirectionAndAmount, nsRange& aRangeToDelete,
const Element& aEditingHost) const;
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
- DeleteBRElement(HTMLEditor& aHTMLEditor,
- nsIEditor::EDirection aDirectionAndAmount,
- const Element& aEditingHost);
- nsresult ComputeRangeToDeleteBRElement(nsRange& aRangeToDelete) const;
+ HandleDeleteLineBreak(HTMLEditor& aHTMLEditor,
+ nsIEditor::EDirection aDirectionAndAmount,
+ const EditorDOMPoint& aCaretPoint,
+ const Element& aEditingHost);
+ enum class ComputeRangeFor : bool { GetTargetRanges, ToDeleteTheRange };
+ nsresult ComputeRangeToDeleteLineBreak(
+ const HTMLEditor& aHTMLEditor, nsRange& aRangeToDelete,
+ const Element& aEditingHost, ComputeRangeFor aComputeRangeFor) const;
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
DeleteContentInRange(HTMLEditor& aHTMLEditor,
nsIEditor::EDirection aDirectionAndAmount,
@@ -913,6 +957,27 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
DeleteTextAtStartAndEndOfRange(HTMLEditor& aHTMLEditor, nsRange& aRange);
+ /**
+ * Return a block element which is an inclusive ancestor of the container of
+ * aPoint if aPoint is start of ancestor blocks. For example, if `<div
+ * id=div1>abc<div id=div2><div id=div3>[]def</div></div></div>`, return
+ * #div2.
+ */
+ template <typename EditorDOMPointType>
+ static Result<Element*, nsresult>
+ GetMostDistantBlockAncestorIfPointIsStartAtBlock(
+ const EditorDOMPointType& aPoint, const Element& aEditingHost,
+ const Element* aAncestorLimiter = nullptr);
+
+ /**
+ * Extend aRangeToDelete to contain new empty inline ancestors and contain
+ * an invisible <br> element before right child block which causes an empty
+ * line but the range starts after it.
+ */
+ void ExtendRangeToDeleteNonCollapsedRange(
+ const HTMLEditor& aHTMLEditor, nsRange& aRangeToDelete,
+ const Element& aEditingHost, ComputeRangeFor aComputeRangeFor) const;
+
class MOZ_STACK_CLASS AutoInclusiveAncestorBlockElementsJoiner final {
public:
AutoInclusiveAncestorBlockElementsJoiner() = delete;
@@ -1030,8 +1095,17 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
JoinOtherBlock,
JoinBlocksInSameParent,
DeleteBRElement,
+ // The instance will handle only the <br> element immediately before a
+ // block.
+ DeletePrecedingBRElementOfBlock,
+ // The instance will handle only the preceding preformatted line break
+ // before a block.
+ DeletePrecedingPreformattedLineBreak,
DeleteContentInRange,
DeleteNonCollapsedRange,
+ // The instance will handle preceding lines of the right block and content
+ // in the range in the right block.
+ DeletePrecedingLinesAndContentInRange,
};
AutoDeleteRangesHandler* mDeleteRangesHandler;
const AutoDeleteRangesHandler& mDeleteRangesHandlerConst;
@@ -1043,6 +1117,7 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
// removed at deletion.
AutoTArray<OwningNonNull<nsIContent>, 8> mSkippedInvisibleContents;
RefPtr<dom::HTMLBRElement> mBRElement;
+ EditorDOMPointInText mPreformattedLineBreak;
Mode mMode = Mode::NotInitialized;
}; // HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner
@@ -1371,10 +1446,10 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDelete(
WSRunScanner wsRunScannerAtCaret(
editingHost, caretPoint,
BlockInlineCheck::UseComputedDisplayOutsideStyle);
- WSScanResult scanFromCaretPointResult =
+ const WSScanResult scanFromCaretPointResult =
aDirectionAndAmount == nsIEditor::eNext
- ? wsRunScannerAtCaret.ScanNextVisibleNodeOrBlockBoundaryFrom(
- caretPoint)
+ ? wsRunScannerAtCaret
+ .ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(caretPoint)
: wsRunScannerAtCaret.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
caretPoint);
if (scanFromCaretPointResult.Failed()) {
@@ -1383,26 +1458,23 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDelete(
"failed");
return NS_ERROR_FAILURE;
}
- if (!scanFromCaretPointResult.GetContent()) {
- return NS_SUCCESS_DOM_NO_OPERATION;
- }
+ MOZ_ASSERT(scanFromCaretPointResult.GetContent());
if (scanFromCaretPointResult.ReachedBRElement()) {
if (scanFromCaretPointResult.BRElementPtr() ==
wsRunScannerAtCaret.GetEditingHost()) {
return NS_OK;
}
- if (!EditorUtils::IsEditableContent(
- *scanFromCaretPointResult.BRElementPtr(), EditorType::HTML)) {
+ if (!scanFromCaretPointResult.IsContentEditable()) {
return NS_SUCCESS_DOM_NO_OPERATION;
}
- if (HTMLEditUtils::IsInvisibleBRElement(
- *scanFromCaretPointResult.BRElementPtr())) {
+ if (scanFromCaretPointResult.ReachedInvisibleBRElement()) {
EditorDOMPoint newCaretPosition =
aDirectionAndAmount == nsIEditor::eNext
- ? EditorDOMPoint::After(
- *scanFromCaretPointResult.BRElementPtr())
- : EditorDOMPoint(scanFromCaretPointResult.BRElementPtr());
+ ? scanFromCaretPointResult
+ .PointAfterReachedContent<EditorDOMPoint>()
+ : scanFromCaretPointResult
+ .PointAtReachedContent<EditorDOMPoint>();
if (NS_WARN_IF(!newCaretPosition.IsSet())) {
return NS_ERROR_FAILURE;
}
@@ -1450,7 +1522,8 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDelete(
// Otherwise, extend the range to contain the invisible `<br>`
// element.
- if (EditorRawDOMPoint(scanFromCaretPointResult.BRElementPtr())
+ if (scanFromCaretPointResult
+ .PointAtReachedContent<EditorRawDOMPoint>()
.IsBefore(
aRangesToDelete
.GetFirstRangeStartPoint<EditorRawDOMPoint>())) {
@@ -1463,12 +1536,13 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDelete(
return rv;
}
if (aRangesToDelete.GetFirstRangeEndPoint<EditorRawDOMPoint>()
- .IsBefore(EditorRawDOMPoint::After(
- *scanFromCaretPointResult.BRElementPtr()))) {
+ .IsBefore(
+ scanFromCaretPointResult
+ .PointAfterReachedContent<EditorRawDOMPoint>())) {
nsresult rv = aRangesToDelete.FirstRangeRef()->SetStartAndEnd(
aRangesToDelete.FirstRangeRef()->StartRef(),
- EditorRawDOMPoint::After(
- *scanFromCaretPointResult.BRElementPtr())
+ scanFromCaretPointResult
+ .PointAfterReachedContent<EditorRawDOMPoint>()
.ToRawRangeBoundary());
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"nsRange::SetStartAndEnd() failed");
@@ -1670,10 +1744,11 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::Run(
WSRunScanner wsRunScannerAtCaret(
&aEditingHost, caretPoint.ref(),
BlockInlineCheck::UseComputedDisplayOutsideStyle);
- WSScanResult scanFromCaretPointResult =
+ const WSScanResult scanFromCaretPointResult =
aDirectionAndAmount == nsIEditor::eNext
- ? wsRunScannerAtCaret.ScanNextVisibleNodeOrBlockBoundaryFrom(
- caretPoint.ref())
+ ? wsRunScannerAtCaret
+ .ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
+ caretPoint.ref())
: wsRunScannerAtCaret.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
caretPoint.ref());
if (MOZ_UNLIKELY(scanFromCaretPointResult.Failed())) {
@@ -1682,20 +1757,17 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::Run(
"failed");
return Err(NS_ERROR_FAILURE);
}
- if (!scanFromCaretPointResult.GetContent()) {
- return EditActionResult::CanceledResult();
- }
+ MOZ_ASSERT(scanFromCaretPointResult.GetContent());
+
// Short circuit for invisible breaks. delete them and recurse.
if (scanFromCaretPointResult.ReachedBRElement()) {
if (scanFromCaretPointResult.BRElementPtr() == &aEditingHost) {
return EditActionResult::HandledResult();
}
- if (!EditorUtils::IsEditableContent(
- *scanFromCaretPointResult.BRElementPtr(), EditorType::HTML)) {
+ if (!scanFromCaretPointResult.IsContentEditable()) {
return EditActionResult::CanceledResult();
}
- if (HTMLEditUtils::IsInvisibleBRElement(
- *scanFromCaretPointResult.BRElementPtr())) {
+ if (scanFromCaretPointResult.ReachedInvisibleBRElement()) {
// TODO: We should extend the range to delete again before/after
// the caret point and use `HandleDeleteNonCollapsedRanges()`
// instead after we would create delete range computation
@@ -1728,10 +1800,10 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::Run(
WSRunScanner wsRunScannerAtCaret(
&aEditingHost, caretPoint.ref(),
BlockInlineCheck::UseComputedDisplayOutsideStyle);
- WSScanResult scanFromCaretPointResult =
+ const WSScanResult scanFromCaretPointResult =
aDirectionAndAmount == nsIEditor::eNext
? wsRunScannerAtCaret
- .ScanNextVisibleNodeOrBlockBoundaryFrom(
+ .ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
caretPoint.ref())
: wsRunScannerAtCaret
.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
@@ -1742,7 +1814,7 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::Run(
"VisibleNodeOrBlockBoundaryFrom() failed");
return Err(NS_ERROR_FAILURE);
}
- if (MOZ_UNLIKELY(
+ if (NS_WARN_IF(
scanFromCaretPointResult.ReachedInvisibleBRElement())) {
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
@@ -1786,8 +1858,11 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAroundCollapsedRanges(
if (aScanFromCaretPointResult.InCollapsibleWhiteSpaces() ||
aScanFromCaretPointResult.InNonCollapsibleCharacters() ||
aScanFromCaretPointResult.ReachedPreformattedLineBreak()) {
+ // This means that if aDirectionAndAmount == nsIEditor::eNext, collapse
+ // selection at the found character. Otherwise, collapse selection after
+ // the found character.
nsresult rv = aRangesToDelete.Collapse(
- aScanFromCaretPointResult.Point<EditorRawDOMPoint>());
+ aScanFromCaretPointResult.Point_Deprecated<EditorRawDOMPoint>());
if (MOZ_UNLIKELY(NS_FAILED(rv))) {
NS_WARNING("AutoRangeArray::Collapse() failed");
return NS_ERROR_FAILURE;
@@ -1826,7 +1901,7 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAroundCollapsedRanges(
}
if (aScanFromCaretPointResult.ReachedOtherBlockElement()) {
- if (NS_WARN_IF(!aScanFromCaretPointResult.GetContent()->IsElement())) {
+ if (NS_WARN_IF(!aScanFromCaretPointResult.ContentIsElement())) {
return NS_ERROR_FAILURE;
}
MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
@@ -1854,10 +1929,9 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAroundCollapsedRanges(
return handled ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION;
}
- if (aScanFromCaretPointResult.ReachedCurrentBlockBoundary()) {
- if (NS_WARN_IF(!aScanFromCaretPointResult.GetContent()->IsElement())) {
- return NS_ERROR_FAILURE;
- }
+ if (aScanFromCaretPointResult.ReachedCurrentBlockBoundary() ||
+ aScanFromCaretPointResult.ReachedInlineEditingHostBoundary()) {
+ MOZ_ASSERT(aScanFromCaretPointResult.ContentIsElement());
MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
bool handled = false;
for (const OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) {
@@ -1865,7 +1939,7 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAroundCollapsedRanges(
if (!joiner.PrepareToDeleteAtCurrentBlockBoundary(
aHTMLEditor, aDirectionAndAmount,
*aScanFromCaretPointResult.ElementPtr(),
- aWSRunScannerAtCaret.ScanStartRef())) {
+ aWSRunScannerAtCaret.ScanStartRef(), aEditingHost)) {
continue;
}
handled = true;
@@ -1904,8 +1978,11 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAroundCollapsedRanges(
if (aScanFromCaretPointResult.InCollapsibleWhiteSpaces() ||
aScanFromCaretPointResult.InNonCollapsibleCharacters() ||
aScanFromCaretPointResult.ReachedPreformattedLineBreak()) {
+ // This means that if aDirectionAndAmount == nsIEditor::eNext, collapse
+ // selection at the found character. Otherwise, collapse selection after
+ // the found character.
nsresult rv = aRangesToDelete.Collapse(
- aScanFromCaretPointResult.Point<EditorRawDOMPoint>());
+ aScanFromCaretPointResult.Point_Deprecated<EditorRawDOMPoint>());
if (NS_FAILED(rv)) {
NS_WARNING("AutoRangeArray::Collapse() failed");
return Err(NS_ERROR_FAILURE);
@@ -1959,13 +2036,16 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAroundCollapsedRanges(
}
if (aScanFromCaretPointResult.InNonCollapsibleCharacters()) {
- if (NS_WARN_IF(!aScanFromCaretPointResult.GetContent()->IsText())) {
+ if (NS_WARN_IF(!aScanFromCaretPointResult.ContentIsText())) {
return Err(NS_ERROR_FAILURE);
}
Result<CaretPoint, nsresult> caretPointOrError =
HandleDeleteCollapsedSelectionAtVisibleChar(
aHTMLEditor, aDirectionAndAmount, aRangesToDelete,
- aScanFromCaretPointResult.Point<EditorDOMPoint>(), aEditingHost);
+ // This means that if aDirectionAndAmount == nsIEditor::eNext,
+ // at the found character. Otherwise, after the found character.
+ aScanFromCaretPointResult.Point_Deprecated<EditorDOMPoint>(),
+ aEditingHost);
if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
NS_WARNING(
"AutoDeleteRangesHandler::"
@@ -2019,7 +2099,7 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAroundCollapsedRanges(
}
if (aScanFromCaretPointResult.ReachedOtherBlockElement()) {
- if (NS_WARN_IF(!aScanFromCaretPointResult.GetContent()->IsElement())) {
+ if (NS_WARN_IF(!aScanFromCaretPointResult.ContentIsElement())) {
return Err(NS_ERROR_FAILURE);
}
MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
@@ -2049,10 +2129,9 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAroundCollapsedRanges(
: std::move(ret);
}
- if (aScanFromCaretPointResult.ReachedCurrentBlockBoundary()) {
- if (NS_WARN_IF(!aScanFromCaretPointResult.GetContent()->IsElement())) {
- return Err(NS_ERROR_FAILURE);
- }
+ if (aScanFromCaretPointResult.ReachedCurrentBlockBoundary() ||
+ aScanFromCaretPointResult.ReachedInlineEditingHostBoundary()) {
+ MOZ_ASSERT(aScanFromCaretPointResult.ContentIsElement());
MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
bool allRangesNotHandled = true;
auto ret = EditActionResult::IgnoredResult();
@@ -2061,7 +2140,7 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAroundCollapsedRanges(
if (!joiner.PrepareToDeleteAtCurrentBlockBoundary(
aHTMLEditor, aDirectionAndAmount,
*aScanFromCaretPointResult.ElementPtr(),
- aWSRunScannerAtCaret.ScanStartRef())) {
+ aWSRunScannerAtCaret.ScanStartRef(), aEditingHost)) {
continue;
}
allRangesNotHandled = false;
@@ -2484,6 +2563,109 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAtomicContent(
return CaretPoint(std::move(pointToPutCaret));
}
+// static
+Result<bool, nsresult> HTMLEditor::AutoDeleteRangesHandler::
+ ExtendRangeToContainAncestorInlineElementsAtStart(
+ nsRange& aRangeToDelete, const Element& aEditingHost) {
+ MOZ_ASSERT(aRangeToDelete.IsPositioned());
+ MOZ_ASSERT(aRangeToDelete.GetCommonAncestorContainer(IgnoreErrors()));
+ MOZ_ASSERT(aRangeToDelete.GetCommonAncestorContainer(IgnoreErrors())
+ ->IsInclusiveDescendantOf(&aEditingHost));
+
+ EditorRawDOMPoint startPoint(aRangeToDelete.StartRef());
+ if (startPoint.IsInTextNode()) {
+ if (!startPoint.IsStartOfContainer()) {
+ // FIXME: If before the point has only collapsible white-spaces and the
+ // text node follows a block boundary, we should treat the range start
+ // from start of the text node.
+ return true;
+ }
+ startPoint.Set(startPoint.ContainerAs<Text>());
+ if (NS_WARN_IF(!startPoint.IsSet())) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ if (startPoint.GetContainer() == &aEditingHost) {
+ return false;
+ }
+ } else if (startPoint.IsInDataNode()) {
+ startPoint.Set(startPoint.ContainerAs<nsIContent>());
+ if (NS_WARN_IF(!startPoint.IsSet())) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ if (startPoint.GetContainer() == &aEditingHost) {
+ return false;
+ }
+ } else if (startPoint.GetContainer() == &aEditingHost) {
+ return false;
+ }
+
+ // FYI: This method is designed for deleting inline elements which become
+ // empty if aRangeToDelete which crosses a block boundary of right block
+ // child. Therefore, you may need to improve this method if you want to use
+ // this in the other cases.
+
+ nsINode* const commonAncestor =
+ nsContentUtils::GetClosestCommonInclusiveAncestor(
+ startPoint.GetContainer(), aRangeToDelete.GetEndContainer());
+ if (NS_WARN_IF(!commonAncestor)) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ MOZ_ASSERT(commonAncestor->IsInclusiveDescendantOf(&aEditingHost));
+
+ EditorRawDOMPoint newStartPoint(startPoint);
+ while (newStartPoint.GetContainer() != &aEditingHost &&
+ newStartPoint.GetContainer() != commonAncestor) {
+ if (NS_WARN_IF(!newStartPoint.IsInContentNode())) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ if (!HTMLEditUtils::IsInlineContent(
+ *newStartPoint.ContainerAs<nsIContent>(),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
+ break;
+ }
+ // The container is inline, check whether the point is first visible point
+ // or not to consider whether climbing up the tree.
+ bool foundVisiblePrevSibling = false;
+ for (nsIContent* content = newStartPoint.GetPreviousSiblingOfChild();
+ content; content = content->GetPreviousSibling()) {
+ if (Text* text = Text::FromNode(content)) {
+ if (HTMLEditUtils::IsVisibleTextNode(*text)) {
+ foundVisiblePrevSibling = true;
+ break;
+ }
+ // The text node is invisible.
+ } else if (content->IsComment()) {
+ // Ignore the comment node.
+ } else if (!HTMLEditUtils::IsInlineContent(
+ *content,
+ BlockInlineCheck::UseComputedDisplayOutsideStyle) ||
+ !HTMLEditUtils::IsEmptyNode(
+ *content,
+ {EmptyCheckOption::TreatSingleBRElementAsVisible})) {
+ foundVisiblePrevSibling = true;
+ break;
+ }
+ }
+ if (foundVisiblePrevSibling) {
+ break;
+ }
+ // the point can be treated as start of the parent inline now.
+ newStartPoint.Set(newStartPoint.ContainerAs<nsIContent>());
+ if (NS_WARN_IF(!newStartPoint.IsSet())) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ }
+ if (newStartPoint == startPoint) {
+ return false; // Don't need to modify the range
+ }
+ IgnoredErrorResult error;
+ aRangeToDelete.SetStart(newStartPoint.ToRawRangeBoundary(), error);
+ if (MOZ_UNLIKELY(error.Failed())) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ return true;
+}
+
bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
PrepareToDeleteAtOtherBlockBoundary(
const HTMLEditor& aHTMLEditor,
@@ -2519,12 +2701,12 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
// Next to a block. See if we are between the block and a `<br>`.
// If so, we really want to delete the `<br>`. Else join content at
// selection to the block.
- WSScanResult scanFromCaretResult =
+ const WSScanResult scanFromCaretResult =
aDirectionAndAmount == nsIEditor::eNext
? aWSRunScannerAtCaret.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
aCaretPoint)
- : aWSRunScannerAtCaret.ScanNextVisibleNodeOrBlockBoundaryFrom(
- aCaretPoint);
+ : aWSRunScannerAtCaret
+ .ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(aCaretPoint);
// If we found a `<br>` element, we need to delete it instead of joining the
// contents.
if (scanFromCaretResult.ReachedBRElement()) {
@@ -2537,58 +2719,159 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
}
nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
- ComputeRangeToDeleteBRElement(nsRange& aRangeToDelete) const {
- MOZ_ASSERT(mBRElement);
- // XXX Why don't we scan invisible leading white-spaces which follows the
- // `<br>` element?
+ ComputeRangeToDeleteLineBreak(const HTMLEditor& aHTMLEditor,
+ nsRange& aRangeToDelete,
+ const Element& aEditingHost,
+ ComputeRangeFor aComputeRangeFor) const {
+ // FIXME: Scan invisible leading white-spaces after the <br>.
+ MOZ_ASSERT_IF(mMode == Mode::DeleteBRElement, mBRElement);
+ MOZ_ASSERT_IF(mMode == Mode::DeletePrecedingBRElementOfBlock, mBRElement);
+ MOZ_ASSERT_IF(mMode == Mode::DeletePrecedingPreformattedLineBreak,
+ mPreformattedLineBreak.IsSetAndValid());
+ MOZ_ASSERT_IF(mMode == Mode::DeletePrecedingPreformattedLineBreak,
+ mPreformattedLineBreak.IsCharPreformattedNewLine());
+ MOZ_ASSERT_IF(aComputeRangeFor == ComputeRangeFor::GetTargetRanges,
+ aRangeToDelete.IsPositioned());
+
+ // If we're computing for beforeinput.getTargetRanges() and the inputType
+ // is not a simple deletion like replacing selected content with new
+ // content, the range should end at the original end boundary of the given
+ // range.
+ const bool preserveEndBoundary =
+ (mMode == Mode::DeletePrecedingBRElementOfBlock ||
+ mMode == Mode::DeletePrecedingPreformattedLineBreak) &&
+ aComputeRangeFor == ComputeRangeFor::GetTargetRanges &&
+ !MayEditActionDeleteAroundCollapsedSelection(aHTMLEditor.GetEditAction());
+
+ if (mMode != Mode::DeletePrecedingPreformattedLineBreak) {
+ Element* const mostDistantInlineAncestor =
+ HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
+ *mBRElement, BlockInlineCheck::UseComputedDisplayOutsideStyle,
+ &aEditingHost);
+ if (preserveEndBoundary) {
+ // FIXME: If the range ends at end of an inline element, we may need to
+ // extend the range.
+ IgnoredErrorResult error;
+ aRangeToDelete.SetStart(EditorRawDOMPoint(mostDistantInlineAncestor
+ ? mostDistantInlineAncestor
+ : mBRElement)
+ .ToRawRangeBoundary(),
+ error);
+ NS_WARNING_ASSERTION(!error.Failed(), "nsRange::SetStart() failed");
+ MOZ_ASSERT_IF(!error.Failed(), !aRangeToDelete.Collapsed());
+ return error.StealNSResult();
+ }
+ IgnoredErrorResult error;
+ aRangeToDelete.SelectNode(
+ mostDistantInlineAncestor ? *mostDistantInlineAncestor : *mBRElement,
+ error);
+ NS_WARNING_ASSERTION(!error.Failed(), "nsRange::SelectNode() failed");
+ return error.StealNSResult();
+ }
+
+ Element* const mostDistantInlineAncestor =
+ mPreformattedLineBreak.ContainerAs<Text>()->TextDataLength() == 1
+ ? HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
+ *mPreformattedLineBreak.ContainerAs<Text>(),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost)
+ : nullptr;
+
+ if (!mostDistantInlineAncestor) {
+ if (preserveEndBoundary) {
+ // FIXME: If the range ends at end of an inline element, we may need to
+ // extend the range.
+ IgnoredErrorResult error;
+ aRangeToDelete.SetStart(mPreformattedLineBreak.ToRawRangeBoundary(),
+ error);
+ MOZ_ASSERT_IF(!error.Failed(), !aRangeToDelete.Collapsed());
+ NS_WARNING_ASSERTION(!error.Failed(), "nsRange::SetStart() failed");
+ return error.StealNSResult();
+ }
+ nsresult rv = aRangeToDelete.SetStartAndEnd(
+ mPreformattedLineBreak.ToRawRangeBoundary(),
+ mPreformattedLineBreak.NextPoint().ToRawRangeBoundary());
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::SetStartAndEnd() failed");
+ return rv;
+ }
+
+ if (preserveEndBoundary) {
+ // FIXME: If the range ends at end of an inline element, we may need to
+ // extend the range.
+ IgnoredErrorResult error;
+ aRangeToDelete.SetStart(
+ EditorRawDOMPoint(mostDistantInlineAncestor).ToRawRangeBoundary(),
+ error);
+ MOZ_ASSERT_IF(!error.Failed(), !aRangeToDelete.Collapsed());
+ NS_WARNING_ASSERTION(!error.Failed(), "nsRange::SetStart() failed");
+ return error.StealNSResult();
+ }
+
IgnoredErrorResult error;
- aRangeToDelete.SelectNode(*mBRElement, error);
+ aRangeToDelete.SelectNode(*mostDistantInlineAncestor, error);
NS_WARNING_ASSERTION(!error.Failed(), "nsRange::SelectNode() failed");
return error.StealNSResult();
}
-Result<EditActionResult, nsresult>
-HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::DeleteBRElement(
- HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
- const Element& aEditingHost) {
+Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
+ AutoBlockElementsJoiner::HandleDeleteLineBreak(
+ HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
+ const EditorDOMPoint& aCaretPoint, const Element& aEditingHost) {
MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
- MOZ_ASSERT(mBRElement);
+ MOZ_ASSERT(mBRElement || mPreformattedLineBreak.IsSet());
// If we're deleting selection (not replacing with new content), we should
// put caret to end of preceding text node if there is. Then, users can type
// text in it like the other browsers.
EditorDOMPoint pointToPutCaret = [&]() {
+ // but when we're deleting a preceding line break of current block, we
+ // should keep the caret position in the current block.
+ if (mMode == Mode::DeletePrecedingBRElementOfBlock ||
+ mMode == Mode::DeletePrecedingPreformattedLineBreak) {
+ return aCaretPoint;
+ }
if (!MayEditActionDeleteAroundCollapsedSelection(
aHTMLEditor.GetEditAction())) {
return EditorDOMPoint();
}
WSRunScanner scanner(&aEditingHost, EditorRawDOMPoint(mBRElement),
BlockInlineCheck::UseComputedDisplayOutsideStyle);
- WSScanResult maybePreviousText =
+ const WSScanResult maybePreviousText =
scanner.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
EditorRawDOMPoint(mBRElement));
if (maybePreviousText.IsContentEditable() &&
maybePreviousText.InVisibleOrCollapsibleCharacters() &&
!HTMLEditor::GetLinkElement(maybePreviousText.TextPtr())) {
- return maybePreviousText.Point<EditorDOMPoint>();
+ return maybePreviousText.PointAfterReachedContent<EditorDOMPoint>();
}
- WSScanResult maybeNextText = scanner.ScanNextVisibleNodeOrBlockBoundaryFrom(
- EditorRawDOMPoint::After(*mBRElement));
+ const WSScanResult maybeNextText =
+ scanner.ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
+ EditorRawDOMPoint::After(*mBRElement));
if (maybeNextText.IsContentEditable() &&
maybeNextText.InVisibleOrCollapsibleCharacters()) {
- return maybeNextText.Point<EditorDOMPoint>();
+ return maybeNextText.PointAtReachedContent<EditorDOMPoint>();
}
return EditorDOMPoint();
}();
- // If we found a `<br>` element, we should delete it instead of joining the
- // contents.
+ RefPtr<nsRange> rangeToDelete =
+ nsRange::Create(const_cast<Element*>(&aEditingHost));
+ MOZ_ASSERT(rangeToDelete);
nsresult rv =
- aHTMLEditor.DeleteNodeWithTransaction(MOZ_KnownLive(*mBRElement));
+ ComputeRangeToDeleteLineBreak(aHTMLEditor, *rangeToDelete, aEditingHost,
+ ComputeRangeFor::ToDeleteTheRange);
if (NS_FAILED(rv)) {
- NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
+ NS_WARNING(
+ "AutoBlockElementsJoiner::ComputeRangeToDeleteLineBreak() failed");
return Err(rv);
}
+ Result<EditActionResult, nsresult> result = HandleDeleteNonCollapsedRange(
+ aHTMLEditor, aDirectionAndAmount, nsIEditor::eNoStrip, *rangeToDelete,
+ SelectionWasCollapsed::Yes, aEditingHost);
+ if (MOZ_UNLIKELY(result.isErr())) {
+ NS_WARNING(
+ "AutoBlockElementsJoiner::HandleDeleteNonCollapsedRange() failed");
+ return result;
+ }
if (mLeftContent && mRightContent &&
HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mLeftContent) !=
@@ -2597,7 +2880,7 @@ HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::DeleteBRElement(
}
// Put selection at edge of block and we are done.
- if (NS_WARN_IF(!mLeafContentInOtherBlock)) {
+ if (NS_WARN_IF(mMode == Mode::DeleteBRElement && !mLeafContentInOtherBlock)) {
// XXX This must be odd case. The other block can be empty.
return Err(NS_ERROR_FAILURE);
}
@@ -2607,7 +2890,7 @@ HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::DeleteBRElement(
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
return Err(NS_ERROR_EDITOR_DESTROYED);
}
- if (NS_SUCCEEDED(rv)) {
+ if (mMode == Mode::DeleteBRElement && NS_SUCCEEDED(rv)) {
// If we prefer to use style in the previous line, we should forget
// previous styles since the caret position has all styles which we want
// to use with new content.
@@ -2622,7 +2905,9 @@ HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::DeleteBRElement(
->ClearLinkAndItsSpecifiedStyle();
}
} else {
- NS_WARNING("EditorBase::CollapseSelectionTo() failed, but ignored");
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "EditorBase::CollapseSelectionTo() failed, but ignored");
}
return EditActionResult::HandledResult();
}
@@ -2937,7 +3222,8 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
PrepareToDeleteAtCurrentBlockBoundary(
const HTMLEditor& aHTMLEditor,
nsIEditor::EDirection aDirectionAndAmount,
- Element& aCurrentBlockElement, const EditorDOMPoint& aCaretPoint) {
+ Element& aCurrentBlockElement, const EditorDOMPoint& aCaretPoint,
+ const Element& aEditingHost) {
MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
// At edge of our block. Look beside it and see if we can join to an
@@ -2956,20 +3242,15 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
return false;
}
- Element* editingHost = aHTMLEditor.ComputeEditingHost();
- if (NS_WARN_IF(!editingHost)) {
- return false;
- }
-
auto ScanJoinTarget = [&]() -> nsIContent* {
nsIContent* targetContent =
aDirectionAndAmount == nsIEditor::ePrevious
? HTMLEditUtils::GetPreviousContent(
aCurrentBlockElement, {WalkTreeOption::IgnoreNonEditableNode},
- BlockInlineCheck::Unused, editingHost)
+ BlockInlineCheck::Unused, &aEditingHost)
: HTMLEditUtils::GetNextContent(
aCurrentBlockElement, {WalkTreeOption::IgnoreNonEditableNode},
- BlockInlineCheck::Unused, editingHost);
+ BlockInlineCheck::Unused, &aEditingHost);
// If found content is an invisible text node, let's scan visible things.
auto IsIgnorableDataNode = [](nsIContent* aContent) {
return aContent && HTMLEditUtils::IsRemovableNode(*aContent) &&
@@ -2987,22 +3268,22 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
? HTMLEditUtils::GetPreviousContent(
*targetContent, {WalkTreeOption::StopAtBlockBoundary},
BlockInlineCheck::UseComputedDisplayOutsideStyle,
- editingHost)
+ &aEditingHost)
: HTMLEditUtils::GetNextContent(
*targetContent, {WalkTreeOption::StopAtBlockBoundary},
BlockInlineCheck::UseComputedDisplayOutsideStyle,
- editingHost);
+ &aEditingHost);
adjacentContent;
adjacentContent =
aDirectionAndAmount == nsIEditor::ePrevious
? HTMLEditUtils::GetPreviousContent(
*adjacentContent, {WalkTreeOption::StopAtBlockBoundary},
BlockInlineCheck::UseComputedDisplayOutsideStyle,
- editingHost)
+ &aEditingHost)
: HTMLEditUtils::GetNextContent(
*adjacentContent, {WalkTreeOption::StopAtBlockBoundary},
BlockInlineCheck::UseComputedDisplayOutsideStyle,
- editingHost)) {
+ &aEditingHost)) {
// If non-editable element is found, we should not skip it to avoid
// joining too far nodes.
if (!HTMLEditUtils::IsSimplyEditableNode(*adjacentContent)) {
@@ -3036,6 +3317,77 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
};
if (aDirectionAndAmount == nsIEditor::ePrevious) {
+ const WSScanResult prevVisibleThing = [&]() {
+ // When Backspace at start of a block, we need to delete only a preceding
+ // <br> element if there is.
+ const Result<Element*, nsresult>
+ inclusiveAncestorOfRightChildBlockOrError = AutoBlockElementsJoiner::
+ GetMostDistantBlockAncestorIfPointIsStartAtBlock(aCaretPoint,
+ aEditingHost);
+ if (NS_WARN_IF(inclusiveAncestorOfRightChildBlockOrError.isErr()) ||
+ !inclusiveAncestorOfRightChildBlockOrError.inspect()) {
+ return WSScanResult::Error();
+ }
+ const WSScanResult prevVisibleThingBeforeCurrentBlock =
+ WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
+ &aEditingHost,
+ EditorRawDOMPoint(
+ inclusiveAncestorOfRightChildBlockOrError.inspect()),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ if (!prevVisibleThingBeforeCurrentBlock.ReachedBRElement() &&
+ !prevVisibleThingBeforeCurrentBlock.ReachedPreformattedLineBreak()) {
+ return WSScanResult::Error();
+ }
+ // There is a preceding line break, but it may be invisible. Then, users
+ // want to delete its preceding content not only the line break.
+ // Therefore, let's check whether the line break follows another line
+ // break or a block boundary. In these cases, the line break causes an
+ // empty line which users may want to delete.
+ const auto atPrecedingLineBreak =
+ prevVisibleThingBeforeCurrentBlock
+ .PointAtReachedContent<EditorRawDOMPoint>();
+ MOZ_ASSERT(atPrecedingLineBreak.IsSet());
+ const WSScanResult prevVisibleThingBeforeLineBreak =
+ WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
+ &aEditingHost, atPrecedingLineBreak,
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ if (prevVisibleThingBeforeLineBreak.ReachedBRElement() ||
+ prevVisibleThingBeforeLineBreak.ReachedPreformattedLineBreak() ||
+ prevVisibleThingBeforeLineBreak.ReachedCurrentBlockBoundary()) {
+ // Target the latter line break for things simpler. It's easier to
+ // compute the target range.
+ MOZ_ASSERT_IF(
+ prevVisibleThingBeforeCurrentBlock.ReachedPreformattedLineBreak() &&
+ prevVisibleThingBeforeLineBreak.ReachedPreformattedLineBreak(),
+ prevVisibleThingBeforeCurrentBlock
+ .PointAtReachedContent<EditorRawDOMPoint>() !=
+ prevVisibleThingBeforeLineBreak
+ .PointAtReachedContent<EditorRawDOMPoint>());
+ return prevVisibleThingBeforeCurrentBlock;
+ }
+ return WSScanResult::Error();
+ }();
+
+ // If previous visible thing is a <br>, we should just delete it without
+ // unwrapping the first line of the right child block. Note that the <br>
+ // is always treated as invisible by HTMLEditUtils because it's immediately
+ // preceding <br> of the block boundary. However, deleting it is fine
+ // because the above checks whether it causes empty line or not.
+ if (prevVisibleThing.ReachedBRElement()) {
+ mMode = Mode::DeletePrecedingBRElementOfBlock;
+ mBRElement = prevVisibleThing.BRElementPtr();
+ return true;
+ }
+
+ // Same for a preformatted line break.
+ if (prevVisibleThing.ReachedPreformattedLineBreak()) {
+ mMode = Mode::DeletePrecedingPreformattedLineBreak;
+ mPreformattedLineBreak =
+ prevVisibleThing.PointAtReachedContent<EditorRawDOMPoint>()
+ .AsInText();
+ return true;
+ }
+
mLeftContent = ScanJoinTarget();
mRightContent = aCaretPoint.GetContainerAs<nsIContent>();
} else {
@@ -3283,7 +3635,8 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteNonCollapsedRanges(
continue;
}
AutoBlockElementsJoiner joiner(*this);
- if (!joiner.PrepareToDeleteNonCollapsedRange(aHTMLEditor, range)) {
+ if (!joiner.PrepareToDeleteNonCollapsedRange(aHTMLEditor, range,
+ aEditingHost)) {
return NS_ERROR_FAILURE;
}
nsresult rv =
@@ -3473,7 +3826,8 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteNonCollapsedRanges(
continue;
}
AutoBlockElementsJoiner joiner(*this);
- if (!joiner.PrepareToDeleteNonCollapsedRange(aHTMLEditor, range)) {
+ if (!joiner.PrepareToDeleteNonCollapsedRange(aHTMLEditor, range,
+ aEditingHost)) {
return Err(NS_ERROR_FAILURE);
}
Result<EditActionResult, nsresult> result =
@@ -3490,7 +3844,8 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteNonCollapsedRanges(
bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
PrepareToDeleteNonCollapsedRange(const HTMLEditor& aHTMLEditor,
- const nsRange& aRangeToDelete) {
+ const nsRange& aRangeToDelete,
+ const Element& aEditingHost) {
MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
MOZ_ASSERT(!aRangeToDelete.Collapsed());
@@ -3528,6 +3883,125 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
return true;
}
+ // If the range starts immediately after a line end and ends in a
+ // child right block, we should not unwrap the right block unless the
+ // right block will have no nodes.
+ if (mRightContent->IsInclusiveDescendantOf(mLeftContent)) {
+ // FYI: Chrome does not remove the right child block even if there will be
+ // only single <br> or a comment node in it. Therefore, we should use this
+ // rough check.
+ const WSScanResult nextVisibleThingOfEndBoundary =
+ WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
+ &aEditingHost, EditorRawDOMPoint(aRangeToDelete.EndRef()),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ if (!nextVisibleThingOfEndBoundary.ReachedCurrentBlockBoundary()) {
+ MOZ_ASSERT(mLeftContent->IsElement());
+ Result<Element*, nsresult> mostDistantBlockOrError =
+ AutoBlockElementsJoiner::
+ GetMostDistantBlockAncestorIfPointIsStartAtBlock(
+ EditorRawDOMPoint(mRightContent, 0), aEditingHost,
+ mLeftContent->AsElement());
+ MOZ_ASSERT(mostDistantBlockOrError.isOk());
+ if (MOZ_LIKELY(mostDistantBlockOrError.inspect())) {
+ const WSScanResult prevVisibleThingOfStartBoundary =
+ WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
+ &aEditingHost, EditorRawDOMPoint(aRangeToDelete.StartRef()),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ if (prevVisibleThingOfStartBoundary.ReachedBRElement()) {
+ // If the range start after a <br> followed by the block boundary,
+ // we want to delete the <br> or following <br> element unless it's
+ // not a part of empty line like `<div>abc<br>{<div>]def`.
+ const WSScanResult nextVisibleThingOfBR =
+ WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
+ &aEditingHost,
+ EditorRawDOMPoint::After(
+ *prevVisibleThingOfStartBoundary.GetContent()),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ MOZ_ASSERT(!nextVisibleThingOfBR.ReachedCurrentBlockBoundary());
+ if (!nextVisibleThingOfBR.ReachedOtherBlockElement() ||
+ nextVisibleThingOfBR.GetContent() !=
+ mostDistantBlockOrError.inspect()) {
+ // The range selects a non-empty line or a child block at least.
+ mMode = Mode::DeletePrecedingLinesAndContentInRange;
+ return true;
+ }
+ const WSScanResult prevVisibleThingOfBR =
+ WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
+ &aEditingHost,
+ EditorRawDOMPoint(
+ prevVisibleThingOfStartBoundary.GetContent()),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ if (prevVisibleThingOfBR.ReachedBRElement() ||
+ prevVisibleThingOfBR.ReachedPreformattedLineBreak() ||
+ prevVisibleThingOfBR.ReachedBlockBoundary()) {
+ // The preceding <br> causes an empty line.
+ mMode = Mode::DeletePrecedingLinesAndContentInRange;
+ return true;
+ }
+ } else if (prevVisibleThingOfStartBoundary
+ .ReachedPreformattedLineBreak()) {
+ const WSScanResult nextVisibleThingOfLineBreak =
+ WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
+ &aEditingHost,
+ prevVisibleThingOfStartBoundary
+ .PointAfterReachedContent<EditorRawDOMPoint>(),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ MOZ_ASSERT(
+ !nextVisibleThingOfLineBreak.ReachedCurrentBlockBoundary());
+ if (!nextVisibleThingOfLineBreak.ReachedOtherBlockElement() ||
+ nextVisibleThingOfLineBreak.GetContent() !=
+ mostDistantBlockOrError.inspect()) {
+ // The range selects a non-empty line or a child block at least.
+ mMode = Mode::DeletePrecedingLinesAndContentInRange;
+ return true;
+ }
+ const WSScanResult prevVisibleThingOfLineBreak =
+ WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
+ &aEditingHost,
+ prevVisibleThingOfStartBoundary
+ .PointAtReachedContent<EditorRawDOMPoint>(),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ if (prevVisibleThingOfLineBreak.ReachedBRElement() ||
+ prevVisibleThingOfLineBreak.ReachedPreformattedLineBreak() ||
+ prevVisibleThingOfLineBreak.ReachedBlockBoundary()) {
+ // The preceding line break causes an empty line.
+ mMode = Mode::DeletePrecedingLinesAndContentInRange;
+ return true;
+ }
+ } else if (prevVisibleThingOfStartBoundary
+ .ReachedCurrentBlockBoundary()) {
+ MOZ_ASSERT(prevVisibleThingOfStartBoundary.ElementPtr() ==
+ mLeftContent);
+ const WSScanResult firstVisibleThingInBlock =
+ WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
+ &aEditingHost,
+ EditorRawDOMPoint(
+ prevVisibleThingOfStartBoundary.ElementPtr(), 0),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ if (!firstVisibleThingInBlock.ReachedOtherBlockElement() ||
+ firstVisibleThingInBlock.ElementPtr() !=
+ mostDistantBlockOrError.inspect()) {
+ mMode = Mode::DeletePrecedingLinesAndContentInRange;
+ return true;
+ }
+ } else if (prevVisibleThingOfStartBoundary.ReachedOtherBlockElement()) {
+ const WSScanResult firstVisibleThingAfterBlock =
+ WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
+ &aEditingHost,
+ EditorRawDOMPoint::After(
+ *prevVisibleThingOfStartBoundary.ElementPtr()),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ if (!firstVisibleThingAfterBlock.ReachedOtherBlockElement() ||
+ firstVisibleThingAfterBlock.ElementPtr() !=
+ mostDistantBlockOrError.inspect()) {
+ mMode = Mode::DeletePrecedingLinesAndContentInRange;
+ return true;
+ }
+ }
+ }
+ }
+ }
+
mMode = Mode::DeleteNonCollapsedRange;
return true;
}
@@ -3744,12 +4218,12 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
aHTMLEditor.GetEditAction())) {
WSRunScanner scanner(&aEditingHost, startOfRightContent,
BlockInlineCheck::UseComputedDisplayOutsideStyle);
- WSScanResult maybePreviousText =
+ const WSScanResult maybePreviousText =
scanner.ScanPreviousVisibleNodeOrBlockBoundaryFrom(startOfRightContent);
if (maybePreviousText.IsContentEditable() &&
maybePreviousText.InVisibleOrCollapsibleCharacters()) {
nsresult rv = aHTMLEditor.CollapseSelectionTo(
- maybePreviousText.Point<EditorRawDOMPoint>());
+ maybePreviousText.PointAfterReachedContent<EditorRawDOMPoint>());
if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::CollapseSelectionTo() failed");
return Err(rv);
@@ -3851,6 +4325,16 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed)
const {
+ switch (mMode) {
+ case Mode::DeletePrecedingLinesAndContentInRange:
+ case Mode::DeleteBRElement:
+ case Mode::DeletePrecedingBRElementOfBlock:
+ case Mode::DeletePrecedingPreformattedLineBreak:
+ return false;
+ default:
+ break;
+ }
+
// If original selection was collapsed, we need always to join the nodes.
// XXX Why?
if (aSelectionWasCollapsed ==
@@ -3890,41 +4374,103 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
DeleteTextAtStartAndEndOfRange(HTMLEditor& aHTMLEditor, nsRange& aRange) {
EditorDOMPoint rangeStart(aRange.StartRef());
EditorDOMPoint rangeEnd(aRange.EndRef());
- if (rangeStart.IsInTextNode() && !rangeStart.IsEndOfContainer()) {
- // Delete to last character
+ if (MOZ_UNLIKELY(aRange.Collapsed())) {
+ return NS_OK;
+ }
+
+ EditorDOMPoint pointToPutCaret;
+ // If the range is in a text node, delete middle of the text or the text node
+ // itself.
+ if (rangeStart.IsInTextNode() &&
+ rangeStart.ContainerAs<Text>() == rangeEnd.GetContainer()) {
OwningNonNull<Text> textNode = *rangeStart.ContainerAs<Text>();
- Result<CaretPoint, nsresult> caretPointOrError =
- aHTMLEditor.DeleteTextWithTransaction(
- textNode, rangeStart.Offset(),
- rangeStart.GetContainer()->Length() - rangeStart.Offset());
- if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
- NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
- return caretPointOrError.unwrapErr();
+ if (rangeStart.IsStartOfContainer() && rangeEnd.IsEndOfContainer()) {
+ EditorDOMPoint pointToPutCaret(textNode);
+ AutoTrackDOMPoint trackTextNodePoint(aHTMLEditor.RangeUpdaterRef(),
+ &pointToPutCaret);
+ nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(textNode);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
+ return rv;
+ }
+ } else {
+ MOZ_ASSERT(rangeEnd.Offset() - rangeStart.Offset() > 0);
+ Result<CaretPoint, nsresult> caretPointOrError =
+ aHTMLEditor.DeleteTextWithTransaction(
+ textNode, rangeStart.Offset(),
+ rangeEnd.Offset() - rangeStart.Offset());
+ if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
+ NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
+ return caretPointOrError.unwrapErr();
+ }
+ caretPointOrError.unwrap().MoveCaretPointTo(
+ pointToPutCaret, aHTMLEditor,
+ {SuggestCaret::OnlyIfHasSuggestion,
+ SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
}
- nsresult rv = caretPointOrError.inspect().SuggestCaretPointTo(
- aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
- SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
- SuggestCaret::AndIgnoreTrivialError});
- if (NS_FAILED(rv)) {
- NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
- return rv;
+ } else {
+ // If the range starts in a text node and ends in a different node, delete
+ // the text after the start boundary.
+ if (rangeStart.IsInTextNode() && !rangeStart.IsEndOfContainer()) {
+ OwningNonNull<Text> textNode = *rangeStart.ContainerAs<Text>();
+ if (rangeStart.IsStartOfContainer()) {
+ pointToPutCaret.Set(textNode);
+ AutoTrackDOMPoint trackTextNodePoint(aHTMLEditor.RangeUpdaterRef(),
+ &pointToPutCaret);
+ nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(textNode);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
+ return rv;
+ }
+ } else {
+ Result<CaretPoint, nsresult> caretPointOrError =
+ aHTMLEditor.DeleteTextWithTransaction(
+ textNode, rangeStart.Offset(),
+ rangeStart.GetContainer()->Length() - rangeStart.Offset());
+ if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
+ NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
+ return caretPointOrError.unwrapErr();
+ }
+ caretPointOrError.unwrap().MoveCaretPointTo(
+ pointToPutCaret, aHTMLEditor,
+ {SuggestCaret::OnlyIfHasSuggestion,
+ SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
+ }
}
- NS_WARNING_ASSERTION(
- rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
- "CaretPoint::SuggestCaretPointTo() failed, but ignored");
- }
- if (rangeEnd.IsInTextNode() && !rangeEnd.IsStartOfContainer()) {
- // Delete to first character
- OwningNonNull<Text> textNode = *rangeEnd.ContainerAs<Text>();
- Result<CaretPoint, nsresult> caretPointOrError =
- aHTMLEditor.DeleteTextWithTransaction(textNode, 0, rangeEnd.Offset());
- if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
- NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
- return caretPointOrError.unwrapErr();
+
+ // If the range ends in a text node and starts from a different node, delete
+ // the text before the end boundary.
+ if (rangeEnd.IsInTextNode() && !rangeEnd.IsStartOfContainer()) {
+ OwningNonNull<Text> textNode = *rangeEnd.ContainerAs<Text>();
+ if (rangeEnd.IsEndOfContainer()) {
+ pointToPutCaret.Set(textNode);
+ AutoTrackDOMPoint trackTextNodePoint(aHTMLEditor.RangeUpdaterRef(),
+ &pointToPutCaret);
+ nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(textNode);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
+ return rv;
+ }
+ } else {
+ Result<CaretPoint, nsresult> caretPointOrError =
+ aHTMLEditor.DeleteTextWithTransaction(textNode, 0,
+ rangeEnd.Offset());
+ if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
+ NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
+ return caretPointOrError.unwrapErr();
+ }
+ caretPointOrError.unwrap().MoveCaretPointTo(
+ pointToPutCaret, aHTMLEditor,
+ {SuggestCaret::OnlyIfHasSuggestion,
+ SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
+ }
}
- nsresult rv = caretPointOrError.inspect().SuggestCaretPointTo(
- aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
- SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
+ }
+
+ if (pointToPutCaret.IsSet()) {
+ CaretPoint caretPoint(std::move(pointToPutCaret));
+ nsresult rv = caretPoint.SuggestCaretPointTo(
+ aHTMLEditor, {SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
SuggestCaret::AndIgnoreTrivialError});
if (NS_FAILED(rv)) {
NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
@@ -3937,6 +4483,201 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
return NS_OK;
}
+// static
+template <typename EditorDOMPointType>
+Result<Element*, nsresult> HTMLEditor::AutoDeleteRangesHandler::
+ AutoBlockElementsJoiner::GetMostDistantBlockAncestorIfPointIsStartAtBlock(
+ const EditorDOMPointType& aPoint, const Element& aEditingHost,
+ const Element* aAncestorLimiter /* = nullptr */) {
+ MOZ_ASSERT(aPoint.IsSetAndValid());
+ MOZ_ASSERT(aPoint.IsInComposedDoc());
+
+ if (!aAncestorLimiter) {
+ aAncestorLimiter = &aEditingHost;
+ }
+
+ const auto ReachedCurrentBlockBoundaryWhichWeCanCross =
+ [&aEditingHost, aAncestorLimiter](const WSScanResult& aScanResult) {
+ // When the scan result is "reached current block boundary", it may not
+ // be so literally.
+ return aScanResult.ReachedCurrentBlockBoundary() &&
+ HTMLEditUtils::IsRemovableFromParentNode(
+ *aScanResult.ElementPtr()) &&
+ aScanResult.ElementPtr() != &aEditingHost &&
+ aScanResult.ElementPtr() != aAncestorLimiter &&
+ // Don't cross <body>, <head> and <html>
+ !aScanResult.ElementPtr()->IsAnyOfHTMLElements(
+ nsGkAtoms::body, nsGkAtoms::head, nsGkAtoms::html) &&
+ // Don't cross table elements
+ !HTMLEditUtils::IsAnyTableElement(aScanResult.ElementPtr());
+ };
+
+ const WSScanResult prevVisibleThing =
+ WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
+ aAncestorLimiter, aPoint,
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ if (!ReachedCurrentBlockBoundaryWhichWeCanCross(prevVisibleThing)) {
+ return nullptr;
+ }
+ MOZ_ASSERT(HTMLEditUtils::IsBlockElement(
+ *prevVisibleThing.ElementPtr(),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle));
+ for (Element* ancestorBlock = prevVisibleThing.ElementPtr(); ancestorBlock;) {
+ const WSScanResult prevVisibleThing =
+ WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
+ aAncestorLimiter, EditorRawDOMPoint(ancestorBlock),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ if (!ReachedCurrentBlockBoundaryWhichWeCanCross(prevVisibleThing)) {
+ return ancestorBlock;
+ }
+ MOZ_ASSERT(HTMLEditUtils::IsBlockElement(
+ *prevVisibleThing.ElementPtr(),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle));
+ ancestorBlock = prevVisibleThing.ElementPtr();
+ }
+ return Err(NS_ERROR_FAILURE);
+}
+
+void HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
+ ExtendRangeToDeleteNonCollapsedRange(
+ const HTMLEditor& aHTMLEditor, nsRange& aRangeToDelete,
+ const Element& aEditingHost, ComputeRangeFor aComputeRangeFor) const {
+ MOZ_ASSERT_IF(aComputeRangeFor == ComputeRangeFor::GetTargetRanges,
+ aRangeToDelete.IsPositioned());
+ MOZ_ASSERT(!aRangeToDelete.Collapsed());
+ MOZ_ASSERT(mLeftContent);
+ MOZ_ASSERT(mLeftContent->IsElement());
+ MOZ_ASSERT(aRangeToDelete.GetStartContainer()->IsInclusiveDescendantOf(
+ mLeftContent));
+ MOZ_ASSERT(mRightContent);
+ MOZ_ASSERT(mRightContent->IsElement());
+ MOZ_ASSERT(
+ aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(mRightContent));
+
+ const DebugOnly<Result<bool, nsresult>> extendRangeResult =
+ AutoDeleteRangesHandler::
+ ExtendRangeToContainAncestorInlineElementsAtStart(aRangeToDelete,
+ aEditingHost);
+ NS_WARNING_ASSERTION(extendRangeResult.value.isOk(),
+ "AutoDeleteRangesHandler::"
+ "ExtendRangeToContainAncestorInlineElementsAtStart() "
+ "failed, but ignored");
+ if (mMode != Mode::DeletePrecedingLinesAndContentInRange) {
+ return;
+ }
+
+ // If we're computing for beforeinput.getTargetRanges() and the inputType
+ // is not a simple deletion like replacing selected content with new
+ // content, the range should end at the original end boundary of the given
+ // range even if we're deleting only preceding lines of the right child
+ // block.
+ const bool preserveEndBoundary =
+ aComputeRangeFor == ComputeRangeFor::GetTargetRanges &&
+ !MayEditActionDeleteAroundCollapsedSelection(aHTMLEditor.GetEditAction());
+ // We need to delete only the preceding lines of the right block. Therefore,
+ // we need to shrink the range to ends before the right block if the range
+ // does not contain any meaningful content in the right block.
+ const Result<Element*, nsresult> inclusiveAncestorCurrentBlockOrError =
+ AutoBlockElementsJoiner::GetMostDistantBlockAncestorIfPointIsStartAtBlock(
+ EditorRawDOMPoint(aRangeToDelete.EndRef()), aEditingHost,
+ mLeftContent->AsElement());
+ MOZ_ASSERT(inclusiveAncestorCurrentBlockOrError.isOk());
+ MOZ_ASSERT_IF(inclusiveAncestorCurrentBlockOrError.inspect(),
+ mRightContent->IsInclusiveDescendantOf(
+ inclusiveAncestorCurrentBlockOrError.inspect()));
+ if (MOZ_UNLIKELY(!inclusiveAncestorCurrentBlockOrError.isOk() ||
+ !inclusiveAncestorCurrentBlockOrError.inspect())) {
+ return;
+ }
+
+ const WSScanResult prevVisibleThingOfStartBoundary =
+ WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
+ &aEditingHost, EditorRawDOMPoint(aRangeToDelete.StartRef()),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ // If the range starts after an invisible <br> of empty line immediately
+ // before the most distant inclusive ancestor of the right block like
+ // `<br><br>{<div>]abc`, we should delete the last empty line because
+ // users won't see any reaction of the builtin editor in this case.
+ if (prevVisibleThingOfStartBoundary.ReachedBRElement() ||
+ prevVisibleThingOfStartBoundary.ReachedPreformattedLineBreak()) {
+ const WSScanResult prevVisibleThingOfPreviousLineBreak =
+ WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
+ &aEditingHost,
+ prevVisibleThingOfStartBoundary
+ .PointAtReachedContent<EditorRawDOMPoint>(),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ const WSScanResult nextVisibleThingOfPreviousBR =
+ WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
+ &aEditingHost,
+ prevVisibleThingOfStartBoundary
+ .PointAfterReachedContent<EditorRawDOMPoint>(),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ if ((prevVisibleThingOfPreviousLineBreak.ReachedBRElement() ||
+ prevVisibleThingOfPreviousLineBreak.ReachedPreformattedLineBreak()) &&
+ nextVisibleThingOfPreviousBR.ReachedOtherBlockElement() &&
+ nextVisibleThingOfPreviousBR.ElementPtr() ==
+ inclusiveAncestorCurrentBlockOrError.inspect()) {
+ aRangeToDelete.SetStart(prevVisibleThingOfStartBoundary
+ .PointAtReachedContent<EditorRawDOMPoint>()
+ .ToRawRangeBoundary(),
+ IgnoreErrors());
+ }
+ }
+
+ if (preserveEndBoundary) {
+ return;
+ }
+
+ if (aComputeRangeFor == ComputeRangeFor::GetTargetRanges) {
+ // When we set the end boundary to around the right block, the new end
+ // boundary should not after inline ancestors of the line break which won't
+ // be deleted.
+ const WSScanResult lastVisibleThingBeforeRightChildBlock =
+ [&]() -> WSScanResult {
+ EditorRawDOMPoint scanStartPoint(aRangeToDelete.StartRef());
+ WSScanResult lastScanResult = WSScanResult::Error();
+ while (true) {
+ WSScanResult scanResult =
+ WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
+ mLeftContent->AsElement(), scanStartPoint,
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ if (scanResult.ReachedBlockBoundary() ||
+ scanResult.ReachedInlineEditingHostBoundary()) {
+ return lastScanResult;
+ }
+ scanStartPoint =
+ scanResult.PointAfterReachedContent<EditorRawDOMPoint>();
+ lastScanResult = scanResult;
+ }
+ }();
+ if (lastVisibleThingBeforeRightChildBlock.GetContent()) {
+ const nsIContent* commonAncestor = nsIContent::FromNode(
+ nsContentUtils::GetClosestCommonInclusiveAncestor(
+ aRangeToDelete.StartRef().Container(),
+ lastVisibleThingBeforeRightChildBlock.GetContent()));
+ MOZ_ASSERT(commonAncestor);
+ if (commonAncestor &&
+ !mRightContent->IsInclusiveDescendantOf(commonAncestor)) {
+ IgnoredErrorResult error;
+ aRangeToDelete.SetEnd(
+ EditorRawDOMPoint::AtEndOf(*commonAncestor).ToRawRangeBoundary(),
+ error);
+ NS_WARNING_ASSERTION(!error.Failed(),
+ "nsRange::SetEnd() failed, but ignored");
+ return;
+ }
+ }
+ }
+
+ IgnoredErrorResult error;
+ aRangeToDelete.SetEnd(
+ EditorRawDOMPoint(inclusiveAncestorCurrentBlockOrError.inspect())
+ .ToRawRangeBoundary(),
+ error);
+ NS_WARNING_ASSERTION(!error.Failed(),
+ "nsRange::SetEnd() failed, but ignored");
+}
+
nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
ComputeRangeToDeleteNonCollapsedRange(
const HTMLEditor& aHTMLEditor,
@@ -3954,6 +4695,10 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
MOZ_ASSERT(
aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(mRightContent));
+ ExtendRangeToDeleteNonCollapsedRange(aHTMLEditor, aRangeToDelete,
+ aEditingHost,
+ ComputeRangeFor::GetTargetRanges);
+
Result<bool, nsresult> result =
ComputeRangeToDeleteNodesEntirelyInRangeButKeepTableStructure(
aHTMLEditor, aRangeToDelete, aSelectionWasCollapsed);
@@ -4003,14 +4748,20 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
MOZ_ASSERT(!aRangeToDelete.Collapsed());
MOZ_ASSERT(mDeleteRangesHandler);
- MOZ_ASSERT(mLeftContent);
- MOZ_ASSERT(mLeftContent->IsElement());
- MOZ_ASSERT(aRangeToDelete.GetStartContainer()->IsInclusiveDescendantOf(
- mLeftContent));
- MOZ_ASSERT(mRightContent);
- MOZ_ASSERT(mRightContent->IsElement());
- MOZ_ASSERT(
- aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(mRightContent));
+
+ const bool isDeletingLineBreak =
+ mMode == Mode::DeleteBRElement ||
+ mMode == Mode::DeletePrecedingBRElementOfBlock ||
+ mMode == Mode::DeletePrecedingPreformattedLineBreak;
+ if (!isDeletingLineBreak) {
+ MOZ_ASSERT(aRangeToDelete.GetStartContainer()->IsInclusiveDescendantOf(
+ mLeftContent));
+ MOZ_ASSERT(aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(
+ mRightContent));
+ ExtendRangeToDeleteNonCollapsedRange(aHTMLEditor, aRangeToDelete,
+ aEditingHost,
+ ComputeRangeFor::ToDeleteTheRange);
+ }
const bool backspaceInRightBlock =
aSelectionWasCollapsed == SelectionWasCollapsed::Yes &&
@@ -4034,7 +4785,8 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
return deleteResult.propagateErr();
}
- const bool joinInclusiveAncestorBlockElements = deleteResult.unwrap();
+ const bool joinInclusiveAncestorBlockElements =
+ !isDeletingLineBreak && deleteResult.unwrap();
// Check endpoints for possible text deletion. We can assume that if
// text node is found, we can delete to end or to beginning as
@@ -4048,9 +4800,24 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
}
if (!joinInclusiveAncestorBlockElements) {
+ // When we delete only preceding lines of the right child block, we should
+ // put caret into start of the right block.
+ if (mMode == Mode::DeletePrecedingLinesAndContentInRange) {
+ result.MarkAsHandled();
+ if (MOZ_LIKELY(mRightContent->IsInComposedDoc())) {
+ pointToPutCaret =
+ HTMLEditUtils::GetDeepestEditableStartPointOf<EditorDOMPoint>(
+ *mRightContent);
+ }
+ }
break;
}
+ MOZ_ASSERT(mLeftContent);
+ MOZ_ASSERT(mLeftContent->IsElement());
+ MOZ_ASSERT(mRightContent);
+ MOZ_ASSERT(mRightContent->IsElement());
+
AutoInclusiveAncestorBlockElementsJoiner joiner(*mLeftContent,
*mRightContent);
Result<bool, nsresult> canJoinThem =
@@ -4106,6 +4873,12 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
break;
}
+ // HandleDeleteLineBreak() should handle the new caret position by itself.
+ if (isDeletingLineBreak) {
+ result.MarkAsHandled();
+ return result;
+ }
+
// If we're deleting selection (not replacing with new content) and
// AutoInclusiveAncestorBlockElementsJoiner computed new caret position, we
// should use it. Otherwise, we should keep the traditional behavior.
@@ -4297,7 +5070,8 @@ HTMLEditor::AutoDeleteRangesHandler::DeleteParentBlocksWithTransactionIfEmpty(
RefPtr<Element> editingHost = aHTMLEditor.ComputeEditingHost();
WSRunScanner wsScannerForPoint(
editingHost, aPoint, BlockInlineCheck::UseComputedDisplayOutsideStyle);
- if (!wsScannerForPoint.StartsFromCurrentBlockBoundary()) {
+ if (!wsScannerForPoint.StartsFromCurrentBlockBoundary() &&
+ !wsScannerForPoint.StartsFromInlineEditingHostBoundary()) {
// If there is visible node before the point, we shouldn't remove the
// parent block.
return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
@@ -4319,8 +5093,8 @@ HTMLEditor::AutoDeleteRangesHandler::DeleteParentBlocksWithTransactionIfEmpty(
}
// Next, check there is visible contents after the point in current block.
- WSScanResult forwardScanFromPointResult =
- wsScannerForPoint.ScanNextVisibleNodeOrBlockBoundaryFrom(aPoint);
+ const WSScanResult forwardScanFromPointResult =
+ wsScannerForPoint.ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(aPoint);
if (forwardScanFromPointResult.Failed()) {
NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom() failed");
return NS_ERROR_FAILURE;
@@ -4340,8 +5114,8 @@ HTMLEditor::AutoDeleteRangesHandler::DeleteParentBlocksWithTransactionIfEmpty(
return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
}
if (wsScannerForPoint.GetEndReasonContent()->GetNextSibling()) {
- WSScanResult scanResult =
- WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
+ const WSScanResult scanResult =
+ WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
editingHost,
EditorRawDOMPoint::After(
*wsScannerForPoint.GetEndReasonContent()),
@@ -4350,13 +5124,15 @@ HTMLEditor::AutoDeleteRangesHandler::DeleteParentBlocksWithTransactionIfEmpty(
NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed");
return NS_ERROR_FAILURE;
}
- if (!scanResult.ReachedCurrentBlockBoundary()) {
+ if (!scanResult.ReachedCurrentBlockBoundary() &&
+ !scanResult.ReachedInlineEditingHostBoundary()) {
// If we couldn't reach the block's end after the invisible <br>,
// that means that there is visible content.
return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
}
}
- } else if (!forwardScanFromPointResult.ReachedCurrentBlockBoundary()) {
+ } else if (!forwardScanFromPointResult.ReachedCurrentBlockBoundary() &&
+ !forwardScanFromPointResult.ReachedInlineEditingHostBoundary()) {
// If we couldn't reach the block's end, the block has visible content.
return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
}
@@ -5169,11 +5945,12 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
aHTMLEditor.GetEditAction())) {
WSRunScanner scanner(&aEditingHost, startOfRightContent,
BlockInlineCheck::UseComputedDisplayStyle);
- WSScanResult maybePreviousText =
+ const WSScanResult maybePreviousText =
scanner.ScanPreviousVisibleNodeOrBlockBoundaryFrom(startOfRightContent);
if (maybePreviousText.IsContentEditable() &&
maybePreviousText.InVisibleOrCollapsibleCharacters()) {
- mPointToPutCaret = maybePreviousText.Point<EditorDOMPoint>();
+ mPointToPutCaret =
+ maybePreviousText.PointAfterReachedContent<EditorDOMPoint>();
}
}
return result;
@@ -6798,11 +7575,12 @@ HTMLEditor::AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete(
if (rangeToDelete.StartRef().GetContainer() !=
closestBlockAncestorOrInlineEditingHost) {
for (;;) {
- WSScanResult backwardScanFromStartResult =
+ const WSScanResult backwardScanFromStartResult =
WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
closestEditingHost, rangeToDelete.StartRef(),
BlockInlineCheck::UseComputedDisplayOutsideStyle);
- if (!backwardScanFromStartResult.ReachedCurrentBlockBoundary()) {
+ if (!backwardScanFromStartResult.ReachedCurrentBlockBoundary() &&
+ !backwardScanFromStartResult.ReachedInlineEditingHostBoundary()) {
break;
}
MOZ_ASSERT(backwardScanFromStartResult.GetContent() ==
@@ -6834,8 +7612,8 @@ HTMLEditor::AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete(
*backwardScanFromStartResult.ElementPtr())) {
break;
}
- rangeToDelete.SetStart(
- backwardScanFromStartResult.PointAtContent<EditorRawDOMPoint>());
+ rangeToDelete.SetStart(backwardScanFromStartResult
+ .PointAtReachedContent<EditorRawDOMPoint>());
}
if (aFrameSelection && !aFrameSelection->IsValidSelectionPoint(
rangeToDelete.StartRef().GetContainer())) {
@@ -6856,8 +7634,8 @@ HTMLEditor::AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete(
WSRunScanner wsScannerAtEnd(
closestEditingHost, rangeToDelete.EndRef(),
BlockInlineCheck::UseComputedDisplayOutsideStyle);
- WSScanResult forwardScanFromEndResult =
- wsScannerAtEnd.ScanNextVisibleNodeOrBlockBoundaryFrom(
+ const WSScanResult forwardScanFromEndResult =
+ wsScannerAtEnd.ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
rangeToDelete.EndRef());
if (forwardScanFromEndResult.ReachedBRElement()) {
// XXX In my understanding, this is odd. The end reason may not be
@@ -6881,7 +7659,9 @@ HTMLEditor::AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete(
continue;
}
- if (forwardScanFromEndResult.ReachedCurrentBlockBoundary()) {
+ if (forwardScanFromEndResult.ReachedCurrentBlockBoundary() ||
+ forwardScanFromEndResult.ReachedInlineEditingHostBoundary()) {
+ MOZ_ASSERT(forwardScanFromEndResult.ContentIsElement());
MOZ_ASSERT(forwardScanFromEndResult.GetContent() ==
wsScannerAtEnd.GetEndReasonContent());
// We want to keep looking up. But stop if we are crossing table
@@ -6895,13 +7675,13 @@ HTMLEditor::AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete(
// Don't cross flex-item/grid-item boundary to make new content inserted
// into it.
if (StaticPrefs::editor_block_inline_check_use_computed_style() &&
- forwardScanFromEndResult.ContentIsElement() &&
HTMLEditUtils::IsFlexOrGridItem(
*forwardScanFromEndResult.ElementPtr())) {
break;
}
rangeToDelete.SetEnd(
- forwardScanFromEndResult.PointAfterContent<EditorRawDOMPoint>());
+ forwardScanFromEndResult
+ .PointAfterReachedContent<EditorRawDOMPoint>());
continue;
}
diff --git a/editor/libeditor/HTMLStyleEditor.cpp b/editor/libeditor/HTMLStyleEditor.cpp
index 2ca7b06c29..5aaff15ba4 100644
--- a/editor/libeditor/HTMLStyleEditor.cpp
+++ b/editor/libeditor/HTMLStyleEditor.cpp
@@ -1914,8 +1914,8 @@ HTMLEditor::AutoInlineStyleSetter::ExtendOrShrinkRangeToApplyTheStyle(
// range to contain the <br> element.
EditorDOMRange range(aRange);
if (range.EndRef().IsInContentNode()) {
- WSScanResult nextContentData =
- WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
+ const WSScanResult nextContentData =
+ WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
&aEditingHost, range.EndRef(),
BlockInlineCheck::UseComputedDisplayOutsideStyle);
if (nextContentData.ReachedInvisibleBRElement() &&
@@ -2076,7 +2076,7 @@ HTMLEditor::SplitAncestorStyledInlineElementsAtRangeEdges(
return result;
}
tracker.FlushAndStopTracking();
- if (NS_WARN_IF(result.inspect().Handled())) {
+ if (result.inspect().Handled()) {
auto endOfRange = result.inspect().AtSplitPoint<EditorDOMPoint>();
if (!endOfRange.IsSet()) {
result.inspect().IgnoreCaretPointSuggestion();
diff --git a/editor/libeditor/WSRunObject.cpp b/editor/libeditor/WSRunObject.cpp
index 7149578be1..8acd1f60e7 100644
--- a/editor/libeditor/WSRunObject.cpp
+++ b/editor/libeditor/WSRunObject.cpp
@@ -49,9 +49,11 @@ template WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
const EditorDOMPoint& aPoint) const;
template WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
const EditorRawDOMPoint& aPoint) const;
-template WSScanResult WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom(
+template WSScanResult
+WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
const EditorDOMPoint& aPoint) const;
-template WSScanResult WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom(
+template WSScanResult
+WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
const EditorRawDOMPoint& aPoint) const;
template EditorDOMPoint WSRunScanner::GetAfterLastVisiblePoint(
Text& aTextNode, const Element* aAncestorLimiter);
@@ -1292,7 +1294,9 @@ Result<InsertTextResult, nsresult> WhiteSpaceVisibilityKeeper::ReplaceText(
// If the insertion point is (was) before the start of text and it's
// immediately after a hard line break, the first ASCII white-space should
// be replaced with an NBSP for making it visible.
- else if (textFragmentDataAtStart.StartsFromHardLineBreak() &&
+ else if ((textFragmentDataAtStart.StartsFromHardLineBreak() ||
+ textFragmentDataAtStart
+ .StartsFromInlineEditingHostBoundary()) &&
isInsertionPointEqualsOrIsBeforeStartOfText) {
theString.SetCharAt(HTMLEditUtils::kNBSP, 0);
}
@@ -1325,7 +1329,8 @@ Result<InsertTextResult, nsresult> WhiteSpaceVisibilityKeeper::ReplaceText(
// If the end of replacing range is (was) after the end of text and it's
// immediately before block boundary, the last ASCII white-space should
// be replaced with an NBSP for making it visible.
- else if (textFragmentDataAtEnd.EndsByBlockBoundary() &&
+ else if ((textFragmentDataAtEnd.EndsByBlockBoundary() ||
+ textFragmentDataAtEnd.EndsByInlineEditingHostBoundary()) &&
isInsertionPointEqualsOrAfterEndOfText) {
theString.SetCharAt(HTMLEditUtils::kNBSP, lastCharIndex);
}
@@ -1721,9 +1726,24 @@ template <typename PT, typename CT>
WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
const EditorDOMPointBase<PT, CT>& aPoint) const {
MOZ_ASSERT(aPoint.IsSet());
+ MOZ_ASSERT(aPoint.IsInComposedDoc());
+
+ if (MOZ_UNLIKELY(!aPoint.IsSet())) {
+ return WSScanResult::Error();
+ }
+
+ // We may not be able to check editable state in uncomposed tree as expected.
+ // For example, only some descendants in an editing host is temporarily
+ // removed from the tree, they are not editable unless nested contenteditable
+ // attribute is set to "true".
+ if (MOZ_UNLIKELY(!aPoint.IsInComposedDoc())) {
+ return WSScanResult(WSScanResult::ScanDirection::Backward,
+ *aPoint.template ContainerAs<nsIContent>(),
+ WSType::InUncomposedDoc, mBlockInlineCheck);
+ }
if (!TextFragmentDataAtStartRef().IsInitialized()) {
- return WSScanResult(nullptr, WSType::UnexpectedError, mBlockInlineCheck);
+ return WSScanResult::Error();
}
// If the range has visible text and start of the visible text is before
@@ -1736,7 +1756,8 @@ WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
// things now. Whether keep scanning editable things or not should be
// considered by the caller.
if (aPoint.GetChild() && !aPoint.GetChild()->IsEditable()) {
- return WSScanResult(aPoint.GetChild(), WSType::SpecialContent,
+ return WSScanResult(WSScanResult::ScanDirection::Backward,
+ *aPoint.GetChild(), WSType::SpecialContent,
mBlockInlineCheck);
}
const auto atPreviousChar =
@@ -1744,35 +1765,82 @@ WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
// When it's a non-empty text node, return it.
if (atPreviousChar.IsSet() && !atPreviousChar.IsContainerEmpty()) {
MOZ_ASSERT(!atPreviousChar.IsEndOfContainer());
- return WSScanResult(atPreviousChar.template NextPoint<EditorDOMPoint>(),
+ return WSScanResult(WSScanResult::ScanDirection::Backward,
+ atPreviousChar.template NextPoint<EditorDOMPoint>(),
atPreviousChar.IsCharCollapsibleASCIISpaceOrNBSP()
? WSType::CollapsibleWhiteSpaces
+ : atPreviousChar.IsCharPreformattedNewLine()
+ ? WSType::PreformattedLineBreak
: WSType::NonCollapsibleCharacters,
mBlockInlineCheck);
}
}
+ if (NS_WARN_IF(TextFragmentDataAtStartRef().StartRawReason() ==
+ WSType::UnexpectedError)) {
+ return WSScanResult::Error();
+ }
+
+ switch (TextFragmentDataAtStartRef().StartRawReason()) {
+ case WSType::CollapsibleWhiteSpaces:
+ case WSType::NonCollapsibleCharacters:
+ case WSType::PreformattedLineBreak:
+ MOZ_ASSERT(TextFragmentDataAtStartRef().StartRef().IsSet());
+ // XXX: If we find the character at last of a text node and we started
+ // scanning from following text node of it, some callers may work with the
+ // point in the following text node instead of end of the found text node.
+ return WSScanResult(WSScanResult::ScanDirection::Backward,
+ TextFragmentDataAtStartRef().StartRef(),
+ TextFragmentDataAtStartRef().StartRawReason(),
+ mBlockInlineCheck);
+ default:
+ break;
+ }
+
// Otherwise, return the start of the range.
if (TextFragmentDataAtStartRef().GetStartReasonContent() !=
TextFragmentDataAtStartRef().StartRef().GetContainer()) {
+ if (NS_WARN_IF(!TextFragmentDataAtStartRef().GetStartReasonContent())) {
+ return WSScanResult::Error();
+ }
// In this case, TextFragmentDataAtStartRef().StartRef().Offset() is not
// meaningful.
- return WSScanResult(TextFragmentDataAtStartRef().GetStartReasonContent(),
+ return WSScanResult(WSScanResult::ScanDirection::Backward,
+ *TextFragmentDataAtStartRef().GetStartReasonContent(),
TextFragmentDataAtStartRef().StartRawReason(),
mBlockInlineCheck);
}
- return WSScanResult(TextFragmentDataAtStartRef().StartRef(),
+ if (NS_WARN_IF(!TextFragmentDataAtStartRef().StartRef().IsSet())) {
+ return WSScanResult::Error();
+ }
+ return WSScanResult(WSScanResult::ScanDirection::Backward,
+ TextFragmentDataAtStartRef().StartRef(),
TextFragmentDataAtStartRef().StartRawReason(),
mBlockInlineCheck);
}
template <typename PT, typename CT>
-WSScanResult WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom(
+WSScanResult WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
const EditorDOMPointBase<PT, CT>& aPoint) const {
MOZ_ASSERT(aPoint.IsSet());
+ MOZ_ASSERT(aPoint.IsInComposedDoc());
+
+ if (MOZ_UNLIKELY(!aPoint.IsSet())) {
+ return WSScanResult::Error();
+ }
+
+ // We may not be able to check editable state in uncomposed tree as expected.
+ // For example, only some descendants in an editing host is temporarily
+ // removed from the tree, they are not editable unless nested contenteditable
+ // attribute is set to "true".
+ if (MOZ_UNLIKELY(!aPoint.IsInComposedDoc())) {
+ return WSScanResult(WSScanResult::ScanDirection::Forward,
+ *aPoint.template ContainerAs<nsIContent>(),
+ WSType::InUncomposedDoc, mBlockInlineCheck);
+ }
if (!TextFragmentDataAtStartRef().IsInitialized()) {
- return WSScanResult(nullptr, WSType::UnexpectedError, mBlockInlineCheck);
+ return WSScanResult::Error();
}
// If the range has visible text and aPoint equals or is before the end of the
@@ -1785,32 +1853,66 @@ WSScanResult WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom(
// things now. Whether keep scanning editable things or not should be
// considered by the caller.
if (aPoint.GetChild() && !aPoint.GetChild()->IsEditable()) {
- return WSScanResult(aPoint.GetChild(), WSType::SpecialContent,
+ return WSScanResult(WSScanResult::ScanDirection::Forward,
+ *aPoint.GetChild(), WSType::SpecialContent,
mBlockInlineCheck);
}
const auto atNextChar =
GetInclusiveNextEditableCharPoint<EditorDOMPoint>(aPoint);
// When it's a non-empty text node, return it.
if (atNextChar.IsSet() && !atNextChar.IsContainerEmpty()) {
- return WSScanResult(atNextChar,
+ return WSScanResult(WSScanResult::ScanDirection::Forward, atNextChar,
!atNextChar.IsEndOfContainer() &&
atNextChar.IsCharCollapsibleASCIISpaceOrNBSP()
? WSType::CollapsibleWhiteSpaces
+ : !atNextChar.IsEndOfContainer() &&
+ atNextChar.IsCharPreformattedNewLine()
+ ? WSType::PreformattedLineBreak
: WSType::NonCollapsibleCharacters,
mBlockInlineCheck);
}
}
+ if (NS_WARN_IF(TextFragmentDataAtStartRef().EndRawReason() ==
+ WSType::UnexpectedError)) {
+ return WSScanResult::Error();
+ }
+
+ switch (TextFragmentDataAtStartRef().EndRawReason()) {
+ case WSType::CollapsibleWhiteSpaces:
+ case WSType::NonCollapsibleCharacters:
+ case WSType::PreformattedLineBreak:
+ MOZ_ASSERT(TextFragmentDataAtStartRef().StartRef().IsSet());
+ // XXX: If we find the character at start of a text node and we
+ // started scanning from preceding text node of it, some callers may want
+ // to work with the point at end of the preceding text node instead of
+ // start of the found text node.
+ return WSScanResult(WSScanResult::ScanDirection::Forward,
+ TextFragmentDataAtStartRef().EndRef(),
+ TextFragmentDataAtStartRef().EndRawReason(),
+ mBlockInlineCheck);
+ default:
+ break;
+ }
+
// Otherwise, return the end of the range.
if (TextFragmentDataAtStartRef().GetEndReasonContent() !=
TextFragmentDataAtStartRef().EndRef().GetContainer()) {
+ if (NS_WARN_IF(!TextFragmentDataAtStartRef().GetEndReasonContent())) {
+ return WSScanResult::Error();
+ }
// In this case, TextFragmentDataAtStartRef().EndRef().Offset() is not
// meaningful.
- return WSScanResult(TextFragmentDataAtStartRef().GetEndReasonContent(),
+ return WSScanResult(WSScanResult::ScanDirection::Forward,
+ *TextFragmentDataAtStartRef().GetEndReasonContent(),
TextFragmentDataAtStartRef().EndRawReason(),
mBlockInlineCheck);
}
- return WSScanResult(TextFragmentDataAtStartRef().EndRef(),
+ if (NS_WARN_IF(!TextFragmentDataAtStartRef().EndRef().IsSet())) {
+ return WSScanResult::Error();
+ }
+ return WSScanResult(WSScanResult::ScanDirection::Forward,
+ TextFragmentDataAtStartRef().EndRef(),
TextFragmentDataAtStartRef().EndRawReason(),
mBlockInlineCheck);
}
@@ -1944,6 +2046,7 @@ WSRunScanner::TextFragmentData::BoundaryData WSRunScanner::TextFragmentData::
const Element* aEditingHost, NoBreakingSpaceData* aNBSPData,
BlockInlineCheck aBlockInlineCheck) {
MOZ_ASSERT(aPoint.IsSetAndValid());
+ MOZ_ASSERT(aEditableBlockParentOrTopmostEditableInlineElement.IsEditable());
if (aPoint.IsInTextNode() && !aPoint.IsStartOfContainer()) {
Maybe<BoundaryData> startInTextNode =
@@ -1967,14 +2070,16 @@ WSRunScanner::TextFragmentData::BoundaryData WSRunScanner::TextFragmentData::
{LeafNodeType::LeafNodeOrNonEditableNode}, aBlockInlineCheck,
aEditingHost);
if (!previousLeafContentOrBlock) {
- // no prior node means we exhausted
- // aEditableBlockParentOrTopmostEditableInlineElement
- // mReasonContent can be either a block element or any non-editable
- // content in this case.
+ // No previous content means that we reached
+ // aEditableBlockParentOrTopmostEditableInlineElement boundary.
return BoundaryData(aPoint,
const_cast<Element&>(
aEditableBlockParentOrTopmostEditableInlineElement),
- WSType::CurrentBlockBoundary);
+ HTMLEditUtils::IsBlockElement(
+ aEditableBlockParentOrTopmostEditableInlineElement,
+ aBlockInlineCheck)
+ ? WSType::CurrentBlockBoundary
+ : WSType::InlineEditingHostBoundary);
}
if (HTMLEditUtils::IsBlockElement(*previousLeafContentOrBlock,
@@ -2088,6 +2193,7 @@ WSRunScanner::TextFragmentData::BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
const Element* aEditingHost, NoBreakingSpaceData* aNBSPData,
BlockInlineCheck aBlockInlineCheck) {
MOZ_ASSERT(aPoint.IsSetAndValid());
+ MOZ_ASSERT(aEditableBlockParentOrTopmostEditableInlineElement.IsEditable());
if (aPoint.IsInTextNode() && !aPoint.IsEndOfContainer()) {
Maybe<BoundaryData> endInTextNode =
@@ -2111,14 +2217,16 @@ WSRunScanner::TextFragmentData::BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
{LeafNodeType::LeafNodeOrNonEditableNode}, aBlockInlineCheck,
aEditingHost);
if (!nextLeafContentOrBlock) {
- // no next node means we exhausted
- // aEditableBlockParentOrTopmostEditableInlineElement
- // mReasonContent can be either a block element or any non-editable
- // content in this case.
+ // No next content means that we reached
+ // aEditableBlockParentOrTopmostEditableInlineElement boundary.
return BoundaryData(aPoint.template To<EditorDOMPoint>(),
const_cast<Element&>(
aEditableBlockParentOrTopmostEditableInlineElement),
- WSType::CurrentBlockBoundary);
+ HTMLEditUtils::IsBlockElement(
+ aEditableBlockParentOrTopmostEditableInlineElement,
+ aBlockInlineCheck)
+ ? WSType::CurrentBlockBoundary
+ : WSType::InlineEditingHostBoundary);
}
if (HTMLEditUtils::IsBlockElement(*nextLeafContentOrBlock,
@@ -2172,7 +2280,7 @@ WSRunScanner::TextFragmentData::InvisibleLeadingWhiteSpaceRangeRef() const {
}
// If it's start of line, there is no invisible leading white-spaces.
- if (!StartsFromHardLineBreak()) {
+ if (!StartsFromHardLineBreak() && !StartsFromInlineEditingHostBoundary()) {
mLeadingWhiteSpaceRange.emplace();
return mLeadingWhiteSpaceRange.ref();
}
@@ -2202,7 +2310,8 @@ WSRunScanner::TextFragmentData::InvisibleTrailingWhiteSpaceRangeRef() const {
// If it's not immediately before a block boundary nor an invisible
// preformatted linefeed, there is no invisible trailing white-spaces. Note
// that collapsible white-spaces before a `<br>` element is visible.
- if (!EndsByBlockBoundary() && !EndsByInvisiblePreformattedLineBreak()) {
+ if (!EndsByBlockBoundary() && !EndsByInlineEditingHostBoundary() &&
+ !EndsByInvisiblePreformattedLineBreak()) {
mTrailingWhiteSpaceRange.emplace();
return mTrailingWhiteSpaceRange.ref();
}
@@ -2332,7 +2441,7 @@ WSRunScanner::TextFragmentData::VisibleWhiteSpacesDataRef() const {
return mVisibleWhiteSpacesData.ref();
}
- if (!StartsFromHardLineBreak()) {
+ if (!StartsFromHardLineBreak() && !StartsFromInlineEditingHostBoundary()) {
VisibleWhiteSpacesData visibleWhiteSpaces;
if (mStart.PointRef().IsSet()) {
visibleWhiteSpaces.SetStartPoint(mStart.PointRef());
@@ -2352,7 +2461,8 @@ WSRunScanner::TextFragmentData::VisibleWhiteSpacesDataRef() const {
return mVisibleWhiteSpacesData.ref();
}
- MOZ_ASSERT(StartsFromHardLineBreak());
+ MOZ_ASSERT(StartsFromHardLineBreak() ||
+ StartsFromInlineEditingHostBoundary());
MOZ_ASSERT(maybeHaveLeadingWhiteSpaces);
VisibleWhiteSpacesData visibleWhiteSpaces;
@@ -2360,7 +2470,7 @@ WSRunScanner::TextFragmentData::VisibleWhiteSpacesDataRef() const {
visibleWhiteSpaces.SetStartPoint(leadingWhiteSpaceRange.EndRef());
}
visibleWhiteSpaces.SetStartFromLeadingWhiteSpaces();
- if (!EndsByBlockBoundary()) {
+ if (!EndsByBlockBoundary() && !EndsByInlineEditingHostBoundary()) {
// then no trailing ws. this normal run ends the overall ws run.
if (mEnd.PointRef().IsSet()) {
visibleWhiteSpaces.SetEndPoint(mEnd.PointRef());
@@ -2370,7 +2480,7 @@ WSRunScanner::TextFragmentData::VisibleWhiteSpacesDataRef() const {
return mVisibleWhiteSpacesData.ref();
}
- MOZ_ASSERT(EndsByBlockBoundary());
+ MOZ_ASSERT(EndsByBlockBoundary() || EndsByInlineEditingHostBoundary());
if (!maybeHaveTrailingWhiteSpaces) {
// normal ws runs right up to adjacent block (nbsp next to block)
@@ -3401,7 +3511,8 @@ nsresult WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
isPreviousCharCollapsibleASCIIWhiteSpace) {
// First, try to insert <br> element if NBSP is at end of a block.
// XXX We should stop this if there is a visible content.
- if (visibleWhiteSpaces.EndsByBlockBoundary() &&
+ if ((visibleWhiteSpaces.EndsByBlockBoundary() ||
+ visibleWhiteSpaces.EndsByInlineEditingHostBoundary()) &&
aPoint.IsInContentNode()) {
bool insertBRElement = HTMLEditUtils::IsBlockElement(
*aPoint.template ContainerAs<nsIContent>(),
@@ -4320,7 +4431,8 @@ WSRunScanner::GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries(
// If there is no invisible white-space and the line starts with a
// text node, shrink the range to start of the text node.
else if (!aRange.StartRef().IsInTextNode() &&
- textFragmentDataAtStart.StartsFromBlockBoundary() &&
+ (textFragmentDataAtStart.StartsFromBlockBoundary() ||
+ textFragmentDataAtStart.StartsFromInlineEditingHostBoundary()) &&
textFragmentDataAtStart.EndRef().IsInTextNode()) {
result.SetStart(textFragmentDataAtStart.EndRef());
}
@@ -4353,7 +4465,8 @@ WSRunScanner::GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries(
// If there is no invisible white-space and the line ends with a text
// node, shrink the range to end of the text node.
else if (!aRange.EndRef().IsInTextNode() &&
- textFragmentDataAtEnd.EndsByBlockBoundary() &&
+ (textFragmentDataAtEnd.EndsByBlockBoundary() ||
+ textFragmentDataAtEnd.EndsByInlineEditingHostBoundary()) &&
textFragmentDataAtEnd.StartRef().IsInTextNode()) {
result.SetEnd(EditorDOMPoint::AtEndOf(
*textFragmentDataAtEnd.StartRef().ContainerAs<Text>()));
diff --git a/editor/libeditor/WSRunObject.h b/editor/libeditor/WSRunObject.h
index 9328b24eb2..bbc6a078d9 100644
--- a/editor/libeditor/WSRunObject.h
+++ b/editor/libeditor/WSRunObject.h
@@ -41,6 +41,8 @@ class MOZ_STACK_CLASS WSScanResult final {
NotInitialized,
// Could be the DOM tree is broken as like crash tests.
UnexpectedError,
+ // The scanner cannot work in uncomposed tree, but tried to scan in it.
+ InUncomposedDoc,
// The run is maybe collapsible white-spaces at start of a hard line.
LeadingWhiteSpaces,
// The run is maybe collapsible white-spaces at end of a hard line.
@@ -59,6 +61,8 @@ class MOZ_STACK_CLASS WSScanResult final {
OtherBlockBoundary,
// Current block's boundary.
CurrentBlockBoundary,
+ // Inline editing host boundary.
+ InlineEditingHostBoundary,
};
friend std::ostream& operator<<(std::ostream& aStream, const WSType& aType) {
@@ -67,6 +71,8 @@ class MOZ_STACK_CLASS WSScanResult final {
return aStream << "WSType::NotInitialized";
case WSType::UnexpectedError:
return aStream << "WSType::UnexpectedError";
+ case WSType::InUncomposedDoc:
+ return aStream << "WSType::InUncomposedDoc";
case WSType::LeadingWhiteSpaces:
return aStream << "WSType::LeadingWhiteSpaces";
case WSType::TrailingWhiteSpaces:
@@ -85,90 +91,111 @@ class MOZ_STACK_CLASS WSScanResult final {
return aStream << "WSType::OtherBlockBoundary";
case WSType::CurrentBlockBoundary:
return aStream << "WSType::CurrentBlockBoundary";
+ case WSType::InlineEditingHostBoundary:
+ return aStream << "WSType::InlineEditingHostBoundary";
}
return aStream << "<Illegal value>";
}
friend class WSRunScanner; // Because of WSType.
+ explicit WSScanResult(WSType aReason) : mReason(aReason) {
+ MOZ_ASSERT(mReason == WSType::UnexpectedError ||
+ mReason == WSType::NotInitialized);
+ }
+
public:
WSScanResult() = delete;
- MOZ_NEVER_INLINE_DEBUG WSScanResult(nsIContent* aContent, WSType aReason,
+ enum class ScanDirection : bool { Backward, Forward };
+ MOZ_NEVER_INLINE_DEBUG WSScanResult(ScanDirection aScanDirection,
+ nsIContent& aContent, WSType aReason,
BlockInlineCheck aBlockInlineCheck)
- : mContent(aContent), mReason(aReason) {
+ : mContent(&aContent), mReason(aReason), mDirection(aScanDirection) {
+ MOZ_ASSERT(aReason != WSType::CollapsibleWhiteSpaces &&
+ aReason != WSType::NonCollapsibleCharacters &&
+ aReason != WSType::PreformattedLineBreak);
AssertIfInvalidData(aBlockInlineCheck);
}
- MOZ_NEVER_INLINE_DEBUG WSScanResult(const EditorDOMPoint& aPoint,
+ MOZ_NEVER_INLINE_DEBUG WSScanResult(ScanDirection aScanDirection,
+ const EditorDOMPoint& aPoint,
WSType aReason,
BlockInlineCheck aBlockInlineCheck)
: mContent(aPoint.GetContainerAs<nsIContent>()),
mOffset(Some(aPoint.Offset())),
- mReason(aReason) {
+ mReason(aReason),
+ mDirection(aScanDirection) {
AssertIfInvalidData(aBlockInlineCheck);
}
+ static WSScanResult Error() { return WSScanResult(WSType::UnexpectedError); }
+
MOZ_NEVER_INLINE_DEBUG void AssertIfInvalidData(
BlockInlineCheck aBlockInlineCheck) const {
#ifdef DEBUG
MOZ_ASSERT(mReason == WSType::UnexpectedError ||
+ mReason == WSType::InUncomposedDoc ||
mReason == WSType::NonCollapsibleCharacters ||
mReason == WSType::CollapsibleWhiteSpaces ||
mReason == WSType::BRElement ||
mReason == WSType::PreformattedLineBreak ||
mReason == WSType::SpecialContent ||
mReason == WSType::CurrentBlockBoundary ||
- mReason == WSType::OtherBlockBoundary);
+ mReason == WSType::OtherBlockBoundary ||
+ mReason == WSType::InlineEditingHostBoundary);
MOZ_ASSERT_IF(mReason == WSType::UnexpectedError, !mContent);
+ MOZ_ASSERT_IF(mReason != WSType::UnexpectedError, mContent);
+ MOZ_ASSERT_IF(mReason == WSType::InUncomposedDoc,
+ !mContent->IsInComposedDoc());
+ MOZ_ASSERT_IF(mContent && !mContent->IsInComposedDoc(),
+ mReason == WSType::InUncomposedDoc);
+ MOZ_ASSERT_IF(mReason == WSType::NonCollapsibleCharacters ||
+ mReason == WSType::CollapsibleWhiteSpaces ||
+ mReason == WSType::PreformattedLineBreak,
+ mContent->IsText());
+ MOZ_ASSERT_IF(mReason == WSType::NonCollapsibleCharacters ||
+ mReason == WSType::CollapsibleWhiteSpaces ||
+ mReason == WSType::PreformattedLineBreak,
+ mOffset.isSome());
MOZ_ASSERT_IF(mReason == WSType::NonCollapsibleCharacters ||
- mReason == WSType::CollapsibleWhiteSpaces,
- mContent && mContent->IsText());
+ mReason == WSType::CollapsibleWhiteSpaces ||
+ mReason == WSType::PreformattedLineBreak,
+ mContent->AsText()->TextDataLength() > 0);
+ MOZ_ASSERT_IF(mDirection == ScanDirection::Backward &&
+ (mReason == WSType::NonCollapsibleCharacters ||
+ mReason == WSType::CollapsibleWhiteSpaces ||
+ mReason == WSType::PreformattedLineBreak),
+ *mOffset > 0);
+ MOZ_ASSERT_IF(mDirection == ScanDirection::Forward &&
+ (mReason == WSType::NonCollapsibleCharacters ||
+ mReason == WSType::CollapsibleWhiteSpaces ||
+ mReason == WSType::PreformattedLineBreak),
+ *mOffset < mContent->AsText()->TextDataLength());
MOZ_ASSERT_IF(mReason == WSType::BRElement,
- mContent && mContent->IsHTMLElement(nsGkAtoms::br));
+ mContent->IsHTMLElement(nsGkAtoms::br));
MOZ_ASSERT_IF(mReason == WSType::PreformattedLineBreak,
- mContent && mContent->IsText() &&
- EditorUtils::IsNewLinePreformatted(*mContent));
+ EditorUtils::IsNewLinePreformatted(*mContent));
MOZ_ASSERT_IF(
mReason == WSType::SpecialContent,
- mContent &&
- ((mContent->IsText() && !mContent->IsEditable()) ||
- (!mContent->IsHTMLElement(nsGkAtoms::br) &&
- !HTMLEditUtils::IsBlockElement(*mContent, aBlockInlineCheck))));
+ (mContent->IsText() && !mContent->IsEditable()) ||
+ (!mContent->IsHTMLElement(nsGkAtoms::br) &&
+ !HTMLEditUtils::IsBlockElement(*mContent, aBlockInlineCheck)));
MOZ_ASSERT_IF(mReason == WSType::OtherBlockBoundary,
- mContent && HTMLEditUtils::IsBlockElement(*mContent,
- aBlockInlineCheck));
- // If mReason is WSType::CurrentBlockBoundary, mContent can be any content.
- // In most cases, it's current block element which is editable. However, if
- // there is no editable block parent, this is topmost editable inline
- // content. Additionally, if there is no editable content, this is the
- // container start of scanner and is not editable.
- if (mReason == WSType::CurrentBlockBoundary) {
- if (!mContent ||
- // Although not expected that scanning in orphan document fragment,
- // it's okay.
- !mContent->IsInComposedDoc() ||
- // This is what the most preferred result is mContent itself is a
- // block.
- HTMLEditUtils::IsBlockElement(*mContent, aBlockInlineCheck) ||
- // If mContent is not editable, we cannot check whether there is no
- // block ancestor in the limiter which we don't have. Therefore,
- // let's skip the ancestor check.
- !mContent->IsEditable()) {
- return;
- }
- const DebugOnly<Element*> closestAncestorEditableBlockElement =
- HTMLEditUtils::GetAncestorElement(
- *mContent, HTMLEditUtils::ClosestEditableBlockElement,
- aBlockInlineCheck);
- MOZ_ASSERT_IF(
- mReason == WSType::CurrentBlockBoundary,
- // There is no editable block ancestor, it's fine.
- !closestAncestorEditableBlockElement ||
- // If we found an editable block, but mContent can be inline if
- // it's an editing host (root or its parent is not editable).
- !closestAncestorEditableBlockElement->GetParentElement() ||
- !closestAncestorEditableBlockElement->GetParentElement()
- ->IsEditable());
- }
+ HTMLEditUtils::IsBlockElement(*mContent, aBlockInlineCheck));
+ MOZ_ASSERT_IF(mReason == WSType::CurrentBlockBoundary,
+ mContent->IsElement());
+ MOZ_ASSERT_IF(mReason == WSType::CurrentBlockBoundary,
+ mContent->IsEditable());
+ MOZ_ASSERT_IF(mReason == WSType::CurrentBlockBoundary,
+ HTMLEditUtils::IsBlockElement(*mContent, aBlockInlineCheck));
+ MOZ_ASSERT_IF(mReason == WSType::InlineEditingHostBoundary,
+ mContent->IsElement());
+ MOZ_ASSERT_IF(mReason == WSType::InlineEditingHostBoundary,
+ mContent->IsEditable());
+ MOZ_ASSERT_IF(mReason == WSType::InlineEditingHostBoundary,
+ !HTMLEditUtils::IsBlockElement(*mContent, aBlockInlineCheck));
+ MOZ_ASSERT_IF(mReason == WSType::InlineEditingHostBoundary,
+ !mContent->GetParentElement() ||
+ !mContent->GetParentElement()->IsEditable());
#endif // #ifdef DEBUG
}
@@ -187,6 +214,10 @@ class MOZ_STACK_CLASS WSScanResult final {
return mContent && mContent->IsElement();
}
+ [[nodiscard]] bool ContentIsText() const {
+ return mContent && mContent->IsText();
+ }
+
/**
* The following accessors makes it easier to understand each callers.
*/
@@ -204,53 +235,65 @@ class MOZ_STACK_CLASS WSScanResult final {
}
/**
- * Returns true if found or reached content is ediable.
+ * Returns true if found or reached content is editable.
*/
bool IsContentEditable() const { return mContent && mContent->IsEditable(); }
/**
- * Offset() returns meaningful value only when
- * InVisibleOrCollapsibleCharacters() returns true or the scanner
- * reached to start or end of its scanning range and that is same as start or
- * end container which are specified when the scanner is initialized. If it's
- * result of scanning backward, this offset means before the found point.
- * Otherwise, i.e., scanning forward, this offset means after the found point.
+ * Offset_Deprecated() returns meaningful value only when
+ * InVisibleOrCollapsibleCharacters() returns true or the scanner reached to
+ * start or end of its scanning range and that is same as start or end
+ * container which are specified when the scanner is initialized. If it's
+ * result of scanning backward, this offset means the point of the found
+ * point. Otherwise, i.e., scanning forward, this offset means next point
+ * of the found point. E.g., if it reaches a collapsible white-space, this
+ * offset is at the first non-collapsible character after it.
*/
- MOZ_NEVER_INLINE_DEBUG uint32_t Offset() const {
+ MOZ_NEVER_INLINE_DEBUG uint32_t Offset_Deprecated() const {
NS_ASSERTION(mOffset.isSome(), "Retrieved non-meaningful offset");
return mOffset.valueOr(0);
}
/**
- * Point() and RawPoint() return the position in found visible node or
- * reached block boundary. So, they return meaningful point only when
- * Offset() returns meaningful value.
+ * Point_Deprecated() returns the position in found visible node or reached
+ * block boundary. So, this returns meaningful point only when
+ * Offset_Deprecated() returns meaningful value.
*/
template <typename EditorDOMPointType>
- EditorDOMPointType Point() const {
+ EditorDOMPointType Point_Deprecated() const {
NS_ASSERTION(mOffset.isSome(), "Retrieved non-meaningful point");
return EditorDOMPointType(mContent, mOffset.valueOr(0));
}
/**
- * PointAtContent() and RawPointAtContent() return the position of found
- * visible content or reached block element.
+ * PointAtReachedContent() returns the position of found visible content or
+ * reached block element.
*/
template <typename EditorDOMPointType>
- EditorDOMPointType PointAtContent() const {
+ EditorDOMPointType PointAtReachedContent() const {
MOZ_ASSERT(mContent);
- return EditorDOMPointType(mContent);
+ switch (mReason) {
+ case WSType::CollapsibleWhiteSpaces:
+ case WSType::NonCollapsibleCharacters:
+ case WSType::PreformattedLineBreak:
+ MOZ_DIAGNOSTIC_ASSERT(mOffset.isSome());
+ return mDirection == ScanDirection::Forward
+ ? EditorDOMPointType(mContent, mOffset.valueOr(0))
+ : EditorDOMPointType(mContent,
+ std::max(mOffset.valueOr(1), 1u) - 1);
+ default:
+ return EditorDOMPointType(mContent);
+ }
}
/**
- * PointAfterContent() and RawPointAfterContent() retrun the position after
- * found visible content or reached block element.
+ * PointAfterReachedContent() returns the position after found visible content
+ * or reached block element.
*/
template <typename EditorDOMPointType>
- EditorDOMPointType PointAfterContent() const {
+ EditorDOMPointType PointAfterReachedContent() const {
MOZ_ASSERT(mContent);
- return mContent ? EditorDOMPointType::After(mContent)
- : EditorDOMPointType();
+ return PointAtReachedContent<EditorDOMPointType>().template NextPoint();
}
/**
@@ -337,6 +380,13 @@ class MOZ_STACK_CLASS WSScanResult final {
}
/**
+ * The scanner reached inline editing host boundary.
+ */
+ [[nodiscard]] bool ReachedInlineEditingHostBoundary() const {
+ return mReason == WSType::InlineEditingHostBoundary;
+ }
+
+ /**
* The scanner reached something non-text node.
*/
bool ReachedSomethingNonTextContent() const {
@@ -347,6 +397,7 @@ class MOZ_STACK_CLASS WSScanResult final {
nsCOMPtr<nsIContent> mContent;
Maybe<uint32_t> mOffset;
WSType mReason;
+ ScanDirection mDirection = ScanDirection::Backward;
};
class MOZ_STACK_CLASS WSRunScanner final {
@@ -363,25 +414,29 @@ class MOZ_STACK_CLASS WSRunScanner final {
aBlockInlineCheck),
mBlockInlineCheck(aBlockInlineCheck) {}
- // ScanNextVisibleNodeOrBlockBoundaryForwardFrom() returns the first visible
- // node after aPoint. If there is no visible nodes after aPoint, returns
- // topmost editable inline ancestor at end of current block. See comments
- // around WSScanResult for the detail.
+ // ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom() returns the first visible
+ // node at or after aPoint. If there is no visible nodes after aPoint,
+ // returns topmost editable inline ancestor at end of current block. See
+ // comments around WSScanResult for the detail. When you reach a character,
+ // this returns WSScanResult both whose Point_Deprecated() and
+ // PointAtReachedContent() return the found character position.
template <typename PT, typename CT>
- WSScanResult ScanNextVisibleNodeOrBlockBoundaryFrom(
+ WSScanResult ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
const EditorDOMPointBase<PT, CT>& aPoint) const;
template <typename PT, typename CT>
- static WSScanResult ScanNextVisibleNodeOrBlockBoundary(
+ static WSScanResult ScanInclusiveNextVisibleNodeOrBlockBoundary(
const Element* aEditingHost, const EditorDOMPointBase<PT, CT>& aPoint,
BlockInlineCheck aBlockInlineCheck) {
return WSRunScanner(aEditingHost, aPoint, aBlockInlineCheck)
- .ScanNextVisibleNodeOrBlockBoundaryFrom(aPoint);
+ .ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(aPoint);
}
// ScanPreviousVisibleNodeOrBlockBoundaryFrom() returns the first visible node
// before aPoint. If there is no visible nodes before aPoint, returns topmost
// editable inline ancestor at start of current block. See comments around
- // WSScanResult for the detail.
+ // WSScanResult for the detail. When you reach a character, this returns
+ // WSScanResult whose Point_Deprecated() returns next point of the found
+ // character and PointAtReachedContent() returns the point at found character.
template <typename PT, typename CT>
WSScanResult ScanPreviousVisibleNodeOrBlockBoundaryFrom(
const EditorDOMPointBase<PT, CT>& aPoint) const;
@@ -588,6 +643,9 @@ class MOZ_STACK_CLASS WSRunScanner final {
bool StartsFromBlockBoundary() const {
return TextFragmentDataAtStartRef().StartsFromBlockBoundary();
}
+ bool StartsFromInlineEditingHostBoundary() const {
+ return TextFragmentDataAtStartRef().StartsFromInlineEditingHostBoundary();
+ }
bool StartsFromHardLineBreak() const {
return TextFragmentDataAtStartRef().StartsFromHardLineBreak();
}
@@ -618,6 +676,9 @@ class MOZ_STACK_CLASS WSRunScanner final {
bool EndsByBlockBoundary() const {
return TextFragmentDataAtStartRef().EndsByBlockBoundary();
}
+ bool EndsByInlineEditingHostBoundary() const {
+ return TextFragmentDataAtStartRef().EndsByInlineEditingHostBoundary();
+ }
MOZ_NEVER_INLINE_DEBUG Element* StartReasonOtherBlockElementPtr() const {
return TextFragmentDataAtStartRef().StartReasonOtherBlockElementPtr();
@@ -688,6 +749,9 @@ class MOZ_STACK_CLASS WSRunScanner final {
return mRightWSType == WSType::CurrentBlockBoundary ||
mRightWSType == WSType::OtherBlockBoundary;
}
+ bool EndsByInlineEditingHostBoundary() const {
+ return mRightWSType == WSType::InlineEditingHostBoundary;
+ }
/**
* ComparePoint() compares aPoint with the white-spaces.
@@ -916,6 +980,9 @@ class MOZ_STACK_CLASS WSRunScanner final {
return mReason == WSType::CurrentBlockBoundary ||
mReason == WSType::OtherBlockBoundary;
}
+ bool IsInlineEditingHostBoundary() const {
+ return mReason == WSType::InlineEditingHostBoundary;
+ }
bool IsHardLineBreak() const {
return mReason == WSType::CurrentBlockBoundary ||
mReason == WSType::OtherBlockBoundary ||
@@ -950,8 +1017,8 @@ class MOZ_STACK_CLASS WSRunScanner final {
EditorDOMPoint mPoint;
// Must be one of WSType::NotInitialized,
// WSType::NonCollapsibleCharacters, WSType::SpecialContent,
- // WSType::BRElement, WSType::CurrentBlockBoundary or
- // WSType::OtherBlockBoundary.
+ // WSType::BRElement, WSType::CurrentBlockBoundary,
+ // WSType::OtherBlockBoundary or WSType::InlineEditingHostBoundary.
WSType mReason = WSType::NotInitialized;
};
@@ -1027,6 +1094,9 @@ class MOZ_STACK_CLASS WSRunScanner final {
return mStart.IsOtherBlockBoundary();
}
bool StartsFromBlockBoundary() const { return mStart.IsBlockBoundary(); }
+ bool StartsFromInlineEditingHostBoundary() const {
+ return mStart.IsInlineEditingHostBoundary();
+ }
bool StartsFromHardLineBreak() const { return mStart.IsHardLineBreak(); }
bool EndsByNonCollapsibleCharacters() const {
return mEnd.IsNonCollapsibleCharacters();
@@ -1053,6 +1123,9 @@ class MOZ_STACK_CLASS WSRunScanner final {
}
bool EndsByOtherBlockElement() const { return mEnd.IsOtherBlockBoundary(); }
bool EndsByBlockBoundary() const { return mEnd.IsBlockBoundary(); }
+ bool EndsByInlineEditingHostBoundary() const {
+ return mEnd.IsInlineEditingHostBoundary();
+ }
WSType StartRawReason() const { return mStart.RawReason(); }
WSType EndRawReason() const { return mEnd.RawReason(); }
@@ -1224,7 +1297,7 @@ class MOZ_STACK_CLASS WSRunScanner final {
bool FollowingContentMayBecomeFirstVisibleContent(
const EditorDOMPointType& aPoint) const {
MOZ_ASSERT(aPoint.IsSetAndValid());
- if (!mStart.IsHardLineBreak()) {
+ if (!mStart.IsHardLineBreak() && !mStart.IsInlineEditingHostBoundary()) {
return false;
}
// If the point is before start of text fragment, that means that the
@@ -1260,7 +1333,7 @@ class MOZ_STACK_CLASS WSRunScanner final {
MOZ_ASSERT(aPoint.IsSetAndValid());
// If this fragment is ends by block boundary, always the caller needs
// additional check.
- if (mEnd.IsBlockBoundary()) {
+ if (mEnd.IsBlockBoundary() || mEnd.IsInlineEditingHostBoundary()) {
return true;
}