/src/mozilla-central/dom/events/ContentEventHandler.h
Line | Count | Source (jump to first uncovered line) |
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/. */ |
6 | | |
7 | | #ifndef mozilla_ContentEventHandler_h_ |
8 | | #define mozilla_ContentEventHandler_h_ |
9 | | |
10 | | #include "mozilla/EventForwards.h" |
11 | | #include "mozilla/dom/Selection.h" |
12 | | #include "nsCOMPtr.h" |
13 | | #include "nsIFrame.h" |
14 | | #include "nsINode.h" |
15 | | #include "nsISelectionController.h" |
16 | | |
17 | | class nsPresContext; |
18 | | class nsRange; |
19 | | |
20 | | struct nsRect; |
21 | | |
22 | | namespace mozilla { |
23 | | |
24 | | enum LineBreakType |
25 | | { |
26 | | LINE_BREAK_TYPE_NATIVE, |
27 | | LINE_BREAK_TYPE_XP |
28 | | }; |
29 | | |
30 | | /* |
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. |
36 | | */ |
37 | | |
38 | | class MOZ_STACK_CLASS ContentEventHandler |
39 | | { |
40 | | private: |
41 | | /** |
42 | | * RawRange is a helper class of ContentEventHandler class. The caller is |
43 | | * responsible for making sure the start/end nodes are in document order. |
44 | | * This is enforced by assertions in DEBUG builds. |
45 | | */ |
46 | | class MOZ_STACK_CLASS RawRange final |
47 | | { |
48 | | public: |
49 | 0 | RawRange() {} |
50 | | |
51 | | void Clear() |
52 | 0 | { |
53 | 0 | mRoot = nullptr; |
54 | 0 | mStart = RangeBoundary(); |
55 | 0 | mEnd = RangeBoundary(); |
56 | 0 | } |
57 | | |
58 | | bool IsPositioned() const |
59 | 0 | { |
60 | 0 | return mStart.IsSet() && mEnd.IsSet(); |
61 | 0 | } |
62 | | bool Collapsed() const |
63 | 0 | { |
64 | 0 | return mStart == mEnd && IsPositioned(); |
65 | 0 | } |
66 | 0 | nsINode* GetStartContainer() const { return mStart.Container(); } |
67 | 0 | nsINode* GetEndContainer() const { return mEnd.Container(); } |
68 | 0 | uint32_t StartOffset() const { return mStart.Offset(); } |
69 | 0 | uint32_t EndOffset() const { return mEnd.Offset(); } |
70 | 0 | nsIContent* StartRef() const { return mStart.Ref(); } |
71 | 0 | nsIContent* EndRef() const { return mEnd.Ref(); } |
72 | | |
73 | 0 | const RangeBoundary& Start() const { return mStart; } |
74 | 0 | const RangeBoundary& End() const { return mEnd; } |
75 | | |
76 | | // XXX: Make these use RangeBoundaries... |
77 | | nsresult CollapseTo(const RawRangeBoundary& aBoundary) |
78 | 0 | { |
79 | 0 | return SetStartAndEnd(aBoundary, aBoundary); |
80 | 0 | } |
81 | | nsresult SetStart(const RawRangeBoundary& aStart); |
82 | | nsresult SetEnd(const RawRangeBoundary& aEnd); |
83 | | |
84 | | // NOTE: These helpers can hide performance problems, as they perform a |
85 | | // search to find aStartOffset in aStartContainer. |
86 | 0 | nsresult SetStart(nsINode* aStartContainer, uint32_t aStartOffset) { |
87 | 0 | return SetStart(RawRangeBoundary(aStartContainer, aStartOffset)); |
88 | 0 | } |
89 | 0 | nsresult SetEnd(nsINode* aEndContainer, uint32_t aEndOffset) { |
90 | 0 | return SetEnd(RawRangeBoundary(aEndContainer, aEndOffset)); |
91 | 0 | } |
92 | | |
93 | | nsresult SetEndAfter(nsINode* aEndContainer); |
94 | | void SetStartAndEnd(const nsRange* aRange); |
95 | | nsresult SetStartAndEnd(const RawRangeBoundary& aStart, |
96 | | const RawRangeBoundary& aEnd); |
97 | | |
98 | | nsresult SelectNodeContents(nsINode* aNodeToSelectContents); |
99 | | |
100 | | private: |
101 | | nsINode* IsValidBoundary(nsINode* aNode) const; |
102 | | inline void AssertStartIsBeforeOrEqualToEnd(); |
103 | | |
104 | | nsCOMPtr<nsINode> mRoot; |
105 | | |
106 | | RangeBoundary mStart; |
107 | | RangeBoundary mEnd; |
108 | | }; |
109 | | |
110 | | public: |
111 | | typedef dom::Selection Selection; |
112 | | |
113 | | explicit ContentEventHandler(nsPresContext* aPresContext); |
114 | | |
115 | | // Handle aEvent in the current process. |
116 | | nsresult HandleQueryContentEvent(WidgetQueryContentEvent* aEvent); |
117 | | |
118 | | // eQuerySelectedText event handler |
119 | | nsresult OnQuerySelectedText(WidgetQueryContentEvent* aEvent); |
120 | | // eQueryTextContent event handler |
121 | | nsresult OnQueryTextContent(WidgetQueryContentEvent* aEvent); |
122 | | // eQueryCaretRect event handler |
123 | | nsresult OnQueryCaretRect(WidgetQueryContentEvent* aEvent); |
124 | | // eQueryTextRect event handler |
125 | | nsresult OnQueryTextRect(WidgetQueryContentEvent* aEvent); |
126 | | // eQueryTextRectArray event handler |
127 | | nsresult OnQueryTextRectArray(WidgetQueryContentEvent* aEvent); |
128 | | // eQueryEditorRect event handler |
129 | | nsresult OnQueryEditorRect(WidgetQueryContentEvent* aEvent); |
130 | | // eQueryContentState event handler |
131 | | nsresult OnQueryContentState(WidgetQueryContentEvent* aEvent); |
132 | | // eQuerySelectionAsTransferable event handler |
133 | | nsresult OnQuerySelectionAsTransferable(WidgetQueryContentEvent* aEvent); |
134 | | // eQueryCharacterAtPoint event handler |
135 | | nsresult OnQueryCharacterAtPoint(WidgetQueryContentEvent* aEvent); |
136 | | // eQueryDOMWidgetHittest event handler |
137 | | nsresult OnQueryDOMWidgetHittest(WidgetQueryContentEvent* aEvent); |
138 | | |
139 | | // NS_SELECTION_* event |
140 | | nsresult OnSelectionEvent(WidgetSelectionEvent* aEvent); |
141 | | |
142 | | protected: |
143 | | nsCOMPtr<nsIDocument> mDocument; |
144 | | // mSelection is typically normal selection but if OnQuerySelectedText() |
145 | | // is called, i.e., handling eQuerySelectedText, it's the specified selection |
146 | | // by WidgetQueryContentEvent::mInput::mSelectionType. |
147 | | RefPtr<Selection> mSelection; |
148 | | // mFirstSelectedRawRange is initialized from the first range of mSelection, |
149 | | // if it exists. Otherwise, it is reset by Clear(). |
150 | | RawRange mFirstSelectedRawRange; |
151 | | nsCOMPtr<nsIContent> mRootContent; |
152 | | |
153 | | nsresult Init(WidgetQueryContentEvent* aEvent); |
154 | | nsresult Init(WidgetSelectionEvent* aEvent); |
155 | | |
156 | | nsresult InitBasic(); |
157 | | nsresult InitCommon(SelectionType aSelectionType = SelectionType::eNormal); |
158 | | /** |
159 | | * InitRootContent() computes the root content of current focused editor. |
160 | | * |
161 | | * @param aNormalSelection This must be a Selection instance whose type is |
162 | | * SelectionType::eNormal. |
163 | | */ |
164 | | nsresult InitRootContent(Selection* aNormalSelection); |
165 | | |
166 | | public: |
167 | | // FlatText means the text that is generated from DOM tree. The BR elements |
168 | | // are replaced to native linefeeds. Other elements are ignored. |
169 | | |
170 | | // NodePosition stores a pair of node and offset in the node. |
171 | | // When mNode is an element and mOffset is 0, the start position means after |
172 | | // the open tag of mNode. |
173 | | // This is useful to receive one or more sets of them instead of nsRange. |
174 | | // This type is intended to be used for short-lived operations, and is thus |
175 | | // marked MOZ_STACK_CLASS. |
176 | | struct MOZ_STACK_CLASS NodePosition : public RangeBoundary |
177 | | { |
178 | | // Only when mNode is an element node and mOffset is 0, mAfterOpenTag is |
179 | | // referred. |
180 | | bool mAfterOpenTag = true; |
181 | | |
182 | | NodePosition() |
183 | | : RangeBoundary() |
184 | 0 | { |
185 | 0 | } |
186 | | |
187 | | NodePosition(nsINode* aContainer, int32_t aOffset) |
188 | | : RangeBoundary(aContainer, aOffset) |
189 | 0 | { |
190 | 0 | } |
191 | | |
192 | | NodePosition(nsINode* aContainer, nsIContent* aRef) |
193 | | : RangeBoundary(aContainer, aRef) |
194 | 0 | { |
195 | 0 | } |
196 | | |
197 | | explicit NodePosition(const nsIFrame::ContentOffsets& aContentOffsets) |
198 | | : RangeBoundary(aContentOffsets.content, aContentOffsets.offset) |
199 | 0 | { |
200 | 0 | } |
201 | | |
202 | | public: |
203 | | bool operator==(const NodePosition& aOther) const |
204 | 0 | { |
205 | 0 | return RangeBoundary::operator==(aOther) && |
206 | 0 | mAfterOpenTag == aOther.mAfterOpenTag; |
207 | 0 | } |
208 | | |
209 | | bool IsBeforeOpenTag() const |
210 | 0 | { |
211 | 0 | return IsSet() && |
212 | 0 | Container()->IsElement() && |
213 | 0 | !Ref() && |
214 | 0 | !mAfterOpenTag; |
215 | 0 | } |
216 | | bool IsImmediatelyAfterOpenTag() const |
217 | 0 | { |
218 | 0 | return IsSet() && |
219 | 0 | Container()->IsElement() && |
220 | 0 | !Ref() && |
221 | 0 | mAfterOpenTag; |
222 | 0 | } |
223 | | }; |
224 | | |
225 | | // NodePositionBefore isn't good name if Container() isn't an element node nor |
226 | | // Offset() is not 0, though, when Container() is an element node and mOffset |
227 | | // is 0, this is treated as before the open tag of Container(). |
228 | | struct NodePositionBefore final : public NodePosition |
229 | | { |
230 | | NodePositionBefore(nsINode* aContainer, int32_t aOffset) |
231 | | : NodePosition(aContainer, aOffset) |
232 | 0 | { |
233 | 0 | mAfterOpenTag = false; |
234 | 0 | } |
235 | | |
236 | | NodePositionBefore(nsINode* aContainer, nsIContent* aRef) |
237 | | : NodePosition(aContainer, aRef) |
238 | 0 | { |
239 | 0 | mAfterOpenTag = false; |
240 | 0 | } |
241 | | }; |
242 | | |
243 | | // Get the flatten text length in the range. |
244 | | // @param aStartPosition Start node and offset in the node of the range. |
245 | | // @param aEndPosition End node and offset in the node of the range. |
246 | | // @param aRootContent The root content of the editor or document. |
247 | | // aRootContent won't cause any text including |
248 | | // line breaks. |
249 | | // @param aLength The result of the flatten text length of the |
250 | | // range. |
251 | | // @param aLineBreakType Whether this computes flatten text length with |
252 | | // native line breakers on the platform or |
253 | | // with XP line breaker (\n). |
254 | | // @param aIsRemovingNode Should be true only when this is called from |
255 | | // nsIMutationObserver::ContentRemoved(). |
256 | | // When this is true, aStartPosition.mNode should |
257 | | // be the root node of removing nodes and mOffset |
258 | | // should be 0 and aEndPosition.mNode should be |
259 | | // same as aStartPosition.mNode and mOffset should |
260 | | // be number of the children of mNode. |
261 | | static nsresult GetFlatTextLengthInRange(const NodePosition& aStartPosition, |
262 | | const NodePosition& aEndPosition, |
263 | | nsIContent* aRootContent, |
264 | | uint32_t* aLength, |
265 | | LineBreakType aLineBreakType, |
266 | | bool aIsRemovingNode = false); |
267 | | // Computes the native text length between aStartOffset and aEndOffset of |
268 | | // aContent. aContent must be a text node. |
269 | | static uint32_t GetNativeTextLength(nsIContent* aContent, |
270 | | uint32_t aStartOffset, |
271 | | uint32_t aEndOffset); |
272 | | // Get the native text length of aContent. aContent must be a text node. |
273 | | static uint32_t GetNativeTextLength(nsIContent* aContent, |
274 | | uint32_t aMaxLength = UINT32_MAX); |
275 | | // Get the native text length which is inserted before aContent. |
276 | | // aContent should be an element. |
277 | | static uint32_t GetNativeTextLengthBefore(nsIContent* aContent, |
278 | | nsINode* aRootNode); |
279 | | |
280 | | protected: |
281 | | // Get the text length of aContent. aContent must be a text node. |
282 | | static uint32_t GetTextLength(nsIContent* aContent, |
283 | | LineBreakType aLineBreakType, |
284 | | uint32_t aMaxLength = UINT32_MAX); |
285 | | // Get the text length of a given range of a content node in |
286 | | // the given line break type. |
287 | | static uint32_t GetTextLengthInRange(nsIContent* aContent, |
288 | | uint32_t aXPStartOffset, |
289 | | uint32_t aXPEndOffset, |
290 | | LineBreakType aLineBreakType); |
291 | | // Get the contents in aContent (meaning all children of aContent) as plain |
292 | | // text. E.g., specifying mRootContent gets whole text in it. |
293 | | // Note that the result is not same as .textContent. The result is |
294 | | // optimized for native IMEs. For example, <br> element and some block |
295 | | // elements causes "\n" (or "\r\n"), see also ShouldBreakLineBefore(). |
296 | | nsresult GenerateFlatTextContent(nsIContent* aContent, |
297 | | nsString& aString, |
298 | | LineBreakType aLineBreakType); |
299 | | // Get the contents of aRange as plain text. |
300 | | nsresult GenerateFlatTextContent(const RawRange& aRawRange, |
301 | | nsString& aString, |
302 | | LineBreakType aLineBreakType); |
303 | | // Get offset of start of aRange. Note that the result includes the length |
304 | | // of line breaker caused by the start of aContent because aRange never |
305 | | // includes the line breaker caused by its start node. |
306 | | nsresult GetStartOffset(const RawRange& aRawRange, |
307 | | uint32_t* aOffset, |
308 | | LineBreakType aLineBreakType); |
309 | | // Check if we should insert a line break before aContent. |
310 | | // This should return false only when aContent is an html element which |
311 | | // is typically used in a paragraph like <em>. |
312 | | static bool ShouldBreakLineBefore(nsIContent* aContent, |
313 | | nsINode* aRootNode); |
314 | | // Get the line breaker length. |
315 | | static inline uint32_t GetBRLength(LineBreakType aLineBreakType); |
316 | | static LineBreakType GetLineBreakType(WidgetQueryContentEvent* aEvent); |
317 | | static LineBreakType GetLineBreakType(WidgetSelectionEvent* aEvent); |
318 | | static LineBreakType GetLineBreakType(bool aUseNativeLineBreak); |
319 | | // Returns focused content (including its descendant documents). |
320 | | nsIContent* GetFocusedContent(); |
321 | | // Returns true if the content is a plugin host. |
322 | | bool IsPlugin(nsIContent* aContent); |
323 | | // QueryContentRect() sets the rect of aContent's frame(s) to aEvent. |
324 | | nsresult QueryContentRect(nsIContent* aContent, |
325 | | WidgetQueryContentEvent* aEvent); |
326 | | // Initialize aRawRange from the offset of FlatText and the text length. |
327 | | // If aExpandToClusterBoundaries is true, the start offset and the end one are |
328 | | // expanded to nearest cluster boundaries. |
329 | | nsresult SetRawRangeFromFlatTextOffset(RawRange* aRawRange, |
330 | | uint32_t aOffset, |
331 | | uint32_t aLength, |
332 | | LineBreakType aLineBreakType, |
333 | | bool aExpandToClusterBoundaries, |
334 | | uint32_t* aNewOffset = nullptr, |
335 | | nsIContent** aLastTextNode = nullptr); |
336 | | // If the aCollapsedRawRange isn't in text node but next to a text node, |
337 | | // this method modifies it in the text node. Otherwise, not modified. |
338 | | nsresult AdjustCollapsedRangeMaybeIntoTextNode(RawRange& aCollapsedRawRange); |
339 | | // Convert the frame relative offset to be relative to the root frame of the |
340 | | // root presContext (but still measured in appUnits of aFrame's presContext). |
341 | | nsresult ConvertToRootRelativeOffset(nsIFrame* aFrame, |
342 | | nsRect& aRect); |
343 | | // Expand aXPOffset to the nearest offset in cluster boundary. aForward is |
344 | | // true, it is expanded to forward. |
345 | | nsresult ExpandToClusterBoundary(nsIContent* aContent, bool aForward, |
346 | | uint32_t* aXPOffset); |
347 | | |
348 | | typedef nsTArray<mozilla::FontRange> FontRangeArray; |
349 | | static void AppendFontRanges(FontRangeArray& aFontRanges, |
350 | | nsIContent* aContent, |
351 | | uint32_t aBaseOffset, |
352 | | uint32_t aXPStartOffset, |
353 | | uint32_t aXPEndOffset, |
354 | | LineBreakType aLineBreakType); |
355 | | nsresult GenerateFlatFontRanges(const RawRange& aRawRange, |
356 | | FontRangeArray& aFontRanges, |
357 | | uint32_t& aLength, |
358 | | LineBreakType aLineBreakType); |
359 | | nsresult QueryTextRectByRange(const RawRange& aRawRange, |
360 | | LayoutDeviceIntRect& aRect, |
361 | | WritingMode& aWritingMode); |
362 | | |
363 | | struct MOZ_STACK_CLASS FrameAndNodeOffset final |
364 | | { |
365 | | // mFrame is safe since this can live in only stack class and |
366 | | // ContentEventHandler doesn't modify layout after |
367 | | // ContentEventHandler::Init() flushes pending layout. In other words, |
368 | | // this struct shouldn't be used before calling |
369 | | // ContentEventHandler::Init(). |
370 | | nsIFrame* mFrame; |
371 | | // offset in the node of mFrame |
372 | | int32_t mOffsetInNode; |
373 | | |
374 | | FrameAndNodeOffset() |
375 | | : mFrame(nullptr) |
376 | | , mOffsetInNode(-1) |
377 | 0 | { |
378 | 0 | } |
379 | | |
380 | | FrameAndNodeOffset(nsIFrame* aFrame, int32_t aStartOffsetInNode) |
381 | | : mFrame(aFrame) |
382 | | , mOffsetInNode(aStartOffsetInNode) |
383 | 0 | { |
384 | 0 | } |
385 | | |
386 | 0 | nsIFrame* operator->() { return mFrame; } |
387 | 0 | const nsIFrame* operator->() const { return mFrame; } |
388 | 0 | operator nsIFrame*() { return mFrame; } |
389 | 0 | operator const nsIFrame*() const { return mFrame; } |
390 | 0 | bool IsValid() const { return mFrame && mOffsetInNode >= 0; } |
391 | | }; |
392 | | // Get first frame after the start of the given range for computing text rect. |
393 | | // This returns invalid FrameAndNodeOffset if there is no content which |
394 | | // should affect to computing text rect in the range. mOffsetInNode is start |
395 | | // offset in the frame. |
396 | | FrameAndNodeOffset GetFirstFrameInRangeForTextRect(const RawRange& aRawRange); |
397 | | |
398 | | // Get last frame before the end of the given range for computing text rect. |
399 | | // This returns invalid FrameAndNodeOffset if there is no content which |
400 | | // should affect to computing text rect in the range. mOffsetInNode is end |
401 | | // offset in the frame. |
402 | | FrameAndNodeOffset GetLastFrameInRangeForTextRect(const RawRange& aRawRange); |
403 | | |
404 | | struct MOZ_STACK_CLASS FrameRelativeRect final |
405 | | { |
406 | | // mRect is relative to the mBaseFrame's position. |
407 | | nsRect mRect; |
408 | | nsIFrame* mBaseFrame; |
409 | | |
410 | | FrameRelativeRect() |
411 | | : mBaseFrame(nullptr) |
412 | 0 | { |
413 | 0 | } |
414 | | |
415 | | explicit FrameRelativeRect(nsIFrame* aBaseFrame) |
416 | | : mBaseFrame(aBaseFrame) |
417 | 0 | { |
418 | 0 | } |
419 | | |
420 | | FrameRelativeRect(const nsRect& aRect, nsIFrame* aBaseFrame) |
421 | | : mRect(aRect) |
422 | | , mBaseFrame(aBaseFrame) |
423 | 0 | { |
424 | 0 | } |
425 | | |
426 | 0 | bool IsValid() const { return mBaseFrame != nullptr; } |
427 | | |
428 | | // Returns an nsRect relative to aBaseFrame instead of mBaseFrame. |
429 | | nsRect RectRelativeTo(nsIFrame* aBaseFrame) const; |
430 | | }; |
431 | | |
432 | | // Returns a rect for line breaker before the node of aFrame (If aFrame is |
433 | | // a <br> frame or a block level frame, it causes a line break at its |
434 | | // element's open tag, see also ShouldBreakLineBefore()). Note that this |
435 | | // doesn't check if aFrame should cause line break in non-debug build. |
436 | | FrameRelativeRect GetLineBreakerRectBefore(nsIFrame* aFrame); |
437 | | |
438 | | // Returns a line breaker rect after aTextContent as there is a line breaker |
439 | | // immediately after aTextContent. This is useful when following block |
440 | | // element causes a line break before it and it needs to compute the line |
441 | | // breaker's rect. For example, if there is |<p>abc</p><p>def</p>|, the |
442 | | // rect of 2nd <p>'s line breaker should be at right of "c" in the first |
443 | | // <p>, not the start of 2nd <p>. The result is relative to the last text |
444 | | // frame which represents the last character of aTextContent. |
445 | | FrameRelativeRect GuessLineBreakerRectAfter(nsIContent* aTextContent); |
446 | | |
447 | | // Returns a guessed first rect. I.e., it may be different from actual |
448 | | // caret when selection is collapsed at start of aFrame. For example, this |
449 | | // guess the caret rect only with the content box of aFrame and its font |
450 | | // height like: |
451 | | // +-aFrame----------------- (border box) |
452 | | // | |
453 | | // | +--------------------- (content box) |
454 | | // | | I |
455 | | // ^ guessed caret rect |
456 | | // However, actual caret is computed with more information like line-height, |
457 | | // child frames of aFrame etc. But this does not emulate actual caret |
458 | | // behavior exactly for simpler and faster code because it's difficult and |
459 | | // we're not sure it's worthwhile to do it with complicated implementation. |
460 | | FrameRelativeRect GuessFirstCaretRectIn(nsIFrame* aFrame); |
461 | | |
462 | | // Make aRect non-empty. If width and/or height is 0, these methods set them |
463 | | // to 1. Note that it doesn't set nsRect's width nor height to one device |
464 | | // pixel because using nsRect::ToOutsidePixels() makes actual width or height |
465 | | // to 2 pixels because x and y may not be aligned to device pixels. |
466 | | void EnsureNonEmptyRect(nsRect& aRect) const; |
467 | | void EnsureNonEmptyRect(LayoutDeviceIntRect& aRect) const; |
468 | | }; |
469 | | |
470 | | } // namespace mozilla |
471 | | |
472 | | #endif // mozilla_ContentEventHandler_h_ |