Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/widget/ContentCache.cpp
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
#include "mozilla/ContentCache.h"
9
10
#include "mozilla/IMEStateManager.h"
11
#include "mozilla/IntegerPrintfMacros.h"
12
#include "mozilla/Logging.h"
13
#include "mozilla/Move.h"
14
#include "mozilla/RefPtr.h"
15
#include "mozilla/TextComposition.h"
16
#include "mozilla/TextEvents.h"
17
#include "mozilla/dom/TabParent.h"
18
#include "nsExceptionHandler.h"
19
#include "nsIWidget.h"
20
21
namespace mozilla {
22
23
using namespace dom;
24
using namespace widget;
25
26
static const char*
27
GetBoolName(bool aBool)
28
0
{
29
0
  return aBool ? "true" : "false";
30
0
}
31
32
static const char*
33
GetNotificationName(const IMENotification* aNotification)
34
0
{
35
0
  if (!aNotification) {
36
0
    return "Not notification";
37
0
  }
38
0
  return ToChar(aNotification->mMessage);
39
0
}
40
41
class GetRectText : public nsAutoCString
42
{
43
public:
44
  explicit GetRectText(const LayoutDeviceIntRect& aRect)
45
0
  {
46
0
    AssignLiteral("{ x=");
47
0
    AppendInt(aRect.X());
48
0
    AppendLiteral(", y=");
49
0
    AppendInt(aRect.Y());
50
0
    AppendLiteral(", width=");
51
0
    AppendInt(aRect.Width());
52
0
    AppendLiteral(", height=");
53
0
    AppendInt(aRect.Height());
54
0
    AppendLiteral(" }");
55
0
  }
56
0
  virtual ~GetRectText() {}
57
};
58
59
class GetWritingModeName : public nsAutoCString
60
{
61
public:
62
  explicit GetWritingModeName(const WritingMode& aWritingMode)
63
0
  {
64
0
    if (!aWritingMode.IsVertical()) {
65
0
      AssignLiteral("Horizontal");
66
0
      return;
67
0
    }
68
0
    if (aWritingMode.IsVerticalLR()) {
69
0
      AssignLiteral("Vertical (LTR)");
70
0
      return;
71
0
    }
72
0
    AssignLiteral("Vertical (RTL)");
73
0
  }
74
0
  virtual ~GetWritingModeName() {}
75
};
76
77
class GetEscapedUTF8String final : public NS_ConvertUTF16toUTF8
78
{
79
public:
80
  explicit GetEscapedUTF8String(const nsAString& aString)
81
    : NS_ConvertUTF16toUTF8(aString)
82
0
  {
83
0
    Escape();
84
0
  }
85
  explicit GetEscapedUTF8String(const char16ptr_t aString)
86
    : NS_ConvertUTF16toUTF8(aString)
87
0
  {
88
0
    Escape();
89
0
  }
90
  GetEscapedUTF8String(const char16ptr_t aString, uint32_t aLength)
91
    : NS_ConvertUTF16toUTF8(aString, aLength)
92
0
  {
93
0
    Escape();
94
0
  }
95
96
private:
97
  void Escape()
98
0
  {
99
0
    ReplaceSubstring("\r", "\\r");
100
0
    ReplaceSubstring("\n", "\\n");
101
0
    ReplaceSubstring("\t", "\\t");
102
0
  }
103
};
104
105
/*****************************************************************************
106
 * mozilla::ContentCache
107
 *****************************************************************************/
108
109
LazyLogModule sContentCacheLog("ContentCacheWidgets");
110
111
ContentCache::ContentCache()
112
  : mCompositionStart(UINT32_MAX)
113
0
{
114
0
}
115
116
/*****************************************************************************
117
 * mozilla::ContentCacheInChild
118
 *****************************************************************************/
119
120
ContentCacheInChild::ContentCacheInChild()
121
  : ContentCache()
122
0
{
123
0
}
124
125
void
126
ContentCacheInChild::Clear()
127
0
{
128
0
  MOZ_LOG(sContentCacheLog, LogLevel::Info,
129
0
    ("0x%p Clear()", this));
130
0
131
0
  mCompositionStart = UINT32_MAX;
132
0
  mText.Truncate();
133
0
  mSelection.Clear();
134
0
  mFirstCharRect.SetEmpty();
135
0
  mCaret.Clear();
136
0
  mTextRectArray.Clear();
137
0
  mEditorRect.SetEmpty();
138
0
}
139
140
bool
141
ContentCacheInChild::CacheAll(nsIWidget* aWidget,
142
                              const IMENotification* aNotification)
143
0
{
144
0
  MOZ_LOG(sContentCacheLog, LogLevel::Info,
145
0
    ("0x%p CacheAll(aWidget=0x%p, aNotification=%s)",
146
0
     this, aWidget, GetNotificationName(aNotification)));
147
0
148
0
  if (NS_WARN_IF(!CacheText(aWidget, aNotification)) ||
149
0
      NS_WARN_IF(!CacheEditorRect(aWidget, aNotification))) {
150
0
    return false;
151
0
  }
152
0
  return true;
153
0
}
154
155
bool
156
ContentCacheInChild::CacheSelection(nsIWidget* aWidget,
157
                                    const IMENotification* aNotification)
158
0
{
159
0
  MOZ_LOG(sContentCacheLog, LogLevel::Info,
160
0
    ("0x%p CacheSelection(aWidget=0x%p, aNotification=%s)",
161
0
     this, aWidget, GetNotificationName(aNotification)));
162
0
163
0
  mCaret.Clear();
164
0
  mSelection.Clear();
165
0
166
0
  nsEventStatus status = nsEventStatus_eIgnore;
167
0
  WidgetQueryContentEvent selection(true, eQuerySelectedText, aWidget);
168
0
  aWidget->DispatchEvent(&selection, status);
169
0
  if (NS_WARN_IF(!selection.mSucceeded)) {
170
0
    MOZ_LOG(sContentCacheLog, LogLevel::Error,
171
0
      ("0x%p CacheSelection(), FAILED, "
172
0
       "couldn't retrieve the selected text", this));
173
0
    return false;
174
0
  }
175
0
  if (selection.mReply.mReversed) {
176
0
    mSelection.mAnchor =
177
0
      selection.mReply.mOffset + selection.mReply.mString.Length();
178
0
    mSelection.mFocus = selection.mReply.mOffset;
179
0
  } else {
180
0
    mSelection.mAnchor = selection.mReply.mOffset;
181
0
    mSelection.mFocus =
182
0
      selection.mReply.mOffset + selection.mReply.mString.Length();
183
0
  }
184
0
  mSelection.mWritingMode = selection.GetWritingMode();
185
0
186
0
  return CacheCaret(aWidget, aNotification) &&
187
0
         CacheTextRects(aWidget, aNotification);
188
0
}
189
190
bool
191
ContentCacheInChild::CacheCaret(nsIWidget* aWidget,
192
                                const IMENotification* aNotification)
193
0
{
194
0
  MOZ_LOG(sContentCacheLog, LogLevel::Info,
195
0
    ("0x%p CacheCaret(aWidget=0x%p, aNotification=%s)",
196
0
     this, aWidget, GetNotificationName(aNotification)));
197
0
198
0
  mCaret.Clear();
199
0
200
0
  if (NS_WARN_IF(!mSelection.IsValid())) {
201
0
    return false;
202
0
  }
203
0
204
0
  // XXX Should be mSelection.mFocus?
205
0
  mCaret.mOffset = mSelection.StartOffset();
206
0
207
0
  nsEventStatus status = nsEventStatus_eIgnore;
208
0
  WidgetQueryContentEvent caretRect(true, eQueryCaretRect, aWidget);
209
0
  caretRect.InitForQueryCaretRect(mCaret.mOffset);
210
0
  aWidget->DispatchEvent(&caretRect, status);
211
0
  if (NS_WARN_IF(!caretRect.mSucceeded)) {
212
0
    MOZ_LOG(sContentCacheLog, LogLevel::Error,
213
0
      ("0x%p CacheCaret(), FAILED, "
214
0
       "couldn't retrieve the caret rect at offset=%u",
215
0
       this, mCaret.mOffset));
216
0
    mCaret.Clear();
217
0
    return false;
218
0
  }
219
0
  mCaret.mRect = caretRect.mReply.mRect;
220
0
  MOZ_LOG(sContentCacheLog, LogLevel::Info,
221
0
    ("0x%p CacheCaret(), Succeeded, "
222
0
     "mSelection={ mAnchor=%u, mFocus=%u, mWritingMode=%s }, "
223
0
     "mCaret={ mOffset=%u, mRect=%s }",
224
0
     this, mSelection.mAnchor, mSelection.mFocus,
225
0
     GetWritingModeName(mSelection.mWritingMode).get(), mCaret.mOffset,
226
0
     GetRectText(mCaret.mRect).get()));
227
0
  return true;
228
0
}
229
230
bool
231
ContentCacheInChild::CacheEditorRect(nsIWidget* aWidget,
232
                                     const IMENotification* aNotification)
233
0
{
234
0
  MOZ_LOG(sContentCacheLog, LogLevel::Info,
235
0
    ("0x%p CacheEditorRect(aWidget=0x%p, aNotification=%s)",
236
0
     this, aWidget, GetNotificationName(aNotification)));
237
0
238
0
  nsEventStatus status = nsEventStatus_eIgnore;
239
0
  WidgetQueryContentEvent editorRectEvent(true, eQueryEditorRect, aWidget);
240
0
  aWidget->DispatchEvent(&editorRectEvent, status);
241
0
  if (NS_WARN_IF(!editorRectEvent.mSucceeded)) {
242
0
    MOZ_LOG(sContentCacheLog, LogLevel::Error,
243
0
      ("0x%p CacheEditorRect(), FAILED, "
244
0
       "couldn't retrieve the editor rect", this));
245
0
    return false;
246
0
  }
247
0
  mEditorRect = editorRectEvent.mReply.mRect;
248
0
  MOZ_LOG(sContentCacheLog, LogLevel::Info,
249
0
    ("0x%p CacheEditorRect(), Succeeded, "
250
0
     "mEditorRect=%s", this, GetRectText(mEditorRect).get()));
251
0
  return true;
252
0
}
253
254
bool
255
ContentCacheInChild::CacheText(nsIWidget* aWidget,
256
                               const IMENotification* aNotification)
257
0
{
258
0
  MOZ_LOG(sContentCacheLog, LogLevel::Info,
259
0
    ("0x%p CacheText(aWidget=0x%p, aNotification=%s)",
260
0
     this, aWidget, GetNotificationName(aNotification)));
261
0
262
0
  nsEventStatus status = nsEventStatus_eIgnore;
263
0
  WidgetQueryContentEvent queryText(true, eQueryTextContent, aWidget);
264
0
  queryText.InitForQueryTextContent(0, UINT32_MAX);
265
0
  aWidget->DispatchEvent(&queryText, status);
266
0
  if (NS_WARN_IF(!queryText.mSucceeded)) {
267
0
    MOZ_LOG(sContentCacheLog, LogLevel::Error,
268
0
      ("0x%p CacheText(), FAILED, couldn't retrieve whole text", this));
269
0
    mText.Truncate();
270
0
    return false;
271
0
  }
272
0
  mText = queryText.mReply.mString;
273
0
  MOZ_LOG(sContentCacheLog, LogLevel::Info,
274
0
    ("0x%p CacheText(), Succeeded, mText.Length()=%u", this, mText.Length()));
275
0
276
0
  return CacheSelection(aWidget, aNotification);
277
0
}
278
279
bool
280
ContentCacheInChild::QueryCharRect(nsIWidget* aWidget,
281
                                   uint32_t aOffset,
282
                                   LayoutDeviceIntRect& aCharRect) const
283
0
{
284
0
  aCharRect.SetEmpty();
285
0
286
0
  nsEventStatus status = nsEventStatus_eIgnore;
287
0
  WidgetQueryContentEvent textRect(true, eQueryTextRect, aWidget);
288
0
  textRect.InitForQueryTextRect(aOffset, 1);
289
0
  aWidget->DispatchEvent(&textRect, status);
290
0
  if (NS_WARN_IF(!textRect.mSucceeded)) {
291
0
    return false;
292
0
  }
293
0
  aCharRect = textRect.mReply.mRect;
294
0
295
0
  // Guarantee the rect is not empty.
296
0
  if (NS_WARN_IF(!aCharRect.Height())) {
297
0
    aCharRect.SetHeight(1);
298
0
  }
299
0
  if (NS_WARN_IF(!aCharRect.Width())) {
300
0
    aCharRect.SetWidth(1);
301
0
  }
302
0
  return true;
303
0
}
304
305
bool
306
ContentCacheInChild::QueryCharRectArray(nsIWidget* aWidget,
307
                                        uint32_t aOffset,
308
                                        uint32_t aLength,
309
                                        RectArray& aCharRectArray) const
310
0
{
311
0
  nsEventStatus status = nsEventStatus_eIgnore;
312
0
  WidgetQueryContentEvent textRects(true, eQueryTextRectArray, aWidget);
313
0
  textRects.InitForQueryTextRectArray(aOffset, aLength);
314
0
  aWidget->DispatchEvent(&textRects, status);
315
0
  if (NS_WARN_IF(!textRects.mSucceeded)) {
316
0
    aCharRectArray.Clear();
317
0
    return false;
318
0
  }
319
0
  aCharRectArray = std::move(textRects.mReply.mRectArray);
320
0
  return true;
321
0
}
322
323
bool
324
ContentCacheInChild::CacheTextRects(nsIWidget* aWidget,
325
                                    const IMENotification* aNotification)
326
0
{
327
0
  MOZ_LOG(sContentCacheLog, LogLevel::Info,
328
0
    ("0x%p CacheTextRects(aWidget=0x%p, aNotification=%s), "
329
0
     "mCaret={ mOffset=%u, IsValid()=%s }",
330
0
     this, aWidget, GetNotificationName(aNotification), mCaret.mOffset,
331
0
     GetBoolName(mCaret.IsValid())));
332
0
333
0
  mCompositionStart = UINT32_MAX;
334
0
  mTextRectArray.Clear();
335
0
  mSelection.ClearAnchorCharRects();
336
0
  mSelection.ClearFocusCharRects();
337
0
  mSelection.mRect.SetEmpty();
338
0
  mFirstCharRect.SetEmpty();
339
0
340
0
  if (NS_WARN_IF(!mSelection.IsValid())) {
341
0
    return false;
342
0
  }
343
0
344
0
  // Retrieve text rects in composition string if there is.
345
0
  RefPtr<TextComposition> textComposition =
346
0
    IMEStateManager::GetTextCompositionFor(aWidget);
347
0
  if (textComposition) {
348
0
    // mCompositionStart may be updated by some composition event handlers.
349
0
    // So, let's update it with the latest information.
350
0
    mCompositionStart = textComposition->NativeOffsetOfStartComposition();
351
0
    // Note that TextComposition::String() may not be modified here because
352
0
    // it's modified after all edit action listeners are performed but this
353
0
    // is called while some of them are performed.
354
0
    // FYI: For supporting IME which commits composition and restart new
355
0
    //      composition immediately, we should cache next character of current
356
0
    //      composition too.
357
0
    uint32_t length = textComposition->LastData().Length() + 1;
358
0
    mTextRectArray.mStart = mCompositionStart;
359
0
    if (NS_WARN_IF(!QueryCharRectArray(aWidget, mTextRectArray.mStart, length,
360
0
                                       mTextRectArray.mRects))) {
361
0
      MOZ_LOG(sContentCacheLog, LogLevel::Error,
362
0
        ("0x%p CacheTextRects(), FAILED, "
363
0
         "couldn't retrieve text rect array of the composition string", this));
364
0
    }
365
0
  }
366
0
367
0
  if (mTextRectArray.InRange(mSelection.mAnchor) &&
368
0
      (!mSelection.mAnchor || mTextRectArray.InRange(mSelection.mAnchor - 1))) {
369
0
    mSelection.mAnchorCharRects[eNextCharRect] =
370
0
      mTextRectArray.GetRect(mSelection.mAnchor);
371
0
    if (mSelection.mAnchor) {
372
0
      mSelection.mAnchorCharRects[ePrevCharRect] =
373
0
        mTextRectArray.GetRect(mSelection.mAnchor - 1);
374
0
    }
375
0
  } else {
376
0
    RectArray rects;
377
0
    uint32_t startOffset = mSelection.mAnchor ? mSelection.mAnchor - 1 : 0;
378
0
    uint32_t length = mSelection.mAnchor ? 2 : 1;
379
0
    if (NS_WARN_IF(!QueryCharRectArray(aWidget, startOffset, length, rects))) {
380
0
      MOZ_LOG(sContentCacheLog, LogLevel::Error,
381
0
        ("0x%p CacheTextRects(), FAILED, "
382
0
         "couldn't retrieve text rect array around the selection anchor (%u)",
383
0
         this, mSelection.mAnchor));
384
0
      MOZ_ASSERT(mSelection.mAnchorCharRects[ePrevCharRect].IsEmpty());
385
0
      MOZ_ASSERT(mSelection.mAnchorCharRects[eNextCharRect].IsEmpty());
386
0
    } else {
387
0
      if (rects.Length() > 1) {
388
0
        mSelection.mAnchorCharRects[ePrevCharRect] = rects[0];
389
0
        mSelection.mAnchorCharRects[eNextCharRect] = rects[1];
390
0
      } else if (rects.Length()) {
391
0
        mSelection.mAnchorCharRects[eNextCharRect] = rects[0];
392
0
        MOZ_ASSERT(mSelection.mAnchorCharRects[ePrevCharRect].IsEmpty());
393
0
      }
394
0
    }
395
0
  }
396
0
397
0
  if (mSelection.Collapsed()) {
398
0
    mSelection.mFocusCharRects[0] = mSelection.mAnchorCharRects[0];
399
0
    mSelection.mFocusCharRects[1] = mSelection.mAnchorCharRects[1];
400
0
  } else if (mTextRectArray.InRange(mSelection.mFocus) &&
401
0
             (!mSelection.mFocus ||
402
0
              mTextRectArray.InRange(mSelection.mFocus - 1))) {
403
0
    mSelection.mFocusCharRects[eNextCharRect] =
404
0
      mTextRectArray.GetRect(mSelection.mFocus);
405
0
    if (mSelection.mFocus) {
406
0
      mSelection.mFocusCharRects[ePrevCharRect] =
407
0
        mTextRectArray.GetRect(mSelection.mFocus - 1);
408
0
    }
409
0
  } else {
410
0
    RectArray rects;
411
0
    uint32_t startOffset = mSelection.mFocus ? mSelection.mFocus - 1 : 0;
412
0
    uint32_t length = mSelection.mFocus ? 2 : 1;
413
0
    if (NS_WARN_IF(!QueryCharRectArray(aWidget, startOffset, length, rects))) {
414
0
      MOZ_LOG(sContentCacheLog, LogLevel::Error,
415
0
        ("0x%p CacheTextRects(), FAILED, "
416
0
         "couldn't retrieve text rect array around the selection focus (%u)",
417
0
         this, mSelection.mFocus));
418
0
      MOZ_ASSERT(mSelection.mFocusCharRects[ePrevCharRect].IsEmpty());
419
0
      MOZ_ASSERT(mSelection.mFocusCharRects[eNextCharRect].IsEmpty());
420
0
    } else {
421
0
      if (rects.Length() > 1) {
422
0
        mSelection.mFocusCharRects[ePrevCharRect] = rects[0];
423
0
        mSelection.mFocusCharRects[eNextCharRect] = rects[1];
424
0
      } else if (rects.Length()) {
425
0
        mSelection.mFocusCharRects[eNextCharRect] = rects[0];
426
0
        MOZ_ASSERT(mSelection.mFocusCharRects[ePrevCharRect].IsEmpty());
427
0
      }
428
0
    }
429
0
  }
430
0
431
0
  if (!mSelection.Collapsed()) {
432
0
    nsEventStatus status = nsEventStatus_eIgnore;
433
0
    WidgetQueryContentEvent textRect(true, eQueryTextRect, aWidget);
434
0
    textRect.InitForQueryTextRect(mSelection.StartOffset(),
435
0
                                  mSelection.Length());
436
0
    aWidget->DispatchEvent(&textRect, status);
437
0
    if (NS_WARN_IF(!textRect.mSucceeded)) {
438
0
      MOZ_LOG(sContentCacheLog, LogLevel::Error,
439
0
        ("0x%p CacheTextRects(), FAILED, "
440
0
         "couldn't retrieve text rect of whole selected text", this));
441
0
    } else {
442
0
      mSelection.mRect = textRect.mReply.mRect;
443
0
    }
444
0
  }
445
0
446
0
  if (!mSelection.mFocus) {
447
0
    mFirstCharRect = mSelection.mFocusCharRects[eNextCharRect];
448
0
  } else if (mSelection.mFocus == 1) {
449
0
    mFirstCharRect = mSelection.mFocusCharRects[ePrevCharRect];
450
0
  } else if (!mSelection.mAnchor) {
451
0
    mFirstCharRect = mSelection.mAnchorCharRects[eNextCharRect];
452
0
  } else if (mSelection.mAnchor == 1) {
453
0
    mFirstCharRect = mSelection.mFocusCharRects[ePrevCharRect];
454
0
  } else if (mTextRectArray.InRange(0)) {
455
0
    mFirstCharRect = mTextRectArray.GetRect(0);
456
0
  } else {
457
0
    LayoutDeviceIntRect charRect;
458
0
    if (NS_WARN_IF(!QueryCharRect(aWidget, 0, charRect))) {
459
0
      MOZ_LOG(sContentCacheLog, LogLevel::Error,
460
0
        ("0x%p CacheTextRects(), FAILED, "
461
0
         "couldn't retrieve first char rect", this));
462
0
    } else {
463
0
      mFirstCharRect = charRect;
464
0
    }
465
0
  }
466
0
467
0
  MOZ_LOG(sContentCacheLog, LogLevel::Info,
468
0
    ("0x%p CacheTextRects(), Succeeded, "
469
0
     "mText.Length()=%x, mTextRectArray={ mStart=%u, mRects.Length()=%zu"
470
0
     " }, mSelection={ mAnchor=%u, mAnchorCharRects[eNextCharRect]=%s, "
471
0
     "mAnchorCharRects[ePrevCharRect]=%s, mFocus=%u, "
472
0
     "mFocusCharRects[eNextCharRect]=%s, mFocusCharRects[ePrevCharRect]=%s, "
473
0
     "mRect=%s }, mFirstCharRect=%s",
474
0
     this, mText.Length(), mTextRectArray.mStart,
475
0
     mTextRectArray.mRects.Length(), mSelection.mAnchor,
476
0
     GetRectText(mSelection.mAnchorCharRects[eNextCharRect]).get(),
477
0
     GetRectText(mSelection.mAnchorCharRects[ePrevCharRect]).get(),
478
0
     mSelection.mFocus,
479
0
     GetRectText(mSelection.mFocusCharRects[eNextCharRect]).get(),
480
0
     GetRectText(mSelection.mFocusCharRects[ePrevCharRect]).get(),
481
0
     GetRectText(mSelection.mRect).get(), GetRectText(mFirstCharRect).get()));
482
0
  return true;
483
0
}
484
485
void
486
ContentCacheInChild::SetSelection(nsIWidget* aWidget,
487
                                  uint32_t aStartOffset,
488
                                  uint32_t aLength,
489
                                  bool aReversed,
490
                                  const WritingMode& aWritingMode)
491
0
{
492
0
  MOZ_LOG(sContentCacheLog, LogLevel::Info,
493
0
    ("0x%p SetSelection(aStartOffset=%u, "
494
0
     "aLength=%u, aReversed=%s, aWritingMode=%s), mText.Length()=%u",
495
0
     this, aStartOffset, aLength, GetBoolName(aReversed),
496
0
     GetWritingModeName(aWritingMode).get(), mText.Length()));
497
0
498
0
  if (!aReversed) {
499
0
    mSelection.mAnchor = aStartOffset;
500
0
    mSelection.mFocus = aStartOffset + aLength;
501
0
  } else {
502
0
    mSelection.mAnchor = aStartOffset + aLength;
503
0
    mSelection.mFocus = aStartOffset;
504
0
  }
505
0
  mSelection.mWritingMode = aWritingMode;
506
0
507
0
  if (NS_WARN_IF(!CacheCaret(aWidget))) {
508
0
    return;
509
0
  }
510
0
  Unused << NS_WARN_IF(!CacheTextRects(aWidget));
511
0
}
512
513
/*****************************************************************************
514
 * mozilla::ContentCacheInParent
515
 *****************************************************************************/
516
517
ContentCacheInParent::ContentCacheInParent(TabParent& aTabParent)
518
  : ContentCache()
519
  , mTabParent(aTabParent)
520
  , mCommitStringByRequest(nullptr)
521
  , mPendingEventsNeedingAck(0)
522
  , mCompositionStartInChild(UINT32_MAX)
523
  , mPendingCommitLength(0)
524
  , mPendingCompositionCount(0)
525
  , mPendingCommitCount(0)
526
  , mWidgetHasComposition(false)
527
  , mIsChildIgnoringCompositionEvents(false)
528
0
{
529
0
}
530
531
void
532
ContentCacheInParent::AssignContent(const ContentCache& aOther,
533
                                    nsIWidget* aWidget,
534
                                    const IMENotification* aNotification)
535
0
{
536
0
  mText = aOther.mText;
537
0
  mSelection = aOther.mSelection;
538
0
  mFirstCharRect = aOther.mFirstCharRect;
539
0
  mCaret = aOther.mCaret;
540
0
  mTextRectArray = aOther.mTextRectArray;
541
0
  mEditorRect = aOther.mEditorRect;
542
0
543
0
  // Only when there is one composition, the TextComposition instance in this
544
0
  // process is managing the composition in the remote process.  Therefore,
545
0
  // we shouldn't update composition start offset of TextComposition with
546
0
  // old composition which is still being handled by the child process.
547
0
  if (mWidgetHasComposition && mPendingCompositionCount == 1) {
548
0
    IMEStateManager::MaybeStartOffsetUpdatedInChild(aWidget, mCompositionStart);
549
0
  }
550
0
551
0
  // When this instance allows to query content relative to composition string,
552
0
  // we should modify mCompositionStart with the latest information in the
553
0
  // remote process because now we have the information around the composition
554
0
  // string.
555
0
  mCompositionStartInChild = aOther.mCompositionStart;
556
0
  if (mWidgetHasComposition || mPendingCommitCount) {
557
0
    if (aOther.mCompositionStart != UINT32_MAX) {
558
0
      if (mCompositionStart != aOther.mCompositionStart) {
559
0
        mCompositionStart = aOther.mCompositionStart;
560
0
        mPendingCommitLength = 0;
561
0
      }
562
0
    } else if (mCompositionStart != mSelection.StartOffset()) {
563
0
      mCompositionStart = mSelection.StartOffset();
564
0
      mPendingCommitLength = 0;
565
0
      NS_WARNING_ASSERTION(mCompositionStart != UINT32_MAX,
566
0
                           "mCompositionStart shouldn't be invalid offset when "
567
0
                           "the widget has composition");
568
0
    }
569
0
  }
570
0
571
0
  MOZ_LOG(sContentCacheLog, LogLevel::Info,
572
0
    ("0x%p AssignContent(aNotification=%s), "
573
0
     "Succeeded, mText.Length()=%u, mSelection={ mAnchor=%u, mFocus=%u, "
574
0
     "mWritingMode=%s, mAnchorCharRects[eNextCharRect]=%s, "
575
0
     "mAnchorCharRects[ePrevCharRect]=%s, mFocusCharRects[eNextCharRect]=%s, "
576
0
     "mFocusCharRects[ePrevCharRect]=%s, mRect=%s }, "
577
0
     "mFirstCharRect=%s, mCaret={ mOffset=%u, mRect=%s }, mTextRectArray={ "
578
0
     "mStart=%u, mRects.Length()=%zu }, mWidgetHasComposition=%s, "
579
0
     "mPendingCompositionCount=%u, mCompositionStart=%u, "
580
0
     "mPendingCommitLength=%u, mEditorRect=%s",
581
0
     this, GetNotificationName(aNotification),
582
0
     mText.Length(), mSelection.mAnchor, mSelection.mFocus,
583
0
     GetWritingModeName(mSelection.mWritingMode).get(),
584
0
     GetRectText(mSelection.mAnchorCharRects[eNextCharRect]).get(),
585
0
     GetRectText(mSelection.mAnchorCharRects[ePrevCharRect]).get(),
586
0
     GetRectText(mSelection.mFocusCharRects[eNextCharRect]).get(),
587
0
     GetRectText(mSelection.mFocusCharRects[ePrevCharRect]).get(),
588
0
     GetRectText(mSelection.mRect).get(), GetRectText(mFirstCharRect).get(),
589
0
     mCaret.mOffset, GetRectText(mCaret.mRect).get(), mTextRectArray.mStart,
590
0
     mTextRectArray.mRects.Length(), GetBoolName(mWidgetHasComposition),
591
0
     mPendingCompositionCount, mCompositionStart, mPendingCommitLength,
592
0
     GetRectText(mEditorRect).get()));
593
0
}
594
595
bool
596
ContentCacheInParent::HandleQueryContentEvent(WidgetQueryContentEvent& aEvent,
597
                                              nsIWidget* aWidget) const
598
0
{
599
0
  MOZ_ASSERT(aWidget);
600
0
601
0
  aEvent.mSucceeded = false;
602
0
  aEvent.mReply.mFocusedWidget = aWidget;
603
0
604
0
  // ContentCache doesn't store offset of its start with XP linebreaks.
605
0
  // So, we don't support to query contents relative to composition start
606
0
  // offset with XP linebreaks.
607
0
  if (NS_WARN_IF(!aEvent.mUseNativeLineBreak)) {
608
0
    MOZ_LOG(sContentCacheLog, LogLevel::Error,
609
0
      ("0x%p HandleQueryContentEvent(), FAILED due to query with XP linebreaks",
610
0
       this));
611
0
    return false;
612
0
  }
613
0
614
0
  if (NS_WARN_IF(!aEvent.mInput.IsValidOffset())) {
615
0
    MOZ_LOG(sContentCacheLog, LogLevel::Error,
616
0
      ("0x%p HandleQueryContentEvent(), FAILED due to invalid offset",
617
0
       this));
618
0
    return false;
619
0
  }
620
0
621
0
  if (NS_WARN_IF(!aEvent.mInput.IsValidEventMessage(aEvent.mMessage))) {
622
0
    MOZ_LOG(sContentCacheLog, LogLevel::Error,
623
0
      ("0x%p HandleQueryContentEvent(), FAILED due to invalid event message",
624
0
       this));
625
0
    return false;
626
0
  }
627
0
628
0
  bool isRelativeToInsertionPoint = aEvent.mInput.mRelativeToInsertionPoint;
629
0
  if (isRelativeToInsertionPoint) {
630
0
    if (aWidget->PluginHasFocus()) {
631
0
      if (NS_WARN_IF(!aEvent.mInput.MakeOffsetAbsolute(0))) {
632
0
        MOZ_LOG(sContentCacheLog, LogLevel::Error,
633
0
          ("0x%p HandleQueryContentEvent(), FAILED due to "
634
0
           "aEvent.mInput.MakeOffsetAbsolute(0) failure, aEvent={ mMessage=%s, "
635
0
           "mInput={ mOffset=%" PRId64 ", mLength=%" PRIu32 " } }",
636
0
           this, ToChar(aEvent.mMessage), aEvent.mInput.mOffset,
637
0
           aEvent.mInput.mLength));
638
0
        return false;
639
0
      }
640
0
    } else if (mWidgetHasComposition || mPendingCommitCount) {
641
0
      if (NS_WARN_IF(!aEvent.mInput.MakeOffsetAbsolute(
642
0
                                      mCompositionStart +
643
0
                                        mPendingCommitLength))) {
644
0
        MOZ_LOG(sContentCacheLog, LogLevel::Error,
645
0
          ("0x%p HandleQueryContentEvent(), FAILED due to "
646
0
           "aEvent.mInput.MakeOffsetAbsolute(mCompositionStart + "
647
0
           "mPendingCommitLength) failure, "
648
0
           "mCompositionStart=%" PRIu32 ", mPendingCommitLength=%" PRIu32 ", "
649
0
           "aEvent={ mMessage=%s, mInput={ mOffset=%" PRId64
650
0
           ", mLength=%" PRIu32 " } }",
651
0
           this, mCompositionStart, mPendingCommitLength,
652
0
           ToChar(aEvent.mMessage), aEvent.mInput.mOffset,
653
0
           aEvent.mInput.mLength));
654
0
        return false;
655
0
      }
656
0
    } else if (NS_WARN_IF(!mSelection.IsValid())) {
657
0
      MOZ_LOG(sContentCacheLog, LogLevel::Error,
658
0
        ("0x%p HandleQueryContentEvent(), FAILED due to mSelection is invalid",
659
0
         this));
660
0
      return false;
661
0
    } else if (NS_WARN_IF(!aEvent.mInput.MakeOffsetAbsolute(
662
0
                                           mSelection.StartOffset() +
663
0
                                             mPendingCommitLength))) {
664
0
      MOZ_LOG(sContentCacheLog, LogLevel::Error,
665
0
        ("0x%p HandleQueryContentEvent(), FAILED due to "
666
0
         "aEvent.mInput.MakeOffsetAbsolute(mSelection.StartOffset() + "
667
0
         "mPendingCommitLength) failure, "
668
0
         "mSelection={ StartOffset()=%d, Length()=%d }, "
669
0
         "mPendingCommitLength=%" PRIu32 ", aEvent={ mMessage=%s, "
670
0
         "mInput={ mOffset=%" PRId64 ", mLength=%" PRIu32 " } }",
671
0
         this, mSelection.StartOffset(), mSelection.Length(),
672
0
         mPendingCommitLength, ToChar(aEvent.mMessage),
673
0
         aEvent.mInput.mOffset, aEvent.mInput.mLength));
674
0
      return false;
675
0
    }
676
0
  }
677
0
678
0
  switch (aEvent.mMessage) {
679
0
    case eQuerySelectedText:
680
0
      MOZ_LOG(sContentCacheLog, LogLevel::Info,
681
0
        ("0x%p HandleQueryContentEvent("
682
0
         "aEvent={ mMessage=eQuerySelectedText }, aWidget=0x%p)",
683
0
         this, aWidget));
684
0
      if (aWidget->PluginHasFocus()) {
685
0
        MOZ_LOG(sContentCacheLog, LogLevel::Info,
686
0
          ("0x%p HandleQueryContentEvent(), "
687
0
           "return emtpy selection becasue plugin has focus",
688
0
           this));
689
0
        aEvent.mSucceeded = true;
690
0
        aEvent.mReply.mOffset = 0;
691
0
        aEvent.mReply.mReversed = false;
692
0
        aEvent.mReply.mHasSelection = false;
693
0
        return true;
694
0
      }
695
0
      if (NS_WARN_IF(!IsSelectionValid())) {
696
0
        // If content cache hasn't been initialized properly, make the query
697
0
        // failed.
698
0
        MOZ_LOG(sContentCacheLog, LogLevel::Error,
699
0
          ("0x%p HandleQueryContentEvent(), "
700
0
           "FAILED because mSelection is not valid", this));
701
0
        return true;
702
0
      }
703
0
      aEvent.mReply.mOffset = mSelection.StartOffset();
704
0
      if (mSelection.Collapsed()) {
705
0
        aEvent.mReply.mString.Truncate(0);
706
0
      } else {
707
0
        if (NS_WARN_IF(mSelection.EndOffset() > mText.Length())) {
708
0
          MOZ_LOG(sContentCacheLog, LogLevel::Error,
709
0
            ("0x%p HandleQueryContentEvent(), "
710
0
             "FAILED because mSelection.EndOffset()=%u is larger than "
711
0
             "mText.Length()=%u",
712
0
             this, mSelection.EndOffset(), mText.Length()));
713
0
          return false;
714
0
        }
715
0
        aEvent.mReply.mString =
716
0
          Substring(mText, aEvent.mReply.mOffset, mSelection.Length());
717
0
      }
718
0
      aEvent.mReply.mReversed = mSelection.Reversed();
719
0
      aEvent.mReply.mHasSelection = true;
720
0
      aEvent.mReply.mWritingMode = mSelection.mWritingMode;
721
0
      MOZ_LOG(sContentCacheLog, LogLevel::Info,
722
0
        ("0x%p HandleQueryContentEvent(), "
723
0
         "Succeeded, aEvent={ mReply={ mOffset=%u, mString=\"%s\", "
724
0
         "mReversed=%s, mHasSelection=%s, mWritingMode=%s } }",
725
0
         this, aEvent.mReply.mOffset,
726
0
         GetEscapedUTF8String(aEvent.mReply.mString).get(),
727
0
         GetBoolName(aEvent.mReply.mReversed),
728
0
         GetBoolName(aEvent.mReply.mHasSelection),
729
0
         GetWritingModeName(aEvent.mReply.mWritingMode).get()));
730
0
      break;
731
0
    case eQueryTextContent: {
732
0
      MOZ_LOG(sContentCacheLog, LogLevel::Info,
733
0
        ("0x%p HandleQueryContentEvent("
734
0
         "aEvent={ mMessage=eQueryTextContent, mInput={ mOffset=%" PRId64
735
0
         ", mLength=%u } }, aWidget=0x%p), mText.Length()=%u",
736
0
         this, aEvent.mInput.mOffset,
737
0
         aEvent.mInput.mLength, aWidget, mText.Length()));
738
0
      uint32_t inputOffset = aEvent.mInput.mOffset;
739
0
      uint32_t inputEndOffset =
740
0
        std::min(aEvent.mInput.EndOffset(), mText.Length());
741
0
      if (NS_WARN_IF(inputEndOffset < inputOffset)) {
742
0
        MOZ_LOG(sContentCacheLog, LogLevel::Error,
743
0
          ("0x%p HandleQueryContentEvent(), "
744
0
           "FAILED because inputOffset=%u is larger than inputEndOffset=%u",
745
0
           this, inputOffset, inputEndOffset));
746
0
        return false;
747
0
      }
748
0
      aEvent.mReply.mOffset = inputOffset;
749
0
      aEvent.mReply.mString =
750
0
        Substring(mText, inputOffset, inputEndOffset - inputOffset);
751
0
      MOZ_LOG(sContentCacheLog, LogLevel::Info,
752
0
        ("0x%p HandleQueryContentEvent(), "
753
0
         "Succeeded, aEvent={ mReply={ mOffset=%u, mString.Length()=%u } }",
754
0
         this, aEvent.mReply.mOffset, aEvent.mReply.mString.Length()));
755
0
      break;
756
0
    }
757
0
    case eQueryTextRect:
758
0
      MOZ_LOG(sContentCacheLog, LogLevel::Info,
759
0
        ("0x%p HandleQueryContentEvent("
760
0
         "aEvent={ mMessage=eQueryTextRect, mInput={ mOffset=%" PRId64
761
0
         ", mLength=%u } }, aWidget=0x%p), mText.Length()=%u",
762
0
         this, aEvent.mInput.mOffset, aEvent.mInput.mLength, aWidget,
763
0
         mText.Length()));
764
0
      if (NS_WARN_IF(!IsSelectionValid())) {
765
0
        // If content cache hasn't been initialized properly, make the query
766
0
        // failed.
767
0
        MOZ_LOG(sContentCacheLog, LogLevel::Error,
768
0
          ("0x%p HandleQueryContentEvent(), "
769
0
           "FAILED because mSelection is not valid", this));
770
0
        return true;
771
0
      }
772
0
      // Note that if the query is relative to insertion point, the query was
773
0
      // probably requested by native IME.  In such case, we should return
774
0
      // non-empty rect since returning failure causes IME showing its window
775
0
      // at odd position.
776
0
      if (aEvent.mInput.mLength) {
777
0
        if (NS_WARN_IF(!GetUnionTextRects(aEvent.mInput.mOffset,
778
0
                                          aEvent.mInput.mLength,
779
0
                                          isRelativeToInsertionPoint,
780
0
                                          aEvent.mReply.mRect))) {
781
0
          // XXX We don't have cache for this request.
782
0
          MOZ_LOG(sContentCacheLog, LogLevel::Error,
783
0
            ("0x%p HandleQueryContentEvent(), "
784
0
             "FAILED to get union rect", this));
785
0
          return false;
786
0
        }
787
0
      } else {
788
0
        // If the length is 0, we should return caret rect instead.
789
0
        if (NS_WARN_IF(!GetCaretRect(aEvent.mInput.mOffset,
790
0
                                     isRelativeToInsertionPoint,
791
0
                                     aEvent.mReply.mRect))) {
792
0
          MOZ_LOG(sContentCacheLog, LogLevel::Error,
793
0
            ("0x%p HandleQueryContentEvent(), "
794
0
             "FAILED to get caret rect", this));
795
0
          return false;
796
0
        }
797
0
      }
798
0
      if (aEvent.mInput.mOffset < mText.Length()) {
799
0
        aEvent.mReply.mString =
800
0
          Substring(mText, aEvent.mInput.mOffset,
801
0
                    mText.Length() >= aEvent.mInput.EndOffset() ?
802
0
                      aEvent.mInput.mLength : UINT32_MAX);
803
0
      } else {
804
0
        aEvent.mReply.mString.Truncate(0);
805
0
      }
806
0
      aEvent.mReply.mOffset = aEvent.mInput.mOffset;
807
0
      // XXX This may be wrong if storing range isn't in the selection range.
808
0
      aEvent.mReply.mWritingMode = mSelection.mWritingMode;
809
0
      MOZ_LOG(sContentCacheLog, LogLevel::Info,
810
0
        ("0x%p HandleQueryContentEvent(), "
811
0
         "Succeeded, aEvent={ mReply={ mOffset=%u, mString=\"%s\", "
812
0
         "mWritingMode=%s, mRect=%s } }",
813
0
         this, aEvent.mReply.mOffset,
814
0
         GetEscapedUTF8String(aEvent.mReply.mString).get(),
815
0
         GetWritingModeName(aEvent.mReply.mWritingMode).get(),
816
0
         GetRectText(aEvent.mReply.mRect).get()));
817
0
      break;
818
0
    case eQueryCaretRect:
819
0
      MOZ_LOG(sContentCacheLog, LogLevel::Info,
820
0
        ("0x%p HandleQueryContentEvent("
821
0
         "aEvent={ mMessage=eQueryCaretRect, mInput={ mOffset=%" PRId64 " } }, "
822
0
         "aWidget=0x%p), mText.Length()=%u",
823
0
         this, aEvent.mInput.mOffset, aWidget, mText.Length()));
824
0
      if (NS_WARN_IF(!IsSelectionValid())) {
825
0
        // If content cache hasn't been initialized properly, make the query
826
0
        // failed.
827
0
        MOZ_LOG(sContentCacheLog, LogLevel::Error,
828
0
          ("0x%p HandleQueryContentEvent(), "
829
0
           "FAILED because mSelection is not valid", this));
830
0
        return true;
831
0
      }
832
0
      // Note that if the query is relative to insertion point, the query was
833
0
      // probably requested by native IME.  In such case, we should return
834
0
      // non-empty rect since returning failure causes IME showing its window
835
0
      // at odd position.
836
0
      if (NS_WARN_IF(!GetCaretRect(aEvent.mInput.mOffset,
837
0
                                   isRelativeToInsertionPoint,
838
0
                                   aEvent.mReply.mRect))) {
839
0
        MOZ_LOG(sContentCacheLog, LogLevel::Error,
840
0
          ("0x%p HandleQueryContentEvent(), "
841
0
           "FAILED to get caret rect", this));
842
0
        return false;
843
0
      }
844
0
      aEvent.mReply.mOffset = aEvent.mInput.mOffset;
845
0
      MOZ_LOG(sContentCacheLog, LogLevel::Info,
846
0
        ("0x%p HandleQueryContentEvent(), "
847
0
         "Succeeded, aEvent={ mReply={ mOffset=%u, mRect=%s } }",
848
0
         this, aEvent.mReply.mOffset, GetRectText(aEvent.mReply.mRect).get()));
849
0
      break;
850
0
    case eQueryEditorRect:
851
0
      MOZ_LOG(sContentCacheLog, LogLevel::Info,
852
0
        ("0x%p HandleQueryContentEvent("
853
0
         "aEvent={ mMessage=eQueryEditorRect }, aWidget=0x%p)",
854
0
         this, aWidget));
855
0
      aEvent.mReply.mRect = mEditorRect;
856
0
      MOZ_LOG(sContentCacheLog, LogLevel::Info,
857
0
        ("0x%p HandleQueryContentEvent(), "
858
0
         "Succeeded, aEvent={ mReply={ mRect=%s } }",
859
0
         this, GetRectText(aEvent.mReply.mRect).get()));
860
0
      break;
861
0
    default:
862
0
      break;
863
0
  }
864
0
  aEvent.mSucceeded = true;
865
0
  return true;
866
0
}
867
868
bool
869
ContentCacheInParent::GetTextRect(uint32_t aOffset,
870
                                  bool aRoundToExistingOffset,
871
                                  LayoutDeviceIntRect& aTextRect) const
872
0
{
873
0
  MOZ_LOG(sContentCacheLog, LogLevel::Info,
874
0
    ("0x%p GetTextRect(aOffset=%u, "
875
0
     "aRoundToExistingOffset=%s), "
876
0
     "mTextRectArray={ mStart=%u, mRects.Length()=%zu }, "
877
0
     "mSelection={ mAnchor=%u, mFocus=%u }",
878
0
     this, aOffset, GetBoolName(aRoundToExistingOffset),
879
0
     mTextRectArray.mStart, mTextRectArray.mRects.Length(),
880
0
     mSelection.mAnchor, mSelection.mFocus));
881
0
882
0
  if (!aOffset) {
883
0
    NS_WARNING_ASSERTION(!mFirstCharRect.IsEmpty(), "empty rect");
884
0
    aTextRect = mFirstCharRect;
885
0
    return !aTextRect.IsEmpty();
886
0
  }
887
0
  if (aOffset == mSelection.mAnchor) {
888
0
    NS_WARNING_ASSERTION(!mSelection.mAnchorCharRects[eNextCharRect].IsEmpty(),
889
0
                         "empty rect");
890
0
    aTextRect = mSelection.mAnchorCharRects[eNextCharRect];
891
0
    return !aTextRect.IsEmpty();
892
0
  }
893
0
  if (mSelection.mAnchor && aOffset == mSelection.mAnchor - 1) {
894
0
    NS_WARNING_ASSERTION(!mSelection.mAnchorCharRects[ePrevCharRect].IsEmpty(),
895
0
                         "empty rect");
896
0
    aTextRect = mSelection.mAnchorCharRects[ePrevCharRect];
897
0
    return !aTextRect.IsEmpty();
898
0
  }
899
0
  if (aOffset == mSelection.mFocus) {
900
0
    NS_WARNING_ASSERTION(!mSelection.mFocusCharRects[eNextCharRect].IsEmpty(),
901
0
                         "empty rect");
902
0
    aTextRect = mSelection.mFocusCharRects[eNextCharRect];
903
0
    return !aTextRect.IsEmpty();
904
0
  }
905
0
  if (mSelection.mFocus && aOffset == mSelection.mFocus - 1) {
906
0
    NS_WARNING_ASSERTION(!mSelection.mFocusCharRects[ePrevCharRect].IsEmpty(),
907
0
                         "empty rect");
908
0
    aTextRect = mSelection.mFocusCharRects[ePrevCharRect];
909
0
    return !aTextRect.IsEmpty();
910
0
  }
911
0
912
0
  uint32_t offset = aOffset;
913
0
  if (!mTextRectArray.InRange(aOffset)) {
914
0
    if (!aRoundToExistingOffset) {
915
0
      aTextRect.SetEmpty();
916
0
      return false;
917
0
    }
918
0
    if (!mTextRectArray.IsValid()) {
919
0
      // If there are no rects in mTextRectArray, we should refer the start of
920
0
      // the selection because IME must query a char rect around it if there is
921
0
      // no composition.
922
0
      aTextRect = mSelection.StartCharRect();
923
0
      return !aTextRect.IsEmpty();
924
0
    }
925
0
    if (offset < mTextRectArray.StartOffset()) {
926
0
      offset = mTextRectArray.StartOffset();
927
0
    } else {
928
0
      offset = mTextRectArray.EndOffset() - 1;
929
0
    }
930
0
  }
931
0
  aTextRect = mTextRectArray.GetRect(offset);
932
0
  return !aTextRect.IsEmpty();
933
0
}
934
935
bool
936
ContentCacheInParent::GetUnionTextRects(
937
                        uint32_t aOffset,
938
                        uint32_t aLength,
939
                        bool aRoundToExistingOffset,
940
                        LayoutDeviceIntRect& aUnionTextRect) const
941
0
{
942
0
  MOZ_LOG(sContentCacheLog, LogLevel::Info,
943
0
    ("0x%p GetUnionTextRects(aOffset=%u, "
944
0
     "aLength=%u, aRoundToExistingOffset=%s), mTextRectArray={ "
945
0
     "mStart=%u, mRects.Length()=%zu }, "
946
0
     "mSelection={ mAnchor=%u, mFocus=%u }",
947
0
     this, aOffset, aLength, GetBoolName(aRoundToExistingOffset),
948
0
     mTextRectArray.mStart, mTextRectArray.mRects.Length(),
949
0
     mSelection.mAnchor, mSelection.mFocus));
950
0
951
0
  CheckedInt<uint32_t> endOffset =
952
0
    CheckedInt<uint32_t>(aOffset) + aLength;
953
0
  if (!endOffset.isValid()) {
954
0
    return false;
955
0
  }
956
0
957
0
  if (!mSelection.Collapsed() &&
958
0
      aOffset == mSelection.StartOffset() && aLength == mSelection.Length()) {
959
0
    NS_WARNING_ASSERTION(!mSelection.mRect.IsEmpty(), "empty rect");
960
0
    aUnionTextRect = mSelection.mRect;
961
0
    return !aUnionTextRect.IsEmpty();
962
0
  }
963
0
964
0
  if (aLength == 1) {
965
0
    if (!aOffset) {
966
0
      NS_WARNING_ASSERTION(!mFirstCharRect.IsEmpty(), "empty rect");
967
0
      aUnionTextRect = mFirstCharRect;
968
0
      return !aUnionTextRect.IsEmpty();
969
0
    }
970
0
    if (aOffset == mSelection.mAnchor) {
971
0
      NS_WARNING_ASSERTION(
972
0
        !mSelection.mAnchorCharRects[eNextCharRect].IsEmpty(), "empty rect");
973
0
      aUnionTextRect = mSelection.mAnchorCharRects[eNextCharRect];
974
0
      return !aUnionTextRect.IsEmpty();
975
0
    }
976
0
    if (mSelection.mAnchor && aOffset == mSelection.mAnchor - 1) {
977
0
      NS_WARNING_ASSERTION(
978
0
        !mSelection.mAnchorCharRects[ePrevCharRect].IsEmpty(), "empty rect");
979
0
      aUnionTextRect = mSelection.mAnchorCharRects[ePrevCharRect];
980
0
      return !aUnionTextRect.IsEmpty();
981
0
    }
982
0
    if (aOffset == mSelection.mFocus) {
983
0
      NS_WARNING_ASSERTION(
984
0
        !mSelection.mFocusCharRects[eNextCharRect].IsEmpty(), "empty rect");
985
0
      aUnionTextRect = mSelection.mFocusCharRects[eNextCharRect];
986
0
      return !aUnionTextRect.IsEmpty();
987
0
    }
988
0
    if (mSelection.mFocus && aOffset == mSelection.mFocus - 1) {
989
0
      NS_WARNING_ASSERTION(
990
0
        !mSelection.mFocusCharRects[ePrevCharRect].IsEmpty(), "empty rect");
991
0
      aUnionTextRect = mSelection.mFocusCharRects[ePrevCharRect];
992
0
      return !aUnionTextRect.IsEmpty();
993
0
    }
994
0
  }
995
0
996
0
  // Even if some text rects are not cached of the queried range,
997
0
  // we should return union rect when the first character's rect is cached
998
0
  // since the first character rect is important and the others are not so
999
0
  // in most cases.
1000
0
1001
0
  if (!aOffset && aOffset != mSelection.mAnchor &&
1002
0
      aOffset != mSelection.mFocus && !mTextRectArray.InRange(aOffset)) {
1003
0
    // The first character rect isn't cached.
1004
0
    return false;
1005
0
  }
1006
0
1007
0
  if ((aRoundToExistingOffset && mTextRectArray.HasRects()) ||
1008
0
      mTextRectArray.IsOverlappingWith(aOffset, aLength)) {
1009
0
    aUnionTextRect =
1010
0
      mTextRectArray.GetUnionRectAsFarAsPossible(aOffset, aLength,
1011
0
                                                 aRoundToExistingOffset);
1012
0
  } else {
1013
0
    aUnionTextRect.SetEmpty();
1014
0
  }
1015
0
1016
0
  if (!aOffset) {
1017
0
    aUnionTextRect = aUnionTextRect.Union(mFirstCharRect);
1018
0
  }
1019
0
  if (aOffset <= mSelection.mAnchor && mSelection.mAnchor < endOffset.value()) {
1020
0
    aUnionTextRect =
1021
0
      aUnionTextRect.Union(mSelection.mAnchorCharRects[eNextCharRect]);
1022
0
  }
1023
0
  if (mSelection.mAnchor && aOffset <= mSelection.mAnchor - 1 &&
1024
0
      mSelection.mAnchor - 1 < endOffset.value()) {
1025
0
    aUnionTextRect =
1026
0
      aUnionTextRect.Union(mSelection.mAnchorCharRects[ePrevCharRect]);
1027
0
  }
1028
0
  if (aOffset <= mSelection.mFocus && mSelection.mFocus < endOffset.value()) {
1029
0
    aUnionTextRect =
1030
0
      aUnionTextRect.Union(mSelection.mFocusCharRects[eNextCharRect]);
1031
0
  }
1032
0
  if (mSelection.mFocus && aOffset <= mSelection.mFocus - 1 &&
1033
0
      mSelection.mFocus - 1 < endOffset.value()) {
1034
0
    aUnionTextRect =
1035
0
      aUnionTextRect.Union(mSelection.mFocusCharRects[ePrevCharRect]);
1036
0
  }
1037
0
1038
0
  return !aUnionTextRect.IsEmpty();
1039
0
}
1040
1041
bool
1042
ContentCacheInParent::GetCaretRect(uint32_t aOffset,
1043
                                   bool aRoundToExistingOffset,
1044
                                   LayoutDeviceIntRect& aCaretRect) const
1045
0
{
1046
0
  MOZ_LOG(sContentCacheLog, LogLevel::Info,
1047
0
    ("0x%p GetCaretRect(aOffset=%u, "
1048
0
     "aRoundToExistingOffset=%s), "
1049
0
     "mCaret={ mOffset=%u, mRect=%s, IsValid()=%s }, mTextRectArray={ "
1050
0
     "mStart=%u, mRects.Length()=%zu }, mSelection={ mAnchor=%u, mFocus=%u, "
1051
0
     "mWritingMode=%s, mAnchorCharRects[eNextCharRect]=%s, "
1052
0
     "mAnchorCharRects[ePrevCharRect]=%s, mFocusCharRects[eNextCharRect]=%s, "
1053
0
     "mFocusCharRects[ePrevCharRect]=%s }, mFirstCharRect=%s",
1054
0
     this, aOffset, GetBoolName(aRoundToExistingOffset),
1055
0
     mCaret.mOffset, GetRectText(mCaret.mRect).get(),
1056
0
     GetBoolName(mCaret.IsValid()), mTextRectArray.mStart,
1057
0
     mTextRectArray.mRects.Length(), mSelection.mAnchor, mSelection.mFocus,
1058
0
     GetWritingModeName(mSelection.mWritingMode).get(),
1059
0
     GetRectText(mSelection.mAnchorCharRects[eNextCharRect]).get(),
1060
0
     GetRectText(mSelection.mAnchorCharRects[ePrevCharRect]).get(),
1061
0
     GetRectText(mSelection.mFocusCharRects[eNextCharRect]).get(),
1062
0
     GetRectText(mSelection.mFocusCharRects[ePrevCharRect]).get(),
1063
0
     GetRectText(mFirstCharRect).get()));
1064
0
1065
0
  if (mCaret.IsValid() && mCaret.mOffset == aOffset) {
1066
0
    aCaretRect = mCaret.mRect;
1067
0
    return true;
1068
0
  }
1069
0
1070
0
  // Guess caret rect from the text rect if it's stored.
1071
0
  if (!GetTextRect(aOffset, aRoundToExistingOffset, aCaretRect)) {
1072
0
    // There might be previous character rect in the cache.  If so, we can
1073
0
    // guess the caret rect with it.
1074
0
    if (!aOffset ||
1075
0
        !GetTextRect(aOffset - 1, aRoundToExistingOffset, aCaretRect)) {
1076
0
      aCaretRect.SetEmpty();
1077
0
      return false;
1078
0
    }
1079
0
1080
0
    if (mSelection.mWritingMode.IsVertical()) {
1081
0
      aCaretRect.MoveToY(aCaretRect.YMost());
1082
0
    } else {
1083
0
      // XXX bidi-unaware.
1084
0
      aCaretRect.MoveToX(aCaretRect.XMost());
1085
0
    }
1086
0
  }
1087
0
1088
0
  // XXX This is not bidi aware because we don't cache each character's
1089
0
  //     direction.  However, this is usually used by IME, so, assuming the
1090
0
  //     character is in LRT context must not cause any problem.
1091
0
  if (mSelection.mWritingMode.IsVertical()) {
1092
0
    aCaretRect.SetHeight(mCaret.IsValid() ? mCaret.mRect.Height() : 1);
1093
0
  } else {
1094
0
    aCaretRect.SetWidth(mCaret.IsValid() ? mCaret.mRect.Width() : 1);
1095
0
  }
1096
0
  return true;
1097
0
}
1098
1099
bool
1100
ContentCacheInParent::OnCompositionEvent(const WidgetCompositionEvent& aEvent)
1101
0
{
1102
0
  MOZ_LOG(sContentCacheLog, LogLevel::Info,
1103
0
    ("0x%p OnCompositionEvent(aEvent={ "
1104
0
     "mMessage=%s, mData=\"%s\" (Length()=%u), mRanges->Length()=%zu }), "
1105
0
     "mPendingEventsNeedingAck=%u, mWidgetHasComposition=%s, "
1106
0
     "mPendingCompositionCount=%" PRIu8 ", mPendingCommitCount=%" PRIu8 ", "
1107
0
     "mIsChildIgnoringCompositionEvents=%s, mCommitStringByRequest=0x%p",
1108
0
     this, ToChar(aEvent.mMessage),
1109
0
     GetEscapedUTF8String(aEvent.mData).get(), aEvent.mData.Length(),
1110
0
     aEvent.mRanges ? aEvent.mRanges->Length() : 0, mPendingEventsNeedingAck,
1111
0
     GetBoolName(mWidgetHasComposition), mPendingCompositionCount,
1112
0
     mPendingCommitCount, GetBoolName(mIsChildIgnoringCompositionEvents),
1113
0
     mCommitStringByRequest));
1114
0
1115
0
#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1116
0
  mDispatchedEventMessages.AppendElement(aEvent.mMessage);
1117
0
#endif // #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1118
0
1119
0
  // We must be able to simulate the selection because
1120
0
  // we might not receive selection updates in time
1121
0
  if (!mWidgetHasComposition) {
1122
0
    if (aEvent.mWidget && aEvent.mWidget->PluginHasFocus()) {
1123
0
      // If focus is on plugin, we cannot get selection range
1124
0
      mCompositionStart = 0;
1125
0
    } else if (mCompositionStartInChild != UINT32_MAX) {
1126
0
      // If there is pending composition in the remote process, let's use
1127
0
      // its start offset temporarily because this stores a lot of information
1128
0
      // around it and the user must look around there, so, showing some UI
1129
0
      // around it must make sense.
1130
0
      mCompositionStart = mCompositionStartInChild;
1131
0
    } else {
1132
0
      mCompositionStart = mSelection.StartOffset();
1133
0
    }
1134
0
    MOZ_ASSERT(aEvent.mMessage == eCompositionStart);
1135
0
    MOZ_RELEASE_ASSERT(mPendingCompositionCount < UINT8_MAX);
1136
0
    mPendingCompositionCount++;
1137
0
  }
1138
0
1139
0
  mWidgetHasComposition = !aEvent.CausesDOMCompositionEndEvent();
1140
0
1141
0
  if (!mWidgetHasComposition) {
1142
0
    // mCompositionStart will be reset when commit event is completely handled
1143
0
    // in the remote process.
1144
0
    if (mPendingCompositionCount == 1) {
1145
0
      mPendingCommitLength = aEvent.mData.Length();
1146
0
    }
1147
0
    mPendingCommitCount++;
1148
0
  } else if (aEvent.mMessage != eCompositionStart) {
1149
0
    mCompositionString = aEvent.mData;
1150
0
  }
1151
0
1152
0
  // During REQUEST_TO_COMMIT_COMPOSITION or REQUEST_TO_CANCEL_COMPOSITION,
1153
0
  // widget usually sends a eCompositionChange and/or eCompositionCommit event
1154
0
  // to finalize or clear the composition, respectively.  In this time,
1155
0
  // we need to intercept all composition events here and pass the commit
1156
0
  // string for returning to the remote process as a result of
1157
0
  // RequestIMEToCommitComposition().  Then, eCommitComposition event will
1158
0
  // be dispatched with the committed string in the remote process internally.
1159
0
  if (mCommitStringByRequest) {
1160
0
    MOZ_ASSERT(aEvent.mMessage == eCompositionChange ||
1161
0
               aEvent.mMessage == eCompositionCommit);
1162
0
    *mCommitStringByRequest = aEvent.mData;
1163
0
    // We need to wait eCompositionCommitRequestHandled from the remote process
1164
0
    // in this case.  Therefore, mPendingEventsNeedingAck needs to be
1165
0
    // incremented here.  Additionally, we stop sending eCompositionCommit(AsIs)
1166
0
    // event.  Therefore, we need to decrement mPendingCommitCount which has
1167
0
    // been incremented above.
1168
0
    if (!mWidgetHasComposition) {
1169
0
      mPendingEventsNeedingAck++;
1170
0
      MOZ_DIAGNOSTIC_ASSERT(mPendingCommitCount);
1171
0
      if (mPendingCommitCount) {
1172
0
        mPendingCommitCount--;
1173
0
      }
1174
0
    }
1175
0
    return false;
1176
0
  }
1177
0
1178
0
  mPendingEventsNeedingAck++;
1179
0
  return true;
1180
0
}
1181
1182
void
1183
ContentCacheInParent::OnSelectionEvent(
1184
                        const WidgetSelectionEvent& aSelectionEvent)
1185
0
{
1186
0
  MOZ_LOG(sContentCacheLog, LogLevel::Info,
1187
0
    ("0x%p OnSelectionEvent(aEvent={ "
1188
0
     "mMessage=%s, mOffset=%u, mLength=%u, mReversed=%s, "
1189
0
     "mExpandToClusterBoundary=%s, mUseNativeLineBreak=%s }), "
1190
0
     "mPendingEventsNeedingAck=%u, mWidgetHasComposition=%s, "
1191
0
     "mPendingCompositionCount=%" PRIu8 ", mPendingCommitCount=%" PRIu8 ", "
1192
0
     "mIsChildIgnoringCompositionEvents=%s",
1193
0
     this, ToChar(aSelectionEvent.mMessage),
1194
0
     aSelectionEvent.mOffset, aSelectionEvent.mLength,
1195
0
     GetBoolName(aSelectionEvent.mReversed),
1196
0
     GetBoolName(aSelectionEvent.mExpandToClusterBoundary),
1197
0
     GetBoolName(aSelectionEvent.mUseNativeLineBreak), mPendingEventsNeedingAck,
1198
0
     GetBoolName(mWidgetHasComposition), mPendingCompositionCount,
1199
0
     mPendingCommitCount, GetBoolName(mIsChildIgnoringCompositionEvents)));
1200
0
1201
0
#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1202
0
  mDispatchedEventMessages.AppendElement(aSelectionEvent.mMessage);
1203
0
#endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
1204
0
1205
0
  mPendingEventsNeedingAck++;
1206
0
}
1207
1208
void
1209
ContentCacheInParent::OnEventNeedingAckHandled(nsIWidget* aWidget,
1210
                                                EventMessage aMessage)
1211
0
{
1212
0
  // This is called when the child process receives WidgetCompositionEvent or
1213
0
  // WidgetSelectionEvent.
1214
0
1215
0
  MOZ_LOG(sContentCacheLog, LogLevel::Info,
1216
0
    ("0x%p OnEventNeedingAckHandled(aWidget=0x%p, "
1217
0
     "aMessage=%s), mPendingEventsNeedingAck=%u, "
1218
0
     "mWidgetHasComposition=%s, mPendingCompositionCount=%" PRIu8 ", "
1219
0
     "mPendingCommitCount=%" PRIu8 ", mIsChildIgnoringCompositionEvents=%s",
1220
0
     this, aWidget, ToChar(aMessage), mPendingEventsNeedingAck,
1221
0
     GetBoolName(mWidgetHasComposition), mPendingCompositionCount,
1222
0
     mPendingCommitCount, GetBoolName(mIsChildIgnoringCompositionEvents)));
1223
0
1224
0
#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1225
0
  mReceivedEventMessages.AppendElement(aMessage);
1226
0
#endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1227
0
1228
0
  bool isCommittedInChild =
1229
0
    // Commit requester in the remote process has committed the composition.
1230
0
    aMessage == eCompositionCommitRequestHandled ||
1231
0
    // The commit event has been handled normally in the remote process.
1232
0
    (!mIsChildIgnoringCompositionEvents &&
1233
0
     WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage));
1234
0
1235
0
  if (isCommittedInChild) {
1236
0
#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1237
0
    if (mPendingCompositionCount == 1) {
1238
0
      RemoveUnnecessaryEventMessageLog();
1239
0
    }
1240
0
#endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1241
0
1242
0
    if (NS_WARN_IF(!mPendingCompositionCount)) {
1243
0
#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1244
0
      nsPrintfCString info("\nThere is no pending composition but received %s "
1245
0
                           "message from the remote child\n\n",
1246
0
                           ToChar(aMessage));
1247
0
      AppendEventMessageLog(info);
1248
0
      CrashReporter::AppendAppNotesToCrashReport(info);
1249
0
      MOZ_DIAGNOSTIC_ASSERT(false,
1250
0
        "No pending composition but received unexpected commit event");
1251
0
#endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1252
0
1253
0
      // Prevent odd behavior in release channel.
1254
0
      mPendingCompositionCount = 1;
1255
0
    }
1256
0
1257
0
    mPendingCompositionCount--;
1258
0
1259
0
    // Forget composition string only when the latest composition string is
1260
0
    // handled in the remote process because if there is 2 or more pending
1261
0
    // composition, this value shouldn't be referred.
1262
0
    if (!mPendingCompositionCount) {
1263
0
      mCompositionString.Truncate();
1264
0
    }
1265
0
1266
0
    // Forget pending commit string length if it's handled in the remote
1267
0
    // process.  Note that this doesn't care too old composition's commit
1268
0
    // string because in such case, we cannot return proper information
1269
0
    // to IME synchornously.
1270
0
    mPendingCommitLength = 0;
1271
0
  }
1272
0
1273
0
  if (WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage)) {
1274
0
    // After the remote process receives eCompositionCommit(AsIs) event,
1275
0
    // it'll restart to handle composition events.
1276
0
    mIsChildIgnoringCompositionEvents = false;
1277
0
1278
0
    if (NS_WARN_IF(!mPendingCommitCount)) {
1279
0
#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1280
0
      nsPrintfCString info("\nThere is no pending comment events but received "
1281
0
                           "%s message from the remote child\n\n",
1282
0
                           ToChar(aMessage));
1283
0
      AppendEventMessageLog(info);
1284
0
      CrashReporter::AppendAppNotesToCrashReport(info);
1285
0
      MOZ_DIAGNOSTIC_ASSERT(false,
1286
0
        "No pending commit events but received unexpected commit event");
1287
0
#endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1288
0
1289
0
      // Prevent odd behavior in release channel.
1290
0
      mPendingCommitCount = 1;
1291
0
    }
1292
0
1293
0
    mPendingCommitCount--;
1294
0
  } else if (aMessage == eCompositionCommitRequestHandled &&
1295
0
             mPendingCommitCount) {
1296
0
    // If the remote process commits composition synchronously after
1297
0
    // requesting commit composition and we've already sent commit composition,
1298
0
    // it starts to ignore following composition events until receiving
1299
0
    // eCompositionStart event.
1300
0
    mIsChildIgnoringCompositionEvents = true;
1301
0
  }
