1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #ifndef mozilla_ContentEventHandler_h_
8 #define mozilla_ContentEventHandler_h_
10 #include "mozilla/EventForwards.h"
11 #include "mozilla/dom/Selection.h"
28 enum LineBreakType
{ LINE_BREAK_TYPE_NATIVE
, LINE_BREAK_TYPE_XP
};
31 * Query Content Event Handler
32 * ContentEventHandler is a helper class for EventStateManager.
33 * The platforms request some content informations, e.g., the selected text,
34 * the offset of the selected text and the text for specified range.
35 * This class answers to NS_QUERY_* events from actual contents.
38 class MOZ_STACK_CLASS ContentEventHandler
{
41 * RawRange is a helper class of ContentEventHandler class. The caller is
42 * responsible for making sure the start/end nodes are in document order.
43 * This is enforced by assertions in DEBUG builds.
45 class MOZ_STACK_CLASS RawRange final
{
51 mStart
= RangeBoundary();
52 mEnd
= RangeBoundary();
55 bool IsPositioned() const { return mStart
.IsSet() && mEnd
.IsSet(); }
56 bool Collapsed() const { return mStart
== mEnd
&& IsPositioned(); }
57 nsINode
* GetStartContainer() const { return mStart
.Container(); }
58 nsINode
* GetEndContainer() const { return mEnd
.Container(); }
59 uint32_t StartOffset() const {
60 return *mStart
.Offset(
61 RangeBoundary::OffsetFilter::kValidOrInvalidOffsets
);
63 uint32_t EndOffset() const {
64 return *mEnd
.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets
);
66 nsIContent
* StartRef() const { return mStart
.Ref(); }
67 nsIContent
* EndRef() const { return mEnd
.Ref(); }
69 const RangeBoundary
& Start() const { return mStart
; }
70 const RangeBoundary
& End() const { return mEnd
; }
72 // XXX: Make these use RangeBoundaries...
73 nsresult
CollapseTo(const RawRangeBoundary
& aBoundary
) {
74 return SetStartAndEnd(aBoundary
, aBoundary
);
76 nsresult
SetStart(const RawRangeBoundary
& aStart
);
77 nsresult
SetEnd(const RawRangeBoundary
& aEnd
);
79 // NOTE: These helpers can hide performance problems, as they perform a
80 // search to find aStartOffset in aStartContainer.
81 nsresult
SetStart(nsINode
* aStartContainer
, uint32_t aStartOffset
) {
82 return SetStart(RawRangeBoundary(aStartContainer
, aStartOffset
));
84 nsresult
SetEnd(nsINode
* aEndContainer
, uint32_t aEndOffset
) {
85 return SetEnd(RawRangeBoundary(aEndContainer
, aEndOffset
));
88 nsresult
SetEndAfter(nsINode
* aEndContainer
);
89 void SetStartAndEnd(const nsRange
* aRange
);
90 nsresult
SetStartAndEnd(const RawRangeBoundary
& aStart
,
91 const RawRangeBoundary
& aEnd
);
93 nsresult
SelectNodeContents(const nsINode
* aNodeToSelectContents
);
96 inline void AssertStartIsBeforeOrEqualToEnd();
98 nsCOMPtr
<nsINode
> mRoot
;
100 RangeBoundary mStart
;
105 using Element
= dom::Element
;
106 using Selection
= dom::Selection
;
108 explicit ContentEventHandler(nsPresContext
* aPresContext
);
110 // Handle aEvent in the current process.
111 MOZ_CAN_RUN_SCRIPT nsresult
112 HandleQueryContentEvent(WidgetQueryContentEvent
* aEvent
);
114 // eQuerySelectedText event handler
115 MOZ_CAN_RUN_SCRIPT nsresult
116 OnQuerySelectedText(WidgetQueryContentEvent
* aEvent
);
117 // eQueryTextContent event handler
118 MOZ_CAN_RUN_SCRIPT nsresult
119 OnQueryTextContent(WidgetQueryContentEvent
* aEvent
);
120 // eQueryCaretRect event handler
121 MOZ_CAN_RUN_SCRIPT nsresult
OnQueryCaretRect(WidgetQueryContentEvent
* aEvent
);
122 // eQueryTextRect event handler
123 MOZ_CAN_RUN_SCRIPT nsresult
OnQueryTextRect(WidgetQueryContentEvent
* aEvent
);
124 // eQueryTextRectArray event handler
125 MOZ_CAN_RUN_SCRIPT nsresult
126 OnQueryTextRectArray(WidgetQueryContentEvent
* aEvent
);
127 // eQueryEditorRect event handler
128 MOZ_CAN_RUN_SCRIPT nsresult
129 OnQueryEditorRect(WidgetQueryContentEvent
* aEvent
);
130 // eQueryContentState event handler
131 MOZ_CAN_RUN_SCRIPT nsresult
132 OnQueryContentState(WidgetQueryContentEvent
* aEvent
);
133 // eQuerySelectionAsTransferable event handler
134 MOZ_CAN_RUN_SCRIPT nsresult
135 OnQuerySelectionAsTransferable(WidgetQueryContentEvent
* aEvent
);
136 // eQueryCharacterAtPoint event handler
137 MOZ_CAN_RUN_SCRIPT nsresult
138 OnQueryCharacterAtPoint(WidgetQueryContentEvent
* aEvent
);
139 // eQueryDOMWidgetHittest event handler
140 MOZ_CAN_RUN_SCRIPT nsresult
141 OnQueryDOMWidgetHittest(WidgetQueryContentEvent
* aEvent
);
143 // NS_SELECTION_* event
144 MOZ_CAN_RUN_SCRIPT nsresult
OnSelectionEvent(WidgetSelectionEvent
* aEvent
);
147 RefPtr
<dom::Document
> mDocument
;
148 // mSelection is typically normal selection but if OnQuerySelectedText()
149 // is called, i.e., handling eQuerySelectedText, it's the specified selection
150 // by WidgetQueryContentEvent::mInput::mSelectionType.
151 RefPtr
<Selection
> mSelection
;
152 // mFirstSelectedRawRange is initialized from the first range of mSelection,
153 // if it exists. Otherwise, it is reset by Clear().
154 RawRange mFirstSelectedRawRange
;
155 RefPtr
<Element
> mRootElement
;
157 MOZ_CAN_RUN_SCRIPT nsresult
Init(WidgetQueryContentEvent
* aEvent
);
158 MOZ_CAN_RUN_SCRIPT nsresult
Init(WidgetSelectionEvent
* aEvent
);
160 nsresult
InitBasic(bool aRequireFlush
= true);
161 MOZ_CAN_RUN_SCRIPT nsresult
162 InitCommon(EventMessage aEventMessage
,
163 SelectionType aSelectionType
= SelectionType::eNormal
,
164 bool aRequireFlush
= true);
166 * InitRootContent() computes the root content of current focused editor.
168 * @param aNormalSelection This must be a Selection instance whose type is
169 * SelectionType::eNormal.
171 MOZ_CAN_RUN_SCRIPT nsresult
172 InitRootContent(const Selection
& aNormalSelection
);
175 // FlatText means the text that is generated from DOM tree. The BR elements
176 // are replaced to native linefeeds. Other elements are ignored.
178 // NodePosition stores a pair of node and offset in the node.
179 // When mNode is an element and mOffset is 0, the start position means after
180 // the open tag of mNode.
181 // This is useful to receive one or more sets of them instead of nsRange.
182 // This type is intended to be used for short-lived operations, and is thus
183 // marked MOZ_STACK_CLASS.
184 struct MOZ_STACK_CLASS NodePosition
: public RangeBoundary
{
185 // Only when mNode is an element node and mOffset is 0, mAfterOpenTag is
187 bool mAfterOpenTag
= true;
189 NodePosition() = default;
191 NodePosition(nsINode
* aContainer
, uint32_t aOffset
)
192 : RangeBoundary(aContainer
, aOffset
) {}
194 NodePosition(nsINode
* aContainer
, nsIContent
* aRef
)
195 : RangeBoundary(aContainer
, aRef
) {}
197 explicit NodePosition(const nsIFrame::ContentOffsets
& aContentOffsets
)
198 : RangeBoundary(aContentOffsets
.content
, aContentOffsets
.offset
) {}
201 bool operator==(const NodePosition
& aOther
) const {
202 return RangeBoundary::operator==(aOther
) &&
203 mAfterOpenTag
== aOther
.mAfterOpenTag
;
206 bool IsBeforeOpenTag() const {
207 return IsSet() && Container()->IsElement() && !Ref() && !mAfterOpenTag
;
209 bool IsImmediatelyAfterOpenTag() const {
210 return IsSet() && Container()->IsElement() && !Ref() && mAfterOpenTag
;
214 // NodePositionBefore isn't good name if Container() isn't an element node nor
215 // Offset() is not 0, though, when Container() is an element node and mOffset
216 // is 0, this is treated as before the open tag of Container().
217 struct NodePositionBefore final
: public NodePosition
{
218 NodePositionBefore(nsINode
* aContainer
, uint32_t aOffset
)
219 : NodePosition(aContainer
, aOffset
) {
220 mAfterOpenTag
= false;
223 NodePositionBefore(nsINode
* aContainer
, nsIContent
* aRef
)
224 : NodePosition(aContainer
, aRef
) {
225 mAfterOpenTag
= false;
229 // Get the flatten text length in the range.
230 // @param aStartPosition Start node and offset in the node of the range.
231 // @param aEndPosition End node and offset in the node of the range.
232 // @param aRootElement The root element of the editor or document.
233 // aRootElement won't cause any text including
235 // @param aLength The result of the flatten text length of the
237 // @param aLineBreakType Whether this computes flatten text length with
238 // native line breakers on the platform or
239 // with XP line breaker (\n).
240 // @param aIsRemovingNode Should be true only when this is called from
241 // nsIMutationObserver::ContentRemoved().
242 // When this is true, aStartPosition.mNode should
243 // be the root node of removing nodes and mOffset
244 // should be 0 and aEndPosition.mNode should be
245 // same as aStartPosition.mNode and mOffset should
246 // be number of the children of mNode.
247 static nsresult
GetFlatTextLengthInRange(const NodePosition
& aStartPosition
,
248 const NodePosition
& aEndPosition
,
249 const Element
* aRootElement
,
251 LineBreakType aLineBreakType
,
252 bool aIsRemovingNode
= false);
253 // Computes the native text length between aStartOffset and aEndOffset of
255 static uint32_t GetNativeTextLength(const dom::Text
& aTextNode
,
256 uint32_t aStartOffset
,
257 uint32_t aEndOffset
);
258 // Get the native text length of aTextNode.
259 static uint32_t GetNativeTextLength(const dom::Text
& aTextNode
,
260 uint32_t aMaxLength
= UINT32_MAX
);
262 static uint32_t GetNativeTextLength(const nsAString
& aText
);
265 // Get the text length of aTextNode.
266 static uint32_t GetTextLength(const dom::Text
& aTextNode
,
267 LineBreakType aLineBreakType
,
268 uint32_t aMaxLength
= UINT32_MAX
);
269 // Get the text length of a given range of a content node in
270 // the given line break type.
271 static uint32_t GetTextLengthInRange(const dom::Text
& aTextNode
,
272 uint32_t aXPStartOffset
,
273 uint32_t aXPEndOffset
,
274 LineBreakType aLineBreakType
);
275 // Get the contents in aElement (meaning all children of aElement) as plain
276 // text. E.g., specifying mRootElement gets whole text in it.
277 // Note that the result is not same as .textContent. The result is
278 // optimized for native IMEs. For example, <br> element and some block
279 // elements causes "\n" (or "\r\n"), see also ShouldBreakLineBefore().
280 nsresult
GenerateFlatTextContent(const Element
* aElement
, nsString
& aString
,
281 LineBreakType aLineBreakType
);
282 // Get the contents of aRange as plain text.
283 nsresult
GenerateFlatTextContent(const RawRange
& aRawRange
, nsString
& aString
,
284 LineBreakType aLineBreakType
);
285 // Get offset of start of aRange. Note that the result includes the length
286 // of line breaker caused by the start of aContent because aRange never
287 // includes the line breaker caused by its start node.
288 nsresult
GetStartOffset(const RawRange
& aRawRange
, uint32_t* aOffset
,
289 LineBreakType aLineBreakType
);
290 // Check if we should insert a line break before aContent.
291 // This should return false only when aContent is an html element which
292 // is typically used in a paragraph like <em>.
293 static bool ShouldBreakLineBefore(const nsIContent
& aContent
,
294 const Element
* aRootElement
);
295 // Get the line breaker length.
296 static inline uint32_t GetBRLength(LineBreakType aLineBreakType
);
297 static LineBreakType
GetLineBreakType(WidgetQueryContentEvent
* aEvent
);
298 static LineBreakType
GetLineBreakType(WidgetSelectionEvent
* aEvent
);
299 static LineBreakType
GetLineBreakType(bool aUseNativeLineBreak
);
300 // Returns focused content (including its descendant documents).
301 nsIContent
* GetFocusedContent();
302 // QueryContentRect() sets the rect of aContent's frame(s) to aEvent.
303 nsresult
QueryContentRect(nsIContent
* aContent
,
304 WidgetQueryContentEvent
* aEvent
);
305 // Initialize aRawRange from the offset of FlatText and the text length.
306 // If aExpandToClusterBoundaries is true, the start offset and the end one are
307 // expanded to nearest cluster boundaries.
308 nsresult
SetRawRangeFromFlatTextOffset(RawRange
* aRawRange
, uint32_t aOffset
,
310 LineBreakType aLineBreakType
,
311 bool aExpandToClusterBoundaries
,
312 uint32_t* aNewOffset
= nullptr,
313 dom::Text
** aLastTextNode
= nullptr);
314 // If the aCollapsedRawRange isn't in text node but next to a text node,
315 // this method modifies it in the text node. Otherwise, not modified.
316 nsresult
AdjustCollapsedRangeMaybeIntoTextNode(RawRange
& aCollapsedRawRange
);
317 // Convert the frame relative offset to be relative to the root frame of the
318 // root presContext (but still measured in appUnits of aFrame's presContext).
319 nsresult
ConvertToRootRelativeOffset(nsIFrame
* aFrame
, nsRect
& aRect
);
320 // Expand aXPOffset to the nearest offset in cluster boundary. aForward is
321 // true, it is expanded to forward.
322 // FYI: Due to `nsFrameSelection::GetFrameForNodeOffset()`, this cannot
323 // take `const dom::Text&`.
324 nsresult
ExpandToClusterBoundary(dom::Text
& aTextNode
, bool aForward
,
325 uint32_t* aXPOffset
) const;
327 using FontRangeArray
= nsTArray
<mozilla::FontRange
>;
328 static void AppendFontRanges(FontRangeArray
& aFontRanges
,
329 const dom::Text
& aTextNode
, uint32_t aBaseOffset
,
330 uint32_t aXPStartOffset
, uint32_t aXPEndOffset
,
331 LineBreakType aLineBreakType
);
332 nsresult
GenerateFlatFontRanges(const RawRange
& aRawRange
,
333 FontRangeArray
& aFontRanges
,
335 LineBreakType aLineBreakType
);
336 nsresult
QueryTextRectByRange(const RawRange
& aRawRange
,
337 LayoutDeviceIntRect
& aRect
,
338 WritingMode
& aWritingMode
);
340 struct MOZ_STACK_CLASS FrameAndNodeOffset final
{
341 // mFrame is safe since this can live in only stack class and
342 // ContentEventHandler doesn't modify layout after
343 // ContentEventHandler::Init() flushes pending layout. In other words,
344 // this struct shouldn't be used before calling
345 // ContentEventHandler::Init().
347 // offset in the node of mFrame
348 int32_t mOffsetInNode
;
350 FrameAndNodeOffset() : mFrame(nullptr), mOffsetInNode(-1) {}
352 FrameAndNodeOffset(nsIFrame
* aFrame
, int32_t aStartOffsetInNode
)
353 : mFrame(aFrame
), mOffsetInNode(aStartOffsetInNode
) {}
355 nsIFrame
* operator->() { return mFrame
; }
356 const nsIFrame
* operator->() const { return mFrame
; }
357 operator nsIFrame
*() { return mFrame
; }
358 operator const nsIFrame
*() const { return mFrame
; }
359 bool IsValid() const { return mFrame
&& mOffsetInNode
>= 0; }
361 // Get first frame after the start of the given range for computing text rect.
362 // This returns invalid FrameAndNodeOffset if there is no content which
363 // should affect to computing text rect in the range. mOffsetInNode is start
364 // offset in the frame.
365 FrameAndNodeOffset
GetFirstFrameInRangeForTextRect(const RawRange
& aRawRange
);
367 // Get last frame before the end of the given range for computing text rect.
368 // This returns invalid FrameAndNodeOffset if there is no content which
369 // should affect to computing text rect in the range. mOffsetInNode is end
370 // offset in the frame.
371 FrameAndNodeOffset
GetLastFrameInRangeForTextRect(const RawRange
& aRawRange
);
373 struct MOZ_STACK_CLASS FrameRelativeRect final
{
374 // mRect is relative to the mBaseFrame's position.
376 nsIFrame
* mBaseFrame
;
378 FrameRelativeRect() : mBaseFrame(nullptr) {}
380 explicit FrameRelativeRect(nsIFrame
* aBaseFrame
) : mBaseFrame(aBaseFrame
) {}
382 FrameRelativeRect(const nsRect
& aRect
, nsIFrame
* aBaseFrame
)
383 : mRect(aRect
), mBaseFrame(aBaseFrame
) {}
385 bool IsValid() const { return mBaseFrame
!= nullptr; }
387 // Returns an nsRect relative to aBaseFrame instead of mBaseFrame.
388 nsRect
RectRelativeTo(nsIFrame
* aBaseFrame
) const;
391 // Returns a rect for line breaker before the node of aFrame (If aFrame is
392 // a <br> frame or a block level frame, it causes a line break at its
393 // element's open tag, see also ShouldBreakLineBefore()). Note that this
394 // doesn't check if aFrame should cause line break in non-debug build.
395 FrameRelativeRect
GetLineBreakerRectBefore(nsIFrame
* aFrame
);
397 // Returns a line breaker rect after aTextNode as there is a line breaker
398 // immediately after aTextNode. This is useful when following block
399 // element causes a line break before it and it needs to compute the line
400 // breaker's rect. For example, if there is |<p>abc</p><p>def</p>|, the
401 // rect of 2nd <p>'s line breaker should be at right of "c" in the first
402 // <p>, not the start of 2nd <p>. The result is relative to the last text
403 // frame which represents the last character of aTextNode.
404 FrameRelativeRect
GuessLineBreakerRectAfter(const dom::Text
& aTextNode
);
406 // Returns a guessed first rect. I.e., it may be different from actual
407 // caret when selection is collapsed at start of aFrame. For example, this
408 // guess the caret rect only with the content box of aFrame and its font
410 // +-aFrame----------------- (border box)
412 // | +--------------------- (content box)
414 // ^ guessed caret rect
415 // However, actual caret is computed with more information like line-height,
416 // child frames of aFrame etc. But this does not emulate actual caret
417 // behavior exactly for simpler and faster code because it's difficult and
418 // we're not sure it's worthwhile to do it with complicated implementation.
419 FrameRelativeRect
GuessFirstCaretRectIn(nsIFrame
* aFrame
);
421 // Make aRect non-empty. If width and/or height is 0, these methods set them
422 // to 1. Note that it doesn't set nsRect's width nor height to one device
423 // pixel because using nsRect::ToOutsidePixels() makes actual width or height
424 // to 2 pixels because x and y may not be aligned to device pixels.
425 void EnsureNonEmptyRect(nsRect
& aRect
) const;
426 void EnsureNonEmptyRect(LayoutDeviceIntRect
& aRect
) const;
429 * Compute caret rect before or after a character rect.
431 static LayoutDeviceIntRect
GetCaretRectBefore(
432 const LayoutDeviceIntRect
& aCharRect
, const WritingMode
& aWritingMode
);
433 static LayoutDeviceIntRect
GetCaretRectAfter(
434 const LayoutDeviceIntRect
& aCharRect
, const WritingMode
& aWritingMode
);
435 static nsRect
GetCaretRectBefore(const nsRect
& aCharRect
,
436 const WritingMode
& aWritingMode
);
437 static nsRect
GetCaretRectAfter(nsPresContext
& aPresContext
,
438 const nsRect
& aCharRect
,
439 const WritingMode
& aWritingMode
);
442 } // namespace mozilla
444 #endif // mozilla_ContentEventHandler_h_