Coverage Report

Created: 2018-09-25 14:53

/work/obj-fuzz/dist/include/mozilla/ContentCache.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: sw=2 ts=8 et :
3
 */
4
/* This Source Code Form is subject to the terms of the Mozilla Public
5
 * License, v. 2.0. If a copy of the MPL was not distributed with this
6
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7
8
#ifndef mozilla_ContentCache_h
9
#define mozilla_ContentCache_h
10
11
#include <stdint.h>
12
13
#include "mozilla/Assertions.h"
14
#include "mozilla/CheckedInt.h"
15
#include "mozilla/EventForwards.h"
16
#include "mozilla/WritingModes.h"
17
#include "nsIWidget.h"
18
#include "nsString.h"
19
#include "nsTArray.h"
20
#include "Units.h"
21
22
namespace mozilla {
23
24
class ContentCacheInParent;
25
26
namespace dom {
27
class TabParent;
28
} // namespace dom
29
30
/**
31
 * ContentCache stores various information of the child content.
32
 * This class has members which are necessary both in parent process and
33
 * content process.
34
 */
35
36
class ContentCache
37
{
38
public:
39
  typedef InfallibleTArray<LayoutDeviceIntRect> RectArray;
40
  typedef widget::IMENotification IMENotification;
41
42
  ContentCache();
43
44
protected:
45
  // Whole text in the target
46
  nsString mText;
47
48
  // Start offset of the composition string.
49
  uint32_t mCompositionStart;
50
51
  enum
52
  {
53
    ePrevCharRect = 1,
54
    eNextCharRect = 0
55
  };
56
57
  struct Selection final
58
  {
59
    // Following values are offset in "flat text".
60
    uint32_t mAnchor;
61
    uint32_t mFocus;
62
63
    WritingMode mWritingMode;
64
65
    // Character rects at previous and next character of mAnchor and mFocus.
66
    // The reason why ContentCache needs to store each previous character of
67
    // them is IME may query character rect of the last character of a line
68
    // when caret is at the end of the line.
69
    // Note that use ePrevCharRect and eNextCharRect for accessing each item.
70
    LayoutDeviceIntRect mAnchorCharRects[2];
71
    LayoutDeviceIntRect mFocusCharRects[2];
72
73
    // Whole rect of selected text. This is empty if the selection is collapsed.
74
    LayoutDeviceIntRect mRect;
75
76
    Selection()
77
      : mAnchor(UINT32_MAX)
78
      , mFocus(UINT32_MAX)
79
0
    {
80
0
    }
81
82
    void Clear()
83
0
    {
84
0
      mAnchor = mFocus = UINT32_MAX;
85
0
      mWritingMode = WritingMode();
86
0
      ClearAnchorCharRects();
87
0
      ClearFocusCharRects();
88
0
      mRect.SetEmpty();
89
0
    }
90
91
    void ClearAnchorCharRects()
92
0
    {
93
0
      for (size_t i = 0; i < ArrayLength(mAnchorCharRects); i++) {
94
0
        mAnchorCharRects[i].SetEmpty();
95
0
      }
96
0
    }
97
    void ClearFocusCharRects()
98
0
    {
99
0
      for (size_t i = 0; i < ArrayLength(mFocusCharRects); i++) {
100
0
        mFocusCharRects[i].SetEmpty();
101
0
      }
102
0
    }
103
104
    bool IsValid() const
105
0
    {
106
0
      return mAnchor != UINT32_MAX && mFocus != UINT32_MAX;
107
0
    }
108
    bool Collapsed() const
109
0
    {
110
0
      NS_ASSERTION(IsValid(),
111
0
                   "The caller should check if the selection is valid");
112
0
      return mFocus == mAnchor;
113
0
    }
114
    bool Reversed() const
115
0
    {
116
0
      NS_ASSERTION(IsValid(),
117
0
                   "The caller should check if the selection is valid");
118
0
      return mFocus < mAnchor;
119
0
    }
120
    uint32_t StartOffset() const
121
0
    {
122
0
      NS_ASSERTION(IsValid(),
123
0
                   "The caller should check if the selection is valid");
124
0
      return Reversed() ? mFocus : mAnchor;
125
0
    }
126
    uint32_t EndOffset() const
127
0
    {
128
0
      NS_ASSERTION(IsValid(),
129
0
                   "The caller should check if the selection is valid");
130
0
      return Reversed() ? mAnchor : mFocus;
131
0
    }
132
    uint32_t Length() const
133
0
    {
134
0
      NS_ASSERTION(IsValid(),
135
0
                   "The caller should check if the selection is valid");
136
0
      return Reversed() ? mAnchor - mFocus : mFocus - mAnchor;
137
0
    }
138
    LayoutDeviceIntRect StartCharRect() const
139
0
    {
140
0
      NS_ASSERTION(IsValid(),
141
0
                   "The caller should check if the selection is valid");
142
0
      return Reversed() ? mFocusCharRects[eNextCharRect] :
143
0
                          mAnchorCharRects[eNextCharRect];
144
0
    }
145
    LayoutDeviceIntRect EndCharRect() const
146
0
    {
147
0
      NS_ASSERTION(IsValid(),
148
0
                   "The caller should check if the selection is valid");
149
0
      return Reversed() ? mAnchorCharRects[eNextCharRect] :
150
0
                          mFocusCharRects[eNextCharRect];
151
0
    }
152
  } mSelection;
153
154
  bool IsSelectionValid() const
155
0
  {
156
0
    return mSelection.IsValid() && mSelection.EndOffset() <= mText.Length();
157
0
  }
158
159
  // Stores first char rect because Yosemite's Japanese IME sometimes tries
160
  // to query it.  If there is no text, this is caret rect.
161
  LayoutDeviceIntRect mFirstCharRect;
162
163
  struct Caret final
164
  {
165
    uint32_t mOffset;
166
    LayoutDeviceIntRect mRect;
167
168
    Caret()
169
      : mOffset(UINT32_MAX)
170
0
    {
171
0
    }
172
173
    void Clear()
174
0
    {
175
0
      mOffset = UINT32_MAX;
176
0
      mRect.SetEmpty();
177
0
    }
178
179
0
    bool IsValid() const { return mOffset != UINT32_MAX; }
180
181
    uint32_t Offset() const
182
0
    {
183
0
      NS_ASSERTION(IsValid(),
184
0
                   "The caller should check if the caret is valid");
185
0
      return mOffset;
186
0
    }
187
  } mCaret;
188
189
  struct TextRectArray final
190
  {
191
    uint32_t mStart;
192
    RectArray mRects;
193
194
    TextRectArray()
195
      : mStart(UINT32_MAX)
196
0
    {
197
0
    }
198
199
    void Clear()
200
0
    {
201
0
      mStart = UINT32_MAX;
202
0
      mRects.Clear();
203
0
    }
204
205
    bool IsValid() const
206
0
    {
207
0
      if (mStart == UINT32_MAX) {
208
0
        return false;
209
0
      }
210
0
      CheckedInt<uint32_t> endOffset =
211
0
        CheckedInt<uint32_t>(mStart) + mRects.Length();
212
0
      return endOffset.isValid();
213
0
    }
214
    bool HasRects() const
215
0
    {
216
0
      return IsValid() && !mRects.IsEmpty();
217
0
    }
218
    uint32_t StartOffset() const
219
0
    {
220
0
      NS_ASSERTION(IsValid(),
221
0
                   "The caller should check if the caret is valid");
222
0
      return mStart;
223
0
    }
224
    uint32_t EndOffset() const
225
0
    {
226
0
      NS_ASSERTION(IsValid(),
227
0
                   "The caller should check if the caret is valid");
228
0
      if (!IsValid()) {
229
0
        return UINT32_MAX;
230
0
      }
231
0
      return mStart + mRects.Length();
232
0
    }
233
    bool InRange(uint32_t aOffset) const
234
0
    {
235
0
      return IsValid() &&
236
0
             StartOffset() <= aOffset && aOffset < EndOffset();
237
0
    }
238
    bool InRange(uint32_t aOffset, uint32_t aLength) const
239
0
    {
240
0
      CheckedInt<uint32_t> endOffset =
241
0
        CheckedInt<uint32_t>(aOffset) + aLength;
242
0
      if (NS_WARN_IF(!endOffset.isValid())) {
243
0
        return false;
244
0
      }
245
0
      return InRange(aOffset) && aOffset + aLength <= EndOffset();
246
0
    }
247
    bool IsOverlappingWith(uint32_t aOffset, uint32_t aLength) const
248
0
    {
249
0
      if (!HasRects() || aOffset == UINT32_MAX || !aLength) {
250
0
        return false;
251
0
      }
252
0
      CheckedInt<uint32_t> endOffset =
253
0
        CheckedInt<uint32_t>(aOffset) + aLength;
254
0
      if (NS_WARN_IF(!endOffset.isValid())) {
255
0
        return false;
256
0
      }
257
0
      return aOffset < EndOffset() && endOffset.value() > mStart;
258
0
    }
259
    LayoutDeviceIntRect GetRect(uint32_t aOffset) const;
260
    LayoutDeviceIntRect GetUnionRect(uint32_t aOffset, uint32_t aLength) const;
261
    LayoutDeviceIntRect GetUnionRectAsFarAsPossible(
262
                          uint32_t aOffset, uint32_t aLength,
263
                          bool aRoundToExistingOffset) const;
264
  } mTextRectArray;
265
266
  LayoutDeviceIntRect mEditorRect;
267
268
  friend class ContentCacheInParent;
269
  friend struct IPC::ParamTraits<ContentCache>;
270
};
271
272
class ContentCacheInChild final : public ContentCache
273
{
274
public:
275
  ContentCacheInChild();
276
277
  /**
278
   * When IME loses focus, this should be called and making this forget the
279
   * content for reducing footprint.
280
   */
281
  void Clear();
282
283
  /**
284
   * Cache*() retrieves the latest content information and store them.
285
   * Be aware, CacheSelection() calls CacheTextRects(), and also CacheText()
286
   * calls CacheSelection().  So, related data is also retrieved automatically.
287
   */
288
  bool CacheEditorRect(nsIWidget* aWidget,
289
                       const IMENotification* aNotification = nullptr);
290
  bool CacheSelection(nsIWidget* aWidget,
291
                      const IMENotification* aNotification = nullptr);
292
  bool CacheText(nsIWidget* aWidget,
293
                 const IMENotification* aNotification = nullptr);
294
295
  bool CacheAll(nsIWidget* aWidget,
296
                const IMENotification* aNotification = nullptr);
297
298
  /**
299
   * SetSelection() modifies selection with specified raw data. And also this
300
   * tries to retrieve text rects too.
301
   */
302
  void SetSelection(nsIWidget* aWidget,
303
                    uint32_t aStartOffset,
304
                    uint32_t aLength,
305
                    bool aReversed,
306
                    const WritingMode& aWritingMode);
307
308
private:
309
  bool QueryCharRect(nsIWidget* aWidget,
310
                     uint32_t aOffset,
311
                     LayoutDeviceIntRect& aCharRect) const;
312
  bool QueryCharRectArray(nsIWidget* aWidget,
313
                          uint32_t aOffset,
314
                          uint32_t aLength,
315
                          RectArray& aCharRectArray) const;
316
  bool CacheCaret(nsIWidget* aWidget,
317
                  const IMENotification* aNotification = nullptr);
318
  bool CacheTextRects(nsIWidget* aWidget,
319
                      const IMENotification* aNotification = nullptr);
320
};
321
322
class ContentCacheInParent final : public ContentCache
323
{
324
public:
325
  explicit ContentCacheInParent(dom::TabParent& aTabParent);
326
327
  /**
328
   * AssignContent() is called when TabParent receives ContentCache from
329
   * the content process.  This doesn't copy composition information because
330
   * it's managed by TabParent itself.
331
   */
332
  void AssignContent(const ContentCache& aOther,
333
                     nsIWidget* aWidget,
334
                     const IMENotification* aNotification = nullptr);
335
336
  /**
337
   * HandleQueryContentEvent() sets content data to aEvent.mReply.
338
   *
339
   * For eQuerySelectedText, fail if the cache doesn't contain the whole
340
   *  selected range. (This shouldn't happen because PuppetWidget should have
341
   *  already sent the whole selection.)
342
   *
343
   * For eQueryTextContent, fail only if the cache doesn't overlap with
344
   *  the queried range. Note the difference from above. We use
345
   *  this behavior because a normal eQueryTextContent event is allowed to
346
   *  have out-of-bounds offsets, so that widget can request content without
347
   *  knowing the exact length of text. It's up to widget to handle cases when
348
   *  the returned offset/length are different from the queried offset/length.
349
   *
350
   * For eQueryTextRect, fail if cached offset/length aren't equals to input.
351
   *   Cocoa widget always queries selected offset, so it works on it.
352
   *
353
   * For eQueryCaretRect, fail if cached offset isn't equals to input
354
   *
355
   * For eQueryEditorRect, always success
356
   */
357
  bool HandleQueryContentEvent(WidgetQueryContentEvent& aEvent,
358
                               nsIWidget* aWidget) const;
359
360
  /**
361
   * OnCompositionEvent() should be called before sending composition string.
362
   * This returns true if the event should be sent.  Otherwise, false.
363
   */
364
  bool OnCompositionEvent(const WidgetCompositionEvent& aCompositionEvent);
365
366
  /**
367
   * OnSelectionEvent() should be called before sending selection event.
368
   */
369
  void OnSelectionEvent(const WidgetSelectionEvent& aSelectionEvent);
370
371
  /**
372
   * OnEventNeedingAckHandled() should be called after the child process
373
   * handles a sent event which needs acknowledging.
374
   *
375
   * WARNING: This may send notifications to IME.  That might cause destroying
376
   *          TabParent or aWidget.  Therefore, the caller must not destroy
377
   *          this instance during a call of this method.
378
   */
379
  void OnEventNeedingAckHandled(nsIWidget* aWidget, EventMessage aMessage);
380
381
  /**
382
   * RequestIMEToCommitComposition() requests aWidget to commit or cancel
383
   * composition.  If it's handled synchronously, this returns true.
384
   *
385
   * @param aWidget     The widget to be requested to commit or cancel
386
   *                    the composition.
387
   * @param aCancel     When the caller tries to cancel the composition, true.
388
   *                    Otherwise, i.e., tries to commit the composition, false.
389
   * @param aCommittedString    The committed string (i.e., the last data of
390
   *                            dispatched composition events during requesting
391
   *                            IME to commit composition.
392
   * @return            Whether the composition is actually committed
393
   *                    synchronously.
394
   */
395
  bool RequestIMEToCommitComposition(nsIWidget* aWidget,
396
                                     bool aCancel,
397
                                     nsAString& aCommittedString);
398
399
  /**
400
   * MaybeNotifyIME() may notify IME of the notification.  If child process
401
   * hasn't been handled all sending events yet, this stores the notification
402
   * and flush it later.
403
   */
404
  void MaybeNotifyIME(nsIWidget* aWidget,
405
                      const IMENotification& aNotification);
406
407
private:
408
  IMENotification mPendingSelectionChange;
409
  IMENotification mPendingTextChange;
410
  IMENotification mPendingLayoutChange;
411
  IMENotification mPendingCompositionUpdate;
412
413
#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
414
  // Log of event messages to be output to crash report.
415
  nsTArray<EventMessage> mDispatchedEventMessages;
416
  nsTArray<EventMessage> mReceivedEventMessages;
417
  // Log of RequestIMEToCommitComposition() in the last 2 compositions.
418
  enum class RequestIMEToCommitCompositionResult : uint8_t
419
  {
420
    eToOldCompositionReceived,
421
    eToCommittedCompositionReceived,
422
    eReceivedAfterTabParentBlur,
423
    eReceivedButNoTextComposition,
424
    eHandledAsynchronously,
425
    eHandledSynchronously,
426
  };
427
  const char* ToReadableText(RequestIMEToCommitCompositionResult aResult) const
428
  {
429
    switch (aResult) {
430
      case RequestIMEToCommitCompositionResult::eToOldCompositionReceived:
431
        return "Commit request is not handled because it's for "
432
               "older composition";
433
      case RequestIMEToCommitCompositionResult::eToCommittedCompositionReceived:
434
        return "Commit request is not handled because TabParent has already "
435
               "sent commit event for the composition";
436
      case RequestIMEToCommitCompositionResult::eReceivedAfterTabParentBlur:
437
        return "Commit request is handled with stored composition string "
438
               "because TabParent has already lost focus";
439
      case RequestIMEToCommitCompositionResult::eReceivedButNoTextComposition:
440
        return "Commit request is not handled because there is no "
441
               "TextCompsition instance";
442
      case RequestIMEToCommitCompositionResult::eHandledAsynchronously:
443
        return "Commit request is handled but IME doesn't commit current "
444
               "composition synchronously";
445
      case RequestIMEToCommitCompositionResult::eHandledSynchronously:
446
        return "Commit reqeust is handled synchronously";
447
      default:
448
        return "Unknown reason";
449
    }
450
  }
451
  nsTArray<RequestIMEToCommitCompositionResult>
452
    mRequestIMEToCommitCompositionResults;
453
#endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
454
455
  // mTabParent is owner of the instance.
456
  dom::TabParent& MOZ_NON_OWNING_REF mTabParent;
457
  // mCompositionString is composition string which were sent to the remote
458
  // process but not yet committed in the remote process.
459
  nsString mCompositionString;
460
  // This is not nullptr only while the instance is requesting IME to
461
  // composition.  Then, data value of dispatched composition events should
462
  // be stored into the instance.
463
  nsAString* mCommitStringByRequest;
464
  // mPendingEventsNeedingAck is increased before sending a composition event or
465
  // a selection event and decreased after they are received in the child
466
  // process.
467
  uint32_t mPendingEventsNeedingAck;
468
  // mCompositionStartInChild stores current composition start offset in the
469
  // remote process.
470
  uint32_t mCompositionStartInChild;
471
  // mPendingCommitLength is commit string length of the first pending
472
  // composition.  This is used by relative offset query events when querying
473
  // new composition start offset.
474
  // Note that when mPendingCompositionCount is not 0, i.e., there are 2 or
475
  // more pending compositions, this cache won't be used because in such case,
476
  // anyway ContentCacheInParent cannot return proper character rect.
477
  uint32_t mPendingCommitLength;
478
  // mPendingCompositionCount is number of compositions which started in widget
479
  // but not yet handled in the child process.
480
  uint8_t mPendingCompositionCount;
481
  // mPendingCommitCount is number of eCompositionCommit(AsIs) events which
482
  // were sent to the child process but not yet handled in it.
483
  uint8_t mPendingCommitCount;
484
  // mWidgetHasComposition is true when the widget in this process thinks that
485
  // IME has composition.  So, this is set to true when eCompositionStart is
486
  // dispatched and set to false when eCompositionCommit(AsIs) is dispatched.
487
  bool mWidgetHasComposition;
488
  // mIsChildIgnoringCompositionEvents is set to true if the child process
489
  // requests commit composition whose commit has already been sent to it.
490
  // Then, set to false when the child process ignores the commit event.
491
  bool mIsChildIgnoringCompositionEvents;
492
493
  ContentCacheInParent() = delete;
494
495
  /**
496
   * When following methods' aRoundToExistingOffset is true, even if specified
497
   * offset or range is out of bounds, the result is computed with the existing
498
   * cache forcibly.
499
   */
500
  bool GetCaretRect(uint32_t aOffset,
501
                    bool aRoundToExistingOffset,
502
                    LayoutDeviceIntRect& aCaretRect) const;
503
  bool GetTextRect(uint32_t aOffset,
504
                   bool aRoundToExistingOffset,
505
                   LayoutDeviceIntRect& aTextRect) const;
506
  bool GetUnionTextRects(uint32_t aOffset,
507
                         uint32_t aLength,
508
                         bool aRoundToExistingOffset,
509
                         LayoutDeviceIntRect& aUnionTextRect) const;
510
511
  void FlushPendingNotifications(nsIWidget* aWidget);
512
513
#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
514
  /**
515
   * Remove unnecessary messages from mDispatchedEventMessages and
516
   * mReceivedEventMessages.
517
   */
518
  void RemoveUnnecessaryEventMessageLog();
519
520
  /**
521
   * Append event message log to aLog.
522
   */
523
  void AppendEventMessageLog(nsACString& aLog) const;
524
#endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
525
};
526
527
} // namespace mozilla
528
529
#endif // mozilla_ContentCache_h