1302
0
1303
0
  // If neither widget (i.e., IME) nor the remote process has composition,
1304
0
  // now, we can forget composition string informations.
1305
0
  if (!mWidgetHasComposition &&
1306
0
      !mPendingCompositionCount && !mPendingCommitCount) {
1307
0
    mCompositionStart = UINT32_MAX;
1308
0
  }
1309
0
1310
0
  if (NS_WARN_IF(!mPendingEventsNeedingAck)) {
1311
0
#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1312
0
    nsPrintfCString info("\nThere is no pending events but received %s "
1313
0
                         "message from the remote child\n\n",
1314
0
                         ToChar(aMessage));
1315
0
    AppendEventMessageLog(info);
1316
0
    CrashReporter::AppendAppNotesToCrashReport(info);
1317
0
#endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1318
0
    MOZ_DIAGNOSTIC_ASSERT(false,
1319
0
      "No pending event message but received unexpected event");
1320
0
    mPendingEventsNeedingAck = 1;
1321
0
  }
1322
0
  if (--mPendingEventsNeedingAck) {
1323
0
    return;
1324
0
  }
1325
0
1326
0
  FlushPendingNotifications(aWidget);
1327
0
}
1328
1329
bool
1330
ContentCacheInParent::RequestIMEToCommitComposition(nsIWidget* aWidget,
1331
                                                    bool aCancel,
1332
                                                    nsAString& aCommittedString)
