Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 */
#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 {
* 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 {
SimpleRangeBase(SimpleRangeBase<NodeType, RangeBoundaryType>&&) noexcept;
template <typename OtherNodeType, typename OtherRangeBoundaryType>
explicit SimpleRangeBase(
const SimpleRangeBase<OtherNodeType, OtherRangeBoundaryType>& aOther);
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(
uint32_t EndOffset() const {
return *mEnd.Offset(
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);
inline void AssertStartIsBeforeOrEqualToEnd();
NodeType mRoot;
RangeBoundaryType mStart;
RangeBoundaryType mEnd;
nsMutationGuard mMutationGuard;
Maybe<JS::AutoAssertNoGC> mAssertNoGC;
using SimpleRange = SimpleRangeBase<RefPtr<nsINode>, RangeBoundary>;
using UnsafeSimpleRange = SimpleRangeBase<nsINode*, RawRangeBoundary>;
using Element = dom::Element;
using Selection = dom::Selection;
explicit ContentEventHandler(nsPresContext* aPresContext);
// Handle aEvent in the current process.
HandleQueryContentEvent(WidgetQueryContentEvent* aEvent);
// eQuerySelectedText event handler
OnQuerySelectedText(WidgetQueryContentEvent* aEvent);
// eQueryTextContent event handler
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
OnQueryTextRectArray(WidgetQueryContentEvent* aEvent);
// eQueryEditorRect event handler
OnQueryEditorRect(WidgetQueryContentEvent* aEvent);
// eQueryContentState event handler
OnQueryContentState(WidgetQueryContentEvent* aEvent);
// eQuerySelectionAsTransferable event handler
OnQuerySelectionAsTransferable(WidgetQueryContentEvent* aEvent);
// eQueryCharacterAtPoint event handler
OnQueryCharacterAtPoint(WidgetQueryContentEvent* aEvent);
// eQueryDOMWidgetHittest event handler
OnQueryDOMWidgetHittest(WidgetQueryContentEvent* aEvent);
// NS_SELECTION_* event
MOZ_CAN_RUN_SCRIPT nsresult OnSelectionEvent(WidgetSelectionEvent* aEvent);
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;
MOZ_CAN_RUN_SCRIPT nsresult Init(WidgetQueryContentEvent* aEvent);
MOZ_CAN_RUN_SCRIPT nsresult Init(WidgetSelectionEvent* aEvent);
nsresult InitBasic(bool aRequireFlush = true);
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.
InitRootContent(const Selection& aNormalSelection);
// 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),
// 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) {}
~RawNodePosition() { MOZ_DIAGNOSTIC_ASSERT(!mMutationGuard.Mutated(0)); }
const RawNodePosition& operator=(const RawNodePosition& aOther) {
if (this != &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;
nsMutationGuard mMutationGuard;
JS::AutoAssertNoGC mAssertNoGC;
// 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);
// 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, <br> 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 <typename NodeType, typename RangeBoundaryType>
nsresult GenerateFlatTextContent(
const SimpleRangeBase<NodeType, RangeBoundaryType>& 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 <typename SimpleRangeType>
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 <em>.
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 <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() &&
// 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 =
using UnsafeDOMRangeAndAdjustedOffsetInFlattenedText =
* 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>,
ConvertFlatTextOffsetToDOMRangeBase(uint32_t aOffset, uint32_t aLength,
LineBreakType aLineBreakType,
bool aExpandToClusterBoundaries);
MOZ_ALWAYS_INLINE Result<DOMRangeAndAdjustedOffsetInFlattenedText, nsresult>
ConvertFlatTextOffsetToDOMRange(uint32_t aOffset, uint32_t aLength,
LineBreakType aLineBreakType,
bool aExpandToClusterBoundaries) {
return ConvertFlatTextOffsetToDOMRangeBase<SimpleRange, RefPtr<dom::Text>>(
aOffset, aLength, aLineBreakType, aExpandToClusterBoundaries);
Result<UnsafeDOMRangeAndAdjustedOffsetInFlattenedText, nsresult>
ConvertFlatTextOffsetToUnsafeDOMRange(uint32_t aOffset, uint32_t aLength,
LineBreakType aLineBreakType,
bool aExpandToClusterBoundaries) {
return ConvertFlatTextOffsetToDOMRangeBase<UnsafeSimpleRange, dom::Text*>(
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<mozilla::FontRange>;
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 <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);
} // namespace mozilla
#endif // mozilla_ContentEventHandler_h_