From 8dd16259287f58f9273002717ec4d27e97127719 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 12 Jun 2024 07:43:14 +0200 Subject: Merging upstream version 127.0. Signed-off-by: Daniel Baumann --- editor/libeditor/WSRunObject.h | 237 +++++++++++++++++++++++++++-------------- 1 file changed, 155 insertions(+), 82 deletions(-) (limited to 'editor/libeditor/WSRunObject.h') diff --git a/editor/libeditor/WSRunObject.h b/editor/libeditor/WSRunObject.h index 9328b24eb2..bbc6a078d9 100644 --- a/editor/libeditor/WSRunObject.h +++ b/editor/libeditor/WSRunObject.h @@ -41,6 +41,8 @@ class MOZ_STACK_CLASS WSScanResult final { NotInitialized, // Could be the DOM tree is broken as like crash tests. UnexpectedError, + // The scanner cannot work in uncomposed tree, but tried to scan in it. + InUncomposedDoc, // The run is maybe collapsible white-spaces at start of a hard line. LeadingWhiteSpaces, // The run is maybe collapsible white-spaces at end of a hard line. @@ -59,6 +61,8 @@ class MOZ_STACK_CLASS WSScanResult final { OtherBlockBoundary, // Current block's boundary. CurrentBlockBoundary, + // Inline editing host boundary. + InlineEditingHostBoundary, }; friend std::ostream& operator<<(std::ostream& aStream, const WSType& aType) { @@ -67,6 +71,8 @@ class MOZ_STACK_CLASS WSScanResult final { return aStream << "WSType::NotInitialized"; case WSType::UnexpectedError: return aStream << "WSType::UnexpectedError"; + case WSType::InUncomposedDoc: + return aStream << "WSType::InUncomposedDoc"; case WSType::LeadingWhiteSpaces: return aStream << "WSType::LeadingWhiteSpaces"; case WSType::TrailingWhiteSpaces: @@ -85,90 +91,111 @@ class MOZ_STACK_CLASS WSScanResult final { return aStream << "WSType::OtherBlockBoundary"; case WSType::CurrentBlockBoundary: return aStream << "WSType::CurrentBlockBoundary"; + case WSType::InlineEditingHostBoundary: + return aStream << "WSType::InlineEditingHostBoundary"; } return aStream << ""; } friend class WSRunScanner; // Because of WSType. + explicit WSScanResult(WSType aReason) : mReason(aReason) { + MOZ_ASSERT(mReason == WSType::UnexpectedError || + mReason == WSType::NotInitialized); + } + public: WSScanResult() = delete; - MOZ_NEVER_INLINE_DEBUG WSScanResult(nsIContent* aContent, WSType aReason, + enum class ScanDirection : bool { Backward, Forward }; + MOZ_NEVER_INLINE_DEBUG WSScanResult(ScanDirection aScanDirection, + nsIContent& aContent, WSType aReason, BlockInlineCheck aBlockInlineCheck) - : mContent(aContent), mReason(aReason) { + : mContent(&aContent), mReason(aReason), mDirection(aScanDirection) { + MOZ_ASSERT(aReason != WSType::CollapsibleWhiteSpaces && + aReason != WSType::NonCollapsibleCharacters && + aReason != WSType::PreformattedLineBreak); AssertIfInvalidData(aBlockInlineCheck); } - MOZ_NEVER_INLINE_DEBUG WSScanResult(const EditorDOMPoint& aPoint, + MOZ_NEVER_INLINE_DEBUG WSScanResult(ScanDirection aScanDirection, + const EditorDOMPoint& aPoint, WSType aReason, BlockInlineCheck aBlockInlineCheck) : mContent(aPoint.GetContainerAs()), mOffset(Some(aPoint.Offset())), - mReason(aReason) { + mReason(aReason), + mDirection(aScanDirection) { AssertIfInvalidData(aBlockInlineCheck); } + static WSScanResult Error() { return WSScanResult(WSType::UnexpectedError); } + MOZ_NEVER_INLINE_DEBUG void AssertIfInvalidData( BlockInlineCheck aBlockInlineCheck) const { #ifdef DEBUG MOZ_ASSERT(mReason == WSType::UnexpectedError || + mReason == WSType::InUncomposedDoc || mReason == WSType::NonCollapsibleCharacters || mReason == WSType::CollapsibleWhiteSpaces || mReason == WSType::BRElement || mReason == WSType::PreformattedLineBreak || mReason == WSType::SpecialContent || mReason == WSType::CurrentBlockBoundary || - mReason == WSType::OtherBlockBoundary); + mReason == WSType::OtherBlockBoundary || + mReason == WSType::InlineEditingHostBoundary); MOZ_ASSERT_IF(mReason == WSType::UnexpectedError, !mContent); + MOZ_ASSERT_IF(mReason != WSType::UnexpectedError, mContent); + MOZ_ASSERT_IF(mReason == WSType::InUncomposedDoc, + !mContent->IsInComposedDoc()); + MOZ_ASSERT_IF(mContent && !mContent->IsInComposedDoc(), + mReason == WSType::InUncomposedDoc); + MOZ_ASSERT_IF(mReason == WSType::NonCollapsibleCharacters || + mReason == WSType::CollapsibleWhiteSpaces || + mReason == WSType::PreformattedLineBreak, + mContent->IsText()); + MOZ_ASSERT_IF(mReason == WSType::NonCollapsibleCharacters || + mReason == WSType::CollapsibleWhiteSpaces || + mReason == WSType::PreformattedLineBreak, + mOffset.isSome()); MOZ_ASSERT_IF(mReason == WSType::NonCollapsibleCharacters || - mReason == WSType::CollapsibleWhiteSpaces, - mContent && mContent->IsText()); + mReason == WSType::CollapsibleWhiteSpaces || + mReason == WSType::PreformattedLineBreak, + mContent->AsText()->TextDataLength() > 0); + MOZ_ASSERT_IF(mDirection == ScanDirection::Backward && + (mReason == WSType::NonCollapsibleCharacters || + mReason == WSType::CollapsibleWhiteSpaces || + mReason == WSType::PreformattedLineBreak), + *mOffset > 0); + MOZ_ASSERT_IF(mDirection == ScanDirection::Forward && + (mReason == WSType::NonCollapsibleCharacters || + mReason == WSType::CollapsibleWhiteSpaces || + mReason == WSType::PreformattedLineBreak), + *mOffset < mContent->AsText()->TextDataLength()); MOZ_ASSERT_IF(mReason == WSType::BRElement, - mContent && mContent->IsHTMLElement(nsGkAtoms::br)); + mContent->IsHTMLElement(nsGkAtoms::br)); MOZ_ASSERT_IF(mReason == WSType::PreformattedLineBreak, - mContent && mContent->IsText() && - EditorUtils::IsNewLinePreformatted(*mContent)); + EditorUtils::IsNewLinePreformatted(*mContent)); MOZ_ASSERT_IF( mReason == WSType::SpecialContent, - mContent && - ((mContent->IsText() && !mContent->IsEditable()) || - (!mContent->IsHTMLElement(nsGkAtoms::br) && - !HTMLEditUtils::IsBlockElement(*mContent, aBlockInlineCheck)))); + (mContent->IsText() && !mContent->IsEditable()) || + (!mContent->IsHTMLElement(nsGkAtoms::br) && + !HTMLEditUtils::IsBlockElement(*mContent, aBlockInlineCheck))); MOZ_ASSERT_IF(mReason == WSType::OtherBlockBoundary, - mContent && HTMLEditUtils::IsBlockElement(*mContent, - aBlockInlineCheck)); - // If mReason is WSType::CurrentBlockBoundary, mContent can be any content. - // In most cases, it's current block element which is editable. However, if - // there is no editable block parent, this is topmost editable inline - // content. Additionally, if there is no editable content, this is the - // container start of scanner and is not editable. - if (mReason == WSType::CurrentBlockBoundary) { - if (!mContent || - // Although not expected that scanning in orphan document fragment, - // it's okay. - !mContent->IsInComposedDoc() || - // This is what the most preferred result is mContent itself is a - // block. - HTMLEditUtils::IsBlockElement(*mContent, aBlockInlineCheck) || - // If mContent is not editable, we cannot check whether there is no - // block ancestor in the limiter which we don't have. Therefore, - // let's skip the ancestor check. - !mContent->IsEditable()) { - return; - } - const DebugOnly closestAncestorEditableBlockElement = - HTMLEditUtils::GetAncestorElement( - *mContent, HTMLEditUtils::ClosestEditableBlockElement, - aBlockInlineCheck); - MOZ_ASSERT_IF( - mReason == WSType::CurrentBlockBoundary, - // There is no editable block ancestor, it's fine. - !closestAncestorEditableBlockElement || - // If we found an editable block, but mContent can be inline if - // it's an editing host (root or its parent is not editable). - !closestAncestorEditableBlockElement->GetParentElement() || - !closestAncestorEditableBlockElement->GetParentElement() - ->IsEditable()); - } + HTMLEditUtils::IsBlockElement(*mContent, aBlockInlineCheck)); + MOZ_ASSERT_IF(mReason == WSType::CurrentBlockBoundary, + mContent->IsElement()); + MOZ_ASSERT_IF(mReason == WSType::CurrentBlockBoundary, + mContent->IsEditable()); + MOZ_ASSERT_IF(mReason == WSType::CurrentBlockBoundary, + HTMLEditUtils::IsBlockElement(*mContent, aBlockInlineCheck)); + MOZ_ASSERT_IF(mReason == WSType::InlineEditingHostBoundary, + mContent->IsElement()); + MOZ_ASSERT_IF(mReason == WSType::InlineEditingHostBoundary, + mContent->IsEditable()); + MOZ_ASSERT_IF(mReason == WSType::InlineEditingHostBoundary, + !HTMLEditUtils::IsBlockElement(*mContent, aBlockInlineCheck)); + MOZ_ASSERT_IF(mReason == WSType::InlineEditingHostBoundary, + !mContent->GetParentElement() || + !mContent->GetParentElement()->IsEditable()); #endif // #ifdef DEBUG } @@ -187,6 +214,10 @@ class MOZ_STACK_CLASS WSScanResult final { return mContent && mContent->IsElement(); } + [[nodiscard]] bool ContentIsText() const { + return mContent && mContent->IsText(); + } + /** * The following accessors makes it easier to understand each callers. */ @@ -204,53 +235,65 @@ class MOZ_STACK_CLASS WSScanResult final { } /** - * Returns true if found or reached content is ediable. + * Returns true if found or reached content is editable. */ bool IsContentEditable() const { return mContent && mContent->IsEditable(); } /** - * Offset() returns meaningful value only when - * InVisibleOrCollapsibleCharacters() returns true or the scanner - * reached to start or end of its scanning range and that is same as start or - * end container which are specified when the scanner is initialized. If it's - * result of scanning backward, this offset means before the found point. - * Otherwise, i.e., scanning forward, this offset means after the found point. + * Offset_Deprecated() returns meaningful value only when + * InVisibleOrCollapsibleCharacters() returns true or the scanner reached to + * start or end of its scanning range and that is same as start or end + * container which are specified when the scanner is initialized. If it's + * result of scanning backward, this offset means the point of the found + * point. Otherwise, i.e., scanning forward, this offset means next point + * of the found point. E.g., if it reaches a collapsible white-space, this + * offset is at the first non-collapsible character after it. */ - MOZ_NEVER_INLINE_DEBUG uint32_t Offset() const { + MOZ_NEVER_INLINE_DEBUG uint32_t Offset_Deprecated() const { NS_ASSERTION(mOffset.isSome(), "Retrieved non-meaningful offset"); return mOffset.valueOr(0); } /** - * Point() and RawPoint() return the position in found visible node or - * reached block boundary. So, they return meaningful point only when - * Offset() returns meaningful value. + * Point_Deprecated() returns the position in found visible node or reached + * block boundary. So, this returns meaningful point only when + * Offset_Deprecated() returns meaningful value. */ template - EditorDOMPointType Point() const { + EditorDOMPointType Point_Deprecated() const { NS_ASSERTION(mOffset.isSome(), "Retrieved non-meaningful point"); return EditorDOMPointType(mContent, mOffset.valueOr(0)); } /** - * PointAtContent() and RawPointAtContent() return the position of found - * visible content or reached block element. + * PointAtReachedContent() returns the position of found visible content or + * reached block element. */ template - EditorDOMPointType PointAtContent() const { + EditorDOMPointType PointAtReachedContent() const { MOZ_ASSERT(mContent); - return EditorDOMPointType(mContent); + switch (mReason) { + case WSType::CollapsibleWhiteSpaces: + case WSType::NonCollapsibleCharacters: + case WSType::PreformattedLineBreak: + MOZ_DIAGNOSTIC_ASSERT(mOffset.isSome()); + return mDirection == ScanDirection::Forward + ? EditorDOMPointType(mContent, mOffset.valueOr(0)) + : EditorDOMPointType(mContent, + std::max(mOffset.valueOr(1), 1u) - 1); + default: + return EditorDOMPointType(mContent); + } } /** - * PointAfterContent() and RawPointAfterContent() retrun the position after - * found visible content or reached block element. + * PointAfterReachedContent() returns the position after found visible content + * or reached block element. */ template - EditorDOMPointType PointAfterContent() const { + EditorDOMPointType PointAfterReachedContent() const { MOZ_ASSERT(mContent); - return mContent ? EditorDOMPointType::After(mContent) - : EditorDOMPointType(); + return PointAtReachedContent().template NextPoint(); } /** @@ -336,6 +379,13 @@ class MOZ_STACK_CLASS WSScanResult final { return ReachedOtherBlockElement() && !GetContent()->IsEditable(); } + /** + * The scanner reached inline editing host boundary. + */ + [[nodiscard]] bool ReachedInlineEditingHostBoundary() const { + return mReason == WSType::InlineEditingHostBoundary; + } + /** * The scanner reached something non-text node. */ @@ -347,6 +397,7 @@ class MOZ_STACK_CLASS WSScanResult final { nsCOMPtr mContent; Maybe mOffset; WSType mReason; + ScanDirection mDirection = ScanDirection::Backward; }; class MOZ_STACK_CLASS WSRunScanner final { @@ -363,25 +414,29 @@ class MOZ_STACK_CLASS WSRunScanner final { aBlockInlineCheck), mBlockInlineCheck(aBlockInlineCheck) {} - // ScanNextVisibleNodeOrBlockBoundaryForwardFrom() returns the first visible - // node after aPoint. If there is no visible nodes after aPoint, returns - // topmost editable inline ancestor at end of current block. See comments - // around WSScanResult for the detail. + // ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom() returns the first visible + // node at or after aPoint. If there is no visible nodes after aPoint, + // returns topmost editable inline ancestor at end of current block. See + // comments around WSScanResult for the detail. When you reach a character, + // this returns WSScanResult both whose Point_Deprecated() and + // PointAtReachedContent() return the found character position. template - WSScanResult ScanNextVisibleNodeOrBlockBoundaryFrom( + WSScanResult ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom( const EditorDOMPointBase& aPoint) const; template - static WSScanResult ScanNextVisibleNodeOrBlockBoundary( + static WSScanResult ScanInclusiveNextVisibleNodeOrBlockBoundary( const Element* aEditingHost, const EditorDOMPointBase& aPoint, BlockInlineCheck aBlockInlineCheck) { return WSRunScanner(aEditingHost, aPoint, aBlockInlineCheck) - .ScanNextVisibleNodeOrBlockBoundaryFrom(aPoint); + .ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(aPoint); } // ScanPreviousVisibleNodeOrBlockBoundaryFrom() returns the first visible node // before aPoint. If there is no visible nodes before aPoint, returns topmost // editable inline ancestor at start of current block. See comments around - // WSScanResult for the detail. + // WSScanResult for the detail. When you reach a character, this returns + // WSScanResult whose Point_Deprecated() returns next point of the found + // character and PointAtReachedContent() returns the point at found character. template WSScanResult ScanPreviousVisibleNodeOrBlockBoundaryFrom( const EditorDOMPointBase& aPoint) const; @@ -588,6 +643,9 @@ class MOZ_STACK_CLASS WSRunScanner final { bool StartsFromBlockBoundary() const { return TextFragmentDataAtStartRef().StartsFromBlockBoundary(); } + bool StartsFromInlineEditingHostBoundary() const { + return TextFragmentDataAtStartRef().StartsFromInlineEditingHostBoundary(); + } bool StartsFromHardLineBreak() const { return TextFragmentDataAtStartRef().StartsFromHardLineBreak(); } @@ -618,6 +676,9 @@ class MOZ_STACK_CLASS WSRunScanner final { bool EndsByBlockBoundary() const { return TextFragmentDataAtStartRef().EndsByBlockBoundary(); } + bool EndsByInlineEditingHostBoundary() const { + return TextFragmentDataAtStartRef().EndsByInlineEditingHostBoundary(); + } MOZ_NEVER_INLINE_DEBUG Element* StartReasonOtherBlockElementPtr() const { return TextFragmentDataAtStartRef().StartReasonOtherBlockElementPtr(); @@ -688,6 +749,9 @@ class MOZ_STACK_CLASS WSRunScanner final { return mRightWSType == WSType::CurrentBlockBoundary || mRightWSType == WSType::OtherBlockBoundary; } + bool EndsByInlineEditingHostBoundary() const { + return mRightWSType == WSType::InlineEditingHostBoundary; + } /** * ComparePoint() compares aPoint with the white-spaces. @@ -916,6 +980,9 @@ class MOZ_STACK_CLASS WSRunScanner final { return mReason == WSType::CurrentBlockBoundary || mReason == WSType::OtherBlockBoundary; } + bool IsInlineEditingHostBoundary() const { + return mReason == WSType::InlineEditingHostBoundary; + } bool IsHardLineBreak() const { return mReason == WSType::CurrentBlockBoundary || mReason == WSType::OtherBlockBoundary || @@ -950,8 +1017,8 @@ class MOZ_STACK_CLASS WSRunScanner final { EditorDOMPoint mPoint; // Must be one of WSType::NotInitialized, // WSType::NonCollapsibleCharacters, WSType::SpecialContent, - // WSType::BRElement, WSType::CurrentBlockBoundary or - // WSType::OtherBlockBoundary. + // WSType::BRElement, WSType::CurrentBlockBoundary, + // WSType::OtherBlockBoundary or WSType::InlineEditingHostBoundary. WSType mReason = WSType::NotInitialized; }; @@ -1027,6 +1094,9 @@ class MOZ_STACK_CLASS WSRunScanner final { return mStart.IsOtherBlockBoundary(); } bool StartsFromBlockBoundary() const { return mStart.IsBlockBoundary(); } + bool StartsFromInlineEditingHostBoundary() const { + return mStart.IsInlineEditingHostBoundary(); + } bool StartsFromHardLineBreak() const { return mStart.IsHardLineBreak(); } bool EndsByNonCollapsibleCharacters() const { return mEnd.IsNonCollapsibleCharacters(); @@ -1053,6 +1123,9 @@ class MOZ_STACK_CLASS WSRunScanner final { } bool EndsByOtherBlockElement() const { return mEnd.IsOtherBlockBoundary(); } bool EndsByBlockBoundary() const { return mEnd.IsBlockBoundary(); } + bool EndsByInlineEditingHostBoundary() const { + return mEnd.IsInlineEditingHostBoundary(); + } WSType StartRawReason() const { return mStart.RawReason(); } WSType EndRawReason() const { return mEnd.RawReason(); } @@ -1224,7 +1297,7 @@ class MOZ_STACK_CLASS WSRunScanner final { bool FollowingContentMayBecomeFirstVisibleContent( const EditorDOMPointType& aPoint) const { MOZ_ASSERT(aPoint.IsSetAndValid()); - if (!mStart.IsHardLineBreak()) { + if (!mStart.IsHardLineBreak() && !mStart.IsInlineEditingHostBoundary()) { return false; } // If the point is before start of text fragment, that means that the @@ -1260,7 +1333,7 @@ class MOZ_STACK_CLASS WSRunScanner final { MOZ_ASSERT(aPoint.IsSetAndValid()); // If this fragment is ends by block boundary, always the caller needs // additional check. - if (mEnd.IsBlockBoundary()) { + if (mEnd.IsBlockBoundary() || mEnd.IsInlineEditingHostBoundary()) { return true; } -- cgit v1.2.3