1333
0
{
1334
0
  MOZ_LOG(sContentCacheLog, LogLevel::Info,
1335
0
    ("0x%p RequestToCommitComposition(aWidget=%p, "
1336
0
     "aCancel=%s), mPendingCompositionCount=%" PRIu8 ", "
1337
0
     "mPendingCommitCount=%" PRIu8 ", mIsChildIgnoringCompositionEvents=%s, "
1338
0
     "IMEStateManager::DoesTabParentHaveIMEFocus(&mTabParent)=%s, "
1339
0
     "mWidgetHasComposition=%s, mCommitStringByRequest=%p",
1340
0
     this, aWidget, GetBoolName(aCancel), mPendingCompositionCount,
1341
0
     mPendingCommitCount, GetBoolName(mIsChildIgnoringCompositionEvents),
1342
0
     GetBoolName(IMEStateManager::DoesTabParentHaveIMEFocus(&mTabParent)),
1343
0
     GetBoolName(mWidgetHasComposition), mCommitStringByRequest));
1344
0
1345
0
  MOZ_ASSERT(!mCommitStringByRequest);
1346
0
1347
0
  // If there are 2 or more pending compositions, we already sent
1348
0
  // eCompositionCommit(AsIs) to the remote process.  So, this request is
1349
0
  // too late for IME.  The remote process should wait following
1350
0
  // composition events for cleaning up TextComposition and handle the
1351
0
  // request as it's handled asynchronously.
1352
0
  if (mPendingCompositionCount > 1) {
1353
0
#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1354
0
    mRequestIMEToCommitCompositionResults.
1355
0
      AppendElement(RequestIMEToCommitCompositionResult::
1356
0
                      eToOldCompositionReceived);
1357
0
#endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1358
0
    return false;
1359
0
  }
1360
0
1361
0
  // If there is no pending composition, we may have already sent
1362
0
  // eCompositionCommit(AsIs) event for the active composition.  If so, the
1363
0
  // remote process will receive composition events which causes cleaning up
1364
0
  // TextComposition.  So, this shouldn't do nothing and TextComposition
1365
0
  // should handle the request as it's handled asynchronously.
1366
0
  // XXX Perhaps, this is wrong because TextComposition in child process
1367
0
  //     may commit the composition with current composition string in the
1368
0
  //     remote process.  I.e., it may be different from actual commit string
1369
0
  //     which user typed.  So, perhaps, we should return true and the commit
1370
0
  //     string.
1371
0
  if (mPendingCommitCount) {
1372
0
#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1373
0
    mRequestIMEToCommitCompositionResults.
1374
0
      AppendElement(RequestIMEToCommitCompositionResult::
1375
0
                      eToCommittedCompositionReceived);
1376
0
#endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1377
0
    return false;
1378
0
  }
1379
0
1380
0
  // If TabParent which has IME focus was already changed to different one, the
1381
0
  // request shouldn't be sent to IME because it's too late.
1382
0
  if (!IMEStateManager::DoesTabParentHaveIMEFocus(&mTabParent)) {
1383
0
    // Use the latest composition string which may not be handled in the
1384
0
    // remote process for avoiding data loss.
1385
0
#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1386
0
    mRequestIMEToCommitCompositionResults.
1387
0
      AppendElement(RequestIMEToCommitCompositionResult::
1388
0
                      eReceivedAfterTabParentBlur);
1389
0
#endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1390
0
    aCommittedString = mCompositionString;
1391
0
    // After we return true from here, i.e., without actually requesting IME
1392
0
    // to commit composition, we will receive eCompositionCommitRequestHandled
1393
0
    // pseudo event message from the remote process.  So, we need to increment
1394
0
    // mPendingEventsNeedingAck here.
1395
0
    mPendingEventsNeedingAck++;
1396
0
    return true;
1397
0
  }
1398
0
1399
0
  RefPtr<TextComposition> composition =
1400
0
    IMEStateManager::GetTextCompositionFor(aWidget);
1401
0
  if (NS_WARN_IF(!composition)) {
1402
0
    MOZ_LOG(sContentCacheLog, LogLevel::Warning,
1403
0
      ("  0x%p RequestToCommitComposition(), "
1404
0
       "does nothing due to no composition", this));
1405
0
#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1406
0
    mRequestIMEToCommitCompositionResults.
1407
0
      AppendElement(RequestIMEToCommitCompositionResult::
1408
0
                      eReceivedButNoTextComposition);
1409
0
#endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1410
0
    return false;
1411
0
  }
1412
0
1413
0
  mCommitStringByRequest = &aCommittedString;
1414
0
1415
0
  // Request commit or cancel composition with TextComposition because we may
1416
0
  // have already requested to commit or cancel the composition or we may
1417
0
  // have already received eCompositionCommit(AsIs) event.  Those status are
1418
0
  // managed by composition.  So, if we don't request commit composition,
1419
0
  // we should do nothing with native IME here.
1420
0
  composition->RequestToCommit(aWidget, aCancel);
1421
0
1422
0
  mCommitStringByRequest = nullptr;
1423
0
1424
0
  MOZ_LOG(sContentCacheLog, LogLevel::Info,
1425
0
    ("  0x%p RequestToCommitComposition(), "
1426
0
     "mWidgetHasComposition=%s, the composition %s committed synchronously",
1427
0
     this, GetBoolName(mWidgetHasComposition),
1428
0
     composition->Destroyed() ? "WAS" : "has NOT been"));
1429
0
1430
0
  if (!composition->Destroyed()) {
1431
0
    // When the composition isn't committed synchronously, the remote process's
1432
0
    // TextComposition instance will synthesize commit events and wait to
1433
0
    // receive delayed composition events.  When TextComposition instances both
1434
0
    // in this process and the remote process will be destroyed when delayed
1435
0
    // composition events received. TextComposition instance in the parent
1436
0
    // process will dispatch following composition events and be destroyed
1437
0
    // normally. On the other hand, TextComposition instance in the remote
1438
0
    // process won't dispatch following composition events and will be
1439
0
    // destroyed by IMEStateManager::DispatchCompositionEvent().
1440
0
#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1441
0
    mRequestIMEToCommitCompositionResults.
1442
0
      AppendElement(RequestIMEToCommitCompositionResult::
1443
0
                      eHandledAsynchronously);
1444
0
#endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1445
0
    return false;
1446
0
  }
1447
0
1448
0
  // When the composition is committed synchronously, the commit string will be
1449
0
  // returned to the remote process.  Then, PuppetWidget will dispatch
1450
0
  // eCompositionCommit event with the returned commit string (i.e., the value
1451
0
  // is aCommittedString of this method) and that causes destroying
1452
0
  // TextComposition instance in the remote process (Note that TextComposition
1453
0
  // instance in this process was already destroyed).
1454
0
#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1455
0
  mRequestIMEToCommitCompositionResults.
1456
0
    AppendElement(RequestIMEToCommitCompositionResult::eHandledSynchronously);
1457
0
#endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1458
0
  return true;
1459
0
}
1460
1461
void
1462
ContentCacheInParent::MaybeNotifyIME(nsIWidget* aWidget,
1463
                                     const IMENotification& aNotification)
