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",""\]\] "
  • foo[\]
" compare innerHTML] expected: FAIL