From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- dom/events/ContentEventHandler.h | 553 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 553 insertions(+) create mode 100644 dom/events/ContentEventHandler.h (limited to 'dom/events/ContentEventHandler.h') diff --git a/dom/events/ContentEventHandler.h b/dom/events/ContentEventHandler.h new file mode 100644 index 0000000000..31f05ca007 --- /dev/null +++ b/dom/events/ContentEventHandler.h @@ -0,0 +1,553 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_ContentEventHandler_h_ +#define mozilla_ContentEventHandler_h_ + +#include "js/GCAPI.h" +#include "mozilla/Assertions.h" +#include "mozilla/EventForwards.h" +#include "mozilla/RangeBoundary.h" +#include "mozilla/dom/Selection.h" +#include "mozilla/dom/Text.h" +#include "nsCOMPtr.h" +#include "nsIFrame.h" +#include "nsINode.h" + +class nsPresContext; +class nsRange; + +struct nsRect; + +namespace mozilla { + +namespace dom { +class Element; +} // namespace dom + +enum LineBreakType { LINE_BREAK_TYPE_NATIVE, LINE_BREAK_TYPE_XP }; + +/* + * Query Content Event Handler + * ContentEventHandler is a helper class for EventStateManager. + * The platforms request some content informations, e.g., the selected text, + * the offset of the selected text and the text for specified range. + * This class answers to NS_QUERY_* events from actual contents. + */ + +class MOZ_STACK_CLASS ContentEventHandler { + private: + /** + * SimpleRangeBase is a helper template class of ContentEventHandler class + * that stores 2 DOM points as a range without observing the mutation. I.e., + * similar to dom::StaticRange, but can only be on the stack and does not have + * unnecessary features for ContentEventHandler so it is fast. + * Therefore, initializers are responsible for making sure the start/end nodes + * are in document order. This is enforced by assertions in DEBUG builds. + */ + template + class MOZ_STACK_CLASS SimpleRangeBase final { + public: + SimpleRangeBase(); + SimpleRangeBase(SimpleRangeBase&&) noexcept; + template + explicit SimpleRangeBase( + const SimpleRangeBase& aOther); +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + ~SimpleRangeBase(); +#endif + + void Clear() { + mRoot = nullptr; + mStart = {}; + mEnd = {}; + } + + bool IsPositioned() const { return mStart.IsSet() && mEnd.IsSet(); } + bool Collapsed() const { return mStart == mEnd && IsPositioned(); } + nsINode* GetStartContainer() const { return mStart.Container(); } + nsINode* GetEndContainer() const { return mEnd.Container(); } + uint32_t StartOffset() const { + return *mStart.Offset( + RangeBoundaryType::OffsetFilter::kValidOrInvalidOffsets); + } + uint32_t EndOffset() const { + return *mEnd.Offset( + RangeBoundaryType::OffsetFilter::kValidOrInvalidOffsets); + } + nsIContent* StartRef() const { return mStart.Ref(); } + nsIContent* EndRef() const { return mEnd.Ref(); } + + const RangeBoundaryType& Start() const { return mStart; } + const RangeBoundaryType& End() const { return mEnd; } + + nsINode* GetRoot() const { return mRoot; } + + // XXX: Make these use RangeBoundaries... + nsresult CollapseTo(const RawRangeBoundary& aBoundary) { + return SetStartAndEnd(aBoundary, aBoundary); + } + nsresult SetStart(const RawRangeBoundary& aStart); + nsresult SetEnd(const RawRangeBoundary& aEnd); + + // NOTE: These helpers can hide performance problems, as they perform a + // search to find aStartOffset in aStartContainer. + nsresult SetStart(nsINode* aStartContainer, uint32_t aStartOffset) { + return SetStart(RawRangeBoundary(aStartContainer, aStartOffset)); + } + nsresult SetEnd(nsINode* aEndContainer, uint32_t aEndOffset) { + return SetEnd(RawRangeBoundary(aEndContainer, aEndOffset)); + } + + nsresult SetEndAfter(nsINode* aEndContainer); + void SetStartAndEnd(const nsRange* aRange); + nsresult SetStartAndEnd(const RawRangeBoundary& aStart, + const RawRangeBoundary& aEnd); + + nsresult SelectNodeContents(const nsINode* aNodeToSelectContents); + + private: + inline void AssertStartIsBeforeOrEqualToEnd(); + + NodeType mRoot; + + RangeBoundaryType mStart; + RangeBoundaryType mEnd; + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + nsMutationGuard mMutationGuard; + Maybe mAssertNoGC; +#endif + }; + + using SimpleRange = SimpleRangeBase, RangeBoundary>; + using UnsafeSimpleRange = SimpleRangeBase; + + public: + using Element = dom::Element; + using Selection = dom::Selection; + + explicit ContentEventHandler(nsPresContext* aPresContext); + + // Handle aEvent in the current process. + MOZ_CAN_RUN_SCRIPT nsresult + HandleQueryContentEvent(WidgetQueryContentEvent* aEvent); + + // eQuerySelectedText event handler + MOZ_CAN_RUN_SCRIPT nsresult + OnQuerySelectedText(WidgetQueryContentEvent* aEvent); + // eQueryTextContent event handler + MOZ_CAN_RUN_SCRIPT nsresult + OnQueryTextContent(WidgetQueryContentEvent* aEvent); + // eQueryCaretRect event handler + MOZ_CAN_RUN_SCRIPT nsresult OnQueryCaretRect(WidgetQueryContentEvent* aEvent); + // eQueryTextRect event handler + MOZ_CAN_RUN_SCRIPT nsresult OnQueryTextRect(WidgetQueryContentEvent* aEvent); + // eQueryTextRectArray event handler + MOZ_CAN_RUN_SCRIPT nsresult + OnQueryTextRectArray(WidgetQueryContentEvent* aEvent); + // eQueryEditorRect event handler + MOZ_CAN_RUN_SCRIPT nsresult + OnQueryEditorRect(WidgetQueryContentEvent* aEvent); + // eQueryContentState event handler + MOZ_CAN_RUN_SCRIPT nsresult + OnQueryContentState(WidgetQueryContentEvent* aEvent); + // eQuerySelectionAsTransferable event handler + MOZ_CAN_RUN_SCRIPT nsresult + OnQuerySelectionAsTransferable(WidgetQueryContentEvent* aEvent); + // eQueryCharacterAtPoint event handler + MOZ_CAN_RUN_SCRIPT nsresult + OnQueryCharacterAtPoint(WidgetQueryContentEvent* aEvent); + // eQueryDOMWidgetHittest event handler + MOZ_CAN_RUN_SCRIPT nsresult + OnQueryDOMWidgetHittest(WidgetQueryContentEvent* aEvent); + + // NS_SELECTION_* event + MOZ_CAN_RUN_SCRIPT nsresult OnSelectionEvent(WidgetSelectionEvent* aEvent); + + protected: + RefPtr mDocument; + // mSelection is typically normal selection but if OnQuerySelectedText() + // is called, i.e., handling eQuerySelectedText, it's the specified selection + // by WidgetQueryContentEvent::mInput::mSelectionType. + RefPtr mSelection; + // mFirstSelectedSimpleRange is initialized from the first range of + // mSelection, if it exists. Otherwise, it is reset by Clear(). + SimpleRange mFirstSelectedSimpleRange; + RefPtr mRootElement; + + MOZ_CAN_RUN_SCRIPT nsresult Init(WidgetQueryContentEvent* aEvent); + MOZ_CAN_RUN_SCRIPT nsresult Init(WidgetSelectionEvent* aEvent); + + nsresult InitBasic(bool aRequireFlush = true); + MOZ_CAN_RUN_SCRIPT nsresult + InitCommon(EventMessage aEventMessage, + SelectionType aSelectionType = SelectionType::eNormal, + bool aRequireFlush = true); + /** + * InitRootContent() computes the root content of current focused editor. + * + * @param aNormalSelection This must be a Selection instance whose type is + * SelectionType::eNormal. + */ + MOZ_CAN_RUN_SCRIPT nsresult + InitRootContent(const Selection& aNormalSelection); + + public: + // FlatText means the text that is generated from DOM tree. The BR elements + // are replaced to native linefeeds. Other elements are ignored. + + // RawNodePosition stores a pair of node and offset in the node. + // When mNode is an element and mOffset is 0, the start position means after + // the open tag of mNode. + // This is useful to receive one or more sets of them instead of nsRange. + // This type is intended to be used for short-lived operations, and is thus + // marked MOZ_STACK_CLASS. + struct MOZ_STACK_CLASS RawNodePosition : public RawRangeBoundary { + // Only when mNode is an element node and mOffset is 0, mAfterOpenTag is + // referred. + bool mAfterOpenTag = true; + + RawNodePosition() = default; + explicit RawNodePosition(const RawNodePosition& aOther) + : RawRangeBoundary(aOther), + mAfterOpenTag(aOther.mAfterOpenTag) + // Don't use the copy constructor of mAssertNoGC. + {} + + RawNodePosition(nsINode* aContainer, uint32_t aOffset) + : RawRangeBoundary(aContainer, aOffset) {} + + RawNodePosition(nsINode* aContainer, nsIContent* aRef) + : RawRangeBoundary(aContainer, aRef) {} + + explicit RawNodePosition(const nsIFrame::ContentOffsets& aContentOffsets) + : RawRangeBoundary(aContentOffsets.content, aContentOffsets.offset) {} + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + ~RawNodePosition() { MOZ_DIAGNOSTIC_ASSERT(!mMutationGuard.Mutated(0)); } +#endif // #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + + public: + const RawNodePosition& operator=(const RawNodePosition& aOther) { + if (this != &aOther) { + RawRangeBoundary::operator=(aOther); + mAfterOpenTag = aOther.mAfterOpenTag; + } + return *this; + } + + bool operator==(const RawNodePosition& aOther) const { + return RawRangeBoundary::operator==(aOther) && + mAfterOpenTag == aOther.mAfterOpenTag; + } + + bool IsBeforeOpenTag() const { + return IsSet() && Container()->IsElement() && !Ref() && !mAfterOpenTag; + } + bool IsImmediatelyAfterOpenTag() const { + return IsSet() && Container()->IsElement() && !Ref() && mAfterOpenTag; + } + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + private: + nsMutationGuard mMutationGuard; + JS::AutoAssertNoGC mAssertNoGC; +#endif // #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + }; + + // RawNodePositionBefore isn't good name if Container() isn't an element node + // nor Offset() is not 0, though, when Container() is an element node and + // mOffset is 0, this is treated as before the open tag of Container(). + struct MOZ_STACK_CLASS RawNodePositionBefore final : public RawNodePosition { + RawNodePositionBefore(nsINode* aContainer, uint32_t aOffset) + : RawNodePosition(aContainer, aOffset) { + mAfterOpenTag = false; + } + + RawNodePositionBefore(nsINode* aContainer, nsIContent* aRef) + : RawNodePosition(aContainer, aRef) { + mAfterOpenTag = false; + } + }; + + // Get the flatten text length in the range. + // @param aStartPosition Start node and offset in the node of the range. + // @param aEndPosition End node and offset in the node of the range. + // @param aRootElement The root element of the editor or document. + // aRootElement won't cause any text including + // line breaks. + // @param aLength The result of the flatten text length of the + // range. + // @param aLineBreakType Whether this computes flatten text length with + // native line breakers on the platform or + // with XP line breaker (\n). + // @param aIsRemovingNode Should be true only when this is called from + // nsIMutationObserver::ContentRemoved(). + // When this is true, aStartPosition.mNode should + // be the root node of removing nodes and mOffset + // should be 0 and aEndPosition.mNode should be + // same as aStartPosition.mNode and mOffset should + // be number of the children of mNode. + static nsresult GetFlatTextLengthInRange( + const RawNodePosition& aStartPosition, + const RawNodePosition& aEndPosition, const Element* aRootElement, + uint32_t* aLength, LineBreakType aLineBreakType, + bool aIsRemovingNode = false); + // Computes the native text length between aStartOffset and aEndOffset of + // aTextNode. + static uint32_t GetNativeTextLength(const dom::Text& aTextNode, + uint32_t aStartOffset, + uint32_t aEndOffset); + // Get the native text length of aTextNode. + static uint32_t GetNativeTextLength(const dom::Text& aTextNode, + uint32_t aMaxLength = UINT32_MAX); + + static uint32_t GetNativeTextLength(const nsAString& aText); + + protected: + // Get the text length of aTextNode. + static uint32_t GetTextLength(const dom::Text& aTextNode, + LineBreakType aLineBreakType, + uint32_t aMaxLength = UINT32_MAX); + // Get the text length of a given range of a content node in + // the given line break type. + static uint32_t GetTextLengthInRange(const dom::Text& aTextNode, + uint32_t aXPStartOffset, + uint32_t aXPEndOffset, + LineBreakType aLineBreakType); + // Get the contents in aElement (meaning all children of aElement) as plain + // text. E.g., specifying mRootElement gets whole text in it. + // Note that the result is not same as .textContent. The result is + // optimized for native IMEs. For example,
element and some block + // elements causes "\n" (or "\r\n"), see also ShouldBreakLineBefore(). + nsresult GenerateFlatTextContent(const Element* aElement, nsString& aString, + LineBreakType aLineBreakType); + // Get the contents of aRange as plain text. + template + nsresult GenerateFlatTextContent( + const SimpleRangeBase& aSimpleRange, + nsString& aString, LineBreakType aLineBreakType); + // Get offset of start of aRange. Note that the result includes the length + // of line breaker caused by the start of aContent because aRange never + // includes the line breaker caused by its start node. + template + nsresult GetStartOffset(const SimpleRangeType& aSimpleRange, + uint32_t* aOffset, LineBreakType aLineBreakType); + // Check if we should insert a line break before aContent. + // This should return false only when aContent is an html element which + // is typically used in a paragraph like . + static bool ShouldBreakLineBefore(const nsIContent& aContent, + const Element* aRootElement); + // Get the line breaker length. + static inline uint32_t GetBRLength(LineBreakType aLineBreakType); + static LineBreakType GetLineBreakType(WidgetQueryContentEvent* aEvent); + static LineBreakType GetLineBreakType(WidgetSelectionEvent* aEvent); + static LineBreakType GetLineBreakType(bool aUseNativeLineBreak); + // Returns focused content (including its descendant documents). + nsIContent* GetFocusedContent(); + // QueryContentRect() sets the rect of aContent's frame(s) to aEvent. + nsresult QueryContentRect(nsIContent* aContent, + WidgetQueryContentEvent* aEvent); + + template + struct MOZ_STACK_CLASS DOMRangeAndAdjustedOffsetInFlattenedTextBase { + bool RangeStartsFromLastTextNode() const { + return mLastTextNode && mRange.GetStartContainer() == mLastTextNode; + } + bool RangeStartsFromEndOfContainer() const { + return mRange.GetStartContainer() && + mRange.GetStartContainer()->Length() == mRange.StartOffset(); + } + bool RangeStartsFromContent() const { + return mRange.GetStartContainer() && + mRange.GetStartContainer()->IsContent(); + } + + // The range in the DOM tree. + RangeType mRange; + // Actual start offset of the range in the flattened text. If aOffset + // of ConvertFlatTextOffsetToDOMRange() is middle of a surrogate pair, + // a CRLF or a complex character of some languages, this may be set to + // different offset. + uint32_t mAdjustedOffset = 0; + // The last text node which is found while walking the tree. + // If the range ends in a text node, this is the text node. Otherwise, + // the last found text node before the end container of mRange. + TextNodeType mLastTextNode = nullptr; + }; + using DOMRangeAndAdjustedOffsetInFlattenedText = + DOMRangeAndAdjustedOffsetInFlattenedTextBase>; + using UnsafeDOMRangeAndAdjustedOffsetInFlattenedText = + DOMRangeAndAdjustedOffsetInFlattenedTextBase; + + /** + * Scans the DOM tree and set mRange as same as from aOffset to aOffset + + * aLength in the flattened text. + * NOTE: Use ConvertFlatTextOffsetToDOMRange() or + * ConvertFlatTextOffsetToUnsafeDOMRange() instead of + * ConvertFlatTextOffsetToDOMRangeBase(). + */ + template + Result, + nsresult> + ConvertFlatTextOffsetToDOMRangeBase(uint32_t aOffset, uint32_t aLength, + LineBreakType aLineBreakType, + bool aExpandToClusterBoundaries); + MOZ_ALWAYS_INLINE Result + ConvertFlatTextOffsetToDOMRange(uint32_t aOffset, uint32_t aLength, + LineBreakType aLineBreakType, + bool aExpandToClusterBoundaries) { + return ConvertFlatTextOffsetToDOMRangeBase>( + aOffset, aLength, aLineBreakType, aExpandToClusterBoundaries); + } + MOZ_ALWAYS_INLINE + Result + ConvertFlatTextOffsetToUnsafeDOMRange(uint32_t aOffset, uint32_t aLength, + LineBreakType aLineBreakType, + bool aExpandToClusterBoundaries) { + return ConvertFlatTextOffsetToDOMRangeBase( + aOffset, aLength, aLineBreakType, aExpandToClusterBoundaries); + } + + // If the aSimpleRange isn't in text node but next to a text node, + // this method modifies it in the text node. Otherwise, not modified. + // Note that aSimpleRange must be collapsed. + nsresult AdjustCollapsedRangeMaybeIntoTextNode(SimpleRange& aSimpleRange); + // Convert the frame relative offset to be relative to the root frame of the + // root presContext (but still measured in appUnits of aFrame's presContext). + nsresult ConvertToRootRelativeOffset(nsIFrame* aFrame, nsRect& aRect); + // Expand aXPOffset to the nearest offset in cluster boundary. aForward is + // true, it is expanded to forward. + // FYI: Due to `nsFrameSelection::GetFrameForNodeOffset()`, this cannot + // take `const dom::Text&`. + nsresult ExpandToClusterBoundary(dom::Text& aTextNode, bool aForward, + uint32_t* aXPOffset) const; + + using FontRangeArray = nsTArray; + static void AppendFontRanges(FontRangeArray& aFontRanges, + const dom::Text& aTextNode, uint32_t aBaseOffset, + uint32_t aXPStartOffset, uint32_t aXPEndOffset, + LineBreakType aLineBreakType); + nsresult GenerateFlatFontRanges(const UnsafeSimpleRange& aSimpleRange, + FontRangeArray& aFontRanges, + uint32_t& aLength, + LineBreakType aLineBreakType); + nsresult QueryTextRectByRange(const SimpleRange& aSimpleRange, + LayoutDeviceIntRect& aRect, + WritingMode& aWritingMode); + + struct MOZ_STACK_CLASS FrameAndNodeOffset final { + // mFrame is safe since this can live in only stack class and + // ContentEventHandler doesn't modify layout after + // ContentEventHandler::Init() flushes pending layout. In other words, + // this struct shouldn't be used before calling + // ContentEventHandler::Init(). + nsIFrame* mFrame; + // offset in the node of mFrame + int32_t mOffsetInNode; + + FrameAndNodeOffset() : mFrame(nullptr), mOffsetInNode(-1) {} + + FrameAndNodeOffset(nsIFrame* aFrame, int32_t aStartOffsetInNode) + : mFrame(aFrame), mOffsetInNode(aStartOffsetInNode) {} + + nsIFrame* operator->() { return mFrame; } + const nsIFrame* operator->() const { return mFrame; } + operator nsIFrame*() { return mFrame; } + operator const nsIFrame*() const { return mFrame; } + bool IsValid() const { return mFrame && mOffsetInNode >= 0; } + }; + // Get first frame after the start of the given range for computing text rect. + // This returns invalid FrameAndNodeOffset if there is no content which + // should affect to computing text rect in the range. mOffsetInNode is start + // offset in the frame. + template + FrameAndNodeOffset GetFirstFrameInRangeForTextRect( + const SimpleRangeBase& aSimpleRange); + + // Get last frame before the end of the given range for computing text rect. + // This returns invalid FrameAndNodeOffset if there is no content which + // should affect to computing text rect in the range. mOffsetInNode is end + // offset in the frame. + template + FrameAndNodeOffset GetLastFrameInRangeForTextRect( + const SimpleRangeBase& aSimpleRange); + + struct MOZ_STACK_CLASS FrameRelativeRect final { + // mRect is relative to the mBaseFrame's position. + nsRect mRect; + nsIFrame* mBaseFrame; + + FrameRelativeRect() : mBaseFrame(nullptr) {} + + explicit FrameRelativeRect(nsIFrame* aBaseFrame) : mBaseFrame(aBaseFrame) {} + + FrameRelativeRect(const nsRect& aRect, nsIFrame* aBaseFrame) + : mRect(aRect), mBaseFrame(aBaseFrame) {} + + bool IsValid() const { return mBaseFrame != nullptr; } + + // Returns an nsRect relative to aBaseFrame instead of mBaseFrame. + nsRect RectRelativeTo(nsIFrame* aBaseFrame) const; + }; + + // Returns a rect for line breaker before the node of aFrame (If aFrame is + // a
frame or a block level frame, it causes a line break at its + // element's open tag, see also ShouldBreakLineBefore()). Note that this + // doesn't check if aFrame should cause line break in non-debug build. + FrameRelativeRect GetLineBreakerRectBefore(nsIFrame* aFrame); + + // Returns a line breaker rect after aTextNode as there is a line breaker + // immediately after aTextNode. This is useful when following block + // element causes a line break before it and it needs to compute the line + // breaker's rect. For example, if there is |

