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/Assertions.h"
16 #include "mozilla/IMEStateManager.h"
17 #include "mozilla/IntegerPrintfMacros.h"
18 #include "mozilla/Logging.h"
19 #include "mozilla/RefPtr.h"
20 #include "mozilla/TextComposition.h"
21 #include "mozilla/dom/BrowserParent.h"
22 #include "nsExceptionHandler.h"
23 #include "nsIWidget.h"
24 #include "nsPrintfCString.h"
29 using namespace widget
;
31 static const char* GetBoolName(bool aBool
) { return aBool
? "true" : "false"; }
33 static const char* GetNotificationName(const IMENotification
* aNotification
) {
35 return "Not notification";
37 return ToChar(aNotification
->mMessage
);
40 /*****************************************************************************
41 * mozilla::ContentCache
42 *****************************************************************************/
44 LazyLogModule
sContentCacheLog("ContentCacheWidgets");
46 bool ContentCache::IsValid() const {
47 if (mText
.isNothing()) {
48 // mSelection and mCaret depend on mText.
49 if (NS_WARN_IF(mSelection
.isSome()) || NS_WARN_IF(mCaret
.isSome())) {
53 // mSelection depends on mText.
54 if (mSelection
.isSome() && NS_WARN_IF(!mSelection
->IsValidIn(*mText
))) {
58 // mCaret depends on mSelection.
59 if (mCaret
.isSome() &&
60 (NS_WARN_IF(mSelection
.isNothing()) ||
61 NS_WARN_IF(!mSelection
->mHasRange
) ||
62 NS_WARN_IF(mSelection
->StartOffset() != mCaret
->Offset()))) {
67 // mTextRectArray stores character rects around composition string.
68 // Note that even if we fail to collect the rects, we may keep storing
70 if (mTextRectArray
.isSome()) {
71 if (NS_WARN_IF(mCompositionStart
.isNothing())) {
79 void ContentCache::AssertIfInvalid() const {
80 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
85 // This text will appear in the crash reports without any permissions.
86 // Do not use `ToString` here to avoid to expose unexpected data with
87 // changing the type or `operator<<()`.
89 "ContentCache={ mText=%s, mSelection=%s, mCaret=%s, mTextRectArray=%s, "
90 "mCompositionStart=%s }\n",
91 // Don't expose mText.ref() value for protecting the user's privacy.
94 : nsPrintfCString("{ Length()=%zu }", mText
->Length()).get(),
95 mSelection
.isNothing()
97 : nsPrintfCString("{ mAnchor=%u, mFocus=%u }", mSelection
->mAnchor
,
102 : nsPrintfCString("{ mOffset=%u }", mCaret
->mOffset
).get(),
103 mTextRectArray
.isNothing()
105 : nsPrintfCString("{ Length()=%u }", mTextRectArray
->Length()).get(),
106 mCompositionStart
.isNothing()
108 : nsPrintfCString("%u", mCompositionStart
.value()).get());
109 CrashReporter::AppendAppNotesToCrashReport(info
);
110 MOZ_DIAGNOSTIC_ASSERT(false, "Invalid ContentCache data");
111 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
114 /*****************************************************************************
115 * mozilla::ContentCacheInChild
116 *****************************************************************************/
118 void ContentCacheInChild::Clear() {
119 MOZ_LOG(sContentCacheLog
, LogLevel::Info
, ("0x%p Clear()", this));
121 mCompositionStart
.reset();
125 mFirstCharRect
.SetEmpty();
127 mTextRectArray
.reset();
128 mLastCommitStringTextRectArray
.reset();
129 mEditorRect
.SetEmpty();
132 void ContentCacheInChild::OnCompositionEvent(
133 const WidgetCompositionEvent
& aCompositionEvent
) {
134 if (aCompositionEvent
.CausesDOMCompositionEndEvent()) {
135 RefPtr
<TextComposition
> composition
=
136 IMEStateManager::GetTextCompositionFor(aCompositionEvent
.mWidget
);
138 nsAutoString lastCommitString
;
139 if (aCompositionEvent
.mMessage
== eCompositionCommitAsIs
) {
140 lastCommitString
= composition
->CommitStringIfCommittedAsIs();
142 lastCommitString
= aCompositionEvent
.mData
;
144 // We don't need to store canceling information because this is required
145 // by undoing of last commit (Kakutei-Undo of Japanese IME).
146 if (!lastCommitString
.IsEmpty()) {
147 mLastCommit
= Some(OffsetAndData
<uint32_t>(
148 composition
->NativeOffsetOfStartComposition(), lastCommitString
));
150 sContentCacheLog
, LogLevel::Debug
,
151 ("0x%p OnCompositionEvent(), stored last composition string data "
152 "(aCompositionEvent={ mMessage=%s, mData=\"%s\"}, mLastCommit=%s)",
153 this, ToChar(aCompositionEvent
.mMessage
),
155 aCompositionEvent
.mData
,
156 PrintStringDetail::kMaxLengthForCompositionString
)
158 ToString(mLastCommit
).c_str()));
163 if (mLastCommit
.isSome()) {
165 sContentCacheLog
, LogLevel::Debug
,
166 ("0x%p OnCompositionEvent(), resetting the last composition string "
167 "data (aCompositionEvent={ mMessage=%s, mData=\"%s\"}, "
169 this, ToChar(aCompositionEvent
.mMessage
),
170 PrintStringDetail(aCompositionEvent
.mData
,
171 PrintStringDetail::kMaxLengthForCompositionString
)
173 ToString(mLastCommit
).c_str()));
178 bool ContentCacheInChild::CacheAll(nsIWidget
* aWidget
,
179 const IMENotification
* aNotification
) {
180 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
181 ("0x%p CacheAll(aWidget=0x%p, aNotification=%s)", this, aWidget
,
182 GetNotificationName(aNotification
)));
184 const bool textCached
= CacheText(aWidget
, aNotification
);
185 const bool editorRectCached
= CacheEditorRect(aWidget
, aNotification
);
187 return (textCached
|| editorRectCached
) && IsValid();
190 bool ContentCacheInChild::CacheSelection(nsIWidget
* aWidget
,
191 const IMENotification
* aNotification
) {
193 sContentCacheLog
, LogLevel::Info
,
194 ("0x%p CacheSelection(aWidget=0x%p, aNotification=%s), mText=%s", this,
195 aWidget
, GetNotificationName(aNotification
),
196 PrintStringDetail(mText
, PrintStringDetail::kMaxLengthForEditor
).get()));
201 if (mText
.isNothing()) {
205 nsEventStatus status
= nsEventStatus_eIgnore
;
206 WidgetQueryContentEvent
querySelectedTextEvent(true, eQuerySelectedText
,
208 aWidget
->DispatchEvent(&querySelectedTextEvent
, status
);
209 if (NS_WARN_IF(querySelectedTextEvent
.Failed())) {
211 sContentCacheLog
, LogLevel::Error
,
212 ("0x%p CacheSelection(), FAILED, couldn't retrieve the selected text",
214 // XXX Allowing selection-independent character rects makes things
215 // complicated in the parent...
217 // ContentCache should store only editable content. Therefore, if current
218 // selection root is not editable, we don't need to store the selection, i.e.,
219 // let's treat it as there is no selection. However, if we already have
220 // previously editable text, let's store the selection even if it becomes
221 // uneditable because not doing so would create odd situation. E.g., IME may
222 // fail only querying selection after succeeded querying text.
223 else if (NS_WARN_IF(!querySelectedTextEvent
.mReply
->mIsEditableContent
)) {
224 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
225 ("0x%p CacheSelection(), FAILED, editable content had already been "
231 mSelection
.emplace(querySelectedTextEvent
);
234 return (CacheCaretAndTextRects(aWidget
, aNotification
) ||
235 querySelectedTextEvent
.Succeeded()) &&
239 bool ContentCacheInChild::CacheCaret(nsIWidget
* aWidget
,
240 const IMENotification
* aNotification
) {
243 if (mSelection
.isNothing()) {
247 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
248 ("0x%p CacheCaret(aWidget=0x%p, aNotification=%s)", this, aWidget
,
249 GetNotificationName(aNotification
)));
251 if (mSelection
->mHasRange
) {
252 // XXX Should be mSelection.mFocus?
253 const uint32_t offset
= mSelection
->StartOffset();
255 nsEventStatus status
= nsEventStatus_eIgnore
;
256 WidgetQueryContentEvent
queryCaretRectEvent(true, eQueryCaretRect
, aWidget
);
257 queryCaretRectEvent
.InitForQueryCaretRect(offset
);
258 aWidget
->DispatchEvent(&queryCaretRectEvent
, status
);
259 if (NS_WARN_IF(queryCaretRectEvent
.Failed())) {
260 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
261 ("0x%p CacheCaret(), FAILED, couldn't retrieve the caret rect "
266 mCaret
.emplace(offset
, queryCaretRectEvent
.mReply
->mRect
);
268 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
269 ("0x%p CacheCaret(), Succeeded, mSelection=%s, mCaret=%s", this,
270 ToString(mSelection
).c_str(), ToString(mCaret
).c_str()));
275 bool ContentCacheInChild::CacheEditorRect(
276 nsIWidget
* aWidget
, const IMENotification
* aNotification
) {
277 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
278 ("0x%p CacheEditorRect(aWidget=0x%p, aNotification=%s)", this,
279 aWidget
, GetNotificationName(aNotification
)));
281 nsEventStatus status
= nsEventStatus_eIgnore
;
282 WidgetQueryContentEvent
queryEditorRectEvent(true, eQueryEditorRect
, aWidget
);
283 aWidget
->DispatchEvent(&queryEditorRectEvent
, status
);
284 if (NS_WARN_IF(queryEditorRectEvent
.Failed())) {
286 sContentCacheLog
, LogLevel::Error
,
287 ("0x%p CacheEditorRect(), FAILED, couldn't retrieve the editor rect",
291 // ContentCache should store only editable content. Therefore, if current
292 // selection root is not editable, we don't need to store the editor rect,
293 // i.e., let's treat it as there is no focused editor.
294 if (NS_WARN_IF(!queryEditorRectEvent
.mReply
->mIsEditableContent
)) {
295 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
296 ("0x%p CacheText(), FAILED, editable content had already been "
301 mEditorRect
= queryEditorRectEvent
.mReply
->mRect
;
302 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
303 ("0x%p CacheEditorRect(), Succeeded, mEditorRect=%s", this,
304 ToString(mEditorRect
).c_str()));
308 bool ContentCacheInChild::CacheCaretAndTextRects(
309 nsIWidget
* aWidget
, const IMENotification
* aNotification
) {
310 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
311 ("0x%p CacheCaretAndTextRects(aWidget=0x%p, aNotification=%s)", this,
312 aWidget
, GetNotificationName(aNotification
)));
314 const bool caretCached
= CacheCaret(aWidget
, aNotification
);
315 const bool textRectsCached
= CacheTextRects(aWidget
, aNotification
);
317 return (caretCached
|| textRectsCached
) && IsValid();
320 bool ContentCacheInChild::CacheText(nsIWidget
* aWidget
,
321 const IMENotification
* aNotification
) {
322 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
323 ("0x%p CacheText(aWidget=0x%p, aNotification=%s)", this, aWidget
,
324 GetNotificationName(aNotification
)));
326 nsEventStatus status
= nsEventStatus_eIgnore
;
327 WidgetQueryContentEvent
queryTextContentEvent(true, eQueryTextContent
,
329 queryTextContentEvent
.InitForQueryTextContent(0, UINT32_MAX
);
330 aWidget
->DispatchEvent(&queryTextContentEvent
, status
);
331 if (NS_WARN_IF(queryTextContentEvent
.Failed())) {
332 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
333 ("0x%p CacheText(), FAILED, couldn't retrieve whole text", this));
336 // ContentCache should store only editable content. Therefore, if current
337 // selection root is not editable, we don't need to store the text, i.e.,
338 // let's treat it as there is no editable text.
339 else if (NS_WARN_IF(!queryTextContentEvent
.mReply
->mIsEditableContent
)) {
340 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
341 ("0x%p CacheText(), FAILED, editable content had already been "
346 mText
= Some(nsString(queryTextContentEvent
.mReply
->DataRef()));
347 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
348 ("0x%p CacheText(), Succeeded, mText=%s", this,
349 PrintStringDetail(mText
, PrintStringDetail::kMaxLengthForEditor
)
353 // Forget last commit range if string in the range is different from the
354 // last commit string.
355 if (mLastCommit
.isSome() &&
356 (mText
.isNothing() ||
357 nsDependentSubstring(mText
.ref(), mLastCommit
->StartOffset(),
358 mLastCommit
->Length()) != mLastCommit
->DataRef())) {
359 MOZ_LOG(sContentCacheLog
, LogLevel::Debug
,
360 ("0x%p CacheText(), resetting the last composition string data "
361 "(mLastCommit=%s, current string=\"%s\")",
362 this, ToString(mLastCommit
).c_str(),
364 nsDependentSubstring(mText
.ref(), mLastCommit
->StartOffset(),
365 mLastCommit
->Length()),
366 PrintStringDetail::kMaxLengthForCompositionString
)
371 // If we fail to get editable text content, it must mean that there is no
372 // focused element anymore or focused element is not editable. In this case,
373 // we should not get selection of non-editable content
374 if (MOZ_UNLIKELY(mText
.isNothing())) {
377 mTextRectArray
.reset();
382 return CacheSelection(aWidget
, aNotification
);
385 bool ContentCacheInChild::QueryCharRect(nsIWidget
* aWidget
, uint32_t aOffset
,
386 LayoutDeviceIntRect
& aCharRect
) const {
387 aCharRect
.SetEmpty();
389 nsEventStatus status
= nsEventStatus_eIgnore
;
390 WidgetQueryContentEvent
queryTextRectEvent(true, eQueryTextRect
, aWidget
);
391 queryTextRectEvent
.InitForQueryTextRect(aOffset
, 1);
392 aWidget
->DispatchEvent(&queryTextRectEvent
, status
);
393 if (NS_WARN_IF(queryTextRectEvent
.Failed())) {
396 aCharRect
= queryTextRectEvent
.mReply
->mRect
;
398 // Guarantee the rect is not empty.
399 if (NS_WARN_IF(!aCharRect
.Height())) {
400 aCharRect
.SetHeight(1);
402 if (NS_WARN_IF(!aCharRect
.Width())) {
403 aCharRect
.SetWidth(1);
408 bool ContentCacheInChild::QueryCharRectArray(nsIWidget
* aWidget
,
409 uint32_t aOffset
, uint32_t aLength
,
410 RectArray
& aCharRectArray
) const {
411 nsEventStatus status
= nsEventStatus_eIgnore
;
412 WidgetQueryContentEvent
queryTextRectsEvent(true, eQueryTextRectArray
,
414 queryTextRectsEvent
.InitForQueryTextRectArray(aOffset
, aLength
);
415 aWidget
->DispatchEvent(&queryTextRectsEvent
, status
);
416 if (NS_WARN_IF(queryTextRectsEvent
.Failed())) {
417 aCharRectArray
.Clear();
420 aCharRectArray
= std::move(queryTextRectsEvent
.mReply
->mRectArray
);
424 bool ContentCacheInChild::CacheTextRects(nsIWidget
* aWidget
,
425 const IMENotification
* aNotification
) {
427 sContentCacheLog
, LogLevel::Info
,
428 ("0x%p CacheTextRects(aWidget=0x%p, aNotification=%s), mCaret=%s", this,
429 aWidget
, GetNotificationName(aNotification
), ToString(mCaret
).c_str()));
431 if (mSelection
.isSome()) {
432 mSelection
->ClearRects();
435 // Retrieve text rects in composition string if there is.
436 RefPtr
<TextComposition
> textComposition
=
437 IMEStateManager::GetTextCompositionFor(aWidget
);
438 if (textComposition
) {
439 // mCompositionStart may be updated by some composition event handlers.
440 // So, let's update it with the latest information.
441 mCompositionStart
= Some(textComposition
->NativeOffsetOfStartComposition());
442 // Note that TextComposition::String() may not be modified here because
443 // it's modified after all edit action listeners are performed but this
444 // is called while some of them are performed.
445 // FYI: For supporting IME which commits composition and restart new
446 // composition immediately, we should cache next character of current
448 uint32_t length
= textComposition
->LastData().Length() + 1;
449 mTextRectArray
= Some(TextRectArray(mCompositionStart
.value()));
450 if (NS_WARN_IF(!QueryCharRectArray(aWidget
, mTextRectArray
->mStart
, length
,
451 mTextRectArray
->mRects
))) {
452 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
453 ("0x%p CacheTextRects(), FAILED, "
454 "couldn't retrieve text rect array of the composition string",
456 mTextRectArray
.reset();
459 mCompositionStart
.reset();
460 mTextRectArray
.reset();
463 if (mSelection
.isSome()) {
464 // Set mSelection->mAnchorCharRects
465 // If we've already have the rect in mTextRectArray, save the query cost.
466 if (mSelection
->mHasRange
&& mTextRectArray
.isSome() &&
467 mTextRectArray
->IsOffsetInRange(mSelection
->mAnchor
) &&
468 (!mSelection
->mAnchor
||
469 mTextRectArray
->IsOffsetInRange(mSelection
->mAnchor
- 1))) {
470 mSelection
->mAnchorCharRects
[eNextCharRect
] =
471 mTextRectArray
->GetRect(mSelection
->mAnchor
);
472 if (mSelection
->mAnchor
) {
473 mSelection
->mAnchorCharRects
[ePrevCharRect
] =
474 mTextRectArray
->GetRect(mSelection
->mAnchor
- 1);
477 // Otherwise, get it from content even if there is no selection ranges.
480 const uint32_t startOffset
= mSelection
->mHasRange
&& mSelection
->mAnchor
481 ? mSelection
->mAnchor
- 1u
483 const uint32_t length
=
484 mSelection
->mHasRange
&& mSelection
->mAnchor
? 2u : 1u;
486 !QueryCharRectArray(aWidget
, startOffset
, length
, rects
))) {
488 sContentCacheLog
, LogLevel::Error
,
489 ("0x%p CacheTextRects(), FAILED, couldn't retrieve text rect "
490 "array around the selection anchor (%s)",
492 mSelection
? ToString(mSelection
->mAnchor
).c_str() : "Nothing"));
493 MOZ_ASSERT_IF(mSelection
.isSome(),
494 mSelection
->mAnchorCharRects
[ePrevCharRect
].IsEmpty());
495 MOZ_ASSERT_IF(mSelection
.isSome(),
496 mSelection
->mAnchorCharRects
[eNextCharRect
].IsEmpty());
497 } else if (rects
.Length()) {
498 if (rects
.Length() > 1) {
499 mSelection
->mAnchorCharRects
[ePrevCharRect
] = rects
[0];
500 mSelection
->mAnchorCharRects
[eNextCharRect
] = rects
[1];
502 mSelection
->mAnchorCharRects
[eNextCharRect
] = rects
[0];
503 MOZ_ASSERT(mSelection
->mAnchorCharRects
[ePrevCharRect
].IsEmpty());
508 // Set mSelection->mFocusCharRects
509 // If selection is collapsed (including no selection case), the focus char
510 // rects are same as the anchor char rects so that we can just copy them.
511 if (mSelection
->IsCollapsed()) {
512 mSelection
->mFocusCharRects
[0] = mSelection
->mAnchorCharRects
[0];
513 mSelection
->mFocusCharRects
[1] = mSelection
->mAnchorCharRects
[1];
515 // If the selection range is in mTextRectArray, save the query cost.
516 else if (mTextRectArray
.isSome() &&
517 mTextRectArray
->IsOffsetInRange(mSelection
->mFocus
) &&
518 (!mSelection
->mFocus
||
519 mTextRectArray
->IsOffsetInRange(mSelection
->mFocus
- 1))) {
520 MOZ_ASSERT(mSelection
->mHasRange
);
521 mSelection
->mFocusCharRects
[eNextCharRect
] =
522 mTextRectArray
->GetRect(mSelection
->mFocus
);
523 if (mSelection
->mFocus
) {
524 mSelection
->mFocusCharRects
[ePrevCharRect
] =
525 mTextRectArray
->GetRect(mSelection
->mFocus
- 1);
528 // Otherwise, including no selection range cases, need to query the rects.
530 MOZ_ASSERT(mSelection
->mHasRange
);
532 const uint32_t startOffset
=
533 mSelection
->mFocus
? mSelection
->mFocus
- 1u : 0u;
534 const uint32_t length
= mSelection
->mFocus
? 2u : 1u;
536 !QueryCharRectArray(aWidget
, startOffset
, length
, rects
))) {
538 sContentCacheLog
, LogLevel::Error
,
539 ("0x%p CacheTextRects(), FAILED, couldn't retrieve text rect "
540 "array around the selection focus (%s)",
542 mSelection
? ToString(mSelection
->mFocus
).c_str() : "Nothing"));
543 MOZ_ASSERT_IF(mSelection
.isSome(),
544 mSelection
->mFocusCharRects
[ePrevCharRect
].IsEmpty());
545 MOZ_ASSERT_IF(mSelection
.isSome(),
546 mSelection
->mFocusCharRects
[eNextCharRect
].IsEmpty());
547 } else if (NS_WARN_IF(mSelection
.isNothing())) {
548 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
549 ("0x%p CacheTextRects(), FAILED, mSelection was reset during "
550 "the call of QueryCharRectArray",
553 if (rects
.Length() > 1) {
554 mSelection
->mFocusCharRects
[ePrevCharRect
] = rects
[0];
555 mSelection
->mFocusCharRects
[eNextCharRect
] = rects
[1];
556 } else if (rects
.Length()) {
557 mSelection
->mFocusCharRects
[eNextCharRect
] = rects
[0];
558 MOZ_ASSERT(mSelection
->mFocusCharRects
[ePrevCharRect
].IsEmpty());
564 // If there is a non-collapsed selection range, let's query the whole selected
565 // text rect. Note that the result cannot be computed from first character
566 // rect and last character rect of the selection because they both may be in
567 // middle of different line.
568 if (mSelection
.isSome() && mSelection
->mHasRange
&&
569 !mSelection
->IsCollapsed()) {
570 nsEventStatus status
= nsEventStatus_eIgnore
;
571 WidgetQueryContentEvent
queryTextRectEvent(true, eQueryTextRect
, aWidget
);
572 queryTextRectEvent
.InitForQueryTextRect(mSelection
->StartOffset(),
573 mSelection
->Length());
574 aWidget
->DispatchEvent(&queryTextRectEvent
, status
);
575 if (NS_WARN_IF(queryTextRectEvent
.Failed())) {
576 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
577 ("0x%p CacheTextRects(), FAILED, "
578 "couldn't retrieve text rect of whole selected text",
581 mSelection
->mRect
= queryTextRectEvent
.mReply
->mRect
;
585 // Even if there is no selection range, we should have the first character
586 // rect for the last resort of suggesting position of IME UI.
587 if (mSelection
.isSome() && mSelection
->mHasRange
&& !mSelection
->mFocus
) {
588 mFirstCharRect
= mSelection
->mFocusCharRects
[eNextCharRect
];
589 } else if (mSelection
.isSome() && mSelection
->mHasRange
&&
590 mSelection
->mFocus
== 1) {
591 mFirstCharRect
= mSelection
->mFocusCharRects
[ePrevCharRect
];
592 } else if (mSelection
.isSome() && mSelection
->mHasRange
&&
593 !mSelection
->mAnchor
) {
594 mFirstCharRect
= mSelection
->mAnchorCharRects
[eNextCharRect
];
595 } else if (mSelection
.isSome() && mSelection
->mHasRange
&&
596 mSelection
->mAnchor
== 1) {
597 mFirstCharRect
= mSelection
->mFocusCharRects
[ePrevCharRect
];
598 } else if (mTextRectArray
.isSome() && mTextRectArray
->IsOffsetInRange(0u)) {
599 mFirstCharRect
= mTextRectArray
->GetRect(0u);
601 LayoutDeviceIntRect charRect
;
602 if (MOZ_UNLIKELY(NS_WARN_IF(!QueryCharRect(aWidget
, 0, charRect
)))) {
603 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
604 ("0x%p CacheTextRects(), FAILED, "
605 "couldn't retrieve first char rect",
607 mFirstCharRect
.SetEmpty();
609 mFirstCharRect
= charRect
;
613 // Finally, let's cache the last commit string's character rects until
614 // selection change or something other editing because user may reconvert
615 // or undo the last commit. Then, IME requires the character rects for
616 // positioning their UI.
617 if (mLastCommit
.isSome()) {
618 mLastCommitStringTextRectArray
=
619 Some(TextRectArray(mLastCommit
->StartOffset()));
620 if (mLastCommit
->Length() == 1 && mSelection
.isSome() &&
621 mSelection
->mHasRange
&&
622 mSelection
->mAnchor
- 1 == mLastCommit
->StartOffset() &&
623 !mSelection
->mAnchorCharRects
[ePrevCharRect
].IsEmpty()) {
624 mLastCommitStringTextRectArray
->mRects
.AppendElement(
625 mSelection
->mAnchorCharRects
[ePrevCharRect
]);
626 } else if (NS_WARN_IF(!QueryCharRectArray(
627 aWidget
, mLastCommit
->StartOffset(), mLastCommit
->Length(),
628 mLastCommitStringTextRectArray
->mRects
))) {
629 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
630 ("0x%p CacheTextRects(), FAILED, "
631 "couldn't retrieve text rect array of the last commit string",
633 mLastCommitStringTextRectArray
.reset();
636 MOZ_ASSERT((mLastCommitStringTextRectArray
.isSome()
637 ? mLastCommitStringTextRectArray
->mRects
.Length()
638 : 0) == (mLastCommit
.isSome() ? mLastCommit
->Length() : 0));
640 mLastCommitStringTextRectArray
.reset();
644 sContentCacheLog
, LogLevel::Info
,
645 ("0x%p CacheTextRects(), Succeeded, "
646 "mText=%s, mTextRectArray=%s, mSelection=%s, "
647 "mFirstCharRect=%s, mLastCommitStringTextRectArray=%s",
649 PrintStringDetail(mText
, PrintStringDetail::kMaxLengthForEditor
).get(),
650 ToString(mTextRectArray
).c_str(), ToString(mSelection
).c_str(),
651 ToString(mFirstCharRect
).c_str(),
652 ToString(mLastCommitStringTextRectArray
).c_str()));
657 bool ContentCacheInChild::SetSelection(
659 const IMENotification::SelectionChangeDataBase
& aSelectionChangeData
) {
661 sContentCacheLog
, LogLevel::Info
,
662 ("0x%p SetSelection(aSelectionChangeData=%s), mText=%s", this,
663 ToString(aSelectionChangeData
).c_str(),
664 PrintStringDetail(mText
, PrintStringDetail::kMaxLengthForEditor
).get()));
666 if (MOZ_UNLIKELY(mText
.isNothing())) {
670 mSelection
= Some(Selection(aSelectionChangeData
));
672 if (mLastCommit
.isSome()) {
673 // Forget last commit string range if selection is not collapsed
674 // at end of the last commit string.
675 if (!mSelection
->mHasRange
|| !mSelection
->IsCollapsed() ||
676 mSelection
->mAnchor
!= mLastCommit
->EndOffset()) {
678 sContentCacheLog
, LogLevel::Debug
,
679 ("0x%p SetSelection(), forgetting last commit composition data "
680 "(mSelection=%s, mLastCommit=%s)",
681 this, ToString(mSelection
).c_str(), ToString(mLastCommit
).c_str()));
687 CacheTextRects(aWidget
);
689 return mSelection
.isSome() && IsValid();
692 /*****************************************************************************
693 * mozilla::ContentCacheInParent
694 *****************************************************************************/
696 ContentCacheInParent::ContentCacheInParent(BrowserParent
& aBrowserParent
)
698 mBrowserParent(aBrowserParent
),
699 mCommitStringByRequest(nullptr),
700 mPendingCommitLength(0),
701 mIsChildIgnoringCompositionEvents(false) {}
703 void ContentCacheInParent::AssignContent(const ContentCache
& aOther
,
705 const IMENotification
* aNotification
) {
706 MOZ_DIAGNOSTIC_ASSERT(aOther
.IsValid());
708 mText
= aOther
.mText
;
709 mSelection
= aOther
.mSelection
;
710 mFirstCharRect
= aOther
.mFirstCharRect
;
711 mCaret
= aOther
.mCaret
;
712 mTextRectArray
= aOther
.mTextRectArray
;
713 mLastCommitStringTextRectArray
= aOther
.mLastCommitStringTextRectArray
;
714 mEditorRect
= aOther
.mEditorRect
;
716 // Only when there is one composition, the TextComposition instance in this
717 // process is managing the composition in the remote process. Therefore,
718 // we shouldn't update composition start offset of TextComposition with
719 // old composition which is still being handled by the child process.
720 if (WidgetHasComposition() && mHandlingCompositions
.Length() == 1 &&
721 mCompositionStart
.isSome()) {
722 IMEStateManager::MaybeStartOffsetUpdatedInChild(aWidget
,
723 mCompositionStart
.value());
726 // When this instance allows to query content relative to composition string,
727 // we should modify mCompositionStart with the latest information in the
728 // remote process because now we have the information around the composition
730 mCompositionStartInChild
= aOther
.mCompositionStart
;
731 if (WidgetHasComposition() || HasPendingCommit()) {
732 if (mCompositionStartInChild
.isSome()) {
733 if (mCompositionStart
.valueOr(UINT32_MAX
) !=
734 mCompositionStartInChild
.value()) {
735 mCompositionStart
= mCompositionStartInChild
;
736 mPendingCommitLength
= 0;
738 } else if (mCompositionStart
.isSome() && mSelection
.isSome() &&
739 mSelection
->mHasRange
&&
740 mCompositionStart
.value() != mSelection
->StartOffset()) {
741 mCompositionStart
= Some(mSelection
->StartOffset());
742 mPendingCommitLength
= 0;
747 sContentCacheLog
, LogLevel::Info
,
748 ("0x%p AssignContent(aNotification=%s), "
749 "Succeeded, mText=%s, mSelection=%s, mFirstCharRect=%s, "
750 "mCaret=%s, mTextRectArray=%s, WidgetHasComposition()=%s, "
751 "mHandlingCompositions.Length()=%zu, mCompositionStart=%s, "
752 "mPendingCommitLength=%u, mEditorRect=%s, "
753 "mLastCommitStringTextRectArray=%s",
754 this, GetNotificationName(aNotification
),
755 PrintStringDetail(mText
, PrintStringDetail::kMaxLengthForEditor
).get(),
756 ToString(mSelection
).c_str(), ToString(mFirstCharRect
).c_str(),
757 ToString(mCaret
).c_str(), ToString(mTextRectArray
).c_str(),
758 GetBoolName(WidgetHasComposition()), mHandlingCompositions
.Length(),
759 ToString(mCompositionStart
).c_str(), mPendingCommitLength
,
760 ToString(mEditorRect
).c_str(),
761 ToString(mLastCommitStringTextRectArray
).c_str()));
764 bool ContentCacheInParent::HandleQueryContentEvent(
765 WidgetQueryContentEvent
& aEvent
, nsIWidget
* aWidget
) const {
768 // ContentCache doesn't store offset of its start with XP linebreaks.
769 // So, we don't support to query contents relative to composition start
770 // offset with XP linebreaks.
771 if (NS_WARN_IF(!aEvent
.mUseNativeLineBreak
)) {
772 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
773 ("0x%p HandleQueryContentEvent(), FAILED due to query with XP "
779 if (NS_WARN_IF(!aEvent
.mInput
.IsValidOffset())) {
781 sContentCacheLog
, LogLevel::Error
,
782 ("0x%p HandleQueryContentEvent(), FAILED due to invalid offset", this));
786 if (NS_WARN_IF(!aEvent
.mInput
.IsValidEventMessage(aEvent
.mMessage
))) {
788 sContentCacheLog
, LogLevel::Error
,
789 ("0x%p HandleQueryContentEvent(), FAILED due to invalid event message",
794 bool isRelativeToInsertionPoint
= aEvent
.mInput
.mRelativeToInsertionPoint
;
795 if (isRelativeToInsertionPoint
) {
797 sContentCacheLog
, LogLevel::Debug
,
798 ("0x%p HandleQueryContentEvent(), "
799 "making offset absolute... aEvent={ mMessage=%s, mInput={ "
800 "mOffset=%" PRId64
", mLength=%" PRIu32
" } }, "
801 "WidgetHasComposition()=%s, HasPendingCommit()=%s, "
802 "mCompositionStart=%" PRIu32
", "
803 "mPendingCommitLength=%" PRIu32
", mSelection=%s",
804 this, ToChar(aEvent
.mMessage
), aEvent
.mInput
.mOffset
,
805 aEvent
.mInput
.mLength
, GetBoolName(WidgetHasComposition()),
806 GetBoolName(HasPendingCommit()), mCompositionStart
.valueOr(UINT32_MAX
),
807 mPendingCommitLength
, ToString(mSelection
).c_str()));
808 if (WidgetHasComposition() || HasPendingCommit()) {
809 if (NS_WARN_IF(mCompositionStart
.isNothing()) ||
810 NS_WARN_IF(!aEvent
.mInput
.MakeOffsetAbsolute(
811 mCompositionStart
.value() + mPendingCommitLength
))) {
813 sContentCacheLog
, LogLevel::Error
,
814 ("0x%p HandleQueryContentEvent(), FAILED due to "
815 "aEvent.mInput.MakeOffsetAbsolute(mCompositionStart + "
816 "mPendingCommitLength) failure, "
817 "mCompositionStart=%" PRIu32
", mPendingCommitLength=%" PRIu32
", "
818 "aEvent={ mMessage=%s, mInput={ mOffset=%" PRId64
819 ", mLength=%" PRIu32
" } }",
820 this, mCompositionStart
.valueOr(UINT32_MAX
), mPendingCommitLength
,
821 ToChar(aEvent
.mMessage
), aEvent
.mInput
.mOffset
,
822 aEvent
.mInput
.mLength
));
825 } else if (NS_WARN_IF(mSelection
.isNothing())) {
826 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
827 ("0x%p HandleQueryContentEvent(), FAILED due to mSelection is "
831 } else if (NS_WARN_IF(mSelection
->mHasRange
)) {
832 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
833 ("0x%p HandleQueryContentEvent(), FAILED due to there is no "
834 "selection range, but the query requested with relative offset "
838 } else if (NS_WARN_IF(!aEvent
.mInput
.MakeOffsetAbsolute(
839 mSelection
->StartOffset() + mPendingCommitLength
))) {
840 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
841 ("0x%p HandleQueryContentEvent(), FAILED due to "
842 "aEvent.mInput.MakeOffsetAbsolute(mSelection->StartOffset() + "
843 "mPendingCommitLength) failure, mSelection=%s, "
844 "mPendingCommitLength=%" PRIu32
", aEvent={ mMessage=%s, "
845 "mInput={ mOffset=%" PRId64
", mLength=%" PRIu32
" } }",
846 this, ToString(mSelection
).c_str(), mPendingCommitLength
,
847 ToChar(aEvent
.mMessage
), aEvent
.mInput
.mOffset
,
848 aEvent
.mInput
.mLength
));
853 switch (aEvent
.mMessage
) {
854 case eQuerySelectedText
:
855 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
856 ("0x%p HandleQueryContentEvent(aEvent={ "
857 "mMessage=eQuerySelectedText }, aWidget=0x%p)",
859 if (MOZ_UNLIKELY(NS_WARN_IF(mSelection
.isNothing()))) {
860 // If content cache hasn't been initialized properly, make the query
862 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
863 ("0x%p HandleQueryContentEvent(), FAILED because mSelection "
868 MOZ_DIAGNOSTIC_ASSERT(mText
.isSome());
869 MOZ_DIAGNOSTIC_ASSERT(mSelection
->IsValidIn(*mText
));
870 aEvent
.EmplaceReply();
871 aEvent
.mReply
->mFocusedWidget
= aWidget
;
872 if (mSelection
->mHasRange
) {
873 if (MOZ_LIKELY(mText
.isSome())) {
874 aEvent
.mReply
->mOffsetAndData
.emplace(
875 mSelection
->StartOffset(),
876 Substring(mText
.ref(), mSelection
->StartOffset(),
877 mSelection
->Length()),
878 OffsetAndDataFor::SelectedString
);
880 // TODO: Investigate this case. I find this during
881 // test_mousecapture.xhtml on Linux.
882 aEvent
.mReply
->mOffsetAndData
.emplace(
883 0u, EmptyString(), OffsetAndDataFor::SelectedString
);
886 aEvent
.mReply
->mWritingMode
= mSelection
->mWritingMode
;
887 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
888 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
889 "mMessage=eQuerySelectedText, mReply=%s }",
890 this, ToString(aEvent
.mReply
).c_str()));
892 case eQueryTextContent
: {
893 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
894 ("0x%p HandleQueryContentEvent(aEvent={ "
895 "mMessage=eQueryTextContent, mInput={ mOffset=%" PRId64
896 ", mLength=%u } }, aWidget=0x%p), mText->Length()=%zu",
897 this, aEvent
.mInput
.mOffset
, aEvent
.mInput
.mLength
, aWidget
,
898 mText
.isSome() ? mText
->Length() : 0u));
899 if (MOZ_UNLIKELY(NS_WARN_IF(mText
.isNothing()))) {
900 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
901 ("0x%p HandleQueryContentEvent(), FAILED because "
902 "there is no text data",
906 const uint32_t inputOffset
= aEvent
.mInput
.mOffset
;
907 const uint32_t inputEndOffset
= std::min
<uint32_t>(
908 aEvent
.mInput
.EndOffset(), mText
.isSome() ? mText
->Length() : 0u);
909 if (MOZ_UNLIKELY(NS_WARN_IF(inputEndOffset
< inputOffset
))) {
910 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
911 ("0x%p HandleQueryContentEvent(), FAILED because "
912 "inputOffset=%u is larger than inputEndOffset=%u",
913 this, inputOffset
, inputEndOffset
));
916 aEvent
.EmplaceReply();
917 aEvent
.mReply
->mFocusedWidget
= aWidget
;
918 const nsAString
& textInQueriedRange
=
919 inputEndOffset
> inputOffset
920 ? static_cast<const nsAString
&>(Substring(
921 mText
.ref(), inputOffset
, inputEndOffset
- inputOffset
))
922 : static_cast<const nsAString
&>(EmptyString());
923 aEvent
.mReply
->mOffsetAndData
.emplace(inputOffset
, textInQueriedRange
,
924 OffsetAndDataFor::EditorString
);
925 // TODO: Support font ranges
926 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
927 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
928 "mMessage=eQueryTextContent, mReply=%s }",
929 this, ToString(aEvent
.mReply
).c_str()));
932 case eQueryTextRect
: {
933 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
934 ("0x%p HandleQueryContentEvent("
935 "aEvent={ mMessage=eQueryTextRect, mInput={ mOffset=%" PRId64
936 ", mLength=%u } }, aWidget=0x%p), mText->Length()=%zu",
937 this, aEvent
.mInput
.mOffset
, aEvent
.mInput
.mLength
, aWidget
,
938 mText
.isSome() ? mText
->Length() : 0u));
939 // Note that if the query is relative to insertion point, the query was
940 // probably requested by native IME. In such case, we should return
941 // non-empty rect since returning failure causes IME showing its window
943 LayoutDeviceIntRect textRect
;
944 if (aEvent
.mInput
.mLength
) {
945 if (MOZ_UNLIKELY(NS_WARN_IF(
946 !GetUnionTextRects(aEvent
.mInput
.mOffset
, aEvent
.mInput
.mLength
,
947 isRelativeToInsertionPoint
, textRect
)))) {
948 // XXX We don't have cache for this request.
949 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
950 ("0x%p HandleQueryContentEvent(), FAILED to get union rect",
955 // If the length is 0, we should return caret rect instead.
956 if (NS_WARN_IF(!GetCaretRect(aEvent
.mInput
.mOffset
,
957 isRelativeToInsertionPoint
, textRect
))) {
958 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
959 ("0x%p HandleQueryContentEvent(), FAILED to get caret rect",
964 aEvent
.EmplaceReply();
965 aEvent
.mReply
->mFocusedWidget
= aWidget
;
966 aEvent
.mReply
->mRect
= textRect
;
967 const nsAString
& textInQueriedRange
=
968 mText
.isSome() && aEvent
.mInput
.mOffset
<
969 static_cast<int64_t>(
970 mText
.isSome() ? mText
->Length() : 0u)
971 ? static_cast<const nsAString
&>(
972 Substring(mText
.ref(), aEvent
.mInput
.mOffset
,
973 mText
->Length() >= aEvent
.mInput
.EndOffset()
974 ? aEvent
.mInput
.mLength
976 : static_cast<const nsAString
&>(EmptyString());
977 aEvent
.mReply
->mOffsetAndData
.emplace(aEvent
.mInput
.mOffset
,
979 OffsetAndDataFor::EditorString
);
980 // XXX This may be wrong if storing range isn't in the selection range.
981 aEvent
.mReply
->mWritingMode
=
982 mSelection
.isSome() ? mSelection
->mWritingMode
: WritingMode();
983 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
984 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
985 "mMessage=eQueryTextRect mReply=%s }",
986 this, ToString(aEvent
.mReply
).c_str()));
989 case eQueryCaretRect
: {
991 sContentCacheLog
, LogLevel::Info
,
992 ("0x%p HandleQueryContentEvent(aEvent={ mMessage=eQueryCaretRect, "
993 "mInput={ mOffset=%" PRId64
994 " } }, aWidget=0x%p), mText->Length()=%zu",
995 this, aEvent
.mInput
.mOffset
, aWidget
,
996 mText
.isSome() ? mText
->Length() : 0u));
997 // Note that if the query is relative to insertion point, the query was
998 // probably requested by native IME. In such case, we should return
999 // non-empty rect since returning failure causes IME showing its window
1001 LayoutDeviceIntRect caretRect
;
1002 if (NS_WARN_IF(!GetCaretRect(aEvent
.mInput
.mOffset
,
1003 isRelativeToInsertionPoint
, caretRect
))) {
1004 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
1005 ("0x%p HandleQueryContentEvent(),FAILED to get caret rect",
1009 aEvent
.EmplaceReply();
1010 aEvent
.mReply
->mFocusedWidget
= aWidget
;
1011 aEvent
.mReply
->mRect
= caretRect
;
1012 aEvent
.mReply
->mOffsetAndData
.emplace(aEvent
.mInput
.mOffset
,
1014 OffsetAndDataFor::SelectedString
);
1015 // TODO: Set mWritingMode here
1016 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
1017 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
1018 "mMessage=eQueryCaretRect, mReply=%s }",
1019 this, ToString(aEvent
.mReply
).c_str()));
1022 case eQueryEditorRect
:
1023 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
1024 ("0x%p HandleQueryContentEvent(aEvent={ "
1025 "mMessage=eQueryEditorRect }, aWidget=0x%p)",
1027 // XXX This query should fail if no editable elmenet has focus. Or,
1028 // perhaps, should return rect of the window instead.
1029 aEvent
.EmplaceReply();
1030 aEvent
.mReply
->mFocusedWidget
= aWidget
;
1031 aEvent
.mReply
->mRect
= mEditorRect
;
1032 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
1033 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
1034 "mMessage=eQueryEditorRect, mReply=%s }",
1035 this, ToString(aEvent
.mReply
).c_str()));
1038 aEvent
.EmplaceReply();
1039 aEvent
.mReply
->mFocusedWidget
= aWidget
;
1040 if (NS_WARN_IF(aEvent
.Failed())) {
1042 sContentCacheLog
, LogLevel::Error
,
1043 ("0x%p HandleQueryContentEvent(), FAILED due to not set enough "
1044 "data, aEvent={ mMessage=%s, mReply=%s }",
1045 this, ToChar(aEvent
.mMessage
), ToString(aEvent
.mReply
).c_str()));
1048 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
1049 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
1050 "mMessage=%s, mReply=%s }",
1051 this, ToChar(aEvent
.mMessage
), ToString(aEvent
.mReply
).c_str()));
1056 bool ContentCacheInParent::GetTextRect(uint32_t aOffset
,
1057 bool aRoundToExistingOffset
,
1058 LayoutDeviceIntRect
& aTextRect
) const {
1060 sContentCacheLog
, LogLevel::Info
,
1061 ("0x%p GetTextRect(aOffset=%u, aRoundToExistingOffset=%s), "
1062 "mTextRectArray=%s, mSelection=%s, mLastCommitStringTextRectArray=%s",
1063 this, aOffset
, GetBoolName(aRoundToExistingOffset
),
1064 ToString(mTextRectArray
).c_str(), ToString(mSelection
).c_str(),
1065 ToString(mLastCommitStringTextRectArray
).c_str()));
1068 NS_WARNING_ASSERTION(!mFirstCharRect
.IsEmpty(), "empty rect");
1069 aTextRect
= mFirstCharRect
;
1070 return !aTextRect
.IsEmpty();
1072 if (mSelection
.isSome() && mSelection
->mHasRange
) {
1073 if (aOffset
== mSelection
->mAnchor
) {
1074 NS_WARNING_ASSERTION(
1075 !mSelection
->mAnchorCharRects
[eNextCharRect
].IsEmpty(), "empty rect");
1076 aTextRect
= mSelection
->mAnchorCharRects
[eNextCharRect
];
1077 return !aTextRect
.IsEmpty();
1079 if (mSelection
->mAnchor
&& aOffset
== mSelection
->mAnchor
- 1) {
1080 NS_WARNING_ASSERTION(
1081 !mSelection
->mAnchorCharRects
[ePrevCharRect
].IsEmpty(), "empty rect");
1082 aTextRect
= mSelection
->mAnchorCharRects
[ePrevCharRect
];
1083 return !aTextRect
.IsEmpty();
1085 if (aOffset
== mSelection
->mFocus
) {
1086 NS_WARNING_ASSERTION(
1087 !mSelection
->mFocusCharRects
[eNextCharRect
].IsEmpty(), "empty rect");
1088 aTextRect
= mSelection
->mFocusCharRects
[eNextCharRect
];
1089 return !aTextRect
.IsEmpty();
1091 if (mSelection
->mFocus
&& aOffset
== mSelection
->mFocus
- 1) {
1092 NS_WARNING_ASSERTION(
1093 !mSelection
->mFocusCharRects
[ePrevCharRect
].IsEmpty(), "empty rect");
1094 aTextRect
= mSelection
->mFocusCharRects
[ePrevCharRect
];
1095 return !aTextRect
.IsEmpty();
1099 if (mTextRectArray
.isSome() && mTextRectArray
->IsOffsetInRange(aOffset
)) {
1100 aTextRect
= mTextRectArray
->GetRect(aOffset
);
1101 return !aTextRect
.IsEmpty();
1104 if (mLastCommitStringTextRectArray
.isSome() &&
1105 mLastCommitStringTextRectArray
->IsOffsetInRange(aOffset
)) {
1106 aTextRect
= mLastCommitStringTextRectArray
->GetRect(aOffset
);
1107 return !aTextRect
.IsEmpty();
1110 if (!aRoundToExistingOffset
) {
1111 aTextRect
.SetEmpty();
1115 if (mTextRectArray
.isNothing() || !mTextRectArray
->HasRects()) {
1116 // If there are no rects in mTextRectArray, we should refer the start of
1117 // the selection if there is because IME must query a char rect around it if
1118 // there is no composition.
1119 if (mSelection
.isNothing()) {
1120 // Unfortunately, there is no data about text rect...
1121 aTextRect
.SetEmpty();
1124 aTextRect
= mSelection
->StartCharRect();
1125 return !aTextRect
.IsEmpty();
1128 // Although we may have mLastCommitStringTextRectArray here and it must have
1129 // previous character rects at selection. However, we should stop using it
1130 // because it's stored really short time after commiting a composition.
1131 // So, multiple query may return different rect and it may cause flickerling
1133 uint32_t offset
= aOffset
;
1134 if (offset
< mTextRectArray
->StartOffset()) {
1135 offset
= mTextRectArray
->StartOffset();
1137 offset
= mTextRectArray
->EndOffset() - 1;
1139 aTextRect
= mTextRectArray
->GetRect(offset
);
1140 return !aTextRect
.IsEmpty();
1143 bool ContentCacheInParent::GetUnionTextRects(
1144 uint32_t aOffset
, uint32_t aLength
, bool aRoundToExistingOffset
,
1145 LayoutDeviceIntRect
& aUnionTextRect
) const {
1146 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
1147 ("0x%p GetUnionTextRects(aOffset=%u, "
1148 "aLength=%u, aRoundToExistingOffset=%s), mTextRectArray=%s, "
1149 "mSelection=%s, mLastCommitStringTextRectArray=%s",
1150 this, aOffset
, aLength
, GetBoolName(aRoundToExistingOffset
),
1151 ToString(mTextRectArray
).c_str(), ToString(mSelection
).c_str(),
1152 ToString(mLastCommitStringTextRectArray
).c_str()));
1154 CheckedInt
<uint32_t> endOffset
= CheckedInt
<uint32_t>(aOffset
) + aLength
;
1155 if (!endOffset
.isValid()) {
1159 if (mSelection
.isSome() && !mSelection
->IsCollapsed() &&
1160 aOffset
== mSelection
->StartOffset() && aLength
== mSelection
->Length()) {
1161 NS_WARNING_ASSERTION(!mSelection
->mRect
.IsEmpty(), "empty rect");
1162 aUnionTextRect
= mSelection
->mRect
;
1163 return !aUnionTextRect
.IsEmpty();
1168 NS_WARNING_ASSERTION(!mFirstCharRect
.IsEmpty(), "empty rect");
1169 aUnionTextRect
= mFirstCharRect
;
1170 return !aUnionTextRect
.IsEmpty();
1172 if (mSelection
.isSome() && mSelection
->mHasRange
) {
1173 if (aOffset
== mSelection
->mAnchor
) {
1174 NS_WARNING_ASSERTION(
1175 !mSelection
->mAnchorCharRects
[eNextCharRect
].IsEmpty(),
1177 aUnionTextRect
= mSelection
->mAnchorCharRects
[eNextCharRect
];
1178 return !aUnionTextRect
.IsEmpty();
1180 if (mSelection
->mAnchor
&& aOffset
== mSelection
->mAnchor
- 1) {
1181 NS_WARNING_ASSERTION(
1182 !mSelection
->mAnchorCharRects
[ePrevCharRect
].IsEmpty(),
1184 aUnionTextRect
= mSelection
->mAnchorCharRects
[ePrevCharRect
];
1185 return !aUnionTextRect
.IsEmpty();
1187 if (aOffset
== mSelection
->mFocus
) {
1188 NS_WARNING_ASSERTION(
1189 !mSelection
->mFocusCharRects
[eNextCharRect
].IsEmpty(),
1191 aUnionTextRect
= mSelection
->mFocusCharRects
[eNextCharRect
];
1192 return !aUnionTextRect
.IsEmpty();
1194 if (mSelection
->mFocus
&& aOffset
== mSelection
->mFocus
- 1) {
1195 NS_WARNING_ASSERTION(
1196 !mSelection
->mFocusCharRects
[ePrevCharRect
].IsEmpty(),
1198 aUnionTextRect
= mSelection
->mFocusCharRects
[ePrevCharRect
];
1199 return !aUnionTextRect
.IsEmpty();
1204 // Even if some text rects are not cached of the queried range,
1205 // we should return union rect when the first character's rect is cached
1206 // since the first character rect is important and the others are not so
1209 if (!aOffset
&& mSelection
.isSome() && mSelection
->mHasRange
&&
1210 aOffset
!= mSelection
->mAnchor
&& aOffset
!= mSelection
->mFocus
&&
1211 (mTextRectArray
.isNothing() ||
1212 !mTextRectArray
->IsOffsetInRange(aOffset
)) &&
1213 (mLastCommitStringTextRectArray
.isNothing() ||
1214 !mLastCommitStringTextRectArray
->IsOffsetInRange(aOffset
))) {
1215 // The first character rect isn't cached.
1219 // Use mLastCommitStringTextRectArray only when it overlaps with aOffset
1220 // even if aROundToExistingOffset is true for avoiding flickerling IME UI.
1221 // See the last comment in GetTextRect() for the detail.
1222 if (mLastCommitStringTextRectArray
.isSome() &&
1223 mLastCommitStringTextRectArray
->IsOverlappingWith(aOffset
, aLength
)) {
1225 mLastCommitStringTextRectArray
->GetUnionRectAsFarAsPossible(
1226 aOffset
, aLength
, aRoundToExistingOffset
);
1228 aUnionTextRect
.SetEmpty();
1231 if (mTextRectArray
.isSome() &&
1232 ((aRoundToExistingOffset
&& mTextRectArray
->HasRects()) ||
1233 mTextRectArray
->IsOverlappingWith(aOffset
, aLength
))) {
1235 aUnionTextRect
.Union(mTextRectArray
->GetUnionRectAsFarAsPossible(
1236 aOffset
, aLength
, aRoundToExistingOffset
));
1240 aUnionTextRect
= aUnionTextRect
.Union(mFirstCharRect
);
1242 if (mSelection
.isSome() && mSelection
->mHasRange
) {
1243 if (aOffset
<= mSelection
->mAnchor
&&
1244 mSelection
->mAnchor
< endOffset
.value()) {
1246 aUnionTextRect
.Union(mSelection
->mAnchorCharRects
[eNextCharRect
]);
1248 if (mSelection
->mAnchor
&& aOffset
<= mSelection
->mAnchor
- 1 &&
1249 mSelection
->mAnchor
- 1 < endOffset
.value()) {
1251 aUnionTextRect
.Union(mSelection
->mAnchorCharRects
[ePrevCharRect
]);
1253 if (aOffset
<= mSelection
->mFocus
&&
1254 mSelection
->mFocus
< endOffset
.value()) {
1256 aUnionTextRect
.Union(mSelection
->mFocusCharRects
[eNextCharRect
]);
1258 if (mSelection
->mFocus
&& aOffset
<= mSelection
->mFocus
- 1 &&
1259 mSelection
->mFocus
- 1 < endOffset
.value()) {
1261 aUnionTextRect
.Union(mSelection
->mFocusCharRects
[ePrevCharRect
]);
1265 return !aUnionTextRect
.IsEmpty();
1268 bool ContentCacheInParent::GetCaretRect(uint32_t aOffset
,
1269 bool aRoundToExistingOffset
,
1270 LayoutDeviceIntRect
& aCaretRect
) const {
1271 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
1272 ("0x%p GetCaretRect(aOffset=%u, aRoundToExistingOffset=%s), "
1273 "mCaret=%s, mTextRectArray=%s, mSelection=%s, mFirstCharRect=%s",
1274 this, aOffset
, GetBoolName(aRoundToExistingOffset
),
1275 ToString(mCaret
).c_str(), ToString(mTextRectArray
).c_str(),
1276 ToString(mSelection
).c_str(), ToString(mFirstCharRect
).c_str()));
1278 if (mCaret
.isSome() && mCaret
->mOffset
== aOffset
) {
1279 aCaretRect
= mCaret
->mRect
;
1283 // Guess caret rect from the text rect if it's stored.
1284 if (!GetTextRect(aOffset
, aRoundToExistingOffset
, aCaretRect
)) {
1285 // There might be previous character rect in the cache. If so, we can
1286 // guess the caret rect with it.
1288 !GetTextRect(aOffset
- 1, aRoundToExistingOffset
, aCaretRect
)) {
1289 aCaretRect
.SetEmpty();
1293 if (mSelection
.isSome() && mSelection
->mWritingMode
.IsVertical()) {
1294 aCaretRect
.MoveToY(aCaretRect
.YMost());
1296 // XXX bidi-unaware.
1297 aCaretRect
.MoveToX(aCaretRect
.XMost());
1301 // XXX This is not bidi aware because we don't cache each character's
1302 // direction. However, this is usually used by IME, so, assuming the
1303 // character is in LRT context must not cause any problem.
1304 if (mSelection
.isSome() && mSelection
->mWritingMode
.IsVertical()) {
1305 aCaretRect
.SetHeight(mCaret
.isSome() ? mCaret
->mRect
.Height() : 1);
1307 aCaretRect
.SetWidth(mCaret
.isSome() ? mCaret
->mRect
.Width() : 1);
1312 bool ContentCacheInParent::OnCompositionEvent(
1313 const WidgetCompositionEvent
& aCompositionEvent
) {
1315 sContentCacheLog
, LogLevel::Info
,
1316 ("0x%p OnCompositionEvent(aCompositionEvent={ "
1317 "mMessage=%s, mData=\"%s\", mRanges->Length()=%zu }), "
1318 "PendingEventsNeedingAck()=%u, WidgetHasComposition()=%s, "
1319 "mHandlingCompositions.Length()=%zu, HasPendingCommit()=%s, "
1320 "mIsChildIgnoringCompositionEvents=%s, mCommitStringByRequest=0x%p",
1321 this, ToChar(aCompositionEvent
.mMessage
),
1322 PrintStringDetail(aCompositionEvent
.mData
,
1323 PrintStringDetail::kMaxLengthForCompositionString
)
1325 aCompositionEvent
.mRanges
? aCompositionEvent
.mRanges
->Length() : 0,
1326 PendingEventsNeedingAck(), GetBoolName(WidgetHasComposition()),
1327 mHandlingCompositions
.Length(), GetBoolName(HasPendingCommit()),
1328 GetBoolName(mIsChildIgnoringCompositionEvents
), mCommitStringByRequest
));
1330 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1331 mDispatchedEventMessages
.AppendElement(aCompositionEvent
.mMessage
);
1332 #endif // #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1334 // We must be able to simulate the selection because
1335 // we might not receive selection updates in time
1336 if (!WidgetHasComposition()) {
1337 if (mCompositionStartInChild
.isSome()) {
1338 // If there is pending composition in the remote process, let's use
1339 // its start offset temporarily because this stores a lot of information
1340 // around it and the user must look around there, so, showing some UI
1341 // around it must make sense.
1342 mCompositionStart
= mCompositionStartInChild
;
1344 mCompositionStart
= Some(mSelection
.isSome() && mSelection
->mHasRange
1345 ? mSelection
->StartOffset()
1348 MOZ_ASSERT(aCompositionEvent
.mMessage
== eCompositionStart
);
1349 mHandlingCompositions
.AppendElement(
1350 HandlingCompositionData(aCompositionEvent
.mCompositionId
));
1353 mHandlingCompositions
.LastElement().mSentCommitEvent
=
1354 aCompositionEvent
.CausesDOMCompositionEndEvent();
1355 MOZ_ASSERT(mHandlingCompositions
.LastElement().mCompositionId
==
1356 aCompositionEvent
.mCompositionId
);
1358 if (!WidgetHasComposition()) {
1359 // mCompositionStart will be reset when commit event is completely handled
1360 // in the remote process.
1361 if (mHandlingCompositions
.Length() == 1u) {
1362 mPendingCommitLength
= aCompositionEvent
.mData
.Length();
1364 MOZ_ASSERT(HasPendingCommit());
1365 } else if (aCompositionEvent
.mMessage
!= eCompositionStart
) {
1366 mHandlingCompositions
.LastElement().mCompositionString
=
1367 aCompositionEvent
.mData
;
1370 // During REQUEST_TO_COMMIT_COMPOSITION or REQUEST_TO_CANCEL_COMPOSITION,
1371 // widget usually sends a eCompositionChange and/or eCompositionCommit event
1372 // to finalize or clear the composition, respectively. In this time,
1373 // we need to intercept all composition events here and pass the commit
1374 // string for returning to the remote process as a result of
1375 // RequestIMEToCommitComposition(). Then, eCommitComposition event will
1376 // be dispatched with the committed string in the remote process internally.
1377 if (mCommitStringByRequest
) {
1378 if (aCompositionEvent
.mMessage
== eCompositionCommitAsIs
) {
1379 *mCommitStringByRequest
=
1380 mHandlingCompositions
.LastElement().mCompositionString
;
1382 MOZ_ASSERT(aCompositionEvent
.mMessage
== eCompositionChange
||
1383 aCompositionEvent
.mMessage
== eCompositionCommit
);
1384 *mCommitStringByRequest
= aCompositionEvent
.mData
;
1386 // We need to wait eCompositionCommitRequestHandled from the remote process
1387 // in this case. Therefore, mPendingEventsNeedingAck needs to be
1388 // incremented here.
1389 if (!WidgetHasComposition()) {
1390 mHandlingCompositions
.LastElement().mPendingEventsNeedingAck
++;
1395 mHandlingCompositions
.LastElement().mPendingEventsNeedingAck
++;
1399 void ContentCacheInParent::OnSelectionEvent(
1400 const WidgetSelectionEvent
& aSelectionEvent
) {
1401 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
1402 ("0x%p OnSelectionEvent(aEvent={ "
1403 "mMessage=%s, mOffset=%u, mLength=%u, mReversed=%s, "
1404 "mExpandToClusterBoundary=%s, mUseNativeLineBreak=%s }), "
1405 "PendingEventsNeedingAck()=%u, WidgetHasComposition()=%s, "
1406 "mHandlingCompositions.Length()=%zu, HasPendingCommit()=%s, "
1407 "mIsChildIgnoringCompositionEvents=%s",
1408 this, ToChar(aSelectionEvent
.mMessage
), aSelectionEvent
.mOffset
,
1409 aSelectionEvent
.mLength
, GetBoolName(aSelectionEvent
.mReversed
),
1410 GetBoolName(aSelectionEvent
.mExpandToClusterBoundary
),
1411 GetBoolName(aSelectionEvent
.mUseNativeLineBreak
),
1412 PendingEventsNeedingAck(), GetBoolName(WidgetHasComposition()),
1413 mHandlingCompositions
.Length(), GetBoolName(HasPendingCommit()),
1414 GetBoolName(mIsChildIgnoringCompositionEvents
)));
1416 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1417 mDispatchedEventMessages
.AppendElement(aSelectionEvent
.mMessage
);
1418 #endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
1420 mPendingSetSelectionEventNeedingAck
++;
1423 void ContentCacheInParent::OnEventNeedingAckHandled(nsIWidget
* aWidget
,
1424 EventMessage aMessage
,
1425 uint32_t aCompositionId
) {
1426 // This is called when the child process receives WidgetCompositionEvent or
1427 // WidgetSelectionEvent.
1429 HandlingCompositionData
* handlingCompositionData
=
1430 aMessage
!= eSetSelection
? GetHandlingCompositionData(aCompositionId
)
1433 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
1434 ("0x%p OnEventNeedingAckHandled(aWidget=0x%p, aMessage=%s, "
1435 "aCompositionId=%" PRIu32
1436 "), PendingEventsNeedingAck()=%u, WidgetHasComposition()=%s, "
1437 "mHandlingCompositions.Length()=%zu, HasPendingCommit()=%s, "
1438 "mIsChildIgnoringCompositionEvents=%s, handlingCompositionData=0x%p",
1439 this, aWidget
, ToChar(aMessage
), aCompositionId
,
1440 PendingEventsNeedingAck(), GetBoolName(WidgetHasComposition()),
1441 mHandlingCompositions
.Length(), GetBoolName(HasPendingCommit()),
1442 GetBoolName(mIsChildIgnoringCompositionEvents
),
1443 handlingCompositionData
));
1445 // If we receive composition event messages for older one or invalid one,
1446 // we should ignore them.
1447 if (NS_WARN_IF(aMessage
!= eSetSelection
&& !handlingCompositionData
)) {
1451 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1452 mReceivedEventMessages
.AppendElement(aMessage
);
1453 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1455 const bool isCommittedInChild
=
1456 // Commit requester in the remote process has committed the composition.
1457 aMessage
== eCompositionCommitRequestHandled
||
1458 // The commit event has been handled normally in the remote process.
1459 (!mIsChildIgnoringCompositionEvents
&&
1460 WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage
));
1461 const bool hasPendingCommit
= HasPendingCommit();
1463 if (isCommittedInChild
) {
1464 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1465 if (mHandlingCompositions
.Length() == 1u) {
1466 RemoveUnnecessaryEventMessageLog();
1469 if (NS_WARN_IF(aMessage
!= eCompositionCommitRequestHandled
&&
1470 !handlingCompositionData
->mSentCommitEvent
)) {
1471 nsPrintfCString
info(
1472 "\nReceived unexpected commit event message (%s) which we've "
1475 AppendEventMessageLog(info
);
1476 CrashReporter::AppendAppNotesToCrashReport(info
);
1477 MOZ_DIAGNOSTIC_ASSERT(
1479 "Received unexpected commit event which has not been sent yet");
1481 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1483 // This should not occur, though. If we receive a commit notification for
1484 // not the oldest composition, we should forget all older compositions.
1485 size_t numberOfOutdatedCompositions
= 1u;
1486 for (auto& data
: mHandlingCompositions
) {
1487 if (&data
== handlingCompositionData
) {
1489 // Don't put the info into the log when we've already sent commit
1490 // event because it may be just inserting a character without
1491 // composing state, but the remote process may move focus at
1492 // eCompositionStart. This may happen with UI of IME to put only
1493 // one character, e.g., the default Emoji picker of Windows.
1494 !data
.mSentCommitEvent
&&
1495 // In the normal case, only one message should remain, however,
1496 // remaining 2 or more messages is also valid, for example, the
1497 // remote process may have a composition update listener which
1498 // takes a while. Then, we can have multiple pending messages.
1499 data
.mPendingEventsNeedingAck
>= 1u) {
1501 sContentCacheLog
, LogLevel::Debug
,
1502 (" NOTE: BrowserParent has %" PRIu32
1503 " pending composition messages for the handling composition, "
1504 "but before they are handled in the remote process, the active "
1505 "composition is commited by a request. "
1506 "OnEventNeedingAckHandled() calls for them will be ignored",
1507 data
.mPendingEventsNeedingAck
));
1511 if (MOZ_UNLIKELY(data
.mPendingEventsNeedingAck
)) {
1512 MOZ_LOG(sContentCacheLog
, LogLevel::Warning
,
1513 (" BrowserParent has %" PRIu32
1514 " pending composition messages for an older composition than "
1515 "the handling composition, but it'll be removed because newer "
1516 "composition gets comitted in the remote process",
1517 data
.mPendingEventsNeedingAck
));
1519 numberOfOutdatedCompositions
++;
1521 mHandlingCompositions
.RemoveElementsAt(0u, numberOfOutdatedCompositions
);
1522 handlingCompositionData
= nullptr;
1524 // Forget pending commit string length if it's handled in the remote
1525 // process. Note that this doesn't care too old composition's commit
1526 // string because in such case, we cannot return proper information
1527 // to IME synchronously.
1528 mPendingCommitLength
= 0;
1531 if (WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage
)) {
1532 // After the remote process receives eCompositionCommit(AsIs) event,
1533 // it'll restart to handle composition events.
1534 mIsChildIgnoringCompositionEvents
= false;
1536 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1537 if (NS_WARN_IF(!hasPendingCommit
)) {
1538 nsPrintfCString
info(
1539 "\nThere is no pending comment events but received "
1540 "%s message from the remote child\n\n",
1542 AppendEventMessageLog(info
);
1543 CrashReporter::AppendAppNotesToCrashReport(info
);
1544 MOZ_DIAGNOSTIC_ASSERT(
1546 "No pending commit events but received unexpected commit event");
1548 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1549 } else if (aMessage
== eCompositionCommitRequestHandled
&& hasPendingCommit
) {
1550 // If the remote process commits composition synchronously after
1551 // requesting commit composition and we've already sent commit composition,
1552 // it starts to ignore following composition events until receiving
1553 // eCompositionStart event.
1554 mIsChildIgnoringCompositionEvents
= true;
1557 // If neither widget (i.e., IME) nor the remote process has composition,
1558 // now, we can forget composition string informations.
1559 if (mHandlingCompositions
.IsEmpty()) {
1560 mCompositionStart
.reset();
1563 if (handlingCompositionData
) {
1564 if (NS_WARN_IF(!handlingCompositionData
->mPendingEventsNeedingAck
)) {
1565 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1566 nsPrintfCString
info(
1567 "\nThere is no pending events but received %s "
1568 "message from the remote child\n\n",
1570 AppendEventMessageLog(info
);
1571 CrashReporter::AppendAppNotesToCrashReport(info
);
1572 MOZ_DIAGNOSTIC_ASSERT(
1573 false, "No pending event message but received unexpected event");
1574 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1576 handlingCompositionData
->mPendingEventsNeedingAck
--;
1578 } else if (aMessage
== eSetSelection
) {
1579 if (NS_WARN_IF(!mPendingSetSelectionEventNeedingAck
)) {
1580 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1582 "\nThere is no pending set selection events but received from the "
1583 "remote child\n\n");
1584 AppendEventMessageLog(info
);
1585 CrashReporter::AppendAppNotesToCrashReport(info
);
1586 MOZ_DIAGNOSTIC_ASSERT(
1587 false, "No pending event message but received unexpected event");
1588 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1590 mPendingSetSelectionEventNeedingAck
--;
1594 if (!PendingEventsNeedingAck()) {
1595 FlushPendingNotifications(aWidget
);
1599 bool ContentCacheInParent::RequestIMEToCommitComposition(
1600 nsIWidget
* aWidget
, bool aCancel
, uint32_t aCompositionId
,
1601 nsAString
& aCommittedString
) {
1602 HandlingCompositionData
* const handlingCompositionData
=
1603 GetHandlingCompositionData(aCompositionId
);
1606 sContentCacheLog
, LogLevel::Info
,
1607 ("0x%p RequestToCommitComposition(aWidget=%p, "
1608 "aCancel=%s, aCompositionId=%" PRIu32
1609 "), mHandlingCompositions.Length()=%zu, HasPendingCommit()=%s, "
1610 "mIsChildIgnoringCompositionEvents=%s, "
1611 "IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)=%s, "
1612 "WidgetHasComposition()=%s, mCommitStringByRequest=%p, "
1613 "handlingCompositionData=0x%p",
1614 this, aWidget
, GetBoolName(aCancel
), aCompositionId
,
1615 mHandlingCompositions
.Length(), GetBoolName(HasPendingCommit()),
1616 GetBoolName(mIsChildIgnoringCompositionEvents
),
1618 IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent
)),
1619 GetBoolName(WidgetHasComposition()), mCommitStringByRequest
,
1620 handlingCompositionData
));
1622 MOZ_ASSERT(!mCommitStringByRequest
);
1624 // If we don't know the composition ID, it must have already been committed
1625 // in this process. In the case, we should do nothing here.
1626 if (NS_WARN_IF(!handlingCompositionData
)) {
1627 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1628 mRequestIMEToCommitCompositionResults
.AppendElement(
1629 RequestIMEToCommitCompositionResult::eToUnknownCompositionReceived
);
1630 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1634 // If we receive a commit result for not latest composition, this request is
1635 // too late for IME. The remote process should wait following composition
1636 // events for cleaning up TextComposition and handle the request as it's
1637 // handled asynchronously.
1638 if (handlingCompositionData
!= &mHandlingCompositions
.LastElement()) {
1639 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1640 mRequestIMEToCommitCompositionResults
.AppendElement(
1641 RequestIMEToCommitCompositionResult::eToOldCompositionReceived
);
1642 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1646 // If the composition has already been commit, th remote process will receive
1647 // composition events and clean up TextComposition. So, this should do
1648 // nothing and TextComposition should handle the request as it's handled
1650 // XXX Perhaps, this is wrong because TextComposition in child process
1651 // may commit the composition with current composition string in the
1652 // remote process. I.e., it may be different from actual commit string
1653 // which user typed. So, perhaps, we should return true and the commit
1655 if (handlingCompositionData
->mSentCommitEvent
) {
1656 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1657 mRequestIMEToCommitCompositionResults
.AppendElement(
1658 RequestIMEToCommitCompositionResult::eToCommittedCompositionReceived
);
1659 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1663 // If BrowserParent which has IME focus was already changed to different one,
1664 // the request shouldn't be sent to IME because it's too late.
1665 if (!IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent
)) {
1666 // Use the latest composition string which may not be handled in the
1667 // remote process for avoiding data loss.
1668 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1669 mRequestIMEToCommitCompositionResults
.AppendElement(
1670 RequestIMEToCommitCompositionResult::eReceivedAfterBrowserParentBlur
);
1671 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1672 aCommittedString
= handlingCompositionData
->mCompositionString
;
1673 // After we return true from here, i.e., without actually requesting IME
1674 // to commit composition, we will receive eCompositionCommitRequestHandled
1675 // pseudo event message from the remote process. So, we need to increment
1676 // mPendingEventsNeedingAck here.
1677 handlingCompositionData
->mPendingEventsNeedingAck
++;
1681 RefPtr
<TextComposition
> composition
=
1682 IMEStateManager::GetTextCompositionFor(aWidget
);
1683 if (NS_WARN_IF(!composition
)) {
1684 MOZ_LOG(sContentCacheLog
, LogLevel::Warning
,
1685 (" 0x%p RequestToCommitComposition(), "
1686 "does nothing due to no composition",
1688 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1689 mRequestIMEToCommitCompositionResults
.AppendElement(
1690 RequestIMEToCommitCompositionResult::eReceivedButNoTextComposition
);
1691 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1695 // If we receive a request for different composition, we must have already
1696 // sent a commit event. So the remote process should handle it.
1697 // XXX I think that this should never happen because we already checked
1698 // whether handlingCompositionData is the latest composition or not.
1699 // However, we don't want to commit different composition for the users.
1700 // Therefore, let's handle the odd case here.
1701 if (NS_WARN_IF(composition
->Id() != aCompositionId
)) {
1702 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1703 mRequestIMEToCommitCompositionResults
.AppendElement(
1704 RequestIMEToCommitCompositionResult::
1705 eReceivedButForDifferentTextComposition
);
1706 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1710 mCommitStringByRequest
= &aCommittedString
;
1712 // Request commit or cancel composition with TextComposition because we may
1713 // have already requested to commit or cancel the composition or we may
1714 // have already received eCompositionCommit(AsIs) event. Those status are
1715 // managed by composition. So, if we don't request commit composition,
1716 // we should do nothing with native IME here.
1717 composition
->RequestToCommit(aWidget
, aCancel
);
1719 mCommitStringByRequest
= nullptr;
1722 sContentCacheLog
, LogLevel::Info
,
1723 (" 0x%p RequestToCommitComposition(), "
1724 "WidgetHasComposition()=%s, the composition %s committed synchronously",
1725 this, GetBoolName(WidgetHasComposition()),
1726 composition
->Destroyed() ? "WAS" : "has NOT been"));
1728 if (!composition
->Destroyed()) {
1729 // When the composition isn't committed synchronously, the remote process's
1730 // TextComposition instance will synthesize commit events and wait to
1731 // receive delayed composition events. When TextComposition instances both
1732 // in this process and the remote process will be destroyed when delayed
1733 // composition events received. TextComposition instance in the parent
1734 // process will dispatch following composition events and be destroyed
1735 // normally. On the other hand, TextComposition instance in the remote
1736 // process won't dispatch following composition events and will be
1737 // destroyed by IMEStateManager::DispatchCompositionEvent().
1738 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1739 mRequestIMEToCommitCompositionResults
.AppendElement(
1740 RequestIMEToCommitCompositionResult::eHandledAsynchronously
);
1741 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1745 // When the composition is committed synchronously, the commit string will be
1746 // returned to the remote process. Then, PuppetWidget will dispatch
1747 // eCompositionCommit event with the returned commit string (i.e., the value
1748 // is aCommittedString of this method) and that causes destroying
1749 // TextComposition instance in the remote process (Note that TextComposition
1750 // instance in this process was already destroyed).
1751 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1752 mRequestIMEToCommitCompositionResults
.AppendElement(
1753 RequestIMEToCommitCompositionResult::eHandledSynchronously
);
1754 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1758 void ContentCacheInParent::MaybeNotifyIME(
1759 nsIWidget
* aWidget
, const IMENotification
& aNotification
) {
1760 if (!PendingEventsNeedingAck()) {
1761 IMEStateManager::NotifyIME(aNotification
, aWidget
, &mBrowserParent
);
1765 switch (aNotification
.mMessage
) {
1766 case NOTIFY_IME_OF_SELECTION_CHANGE
:
1767 mPendingSelectionChange
.MergeWith(aNotification
);
1769 case NOTIFY_IME_OF_TEXT_CHANGE
:
1770 mPendingTextChange
.MergeWith(aNotification
);
1772 case NOTIFY_IME_OF_POSITION_CHANGE
:
1773 mPendingLayoutChange
.MergeWith(aNotification
);
1775 case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED
:
1776 mPendingCompositionUpdate
.MergeWith(aNotification
);
1779 MOZ_CRASH("Unsupported notification");
1784 void ContentCacheInParent::FlushPendingNotifications(nsIWidget
* aWidget
) {
1785 MOZ_ASSERT(!PendingEventsNeedingAck());
1787 // If the BrowserParent's widget has already gone, this can do nothing since
1788 // widget is necessary to notify IME of something.
1793 // New notifications which are notified during flushing pending notifications
1794 // should be merged again.
1795 const bool pendingEventNeedingAckIncremented
=
1796 !mHandlingCompositions
.IsEmpty();
1797 if (pendingEventNeedingAckIncremented
) {
1798 mHandlingCompositions
.LastElement().mPendingEventsNeedingAck
++;
1801 nsCOMPtr
<nsIWidget
> widget
= aWidget
;
1803 // First, text change notification should be sent because selection change
1804 // notification notifies IME of current selection range in the latest content.
1805 // So, IME may need the latest content before that.
1806 if (mPendingTextChange
.HasNotification()) {
1807 IMENotification
notification(mPendingTextChange
);
1808 if (!widget
->Destroyed()) {
1809 mPendingTextChange
.Clear();
1810 IMEStateManager::NotifyIME(notification
, widget
, &mBrowserParent
);
1814 if (mPendingSelectionChange
.HasNotification()) {
1815 IMENotification
notification(mPendingSelectionChange
);
1816 if (!widget
->Destroyed()) {
1817 mPendingSelectionChange
.Clear();
1818 IMEStateManager::NotifyIME(notification
, widget
, &mBrowserParent
);
1822 // Layout change notification should be notified after selection change
1823 // notification because IME may want to query position of new caret position.
1824 if (mPendingLayoutChange
.HasNotification()) {
1825 IMENotification
notification(mPendingLayoutChange
);
1826 if (!widget
->Destroyed()) {
1827 mPendingLayoutChange
.Clear();
1828 IMEStateManager::NotifyIME(notification
, widget
, &mBrowserParent
);
1832 // Finally, send composition update notification because it notifies IME of
1833 // finishing handling whole sending events.
1834 if (mPendingCompositionUpdate
.HasNotification()) {
1835 IMENotification
notification(mPendingCompositionUpdate
);
1836 if (!widget
->Destroyed()) {
1837 mPendingCompositionUpdate
.Clear();
1838 IMEStateManager::NotifyIME(notification
, widget
, &mBrowserParent
);
1842 // Decrement it which was incremented above.
1843 if (!mHandlingCompositions
.IsEmpty() && pendingEventNeedingAckIncremented
&&
1844 mHandlingCompositions
.LastElement().mPendingEventsNeedingAck
) {
1845 mHandlingCompositions
.LastElement().mPendingEventsNeedingAck
--;
1848 if (!PendingEventsNeedingAck() && !widget
->Destroyed() &&
1849 (mPendingTextChange
.HasNotification() ||
1850 mPendingSelectionChange
.HasNotification() ||
1851 mPendingLayoutChange
.HasNotification() ||
1852 mPendingCompositionUpdate
.HasNotification())) {
1853 FlushPendingNotifications(widget
);
1857 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1859 void ContentCacheInParent::RemoveUnnecessaryEventMessageLog() {
1860 bool foundLastCompositionStart
= false;
1861 for (size_t i
= mDispatchedEventMessages
.Length(); i
> 1; i
--) {
1862 if (mDispatchedEventMessages
[i
- 1] != eCompositionStart
) {
1865 if (!foundLastCompositionStart
) {
1866 // Find previous eCompositionStart of the latest eCompositionStart.
1867 foundLastCompositionStart
= true;
1870 // Remove the messages before the last 2 sets of composition events.
1871 mDispatchedEventMessages
.RemoveElementsAt(0, i
- 1);
1874 uint32_t numberOfCompositionCommitRequestHandled
= 0;
1875 foundLastCompositionStart
= false;
1876 for (size_t i
= mReceivedEventMessages
.Length(); i
> 1; i
--) {
1877 if (mReceivedEventMessages
[i
- 1] == eCompositionCommitRequestHandled
) {
1878 numberOfCompositionCommitRequestHandled
++;
1880 if (mReceivedEventMessages
[i
- 1] != eCompositionStart
) {
1883 if (!foundLastCompositionStart
) {
1884 // Find previous eCompositionStart of the latest eCompositionStart.
1885 foundLastCompositionStart
= true;
1888 // Remove the messages before the last 2 sets of composition events.
1889 mReceivedEventMessages
.RemoveElementsAt(0, i
- 1);
1893 if (!numberOfCompositionCommitRequestHandled
) {
1894 // If there is no eCompositionCommitRequestHandled in
1895 // mReceivedEventMessages, we don't need to store log of
1896 // RequestIMEToCommmitComposition().
1897 mRequestIMEToCommitCompositionResults
.Clear();
1899 // We need to keep all reason of eCompositionCommitRequestHandled, which
1900 // is sent when mRequestIMEToCommitComposition() returns true.
1901 // So, we can discard older log than the first
1902 // eCompositionCommitRequestHandled in mReceivedEventMessages.
1903 for (size_t i
= mRequestIMEToCommitCompositionResults
.Length(); i
> 1;
1905 if (mRequestIMEToCommitCompositionResults
[i
- 1] ==
1906 RequestIMEToCommitCompositionResult::
1907 eReceivedAfterBrowserParentBlur
||
1908 mRequestIMEToCommitCompositionResults
[i
- 1] ==
1909 RequestIMEToCommitCompositionResult::eHandledSynchronously
) {
1910 --numberOfCompositionCommitRequestHandled
;
1911 if (!numberOfCompositionCommitRequestHandled
) {
1912 mRequestIMEToCommitCompositionResults
.RemoveElementsAt(0, i
- 1);
1920 void ContentCacheInParent::AppendEventMessageLog(nsACString
& aLog
) const {
1921 aLog
.AppendLiteral("Dispatched Event Message Log:\n");
1922 for (EventMessage message
: mDispatchedEventMessages
) {
1923 aLog
.AppendLiteral(" ");
1924 aLog
.Append(ToChar(message
));
1925 aLog
.AppendLiteral("\n");
1927 aLog
.AppendLiteral("\nReceived Event Message Log:\n");
1928 for (EventMessage message
: mReceivedEventMessages
) {
1929 aLog
.AppendLiteral(" ");
1930 aLog
.Append(ToChar(message
));
1931 aLog
.AppendLiteral("\n");
1933 aLog
.AppendLiteral("\nResult of RequestIMEToCommitComposition():\n");
1934 for (RequestIMEToCommitCompositionResult result
:
1935 mRequestIMEToCommitCompositionResults
) {
1936 aLog
.AppendLiteral(" ");
1937 aLog
.Append(ToReadableText(result
));
1938 aLog
.AppendLiteral("\n");
1940 aLog
.AppendLiteral("\n");
1943 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1945 /*****************************************************************************
1946 * mozilla::ContentCache::Selection
1947 *****************************************************************************/
1949 ContentCache::Selection::Selection(
1950 const WidgetQueryContentEvent
& aQuerySelectedTextEvent
)
1951 : mAnchor(UINT32_MAX
),
1953 mWritingMode(aQuerySelectedTextEvent
.mReply
->WritingModeRef()),
1954 mHasRange(aQuerySelectedTextEvent
.mReply
->mOffsetAndData
.isSome()) {
1955 MOZ_ASSERT(aQuerySelectedTextEvent
.mMessage
== eQuerySelectedText
);
1956 MOZ_ASSERT(aQuerySelectedTextEvent
.Succeeded());
1958 mAnchor
= aQuerySelectedTextEvent
.mReply
->AnchorOffset();
1959 mFocus
= aQuerySelectedTextEvent
.mReply
->FocusOffset();
1963 /*****************************************************************************
1964 * mozilla::ContentCache::TextRectArray
1965 *****************************************************************************/
1967 LayoutDeviceIntRect
ContentCache::TextRectArray::GetRect(
1968 uint32_t aOffset
) const {
1969 LayoutDeviceIntRect rect
;
1970 if (IsOffsetInRange(aOffset
)) {
1971 rect
= mRects
[aOffset
- mStart
];
1976 LayoutDeviceIntRect
ContentCache::TextRectArray::GetUnionRect(
1977 uint32_t aOffset
, uint32_t aLength
) const {
1978 LayoutDeviceIntRect rect
;
1979 if (!IsRangeCompletelyInRange(aOffset
, aLength
)) {
1982 for (uint32_t i
= 0; i
< aLength
; i
++) {
1983 rect
= rect
.Union(mRects
[aOffset
- mStart
+ i
]);
1988 LayoutDeviceIntRect
ContentCache::TextRectArray::GetUnionRectAsFarAsPossible(
1989 uint32_t aOffset
, uint32_t aLength
, bool aRoundToExistingOffset
) const {
1990 LayoutDeviceIntRect rect
;
1992 (!aRoundToExistingOffset
&& !IsOverlappingWith(aOffset
, aLength
))) {
1995 uint32_t startOffset
= std::max(aOffset
, mStart
);
1996 if (aRoundToExistingOffset
&& startOffset
>= EndOffset()) {
1997 startOffset
= EndOffset() - 1;
1999 uint32_t endOffset
= std::min(aOffset
+ aLength
, EndOffset());
2000 if (aRoundToExistingOffset
&& endOffset
< mStart
+ 1) {
2001 endOffset
= mStart
+ 1;
2003 if (NS_WARN_IF(endOffset
< startOffset
)) {
2006 for (uint32_t i
= 0; i
< endOffset
- startOffset
; i
++) {
2007 rect
= rect
.Union(mRects
[startOffset
- mStart
+ i
]);
2012 } // namespace mozilla