diff --git a/editor/libeditor/EditorDOMPoint.h b/editor/libeditor/EditorDOMPoint.h
index ff420066787d..2330c52eb93f 100644
--- a/editor/libeditor/EditorDOMPoint.h
+++ b/editor/libeditor/EditorDOMPoint.h
@@ -373,6 +373,11 @@ class EditorDOMPointBase final {
}
SetToEndOf(parentNode);
}
+ static SelfType After(const nsINode& aContainer) {
+ SelfType point;
+ point.SetAfter(&aContainer);
+ return point;
+ }
/**
* Clear() makes the instance not point anywhere.
diff --git a/editor/libeditor/HTMLEditSubActionHandler.cpp b/editor/libeditor/HTMLEditSubActionHandler.cpp
index 1801b58f9bbe..986c2c2a9ce8 100644
--- a/editor/libeditor/HTMLEditSubActionHandler.cpp
+++ b/editor/libeditor/HTMLEditSubActionHandler.cpp
@@ -1920,8 +1920,7 @@ nsresult HTMLEditor::InsertBRElement(const EditorDOMPoint& aPointToBreak) {
return NS_ERROR_INVALID_ARG;
}
- bool brElementIsAfterBlock = false;
- bool brElementIsBeforeBlock = false;
+ bool brElementIsAfterBlock = false, brElementIsBeforeBlock = false;
// First, insert a element.
RefPtr brElement;
@@ -1936,15 +1935,12 @@ nsresult HTMLEditor::InsertBRElement(const EditorDOMPoint& aPointToBreak) {
} else {
EditorDOMPoint pointToBreak(aPointToBreak);
WSRunObject wsObj(this, pointToBreak);
- WSType wsType;
- wsObj.PriorVisibleNode(pointToBreak, &wsType);
- if (wsType & WSType::block) {
- brElementIsAfterBlock = true;
- }
- wsObj.NextVisibleNode(pointToBreak, &wsType);
- if (wsType & WSType::block) {
- brElementIsBeforeBlock = true;
- }
+ brElementIsAfterBlock =
+ wsObj.ScanPreviousVisibleNodeOrBlockBoundaryFrom(pointToBreak)
+ .ReachedBlockBoundary();
+ brElementIsBeforeBlock =
+ wsObj.ScanNextVisibleNodeOrBlockBoundaryFrom(pointToBreak)
+ .ReachedBlockBoundary();
// If the container of the break is a link, we need to split it and
// insert new between the split links.
RefPtr linkNode =
@@ -2003,22 +1999,21 @@ nsresult HTMLEditor::InsertBRElement(const EditorDOMPoint& aPointToBreak) {
DebugOnly advanced = afterBRElement.AdvanceOffset();
NS_WARNING_ASSERTION(advanced,
"Failed to advance offset after the new element");
- WSRunObject wsObj(this, afterBRElement);
- nsCOMPtr maybeSecondBRNode;
- WSType wsType;
- wsObj.NextVisibleNode(afterBRElement, address_of(maybeSecondBRNode), nullptr,
- &wsType);
- if (wsType == WSType::br) {
+ WSScanResult forwardScanFromAfterBRElementResult =
+ WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(*this, afterBRElement);
+ if (forwardScanFromAfterBRElementResult.ReachedBRElement()) {
// The next thing after the break we inserted is another break. Move the
// second break to be the first break's sibling. This will prevent them
// from being in different inline nodes, which would break
// SetInterlinePosition(). It will also assure that if the user clicks
// away and then clicks back on their new blank line, they will still get
// the style from the line above.
- EditorDOMPoint atSecondBRElement(maybeSecondBRNode);
- if (brElement->GetNextSibling() != maybeSecondBRNode) {
+ if (brElement->GetNextSibling() !=
+ forwardScanFromAfterBRElementResult.BRElementPtr()) {
+ MOZ_ASSERT(forwardScanFromAfterBRElementResult.BRElementPtr());
nsresult rv = MoveNodeWithTransaction(
- MOZ_KnownLive(*maybeSecondBRNode->AsContent()), afterBRElement);
+ MOZ_KnownLive(*forwardScanFromAfterBRElementResult.BRElementPtr()),
+ afterBRElement);
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
}
@@ -2077,18 +2072,14 @@ EditActionResult HTMLEditor::SplitMailCiteElements(
// 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.
- WSRunObject wsObj(this, pointToSplit);
- nsCOMPtr visNode;
- WSType wsType;
- wsObj.NextVisibleNode(pointToSplit, address_of(visNode), nullptr, &wsType);
+ WSScanResult forwardScanFromPointToSplitResult =
+ WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(*this, pointToSplit);
// If selection start point is before a break and it's inside the mailquote,
// let's split it after the visible node.
- if (wsType == WSType::br && visNode != citeNode &&
- citeNode->Contains(visNode)) {
- pointToSplit.Set(visNode);
- DebugOnly advanced = pointToSplit.AdvanceOffset();
- NS_WARNING_ASSERTION(advanced,
- "Failed to advance offset to after the visible node");
+ if (forwardScanFromPointToSplitResult.ReachedBRElement() &&
+ forwardScanFromPointToSplitResult.BRElementPtr() != citeNode &&
+ citeNode->Contains(forwardScanFromPointToSplitResult.BRElementPtr())) {
+ pointToSplit = forwardScanFromPointToSplitResult.PointAfterContent();
}
if (NS_WARN_IF(!pointToSplit.GetContainerAsContent())) {
@@ -2174,21 +2165,26 @@ EditActionResult HTMLEditor::SplitMailCiteElements(
EditorDOMPoint pointToCreateNewBrNode(atBrNode.GetContainer(),
atBrNode.Offset());
- WSRunObject wsObj(this, pointToCreateNewBrNode);
- WSType wsType;
- wsObj.PriorVisibleNode(pointToCreateNewBrNode, nullptr, nullptr, &wsType);
- if (wsType == WSType::normalWS || wsType == WSType::text ||
- wsType == WSType::special) {
+ WSScanResult backwardScanFromPointToCreateNewBRElementResult =
+ WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
+ *this, pointToCreateNewBrNode);
+ if (backwardScanFromPointToCreateNewBRElementResult
+ .InNormalWhiteSpacesOrText() ||
+ backwardScanFromPointToCreateNewBRElementResult
+ .ReachedSpecialContent()) {
EditorRawDOMPoint pointAfterNewBrNode(pointToCreateNewBrNode);
DebugOnly advanced = pointAfterNewBrNode.AdvanceOffset();
NS_WARNING_ASSERTION(advanced,
"Failed to advance offset after the node");
- WSRunObject wsObjAfterBR(this, pointAfterNewBrNode);
- wsObjAfterBR.NextVisibleNode(pointAfterNewBrNode, &wsType);
- if (wsType == WSType::normalWS || wsType == WSType::text ||
- wsType == WSType::special ||
+ WSScanResult forwardScanFromPointAfterNewBRElementResult =
+ WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(*this,
+ pointAfterNewBrNode);
+ if (forwardScanFromPointAfterNewBRElementResult
+ .InNormalWhiteSpacesOrText() ||
+ forwardScanFromPointAfterNewBRElementResult.ReachedSpecialContent() ||
// In case we're at the very end.
- wsType == WSType::thisBlock) {
+ forwardScanFromPointAfterNewBRElementResult
+ .ReachedCurrentBlockBoundary()) {
brElement = InsertBRElementWithTransaction(pointToCreateNewBrNode);
if (NS_WARN_IF(Destroyed())) {
return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
@@ -2397,24 +2393,15 @@ EditActionResult HTMLEditor::HandleDeleteAroundCollapsedSelection(
// What's in the direction we are deleting?
WSRunObject wsObj(this, startPoint);
- nsCOMPtr visibleNode;
- int32_t visibleNodeOffset;
- WSType wsType;
-
- // Find next visible node
- if (aDirectionAndAmount == nsIEditor::eNext) {
- wsObj.NextVisibleNode(startPoint, address_of(visibleNode),
- &visibleNodeOffset, &wsType);
- } else {
- wsObj.PriorVisibleNode(startPoint, address_of(visibleNode),
- &visibleNodeOffset, &wsType);
- }
-
- if (!visibleNode) {
+ WSScanResult scanFromStartPointResult =
+ aDirectionAndAmount == nsIEditor::eNext
+ ? wsObj.ScanNextVisibleNodeOrBlockBoundaryFrom(startPoint)
+ : wsObj.ScanPreviousVisibleNodeOrBlockBoundaryFrom(startPoint);
+ if (!scanFromStartPointResult.GetContent()) {
return EditActionCanceled();
}
- if (wsType == WSType::normalWS) {
+ if (scanFromStartPointResult.InNormalWhiteSpaces()) {
EditActionResult result =
HandleDeleteCollapsedSelectionAtWhiteSpaces(aDirectionAndAmount, wsObj);
NS_WARNING_ASSERTION(
@@ -2423,53 +2410,53 @@ EditActionResult HTMLEditor::HandleDeleteAroundCollapsedSelection(
return result;
}
- if (wsType == WSType::text) {
- if (NS_WARN_IF(!visibleNode->IsText())) {
+ if (scanFromStartPointResult.InNormalText()) {
+ if (NS_WARN_IF(!scanFromStartPointResult.GetContent()->IsText())) {
return EditActionResult(NS_ERROR_FAILURE);
}
EditActionResult result = HandleDeleteCollapsedSelectionAtTextNode(
- aDirectionAndAmount, EditorDOMPoint(visibleNode, visibleNodeOffset));
+ aDirectionAndAmount, scanFromStartPointResult.Point());
NS_WARNING_ASSERTION(result.Succeeded(),
"HandleDeleteCollapsedSelectionAtTextNode() failed");
return result;
}
- if (wsType == WSType::special || wsType == WSType::br ||
- visibleNode->IsHTMLElement(nsGkAtoms::hr)) {
- if (NS_WARN_IF(!visibleNode->IsContent())) {
- return EditActionResult(NS_ERROR_FAILURE);
- }
+ if (scanFromStartPointResult.ReachedSpecialContent() ||
+ scanFromStartPointResult.ReachedBRElement() ||
+ scanFromStartPointResult.ReachedHRElement()) {
EditActionResult result = HandleDeleteCollapsedSelectionAtAtomicContent(
aDirectionAndAmount, aStripWrappers,
- MOZ_KnownLive(*visibleNode->AsContent()), startPoint, wsObj);
+ MOZ_KnownLive(*scanFromStartPointResult.GetContent()), startPoint,
+ wsObj);
NS_WARNING_ASSERTION(
result.Succeeded(),
"HandleDeleteCollapsedSelectionAtAtomicContent() failed");
return result;
}
- if (wsType == WSType::otherBlock) {
- if (NS_WARN_IF(!visibleNode->IsElement())) {
+ if (scanFromStartPointResult.ReachedOtherBlockElement()) {
+ if (NS_WARN_IF(!scanFromStartPointResult.GetContent()->IsElement())) {
return EditActionResult(NS_ERROR_FAILURE);
}
EditActionResult result =
HandleDeleteCollapsedSelectionAtOtherBlockBoundary(
aDirectionAndAmount, aStripWrappers,
- MOZ_KnownLive(*visibleNode->AsElement()), startPoint, wsObj);
+ MOZ_KnownLive(*scanFromStartPointResult.ElementPtr()), startPoint,
+ wsObj);
NS_WARNING_ASSERTION(
result.Succeeded(),
"HandleDeleteCollapsedSelectionAtOtherBlockBoundary() failed");
return result;
}
- if (wsType == WSType::thisBlock) {
- if (NS_WARN_IF(!visibleNode->IsElement())) {
+ if (scanFromStartPointResult.ReachedCurrentBlockBoundary()) {
+ if (NS_WARN_IF(!scanFromStartPointResult.GetContent()->IsElement())) {
return EditActionResult(NS_ERROR_FAILURE);
}
EditActionResult result =
HandleDeleteCollapsedSelectionAtCurrentBlockBoundary(
- aDirectionAndAmount, MOZ_KnownLive(*visibleNode->AsElement()),
- startPoint);
+ aDirectionAndAmount,
+ MOZ_KnownLive(*scanFromStartPointResult.ElementPtr()), startPoint);
NS_WARNING_ASSERTION(
result.Succeeded(),
"HandleDeleteCollapsedSelectionAtCurrentBlockBoundary() failed");
@@ -2608,12 +2595,12 @@ EditActionResult HTMLEditor::HandleDeleteCollapsedSelectionAtTextNode(
EditActionResult HTMLEditor::HandleDeleteCollapsedSelectionAtAtomicContent(
nsIEditor::EDirection aDirectionAndAmount,
nsIEditor::EStripWrappers aStripWrappers, nsIContent& aAtomicContent,
- const EditorDOMPoint& aCaretPoint, WSRunObject& aWSRunObjectAtCaret) {
+ const EditorDOMPoint& aCaretPoint, WSRunScanner& aWSRunScannerAtCaret) {
MOZ_ASSERT(IsEditActionDataAvailable());
MOZ_ASSERT(aCaretPoint.IsSet());
// If the atomic element is editing host, we should do nothing.
- if (&aAtomicContent == aWSRunObjectAtCaret.GetEditingHost()) {
+ if (&aAtomicContent == aWSRunScannerAtCaret.GetEditingHost()) {
return EditActionHandled();
}
@@ -2700,30 +2687,24 @@ EditActionResult HTMLEditor::HandleDeleteCollapsedSelectionAtAtomicContent(
// There is one exception to the move only case. If the is
// followed by a we want to delete the .
- WSType otherWSType;
- nsCOMPtr otherNode;
-
- aWSRunObjectAtCaret.NextVisibleNode(aCaretPoint, address_of(otherNode),
- nullptr, &otherWSType);
-
- if (otherWSType != WSType::br) {
+ WSScanResult forwardScanFromCaretResult =
+ aWSRunScannerAtCaret.ScanNextVisibleNodeOrBlockBoundaryFrom(
+ aCaretPoint);
+ if (!forwardScanFromCaretResult.ReachedBRElement()) {
return EditActionHandled();
}
// Delete the
- if (NS_WARN_IF(!otherNode->IsContent())) {
- return EditActionHandled(NS_ERROR_FAILURE);
- }
- nsIContent* otherContent = otherNode->AsContent();
- nsresult rv =
- WSRunObject::PrepareToDeleteNode(this, MOZ_KnownLive(otherContent));
+ nsresult rv = WSRunObject::PrepareToDeleteNode(
+ this, MOZ_KnownLive(forwardScanFromCaretResult.BRElementPtr()));
if (NS_WARN_IF(Destroyed())) {
return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
}
if (NS_WARN_IF(NS_FAILED(rv))) {
return EditActionHandled(rv);
}
- rv = DeleteNodeWithTransaction(MOZ_KnownLive(*otherContent));
+ rv = DeleteNodeWithTransaction(
+ MOZ_KnownLive(*forwardScanFromCaretResult.BRElementPtr()));
if (NS_WARN_IF(Destroyed())) {
return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
}
@@ -2799,7 +2780,7 @@ EditActionResult HTMLEditor::HandleDeleteCollapsedSelectionAtAtomicContent(
EditActionResult HTMLEditor::HandleDeleteCollapsedSelectionAtOtherBlockBoundary(
nsIEditor::EDirection aDirectionAndAmount,
nsIEditor::EStripWrappers aStripWrappers, Element& aOtherBlockElement,
- const EditorDOMPoint& aCaretPoint, WSRunObject& aWSRunObjectAtCaret) {
+ const EditorDOMPoint& aCaretPoint, WSRunScanner& aWSRunScannerAtCaret) {
MOZ_ASSERT(IsEditActionDataAvailable());
MOZ_ASSERT(aCaretPoint.IsSet());
@@ -2812,17 +2793,12 @@ EditActionResult HTMLEditor::HandleDeleteCollapsedSelectionAtOtherBlockBoundary(
// Next to a block. See if we are between a block and a br. If so, we
// really want to delete the br. Else join content at selection to the
// block.
- WSType otherWSType;
- nsCOMPtr otherNode;
-
- // Find node in other direction
- if (aDirectionAndAmount == nsIEditor::eNext) {
- aWSRunObjectAtCaret.PriorVisibleNode(aCaretPoint, address_of(otherNode),
- nullptr, &otherWSType);
- } else {
- aWSRunObjectAtCaret.NextVisibleNode(aCaretPoint, address_of(otherNode),
- nullptr, &otherWSType);
- }
+ WSScanResult scanFromCaretResult =
+ aDirectionAndAmount == nsIEditor::eNext
+ ? aWSRunScannerAtCaret.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
+ aCaretPoint)
+ : aWSRunScannerAtCaret.ScanNextVisibleNodeOrBlockBoundaryFrom(
+ aCaretPoint);
// First find the adjacent node in the block
nsCOMPtr leafNode;
@@ -2838,8 +2814,9 @@ EditActionResult HTMLEditor::HandleDeleteCollapsedSelectionAtOtherBlockBoundary(
}
bool didBRElementDeleted = false;
- if (otherNode->IsHTMLElement(nsGkAtoms::br)) {
- nsresult rv = DeleteNodeWithTransaction(*otherNode);
+ if (scanFromCaretResult.ReachedBRElement()) {
+ nsresult rv = DeleteNodeWithTransaction(
+ MOZ_KnownLive(*scanFromCaretResult.BRElementPtr()));
if (NS_WARN_IF(Destroyed())) {
return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
}
@@ -6895,20 +6872,20 @@ HTMLEditor::GetExtendedRangeToIncludeInvisibleNodes(
if (atStart.GetContainer() != commonAncestorBlock &&
atStart.GetContainer() != editingHost) {
for (;;) {
- WSRunObject wsObj(this, atStart);
- WSType wsType;
- wsObj.PriorVisibleNode(atStart, &wsType);
- if (wsType != WSType::thisBlock) {
+ WSRunScanner wsScannerAtStart(this, atStart);
+ if (!wsScannerAtStart.ScanPreviousVisibleNodeOrBlockBoundaryFrom(atStart)
+ .ReachedCurrentBlockBoundary()) {
break;
}
// We want to keep looking up. But stop if we are crossing table
// element boundaries, or if we hit the root.
- if (HTMLEditUtils::IsTableElement(wsObj.GetStartReasonContent()) ||
- wsObj.GetStartReasonContent() == commonAncestorBlock ||
- wsObj.GetStartReasonContent() == editingHost) {
+ if (HTMLEditUtils::IsTableElement(
+ wsScannerAtStart.GetStartReasonContent()) ||
+ wsScannerAtStart.GetStartReasonContent() == commonAncestorBlock ||
+ wsScannerAtStart.GetStartReasonContent() == editingHost) {
break;
}
- atStart.Set(wsObj.GetStartReasonContent());
+ atStart.Set(wsScannerAtStart.GetStartReasonContent());
}
}
@@ -6921,31 +6898,30 @@ HTMLEditor::GetExtendedRangeToIncludeInvisibleNodes(
atEnd.GetContainer() != editingHost) {
EditorDOMPoint atFirstInvisibleBRElement;
for (;;) {
- WSRunObject wsObj(this, atEnd);
- WSType wsType;
- wsObj.NextVisibleNode(atEnd, &wsType);
- if (wsType == WSType::br) {
- if (IsVisibleBRElement(wsObj.GetEndReasonContent())) {
+ WSRunScanner wsScannerAtEnd(this, atEnd);
+ WSScanResult forwardScanFromEndResult =
+ wsScannerAtEnd.ScanNextVisibleNodeOrBlockBoundaryFrom(atEnd);
+ if (forwardScanFromEndResult.ReachedBRElement()) {
+ if (IsVisibleBRElement(wsScannerAtEnd.GetEndReasonContent())) {
break;
}
if (!atFirstInvisibleBRElement.IsSet()) {
atFirstInvisibleBRElement = atEnd;
}
- atEnd.Set(wsObj.GetEndReasonContent());
- atEnd.AdvanceOffset();
+ atEnd.SetAfter(wsScannerAtEnd.GetEndReasonContent());
continue;
}
- if (wsType == WSType::thisBlock) {
+ if (forwardScanFromEndResult.ReachedCurrentBlockBoundary()) {
// We want to keep looking up. But stop if we are crossing table
// element boundaries, or if we hit the root.
- if (HTMLEditUtils::IsTableElement(wsObj.GetEndReasonContent()) ||
- wsObj.GetEndReasonContent() == commonAncestorBlock ||
- wsObj.GetEndReasonContent() == editingHost) {
+ if (HTMLEditUtils::IsTableElement(
+ wsScannerAtEnd.GetEndReasonContent()) ||
+ wsScannerAtEnd.GetEndReasonContent() == commonAncestorBlock ||
+ wsScannerAtEnd.GetEndReasonContent() == editingHost) {
break;
}
- atEnd.Set(wsObj.GetEndReasonContent());
- atEnd.AdvanceOffset();
+ atEnd.SetAfter(wsScannerAtEnd.GetEndReasonContent());
continue;
}
@@ -7022,63 +6998,59 @@ nsresult HTMLEditor::MaybeExtendSelectionToHardLineEdgesForBlockEditAction() {
EditorDOMPoint newStartPoint(startPoint);
EditorDOMPoint newEndPoint(endPoint);
- // some locals we need for whitespace code
- WSType wsType;
-
- // let the whitespace code do the heavy lifting
- WSRunObject wsEndObj(this, endPoint);
// Is there any intervening visible whitespace? If so we can't push
// selection past that, it would visibly change meaning of users selection.
- wsEndObj.PriorVisibleNode(endPoint, &wsType);
- if (wsType != WSType::text && wsType != WSType::normalWS) {
+ WSRunScanner wsScannerAtEnd(this, endPoint);
+ if (wsScannerAtEnd.ScanPreviousVisibleNodeOrBlockBoundaryFrom(endPoint)
+ .ReachedSomething()) {
// eThisBlock and eOtherBlock conveniently distinguish cases
// of going "down" into a block and "up" out of a block.
- if (wsEndObj.mStartReason == WSType::otherBlock) {
+ if (wsScannerAtEnd.StartReason() == WSType::otherBlock) {
// endpoint is just after the close of a block.
nsINode* child =
- GetRightmostChild(wsEndObj.GetStartReasonContent(), true);
+ GetRightmostChild(wsScannerAtEnd.GetStartReasonContent(), true);
if (child) {
newEndPoint.SetAfter(child);
}
// else block is empty - we can leave selection alone here, i think.
- } else if (wsEndObj.mStartReason == WSType::thisBlock) {
+ } else if (wsScannerAtEnd.StartReason() == WSType::thisBlock) {
// endpoint is just after start of this block
nsINode* child = GetPreviousEditableHTMLNode(endPoint);
if (child) {
newEndPoint.SetAfter(child);
}
// else block is empty - we can leave selection alone here, i think.
- } else if (wsEndObj.mStartReason == WSType::br) {
+ } else if (wsScannerAtEnd.StartReason() == WSType::br) {
// endpoint is just after break. lets adjust it to before it.
- newEndPoint.Set(wsEndObj.GetStartReasonContent());
+ newEndPoint.Set(wsScannerAtEnd.GetStartReasonContent());
}
}
- // similar dealio for start of range
- WSRunObject wsStartObj(this, startPoint);
// Is there any intervening visible whitespace? If so we can't push
// selection past that, it would visibly change meaning of users selection.
- wsStartObj.NextVisibleNode(startPoint, &wsType);
- if (wsType != WSType::text && wsType != WSType::normalWS) {
+ WSRunScanner wsScannerAtStart(this, startPoint);
+ if (wsScannerAtStart.ScanNextVisibleNodeOrBlockBoundaryFrom(startPoint)
+ .ReachedSomething()) {
// eThisBlock and eOtherBlock conveniently distinguish cases
// of going "down" into a block and "up" out of a block.
- if (wsStartObj.mEndReason == WSType::otherBlock) {
+ if (wsScannerAtStart.EndReason() == WSType::otherBlock) {
// startpoint is just before the start of a block.
- nsINode* child = GetLeftmostChild(wsStartObj.GetEndReasonContent(), true);
+ nsINode* child =
+ GetLeftmostChild(wsScannerAtStart.GetEndReasonContent(), true);
if (child) {
newStartPoint.Set(child);
}
// else block is empty - we can leave selection alone here, i think.
- } else if (wsStartObj.mEndReason == WSType::thisBlock) {
+ } else if (wsScannerAtStart.EndReason() == WSType::thisBlock) {
// startpoint is just before end of this block
nsINode* child = GetNextEditableHTMLNode(startPoint);
if (child) {
newStartPoint.Set(child);
}
// else block is empty - we can leave selection alone here, i think.
- } else if (wsStartObj.mEndReason == WSType::br) {
+ } else if (wsScannerAtStart.EndReason() == WSType::br) {
// startpoint is just before a break. lets adjust it to after it.
- newStartPoint.SetAfter(wsStartObj.GetEndReasonContent());
+ newStartPoint.SetAfter(wsScannerAtStart.GetEndReasonContent());
}
}
@@ -8635,20 +8607,19 @@ nsresult HTMLEditor::HandleInsertParagraphInListItemElement(Element& aListItem,
return NS_OK;
}
} else {
- WSRunObject wsObj(this, &aListItem, 0);
- nsCOMPtr visNode;
- int32_t visOffset = 0;
- WSType wsType;
- wsObj.NextVisibleNode(EditorRawDOMPoint(&aListItem, 0),
- address_of(visNode), &visOffset, &wsType);
- if (wsType == WSType::special || wsType == WSType::br ||
- visNode->IsHTMLElement(nsGkAtoms::hr)) {
- EditorRawDOMPoint atVisNode(visNode);
- if (NS_WARN_IF(!atVisNode.IsSetAndValid())) {
+ WSScanResult forwardScanFromStartOfListItemResult =
+ WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
+ *this, EditorRawDOMPoint(&aListItem, 0));
+ if (forwardScanFromStartOfListItemResult.ReachedSpecialContent() ||
+ forwardScanFromStartOfListItemResult.ReachedBRElement() ||
+ forwardScanFromStartOfListItemResult.ReachedHRElement()) {
+ EditorRawDOMPoint atFoundElement(
+ forwardScanFromStartOfListItemResult.RawPointAtContent());
+ if (NS_WARN_IF(!atFoundElement.IsSetAndValid())) {
return NS_ERROR_FAILURE;
}
ErrorResult error;
- SelectionRefPtr()->Collapse(atVisNode, error);
+ SelectionRefPtr()->Collapse(atFoundElement, error);
if (NS_WARN_IF(Destroyed())) {
error.SuppressException();
return NS_ERROR_EDITOR_DESTROYED;
@@ -8659,7 +8630,11 @@ nsresult HTMLEditor::HandleInsertParagraphInListItemElement(Element& aListItem,
return NS_OK;
}
- rv = SelectionRefPtr()->Collapse(visNode, visOffset);
+ // XXX This may be not meaningful position if it reached block element
+ // in aListItem.
+ rv = SelectionRefPtr()->Collapse(
+ forwardScanFromStartOfListItemResult.RawPoint()
+ .ToRawRangeBoundary());
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
}
diff --git a/editor/libeditor/HTMLEditor.cpp b/editor/libeditor/HTMLEditor.cpp
index d7265787ca6c..375f5edc436a 100644
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -541,49 +541,47 @@ nsresult HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode(
// Find first editable and visible node.
EditorRawDOMPoint pointToPutCaret(editingHost, 0);
for (;;) {
- WSRunObject wsObj(this, pointToPutCaret.GetContainer(),
- pointToPutCaret.Offset());
- int32_t visOffset = 0;
- WSType visType;
- nsCOMPtr visNode;
- wsObj.NextVisibleNode(pointToPutCaret, address_of(visNode), &visOffset,
- &visType);
-
+ WSScanResult forwardScanFromPointToPutCaretResult =
+ WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(*this,
+ pointToPutCaret);
// If we meet a non-editable node first, we should move caret to start of
// the editing host (perhaps, user may want to insert something before
// the first non-editable node? Chromium behaves so).
- if (visNode && !visNode->IsEditable()) {
+ if (forwardScanFromPointToPutCaretResult.GetContent() &&
+ !forwardScanFromPointToPutCaretResult.IsContentEditable()) {
pointToPutCaret.Set(editingHost, 0);
break;
}
- // WSRunObject::NextVisibleNode() returns WSType::special and the "special"
- // node when it meets empty inline element. In this case, we should go to
- // next sibling. For example, if current editor is:
- //
+ // WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() returns
+ // WSType::special and the "special" node when it meets empty inline
+ // element. In this case, we should go to next sibling. For example, if
+ // current editor is:
// then, we should put caret at the element. So, let's check if
// found node is an empty inline container element.
- if (visType == WSType::special && visNode &&
- TagCanContainTag(*visNode->NodeInfo()->NameAtom(),
+ if (forwardScanFromPointToPutCaretResult.ReachedSpecialContent() &&
+ forwardScanFromPointToPutCaretResult.GetContent() &&
+ TagCanContainTag(*forwardScanFromPointToPutCaretResult.GetContent()
+ ->NodeInfo()
+ ->NameAtom(),
*nsGkAtoms::textTagName)) {
- pointToPutCaret.Set(visNode);
- DebugOnly advanced = pointToPutCaret.AdvanceOffset();
- NS_WARNING_ASSERTION(
- advanced,
- "Failed to advance offset from found empty inline container element");
+ pointToPutCaret =
+ forwardScanFromPointToPutCaretResult.RawPointAfterContent();
continue;
}
// If there is editable and visible text node, move caret at start of it.
- if (visType == WSType::normalWS || visType == WSType::text) {
- pointToPutCaret.Set(visNode, visOffset);
+ if (forwardScanFromPointToPutCaretResult.InNormalWhiteSpacesOrText()) {
+ pointToPutCaret = forwardScanFromPointToPutCaretResult.RawPoint();
break;
}
// If there is editable or something inline special element like
// , , etc, move caret before it.
- if (visType == WSType::br || visType == WSType::special) {
- pointToPutCaret.Set(visNode);
+ if (forwardScanFromPointToPutCaretResult.ReachedBRElement() ||
+ forwardScanFromPointToPutCaretResult.ReachedSpecialContent()) {
+ pointToPutCaret =
+ forwardScanFromPointToPutCaretResult.RawPointAtContent();
break;
}
@@ -592,34 +590,34 @@ nsresult HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode(
// host.
// XXX This may not make sense, but Chromium behaves so. Therefore, the
// reason why we do this is just compatibility with Chromium.
- if (visType != WSType::otherBlock) {
+ if (!forwardScanFromPointToPutCaretResult.ReachedOtherBlockElement()) {
pointToPutCaret.Set(editingHost, 0);
break;
}
- // By definition of WSRunObject, a block element terminates a whitespace
+ // By definition of WSRunScanner, a block element terminates a whitespace
// run. That is, although we are calling a method that is named
- // "NextVisibleNode", the node returned might not be visible/editable!
+ // "ScanNextVisibleNodeOrBlockBoundary", the node returned might not
+ // be visible/editable!
// However, we were given a block that is not a container. Since the
// block can not contain anything that's visible, such a block only
// makes sense if it is visible by itself, like a . We want to
// place the caret in front of that block.
- if (!IsContainer(visNode)) {
- pointToPutCaret.Set(visNode);
+ if (!IsContainer(forwardScanFromPointToPutCaretResult.GetContent())) {
+ pointToPutCaret =
+ forwardScanFromPointToPutCaretResult.RawPointAtContent();
break;
}
// If the given block does not contain any visible/editable items, we want
// to skip it and continue our search.
- if (IsEmptyNode(*visNode)) {
+ if (IsEmptyNode(*forwardScanFromPointToPutCaretResult.GetContent())) {
// Skip the empty block
- pointToPutCaret.Set(visNode);
- DebugOnly advanced = pointToPutCaret.AdvanceOffset();
- NS_WARNING_ASSERTION(
- advanced, "Failed to advance offset from the found empty block node");
+ pointToPutCaret =
+ forwardScanFromPointToPutCaretResult.RawPointAfterContent();
} else {
- pointToPutCaret.Set(visNode, 0);
+ pointToPutCaret.Set(forwardScanFromPointToPutCaretResult.GetContent(), 0);
}
}
nsresult rv = SelectionRefPtr()->Collapse(pointToPutCaret);
@@ -934,18 +932,13 @@ bool HTMLEditor::IsVisibleBRElement(nsINode* aNode) {
// Sigh. We have to use expensive whitespace calculation code to
// determine what is going on
- int32_t selOffset;
- nsCOMPtr selNode = GetNodeLocation(aNode, &selOffset);
- // Let's look after the break
- selOffset++;
- WSRunObject wsObj(this, selNode, selOffset);
- WSType visType;
- wsObj.NextVisibleNode(EditorRawDOMPoint(selNode, selOffset), &visType);
- if (visType & WSType::block) {
+ EditorRawDOMPoint afterBRElement(EditorRawDOMPoint::After(*aNode));
+ if (NS_WARN_IF(!afterBRElement.IsSet())) {
return false;
}
-
- return true;
+ return !WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(*this,
+ afterBRElement)
+ .ReachedBlockBoundary();
}
NS_IMETHODIMP
@@ -1474,18 +1467,18 @@ EditorRawDOMPoint HTMLEditor::GetBetterInsertionPointFor(
return pointToInsert;
}
- WSRunObject wsObj(this, pointToInsert.GetContainer(), pointToInsert.Offset());
+ WSRunScanner wsScannerForPointToInsert(this, pointToInsert);
// 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 ,
// we want to skip to the position just after the line break (see bug 68767).
- nsCOMPtr nextVisibleNode;
- WSType nextVisibleType;
- wsObj.NextVisibleNode(pointToInsert, address_of(nextVisibleNode), nullptr,
- &nextVisibleType);
+ WSScanResult forwardScanFromPointToInsertResult =
+ wsScannerForPointToInsert.ScanNextVisibleNodeOrBlockBoundaryFrom(
+ pointToInsert);
// So, if the next visible node isn't a element, we can insert the block
// level element to the point.
- if (!nextVisibleNode || !(nextVisibleType & WSType::br)) {
+ if (!forwardScanFromPointToInsertResult.GetContent() ||
+ !forwardScanFromPointToInsertResult.ReachedBRElement()) {
return pointToInsert;
}
@@ -1493,25 +1486,21 @@ EditorRawDOMPoint HTMLEditor::GetBetterInsertionPointFor(
// positioned at the beginning of a block, in that case skipping the
// would not insert the at the caret position, but after the current
// empty line.
- nsCOMPtr previousVisibleNode;
- WSType previousVisibleType;
- wsObj.PriorVisibleNode(pointToInsert, address_of(previousVisibleNode),
- nullptr, &previousVisibleType);
+ WSScanResult backwardScanFromPointToInsertResult =
+ wsScannerForPointToInsert.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
+ pointToInsert);
// So, if there is no previous visible node,
// or, if both nodes of the insertion point is elements,
// or, if the previous visible node is different block,
// we need to skip the following . So, otherwise, we can insert the
// block at the insertion point.
- if (!previousVisibleNode || (previousVisibleType & WSType::br) ||
- (previousVisibleType & WSType::thisBlock)) {
+ if (!backwardScanFromPointToInsertResult.GetContent() ||
+ backwardScanFromPointToInsertResult.ReachedBRElement() ||
+ backwardScanFromPointToInsertResult.ReachedCurrentBlockBoundary()) {
return pointToInsert;
}
- EditorRawDOMPoint afterBRNode(nextVisibleNode);
- DebugOnly advanced = afterBRNode.AdvanceOffset();
- NS_WARNING_ASSERTION(advanced,
- "Failed to advance offset to after the node");
- return afterBRNode;
+ return forwardScanFromPointToInsertResult.RawPointAfterContent();
}
NS_IMETHODIMP
@@ -3128,21 +3117,23 @@ nsresult HTMLEditor::DeleteParentBlocksWithTransactionIfEmpty(
MOZ_ASSERT(mPlaceholderBatch);
// First, check there is visible contents before the point in current block.
- WSRunObject wsObj(this, aPoint);
- if (!(wsObj.mStartReason & WSType::thisBlock)) {
+ WSRunScanner wsScannerForPoint(this, aPoint);
+ if (wsScannerForPoint.StartReason() != WSType::thisBlock) {
// If there is visible node before the point, we shouldn't remove the
// parent block.
return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
}
- if (NS_WARN_IF(!wsObj.GetStartReasonContent()) ||
- NS_WARN_IF(!wsObj.GetStartReasonContent()->GetParentNode())) {
+ if (NS_WARN_IF(!wsScannerForPoint.GetStartReasonContent()) ||
+ NS_WARN_IF(!wsScannerForPoint.GetStartReasonContent()->GetParentNode())) {
return NS_ERROR_FAILURE;
}
- if (wsObj.GetEditingHost() == wsObj.GetStartReasonContent()) {
+ if (wsScannerForPoint.GetEditingHost() ==
+ wsScannerForPoint.GetStartReasonContent()) {
// If we reach editing host, there is no parent blocks which can be removed.
return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
}
- if (HTMLEditUtils::IsTableCellOrCaption(*wsObj.GetStartReasonContent())) {
+ if (HTMLEditUtils::IsTableCellOrCaption(
+ *wsScannerForPoint.GetStartReasonContent())) {
// If we reach a
,
or
, we shouldn't remove it even
// becomes empty because removing such element changes the structure of
// the
.
@@ -3150,34 +3141,33 @@ nsresult HTMLEditor::DeleteParentBlocksWithTransactionIfEmpty(
}
// Next, check there is visible contents after the point in current block.
- WSType wsType = WSType::none;
- wsObj.NextVisibleNode(aPoint, &wsType);
- if (wsType == WSType::br) {
+ WSScanResult forwardScanFromPointResult =
+ wsScannerForPoint.ScanNextVisibleNodeOrBlockBoundaryFrom(aPoint);
+ if (forwardScanFromPointResult.ReachedBRElement()) {
// If the element is visible, we shouldn't remove the parent block.
- if (IsVisibleBRElement(wsObj.GetEndReasonContent())) {
+ if (IsVisibleBRElement(wsScannerForPoint.GetEndReasonContent())) {
return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
}
- if (wsObj.GetEndReasonContent()->GetNextSibling()) {
- EditorRawDOMPoint afterBRElement;
- afterBRElement.SetAfter(wsObj.GetEndReasonContent());
- WSRunObject wsRunObjAfterBR(this, afterBRElement);
- WSType wsTypeAfterBR = WSType::none;
- wsRunObjAfterBR.NextVisibleNode(afterBRElement, &wsTypeAfterBR);
- if (wsTypeAfterBR != WSType::thisBlock) {
+ if (wsScannerForPoint.GetEndReasonContent()->GetNextSibling()) {
+ if (!WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
+ *this, EditorRawDOMPoint::After(
+ *wsScannerForPoint.GetEndReasonContent()))
+ .ReachedCurrentBlockBoundary()) {
// If we couldn't reach the block's end after the invisible ,
// that means that there is visible content.
return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
}
}
- } else if (wsType != WSType::thisBlock) {
+ } else if (!forwardScanFromPointResult.ReachedCurrentBlockBoundary()) {
// If we couldn't reach the block's end, the block has visible content.
return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
}
// Delete the parent block.
- EditorDOMPoint nextPoint(wsObj.GetStartReasonContent()->GetParentNode(), 0);
- nsresult rv =
- DeleteNodeWithTransaction(MOZ_KnownLive(*wsObj.GetStartReasonContent()));
+ EditorDOMPoint nextPoint(
+ wsScannerForPoint.GetStartReasonContent()->GetParentNode(), 0);
+ nsresult rv = DeleteNodeWithTransaction(
+ MOZ_KnownLive(*wsScannerForPoint.GetStartReasonContent()));
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
return NS_ERROR_EDITOR_DESTROYED;
}
@@ -3185,7 +3175,7 @@ nsresult HTMLEditor::DeleteParentBlocksWithTransactionIfEmpty(
return rv;
}
// If we reach editing host, return NS_OK.
- if (nextPoint.GetContainer() == wsObj.GetEditingHost()) {
+ if (nextPoint.GetContainer() == wsScannerForPoint.GetEditingHost()) {
return NS_OK;
}
@@ -3199,7 +3189,7 @@ nsresult HTMLEditor::DeleteParentBlocksWithTransactionIfEmpty(
NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED)) {
Element* editingHost = GetActiveEditingHost();
if (NS_WARN_IF(!editingHost) ||
- NS_WARN_IF(editingHost != wsObj.GetEditingHost())) {
+ NS_WARN_IF(editingHost != wsScannerForPoint.GetEditingHost())) {
return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
}
if (NS_WARN_IF(!EditorUtils::IsDescendantOf(*nextPoint.GetContainer(),
@@ -3970,15 +3960,11 @@ bool HTMLEditor::IsVisibleTextNode(Text& aText) const {
return true;
}
- WSRunScanner wsRunScanner(this, &aText, 0);
- nsCOMPtr nextVisibleNode;
- WSType visibleNodeType;
- wsRunScanner.NextVisibleNode(EditorRawDOMPoint(&aText, 0),
- address_of(nextVisibleNode), nullptr,
- &visibleNodeType);
- return (visibleNodeType == WSType::normalWS ||
- visibleNodeType == WSType::text) &&
- &aText == nextVisibleNode;
+ WSScanResult nextWSScanResult =
+ WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
+ *this, EditorRawDOMPoint(&aText, 0));
+ return nextWSScanResult.InNormalWhiteSpacesOrText() &&
+ nextWSScanResult.TextPtr() == &aText;
}
bool HTMLEditor::IsEmpty() const {
diff --git a/editor/libeditor/HTMLEditor.h b/editor/libeditor/HTMLEditor.h
index e802cd514244..76222088b2de 100644
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -54,6 +54,8 @@ class ResizerSelectionListener;
class SplitRangeOffFromNodeResult;
class SplitRangeOffResult;
class WSRunObject;
+class WSRunScanner;
+class WSScanResult;
enum class EditSubAction : int32_t;
struct PropItem;
template
@@ -2246,14 +2248,14 @@ class HTMLEditor final : public TextEditor,
* @param aAtomicContent The atomic content to be deleted.
* @param aCaretPoint The caret point (i.e., selection start or
* end).
- * @param aWSRunObjectAtCaret WSRunObject instance which was initialized with
- * the caret point.
+ * @param aWSRunScannerAtCaret WSRunScanner instance which was initialized
+ * with the caret point.
*/
MOZ_CAN_RUN_SCRIPT MOZ_MUST_USE EditActionResult
HandleDeleteCollapsedSelectionAtAtomicContent(
nsIEditor::EDirection aDirectionAndAmount,
nsIEditor::EStripWrappers aStripWrappers, nsIContent& aAtomicContent,
- const EditorDOMPoint& aCaretPoint, WSRunObject& aWSRunObjectAtCaret);
+ const EditorDOMPoint& aCaretPoint, WSRunScanner& aWSRunScannerAtCaret);
/**
* HandleDeleteCollapsedSelectionAtOtherBlockBoundary() handles deletion at
@@ -2267,14 +2269,14 @@ class HTMLEditor final : public TextEditor,
* is followed by caret.
* @param aCaretPoint The caret point (i.e., selection start or
* end).
- * @param aWSRunObjectAtCaret WSRunObject instance which was initialized with
- * the caret point.
+ * @param aWSRunScannerAtCaret WSRunScanner instance which was initialized
+ * with the caret point.
*/
MOZ_CAN_RUN_SCRIPT MOZ_MUST_USE EditActionResult
HandleDeleteCollapsedSelectionAtOtherBlockBoundary(
nsIEditor::EDirection aDirectionAndAmount,
nsIEditor::EStripWrappers aStripWrappers, Element& aOtherBlockElement,
- const EditorDOMPoint& aCaretPoint, WSRunObject& aWSRunObjectAtCaret);
+ const EditorDOMPoint& aCaretPoint, WSRunScanner& aWSRunScannerAtCaret);
/**
* HandleDeleteCollapsedSelectionAtCurrentBlockBoundary() handles deletion
@@ -4577,6 +4579,7 @@ class HTMLEditor final : public TextEditor,
friend class TextEditor;
friend class WSRunObject;
friend class WSRunScanner;
+ friend class WSScanResult;
};
/**
diff --git a/editor/libeditor/HTMLEditorDataTransfer.cpp b/editor/libeditor/HTMLEditorDataTransfer.cpp
index abb1c5b648bf..904941f90c7a 100644
--- a/editor/libeditor/HTMLEditorDataTransfer.cpp
+++ b/editor/libeditor/HTMLEditorDataTransfer.cpp
@@ -632,24 +632,21 @@ nsresult HTMLEditor::DoInsertHTMLWithContext(
// Make sure we don't end up with selection collapsed after an invisible
// ` ` element.
- WSRunObject wsRunObj(this, pointToPutCaret);
- WSType visType;
- wsRunObj.PriorVisibleNode(pointToPutCaret, &visType);
- if (visType == WSType::br &&
- !IsVisibleBRElement(wsRunObj.GetStartReasonContent())) {
- WSRunObject wsRunObj2(this,
- EditorDOMPoint(wsRunObj.GetStartReasonContent()));
- nsCOMPtr visibleNode;
- int32_t visibleNodeOffset;
- wsRunObj2.PriorVisibleNode(pointToPutCaret, address_of(visibleNode),
- &visibleNodeOffset, &visType);
- if (visType == WSType::text || visType == WSType::normalWS) {
- pointToPutCaret.Set(visibleNode, visibleNodeOffset);
- } else if (visType == WSType::special) {
- pointToPutCaret.Set(wsRunObj2.GetStartReasonContent());
- DebugOnly advanced = pointToPutCaret.AdvanceOffset();
- NS_WARNING_ASSERTION(advanced,
- "Failed to advance offset from found object");
+ WSRunScanner wsRunScannerAtCaret(this, pointToPutCaret);
+ if (wsRunScannerAtCaret
+ .ScanPreviousVisibleNodeOrBlockBoundaryFrom(pointToPutCaret)
+ .ReachedBRElement() &&
+ !IsVisibleBRElement(wsRunScannerAtCaret.GetStartReasonContent())) {
+ WSRunScanner wsRunScannerAtStartReason(
+ this, EditorDOMPoint(wsRunScannerAtCaret.GetStartReasonContent()));
+ WSScanResult backwardScanFromPointToCaretResult =
+ wsRunScannerAtStartReason.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
+ pointToPutCaret);
+ if (backwardScanFromPointToCaretResult.InNormalWhiteSpacesOrText()) {
+ pointToPutCaret = backwardScanFromPointToCaretResult.Point();
+ } else if (backwardScanFromPointToCaretResult.ReachedSpecialContent()) {
+ pointToPutCaret.SetAfter(
+ wsRunScannerAtStartReason.GetStartReasonContent());
}
}
DebugOnly rvIgnored =
diff --git a/editor/libeditor/WSRunObject.cpp b/editor/libeditor/WSRunObject.cpp
index 134732214706..032caa5c61d8 100644
--- a/editor/libeditor/WSRunObject.cpp
+++ b/editor/libeditor/WSRunObject.cpp
@@ -44,22 +44,14 @@ template WSRunObject::WSRunObject(HTMLEditor* aHTMLEditor,
template WSRunObject::WSRunObject(HTMLEditor* aHTMLEditor,
const EditorRawDOMPoint& aScanStartPoint,
const EditorRawDOMPoint& aScanEndPoint);
-template void WSRunScanner::PriorVisibleNode(const EditorDOMPoint& aPoint,
- nsCOMPtr* outVisNode,
- int32_t* outVisOffset,
- WSType* outType) const;
-template void WSRunScanner::PriorVisibleNode(const EditorRawDOMPoint& aPoint,
- nsCOMPtr* outVisNode,
- int32_t* outVisOffset,
- WSType* outType) const;
-template void WSRunScanner::NextVisibleNode(const EditorDOMPoint& aPoint,
- nsCOMPtr* outVisNode,
- int32_t* outVisOffset,
- WSType* outType) const;
-template void WSRunScanner::NextVisibleNode(const EditorRawDOMPoint& aPoint,
- nsCOMPtr* outVisNode,
- int32_t* outVisOffset,
- WSType* outType) const;
+template WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
+ const EditorDOMPoint& aPoint) const;
+template WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
+ const EditorRawDOMPoint& aPoint) const;
+template WSScanResult WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom(
+ const EditorDOMPoint& aPoint) const;
+template WSScanResult WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom(
+ const EditorRawDOMPoint& aPoint) const;
template void WSRunObject::GetASCIIWhitespacesBounds(
int16_t aDir, const EditorDOMPoint& aPoint, dom::Text** outStartNode,
int32_t* outStartOffset, dom::Text** outEndNode,
@@ -537,14 +529,12 @@ nsresult WSRunObject::DeleteWSForward() {
}
template
-void WSRunScanner::PriorVisibleNode(const EditorDOMPointBase& aPoint,
- nsCOMPtr* outVisNode,
- int32_t* outVisOffset,
- WSType* outType) const {
+WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
+ const EditorDOMPointBase& aPoint) const {
// Find first visible thing before the point. Position
// outVisNode/outVisOffset just _after_ that thing. If we don't find
// anything return start of ws.
- MOZ_ASSERT(aPoint.IsSet() && outType);
+ MOZ_ASSERT(aPoint.IsSet());
WSFragment* run = FindNearestRun(aPoint, false);
@@ -554,43 +544,30 @@ void WSRunScanner::PriorVisibleNode(const EditorDOMPointBase& aPoint,
WSPoint point = GetPreviousCharPoint(aPoint);
// When it's a non-empty text node, return it.
if (point.mTextNode && point.mTextNode->Length()) {
- if (outVisNode) {
- *outVisNode = point.mTextNode;
- }
- if (outVisOffset) {
- *outVisOffset = point.mOffset + 1;
- }
- if (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == kNBSP) {
- *outType = WSType::normalWS;
- } else {
- *outType = WSType::text;
- }
- return;
+ return WSScanResult(
+ point.mTextNode, point.mOffset + 1,
+ nsCRT::IsAsciiSpace(point.mChar) || point.mChar == kNBSP
+ ? WSType::normalWS
+ : WSType::text);
}
// If no text node, keep looking. We should eventually fall out of loop
}
}
- if (outVisNode) {
- // If we get here, then nothing in ws data to find. Return start reason.
- *outVisNode = mStartReasonContent;
+ if (mStartReasonContent != mStartNode) {
+ // In this case, mStartOffset is not meaningful.
+ return WSScanResult(mStartReasonContent, mStartReason);
}
- if (outVisOffset) {
- // This really isn't meaningful if mStartReasonContent != mStartNode
- *outVisOffset = mStartOffset;
- }
- *outType = mStartReason;
+ return WSScanResult(mStartReasonContent, mStartOffset, mStartReason);
}
template
-void WSRunScanner::NextVisibleNode(const EditorDOMPointBase& aPoint,
- nsCOMPtr* outVisNode,
- int32_t* outVisOffset,
- WSType* outType) const {
+WSScanResult WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom(
+ const EditorDOMPointBase& aPoint) const {
// Find first visible thing after the point. Position
// outVisNode/outVisOffset just _before_ that thing. If we don't find
// anything return end of ws.
- MOZ_ASSERT(aPoint.IsSet() && outType);
+ MOZ_ASSERT(aPoint.IsSet());
WSFragment* run = FindNearestRun(aPoint, true);
@@ -600,32 +577,21 @@ void WSRunScanner::NextVisibleNode(const EditorDOMPointBase& aPoint,
WSPoint point = GetNextCharPoint(aPoint);
// When it's a non-empty text node, return it.
if (point.mTextNode && point.mTextNode->Length()) {
- if (outVisNode) {
- *outVisNode = point.mTextNode;
- }
- if (outVisOffset) {
- *outVisOffset = point.mOffset;
- }
- if (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == kNBSP) {
- *outType = WSType::normalWS;
- } else {
- *outType = WSType::text;
- }
- return;
+ return WSScanResult(
+ point.mTextNode, point.mOffset,
+ nsCRT::IsAsciiSpace(point.mChar) || point.mChar == kNBSP
+ ? WSType::normalWS
+ : WSType::text);
}
// If no text node, keep looking. We should eventually fall out of loop
}
}
- if (outVisNode) {
- // If we get here, then nothing in ws data to find. Return end reason
- *outVisNode = mEndReasonContent;
+ if (mEndReasonContent != mEndNode) {
+ // In this case, mEndOffset is not meaningful.
+ return WSScanResult(mEndReasonContent, mEndReason);
}
- if (outVisOffset) {
- // This really isn't meaningful if mEndReasonContent != mEndNode
- *outVisOffset = mEndOffset;
- }
- *outType = mEndReason;
+ return WSScanResult(mEndReasonContent, mEndOffset, mEndReason);
}
nsresult WSRunObject::AdjustWhitespace() {
@@ -2079,8 +2045,4 @@ nsresult WSRunObject::Scrub() {
return NS_OK;
}
-bool WSRunScanner::IsBlockNode(nsINode* aNode) {
- return aNode && aNode->IsElement() && HTMLEditor::NodeIsBlockStatic(*aNode);
-}
-
} // namespace mozilla
diff --git a/editor/libeditor/WSRunObject.h b/editor/libeditor/WSRunObject.h
index b28f2bd5cfd9..89ebb77daa10 100644
--- a/editor/libeditor/WSRunObject.h
+++ b/editor/libeditor/WSRunObject.h
@@ -6,15 +6,20 @@
#ifndef WSRunObject_h
#define WSRunObject_h
-#include "mozilla/dom/Text.h"
+#include "mozilla/Assertions.h"
#include "mozilla/EditAction.h"
#include "mozilla/EditorBase.h"
#include "mozilla/EditorDOMPoint.h" // for EditorDOMPoint
+#include "mozilla/HTMLEditor.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLBRElement.h"
+#include "mozilla/dom/Text.h"
+#include "nsCOMPtr.h"
+#include "nsIContent.h"
namespace mozilla {
-class HTMLEditor;
-
// class WSRunObject represents the entire whitespace situation
// around a given point. It collects up a list of nodes that contain
// whitespace and categorizes in up to 3 different WSFragments (detailed
@@ -131,6 +136,201 @@ inline const WSType operator|(const WSType::Enum& aLeft,
return WSType(aLeft) | WSType(aRight);
}
+/**
+ * WSScanResult is result of ScanNextVisibleNodeOrBlockBoundaryFrom(),
+ * ScanPreviousVisibleNodeOrBlockBoundaryFrom(), and their static wrapper
+ * methods. This will have information of found visible content (and its
+ * position) or reached block element or topmost editable content at the
+ * start of scanner.
+ */
+class MOZ_STACK_CLASS WSScanResult final {
+ public:
+ WSScanResult() = delete;
+ MOZ_NEVER_INLINE_DEBUG WSScanResult(nsIContent* aContent, WSType aReason)
+ : mContent(aContent), mReason(aReason) {
+ AssertIfInvalidData();
+ }
+ MOZ_NEVER_INLINE_DEBUG WSScanResult(nsIContent* aContent, uint32_t aOffset,
+ WSType aReason)
+ : mContent(aContent), mOffset(Some(aOffset)), mReason(aReason) {
+ AssertIfInvalidData();
+ }
+
+ void AssertIfInvalidData() const {
+#ifdef DEBUG
+ MOZ_ASSERT(mReason == WSType::text || mReason == WSType::normalWS ||
+ mReason == WSType::br || mReason == WSType::special ||
+ mReason == WSType::thisBlock || mReason == WSType::otherBlock);
+ MOZ_ASSERT_IF(mReason == WSType::text || mReason == WSType::normalWS,
+ mContent && mContent->IsText());
+ MOZ_ASSERT_IF(mReason == WSType::br,
+ mContent && mContent->IsHTMLElement(nsGkAtoms::br));
+ MOZ_ASSERT_IF(
+ mReason == WSType::special,
+ mContent && ((mContent->IsText() && !mContent->IsEditable()) ||
+ (!mContent->IsHTMLElement(nsGkAtoms::br) &&
+ !HTMLEditor::NodeIsBlockStatic(*mContent))));
+ MOZ_ASSERT_IF(mReason == WSType::otherBlock,
+ mContent && HTMLEditor::NodeIsBlockStatic(*mContent));
+ // If mReason is WSType::thisBlock, 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.
+ MOZ_ASSERT_IF(
+ mReason == WSType::thisBlock,
+ !mContent || !mContent->GetParentElement() ||
+ HTMLEditor::NodeIsBlockStatic(*mContent) ||
+ HTMLEditor::NodeIsBlockStatic(*mContent->GetParentElement()) ||
+ !mContent->GetParentElement()->IsEditable());
+#endif // #ifdef DEBUG
+ }
+
+ /**
+ * GetContent() returns found visible and editable content/element.
+ * See MOZ_ASSERT_IF()s in AssertIfInvalidData() for the detail.
+ */
+ nsIContent* GetContent() const { return mContent; }
+
+ /**
+ * The following accessors makes it easier to understand each callers.
+ */
+ MOZ_NEVER_INLINE_DEBUG dom::Element* ElementPtr() const {
+ MOZ_DIAGNOSTIC_ASSERT(mContent->IsElement());
+ return mContent->AsElement();
+ }
+ MOZ_NEVER_INLINE_DEBUG dom::HTMLBRElement* BRElementPtr() const {
+ MOZ_DIAGNOSTIC_ASSERT(mContent->IsHTMLElement(nsGkAtoms::br));
+ return static_cast(mContent.get());
+ }
+ MOZ_NEVER_INLINE_DEBUG dom::Text* TextPtr() const {
+ MOZ_DIAGNOSTIC_ASSERT(mContent->IsText());
+ return mContent->AsText();
+ }
+
+ /**
+ * Returns true if found or reached content is ediable.
+ */
+ bool IsContentEditable() const { return mContent && mContent->IsEditable(); }
+
+ /**
+ * Offset() returns meaningful value only when InNormalWhiteSpacesOrText()
+ * 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.
+ */
+ MOZ_NEVER_INLINE_DEBUG uint32_t Offset() 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.
+ */
+ MOZ_NEVER_INLINE_DEBUG EditorDOMPoint Point() const {
+ NS_ASSERTION(mOffset.isSome(), "Retrieved non-meaningful point");
+ return EditorDOMPoint(mContent, mOffset.valueOr(0));
+ }
+ MOZ_NEVER_INLINE_DEBUG EditorRawDOMPoint RawPoint() const {
+ NS_ASSERTION(mOffset.isSome(), "Retrieved non-meaningful raw point");
+ return EditorRawDOMPoint(mContent, mOffset.valueOr(0));
+ }
+
+ /**
+ * PointAtContent() and RawPointAtContent() return the position of found
+ * visible content or reached block element.
+ */
+ MOZ_NEVER_INLINE_DEBUG EditorDOMPoint PointAtContent() const {
+ MOZ_ASSERT(mContent);
+ return EditorDOMPoint(mContent);
+ }
+ MOZ_NEVER_INLINE_DEBUG EditorRawDOMPoint RawPointAtContent() const {
+ MOZ_ASSERT(mContent);
+ return EditorRawDOMPoint(mContent);
+ }
+
+ /**
+ * PointAfterContent() and RawPointAfterContent() retrun the position after
+ * found visible content or reached block element.
+ */
+ MOZ_NEVER_INLINE_DEBUG EditorDOMPoint PointAfterContent() const {
+ MOZ_ASSERT(mContent);
+ return mContent ? EditorDOMPoint::After(*mContent) : EditorDOMPoint();
+ }
+ MOZ_NEVER_INLINE_DEBUG EditorRawDOMPoint RawPointAfterContent() const {
+ MOZ_ASSERT(mContent);
+ return mContent ? EditorRawDOMPoint::After(*mContent) : EditorRawDOMPoint();
+ }
+
+ /**
+ * The scanner reached or something which is inline and is not a
+ * container.
+ */
+ bool ReachedSpecialContent() const { return mReason == WSType::special; }
+
+ /**
+ * The point is in normal whitespaces or text.
+ */
+ bool InNormalWhiteSpacesOrText() const {
+ return mReason == WSType::normalWS || mReason == WSType::text;
+ }
+
+ /**
+ * The point is in normal whitespaces.
+ */
+ bool InNormalWhiteSpaces() const { return mReason == WSType::normalWS; }
+
+ /**
+ * The point is in normal text.
+ */
+ bool InNormalText() const { return mReason == WSType::text; }
+
+ /**
+ * The scanner reached a element.
+ */
+ bool ReachedBRElement() const { return mReason == WSType::br; }
+
+ /**
+ * The scanner reached a element.
+ */
+ bool ReachedHRElement() const {
+ return mContent && mContent->IsHTMLElement(nsGkAtoms::hr);
+ }
+
+ /**
+ * The scanner reached current block boundary or other block element.
+ */
+ bool ReachedBlockBoundary() const { return !!(mReason & WSType::block); }
+
+ /**
+ * The scanner reached current block element boundary.
+ */
+ bool ReachedCurrentBlockBoundary() const {
+ return mReason == WSType::thisBlock;
+ }
+
+ /**
+ * The scanner reached other block element.
+ */
+ bool ReachedOtherBlockElement() const {
+ return mReason == WSType::otherBlock;
+ }
+
+ /**
+ * The scanner reached something non-text node.
+ */
+ bool ReachedSomething() const { return !InNormalWhiteSpacesOrText(); }
+
+ private:
+ nsCOMPtr mContent;
+ Maybe mOffset;
+ WSType mReason;
+};
+
class MOZ_STACK_CLASS WSRunScanner {
public:
/**
@@ -166,46 +366,62 @@ class MOZ_STACK_CLASS WSRunScanner {
EditorRawDOMPoint(aScanStartNode, aScanStartOffset)) {}
~WSRunScanner();
- // NextVisibleNode() returns the first piece of visible thing after aPoint.
- // If there is no visible ws qualifying it returns what is after the ws run.
- // If outVisNode and/or outvisOffset is unused, callers can use nullptr.
- // Note that {outVisNode,outVisOffset} is set to just BEFORE the visible
- // object. Also outVisOffset might be invalid offset unless outVisNode is
- // end reason node.
+ // 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.
template
- void NextVisibleNode(const EditorDOMPointBase& aPoint,
- nsCOMPtr* outVisNode, int32_t* outVisOffset,
- WSType* outType) const;
-
+ WSScanResult ScanNextVisibleNodeOrBlockBoundaryFrom(
+ const EditorDOMPointBase& aPoint) const;
template
- void NextVisibleNode(const EditorDOMPointBase& aPoint,
- WSType* outType) const {
- NextVisibleNode(aPoint, nullptr, nullptr, outType);
+ static WSScanResult ScanNextVisibleNodeOrBlockBoundary(
+ const HTMLEditor& aHTMLEditor, const EditorDOMPointBase& aPoint) {
+ return WSRunScanner(&aHTMLEditor, aPoint)
+ .ScanNextVisibleNodeOrBlockBoundaryFrom(aPoint);
}
- // PriorVisibleNode() returns the first piece of visible thing before aPoint.
- // If there is no visible ws qualifying it returns what is before the ws run.
- // If outVisNode and/or outvisOffset is unused, callers can use nullptr.
- // Note that {outVisNode,outVisOffset} is set to just AFTER the visible
- // object. Also outVisOffset might be invalid offset unless outVisNode is
- // start reason node.
+ // 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.
template
- void PriorVisibleNode(const EditorDOMPointBase& aPoint,
- nsCOMPtr* outVisNode, int32_t* outVisOffset,
- WSType* outType) const;
-
+ WSScanResult ScanPreviousVisibleNodeOrBlockBoundaryFrom(
+ const EditorDOMPointBase& aPoint) const;
template
- void PriorVisibleNode(const EditorDOMPointBase& aPoint,
- WSType* outType) const {
- PriorVisibleNode(aPoint, nullptr, nullptr, outType);
+ static WSScanResult ScanPreviousVisibleNodeOrBlockBoundary(
+ const HTMLEditor& aHTMLEditor, const EditorDOMPointBase& aPoint) {
+ return WSRunScanner(&aHTMLEditor, aPoint)
+ .ScanPreviousVisibleNodeOrBlockBoundaryFrom(aPoint);
}
/**
- * TODO: Document us!
+ * GetStartReasonContent() and GetEndReasonContent() return a node which
+ * was found by scanning from mScanStartPoint backward or mScanEndPoint
+ * forward. If there was whitespaces or text from the point, returns the
+ * text node. Otherwise, returns an element which is explained by
+ * StartReason() or EndReason(). Note that when the reason is
+ * WSType::thisBlock, In most cases, it's current block element which is
+ * editable, but also may be non-element and/or non-editable. See
+ * MOZ_ASSERT_IF()s in WSScanResult::AssertIfInvalidData() for the detail.
*/
nsIContent* GetStartReasonContent() const { return mStartReasonContent; }
nsIContent* GetEndReasonContent() const { return mEndReasonContent; }
+ /**
+ * StartReason() and EndReason() return one of WSType::normalWS, WSType::text,
+ * WSType::br, WSType::special, WSType::thisBlock, WSType::otherBlock.
+ * If WSType::normalWS or WSType::text, scanning from mScanStartPoint backward
+ * or mScanEndPoint forward stopped in a text node.
+ * Otherwise, they reached an element which is what the WSType indicates.
+ */
+ WSType StartReason() const { return mStartReason; }
+ WSType EndReason() const { return mEndReason; }
+
+ /**
+ * Active editing host when this instance is created.
+ */
+ Element* GetEditingHost() const { return mEditingHost; }
+
protected:
// WSFragment represents a single run of ws (all leadingws, or all normalws,
// or all trailingws, or all leading+trailingws). Note that this single run
@@ -304,7 +520,9 @@ class MOZ_STACK_CLASS WSRunScanner {
nsIContent* GetEditableBlockParentOrTopmotEditableInlineContent(
nsIContent* aContent) const;
- static bool IsBlockNode(nsINode* aNode);
+ static bool IsBlockNode(nsINode* aNode) {
+ return aNode && aNode->IsElement() && HTMLEditor::NodeIsBlockStatic(*aNode);
+ }
nsIContent* GetPreviousWSNodeInner(nsINode* aStartNode,
nsINode* aBlockParent) const;
@@ -370,6 +588,7 @@ class MOZ_STACK_CLASS WSRunScanner {
// The last WSFragment in the run, may be same as first.
WSFragment* mEndRun;
+ // See above comment for GetStartReasonContent() and GetEndReasonContent().
nsCOMPtr mStartReasonContent;
nsCOMPtr mEndReasonContent;
@@ -526,8 +745,6 @@ class MOZ_STACK_CLASS WSRunObject final : public WSRunScanner {
// be safely converted to regular ascii space and converts them.
MOZ_CAN_RUN_SCRIPT nsresult AdjustWhitespace();
- Element* GetEditingHost() const { return mEditingHost; }
-
protected:
using WSPoint = WSRunScanner::WSPoint;
diff --git a/testing/web-platform/meta/editing/run/insertparagraph.html.ini b/testing/web-platform/meta/editing/run/insertparagraph.html.ini
index da5312dc48ce..2d59b622632b 100644
--- a/testing/web-platform/meta/editing/run/insertparagraph.html.ini
+++ b/testing/web-platform/meta/editing/run/insertparagraph.html.ini
@@ -489,6 +489,7 @@
[insertparagraph.html?6001-last]
+ max-asserts: 2 # Only on W-fis on Linux1804-64-qr and on Android, there are 2 assertions of retrieving meaningless raw DOM point from WSScannerResult
disabled:
if (os == "android") and not e10s: https://bugzilla.mozilla.org/show_bug.cgi?id=1499003
[[["defaultparagraphseparator","div"\],["insertparagraph",""\]\] "
[\]foo
" queryCommandValue("defaultparagraphseparator") before]
@@ -502,6 +503,8 @@
[insertparagraph.html?5001-6000]
+ min-asserts: 14 # Retrieving meaningless raw DOM point from WSScannerResult
+ max-asserts: 16 # Only on W-fis on Linux1804-64-qr and on Android, there are 2 additional assertions.
[[["defaultparagraphseparator","p"\],["insertparagraph",""\]\] "