Coverage Report

Created: 2018-09-25 14:53

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