abc

def

|, the + // rect of 2nd

's line breaker should be at right of "c" in the first + //

, not the start of 2nd

. The result is relative to the last text + // frame which represents the last character of aTextNode. + FrameRelativeRect GuessLineBreakerRectAfter(const dom::Text& aTextNode); + + // Returns a guessed first rect. I.e., it may be different from actual + // caret when selection is collapsed at start of aFrame. For example, this + // guess the caret rect only with the content box of aFrame and its font + // height like: + // +-aFrame----------------- (border box) + // | + // | +--------------------- (content box) + // | | I + // ^ guessed caret rect + // However, actual caret is computed with more information like line-height, + // child frames of aFrame etc. But this does not emulate actual caret + // behavior exactly for simpler and faster code because it's difficult and + // we're not sure it's worthwhile to do it with complicated implementation. + FrameRelativeRect GuessFirstCaretRectIn(nsIFrame* aFrame); + + // Make aRect non-empty. If width and/or height is 0, these methods set them + // to 1. Note that it doesn't set nsRect's width nor height to one device + // pixel because using nsRect::ToOutsidePixels() makes actual width or height + // to 2 pixels because x and y may not be aligned to device pixels. + void EnsureNonEmptyRect(nsRect& aRect) const; + void EnsureNonEmptyRect(LayoutDeviceIntRect& aRect) const; + + /** + * Compute caret rect before or after a character rect. + */ + static LayoutDeviceIntRect GetCaretRectBefore( + const LayoutDeviceIntRect& aCharRect, const WritingMode& aWritingMode); + static LayoutDeviceIntRect GetCaretRectAfter( + const LayoutDeviceIntRect& aCharRect, const WritingMode& aWritingMode); + static nsRect GetCaretRectBefore(const nsRect& aCharRect, + const WritingMode& aWritingMode); + static nsRect GetCaretRectAfter(nsPresContext& aPresContext, + const nsRect& aCharRect, + const WritingMode& aWritingMode); +}; + +} // namespace mozilla + +#endif // mozilla_ContentEventHandler_h_ -- cgit v1.2.3