1464
0
{
1465
0
  if (!mPendingEventsNeedingAck) {
1466
0
    IMEStateManager::NotifyIME(aNotification, aWidget, &mTabParent);
1467
0
    return;
1468
0
  }
1469
0
1470
0
  switch (aNotification.mMessage) {
1471
0
    case NOTIFY_IME_OF_SELECTION_CHANGE:
1472
0
      mPendingSelectionChange.MergeWith(aNotification);
1473
0
      break;
1474
0
    case NOTIFY_IME_OF_TEXT_CHANGE:
1475
0
      mPendingTextChange.MergeWith(aNotification);
1476
0
      break;
1477
0
    case NOTIFY_IME_OF_POSITION_CHANGE:
1478
0
      mPendingLayoutChange.MergeWith(aNotification);
1479
0
      break;
1480
0
    case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
1481
0
      mPendingCompositionUpdate.MergeWith(aNotification);
1482
0
      break;
1483
0
    default:
1484
0
      MOZ_CRASH("Unsupported notification");
1485
0
      break;
1486
0
  }
1487
0
}
1488
1489
void
1490
ContentCacheInParent::FlushPendingNotifications(nsIWidget* aWidget)
1491
0
{
1492
0
  MOZ_ASSERT(!mPendingEventsNeedingAck);
1493
0
1494
0
  // If the TabParent's widget has already gone, this can do nothing since
1495
0
  // widget is necessary to notify IME of something.
1496
0
  if (!aWidget) {
1497
0
    return;
1498
0
  }
1499
0
1500
0
  // New notifications which are notified during flushing pending notifications
1501
0
  // should be merged again.
1502
0
  mPendingEventsNeedingAck++;
1503
0
1504
0
  nsCOMPtr<nsIWidget> widget = aWidget;
1505
0
1506
0
  // First, text change notification should be sent because selection change
1507
0
  // notification notifies IME of current selection range in the latest content.
1508
0
  // So, IME may need the latest content before that.
1509
0
  if (mPendingTextChange.HasNotification()) {
1510
0
    IMENotification notification(mPendingTextChange);
1511
0
    if (!widget->Destroyed()) {
1512
0
      mPendingTextChange.Clear();
1513
0
      IMEStateManager::NotifyIME(notification, widget, &mTabParent);
1514
0
    }
1515
0
  }
1516
0
1517
0
  if (mPendingSelectionChange.HasNotification()) {
1518
0
    IMENotification notification(mPendingSelectionChange);
1519
0
    if (!widget->Destroyed()) {
1520
0
      mPendingSelectionChange.Clear();
1521
0
      IMEStateManager::NotifyIME(notification, widget, &mTabParent);
1522
0
    }
1523
0
  }
1524
0
1525
0
  // Layout change notification should be notified after selection change
1526
0
  // notification because IME may want to query position of new caret position.
1527
0
  if (mPendingLayoutChange.HasNotification()) {
1528
0
    IMENotification notification(mPendingLayoutChange);
1529
0
    if (!widget->Destroyed()) {
1530
0
      mPendingLayoutChange.Clear();
1531
0
      IMEStateManager::NotifyIME(notification, widget, &mTabParent);
1532
0
    }
1533
0
  }
1534
0
1535
0
  // Finally, send composition update notification because it notifies IME of
1536
0
  // finishing handling whole sending events.
1537
0
  if (mPendingCompositionUpdate.HasNotification()) {
1538
0
    IMENotification notification(mPendingCompositionUpdate);
1539
0
    if (!widget->Destroyed()) {
1540
0
      mPendingCompositionUpdate.Clear();
1541
0
      IMEStateManager::NotifyIME(notification, widget, &mTabParent);
1542
0
    }
1543
0
  }
1544
0
1545
0
  if (!--mPendingEventsNeedingAck && !widget->Destroyed() &&
1546
0
      (mPendingTextChange.HasNotification() ||
1547
0
       mPendingSelectionChange.HasNotification() ||
1548
0
       mPendingLayoutChange.HasNotification() ||
1549
0
       mPendingCompositionUpdate.HasNotification())) {
1550
0
    FlushPendingNotifications(widget);
1551
0
  }
1552
0
}
1553
1554
#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1555
1556
void
1557
ContentCacheInParent::RemoveUnnecessaryEventMessageLog()
1558
0
{
1559
0
  bool foundLastCompositionStart = false;
1560
0
  for (size_t i = mDispatchedEventMessages.Length(); i > 1; i--) {
1561
0
    if (mDispatchedEventMessages[i - 1] != eCompositionStart) {
1562
0
      continue;
1563
0
    }
1564
0
    if (!foundLastCompositionStart) {
1565
0
      // Find previous eCompositionStart of the latest eCompositionStart.
1566
0
      foundLastCompositionStart = true;
1567
0
      continue;
1568
0
    }
1569
0
    // Remove the messages before the last 2 sets of composition events.
1570
0
    mDispatchedEventMessages.RemoveElementsAt(0, i - 1);
1571
0
    break;
1572
0
  }
1573
0
  uint32_t numberOfCompositionCommitRequestHandled = 0;
1574
0
  foundLastCompositionStart = false;
1575
0
  for (size_t i = mReceivedEventMessages.Length(); i > 1; i--) {
1576
0
    if (mReceivedEventMessages[i - 1] == eCompositionCommitRequestHandled) {
1577
0
      numberOfCompositionCommitRequestHandled++;
1578
0
    }
1579
0
    if (mReceivedEventMessages[i - 1] != eCompositionStart) {
1580
0
      continue;
1581
0
    }
1582
0
    if (!foundLastCompositionStart) {
1583
0
      // Find previous eCompositionStart of the latest eCompositionStart.
1584
0
      foundLastCompositionStart = true;
1585
0
      continue;
1586
0
    }
1587
0
    // Remove the messages before the last 2 sets of composition events.
1588
0
    mReceivedEventMessages.RemoveElementsAt(0, i - 1);
1589
0
    break;
1590
0
  }
1591
0
1592
0
  if (!numberOfCompositionCommitRequestHandled) {
1593
0
    // If there is no eCompositionCommitRequestHandled in
1594
0
    // mReceivedEventMessages, we don't need to store log of
1595
0
    // RequestIMEToCommmitComposition().
1596
0
    mRequestIMEToCommitCompositionResults.Clear();
1597
0
  } else {
1598
0
    // We need to keep all reason of eCompositionCommitRequestHandled, which
1599
0
    // is sent when mRequestIMEToCommitComposition() returns true.
1600
0
    // So, we can discard older log than the first
1601
0
    // eCompositionCommitRequestHandled in mReceivedEventMessages.
1602
0
    for (size_t i = mRequestIMEToCommitCompositionResults.Length();
1603
0
         i > 1; i--) {
1604
0
      if (mRequestIMEToCommitCompositionResults[i - 1] ==
1605
0
            RequestIMEToCommitCompositionResult::eReceivedAfterTabParentBlur ||
1606
0
          mRequestIMEToCommitCompositionResults[i - 1] ==
1607
0
            RequestIMEToCommitCompositionResult::eHandledSynchronously) {
1608
0
        --numberOfCompositionCommitRequestHandled;
1609
0
        if (!numberOfCompositionCommitRequestHandled) {
1610
0
          mRequestIMEToCommitCompositionResults.RemoveElementsAt(0, i - 1);
1611
0
          break;
1612
0
        }
1613
0
      }
1614
0
    }
1615
0
  }
1616
0
}
1617
1618
void
1619
ContentCacheInParent::AppendEventMessageLog(nsACString& aLog) const
1620
0
{
1621
0
  aLog.AppendLiteral("Dispatched Event Message Log:\n");
1622
0
  for (EventMessage message : mDispatchedEventMessages) {
1623
0
    aLog.AppendLiteral("  ");
1624
0
    aLog.Append(ToChar(message));
1625
0
    aLog.AppendLiteral("\n");
1626
0
  }
1627
0
  aLog.AppendLiteral("\nReceived Event Message Log:\n");
1628
0
  for (EventMessage message : mReceivedEventMessages) {
1629
0
    aLog.AppendLiteral("  ");
1630
0
    aLog.Append(ToChar(message));
1631
0
    aLog.AppendLiteral("\n");
1632
0
  }
1633
0
  aLog.AppendLiteral("\nResult of RequestIMEToCommitComposition():\n");
1634
0
  for (RequestIMEToCommitCompositionResult result :
1635
0
         mRequestIMEToCommitCompositionResults) {
1636
0
    aLog.AppendLiteral("  ");
1637
0
    aLog.Append(ToReadableText(result));
1638
0
    aLog.AppendLiteral("\n");
1639
0
  }
1640
0
  aLog.AppendLiteral("\n");
1641
0
}
1642
1643
#endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1644
1645
/*****************************************************************************
1646
 * mozilla::ContentCache::TextRectArray
1647
 *****************************************************************************/
