/* 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

/*
 * 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 <typename NodeType, typename RangeBoundaryType>
  class MOZ_STACK_CLASS SimpleRangeBase final {
   public:
    SimpleRangeBase();
    SimpleRangeBase(SimpleRangeBase<NodeType, RangeBoundaryType>&&) noexcept;
    template <typename OtherNodeType, typename OtherRangeBoundaryType>
    explicit SimpleRangeBase(
        const SimpleRangeBase<OtherNodeType, OtherRangeBoundaryType>& aOther);
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
    ~SimpleRangeBase();
#endif

    void Clear() {
      mRoot = nullptr;
      mStart = RangeBoundaryType{};
      mEnd = RangeBoundaryType{};
    }

    bool IsPositioned() const { return mStart.IsSet() && mEnd.IsSet(); }
    bool Collapsed() const { return mStart == mEnd && IsPositioned(); }
    nsINode* GetStartContainer() const { return mStart.GetContainer(); }
    nsINode* GetEndContainer() const { return mEnd.GetContainer(); }
    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(nsIContent* 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<JS::AutoAssertNoGC> mAssertNoGC;
#endif
  };

  using SimpleRange = SimpleRangeBase<RefPtr<nsINode>, RangeBoundary>;
  using UnsafeSimpleRange = SimpleRangeBase<nsINode*, RawRangeBoundary>;

 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);
  // eQueryDropTargetHittest event handler
  MOZ_CAN_RUN_SCRIPT nsresult
  OnQueryDropTargetHittest(WidgetQueryContentEvent* aEvent);

  // NS_SELECTION_* event
  MOZ_CAN_RUN_SCRIPT nsresult OnSelectionEvent(WidgetSelectionEvent* aEvent);

 protected:
  RefPtr<dom::Document> 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<Selection> mSelection;
  // mFirstSelectedSimpleRange is initialized from the first range of
  // mSelection, if it exists.  Otherwise, it is reset by Clear().
  SimpleRange mFirstSelectedSimpleRange;
  RefPtr<Element> mRootElement;

  dom::EditContext* GetEditContext() const {
    MOZ_ASSERT(mRootElement);
    if (MOZ_LIKELY(!mRootElement->HasFlag(ELEMENT_HAS_EDIT_CONTEXT))) {
      return nullptr;
    }
    auto* htmlElement = nsGenericHTMLElement::FromNode(mRootElement);
    MOZ_ASSERT(htmlElement);
    dom::EditContext* editContext = htmlElement->GetEditContext();
    MOZ_ASSERT(editContext);
    return editContext;
  }

  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() initializes mRootElement and return the first selection
   * range in it.
   *
   * @param aNormalSelection    This must be a Selection instance whose type is
   *                            SelectionType::eNormal.
   * @return The first valid range of aNormalSelection.
   */
  MOZ_CAN_RUN_SCRIPT Result<nsRange*, 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;
    MOZ_IMPLICIT RawNodePosition(const RawNodePosition& aOther)
        : RawRangeBoundary(aOther),
          mAfterOpenTag(aOther.mAfterOpenTag)
    // Don't use the copy constructor of mAssertNoGC.
    {}

    /**
     * Factory method returning a RawNodePosition object which points start of
     * first content of aContainer (first child or first character in the data).
     * I.e., if aContainer is an element node, the result points before the
     * first child but after the open tag, e.g., <div>{}abc</div> if aContainer
     * is the <div>.  This is important to understand the difference with the
     * result of Before().
     */
    static RawNodePosition BeforeFirstContentOf(const nsINode& aContainer) {
      return RawNodePosition(const_cast<nsINode*>(&aContainer), 0u);
    }

    /**
     * Factory method returning a RawNodePosition object which points after
     * aContent.  I.e., if aContent is an element node, the result points after
     * its close tag, e.g., `<div>abc</div>{}` if aContent is the <div>.
     */
    static RawNodePosition After(const nsIContent& aContent) {
      RawNodePosition it(aContent.GetParentNode(),
                         const_cast<nsIContent*>(&aContent));
      it.mAfterOpenTag = false;
      return it;
    }

    /**
     * Factory method returning a RawNodePosition object which points end of
     * aContainer.  If aContainer is an element node, the result points before
     * its close tag, e.g., `<div>abc{}</div>` if aContainer is the <div>.
     */
    static RawNodePosition AtEndOf(const nsINode& aContainer) {
      return RawNodePosition(const_cast<nsINode*>(&aContainer),
                             aContainer.IsText()
                                 ? aContainer.AsText()->TextDataLength()
                                 : aContainer.GetChildCount());
    }

    /**
     * Factory method returning a RawNodePosition object which points before
     * aContent.  I.e., if aContent is an element node, the result points
     * before its open tag, e.g., `{}<div>abc</div>` if aContent is the <div>.
     * Note that this sets different containers whether aContent is being
     * removed or not.  If aContent is being removed, i.e., this is used in
     * nsIMutationObserver::ContentRemoved(), aContent is already not a child
     * of its ex-parent.  Therefore, the container becomes aContent, but
     * indicates that it points before the container with mAfterOpenTag.
     * On the other hand, if it's not being removed, the container is set to
     * the parent node of aContent.  So, in this case, it points after the
     * previous sibling of aContent actually.
     */
    static RawNodePosition Before(const nsIContent& aContent) {
      return RawNodePosition(aContent.GetParentNode(),
                             aContent.GetPreviousSibling());
    }

    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() && GetContainer()->IsElement() && !Ref() && !mAfterOpenTag;
    }
    bool IsImmediatelyAfterOpenTag() const {
      return IsSet() && GetContainer()->IsElement() && !Ref() && mAfterOpenTag;
    }

