1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
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/. */
8 #include "ContentCache.h"
13 #include "TextEvents.h"
15 #include "mozilla/IMEStateManager.h"
16 #include "mozilla/IntegerPrintfMacros.h"
17 #include "mozilla/Logging.h"
18 #include "mozilla/RefPtr.h"
19 #include "mozilla/TextComposition.h"
20 #include "mozilla/dom/BrowserParent.h"
21 #include "nsExceptionHandler.h"
22 #include "nsIWidget.h"
27 using namespace widget
;
29 static const char* GetBoolName(bool aBool
) { return aBool
? "true" : "false"; }
31 static const char* GetNotificationName(const IMENotification
* aNotification
) {
33 return "Not notification";
35 return ToChar(aNotification
->mMessage
);
38 /*****************************************************************************
39 * mozilla::ContentCache
40 *****************************************************************************/
42 LazyLogModule
sContentCacheLog("ContentCacheWidgets");
44 /*****************************************************************************
45 * mozilla::ContentCacheInChild
46 *****************************************************************************/
48 void ContentCacheInChild::Clear() {
49 MOZ_LOG(sContentCacheLog
, LogLevel::Info
, ("0x%p Clear()", this));
51 mCompositionStart
.reset();
55 mFirstCharRect
.SetEmpty();
57 mTextRectArray
.reset();
58 mLastCommitStringTextRectArray
.reset();
59 mEditorRect
.SetEmpty();
62 void ContentCacheInChild::OnCompositionEvent(
63 const WidgetCompositionEvent
& aCompositionEvent
) {
64 if (aCompositionEvent
.CausesDOMCompositionEndEvent()) {
65 RefPtr
<TextComposition
> composition
=
66 IMEStateManager::GetTextCompositionFor(aCompositionEvent
.mWidget
);
68 nsAutoString lastCommitString
;
69 if (aCompositionEvent
.mMessage
== eCompositionCommitAsIs
) {
70 lastCommitString
= composition
->CommitStringIfCommittedAsIs();
72 lastCommitString
= aCompositionEvent
.mData
;
74 // We don't need to store canceling information because this is required
75 // by undoing of last commit (Kakutei-Undo of Japanese IME).
76 if (!lastCommitString
.IsEmpty()) {
77 mLastCommit
= Some(OffsetAndData
<uint32_t>(
78 composition
->NativeOffsetOfStartComposition(), lastCommitString
));
80 sContentCacheLog
, LogLevel::Debug
,
81 ("0x%p OnCompositionEvent(), stored last composition string data "
82 "(aCompositionEvent={ mMessage=%s, mData=\"%s\"}, mLastCommit=%s)",
83 this, ToChar(aCompositionEvent
.mMessage
),
85 aCompositionEvent
.mData
,
86 PrintStringDetail::kMaxLengthForCompositionString
)
88 ToString(mLastCommit
).c_str()));
93 if (mLastCommit
.isSome()) {
95 sContentCacheLog
, LogLevel::Debug
,
96 ("0x%p OnCompositionEvent(), resetting the last composition string "
97 "data (aCompositionEvent={ mMessage=%s, mData=\"%s\"}, "
99 this, ToChar(aCompositionEvent
.mMessage
),
100 PrintStringDetail(aCompositionEvent
.mData
,
101 PrintStringDetail::kMaxLengthForCompositionString
)
103 ToString(mLastCommit
).c_str()));
108 bool ContentCacheInChild::CacheAll(nsIWidget
* aWidget
,
109 const IMENotification
* aNotification
) {
110 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
111 ("0x%p CacheAll(aWidget=0x%p, aNotification=%s)", this, aWidget
,
112 GetNotificationName(aNotification
)));
114 const bool textCached
= CacheText(aWidget
, aNotification
);
115 const bool editorRectCached
= CacheEditorRect(aWidget
, aNotification
);
116 return textCached
|| editorRectCached
;
119 bool ContentCacheInChild::CacheSelection(nsIWidget
* aWidget
,
120 const IMENotification
* aNotification
) {
121 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
122 ("0x%p CacheSelection(aWidget=0x%p, aNotification=%s)", this, aWidget
,
123 GetNotificationName(aNotification
)));
127 nsEventStatus status
= nsEventStatus_eIgnore
;
128 WidgetQueryContentEvent
querySelectedTextEvent(true, eQuerySelectedText
,
130 aWidget
->DispatchEvent(&querySelectedTextEvent
, status
);
131 if (NS_WARN_IF(querySelectedTextEvent
.Failed())) {
133 sContentCacheLog
, LogLevel::Error
,
134 ("0x%p CacheSelection(), FAILED, couldn't retrieve the selected text",
137 mSelection
.emplace(querySelectedTextEvent
);
140 const bool caretCached
= CacheCaret(aWidget
, aNotification
);
141 const bool textRectsCached
= CacheTextRects(aWidget
, aNotification
);
142 return caretCached
|| textRectsCached
|| querySelectedTextEvent
.Succeeded();
145 bool ContentCacheInChild::CacheCaret(nsIWidget
* aWidget
,
146 const IMENotification
* aNotification
) {
149 if (MOZ_UNLIKELY(mSelection
.isNothing())) {
153 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
154 ("0x%p CacheCaret(aWidget=0x%p, aNotification=%s)", this, aWidget
,
155 GetNotificationName(aNotification
)));
157 if (mSelection
->mHasRange
) {
158 // XXX Should be mSelection.mFocus?
159 const uint32_t offset
= mSelection
->StartOffset();
161 nsEventStatus status
= nsEventStatus_eIgnore
;
162 WidgetQueryContentEvent
queryCaretRectEvet(true, eQueryCaretRect
, aWidget
);
163 queryCaretRectEvet
.InitForQueryCaretRect(offset
);
164 aWidget
->DispatchEvent(&queryCaretRectEvet
, status
);
165 if (NS_WARN_IF(queryCaretRectEvet
.Failed())) {
166 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
167 ("0x%p CacheCaret(), FAILED, couldn't retrieve the caret rect "
172 mCaret
.emplace(offset
, queryCaretRectEvet
.mReply
->mRect
);
174 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
175 ("0x%p CacheCaret(), Succeeded, mSelection=%s, mCaret=%s", this,
176 ToString(mSelection
).c_str(), ToString(mCaret
).c_str()));
180 bool ContentCacheInChild::CacheEditorRect(
181 nsIWidget
* aWidget
, const IMENotification
* aNotification
) {
182 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
183 ("0x%p CacheEditorRect(aWidget=0x%p, aNotification=%s)", this,
184 aWidget
, GetNotificationName(aNotification
)));
186 nsEventStatus status
= nsEventStatus_eIgnore
;
187 WidgetQueryContentEvent
queryEditorRectEvent(true, eQueryEditorRect
, aWidget
);
188 aWidget
->DispatchEvent(&queryEditorRectEvent
, status
);
189 if (NS_WARN_IF(queryEditorRectEvent
.Failed())) {
191 sContentCacheLog
, LogLevel::Error
,
192 ("0x%p CacheEditorRect(), FAILED, couldn't retrieve the editor rect",
196 mEditorRect
= queryEditorRectEvent
.mReply
->mRect
;
197 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
198 ("0x%p CacheEditorRect(), Succeeded, mEditorRect=%s", this,
199 ToString(mEditorRect
).c_str()));
203 bool ContentCacheInChild::CacheText(nsIWidget
* aWidget
,
204 const IMENotification
* aNotification
) {
205 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
206 ("0x%p CacheText(aWidget=0x%p, aNotification=%s)", this, aWidget
,
207 GetNotificationName(aNotification
)));
209 nsEventStatus status
= nsEventStatus_eIgnore
;
210 WidgetQueryContentEvent
queryTextContentEvent(true, eQueryTextContent
,
212 queryTextContentEvent
.InitForQueryTextContent(0, UINT32_MAX
);
213 aWidget
->DispatchEvent(&queryTextContentEvent
, status
);
214 if (NS_WARN_IF(queryTextContentEvent
.Failed())) {
215 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
216 ("0x%p CacheText(), FAILED, couldn't retrieve whole text", this));
219 mText
= Some(nsString(queryTextContentEvent
.mReply
->DataRef()));
220 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
221 ("0x%p CacheText(), Succeeded, mText=%s", this,
222 PrintStringDetail(mText
, PrintStringDetail::kMaxLengthForEditor
)
226 // Forget last commit range if string in the range is different from the
227 // last commit string.
228 if (mLastCommit
.isSome() &&
229 (mText
.isNothing() ||
230 nsDependentSubstring(mText
.ref(), mLastCommit
->StartOffset(),
231 mLastCommit
->Length()) != mLastCommit
->DataRef())) {
232 MOZ_LOG(sContentCacheLog
, LogLevel::Debug
,
233 ("0x%p CacheText(), resetting the last composition string data "
234 "(mLastCommit=%s, current string=\"%s\")",
235 this, ToString(mLastCommit
).c_str(),
237 nsDependentSubstring(mText
.ref(), mLastCommit
->StartOffset(),
238 mLastCommit
->Length()),
239 PrintStringDetail::kMaxLengthForCompositionString
)
244 const bool selectionCached
= CacheSelection(aWidget
, aNotification
);
245 return selectionCached
|| queryTextContentEvent
.Succeeded();
248 bool ContentCacheInChild::QueryCharRect(nsIWidget
* aWidget
, uint32_t aOffset
,
249 LayoutDeviceIntRect
& aCharRect
) const {
250 aCharRect
.SetEmpty();
252 nsEventStatus status
= nsEventStatus_eIgnore
;
253 WidgetQueryContentEvent
queryTextRectEvent(true, eQueryTextRect
, aWidget
);
254 queryTextRectEvent
.InitForQueryTextRect(aOffset
, 1);
255 aWidget
->DispatchEvent(&queryTextRectEvent
, status
);
256 if (NS_WARN_IF(queryTextRectEvent
.Failed())) {
259 aCharRect
= queryTextRectEvent
.mReply
->mRect
;
261 // Guarantee the rect is not empty.
262 if (NS_WARN_IF(!aCharRect
.Height())) {
263 aCharRect
.SetHeight(1);
265 if (NS_WARN_IF(!aCharRect
.Width())) {
266 aCharRect
.SetWidth(1);
271 bool ContentCacheInChild::QueryCharRectArray(nsIWidget
* aWidget
,
272 uint32_t aOffset
, uint32_t aLength
,
273 RectArray
& aCharRectArray
) const {
274 nsEventStatus status
= nsEventStatus_eIgnore
;
275 WidgetQueryContentEvent
queryTextRectsEvent(true, eQueryTextRectArray
,
277 queryTextRectsEvent
.InitForQueryTextRectArray(aOffset
, aLength
);
278 aWidget
->DispatchEvent(&queryTextRectsEvent
, status
);
279 if (NS_WARN_IF(queryTextRectsEvent
.Failed())) {
280 aCharRectArray
.Clear();
283 aCharRectArray
= std::move(queryTextRectsEvent
.mReply
->mRectArray
);
287 bool ContentCacheInChild::CacheTextRects(nsIWidget
* aWidget
,
288 const IMENotification
* aNotification
) {
290 sContentCacheLog
, LogLevel::Info
,
291 ("0x%p CacheTextRects(aWidget=0x%p, aNotification=%s), mCaret=%s", this,
292 aWidget
, GetNotificationName(aNotification
), ToString(mCaret
).c_str()));
294 if (mSelection
.isSome()) {
295 mSelection
->ClearRects();
298 // Retrieve text rects in composition string if there is.
299 RefPtr
<TextComposition
> textComposition
=
300 IMEStateManager::GetTextCompositionFor(aWidget
);
301 if (textComposition
) {
302 // mCompositionStart may be updated by some composition event handlers.
303 // So, let's update it with the latest information.
304 mCompositionStart
= Some(textComposition
->NativeOffsetOfStartComposition());
305 // Note that TextComposition::String() may not be modified here because
306 // it's modified after all edit action listeners are performed but this
307 // is called while some of them are performed.
308 // FYI: For supporting IME which commits composition and restart new
309 // composition immediately, we should cache next character of current
311 uint32_t length
= textComposition
->LastData().Length() + 1;
312 mTextRectArray
= Some(TextRectArray(mCompositionStart
.value()));
313 if (NS_WARN_IF(!QueryCharRectArray(aWidget
, mTextRectArray
->mStart
, length
,
314 mTextRectArray
->mRects
))) {
315 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
316 ("0x%p CacheTextRects(), FAILED, "
317 "couldn't retrieve text rect array of the composition string",
319 mTextRectArray
.reset();
322 mCompositionStart
.reset();
323 mTextRectArray
.reset();
326 // Set mSelection->mAnchorCharRects
327 // If we've already have the rect in mTextRectArray, save the query cost.
328 if (mSelection
.isSome() && mSelection
->mHasRange
&& mTextRectArray
.isSome() &&
329 mTextRectArray
->IsOffsetInRange(mSelection
->mAnchor
) &&
330 (!mSelection
->mAnchor
||
331 mTextRectArray
->IsOffsetInRange(mSelection
->mAnchor
- 1))) {
332 mSelection
->mAnchorCharRects
[eNextCharRect
] =
333 mTextRectArray
->GetRect(mSelection
->mAnchor
);
334 if (mSelection
->mAnchor
) {
335 mSelection
->mAnchorCharRects
[ePrevCharRect
] =
336 mTextRectArray
->GetRect(mSelection
->mAnchor
- 1);
339 // Otherwise, get it from content even if there is no selection ranges.
342 const uint32_t startOffset
=
343 mSelection
.isSome() && mSelection
->mHasRange
&& mSelection
->mAnchor
344 ? mSelection
->mAnchor
- 1u
346 const uint32_t length
=
347 mSelection
.isSome() && mSelection
->mHasRange
&& mSelection
->mAnchor
350 if (NS_WARN_IF(!QueryCharRectArray(aWidget
, startOffset
, length
, rects
))) {
352 sContentCacheLog
, LogLevel::Error
,
353 ("0x%p CacheTextRects(), FAILED, "
354 "couldn't retrieve text rect array around the selection anchor (%u)",
355 this, mSelection
->mAnchor
));
356 MOZ_ASSERT_IF(mSelection
.isSome(),
357 mSelection
->mAnchorCharRects
[ePrevCharRect
].IsEmpty());
358 MOZ_ASSERT_IF(mSelection
.isSome(),
359 mSelection
->mAnchorCharRects
[eNextCharRect
].IsEmpty());
360 } else if (rects
.Length()) {
361 if (mSelection
.isNothing()) {
362 mSelection
.emplace(); // With no range
364 if (rects
.Length() > 1) {
365 mSelection
->mAnchorCharRects
[ePrevCharRect
] = rects
[0];
366 mSelection
->mAnchorCharRects
[eNextCharRect
] = rects
[1];
368 mSelection
->mAnchorCharRects
[eNextCharRect
] = rects
[0];
369 MOZ_ASSERT(mSelection
->mAnchorCharRects
[ePrevCharRect
].IsEmpty());
374 // Note that if mSelection is Nothing here, we've already failed to get
375 // rects in the `else` block above. In such case, we cannot get character
376 // rects around focus point.
377 if (mSelection
.isSome()) {
378 // Set mSelection->mFocusCharRects
379 // If selection is collapsed (including no selection case), the focus char
380 // rects are same as the anchor char rects so that we can just copy them.
381 if (mSelection
->IsCollapsed()) {
382 mSelection
->mFocusCharRects
[0] = mSelection
->mAnchorCharRects
[0];
383 mSelection
->mFocusCharRects
[1] = mSelection
->mAnchorCharRects
[1];
385 // If the selection range is in mTextRectArray, save the query cost.
386 else if (mTextRectArray
.isSome() &&
387 mTextRectArray
->IsOffsetInRange(mSelection
->mFocus
) &&
388 (!mSelection
->mFocus
||
389 mTextRectArray
->IsOffsetInRange(mSelection
->mFocus
- 1))) {
390 MOZ_ASSERT(mSelection
->mHasRange
);
391 mSelection
->mFocusCharRects
[eNextCharRect
] =
392 mTextRectArray
->GetRect(mSelection
->mFocus
);
393 if (mSelection
->mFocus
) {
394 mSelection
->mFocusCharRects
[ePrevCharRect
] =
395 mTextRectArray
->GetRect(mSelection
->mFocus
- 1);
398 // Otherwise, including no selection range cases, need to query the rects.
400 MOZ_ASSERT(mSelection
->mHasRange
);
402 const uint32_t startOffset
=
403 mSelection
->mFocus
? mSelection
->mFocus
- 1u : 0u;
404 const uint32_t length
= mSelection
->mFocus
? 2u : 1u;
406 !QueryCharRectArray(aWidget
, startOffset
, length
, rects
))) {
407 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
408 ("0x%p CacheTextRects(), FAILED, "
409 "couldn't retrieve text rect array around the selection focus "
411 this, mSelection
->mFocus
));
412 MOZ_ASSERT(mSelection
->mFocusCharRects
[ePrevCharRect
].IsEmpty());
413 MOZ_ASSERT(mSelection
->mFocusCharRects
[eNextCharRect
].IsEmpty());
415 if (rects
.Length() > 1) {
416 mSelection
->mFocusCharRects
[ePrevCharRect
] = rects
[0];
417 mSelection
->mFocusCharRects
[eNextCharRect
] = rects
[1];
418 } else if (rects
.Length()) {
419 mSelection
->mFocusCharRects
[eNextCharRect
] = rects
[0];
420 MOZ_ASSERT(mSelection
->mFocusCharRects
[ePrevCharRect
].IsEmpty());
426 // If there is a non-collapsed selection range, let's query the whole selected
427 // text rect. Note that the result cannot be computed from first character
428 // rect and last character rect of the selection because they both may be in
429 // middle of different line.
430 if (mSelection
.isSome() && mSelection
->mHasRange
&&
431 !mSelection
->IsCollapsed()) {
432 nsEventStatus status
= nsEventStatus_eIgnore
;
433 WidgetQueryContentEvent
queryTextRectEvent(true, eQueryTextRect
, aWidget
);
434 queryTextRectEvent
.InitForQueryTextRect(mSelection
->StartOffset(),
435 mSelection
->Length());
436 aWidget
->DispatchEvent(&queryTextRectEvent
, status
);
437 if (NS_WARN_IF(queryTextRectEvent
.Failed())) {
438 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
439 ("0x%p CacheTextRects(), FAILED, "
440 "couldn't retrieve text rect of whole selected text",
443 mSelection
->mRect
= queryTextRectEvent
.mReply
->mRect
;
447 // Even if there is no selection range, we should have the first character
448 // rect for the last resort of suggesting position of IME UI.
449 if (mSelection
.isSome() && mSelection
->mHasRange
&& !mSelection
->mFocus
) {
450 mFirstCharRect
= mSelection
->mFocusCharRects
[eNextCharRect
];
451 } else if (mSelection
.isSome() && mSelection
->mHasRange
&&
452 mSelection
->mFocus
== 1) {
453 mFirstCharRect
= mSelection
->mFocusCharRects
[ePrevCharRect
];
454 } else if (mSelection
.isSome() && mSelection
->mHasRange
&&
455 !mSelection
->mAnchor
) {
456 mFirstCharRect
= mSelection
->mAnchorCharRects
[eNextCharRect
];
457 } else if (mSelection
.isSome() && mSelection
->mHasRange
&&
458 mSelection
->mAnchor
== 1) {
459 mFirstCharRect
= mSelection
->mFocusCharRects
[ePrevCharRect
];
460 } else if (mTextRectArray
.isSome() && mTextRectArray
->IsOffsetInRange(0u)) {
461 mFirstCharRect
= mTextRectArray
->GetRect(0u);
463 LayoutDeviceIntRect charRect
;
464 if (MOZ_UNLIKELY(NS_WARN_IF(!QueryCharRect(aWidget
, 0, charRect
)))) {
465 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
466 ("0x%p CacheTextRects(), FAILED, "
467 "couldn't retrieve first char rect",
469 mFirstCharRect
.SetEmpty();
471 mFirstCharRect
= charRect
;
475 // Finally, let's cache the last commit string's character rects until
476 // selection change or something other editing because user may reconvert
477 // or undo the last commit. Then, IME requires the character rects for
478 // positioning their UI.
479 if (mLastCommit
.isSome()) {
480 mLastCommitStringTextRectArray
=
481 Some(TextRectArray(mLastCommit
->StartOffset()));
482 if (mLastCommit
->Length() == 1 && mSelection
.isSome() &&
483 mSelection
->mHasRange
&&
484 mSelection
->mAnchor
- 1 == mLastCommit
->StartOffset() &&
485 !mSelection
->mAnchorCharRects
[ePrevCharRect
].IsEmpty()) {
486 mLastCommitStringTextRectArray
->mRects
.AppendElement(
487 mSelection
->mAnchorCharRects
[ePrevCharRect
]);
488 } else if (NS_WARN_IF(!QueryCharRectArray(
489 aWidget
, mLastCommit
->StartOffset(), mLastCommit
->Length(),
490 mLastCommitStringTextRectArray
->mRects
))) {
491 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
492 ("0x%p CacheTextRects(), FAILED, "
493 "couldn't retrieve text rect array of the last commit string",
495 mLastCommitStringTextRectArray
.reset();
498 MOZ_ASSERT((mLastCommitStringTextRectArray
.isSome()
499 ? mLastCommitStringTextRectArray
->mRects
.Length()
500 : 0) == (mLastCommit
.isSome() ? mLastCommit
->Length() : 0));
502 mLastCommitStringTextRectArray
.reset();
506 sContentCacheLog
, LogLevel::Info
,
507 ("0x%p CacheTextRects(), Succeeded, "
508 "mText=%s, mTextRectArray=%s, mSelection=%s, "
509 "mFirstCharRect=%s, mLastCommitStringTextRectArray=%s",
511 PrintStringDetail(mText
, PrintStringDetail::kMaxLengthForEditor
).get(),
512 ToString(mTextRectArray
).c_str(), ToString(mSelection
).c_str(),
513 ToString(mFirstCharRect
).c_str(),
514 ToString(mLastCommitStringTextRectArray
).c_str()));
518 void ContentCacheInChild::SetSelection(
520 const IMENotification::SelectionChangeDataBase
& aSelectionChangeData
) {
522 sContentCacheLog
, LogLevel::Info
,
523 ("0x%p SetSelection(aSelectionChangeData=%s), mText=%s", this,
524 ToString(aSelectionChangeData
).c_str(),
525 PrintStringDetail(mText
, PrintStringDetail::kMaxLengthForEditor
).get()));
527 mSelection
= Some(Selection(aSelectionChangeData
));
529 if (mLastCommit
.isSome()) {
530 // Forget last commit string range if selection is not collapsed
531 // at end of the last commit string.
532 if (!mSelection
->mHasRange
|| !mSelection
->IsCollapsed() ||
533 mSelection
->mAnchor
!= mLastCommit
->EndOffset()) {
535 sContentCacheLog
, LogLevel::Debug
,
536 ("0x%p SetSelection(), forgetting last commit composition data "
537 "(mSelection=%s, mLastCommit=%s)",
538 this, ToString(mSelection
).c_str(), ToString(mLastCommit
).c_str()));
544 CacheTextRects(aWidget
);
547 /*****************************************************************************
548 * mozilla::ContentCacheInParent
549 *****************************************************************************/
551 ContentCacheInParent::ContentCacheInParent(BrowserParent
& aBrowserParent
)
553 mBrowserParent(aBrowserParent
),
554 mCommitStringByRequest(nullptr),
555 mPendingEventsNeedingAck(0),
556 mPendingCommitLength(0),
557 mPendingCompositionCount(0),
558 mPendingCommitCount(0),
559 mWidgetHasComposition(false),
560 mIsChildIgnoringCompositionEvents(false) {}
562 void ContentCacheInParent::AssignContent(const ContentCache
& aOther
,
564 const IMENotification
* aNotification
) {
565 mText
= aOther
.mText
;
566 mSelection
= aOther
.mSelection
;
567 mFirstCharRect
= aOther
.mFirstCharRect
;
568 mCaret
= aOther
.mCaret
;
569 mTextRectArray
= aOther
.mTextRectArray
;
570 mLastCommitStringTextRectArray
= aOther
.mLastCommitStringTextRectArray
;
571 mEditorRect
= aOther
.mEditorRect
;
573 // Only when there is one composition, the TextComposition instance in this
574 // process is managing the composition in the remote process. Therefore,
575 // we shouldn't update composition start offset of TextComposition with
576 // old composition which is still being handled by the child process.
577 if (mWidgetHasComposition
&& mPendingCompositionCount
== 1 &&
578 mCompositionStart
.isSome()) {
579 IMEStateManager::MaybeStartOffsetUpdatedInChild(aWidget
,
580 mCompositionStart
.value());
583 // When this instance allows to query content relative to composition string,
584 // we should modify mCompositionStart with the latest information in the
585 // remote process because now we have the information around the composition
587 mCompositionStartInChild
= aOther
.mCompositionStart
;
588 if (mWidgetHasComposition
|| mPendingCommitCount
) {
589 if (mCompositionStartInChild
.isSome()) {
590 if (mCompositionStart
.valueOr(UINT32_MAX
) !=
591 mCompositionStartInChild
.value()) {
592 mCompositionStart
= mCompositionStartInChild
;
593 mPendingCommitLength
= 0;
595 } else if (mCompositionStart
.isSome() && mSelection
.isSome() &&
596 mSelection
->mHasRange
&&
597 mCompositionStart
.value() != mSelection
->StartOffset()) {
598 mCompositionStart
= Some(mSelection
->StartOffset());
599 mPendingCommitLength
= 0;
604 sContentCacheLog
, LogLevel::Info
,
605 ("0x%p AssignContent(aNotification=%s), "
606 "Succeeded, mText=%s, mSelection=%s, mFirstCharRect=%s, "
607 "mCaret=%s, mTextRectArray=%s, mWidgetHasComposition=%s, "
608 "mPendingCompositionCount=%u, mCompositionStart=%s, "
609 "mPendingCommitLength=%u, mEditorRect=%s, "
610 "mLastCommitStringTextRectArray=%s",
611 this, GetNotificationName(aNotification
),
612 PrintStringDetail(mText
, PrintStringDetail::kMaxLengthForEditor
).get(),
613 ToString(mSelection
).c_str(), ToString(mFirstCharRect
).c_str(),
614 ToString(mCaret
).c_str(), ToString(mTextRectArray
).c_str(),
615 GetBoolName(mWidgetHasComposition
), mPendingCompositionCount
,
616 ToString(mCompositionStart
).c_str(), mPendingCommitLength
,
617 ToString(mEditorRect
).c_str(),
618 ToString(mLastCommitStringTextRectArray
).c_str()));
621 bool ContentCacheInParent::HandleQueryContentEvent(
622 WidgetQueryContentEvent
& aEvent
, nsIWidget
* aWidget
) const {
625 // ContentCache doesn't store offset of its start with XP linebreaks.
626 // So, we don't support to query contents relative to composition start
627 // offset with XP linebreaks.
628 if (NS_WARN_IF(!aEvent
.mUseNativeLineBreak
)) {
629 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
630 ("0x%p HandleQueryContentEvent(), FAILED due to query with XP "
636 if (NS_WARN_IF(!aEvent
.mInput
.IsValidOffset())) {
638 sContentCacheLog
, LogLevel::Error
,
639 ("0x%p HandleQueryContentEvent(), FAILED due to invalid offset", this));
643 if (NS_WARN_IF(!aEvent
.mInput
.IsValidEventMessage(aEvent
.mMessage
))) {
645 sContentCacheLog
, LogLevel::Error
,
646 ("0x%p HandleQueryContentEvent(), FAILED due to invalid event message",
651 bool isRelativeToInsertionPoint
= aEvent
.mInput
.mRelativeToInsertionPoint
;
652 if (isRelativeToInsertionPoint
) {
653 MOZ_LOG(sContentCacheLog
, LogLevel::Debug
,
654 ("0x%p HandleQueryContentEvent(), "
655 "making offset absolute... aEvent={ mMessage=%s, mInput={ "
656 "mOffset=%" PRId64
", mLength=%" PRIu32
" } }, "
657 "mWidgetHasComposition=%s, mPendingCommitCount=%" PRIu8
658 ", mCompositionStart=%" PRIu32
", "
659 "mPendingCommitLength=%" PRIu32
", mSelection=%s",
660 this, ToChar(aEvent
.mMessage
), aEvent
.mInput
.mOffset
,
661 aEvent
.mInput
.mLength
, GetBoolName(mWidgetHasComposition
),
662 mPendingCommitCount
, mCompositionStart
.valueOr(UINT32_MAX
),
663 mPendingCommitLength
, ToString(mSelection
).c_str()));
664 if (mWidgetHasComposition
|| mPendingCommitCount
) {
665 if (NS_WARN_IF(mCompositionStart
.isNothing()) ||
666 NS_WARN_IF(!aEvent
.mInput
.MakeOffsetAbsolute(
667 mCompositionStart
.value() + mPendingCommitLength
))) {
669 sContentCacheLog
, LogLevel::Error
,
670 ("0x%p HandleQueryContentEvent(), FAILED due to "
671 "aEvent.mInput.MakeOffsetAbsolute(mCompositionStart + "
672 "mPendingCommitLength) failure, "
673 "mCompositionStart=%" PRIu32
", mPendingCommitLength=%" PRIu32
", "
674 "aEvent={ mMessage=%s, mInput={ mOffset=%" PRId64
675 ", mLength=%" PRIu32
" } }",
676 this, mCompositionStart
.valueOr(UINT32_MAX
), mPendingCommitLength
,
677 ToChar(aEvent
.mMessage
), aEvent
.mInput
.mOffset
,
678 aEvent
.mInput
.mLength
));
681 } else if (NS_WARN_IF(mSelection
.isNothing())) {
682 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
683 ("0x%p HandleQueryContentEvent(), FAILED due to mSelection is "
687 } else if (NS_WARN_IF(mSelection
->mHasRange
)) {
688 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
689 ("0x%p HandleQueryContentEvent(), FAILED due to there is no "
690 "selection range, but the query requested with relative offset "
694 } else if (NS_WARN_IF(!aEvent
.mInput
.MakeOffsetAbsolute(
695 mSelection
->StartOffset() + mPendingCommitLength
))) {
696 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
697 ("0x%p HandleQueryContentEvent(), FAILED due to "
698 "aEvent.mInput.MakeOffsetAbsolute(mSelection->StartOffset() + "
699 "mPendingCommitLength) failure, mSelection=%s, "
700 "mPendingCommitLength=%" PRIu32
", aEvent={ mMessage=%s, "
701 "mInput={ mOffset=%" PRId64
", mLength=%" PRIu32
" } }",
702 this, ToString(mSelection
).c_str(), mPendingCommitLength
,
703 ToChar(aEvent
.mMessage
), aEvent
.mInput
.mOffset
,
704 aEvent
.mInput
.mLength
));
709 switch (aEvent
.mMessage
) {
710 case eQuerySelectedText
:
711 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
712 ("0x%p HandleQueryContentEvent(aEvent={ "
713 "mMessage=eQuerySelectedText }, aWidget=0x%p)",
715 if (MOZ_UNLIKELY(NS_WARN_IF(mSelection
.isNothing()))) {
716 // If content cache hasn't been initialized properly, make the query
718 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
719 ("0x%p HandleQueryContentEvent(), FAILED because mSelection "
724 MOZ_DIAGNOSTIC_ASSERT_IF(!mSelection
->IsCollapsed(), mText
.isSome());
725 MOZ_DIAGNOSTIC_ASSERT_IF(!mSelection
->IsCollapsed(),
726 mSelection
->EndOffset() <= mText
->Length());
727 aEvent
.EmplaceReply();
728 aEvent
.mReply
->mFocusedWidget
= aWidget
;
729 if (mSelection
->mHasRange
) {
730 if (MOZ_LIKELY(mText
.isSome())) {
731 aEvent
.mReply
->mOffsetAndData
.emplace(
732 mSelection
->StartOffset(),
733 Substring(mText
.ref(), mSelection
->StartOffset(),
734 mSelection
->Length()),
735 OffsetAndDataFor::SelectedString
);
737 // TODO: Investigate this case. I find this during
738 // test_mousecapture.xhtml on Linux.
739 aEvent
.mReply
->mOffsetAndData
.emplace(
740 0u, EmptyString(), OffsetAndDataFor::SelectedString
);
743 aEvent
.mReply
->mWritingMode
= mSelection
->mWritingMode
;
744 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
745 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
746 "mMessage=eQuerySelectedText, mReply=%s }",
747 this, ToString(aEvent
.mReply
).c_str()));
749 case eQueryTextContent
: {
750 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
751 ("0x%p HandleQueryContentEvent(aEvent={ "
752 "mMessage=eQueryTextContent, mInput={ mOffset=%" PRId64
753 ", mLength=%u } }, aWidget=0x%p), mText->Length()=%zu",
754 this, aEvent
.mInput
.mOffset
, aEvent
.mInput
.mLength
, aWidget
,
755 mText
.isSome() ? mText
->Length() : 0u));
756 if (MOZ_UNLIKELY(NS_WARN_IF(mText
.isNothing()))) {
757 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
758 ("0x%p HandleQueryContentEvent(), FAILED because "
759 "there is no text data",
763 const uint32_t inputOffset
= aEvent
.mInput
.mOffset
;
764 const uint32_t inputEndOffset
= std::min
<uint32_t>(
765 aEvent
.mInput
.EndOffset(), mText
.isSome() ? mText
->Length() : 0u);
766 if (MOZ_UNLIKELY(NS_WARN_IF(inputEndOffset
< inputOffset
))) {
767 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
768 ("0x%p HandleQueryContentEvent(), FAILED because "
769 "inputOffset=%u is larger than inputEndOffset=%u",
770 this, inputOffset
, inputEndOffset
));
773 aEvent
.EmplaceReply();
774 aEvent
.mReply
->mFocusedWidget
= aWidget
;
775 const nsAString
& textInQueriedRange
=
776 inputEndOffset
> inputOffset
777 ? static_cast<const nsAString
&>(Substring(
778 mText
.ref(), inputOffset
, inputEndOffset
- inputOffset
))
779 : static_cast<const nsAString
&>(EmptyString());
780 aEvent
.mReply
->mOffsetAndData
.emplace(inputOffset
, textInQueriedRange
,
781 OffsetAndDataFor::EditorString
);
782 // TODO: Support font ranges
783 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
784 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
785 "mMessage=eQueryTextContent, mReply=%s }",
786 this, ToString(aEvent
.mReply
).c_str()));
789 case eQueryTextRect
: {
790 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
791 ("0x%p HandleQueryContentEvent("
792 "aEvent={ mMessage=eQueryTextRect, mInput={ mOffset=%" PRId64
793 ", mLength=%u } }, aWidget=0x%p), mText->Length()=%zu",
794 this, aEvent
.mInput
.mOffset
, aEvent
.mInput
.mLength
, aWidget
,
795 mText
.isSome() ? mText
->Length() : 0u));
796 // Note that if the query is relative to insertion point, the query was
797 // probably requested by native IME. In such case, we should return
798 // non-empty rect since returning failure causes IME showing its window
800 LayoutDeviceIntRect textRect
;
801 if (aEvent
.mInput
.mLength
) {
802 if (MOZ_UNLIKELY(NS_WARN_IF(
803 !GetUnionTextRects(aEvent
.mInput
.mOffset
, aEvent
.mInput
.mLength
,
804 isRelativeToInsertionPoint
, textRect
)))) {
805 // XXX We don't have cache for this request.
806 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
807 ("0x%p HandleQueryContentEvent(), FAILED to get union rect",
812 // If the length is 0, we should return caret rect instead.
813 if (NS_WARN_IF(!GetCaretRect(aEvent
.mInput
.mOffset
,
814 isRelativeToInsertionPoint
, textRect
))) {
815 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
816 ("0x%p HandleQueryContentEvent(), FAILED to get caret rect",
821 aEvent
.EmplaceReply();
822 aEvent
.mReply
->mFocusedWidget
= aWidget
;
823 aEvent
.mReply
->mRect
= textRect
;
824 const nsAString
& textInQueriedRange
=
825 mText
.isSome() && aEvent
.mInput
.mOffset
<
826 static_cast<int64_t>(
827 mText
.isSome() ? mText
->Length() : 0u)
828 ? static_cast<const nsAString
&>(
829 Substring(mText
.ref(), aEvent
.mInput
.mOffset
,
830 mText
->Length() >= aEvent
.mInput
.EndOffset()
831 ? aEvent
.mInput
.mLength
833 : static_cast<const nsAString
&>(EmptyString());
834 aEvent
.mReply
->mOffsetAndData
.emplace(aEvent
.mInput
.mOffset
,
836 OffsetAndDataFor::EditorString
);
837 // XXX This may be wrong if storing range isn't in the selection range.
838 aEvent
.mReply
->mWritingMode
= mSelection
->mWritingMode
;
839 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
840 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
841 "mMessage=eQueryTextRect mReply=%s }",
842 this, ToString(aEvent
.mReply
).c_str()));
845 case eQueryCaretRect
: {
847 sContentCacheLog
, LogLevel::Info
,
848 ("0x%p HandleQueryContentEvent(aEvent={ mMessage=eQueryCaretRect, "
849 "mInput={ mOffset=%" PRId64
850 " } }, aWidget=0x%p), mText->Length()=%zu",
851 this, aEvent
.mInput
.mOffset
, aWidget
,
852 mText
.isSome() ? mText
->Length() : 0u));
853 // Note that if the query is relative to insertion point, the query was
854 // probably requested by native IME. In such case, we should return
855 // non-empty rect since returning failure causes IME showing its window
857 LayoutDeviceIntRect caretRect
;
858 if (NS_WARN_IF(!GetCaretRect(aEvent
.mInput
.mOffset
,
859 isRelativeToInsertionPoint
, caretRect
))) {
860 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
861 ("0x%p HandleQueryContentEvent(),FAILED to get caret rect",
865 aEvent
.EmplaceReply();
866 aEvent
.mReply
->mFocusedWidget
= aWidget
;
867 aEvent
.mReply
->mRect
= caretRect
;
868 aEvent
.mReply
->mOffsetAndData
.emplace(aEvent
.mInput
.mOffset
,
870 OffsetAndDataFor::SelectedString
);
871 // TODO: Set mWritingMode here
872 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
873 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
874 "mMessage=eQueryCaretRect, mReply=%s }",
875 this, ToString(aEvent
.mReply
).c_str()));
878 case eQueryEditorRect
:
879 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
880 ("0x%p HandleQueryContentEvent(aEvent={ "
881 "mMessage=eQueryEditorRect }, aWidget=0x%p)",
883 aEvent
.EmplaceReply();
884 aEvent
.mReply
->mFocusedWidget
= aWidget
;
885 aEvent
.mReply
->mRect
= mEditorRect
;
886 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
887 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
888 "mMessage=eQueryEditorRect, mReply=%s }",
889 this, ToString(aEvent
.mReply
).c_str()));
892 aEvent
.EmplaceReply();
893 aEvent
.mReply
->mFocusedWidget
= aWidget
;
894 if (NS_WARN_IF(aEvent
.Failed())) {
896 sContentCacheLog
, LogLevel::Error
,
897 ("0x%p HandleQueryContentEvent(), FAILED due to not set enough "
898 "data, aEvent={ mMessage=%s, mReply=%s }",
899 this, ToChar(aEvent
.mMessage
), ToString(aEvent
.mReply
).c_str()));
902 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
903 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
904 "mMessage=%s, mReply=%s }",
905 this, ToChar(aEvent
.mMessage
), ToString(aEvent
.mReply
).c_str()));
910 bool ContentCacheInParent::GetTextRect(uint32_t aOffset
,
911 bool aRoundToExistingOffset
,
912 LayoutDeviceIntRect
& aTextRect
) const {
914 sContentCacheLog
, LogLevel::Info
,
915 ("0x%p GetTextRect(aOffset=%u, aRoundToExistingOffset=%s), "
916 "mTextRectArray=%s, mSelection=%s, mLastCommitStringTextRectArray=%s",
917 this, aOffset
, GetBoolName(aRoundToExistingOffset
),
918 ToString(mTextRectArray
).c_str(), ToString(mSelection
).c_str(),
919 ToString(mLastCommitStringTextRectArray
).c_str()));
922 NS_WARNING_ASSERTION(!mFirstCharRect
.IsEmpty(), "empty rect");
923 aTextRect
= mFirstCharRect
;
924 return !aTextRect
.IsEmpty();
926 if (mSelection
.isSome() && mSelection
->mHasRange
) {
927 if (aOffset
== mSelection
->mAnchor
) {
928 NS_WARNING_ASSERTION(
929 !mSelection
->mAnchorCharRects
[eNextCharRect
].IsEmpty(), "empty rect");
930 aTextRect
= mSelection
->mAnchorCharRects
[eNextCharRect
];
931 return !aTextRect
.IsEmpty();
933 if (mSelection
->mAnchor
&& aOffset
== mSelection
->mAnchor
- 1) {
934 NS_WARNING_ASSERTION(
935 !mSelection
->mAnchorCharRects
[ePrevCharRect
].IsEmpty(), "empty rect");
936 aTextRect
= mSelection
->mAnchorCharRects
[ePrevCharRect
];
937 return !aTextRect
.IsEmpty();
939 if (aOffset
== mSelection
->mFocus
) {
940 NS_WARNING_ASSERTION(
941 !mSelection
->mFocusCharRects
[eNextCharRect
].IsEmpty(), "empty rect");
942 aTextRect
= mSelection
->mFocusCharRects
[eNextCharRect
];
943 return !aTextRect
.IsEmpty();
945 if (mSelection
->mFocus
&& aOffset
== mSelection
->mFocus
- 1) {
946 NS_WARNING_ASSERTION(
947 !mSelection
->mFocusCharRects
[ePrevCharRect
].IsEmpty(), "empty rect");
948 aTextRect
= mSelection
->mFocusCharRects
[ePrevCharRect
];
949 return !aTextRect
.IsEmpty();
953 if (mTextRectArray
.isSome() && mTextRectArray
->IsOffsetInRange(aOffset
)) {
954 aTextRect
= mTextRectArray
->GetRect(aOffset
);
955 return !aTextRect
.IsEmpty();
958 if (mLastCommitStringTextRectArray
.isSome() &&
959 mLastCommitStringTextRectArray
->IsOffsetInRange(aOffset
)) {
960 aTextRect
= mLastCommitStringTextRectArray
->GetRect(aOffset
);
961 return !aTextRect
.IsEmpty();
964 if (!aRoundToExistingOffset
) {
965 aTextRect
.SetEmpty();
969 if (mTextRectArray
.isNothing() || !mTextRectArray
->HasRects()) {
970 // If there are no rects in mTextRectArray, we should refer the start of
971 // the selection because IME must query a char rect around it if there is
973 aTextRect
= mSelection
->StartCharRect();
974 return !aTextRect
.IsEmpty();
977 // Although we may have mLastCommitStringTextRectArray here and it must have
978 // previous character rects at selection. However, we should stop using it
979 // because it's stored really short time after commiting a composition.
980 // So, multiple query may return different rect and it may cause flickerling
982 uint32_t offset
= aOffset
;
983 if (offset
< mTextRectArray
->StartOffset()) {
984 offset
= mTextRectArray
->StartOffset();
986 offset
= mTextRectArray
->EndOffset() - 1;
988 aTextRect
= mTextRectArray
->GetRect(offset
);
989 return !aTextRect
.IsEmpty();
992 bool ContentCacheInParent::GetUnionTextRects(
993 uint32_t aOffset
, uint32_t aLength
, bool aRoundToExistingOffset
,
994 LayoutDeviceIntRect
& aUnionTextRect
) const {
995 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
996 ("0x%p GetUnionTextRects(aOffset=%u, "
997 "aLength=%u, aRoundToExistingOffset=%s), mTextRectArray=%s, "
998 "mSelection=%s, mLastCommitStringTextRectArray=%s",
999 this, aOffset
, aLength
, GetBoolName(aRoundToExistingOffset
),
1000 ToString(mTextRectArray
).c_str(), ToString(mSelection
).c_str(),
1001 ToString(mLastCommitStringTextRectArray
).c_str()));
1003 CheckedInt
<uint32_t> endOffset
= CheckedInt
<uint32_t>(aOffset
) + aLength
;
1004 if (!endOffset
.isValid()) {
1008 if (mSelection
.isSome() && !mSelection
->IsCollapsed() &&
1009 aOffset
== mSelection
->StartOffset() && aLength
== mSelection
->Length()) {
1010 NS_WARNING_ASSERTION(!mSelection
->mRect
.IsEmpty(), "empty rect");
1011 aUnionTextRect
= mSelection
->mRect
;
1012 return !aUnionTextRect
.IsEmpty();
1017 NS_WARNING_ASSERTION(!mFirstCharRect
.IsEmpty(), "empty rect");
1018 aUnionTextRect
= mFirstCharRect
;
1019 return !aUnionTextRect
.IsEmpty();
1021 if (mSelection
.isSome() && mSelection
->mHasRange
) {
1022 if (aOffset
== mSelection
->mAnchor
) {
1023 NS_WARNING_ASSERTION(
1024 !mSelection
->mAnchorCharRects
[eNextCharRect
].IsEmpty(),
1026 aUnionTextRect
= mSelection
->mAnchorCharRects
[eNextCharRect
];
1027 return !aUnionTextRect
.IsEmpty();
1029 if (mSelection
->mAnchor
&& aOffset
== mSelection
->mAnchor
- 1) {
1030 NS_WARNING_ASSERTION(
1031 !mSelection
->mAnchorCharRects
[ePrevCharRect
].IsEmpty(),
1033 aUnionTextRect
= mSelection
->mAnchorCharRects
[ePrevCharRect
];
1034 return !aUnionTextRect
.IsEmpty();
1036 if (aOffset
== mSelection
->mFocus
) {
1037 NS_WARNING_ASSERTION(
1038 !mSelection
->mFocusCharRects
[eNextCharRect
].IsEmpty(),
1040 aUnionTextRect
= mSelection
->mFocusCharRects
[eNextCharRect
];
1041 return !aUnionTextRect
.IsEmpty();
1043 if (mSelection
->mFocus
&& aOffset
== mSelection
->mFocus
- 1) {
1044 NS_WARNING_ASSERTION(
1045 !mSelection
->mFocusCharRects
[ePrevCharRect
].IsEmpty(),
1047 aUnionTextRect
= mSelection
->mFocusCharRects
[ePrevCharRect
];
1048 return !aUnionTextRect
.IsEmpty();
1053 // Even if some text rects are not cached of the queried range,
1054 // we should return union rect when the first character's rect is cached
1055 // since the first character rect is important and the others are not so
1058 if (!aOffset
&& mSelection
.isSome() && mSelection
->mHasRange
&&
1059 aOffset
!= mSelection
->mAnchor
&& aOffset
!= mSelection
->mFocus
&&
1060 (mTextRectArray
.isNothing() ||
1061 !mTextRectArray
->IsOffsetInRange(aOffset
)) &&
1062 (mLastCommitStringTextRectArray
.isNothing() ||
1063 !mLastCommitStringTextRectArray
->IsOffsetInRange(aOffset
))) {
1064 // The first character rect isn't cached.
1068 // Use mLastCommitStringTextRectArray only when it overlaps with aOffset
1069 // even if aROundToExistingOffset is true for avoiding flickerling IME UI.
1070 // See the last comment in GetTextRect() for the detail.
1071 if (mLastCommitStringTextRectArray
.isSome() &&
1072 mLastCommitStringTextRectArray
->IsOverlappingWith(aOffset
, aLength
)) {
1074 mLastCommitStringTextRectArray
->GetUnionRectAsFarAsPossible(
1075 aOffset
, aLength
, aRoundToExistingOffset
);
1077 aUnionTextRect
.SetEmpty();
1080 if (mTextRectArray
.isSome() &&
1081 ((aRoundToExistingOffset
&& mTextRectArray
->HasRects()) ||
1082 mTextRectArray
->IsOverlappingWith(aOffset
, aLength
))) {
1084 aUnionTextRect
.Union(mTextRectArray
->GetUnionRectAsFarAsPossible(
1085 aOffset
, aLength
, aRoundToExistingOffset
));
1089 aUnionTextRect
= aUnionTextRect
.Union(mFirstCharRect
);
1091 if (mSelection
.isSome() && mSelection
->mHasRange
) {
1092 if (aOffset
<= mSelection
->mAnchor
&&
1093 mSelection
->mAnchor
< endOffset
.value()) {
1095 aUnionTextRect
.Union(mSelection
->mAnchorCharRects
[eNextCharRect
]);
1097 if (mSelection
->mAnchor
&& aOffset
<= mSelection
->mAnchor
- 1 &&
1098 mSelection
->mAnchor
- 1 < endOffset
.value()) {
1100 aUnionTextRect
.Union(mSelection
->mAnchorCharRects
[ePrevCharRect
]);
1102 if (aOffset
<= mSelection
->mFocus
&&
1103 mSelection
->mFocus
< endOffset
.value()) {
1105 aUnionTextRect
.Union(mSelection
->mFocusCharRects
[eNextCharRect
]);
1107 if (mSelection
->mFocus
&& aOffset
<= mSelection
->mFocus
- 1 &&
1108 mSelection
->mFocus
- 1 < endOffset
.value()) {
1110 aUnionTextRect
.Union(mSelection
->mFocusCharRects
[ePrevCharRect
]);
1114 return !aUnionTextRect
.IsEmpty();
1117 bool ContentCacheInParent::GetCaretRect(uint32_t aOffset
,
1118 bool aRoundToExistingOffset
,
1119 LayoutDeviceIntRect
& aCaretRect
) const {
1120 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
1121 ("0x%p GetCaretRect(aOffset=%u, aRoundToExistingOffset=%s), "
1122 "mCaret=%s, mTextRectArray=%s, mSelection=%s, mFirstCharRect=%s",
1123 this, aOffset
, GetBoolName(aRoundToExistingOffset
),
1124 ToString(mCaret
).c_str(), ToString(mTextRectArray
).c_str(),
1125 ToString(mSelection
).c_str(), ToString(mFirstCharRect
).c_str()));
1127 if (mCaret
.isSome() && mCaret
->mOffset
== aOffset
) {
1128 aCaretRect
= mCaret
->mRect
;
1132 // Guess caret rect from the text rect if it's stored.
1133 if (!GetTextRect(aOffset
, aRoundToExistingOffset
, aCaretRect
)) {
1134 // There might be previous character rect in the cache. If so, we can
1135 // guess the caret rect with it.
1137 !GetTextRect(aOffset
- 1, aRoundToExistingOffset
, aCaretRect
)) {
1138 aCaretRect
.SetEmpty();
1142 if (mSelection
.isSome() && mSelection
->mWritingMode
.IsVertical()) {
1143 aCaretRect
.MoveToY(aCaretRect
.YMost());
1145 // XXX bidi-unaware.
1146 aCaretRect
.MoveToX(aCaretRect
.XMost());
1150 // XXX This is not bidi aware because we don't cache each character's
1151 // direction. However, this is usually used by IME, so, assuming the
1152 // character is in LRT context must not cause any problem.
1153 if (mSelection
.isSome() && mSelection
->mWritingMode
.IsVertical()) {
1154 aCaretRect
.SetHeight(mCaret
.isSome() ? mCaret
->mRect
.Height() : 1);
1156 aCaretRect
.SetWidth(mCaret
.isSome() ? mCaret
->mRect
.Width() : 1);
1161 bool ContentCacheInParent::OnCompositionEvent(
1162 const WidgetCompositionEvent
& aEvent
) {
1164 sContentCacheLog
, LogLevel::Info
,
1165 ("0x%p OnCompositionEvent(aEvent={ "
1166 "mMessage=%s, mData=\"%s\", mRanges->Length()=%zu }), "
1167 "mPendingEventsNeedingAck=%u, mWidgetHasComposition=%s, "
1168 "mPendingCompositionCount=%" PRIu8
", mPendingCommitCount=%" PRIu8
", "
1169 "mIsChildIgnoringCompositionEvents=%s, mCommitStringByRequest=0x%p",
1170 this, ToChar(aEvent
.mMessage
),
1171 PrintStringDetail(aEvent
.mData
,
1172 PrintStringDetail::kMaxLengthForCompositionString
)
1174 aEvent
.mRanges
? aEvent
.mRanges
->Length() : 0, mPendingEventsNeedingAck
,
1175 GetBoolName(mWidgetHasComposition
), mPendingCompositionCount
,
1176 mPendingCommitCount
, GetBoolName(mIsChildIgnoringCompositionEvents
),
1177 mCommitStringByRequest
));
1179 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1180 mDispatchedEventMessages
.AppendElement(aEvent
.mMessage
);
1181 #endif // #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1183 // We must be able to simulate the selection because
1184 // we might not receive selection updates in time
1185 if (!mWidgetHasComposition
) {
1186 if (mCompositionStartInChild
.isSome()) {
1187 // If there is pending composition in the remote process, let's use
1188 // its start offset temporarily because this stores a lot of information
1189 // around it and the user must look around there, so, showing some UI
1190 // around it must make sense.
1191 mCompositionStart
= mCompositionStartInChild
;
1193 mCompositionStart
= Some(mSelection
.isSome() && mSelection
->mHasRange
1194 ? mSelection
->StartOffset()
1197 MOZ_ASSERT(aEvent
.mMessage
== eCompositionStart
);
1198 MOZ_RELEASE_ASSERT(mPendingCompositionCount
< UINT8_MAX
);
1199 mPendingCompositionCount
++;
1202 mWidgetHasComposition
= !aEvent
.CausesDOMCompositionEndEvent();
1204 if (!mWidgetHasComposition
) {
1205 // mCompositionStart will be reset when commit event is completely handled
1206 // in the remote process.
1207 if (mPendingCompositionCount
== 1) {
1208 mPendingCommitLength
= aEvent
.mData
.Length();
1210 mPendingCommitCount
++;
1211 } else if (aEvent
.mMessage
!= eCompositionStart
) {
1212 mCompositionString
= aEvent
.mData
;
1215 // During REQUEST_TO_COMMIT_COMPOSITION or REQUEST_TO_CANCEL_COMPOSITION,
1216 // widget usually sends a eCompositionChange and/or eCompositionCommit event
1217 // to finalize or clear the composition, respectively. In this time,
1218 // we need to intercept all composition events here and pass the commit
1219 // string for returning to the remote process as a result of
1220 // RequestIMEToCommitComposition(). Then, eCommitComposition event will
1221 // be dispatched with the committed string in the remote process internally.
1222 if (mCommitStringByRequest
) {
1223 if (aEvent
.mMessage
== eCompositionCommitAsIs
) {
1224 *mCommitStringByRequest
= mCompositionString
;
1226 MOZ_ASSERT(aEvent
.mMessage
== eCompositionChange
||
1227 aEvent
.mMessage
== eCompositionCommit
);
1228 *mCommitStringByRequest
= aEvent
.mData
;
1230 // We need to wait eCompositionCommitRequestHandled from the remote process
1231 // in this case. Therefore, mPendingEventsNeedingAck needs to be
1232 // incremented here. Additionally, we stop sending eCompositionCommit(AsIs)
1233 // event. Therefore, we need to decrement mPendingCommitCount which has
1234 // been incremented above.
1235 if (!mWidgetHasComposition
) {
1236 mPendingEventsNeedingAck
++;
1237 MOZ_DIAGNOSTIC_ASSERT(mPendingCommitCount
);
1238 if (mPendingCommitCount
) {
1239 mPendingCommitCount
--;
1245 mPendingEventsNeedingAck
++;
1249 void ContentCacheInParent::OnSelectionEvent(
1250 const WidgetSelectionEvent
& aSelectionEvent
) {
1252 sContentCacheLog
, LogLevel::Info
,
1253 ("0x%p OnSelectionEvent(aEvent={ "
1254 "mMessage=%s, mOffset=%u, mLength=%u, mReversed=%s, "
1255 "mExpandToClusterBoundary=%s, mUseNativeLineBreak=%s }), "
1256 "mPendingEventsNeedingAck=%u, mWidgetHasComposition=%s, "
1257 "mPendingCompositionCount=%" PRIu8
", mPendingCommitCount=%" PRIu8
", "
1258 "mIsChildIgnoringCompositionEvents=%s",
1259 this, ToChar(aSelectionEvent
.mMessage
), aSelectionEvent
.mOffset
,
1260 aSelectionEvent
.mLength
, GetBoolName(aSelectionEvent
.mReversed
),
1261 GetBoolName(aSelectionEvent
.mExpandToClusterBoundary
),
1262 GetBoolName(aSelectionEvent
.mUseNativeLineBreak
),
1263 mPendingEventsNeedingAck
, GetBoolName(mWidgetHasComposition
),
1264 mPendingCompositionCount
, mPendingCommitCount
,
1265 GetBoolName(mIsChildIgnoringCompositionEvents
)));
1267 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1268 mDispatchedEventMessages
.AppendElement(aSelectionEvent
.mMessage
);
1269 #endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
1271 mPendingEventsNeedingAck
++;
1274 void ContentCacheInParent::OnEventNeedingAckHandled(nsIWidget
* aWidget
,
1275 EventMessage aMessage
) {
1276 // This is called when the child process receives WidgetCompositionEvent or
1277 // WidgetSelectionEvent.
1280 sContentCacheLog
, LogLevel::Info
,
1281 ("0x%p OnEventNeedingAckHandled(aWidget=0x%p, "
1282 "aMessage=%s), mPendingEventsNeedingAck=%u, "
1283 "mWidgetHasComposition=%s, mPendingCompositionCount=%" PRIu8
", "
1284 "mPendingCommitCount=%" PRIu8
", mIsChildIgnoringCompositionEvents=%s",
1285 this, aWidget
, ToChar(aMessage
), mPendingEventsNeedingAck
,
1286 GetBoolName(mWidgetHasComposition
), mPendingCompositionCount
,
1287 mPendingCommitCount
, GetBoolName(mIsChildIgnoringCompositionEvents
)));
1289 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1290 mReceivedEventMessages
.AppendElement(aMessage
);
1291 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1293 bool isCommittedInChild
=
1294 // Commit requester in the remote process has committed the composition.
1295 aMessage
== eCompositionCommitRequestHandled
||
1296 // The commit event has been handled normally in the remote process.
1297 (!mIsChildIgnoringCompositionEvents
&&
1298 WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage
));
1300 if (isCommittedInChild
) {
1301 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1302 if (mPendingCompositionCount
== 1) {
1303 RemoveUnnecessaryEventMessageLog();
1305 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1307 if (NS_WARN_IF(!mPendingCompositionCount
)) {
1308 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1309 nsPrintfCString
info(
1310 "\nThere is no pending composition but received %s "
1311 "message from the remote child\n\n",
1313 AppendEventMessageLog(info
);
1314 CrashReporter::AppendAppNotesToCrashReport(info
);
1315 MOZ_DIAGNOSTIC_ASSERT(
1316 false, "No pending composition but received unexpected commit event");
1317 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1319 // Prevent odd behavior in release channel.
1320 mPendingCompositionCount
= 1;
1323 mPendingCompositionCount
--;
1325 // Forget composition string only when the latest composition string is
1326 // handled in the remote process because if there is 2 or more pending
1327 // composition, this value shouldn't be referred.
1328 if (!mPendingCompositionCount
) {
1329 mCompositionString
.Truncate();
1332 // Forget pending commit string length if it's handled in the remote
1333 // process. Note that this doesn't care too old composition's commit
1334 // string because in such case, we cannot return proper information
1335 // to IME synchornously.
1336 mPendingCommitLength
= 0;
1339 if (WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage
)) {
1340 // After the remote process receives eCompositionCommit(AsIs) event,
1341 // it'll restart to handle composition events.
1342 mIsChildIgnoringCompositionEvents
= false;
1344 if (NS_WARN_IF(!mPendingCommitCount
)) {
1345 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1346 nsPrintfCString
info(
1347 "\nThere is no pending comment events but received "
1348 "%s message from the remote child\n\n",
1350 AppendEventMessageLog(info
);
1351 CrashReporter::AppendAppNotesToCrashReport(info
);
1352 MOZ_DIAGNOSTIC_ASSERT(
1354 "No pending commit events but received unexpected commit event");
1355 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1357 // Prevent odd behavior in release channel.
1358 mPendingCommitCount
= 1;
1361 mPendingCommitCount
--;
1362 } else if (aMessage
== eCompositionCommitRequestHandled
&&
1363 mPendingCommitCount
) {
1364 // If the remote process commits composition synchronously after
1365 // requesting commit composition and we've already sent commit composition,
1366 // it starts to ignore following composition events until receiving
1367 // eCompositionStart event.
1368 mIsChildIgnoringCompositionEvents
= true;
1371 // If neither widget (i.e., IME) nor the remote process has composition,
1372 // now, we can forget composition string informations.
1373 if (!mWidgetHasComposition
&& !mPendingCompositionCount
&&
1374 !mPendingCommitCount
) {
1375 mCompositionStart
.reset();
1378 if (NS_WARN_IF(!mPendingEventsNeedingAck
)) {
1379 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1380 nsPrintfCString
info(
1381 "\nThere is no pending events but received %s "
1382 "message from the remote child\n\n",
1384 AppendEventMessageLog(info
);
1385 CrashReporter::AppendAppNotesToCrashReport(info
);
1386 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1387 MOZ_DIAGNOSTIC_ASSERT(
1388 false, "No pending event message but received unexpected event");
1389 mPendingEventsNeedingAck
= 1;
1391 if (--mPendingEventsNeedingAck
) {
1395 FlushPendingNotifications(aWidget
);
1398 bool ContentCacheInParent::RequestIMEToCommitComposition(
1399 nsIWidget
* aWidget
, bool aCancel
, nsAString
& aCommittedString
) {
1401 sContentCacheLog
, LogLevel::Info
,
1402 ("0x%p RequestToCommitComposition(aWidget=%p, "
1403 "aCancel=%s), mPendingCompositionCount=%" PRIu8
", "
1404 "mPendingCommitCount=%" PRIu8
", mIsChildIgnoringCompositionEvents=%s, "
1405 "IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)=%s, "
1406 "mWidgetHasComposition=%s, mCommitStringByRequest=%p",
1407 this, aWidget
, GetBoolName(aCancel
), mPendingCompositionCount
,
1408 mPendingCommitCount
, GetBoolName(mIsChildIgnoringCompositionEvents
),
1410 IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent
)),
1411 GetBoolName(mWidgetHasComposition
), mCommitStringByRequest
));
1413 MOZ_ASSERT(!mCommitStringByRequest
);
1415 // If there are 2 or more pending compositions, we already sent
1416 // eCompositionCommit(AsIs) to the remote process. So, this request is
1417 // too late for IME. The remote process should wait following
1418 // composition events for cleaning up TextComposition and handle the
1419 // request as it's handled asynchronously.
1420 if (mPendingCompositionCount
> 1) {
1421 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1422 mRequestIMEToCommitCompositionResults
.AppendElement(
1423 RequestIMEToCommitCompositionResult::eToOldCompositionReceived
);
1424 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1428 // If there is no pending composition, we may have already sent
1429 // eCompositionCommit(AsIs) event for the active composition. If so, the
1430 // remote process will receive composition events which causes cleaning up
1431 // TextComposition. So, this shouldn't do nothing and TextComposition
1432 // should handle the request as it's handled asynchronously.
1433 // XXX Perhaps, this is wrong because TextComposition in child process
1434 // may commit the composition with current composition string in the
1435 // remote process. I.e., it may be different from actual commit string
1436 // which user typed. So, perhaps, we should return true and the commit
1438 if (mPendingCommitCount
) {
1439 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1440 mRequestIMEToCommitCompositionResults
.AppendElement(
1441 RequestIMEToCommitCompositionResult::eToCommittedCompositionReceived
);
1442 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1446 // If BrowserParent which has IME focus was already changed to different one,
1447 // the request shouldn't be sent to IME because it's too late.
1448 if (!IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent
)) {
1449 // Use the latest composition string which may not be handled in the
1450 // remote process for avoiding data loss.
1451 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1452 mRequestIMEToCommitCompositionResults
.AppendElement(
1453 RequestIMEToCommitCompositionResult::eReceivedAfterBrowserParentBlur
);
1454 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1455 aCommittedString
= mCompositionString
;
1456 // After we return true from here, i.e., without actually requesting IME
1457 // to commit composition, we will receive eCompositionCommitRequestHandled
1458 // pseudo event message from the remote process. So, we need to increment
1459 // mPendingEventsNeedingAck here.
1460 mPendingEventsNeedingAck
++;
1464 RefPtr
<TextComposition
> composition
=
1465 IMEStateManager::GetTextCompositionFor(aWidget
);
1466 if (NS_WARN_IF(!composition
)) {
1467 MOZ_LOG(sContentCacheLog
, LogLevel::Warning
,
1468 (" 0x%p RequestToCommitComposition(), "
1469 "does nothing due to no composition",
1471 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1472 mRequestIMEToCommitCompositionResults
.AppendElement(
1473 RequestIMEToCommitCompositionResult::eReceivedButNoTextComposition
);
1474 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1478 mCommitStringByRequest
= &aCommittedString
;
1480 // Request commit or cancel composition with TextComposition because we may
1481 // have already requested to commit or cancel the composition or we may
1482 // have already received eCompositionCommit(AsIs) event. Those status are
1483 // managed by composition. So, if we don't request commit composition,
1484 // we should do nothing with native IME here.
1485 composition
->RequestToCommit(aWidget
, aCancel
);
1487 mCommitStringByRequest
= nullptr;
1490 sContentCacheLog
, LogLevel::Info
,
1491 (" 0x%p RequestToCommitComposition(), "
1492 "mWidgetHasComposition=%s, the composition %s committed synchronously",
1493 this, GetBoolName(mWidgetHasComposition
),
1494 composition
->Destroyed() ? "WAS" : "has NOT been"));
1496 if (!composition
->Destroyed()) {
1497 // When the composition isn't committed synchronously, the remote process's
1498 // TextComposition instance will synthesize commit events and wait to
1499 // receive delayed composition events. When TextComposition instances both
1500 // in this process and the remote process will be destroyed when delayed
1501 // composition events received. TextComposition instance in the parent
1502 // process will dispatch following composition events and be destroyed
1503 // normally. On the other hand, TextComposition instance in the remote
1504 // process won't dispatch following composition events and will be
1505 // destroyed by IMEStateManager::DispatchCompositionEvent().
1506 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1507 mRequestIMEToCommitCompositionResults
.AppendElement(
1508 RequestIMEToCommitCompositionResult::eHandledAsynchronously
);
1509 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1513 // When the composition is committed synchronously, the commit string will be
1514 // returned to the remote process. Then, PuppetWidget will dispatch
1515 // eCompositionCommit event with the returned commit string (i.e., the value
1516 // is aCommittedString of this method) and that causes destroying
1517 // TextComposition instance in the remote process (Note that TextComposition
1518 // instance in this process was already destroyed).
1519 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1520 mRequestIMEToCommitCompositionResults
.AppendElement(
1521 RequestIMEToCommitCompositionResult::eHandledSynchronously
);
1522 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1526 void ContentCacheInParent::MaybeNotifyIME(
1527 nsIWidget
* aWidget
, const IMENotification
& aNotification
) {
1528 if (!mPendingEventsNeedingAck
) {
1529 IMEStateManager::NotifyIME(aNotification
, aWidget
, &mBrowserParent
);
1533 switch (aNotification
.mMessage
) {
1534 case NOTIFY_IME_OF_SELECTION_CHANGE
:
1535 mPendingSelectionChange
.MergeWith(aNotification
);
1537 case NOTIFY_IME_OF_TEXT_CHANGE
:
1538 mPendingTextChange
.MergeWith(aNotification
);
1540 case NOTIFY_IME_OF_POSITION_CHANGE
:
1541 mPendingLayoutChange
.MergeWith(aNotification
);
1543 case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED
:
1544 mPendingCompositionUpdate
.MergeWith(aNotification
);
1547 MOZ_CRASH("Unsupported notification");
1552 void ContentCacheInParent::FlushPendingNotifications(nsIWidget
* aWidget
) {
1553 MOZ_ASSERT(!mPendingEventsNeedingAck
);
1555 // If the BrowserParent's widget has already gone, this can do nothing since
1556 // widget is necessary to notify IME of something.
1561 // New notifications which are notified during flushing pending notifications
1562 // should be merged again.
1563 mPendingEventsNeedingAck
++;
1565 nsCOMPtr
<nsIWidget
> widget
= aWidget
;
1567 // First, text change notification should be sent because selection change
1568 // notification notifies IME of current selection range in the latest content.
1569 // So, IME may need the latest content before that.
1570 if (mPendingTextChange
.HasNotification()) {
1571 IMENotification
notification(mPendingTextChange
);
1572 if (!widget
->Destroyed()) {
1573 mPendingTextChange
.Clear();
1574 IMEStateManager::NotifyIME(notification
, widget
, &mBrowserParent
);
1578 if (mPendingSelectionChange
.HasNotification()) {
1579 IMENotification
notification(mPendingSelectionChange
);
1580 if (!widget
->Destroyed()) {
1581 mPendingSelectionChange
.Clear();
1582 IMEStateManager::NotifyIME(notification
, widget
, &mBrowserParent
);
1586 // Layout change notification should be notified after selection change
1587 // notification because IME may want to query position of new caret position.
1588 if (mPendingLayoutChange
.HasNotification()) {
1589 IMENotification
notification(mPendingLayoutChange
);
1590 if (!widget
->Destroyed()) {
1591 mPendingLayoutChange
.Clear();
1592 IMEStateManager::NotifyIME(notification
, widget
, &mBrowserParent
);
1596 // Finally, send composition update notification because it notifies IME of
1597 // finishing handling whole sending events.
1598 if (mPendingCompositionUpdate
.HasNotification()) {
1599 IMENotification
notification(mPendingCompositionUpdate
);
1600 if (!widget
->Destroyed()) {
1601 mPendingCompositionUpdate
.Clear();
1602 IMEStateManager::NotifyIME(notification
, widget
, &mBrowserParent
);
1606 if (!--mPendingEventsNeedingAck
&& !widget
->Destroyed() &&
1607 (mPendingTextChange
.HasNotification() ||
1608 mPendingSelectionChange
.HasNotification() ||
1609 mPendingLayoutChange
.HasNotification() ||
1610 mPendingCompositionUpdate
.HasNotification())) {
1611 FlushPendingNotifications(widget
);
1615 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1617 void ContentCacheInParent::RemoveUnnecessaryEventMessageLog() {
1618 bool foundLastCompositionStart
= false;
1619 for (size_t i
= mDispatchedEventMessages
.Length(); i
> 1; i
--) {
1620 if (mDispatchedEventMessages
[i
- 1] != eCompositionStart
) {
1623 if (!foundLastCompositionStart
) {
1624 // Find previous eCompositionStart of the latest eCompositionStart.
1625 foundLastCompositionStart
= true;
1628 // Remove the messages before the last 2 sets of composition events.
1629 mDispatchedEventMessages
.RemoveElementsAt(0, i
- 1);
1632 uint32_t numberOfCompositionCommitRequestHandled
= 0;
1633 foundLastCompositionStart
= false;
1634 for (size_t i
= mReceivedEventMessages
.Length(); i
> 1; i
--) {
1635 if (mReceivedEventMessages
[i
- 1] == eCompositionCommitRequestHandled
) {
1636 numberOfCompositionCommitRequestHandled
++;
1638 if (mReceivedEventMessages
[i
- 1] != eCompositionStart
) {
1641 if (!foundLastCompositionStart
) {
1642 // Find previous eCompositionStart of the latest eCompositionStart.
1643 foundLastCompositionStart
= true;
1646 // Remove the messages before the last 2 sets of composition events.
1647 mReceivedEventMessages
.RemoveElementsAt(0, i
- 1);
1651 if (!numberOfCompositionCommitRequestHandled
) {
1652 // If there is no eCompositionCommitRequestHandled in
1653 // mReceivedEventMessages, we don't need to store log of
1654 // RequestIMEToCommmitComposition().
1655 mRequestIMEToCommitCompositionResults
.Clear();
1657 // We need to keep all reason of eCompositionCommitRequestHandled, which
1658 // is sent when mRequestIMEToCommitComposition() returns true.
1659 // So, we can discard older log than the first
1660 // eCompositionCommitRequestHandled in mReceivedEventMessages.
1661 for (size_t i
= mRequestIMEToCommitCompositionResults
.Length(); i
> 1;
1663 if (mRequestIMEToCommitCompositionResults
[i
- 1] ==
1664 RequestIMEToCommitCompositionResult::
1665 eReceivedAfterBrowserParentBlur
||
1666 mRequestIMEToCommitCompositionResults
[i
- 1] ==
1667 RequestIMEToCommitCompositionResult::eHandledSynchronously
) {
1668 --numberOfCompositionCommitRequestHandled
;
1669 if (!numberOfCompositionCommitRequestHandled
) {
1670 mRequestIMEToCommitCompositionResults
.RemoveElementsAt(0, i
- 1);
1678 void ContentCacheInParent::AppendEventMessageLog(nsACString
& aLog
) const {
1679 aLog
.AppendLiteral("Dispatched Event Message Log:\n");
1680 for (EventMessage message
: mDispatchedEventMessages
) {
1681 aLog
.AppendLiteral(" ");
1682 aLog
.Append(ToChar(message
));
1683 aLog
.AppendLiteral("\n");
1685 aLog
.AppendLiteral("\nReceived Event Message Log:\n");
1686 for (EventMessage message
: mReceivedEventMessages
) {
1687 aLog
.AppendLiteral(" ");
1688 aLog
.Append(ToChar(message
));
1689 aLog
.AppendLiteral("\n");
1691 aLog
.AppendLiteral("\nResult of RequestIMEToCommitComposition():\n");
1692 for (RequestIMEToCommitCompositionResult result
:
1693 mRequestIMEToCommitCompositionResults
) {
1694 aLog
.AppendLiteral(" ");
1695 aLog
.Append(ToReadableText(result
));
1696 aLog
.AppendLiteral("\n");
1698 aLog
.AppendLiteral("\n");
1701 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1703 /*****************************************************************************
1704 * mozilla::ContentCache::Selection
1705 *****************************************************************************/
1707 ContentCache::Selection::Selection(
1708 const WidgetQueryContentEvent
& aQuerySelectedTextEvent
)
1709 : mAnchor(UINT32_MAX
),
1711 mWritingMode(aQuerySelectedTextEvent
.mReply
->WritingModeRef()),
1712 mHasRange(aQuerySelectedTextEvent
.mReply
->mOffsetAndData
.isSome()) {
1713 MOZ_ASSERT(aQuerySelectedTextEvent
.mMessage
== eQuerySelectedText
);
1714 MOZ_ASSERT(aQuerySelectedTextEvent
.Succeeded());
1716 mAnchor
= aQuerySelectedTextEvent
.mReply
->AnchorOffset();
1717 mFocus
= aQuerySelectedTextEvent
.mReply
->FocusOffset();
1721 /*****************************************************************************
1722 * mozilla::ContentCache::TextRectArray
1723 *****************************************************************************/
1725 LayoutDeviceIntRect
ContentCache::TextRectArray::GetRect(
1726 uint32_t aOffset
) const {
1727 LayoutDeviceIntRect rect
;
1728 if (IsOffsetInRange(aOffset
)) {
1729 rect
= mRects
[aOffset
- mStart
];
1734 LayoutDeviceIntRect
ContentCache::TextRectArray::GetUnionRect(
1735 uint32_t aOffset
, uint32_t aLength
) const {
1736 LayoutDeviceIntRect rect
;
1737 if (!IsRangeCompletelyInRange(aOffset
, aLength
)) {
1740 for (uint32_t i
= 0; i
< aLength
; i
++) {
1741 rect
= rect
.Union(mRects
[aOffset
- mStart
+ i
]);
1746 LayoutDeviceIntRect
ContentCache::TextRectArray::GetUnionRectAsFarAsPossible(
1747 uint32_t aOffset
, uint32_t aLength
, bool aRoundToExistingOffset
) const {
1748 LayoutDeviceIntRect rect
;
1750 (!aRoundToExistingOffset
&& !IsOverlappingWith(aOffset
, aLength
))) {
1753 uint32_t startOffset
= std::max(aOffset
, mStart
);
1754 if (aRoundToExistingOffset
&& startOffset
>= EndOffset()) {
1755 startOffset
= EndOffset() - 1;
1757 uint32_t endOffset
= std::min(aOffset
+ aLength
, EndOffset());
1758 if (aRoundToExistingOffset
&& endOffset
< mStart
+ 1) {
1759 endOffset
= mStart
+ 1;
1761 if (NS_WARN_IF(endOffset
< startOffset
)) {
1764 for (uint32_t i
= 0; i
< endOffset
- startOffset
; i
++) {
1765 rect
= rect
.Union(mRects
[startOffset
- mStart
+ i
]);
1770 } // namespace mozilla