1648
1649
LayoutDeviceIntRect
1650
ContentCache::TextRectArray::GetRect(uint32_t aOffset) const
1651
0
{
1652
0
  LayoutDeviceIntRect rect;
1653
0
  if (InRange(aOffset)) {
1654
0
    rect = mRects[aOffset - mStart];
1655
0
  }
1656
0
  return rect;
1657
0
}
1658
1659
LayoutDeviceIntRect
1660
ContentCache::TextRectArray::GetUnionRect(uint32_t aOffset,
1661
                                          uint32_t aLength) const
1662
0
{
1663
0
  LayoutDeviceIntRect rect;
1664
0
  if (!InRange(aOffset, aLength)) {
1665
0
    return rect;
1666
0
  }
1667
0
  for (uint32_t i = 0; i < aLength; i++) {
1668
0
    rect = rect.Union(mRects[aOffset - mStart + i]);
1669
0
  }
1670
0
  return rect;
1671
0
}
1672
1673
LayoutDeviceIntRect
1674
ContentCache::TextRectArray::GetUnionRectAsFarAsPossible(
1675
                               uint32_t aOffset,
1676
                               uint32_t aLength,
1677
                               bool aRoundToExistingOffset) const
1678
0
{
1679
0
  LayoutDeviceIntRect rect;
1680
0
  if (!HasRects() ||
1681
0
      (!aRoundToExistingOffset && !IsOverlappingWith(aOffset, aLength))) {
1682
0
    return rect;
1683
0
  }
1684
0
  uint32_t startOffset = std::max(aOffset, mStart);
1685
0
  if (aRoundToExistingOffset && startOffset >= EndOffset()) {
1686
0
    startOffset = EndOffset() - 1;
1687
0
  }
1688
0
  uint32_t endOffset = std::min(aOffset + aLength, EndOffset());
1689
0
  if (aRoundToExistingOffset && endOffset < mStart + 1) {
1690
0
    endOffset = mStart + 1;
1691
0
  }
1692
0
  if (NS_WARN_IF(endOffset < startOffset)) {
1693
0
    return rect;
1694
0
  }
1695
0
  for (uint32_t i = 0; i < endOffset - startOffset; i++) {
1696
0
    rect = rect.Union(mRects[startOffset - mStart + i]);
1697
0
  }
1698
0
  return rect;
1699
0
}
1700
1701
} // namespace mozilla