#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
   private:
    nsMutationGuard mMutationGuard;
    JS::AutoAssertNoGC mAssertNoGC;
#endif  // #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
  };

  /**
   * Get the flatten text length in the range.
   *
   * @param aStartPosition      Start node and offset in the node of the range.
   *                            If the container is an element node, it's
   *                            important to start from before or after its open
   *                            tag because open tag of some elements causes a
   *                            line break in the result.  If you need the line
   *                            break, you need to use
   *                            RawNodePosition::Before().
   * @param aEndPosition        End node and offset in the node of the range.
   *                            If you don't want to include line break which is
   *                            caused by the open tag of the container when
   *                            you specify start of an element node, you need
   *                            to use RawNodePosition::Before().
   * @param aRootElement        The root element of the editor or document.
   *                            aRootElement won't cause any text including
   *                            line breaks.
   */
  static Result<uint32_t, nsresult> GetFlatTextLengthInRange(
      const RawNodePosition& aStartPosition,
      const RawNodePosition& aEndPosition, const Element* aRootElement);

  // 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);

  // Get the range between start offset and end offset
  MOZ_CAN_RUN_SCRIPT
  already_AddRefed<nsRange> GetRangeFromFlatTextOffset(
      WidgetContentCommandEvent* aEvent, uint32_t aOffset, uint32_t aLength);

  // Get the contents of aRange as plain text.
  nsresult GenerateFlatTextContent(const nsRange* aRange, nsString& aString);

 protected:
  // Get the text length of aTextNode.
  static uint32_t GetTextLength(const dom::Text& aTextNode,
                                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);
  // 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, <br> element and some block
  // elements causes "\n", see also ShouldBreakLineBefore().
  nsresult GenerateFlatTextContent(const Element* aElement, nsString& aString);
  // Get the contents of aRange as plain text.
  template <typename NodeType, typename RangeBoundaryType>
  nsresult GenerateFlatTextContent(
      const SimpleRangeBase<NodeType, RangeBoundaryType>& aSimpleRange,
      nsString& aString);
  // 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 <typename SimpleRangeType>
  Result<uint32_t, nsresult> GetStartOffset(
      const SimpleRangeType& aSimpleRange) const;
  // 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 <em>.
  static bool ShouldBreakLineBefore(const nsIContent& aContent,
                                    const Element* aRootElement);
  // Get the line breaker length.
  constexpr static uint32_t kBRLength = 1;
  // 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 <typename RangeType, typename TextNodeType>
  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<SimpleRange,
                                                   RefPtr<dom::Text>>;
  using UnsafeDOMRangeAndAdjustedOffsetInFlattenedText =
      DOMRangeAndAdjustedOffsetInFlattenedTextBase<UnsafeSimpleRange,
                                                   dom::Text*>;

  /**
   * 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<RangeType, TextNodeType>().
   */
  template <typename RangeType, typename TextNodeType>
  Result<DOMRangeAndAdjustedOffsetInFlattenedTextBase<RangeType, TextNodeType>,
         nsresult>
  ConvertFlatTextOffsetToDOMRangeBase(uint32_t aOffset, uint32_t aLength,
                                      bool aExpandToClusterBoundaries);
  MOZ_ALWAYS_INLINE Result<DOMRangeAndAdjustedOffsetInFlattenedText, nsresult>
  ConvertFlatTextOffsetToDOMRange(uint32_t aOffset, uint32_t aLength,
                                  bool aExpandToClusterBoundaries) {
    return ConvertFlatTextOffsetToDOMRangeBase<SimpleRange, RefPtr<dom::Text>>(
        aOffset, aLength, aExpandToClusterBoundaries);
  }
  MOZ_ALWAYS_INLINE
  Result<UnsafeDOMRangeAndAdjustedOffsetInFlattenedText, nsresult>
  ConvertFlatTextOffsetToUnsafeDOMRange(uint32_t aOffset, uint32_t aLength,
                                        bool aExpandToClusterBoundaries) {
    return ConvertFlatTextOffsetToDOMRangeBase<UnsafeSimpleRange, dom::Text*>(
        aOffset, aLength, 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<mozilla::FontRange>;
  static void AppendFontRanges(FontRangeArray& aFontRanges,
                               const dom::Text& aTextNode, uint32_t aBaseOffset,
                               uint32_t aXPStartOffset, uint32_t aXPEndOffset);
  nsresult GenerateFlatFontRanges(const UnsafeSimpleRange& aSimpleRange,
                                  FontRangeArray& aFontRanges,
                                  uint32_t& aLength);
  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 <typename NodeType, typename RangeBoundaryType>
  FrameAndNodeOffset GetFirstFrameInRangeForTextRect(
      const SimpleRangeBase<NodeType, RangeBoundaryType>& 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 <typename NodeType, typename RangeBoundaryType>
  FrameAndNodeOffset GetLastFrameInRangeForTextRect(
      const SimpleRangeBase<NodeType, RangeBoundaryType>& 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 <br> 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 |<p>abc</p><p>def</p>|, the
  // rect of 2nd <p>'s line breaker should be at right of "c" in the first
  // <p>, not the start of 2nd <p>.  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);

  nsresult QueryHittestImpl(WidgetQueryContentEvent* aEvent, bool aFlushLayout,
                            bool aPerformRetargeting,
                            Element** aContentUnderMouse);
};

}  // namespace mozilla

#endif  // mozilla_ContentEventHandler_h_
