diff --git a/editor/libeditor/WSRunObject.cpp b/editor/libeditor/WSRunObject.cpp index 3eea6d4e01fe..9259560e1dcf 100644 --- a/editor/libeditor/WSRunObject.cpp +++ b/editor/libeditor/WSRunObject.cpp @@ -696,9 +696,9 @@ nsresult WSRunObject::AdjustWhitespace() { if (!run->IsVisibleAndMiddleOfHardLine()) { continue; } - nsresult rv = CheckTrailingNBSPOfRun(run); + nsresult rv = NormalizeWhitespacesAtEndOf(*run); if (NS_FAILED(rv)) { - NS_WARNING("WSRunObject::CheckTrailingNBSPOfRun() failed"); + NS_WARNING("WSRunObject::NormalizeWhitespacesAtEndOf() failed"); return rv; } } @@ -1594,183 +1594,194 @@ char16_t WSRunScanner::GetCharAt(Text* aTextNode, int32_t aOffset) const { return aTextNode->TextFragment().CharAt(aOffset); } -nsresult WSRunObject::CheckTrailingNBSPOfRun(WSFragment* aRun) { - if (NS_WARN_IF(!aRun)) { - return NS_ERROR_INVALID_ARG; - } - +nsresult WSRunObject::NormalizeWhitespacesAtEndOf(const WSFragment& aRun) { // Check if it's a visible fragment in a hard line. - if (!aRun->IsVisibleAndMiddleOfHardLine()) { + if (!aRun.IsVisibleAndMiddleOfHardLine()) { return NS_ERROR_FAILURE; } // first check for trailing nbsp + EditorDOMPoint atEndOfRun = aRun.EndPoint(); EditorDOMPointInText atPreviousCharOfEndOfRun = - GetPreviousEditableCharPoint(aRun->RawEndPoint()); - if (atPreviousCharOfEndOfRun.IsSet() && - !atPreviousCharOfEndOfRun.IsEndOfContainer() && - atPreviousCharOfEndOfRun.IsCharNBSP()) { - // now check that what is to the left of it is compatible with replacing - // nbsp with space - EditorDOMPointInText atPreviousCharOfPreviousCharOfEndOfRun = - GetPreviousEditableCharPoint(atPreviousCharOfEndOfRun); - bool isPreviousCharASCIIWhitespace = - atPreviousCharOfPreviousCharOfEndOfRun.IsSet() && - !atPreviousCharOfPreviousCharOfEndOfRun.IsEndOfContainer() && - atPreviousCharOfPreviousCharOfEndOfRun.IsCharASCIISpace(); - bool maybeNBSPFollowingVisibleContent = - (atPreviousCharOfPreviousCharOfEndOfRun.IsSet() && - !isPreviousCharASCIIWhitespace) || - (!atPreviousCharOfPreviousCharOfEndOfRun.IsSet() && - (aRun->StartsFromNormalText() || aRun->StartsFromSpecialContent())); - bool followedByVisibleContentOrBRElement = false; - if (maybeNBSPFollowingVisibleContent || isPreviousCharASCIIWhitespace) { - // now check that what is to the right of it is compatible with replacing - // nbsp with space - followedByVisibleContentOrBRElement = aRun->EndsByNormalText() || - aRun->EndsBySpecialContent() || - aRun->EndsByBRElement(); - if (aRun->EndsByBlockBoundary() && mScanStartPoint.IsInContentNode()) { - bool insertBRElement = HTMLEditUtils::IsBlockElement( - *mScanStartPoint.ContainerAsContent()); - if (!insertBRElement) { - nsIContent* blockParentOrTopmostEditableInlineContent = - GetEditableBlockParentOrTopmotEditableInlineContent( - mScanStartPoint.ContainerAsContent()); - insertBRElement = blockParentOrTopmostEditableInlineContent && - HTMLEditUtils::IsBlockElement( - *blockParentOrTopmostEditableInlineContent); - } - if (insertBRElement) { - // We are at a block boundary. Insert a
. Why? Well, first note - // that the br will have no visible effect since it is up against a - // block boundary. |foo

bar| renders like |foo

bar| and - // similarly |

foo

bar| renders like |

foo

bar|. What - // this
addition gets us is the ability to convert a trailing - // nbsp to a space. Consider: |foo. '|, where ' - // represents selection. User types space attempting to put 2 spaces - // after the end of their sentence. We used to do this as: - // |foo.  | This caused problems with soft wrapping: - // the nbsp would wrap to the next line, which looked attrocious. If - // you try to do: |foo.  | instead, the trailing - // space is invisible because it is against a block boundary. If you - // do: - // |foo.  | then you get an even uglier soft - // wrapping problem, where foo is on one line until you type the final - // space, and then "foo " jumps down to the next line. Ugh. The - // best way I can find out of this is to throw in a harmless
- // here, which allows us to do: |foo. 
|, which - // doesn't cause foo to jump lines, doesn't cause spaces to show up at - // the beginning of soft wrapped lines, and lets the user see 2 spaces - // when they type 2 spaces. + GetPreviousEditableCharPoint(atEndOfRun); + if (!atPreviousCharOfEndOfRun.IsSet() || + atPreviousCharOfEndOfRun.IsEndOfContainer() || + !atPreviousCharOfEndOfRun.IsCharNBSP()) { + return NS_OK; + } - RefPtr brElement = - MOZ_KnownLive(mHTMLEditor) - .InsertBRElementWithTransaction(aRun->EndPoint()); - if (!brElement) { - NS_WARNING("HTMLEditor::InsertBRElementWithTransaction() failed"); - return NS_ERROR_FAILURE; - } + // now check that what is to the left of it is compatible with replacing + // nbsp with space + EditorDOMPointInText atPreviousCharOfPreviousCharOfEndOfRun = + GetPreviousEditableCharPoint(atPreviousCharOfEndOfRun); + bool isPreviousCharASCIIWhitespace = + atPreviousCharOfPreviousCharOfEndOfRun.IsSet() && + !atPreviousCharOfPreviousCharOfEndOfRun.IsEndOfContainer() && + atPreviousCharOfPreviousCharOfEndOfRun.IsCharASCIISpace(); + bool maybeNBSPFollowingVisibleContent = + (atPreviousCharOfPreviousCharOfEndOfRun.IsSet() && + !isPreviousCharASCIIWhitespace) || + (!atPreviousCharOfPreviousCharOfEndOfRun.IsSet() && + (aRun.StartsFromNormalText() || aRun.StartsFromSpecialContent())); + bool followedByVisibleContentOrBRElement = false; - atPreviousCharOfEndOfRun = - GetPreviousEditableCharPoint(aRun->RawEndPoint()); - atPreviousCharOfPreviousCharOfEndOfRun = - GetPreviousEditableCharPoint(atPreviousCharOfEndOfRun); - followedByVisibleContentOrBRElement = true; - } + // If the NBSP follows a visible content or an ASCII whitespace, i.e., + // unless NBSP is first character and start of a block, we may need to + // insert
element and restore the NBSP to an ASCII whitespace. + if (maybeNBSPFollowingVisibleContent || isPreviousCharASCIIWhitespace) { + followedByVisibleContentOrBRElement = aRun.EndsByNormalText() || + aRun.EndsBySpecialContent() || + aRun.EndsByBRElement(); + // First, try to insert
element if NBSP is at end of a block. + // XXX We should stop this if there is a visible content. + if (aRun.EndsByBlockBoundary() && mScanStartPoint.IsInContentNode()) { + bool insertBRElement = + HTMLEditUtils::IsBlockElement(*mScanStartPoint.ContainerAsContent()); + if (!insertBRElement) { + nsIContent* blockParentOrTopmostEditableInlineContent = + GetEditableBlockParentOrTopmotEditableInlineContent( + mScanStartPoint.ContainerAsContent()); + insertBRElement = blockParentOrTopmostEditableInlineContent && + HTMLEditUtils::IsBlockElement( + *blockParentOrTopmostEditableInlineContent); } + if (insertBRElement) { + // We are at a block boundary. Insert a
. Why? Well, first note + // that the br will have no visible effect since it is up against a + // block boundary. |foo

bar| renders like |foo

bar| and + // similarly |

foo

bar| renders like |

foo

bar|. What + // this
addition gets us is the ability to convert a trailing + // nbsp to a space. Consider: |foo. '|, where ' + // represents selection. User types space attempting to put 2 spaces + // after the end of their sentence. We used to do this as: + // |foo.  | This caused problems with soft wrapping: + // the nbsp would wrap to the next line, which looked attrocious. If + // you try to do: |foo.  | instead, the trailing + // space is invisible because it is against a block boundary. If you + // do: + // |foo.  | then you get an even uglier soft + // wrapping problem, where foo is on one line until you type the final + // space, and then "foo " jumps down to the next line. Ugh. The + // best way I can find out of this is to throw in a harmless
+ // here, which allows us to do: |foo. 
|, which + // doesn't cause foo to jump lines, doesn't cause spaces to show up at + // the beginning of soft wrapped lines, and lets the user see 2 spaces + // when they type 2 spaces. - if (maybeNBSPFollowingVisibleContent && - followedByVisibleContentOrBRElement) { - // Now replace nbsp with space. First, insert a space - AutoTransactionsConserveSelection dontChangeMySelection(mHTMLEditor); - nsresult rv = + RefPtr brElement = MOZ_KnownLive(mHTMLEditor) - .InsertTextIntoTextNodeWithTransaction( - NS_LITERAL_STRING(" "), atPreviousCharOfEndOfRun, true); - if (NS_FAILED(rv)) { - NS_WARNING( - "EditorBase::InsertTextIntoTextNodeWithTransaction() failed"); - return rv; + .InsertBRElementWithTransaction(atEndOfRun); + if (NS_WARN_IF(mHTMLEditor.Destroyed())) { + return NS_ERROR_EDITOR_DESTROYED; + } + if (!brElement) { + NS_WARNING("HTMLEditor::InsertBRElementWithTransaction() failed"); + return NS_ERROR_FAILURE; } - // Finally, delete that nbsp - NS_ASSERTION(!atPreviousCharOfEndOfRun.IsEndOfContainer() && - !atPreviousCharOfEndOfRun.IsAtLastContent(), - "The text node was modified by mutation event listener"); - if (!atPreviousCharOfEndOfRun.IsEndOfContainer() && - !atPreviousCharOfEndOfRun.IsAtLastContent()) { - NS_ASSERTION(atPreviousCharOfEndOfRun.IsNextCharNBSP(), - "Trying to remove an NBSP, but it's gone from the " - "expected position"); - EditorDOMPointInText atNextCharOfPreviousCharOfEndOfRun = - atPreviousCharOfEndOfRun.NextPoint(); - nsresult rv = MOZ_KnownLive(mHTMLEditor) - .DeleteTextAndTextNodesWithTransaction( - atNextCharOfPreviousCharOfEndOfRun, - atNextCharOfPreviousCharOfEndOfRun.NextPoint()); - if (NS_FAILED(rv)) { - NS_WARNING( - "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed"); - return rv; - } - } - return NS_OK; + atPreviousCharOfEndOfRun = GetPreviousEditableCharPoint(atEndOfRun); + atPreviousCharOfPreviousCharOfEndOfRun = + GetPreviousEditableCharPoint(atPreviousCharOfEndOfRun); + followedByVisibleContentOrBRElement = true; } } - if (!mPRE && !maybeNBSPFollowingVisibleContent && - isPreviousCharASCIIWhitespace && followedByVisibleContentOrBRElement) { - // Don't mess with this preformatted for now. We have a run of ASCII - // whitespace (which will render as one space) followed by an nbsp (which - // is at the end of the whitespace run). Let's switch their order. This - // will ensure that if someone types two spaces after a sentence, and the - // editor softwraps at this point, the spaces won't be split across lines, - // which looks ugly and is bad for the moose. - MOZ_ASSERT(!atPreviousCharOfPreviousCharOfEndOfRun.IsEndOfContainer()); - EditorDOMPointInText start, end; - // XXX end won't be used, whey `eBoth`? - Tie(start, end) = GetASCIIWhitespacesBounds( - eBoth, atPreviousCharOfPreviousCharOfEndOfRun.NextPoint()); - NS_WARNING_ASSERTION( - start.IsSet(), - "WSRunObject::GetASCIIWhitespacesBounds() didn't return start point"); - NS_WARNING_ASSERTION(end.IsSet(), - "WSRunObject::GetASCIIWhitespacesBounds() didn't " - "return end point, but ignored"); - - // Delete that nbsp - NS_ASSERTION(!atPreviousCharOfEndOfRun.IsEndOfContainer(), - "The text node was modified by mutation event listener"); - if (!atPreviousCharOfEndOfRun.IsEndOfContainer()) { - NS_ASSERTION(atPreviousCharOfEndOfRun.IsCharNBSP(), - "Trying to remove an NBSP, but it's gone from the " - "expected position"); - nsresult rv = MOZ_KnownLive(mHTMLEditor) - .DeleteTextAndTextNodesWithTransaction( - atPreviousCharOfEndOfRun, - atPreviousCharOfEndOfRun.NextPoint()); - if (NS_FAILED(rv)) { - NS_WARNING( - "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed"); - return rv; - } + // Next, replace the NBSP with an ASCII whitespace if it's surrounded + // by visible contents (or immediately before a
element). + if (maybeNBSPFollowingVisibleContent && + followedByVisibleContentOrBRElement) { + // Now replace nbsp with space. First, insert a space + AutoTransactionsConserveSelection dontChangeMySelection(mHTMLEditor); + nsresult rv = + MOZ_KnownLive(mHTMLEditor) + .InsertTextIntoTextNodeWithTransaction( + NS_LITERAL_STRING(" "), atPreviousCharOfEndOfRun, true); + if (NS_WARN_IF(mHTMLEditor.Destroyed())) { + return NS_ERROR_EDITOR_DESTROYED; + } + if (NS_FAILED(rv)) { + NS_WARNING( + "EditorBase::InsertTextIntoTextNodeWithTransaction() failed"); + return rv; } - // Finally, insert that nbsp before the ASCII ws run - NS_ASSERTION(start.IsSetAndValid(), - "The text node was modified by mutation event listener"); - if (start.IsSetAndValid()) { - AutoTransactionsConserveSelection dontChangeMySelection(mHTMLEditor); - nsresult rv = MOZ_KnownLive(mHTMLEditor) - .InsertTextIntoTextNodeWithTransaction( - nsDependentSubstring(&kNBSP, 1), start, true); - if (NS_FAILED(rv)) { - NS_WARNING( - "EditorBase::InsertTextIntoTextNodeWithTransaction() failed"); - return rv; - } + if (atPreviousCharOfEndOfRun.IsEndOfContainer() || + atPreviousCharOfEndOfRun.IsAtLastContent()) { + NS_WARNING("The text node was modified by mutation event listener"); + return NS_OK; } + + // Finally, delete that nbsp + NS_ASSERTION(atPreviousCharOfEndOfRun.IsNextCharNBSP(), + "Trying to remove an NBSP, but it's gone from the " + "expected position"); + EditorDOMPointInText atNextCharOfPreviousCharOfEndOfRun = + atPreviousCharOfEndOfRun.NextPoint(); + rv = MOZ_KnownLive(mHTMLEditor) + .DeleteTextAndTextNodesWithTransaction( + atNextCharOfPreviousCharOfEndOfRun, + atNextCharOfPreviousCharOfEndOfRun.NextPoint()); + if (NS_FAILED(rv)) { + NS_WARNING( + "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed"); + return rv; + } + return NS_OK; + } + } + + // If the text node is not preformatted, and the NBSP is followed by a
+ // element and following (maybe multiple) ASCII spaces, remove the NBSP, + // but inserts a NBSP before the spaces. This makes a line break opportunity + // to wrap the line. + // XXX This is different behavior from Blink. Blink generates pairs of + // an NBSP and an ASCII whitespace, but put NBSP at the end of the + // sequence. We should follow the behavior for web-compat. + if (mPRE || maybeNBSPFollowingVisibleContent || + !isPreviousCharASCIIWhitespace || !followedByVisibleContentOrBRElement) { + return NS_OK; + } + + MOZ_ASSERT(!atPreviousCharOfPreviousCharOfEndOfRun.IsEndOfContainer()); + EditorDOMPointInText start, end; + // XXX end won't be used, whey `eBoth`? + Tie(start, end) = GetASCIIWhitespacesBounds( + eBoth, atPreviousCharOfPreviousCharOfEndOfRun.NextPoint()); + NS_WARNING_ASSERTION( + start.IsSet(), + "WSRunObject::GetASCIIWhitespacesBounds() didn't return start point"); + NS_WARNING_ASSERTION(end.IsSet(), + "WSRunObject::GetASCIIWhitespacesBounds() didn't " + "return end point, but ignored"); + + // Delete that nbsp + NS_ASSERTION(!atPreviousCharOfEndOfRun.IsEndOfContainer(), + "The text node was modified by mutation event listener"); + if (!atPreviousCharOfEndOfRun.IsEndOfContainer()) { + NS_ASSERTION(atPreviousCharOfEndOfRun.IsCharNBSP(), + "Trying to remove an NBSP, but it's gone from the " + "expected position"); + nsresult rv = + MOZ_KnownLive(mHTMLEditor) + .DeleteTextAndTextNodesWithTransaction( + atPreviousCharOfEndOfRun, atPreviousCharOfEndOfRun.NextPoint()); + if (NS_FAILED(rv)) { + NS_WARNING("HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed"); + return rv; + } + } + + // Finally, insert that nbsp before the ASCII ws run + NS_ASSERTION(start.IsSetAndValid(), + "The text node was modified by mutation event listener"); + if (start.IsSetAndValid()) { + AutoTransactionsConserveSelection dontChangeMySelection(mHTMLEditor); + nsresult rv = MOZ_KnownLive(mHTMLEditor) + .InsertTextIntoTextNodeWithTransaction( + nsDependentSubstring(&kNBSP, 1), start, true); + if (NS_FAILED(rv)) { + NS_WARNING("EditorBase::InsertTextIntoTextNodeWithTransaction() failed"); + return rv; } } return NS_OK; diff --git a/editor/libeditor/WSRunObject.h b/editor/libeditor/WSRunObject.h index d662072625a3..d31981eddd14 100644 --- a/editor/libeditor/WSRunObject.h +++ b/editor/libeditor/WSRunObject.h @@ -810,7 +810,8 @@ class MOZ_STACK_CLASS WSRunObject final : public WSRunScanner { Tuple GetASCIIWhitespacesBounds( int16_t aDir, const EditorDOMPointBase& aPoint) const; - MOZ_CAN_RUN_SCRIPT nsresult CheckTrailingNBSPOfRun(WSFragment* aRun); + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + NormalizeWhitespacesAtEndOf(const WSFragment& aRun); /** * MaybeReplacePreviousNBSPWithASCIIWhitespace() replaces previous character