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 /*****************************************************************************
80 * mozilla::ContentCacheInChild
81 *****************************************************************************/
83 void ContentCacheInChild::Clear() {
84 MOZ_LOG(sContentCacheLog
, LogLevel::Info
, ("0x%p Clear()", this));
86 mCompositionStart
.reset();
90 mFirstCharRect
.SetEmpty();
92 mTextRectArray
.reset();
93 mLastCommitStringTextRectArray
.reset();
94 mEditorRect
.SetEmpty();
97 void ContentCacheInChild::OnCompositionEvent(
98 const WidgetCompositionEvent
& aCompositionEvent
) {
99 if (aCompositionEvent
.CausesDOMCompositionEndEvent()) {
100 RefPtr
<TextComposition
> composition
=
101 IMEStateManager::GetTextCompositionFor(aCompositionEvent
.mWidget
);
103 nsAutoString lastCommitString
;
104 if (aCompositionEvent
.mMessage
== eCompositionCommitAsIs
) {
105 lastCommitString
= composition
->CommitStringIfCommittedAsIs();
107 lastCommitString
= aCompositionEvent
.mData
;
109 // We don't need to store canceling information because this is required
110 // by undoing of last commit (Kakutei-Undo of Japanese IME).
111 if (!lastCommitString
.IsEmpty()) {
112 mLastCommit
= Some(OffsetAndData
<uint32_t>(
113 composition
->NativeOffsetOfStartComposition(), lastCommitString
));
115 sContentCacheLog
, LogLevel::Debug
,
116 ("0x%p OnCompositionEvent(), stored last composition string data "
117 "(aCompositionEvent={ mMessage=%s, mData=\"%s\"}, mLastCommit=%s)",
118 this, ToChar(aCompositionEvent
.mMessage
),
120 aCompositionEvent
.mData
,
121 PrintStringDetail::kMaxLengthForCompositionString
)
123 ToString(mLastCommit
).c_str()));
128 if (mLastCommit
.isSome()) {
130 sContentCacheLog
, LogLevel::Debug
,
131 ("0x%p OnCompositionEvent(), resetting the last composition string "
132 "data (aCompositionEvent={ mMessage=%s, mData=\"%s\"}, "
134 this, ToChar(aCompositionEvent
.mMessage
),
135 PrintStringDetail(aCompositionEvent
.mData
,
136 PrintStringDetail::kMaxLengthForCompositionString
)
138 ToString(mLastCommit
).c_str()));
143 bool ContentCacheInChild::CacheAll(nsIWidget
* aWidget
,
144 const IMENotification
* aNotification
) {
145 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
146 ("0x%p CacheAll(aWidget=0x%p, aNotification=%s)", this, aWidget
,
147 GetNotificationName(aNotification
)));
149 const bool textCached
= CacheText(aWidget
, aNotification
);
150 const bool editorRectCached
= CacheEditorRect(aWidget
, aNotification
);
151 MOZ_DIAGNOSTIC_ASSERT(IsValid());
152 return textCached
|| editorRectCached
;
155 bool ContentCacheInChild::CacheSelection(nsIWidget
* aWidget
,
156 const IMENotification
* aNotification
) {
158 sContentCacheLog
, LogLevel::Info
,
159 ("0x%p CacheSelection(aWidget=0x%p, aNotification=%s), mText=%s", this,
160 aWidget
, GetNotificationName(aNotification
),
161 PrintStringDetail(mText
, PrintStringDetail::kMaxLengthForEditor
).get()));
166 if (mText
.isNothing()) {
170 nsEventStatus status
= nsEventStatus_eIgnore
;
171 WidgetQueryContentEvent
querySelectedTextEvent(true, eQuerySelectedText
,
173 aWidget
->DispatchEvent(&querySelectedTextEvent
, status
);
174 if (NS_WARN_IF(querySelectedTextEvent
.Failed())) {
176 sContentCacheLog
, LogLevel::Error
,
177 ("0x%p CacheSelection(), FAILED, couldn't retrieve the selected text",
179 // XXX Allowing selection-independent character rects makes things
180 // complicated in the parent...
182 // ContentCache should store only editable content. Therefore, if current
183 // selection root is not editable, we don't need to store the selection, i.e.,
184 // let's treat it as there is no selection. However, if we already have
185 // previously editable text, let's store the selection even if it becomes
186 // uneditable because not doing so would create odd situation. E.g., IME may
187 // fail only querying selection after succeeded querying text.
188 else if (NS_WARN_IF(!querySelectedTextEvent
.mReply
->mIsEditableContent
)) {
189 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
190 ("0x%p CacheSelection(), FAILED, editable content had already been "
193 MOZ_DIAGNOSTIC_ASSERT(IsValid());
196 mSelection
.emplace(querySelectedTextEvent
);
199 return CacheCaretAndTextRects(aWidget
, aNotification
) ||
200 querySelectedTextEvent
.Succeeded();
203 bool ContentCacheInChild::CacheCaret(nsIWidget
* aWidget
,
204 const IMENotification
* aNotification
) {
207 if (mSelection
.isNothing()) {
211 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
212 ("0x%p CacheCaret(aWidget=0x%p, aNotification=%s)", this, aWidget
,
213 GetNotificationName(aNotification
)));
215 if (mSelection
->mHasRange
) {
216 // XXX Should be mSelection.mFocus?
217 const uint32_t offset
= mSelection
->StartOffset();
219 nsEventStatus status
= nsEventStatus_eIgnore
;
220 WidgetQueryContentEvent
queryCaretRectEvent(true, eQueryCaretRect
, aWidget
);
221 queryCaretRectEvent
.InitForQueryCaretRect(offset
);
222 aWidget
->DispatchEvent(&queryCaretRectEvent
, status
);
223 if (NS_WARN_IF(queryCaretRectEvent
.Failed())) {
224 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
225 ("0x%p CacheCaret(), FAILED, couldn't retrieve the caret rect "
230 mCaret
.emplace(offset
, queryCaretRectEvent
.mReply
->mRect
);
232 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
233 ("0x%p CacheCaret(), Succeeded, mSelection=%s, mCaret=%s", this,
234 ToString(mSelection
).c_str(), ToString(mCaret
).c_str()));
235 MOZ_DIAGNOSTIC_ASSERT(IsValid());
239 bool ContentCacheInChild::CacheEditorRect(
240 nsIWidget
* aWidget
, const IMENotification
* aNotification
) {
241 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
242 ("0x%p CacheEditorRect(aWidget=0x%p, aNotification=%s)", this,
243 aWidget
, GetNotificationName(aNotification
)));
245 nsEventStatus status
= nsEventStatus_eIgnore
;
246 WidgetQueryContentEvent
queryEditorRectEvent(true, eQueryEditorRect
, aWidget
);
247 aWidget
->DispatchEvent(&queryEditorRectEvent
, status
);
248 if (NS_WARN_IF(queryEditorRectEvent
.Failed())) {
250 sContentCacheLog
, LogLevel::Error
,
251 ("0x%p CacheEditorRect(), FAILED, couldn't retrieve the editor rect",
255 // ContentCache should store only editable content. Therefore, if current
256 // selection root is not editable, we don't need to store the editor rect,
257 // i.e., let's treat it as there is no focused editor.
258 if (NS_WARN_IF(!queryEditorRectEvent
.mReply
->mIsEditableContent
)) {
259 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
260 ("0x%p CacheText(), FAILED, editable content had already been "
265 mEditorRect
= queryEditorRectEvent
.mReply
->mRect
;
266 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
267 ("0x%p CacheEditorRect(), Succeeded, mEditorRect=%s", this,
268 ToString(mEditorRect
).c_str()));
272 bool ContentCacheInChild::CacheCaretAndTextRects(
273 nsIWidget
* aWidget
, const IMENotification
* aNotification
) {
274 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
275 ("0x%p CacheCaretAndTextRects(aWidget=0x%p, aNotification=%s)", this,
276 aWidget
, GetNotificationName(aNotification
)));
278 const bool caretCached
= CacheCaret(aWidget
, aNotification
);
279 const bool textRectsCached
= CacheTextRects(aWidget
, aNotification
);
280 MOZ_DIAGNOSTIC_ASSERT(IsValid());
281 return caretCached
|| textRectsCached
;
284 bool ContentCacheInChild::CacheText(nsIWidget
* aWidget
,
285 const IMENotification
* aNotification
) {
286 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
287 ("0x%p CacheText(aWidget=0x%p, aNotification=%s)", this, aWidget
,
288 GetNotificationName(aNotification
)));
290 nsEventStatus status
= nsEventStatus_eIgnore
;
291 WidgetQueryContentEvent
queryTextContentEvent(true, eQueryTextContent
,
293 queryTextContentEvent
.InitForQueryTextContent(0, UINT32_MAX
);
294 aWidget
->DispatchEvent(&queryTextContentEvent
, status
);
295 if (NS_WARN_IF(queryTextContentEvent
.Failed())) {
296 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
297 ("0x%p CacheText(), FAILED, couldn't retrieve whole text", this));
300 // ContentCache should store only editable content. Therefore, if current
301 // selection root is not editable, we don't need to store the text, i.e.,
302 // let's treat it as there is no editable text.
303 else if (NS_WARN_IF(!queryTextContentEvent
.mReply
->mIsEditableContent
)) {
304 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
305 ("0x%p CacheText(), FAILED, editable content had already been "
310 mText
= Some(nsString(queryTextContentEvent
.mReply
->DataRef()));
311 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
312 ("0x%p CacheText(), Succeeded, mText=%s", this,
313 PrintStringDetail(mText
, PrintStringDetail::kMaxLengthForEditor
)
317 // Forget last commit range if string in the range is different from the
318 // last commit string.
319 if (mLastCommit
.isSome() &&
320 (mText
.isNothing() ||
321 nsDependentSubstring(mText
.ref(), mLastCommit
->StartOffset(),
322 mLastCommit
->Length()) != mLastCommit
->DataRef())) {
323 MOZ_LOG(sContentCacheLog
, LogLevel::Debug
,
324 ("0x%p CacheText(), resetting the last composition string data "
325 "(mLastCommit=%s, current string=\"%s\")",
326 this, ToString(mLastCommit
).c_str(),
328 nsDependentSubstring(mText
.ref(), mLastCommit
->StartOffset(),
329 mLastCommit
->Length()),
330 PrintStringDetail::kMaxLengthForCompositionString
)
335 // If we fail to get editable text content, it must mean that there is no
336 // focused element anymore or focused element is not editable. In this case,
337 // we should not get selection of non-editable content
338 if (MOZ_UNLIKELY(mText
.isNothing())) {
341 mTextRectArray
.reset();
342 MOZ_DIAGNOSTIC_ASSERT(IsValid());
346 return CacheSelection(aWidget
, aNotification
);
349 bool ContentCacheInChild::QueryCharRect(nsIWidget
* aWidget
, uint32_t aOffset
,
350 LayoutDeviceIntRect
& aCharRect
) const {
351 aCharRect
.SetEmpty();
353 nsEventStatus status
= nsEventStatus_eIgnore
;
354 WidgetQueryContentEvent
queryTextRectEvent(true, eQueryTextRect
, aWidget
);
355 queryTextRectEvent
.InitForQueryTextRect(aOffset
, 1);
356 aWidget
->DispatchEvent(&queryTextRectEvent
, status
);
357 if (NS_WARN_IF(queryTextRectEvent
.Failed())) {
360 aCharRect
= queryTextRectEvent
.mReply
->mRect
;
362 // Guarantee the rect is not empty.
363 if (NS_WARN_IF(!aCharRect
.Height())) {
364 aCharRect
.SetHeight(1);
366 if (NS_WARN_IF(!aCharRect
.Width())) {
367 aCharRect
.SetWidth(1);
372 bool ContentCacheInChild::QueryCharRectArray(nsIWidget
* aWidget
,
373 uint32_t aOffset
, uint32_t aLength
,
374 RectArray
& aCharRectArray
) const {
375 nsEventStatus status
= nsEventStatus_eIgnore
;
376 WidgetQueryContentEvent
queryTextRectsEvent(true, eQueryTextRectArray
,
378 queryTextRectsEvent
.InitForQueryTextRectArray(aOffset
, aLength
);
379 aWidget
->DispatchEvent(&queryTextRectsEvent
, status
);
380 if (NS_WARN_IF(queryTextRectsEvent
.Failed())) {
381 aCharRectArray
.Clear();
384 aCharRectArray
= std::move(queryTextRectsEvent
.mReply
->mRectArray
);
388 bool ContentCacheInChild::CacheTextRects(nsIWidget
* aWidget
,
389 const IMENotification
* aNotification
) {
391 sContentCacheLog
, LogLevel::Info
,
392 ("0x%p CacheTextRects(aWidget=0x%p, aNotification=%s), mCaret=%s", this,
393 aWidget
, GetNotificationName(aNotification
), ToString(mCaret
).c_str()));
395 if (mSelection
.isSome()) {
396 mSelection
->ClearRects();
399 // Retrieve text rects in composition string if there is.
400 RefPtr
<TextComposition
> textComposition
=
401 IMEStateManager::GetTextCompositionFor(aWidget
);
402 if (textComposition
) {
403 // mCompositionStart may be updated by some composition event handlers.
404 // So, let's update it with the latest information.
405 mCompositionStart
= Some(textComposition
->NativeOffsetOfStartComposition());
406 // Note that TextComposition::String() may not be modified here because
407 // it's modified after all edit action listeners are performed but this
408 // is called while some of them are performed.
409 // FYI: For supporting IME which commits composition and restart new
410 // composition immediately, we should cache next character of current
412 uint32_t length
= textComposition
->LastData().Length() + 1;
413 mTextRectArray
= Some(TextRectArray(mCompositionStart
.value()));
414 if (NS_WARN_IF(!QueryCharRectArray(aWidget
, mTextRectArray
->mStart
, length
,
415 mTextRectArray
->mRects
))) {
416 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
417 ("0x%p CacheTextRects(), FAILED, "
418 "couldn't retrieve text rect array of the composition string",
420 mTextRectArray
.reset();
423 mCompositionStart
.reset();
424 mTextRectArray
.reset();
427 if (mSelection
.isSome()) {
428 // Set mSelection->mAnchorCharRects
429 // If we've already have the rect in mTextRectArray, save the query cost.
430 if (mSelection
->mHasRange
&& mTextRectArray
.isSome() &&
431 mTextRectArray
->IsOffsetInRange(mSelection
->mAnchor
) &&
432 (!mSelection
->mAnchor
||
433 mTextRectArray
->IsOffsetInRange(mSelection
->mAnchor
- 1))) {
434 mSelection
->mAnchorCharRects
[eNextCharRect
] =
435 mTextRectArray
->GetRect(mSelection
->mAnchor
);
436 if (mSelection
->mAnchor
) {
437 mSelection
->mAnchorCharRects
[ePrevCharRect
] =
438 mTextRectArray
->GetRect(mSelection
->mAnchor
- 1);
441 // Otherwise, get it from content even if there is no selection ranges.
444 const uint32_t startOffset
= mSelection
->mHasRange
&& mSelection
->mAnchor
445 ? mSelection
->mAnchor
- 1u
447 const uint32_t length
=
448 mSelection
->mHasRange
&& mSelection
->mAnchor
? 2u : 1u;
450 !QueryCharRectArray(aWidget
, startOffset
, length
, rects
))) {
452 sContentCacheLog
, LogLevel::Error
,
453 ("0x%p CacheTextRects(), FAILED, couldn't retrieve text rect "
454 "array around the selection anchor (%s)",
456 mSelection
? ToString(mSelection
->mAnchor
).c_str() : "Nothing"));
457 MOZ_ASSERT_IF(mSelection
.isSome(),
458 mSelection
->mAnchorCharRects
[ePrevCharRect
].IsEmpty());
459 MOZ_ASSERT_IF(mSelection
.isSome(),
460 mSelection
->mAnchorCharRects
[eNextCharRect
].IsEmpty());
461 } else if (rects
.Length()) {
462 if (rects
.Length() > 1) {
463 mSelection
->mAnchorCharRects
[ePrevCharRect
] = rects
[0];
464 mSelection
->mAnchorCharRects
[eNextCharRect
] = rects
[1];
466 mSelection
->mAnchorCharRects
[eNextCharRect
] = rects
[0];
467 MOZ_ASSERT(mSelection
->mAnchorCharRects
[ePrevCharRect
].IsEmpty());
472 // Set mSelection->mFocusCharRects
473 // If selection is collapsed (including no selection case), the focus char
474 // rects are same as the anchor char rects so that we can just copy them.
475 if (mSelection
->IsCollapsed()) {
476 mSelection
->mFocusCharRects
[0] = mSelection
->mAnchorCharRects
[0];
477 mSelection
->mFocusCharRects
[1] = mSelection
->mAnchorCharRects
[1];
479 // If the selection range is in mTextRectArray, save the query cost.
480 else if (mTextRectArray
.isSome() &&
481 mTextRectArray
->IsOffsetInRange(mSelection
->mFocus
) &&
482 (!mSelection
->mFocus
||
483 mTextRectArray
->IsOffsetInRange(mSelection
->mFocus
- 1))) {
484 MOZ_ASSERT(mSelection
->mHasRange
);
485 mSelection
->mFocusCharRects
[eNextCharRect
] =
486 mTextRectArray
->GetRect(mSelection
->mFocus
);
487 if (mSelection
->mFocus
) {
488 mSelection
->mFocusCharRects
[ePrevCharRect
] =
489 mTextRectArray
->GetRect(mSelection
->mFocus
- 1);
492 // Otherwise, including no selection range cases, need to query the rects.
494 MOZ_ASSERT(mSelection
->mHasRange
);
496 const uint32_t startOffset
=
497 mSelection
->mFocus
? mSelection
->mFocus
- 1u : 0u;
498 const uint32_t length
= mSelection
->mFocus
? 2u : 1u;
500 !QueryCharRectArray(aWidget
, startOffset
, length
, rects
))) {
502 sContentCacheLog
, LogLevel::Error
,
503 ("0x%p CacheTextRects(), FAILED, couldn't retrieve text rect "
504 "array around the selection focus (%s)",
506 mSelection
? ToString(mSelection
->mFocus
).c_str() : "Nothing"));
507 MOZ_ASSERT_IF(mSelection
.isSome(),
508 mSelection
->mFocusCharRects
[ePrevCharRect
].IsEmpty());
509 MOZ_ASSERT_IF(mSelection
.isSome(),
510 mSelection
->mFocusCharRects
[eNextCharRect
].IsEmpty());
511 } else if (NS_WARN_IF(mSelection
.isNothing())) {
512 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
513 ("0x%p CacheTextRects(), FAILED, mSelection was reset during "
514 "the call of QueryCharRectArray",
517 if (rects
.Length() > 1) {
518 mSelection
->mFocusCharRects
[ePrevCharRect
] = rects
[0];
519 mSelection
->mFocusCharRects
[eNextCharRect
] = rects
[1];
520 } else if (rects
.Length()) {
521 mSelection
->mFocusCharRects
[eNextCharRect
] = rects
[0];
522 MOZ_ASSERT(mSelection
->mFocusCharRects
[ePrevCharRect
].IsEmpty());
528 // If there is a non-collapsed selection range, let's query the whole selected
529 // text rect. Note that the result cannot be computed from first character
530 // rect and last character rect of the selection because they both may be in
531 // middle of different line.
532 if (mSelection
.isSome() && mSelection
->mHasRange
&&
533 !mSelection
->IsCollapsed()) {
534 nsEventStatus status
= nsEventStatus_eIgnore
;
535 WidgetQueryContentEvent
queryTextRectEvent(true, eQueryTextRect
, aWidget
);
536 queryTextRectEvent
.InitForQueryTextRect(mSelection
->StartOffset(),
537 mSelection
->Length());
538 aWidget
->DispatchEvent(&queryTextRectEvent
, status
);
539 if (NS_WARN_IF(queryTextRectEvent
.Failed())) {
540 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
541 ("0x%p CacheTextRects(), FAILED, "
542 "couldn't retrieve text rect of whole selected text",
545 mSelection
->mRect
= queryTextRectEvent
.mReply
->mRect
;
549 // Even if there is no selection range, we should have the first character
550 // rect for the last resort of suggesting position of IME UI.
551 if (mSelection
.isSome() && mSelection
->mHasRange
&& !mSelection
->mFocus
) {
552 mFirstCharRect
= mSelection
->mFocusCharRects
[eNextCharRect
];
553 } else if (mSelection
.isSome() && mSelection
->mHasRange
&&
554 mSelection
->mFocus
== 1) {
555 mFirstCharRect
= mSelection
->mFocusCharRects
[ePrevCharRect
];
556 } else if (mSelection
.isSome() && mSelection
->mHasRange
&&
557 !mSelection
->mAnchor
) {
558 mFirstCharRect
= mSelection
->mAnchorCharRects
[eNextCharRect
];
559 } else if (mSelection
.isSome() && mSelection
->mHasRange
&&
560 mSelection
->mAnchor
== 1) {
561 mFirstCharRect
= mSelection
->mFocusCharRects
[ePrevCharRect
];
562 } else if (mTextRectArray
.isSome() && mTextRectArray
->IsOffsetInRange(0u)) {
563 mFirstCharRect
= mTextRectArray
->GetRect(0u);
565 LayoutDeviceIntRect charRect
;
566 if (MOZ_UNLIKELY(NS_WARN_IF(!QueryCharRect(aWidget
, 0, charRect
)))) {
567 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
568 ("0x%p CacheTextRects(), FAILED, "
569 "couldn't retrieve first char rect",
571 mFirstCharRect
.SetEmpty();
573 mFirstCharRect
= charRect
;
577 // Finally, let's cache the last commit string's character rects until
578 // selection change or something other editing because user may reconvert
579 // or undo the last commit. Then, IME requires the character rects for
580 // positioning their UI.
581 if (mLastCommit
.isSome()) {
582 mLastCommitStringTextRectArray
=
583 Some(TextRectArray(mLastCommit
->StartOffset()));
584 if (mLastCommit
->Length() == 1 && mSelection
.isSome() &&
585 mSelection
->mHasRange
&&
586 mSelection
->mAnchor
- 1 == mLastCommit
->StartOffset() &&
587 !mSelection
->mAnchorCharRects
[ePrevCharRect
].IsEmpty()) {
588 mLastCommitStringTextRectArray
->mRects
.AppendElement(
589 mSelection
->mAnchorCharRects
[ePrevCharRect
]);
590 } else if (NS_WARN_IF(!QueryCharRectArray(
591 aWidget
, mLastCommit
->StartOffset(), mLastCommit
->Length(),
592 mLastCommitStringTextRectArray
->mRects
))) {
593 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
594 ("0x%p CacheTextRects(), FAILED, "
595 "couldn't retrieve text rect array of the last commit string",
597 mLastCommitStringTextRectArray
.reset();
600 MOZ_ASSERT((mLastCommitStringTextRectArray
.isSome()
601 ? mLastCommitStringTextRectArray
->mRects
.Length()
602 : 0) == (mLastCommit
.isSome() ? mLastCommit
->Length() : 0));
604 mLastCommitStringTextRectArray
.reset();
608 sContentCacheLog
, LogLevel::Info
,
609 ("0x%p CacheTextRects(), Succeeded, "
610 "mText=%s, mTextRectArray=%s, mSelection=%s, "
611 "mFirstCharRect=%s, mLastCommitStringTextRectArray=%s",
613 PrintStringDetail(mText
, PrintStringDetail::kMaxLengthForEditor
).get(),
614 ToString(mTextRectArray
).c_str(), ToString(mSelection
).c_str(),
615 ToString(mFirstCharRect
).c_str(),
616 ToString(mLastCommitStringTextRectArray
).c_str()));
617 MOZ_DIAGNOSTIC_ASSERT(IsValid());
621 bool ContentCacheInChild::SetSelection(
623 const IMENotification::SelectionChangeDataBase
& aSelectionChangeData
) {
625 sContentCacheLog
, LogLevel::Info
,
626 ("0x%p SetSelection(aSelectionChangeData=%s), mText=%s", this,
627 ToString(aSelectionChangeData
).c_str(),
628 PrintStringDetail(mText
, PrintStringDetail::kMaxLengthForEditor
).get()));
630 if (MOZ_UNLIKELY(mText
.isNothing())) {
634 mSelection
= Some(Selection(aSelectionChangeData
));
636 if (mLastCommit
.isSome()) {
637 // Forget last commit string range if selection is not collapsed
638 // at end of the last commit string.
639 if (!mSelection
->mHasRange
|| !mSelection
->IsCollapsed() ||
640 mSelection
->mAnchor
!= mLastCommit
->EndOffset()) {
642 sContentCacheLog
, LogLevel::Debug
,
643 ("0x%p SetSelection(), forgetting last commit composition data "
644 "(mSelection=%s, mLastCommit=%s)",
645 this, ToString(mSelection
).c_str(), ToString(mLastCommit
).c_str()));
651 CacheTextRects(aWidget
);
653 return mSelection
.isSome();
656 /*****************************************************************************
657 * mozilla::ContentCacheInParent
658 *****************************************************************************/
660 ContentCacheInParent::ContentCacheInParent(BrowserParent
& aBrowserParent
)
662 mBrowserParent(aBrowserParent
),
663 mCommitStringByRequest(nullptr),
664 mPendingCommitLength(0),
665 mIsChildIgnoringCompositionEvents(false) {}
667 void ContentCacheInParent::AssignContent(const ContentCache
& aOther
,
669 const IMENotification
* aNotification
) {
670 MOZ_DIAGNOSTIC_ASSERT(aOther
.IsValid());
672 mText
= aOther
.mText
;
673 mSelection
= aOther
.mSelection
;
674 mFirstCharRect
= aOther
.mFirstCharRect
;
675 mCaret
= aOther
.mCaret
;
676 mTextRectArray
= aOther
.mTextRectArray
;
677 mLastCommitStringTextRectArray
= aOther
.mLastCommitStringTextRectArray
;
678 mEditorRect
= aOther
.mEditorRect
;
680 // Only when there is one composition, the TextComposition instance in this
681 // process is managing the composition in the remote process. Therefore,
682 // we shouldn't update composition start offset of TextComposition with
683 // old composition which is still being handled by the child process.
684 if (WidgetHasComposition() && mHandlingCompositions
.Length() == 1 &&
685 mCompositionStart
.isSome()) {
686 IMEStateManager::MaybeStartOffsetUpdatedInChild(aWidget
,
687 mCompositionStart
.value());
690 // When this instance allows to query content relative to composition string,
691 // we should modify mCompositionStart with the latest information in the
692 // remote process because now we have the information around the composition
694 mCompositionStartInChild
= aOther
.mCompositionStart
;
695 if (WidgetHasComposition() || HasPendingCommit()) {
696 if (mCompositionStartInChild
.isSome()) {
697 if (mCompositionStart
.valueOr(UINT32_MAX
) !=
698 mCompositionStartInChild
.value()) {
699 mCompositionStart
= mCompositionStartInChild
;
700 mPendingCommitLength
= 0;
702 } else if (mCompositionStart
.isSome() && mSelection
.isSome() &&
703 mSelection
->mHasRange
&&
704 mCompositionStart
.value() != mSelection
->StartOffset()) {
705 mCompositionStart
= Some(mSelection
->StartOffset());
706 mPendingCommitLength
= 0;
711 sContentCacheLog
, LogLevel::Info
,
712 ("0x%p AssignContent(aNotification=%s), "
713 "Succeeded, mText=%s, mSelection=%s, mFirstCharRect=%s, "
714 "mCaret=%s, mTextRectArray=%s, WidgetHasComposition()=%s, "
715 "mHandlingCompositions.Length()=%zu, mCompositionStart=%s, "
716 "mPendingCommitLength=%u, mEditorRect=%s, "
717 "mLastCommitStringTextRectArray=%s",
718 this, GetNotificationName(aNotification
),
719 PrintStringDetail(mText
, PrintStringDetail::kMaxLengthForEditor
).get(),
720 ToString(mSelection
).c_str(), ToString(mFirstCharRect
).c_str(),
721 ToString(mCaret
).c_str(), ToString(mTextRectArray
).c_str(),
722 GetBoolName(WidgetHasComposition()), mHandlingCompositions
.Length(),
723 ToString(mCompositionStart
).c_str(), mPendingCommitLength
,
724 ToString(mEditorRect
).c_str(),
725 ToString(mLastCommitStringTextRectArray
).c_str()));
728 bool ContentCacheInParent::HandleQueryContentEvent(
729 WidgetQueryContentEvent
& aEvent
, nsIWidget
* aWidget
) const {
732 // ContentCache doesn't store offset of its start with XP linebreaks.
733 // So, we don't support to query contents relative to composition start
734 // offset with XP linebreaks.
735 if (NS_WARN_IF(!aEvent
.mUseNativeLineBreak
)) {
736 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
737 ("0x%p HandleQueryContentEvent(), FAILED due to query with XP "
743 if (NS_WARN_IF(!aEvent
.mInput
.IsValidOffset())) {
745 sContentCacheLog
, LogLevel::Error
,
746 ("0x%p HandleQueryContentEvent(), FAILED due to invalid offset", this));
750 if (NS_WARN_IF(!aEvent
.mInput
.IsValidEventMessage(aEvent
.mMessage
))) {
752 sContentCacheLog
, LogLevel::Error
,
753 ("0x%p HandleQueryContentEvent(), FAILED due to invalid event message",
758 bool isRelativeToInsertionPoint
= aEvent
.mInput
.mRelativeToInsertionPoint
;
759 if (isRelativeToInsertionPoint
) {
761 sContentCacheLog
, LogLevel::Debug
,
762 ("0x%p HandleQueryContentEvent(), "
763 "making offset absolute... aEvent={ mMessage=%s, mInput={ "
764 "mOffset=%" PRId64
", mLength=%" PRIu32
" } }, "
765 "WidgetHasComposition()=%s, HasPendingCommit()=%s, "
766 "mCompositionStart=%" PRIu32
", "
767 "mPendingCommitLength=%" PRIu32
", mSelection=%s",
768 this, ToChar(aEvent
.mMessage
), aEvent
.mInput
.mOffset
,
769 aEvent
.mInput
.mLength
, GetBoolName(WidgetHasComposition()),
770 GetBoolName(HasPendingCommit()), mCompositionStart
.valueOr(UINT32_MAX
),
771 mPendingCommitLength
, ToString(mSelection
).c_str()));
772 if (WidgetHasComposition() || HasPendingCommit()) {
773 if (NS_WARN_IF(mCompositionStart
.isNothing()) ||
774 NS_WARN_IF(!aEvent
.mInput
.MakeOffsetAbsolute(
775 mCompositionStart
.value() + mPendingCommitLength
))) {
777 sContentCacheLog
, LogLevel::Error
,
778 ("0x%p HandleQueryContentEvent(), FAILED due to "
779 "aEvent.mInput.MakeOffsetAbsolute(mCompositionStart + "
780 "mPendingCommitLength) failure, "
781 "mCompositionStart=%" PRIu32
", mPendingCommitLength=%" PRIu32
", "
782 "aEvent={ mMessage=%s, mInput={ mOffset=%" PRId64
783 ", mLength=%" PRIu32
" } }",
784 this, mCompositionStart
.valueOr(UINT32_MAX
), mPendingCommitLength
,
785 ToChar(aEvent
.mMessage
), aEvent
.mInput
.mOffset
,
786 aEvent
.mInput
.mLength
));
789 } else if (NS_WARN_IF(mSelection
.isNothing())) {
790 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
791 ("0x%p HandleQueryContentEvent(), FAILED due to mSelection is "
795 } else if (NS_WARN_IF(mSelection
->mHasRange
)) {
796 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
797 ("0x%p HandleQueryContentEvent(), FAILED due to there is no "
798 "selection range, but the query requested with relative offset "
802 } else if (NS_WARN_IF(!aEvent
.mInput
.MakeOffsetAbsolute(
803 mSelection
->StartOffset() + mPendingCommitLength
))) {
804 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
805 ("0x%p HandleQueryContentEvent(), FAILED due to "
806 "aEvent.mInput.MakeOffsetAbsolute(mSelection->StartOffset() + "
807 "mPendingCommitLength) failure, mSelection=%s, "
808 "mPendingCommitLength=%" PRIu32
", aEvent={ mMessage=%s, "
809 "mInput={ mOffset=%" PRId64
", mLength=%" PRIu32
" } }",
810 this, ToString(mSelection
).c_str(), mPendingCommitLength
,
811 ToChar(aEvent
.mMessage
), aEvent
.mInput
.mOffset
,
812 aEvent
.mInput
.mLength
));
817 switch (aEvent
.mMessage
) {
818 case eQuerySelectedText
:
819 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
820 ("0x%p HandleQueryContentEvent(aEvent={ "
821 "mMessage=eQuerySelectedText }, aWidget=0x%p)",
823 if (MOZ_UNLIKELY(NS_WARN_IF(mSelection
.isNothing()))) {
824 // If content cache hasn't been initialized properly, make the query
826 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
827 ("0x%p HandleQueryContentEvent(), FAILED because mSelection "
832 MOZ_DIAGNOSTIC_ASSERT(mText
.isSome());
833 MOZ_DIAGNOSTIC_ASSERT(mSelection
->IsValidIn(*mText
));
834 aEvent
.EmplaceReply();
835 aEvent
.mReply
->mFocusedWidget
= aWidget
;
836 if (mSelection
->mHasRange
) {
837 if (MOZ_LIKELY(mText
.isSome())) {
838 aEvent
.mReply
->mOffsetAndData
.emplace(
839 mSelection
->StartOffset(),
840 Substring(mText
.ref(), mSelection
->StartOffset(),
841 mSelection
->Length()),
842 OffsetAndDataFor::SelectedString
);
844 // TODO: Investigate this case. I find this during
845 // test_mousecapture.xhtml on Linux.
846 aEvent
.mReply
->mOffsetAndData
.emplace(
847 0u, EmptyString(), OffsetAndDataFor::SelectedString
);
850 aEvent
.mReply
->mWritingMode
= mSelection
->mWritingMode
;
851 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
852 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
853 "mMessage=eQuerySelectedText, mReply=%s }",
854 this, ToString(aEvent
.mReply
).c_str()));
856 case eQueryTextContent
: {
857 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
858 ("0x%p HandleQueryContentEvent(aEvent={ "
859 "mMessage=eQueryTextContent, mInput={ mOffset=%" PRId64
860 ", mLength=%u } }, aWidget=0x%p), mText->Length()=%zu",
861 this, aEvent
.mInput
.mOffset
, aEvent
.mInput
.mLength
, aWidget
,
862 mText
.isSome() ? mText
->Length() : 0u));
863 if (MOZ_UNLIKELY(NS_WARN_IF(mText
.isNothing()))) {
864 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
865 ("0x%p HandleQueryContentEvent(), FAILED because "
866 "there is no text data",
870 const uint32_t inputOffset
= aEvent
.mInput
.mOffset
;
871 const uint32_t inputEndOffset
= std::min
<uint32_t>(
872 aEvent
.mInput
.EndOffset(), mText
.isSome() ? mText
->Length() : 0u);
873 if (MOZ_UNLIKELY(NS_WARN_IF(inputEndOffset
< inputOffset
))) {
874 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
875 ("0x%p HandleQueryContentEvent(), FAILED because "
876 "inputOffset=%u is larger than inputEndOffset=%u",
877 this, inputOffset
, inputEndOffset
));
880 aEvent
.EmplaceReply();
881 aEvent
.mReply
->mFocusedWidget
= aWidget
;
882 const nsAString
& textInQueriedRange
=
883 inputEndOffset
> inputOffset
884 ? static_cast<const nsAString
&>(Substring(
885 mText
.ref(), inputOffset
, inputEndOffset
- inputOffset
))
886 : static_cast<const nsAString
&>(EmptyString());
887 aEvent
.mReply
->mOffsetAndData
.emplace(inputOffset
, textInQueriedRange
,
888 OffsetAndDataFor::EditorString
);
889 // TODO: Support font ranges
890 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
891 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
892 "mMessage=eQueryTextContent, mReply=%s }",
893 this, ToString(aEvent
.mReply
).c_str()));
896 case eQueryTextRect
: {
897 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
898 ("0x%p HandleQueryContentEvent("
899 "aEvent={ mMessage=eQueryTextRect, mInput={ mOffset=%" PRId64
900 ", mLength=%u } }, aWidget=0x%p), mText->Length()=%zu",
901 this, aEvent
.mInput
.mOffset
, aEvent
.mInput
.mLength
, aWidget
,
902 mText
.isSome() ? mText
->Length() : 0u));
903 // Note that if the query is relative to insertion point, the query was
904 // probably requested by native IME. In such case, we should return
905 // non-empty rect since returning failure causes IME showing its window
907 LayoutDeviceIntRect textRect
;
908 if (aEvent
.mInput
.mLength
) {
909 if (MOZ_UNLIKELY(NS_WARN_IF(
910 !GetUnionTextRects(aEvent
.mInput
.mOffset
, aEvent
.mInput
.mLength
,
911 isRelativeToInsertionPoint
, textRect
)))) {
912 // XXX We don't have cache for this request.
913 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
914 ("0x%p HandleQueryContentEvent(), FAILED to get union rect",
919 // If the length is 0, we should return caret rect instead.
920 if (NS_WARN_IF(!GetCaretRect(aEvent
.mInput
.mOffset
,
921 isRelativeToInsertionPoint
, textRect
))) {
922 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
923 ("0x%p HandleQueryContentEvent(), FAILED to get caret rect",
928 aEvent
.EmplaceReply();
929 aEvent
.mReply
->mFocusedWidget
= aWidget
;
930 aEvent
.mReply
->mRect
= textRect
;
931 const nsAString
& textInQueriedRange
=
932 mText
.isSome() && aEvent
.mInput
.mOffset
<
933 static_cast<int64_t>(
934 mText
.isSome() ? mText
->Length() : 0u)
935 ? static_cast<const nsAString
&>(
936 Substring(mText
.ref(), aEvent
.mInput
.mOffset
,
937 mText
->Length() >= aEvent
.mInput
.EndOffset()
938 ? aEvent
.mInput
.mLength
940 : static_cast<const nsAString
&>(EmptyString());
941 aEvent
.mReply
->mOffsetAndData
.emplace(aEvent
.mInput
.mOffset
,
943 OffsetAndDataFor::EditorString
);
944 // XXX This may be wrong if storing range isn't in the selection range.
945 aEvent
.mReply
->mWritingMode
=
946 mSelection
.isSome() ? mSelection
->mWritingMode
: WritingMode();
947 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
948 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
949 "mMessage=eQueryTextRect mReply=%s }",
950 this, ToString(aEvent
.mReply
).c_str()));
953 case eQueryCaretRect
: {
955 sContentCacheLog
, LogLevel::Info
,
956 ("0x%p HandleQueryContentEvent(aEvent={ mMessage=eQueryCaretRect, "
957 "mInput={ mOffset=%" PRId64
958 " } }, aWidget=0x%p), mText->Length()=%zu",
959 this, aEvent
.mInput
.mOffset
, aWidget
,
960 mText
.isSome() ? mText
->Length() : 0u));
961 // Note that if the query is relative to insertion point, the query was
962 // probably requested by native IME. In such case, we should return
963 // non-empty rect since returning failure causes IME showing its window
965 LayoutDeviceIntRect caretRect
;
966 if (NS_WARN_IF(!GetCaretRect(aEvent
.mInput
.mOffset
,
967 isRelativeToInsertionPoint
, caretRect
))) {
968 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
969 ("0x%p HandleQueryContentEvent(),FAILED to get caret rect",
973 aEvent
.EmplaceReply();
974 aEvent
.mReply
->mFocusedWidget
= aWidget
;
975 aEvent
.mReply
->mRect
= caretRect
;
976 aEvent
.mReply
->mOffsetAndData
.emplace(aEvent
.mInput
.mOffset
,
978 OffsetAndDataFor::SelectedString
);
979 // TODO: Set mWritingMode here
980 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
981 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
982 "mMessage=eQueryCaretRect, mReply=%s }",
983 this, ToString(aEvent
.mReply
).c_str()));
986 case eQueryEditorRect
:
987 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
988 ("0x%p HandleQueryContentEvent(aEvent={ "
989 "mMessage=eQueryEditorRect }, aWidget=0x%p)",
991 // XXX This query should fail if no editable elmenet has focus. Or,
992 // perhaps, should return rect of the window instead.
993 aEvent
.EmplaceReply();
994 aEvent
.mReply
->mFocusedWidget
= aWidget
;
995 aEvent
.mReply
->mRect
= mEditorRect
;
996 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
997 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
998 "mMessage=eQueryEditorRect, mReply=%s }",
999 this, ToString(aEvent
.mReply
).c_str()));
1002 aEvent
.EmplaceReply();
1003 aEvent
.mReply
->mFocusedWidget
= aWidget
;
1004 if (NS_WARN_IF(aEvent
.Failed())) {
1006 sContentCacheLog
, LogLevel::Error
,
1007 ("0x%p HandleQueryContentEvent(), FAILED due to not set enough "
1008 "data, aEvent={ mMessage=%s, mReply=%s }",
1009 this, ToChar(aEvent
.mMessage
), ToString(aEvent
.mReply
).c_str()));
1012 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
1013 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
1014 "mMessage=%s, mReply=%s }",
1015 this, ToChar(aEvent
.mMessage
), ToString(aEvent
.mReply
).c_str()));
1020 bool ContentCacheInParent::GetTextRect(uint32_t aOffset
,
1021 bool aRoundToExistingOffset
,
1022 LayoutDeviceIntRect
& aTextRect
) const {
1024 sContentCacheLog
, LogLevel::Info
,
1025 ("0x%p GetTextRect(aOffset=%u, aRoundToExistingOffset=%s), "
1026 "mTextRectArray=%s, mSelection=%s, mLastCommitStringTextRectArray=%s",
1027 this, aOffset
, GetBoolName(aRoundToExistingOffset
),
1028 ToString(mTextRectArray
).c_str(), ToString(mSelection
).c_str(),
1029 ToString(mLastCommitStringTextRectArray
).c_str()));
1032 NS_WARNING_ASSERTION(!mFirstCharRect
.IsEmpty(), "empty rect");
1033 aTextRect
= mFirstCharRect
;
1034 return !aTextRect
.IsEmpty();
1036 if (mSelection
.isSome() && mSelection
->mHasRange
) {
1037 if (aOffset
== mSelection
->mAnchor
) {
1038 NS_WARNING_ASSERTION(
1039 !mSelection
->mAnchorCharRects
[eNextCharRect
].IsEmpty(), "empty rect");
1040 aTextRect
= mSelection
->mAnchorCharRects
[eNextCharRect
];
1041 return !aTextRect
.IsEmpty();
1043 if (mSelection
->mAnchor
&& aOffset
== mSelection
->mAnchor
- 1) {
1044 NS_WARNING_ASSERTION(
1045 !mSelection
->mAnchorCharRects
[ePrevCharRect
].IsEmpty(), "empty rect");
1046 aTextRect
= mSelection
->mAnchorCharRects
[ePrevCharRect
];
1047 return !aTextRect
.IsEmpty();
1049 if (aOffset
== mSelection
->mFocus
) {
1050 NS_WARNING_ASSERTION(
1051 !mSelection
->mFocusCharRects
[eNextCharRect
].IsEmpty(), "empty rect");
1052 aTextRect
= mSelection
->mFocusCharRects
[eNextCharRect
];
1053 return !aTextRect
.IsEmpty();
1055 if (mSelection
->mFocus
&& aOffset
== mSelection
->mFocus
- 1) {
1056 NS_WARNING_ASSERTION(
1057 !mSelection
->mFocusCharRects
[ePrevCharRect
].IsEmpty(), "empty rect");
1058 aTextRect
= mSelection
->mFocusCharRects
[ePrevCharRect
];
1059 return !aTextRect
.IsEmpty();
1063 if (mTextRectArray
.isSome() && mTextRectArray
->IsOffsetInRange(aOffset
)) {
1064 aTextRect
= mTextRectArray
->GetRect(aOffset
);
1065 return !aTextRect
.IsEmpty();
1068 if (mLastCommitStringTextRectArray
.isSome() &&
1069 mLastCommitStringTextRectArray
->IsOffsetInRange(aOffset
)) {
1070 aTextRect
= mLastCommitStringTextRectArray
->GetRect(aOffset
);
1071 return !aTextRect
.IsEmpty();
1074 if (!aRoundToExistingOffset
) {
1075 aTextRect
.SetEmpty();
1079 if (mTextRectArray
.isNothing() || !mTextRectArray
->HasRects()) {
1080 // If there are no rects in mTextRectArray, we should refer the start of
1081 // the selection if there is because IME must query a char rect around it if
1082 // there is no composition.
1083 if (mSelection
.isNothing()) {
1084 // Unfortunately, there is no data about text rect...
1085 aTextRect
.SetEmpty();
1088 aTextRect
= mSelection
->StartCharRect();
1089 return !aTextRect
.IsEmpty();
1092 // Although we may have mLastCommitStringTextRectArray here and it must have
1093 // previous character rects at selection. However, we should stop using it
1094 // because it's stored really short time after commiting a composition.
1095 // So, multiple query may return different rect and it may cause flickerling
1097 uint32_t offset
= aOffset
;
1098 if (offset
< mTextRectArray
->StartOffset()) {
1099 offset
= mTextRectArray
->StartOffset();
1101 offset
= mTextRectArray
->EndOffset() - 1;
1103 aTextRect
= mTextRectArray
->GetRect(offset
);
1104 return !aTextRect
.IsEmpty();
1107 bool ContentCacheInParent::GetUnionTextRects(
1108 uint32_t aOffset
, uint32_t aLength
, bool aRoundToExistingOffset
,
1109 LayoutDeviceIntRect
& aUnionTextRect
) const {
1110 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
1111 ("0x%p GetUnionTextRects(aOffset=%u, "
1112 "aLength=%u, aRoundToExistingOffset=%s), mTextRectArray=%s, "
1113 "mSelection=%s, mLastCommitStringTextRectArray=%s",
1114 this, aOffset
, aLength
, GetBoolName(aRoundToExistingOffset
),
1115 ToString(mTextRectArray
).c_str(), ToString(mSelection
).c_str(),
1116 ToString(mLastCommitStringTextRectArray
).c_str()));
1118 CheckedInt
<uint32_t> endOffset
= CheckedInt
<uint32_t>(aOffset
) + aLength
;
1119 if (!endOffset
.isValid()) {
1123 if (mSelection
.isSome() && !mSelection
->IsCollapsed() &&
1124 aOffset
== mSelection
->StartOffset() && aLength
== mSelection
->Length()) {
1125 NS_WARNING_ASSERTION(!mSelection
->mRect
.IsEmpty(), "empty rect");
1126 aUnionTextRect
= mSelection
->mRect
;
1127 return !aUnionTextRect
.IsEmpty();
1132 NS_WARNING_ASSERTION(!mFirstCharRect
.IsEmpty(), "empty rect");
1133 aUnionTextRect
= mFirstCharRect
;
1134 return !aUnionTextRect
.IsEmpty();
1136 if (mSelection
.isSome() && mSelection
->mHasRange
) {
1137 if (aOffset
== mSelection
->mAnchor
) {
1138 NS_WARNING_ASSERTION(
1139 !mSelection
->mAnchorCharRects
[eNextCharRect
].IsEmpty(),
1141 aUnionTextRect
= mSelection
->mAnchorCharRects
[eNextCharRect
];
1142 return !aUnionTextRect
.IsEmpty();
1144 if (mSelection
->mAnchor
&& aOffset
== mSelection
->mAnchor
- 1) {
1145 NS_WARNING_ASSERTION(
1146 !mSelection
->mAnchorCharRects
[ePrevCharRect
].IsEmpty(),
1148 aUnionTextRect
= mSelection
->mAnchorCharRects
[ePrevCharRect
];
1149 return !aUnionTextRect
.IsEmpty();
1151 if (aOffset
== mSelection
->mFocus
) {
1152 NS_WARNING_ASSERTION(
1153 !mSelection
->mFocusCharRects
[eNextCharRect
].IsEmpty(),
1155 aUnionTextRect
= mSelection
->mFocusCharRects
[eNextCharRect
];
1156 return !aUnionTextRect
.IsEmpty();
1158 if (mSelection
->mFocus
&& aOffset
== mSelection
->mFocus
- 1) {
1159 NS_WARNING_ASSERTION(
1160 !mSelection
->mFocusCharRects
[ePrevCharRect
].IsEmpty(),
1162 aUnionTextRect
= mSelection
->mFocusCharRects
[ePrevCharRect
];
1163 return !aUnionTextRect
.IsEmpty();
1168 // Even if some text rects are not cached of the queried range,
1169 // we should return union rect when the first character's rect is cached
1170 // since the first character rect is important and the others are not so
1173 if (!aOffset
&& mSelection
.isSome() && mSelection
->mHasRange
&&
1174 aOffset
!= mSelection
->mAnchor
&& aOffset
!= mSelection
->mFocus
&&
1175 (mTextRectArray
.isNothing() ||
1176 !mTextRectArray
->IsOffsetInRange(aOffset
)) &&
1177 (mLastCommitStringTextRectArray
.isNothing() ||
1178 !mLastCommitStringTextRectArray
->IsOffsetInRange(aOffset
))) {
1179 // The first character rect isn't cached.
1183 // Use mLastCommitStringTextRectArray only when it overlaps with aOffset
1184 // even if aROundToExistingOffset is true for avoiding flickerling IME UI.
1185 // See the last comment in GetTextRect() for the detail.
1186 if (mLastCommitStringTextRectArray
.isSome() &&
1187 mLastCommitStringTextRectArray
->IsOverlappingWith(aOffset
, aLength
)) {
1189 mLastCommitStringTextRectArray
->GetUnionRectAsFarAsPossible(
1190 aOffset
, aLength
, aRoundToExistingOffset
);
1192 aUnionTextRect
.SetEmpty();
1195 if (mTextRectArray
.isSome() &&
1196 ((aRoundToExistingOffset
&& mTextRectArray
->HasRects()) ||
1197 mTextRectArray
->IsOverlappingWith(aOffset
, aLength
))) {
1199 aUnionTextRect
.Union(mTextRectArray
->GetUnionRectAsFarAsPossible(
1200 aOffset
, aLength
, aRoundToExistingOffset
));
1204 aUnionTextRect
= aUnionTextRect
.Union(mFirstCharRect
);
1206 if (mSelection
.isSome() && mSelection
->mHasRange
) {
1207 if (aOffset
<= mSelection
->mAnchor
&&
1208 mSelection
->mAnchor
< endOffset
.value()) {
1210 aUnionTextRect
.Union(mSelection
->mAnchorCharRects
[eNextCharRect
]);
1212 if (mSelection
->mAnchor
&& aOffset
<= mSelection
->mAnchor
- 1 &&
1213 mSelection
->mAnchor
- 1 < endOffset
.value()) {
1215 aUnionTextRect
.Union(mSelection
->mAnchorCharRects
[ePrevCharRect
]);
1217 if (aOffset
<= mSelection
->mFocus
&&
1218 mSelection
->mFocus
< endOffset
.value()) {
1220 aUnionTextRect
.Union(mSelection
->mFocusCharRects
[eNextCharRect
]);
1222 if (mSelection
->mFocus
&& aOffset
<= mSelection
->mFocus
- 1 &&
1223 mSelection
->mFocus
- 1 < endOffset
.value()) {
1225 aUnionTextRect
.Union(mSelection
->mFocusCharRects
[ePrevCharRect
]);
1229 return !aUnionTextRect
.IsEmpty();
1232 bool ContentCacheInParent::GetCaretRect(uint32_t aOffset
,
1233 bool aRoundToExistingOffset
,
1234 LayoutDeviceIntRect
& aCaretRect
) const {
1235 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
1236 ("0x%p GetCaretRect(aOffset=%u, aRoundToExistingOffset=%s), "
1237 "mCaret=%s, mTextRectArray=%s, mSelection=%s, mFirstCharRect=%s",
1238 this, aOffset
, GetBoolName(aRoundToExistingOffset
),
1239 ToString(mCaret
).c_str(), ToString(mTextRectArray
).c_str(),
1240 ToString(mSelection
).c_str(), ToString(mFirstCharRect
).c_str()));
1242 if (mCaret
.isSome() && mCaret
->mOffset
== aOffset
) {
1243 aCaretRect
= mCaret
->mRect
;
1247 // Guess caret rect from the text rect if it's stored.
1248 if (!GetTextRect(aOffset
, aRoundToExistingOffset
, aCaretRect
)) {
1249 // There might be previous character rect in the cache. If so, we can
1250 // guess the caret rect with it.
1252 !GetTextRect(aOffset
- 1, aRoundToExistingOffset
, aCaretRect
)) {
1253 aCaretRect
.SetEmpty();
1257 if (mSelection
.isSome() && mSelection
->mWritingMode
.IsVertical()) {
1258 aCaretRect
.MoveToY(aCaretRect
.YMost());
1260 // XXX bidi-unaware.
1261 aCaretRect
.MoveToX(aCaretRect
.XMost());
1265 // XXX This is not bidi aware because we don't cache each character's
1266 // direction. However, this is usually used by IME, so, assuming the
1267 // character is in LRT context must not cause any problem.
1268 if (mSelection
.isSome() && mSelection
->mWritingMode
.IsVertical()) {
1269 aCaretRect
.SetHeight(mCaret
.isSome() ? mCaret
->mRect
.Height() : 1);
1271 aCaretRect
.SetWidth(mCaret
.isSome() ? mCaret
->mRect
.Width() : 1);
1276 bool ContentCacheInParent::OnCompositionEvent(
1277 const WidgetCompositionEvent
& aCompositionEvent
) {
1279 sContentCacheLog
, LogLevel::Info
,
1280 ("0x%p OnCompositionEvent(aCompositionEvent={ "
1281 "mMessage=%s, mData=\"%s\", mRanges->Length()=%zu }), "
1282 "PendingEventsNeedingAck()=%u, WidgetHasComposition()=%s, "
1283 "mHandlingCompositions.Length()=%zu, HasPendingCommit()=%s, "
1284 "mIsChildIgnoringCompositionEvents=%s, mCommitStringByRequest=0x%p",
1285 this, ToChar(aCompositionEvent
.mMessage
),
1286 PrintStringDetail(aCompositionEvent
.mData
,
1287 PrintStringDetail::kMaxLengthForCompositionString
)
1289 aCompositionEvent
.mRanges
? aCompositionEvent
.mRanges
->Length() : 0,
1290 PendingEventsNeedingAck(), GetBoolName(WidgetHasComposition()),
1291 mHandlingCompositions
.Length(), GetBoolName(HasPendingCommit()),
1292 GetBoolName(mIsChildIgnoringCompositionEvents
), mCommitStringByRequest
));
1294 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1295 mDispatchedEventMessages
.AppendElement(aCompositionEvent
.mMessage
);
1296 #endif // #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1298 // We must be able to simulate the selection because
1299 // we might not receive selection updates in time
1300 if (!WidgetHasComposition()) {
1301 if (mCompositionStartInChild
.isSome()) {
1302 // If there is pending composition in the remote process, let's use
1303 // its start offset temporarily because this stores a lot of information
1304 // around it and the user must look around there, so, showing some UI
1305 // around it must make sense.
1306 mCompositionStart
= mCompositionStartInChild
;
1308 mCompositionStart
= Some(mSelection
.isSome() && mSelection
->mHasRange
1309 ? mSelection
->StartOffset()
1312 MOZ_ASSERT(aCompositionEvent
.mMessage
== eCompositionStart
);
1313 mHandlingCompositions
.AppendElement(
1314 HandlingCompositionData(aCompositionEvent
.mCompositionId
));
1317 mHandlingCompositions
.LastElement().mSentCommitEvent
=
1318 aCompositionEvent
.CausesDOMCompositionEndEvent();
1319 MOZ_ASSERT(mHandlingCompositions
.LastElement().mCompositionId
==
1320 aCompositionEvent
.mCompositionId
);
1322 if (!WidgetHasComposition()) {
1323 // mCompositionStart will be reset when commit event is completely handled
1324 // in the remote process.
1325 if (mHandlingCompositions
.Length() == 1u) {
1326 mPendingCommitLength
= aCompositionEvent
.mData
.Length();
1328 MOZ_ASSERT(HasPendingCommit());
1329 } else if (aCompositionEvent
.mMessage
!= eCompositionStart
) {
1330 mHandlingCompositions
.LastElement().mCompositionString
=
1331 aCompositionEvent
.mData
;
1334 // During REQUEST_TO_COMMIT_COMPOSITION or REQUEST_TO_CANCEL_COMPOSITION,
1335 // widget usually sends a eCompositionChange and/or eCompositionCommit event
1336 // to finalize or clear the composition, respectively. In this time,
1337 // we need to intercept all composition events here and pass the commit
1338 // string for returning to the remote process as a result of
1339 // RequestIMEToCommitComposition(). Then, eCommitComposition event will
1340 // be dispatched with the committed string in the remote process internally.
1341 if (mCommitStringByRequest
) {
1342 if (aCompositionEvent
.mMessage
== eCompositionCommitAsIs
) {
1343 *mCommitStringByRequest
=
1344 mHandlingCompositions
.LastElement().mCompositionString
;
1346 MOZ_ASSERT(aCompositionEvent
.mMessage
== eCompositionChange
||
1347 aCompositionEvent
.mMessage
== eCompositionCommit
);
1348 *mCommitStringByRequest
= aCompositionEvent
.mData
;
1350 // We need to wait eCompositionCommitRequestHandled from the remote process
1351 // in this case. Therefore, mPendingEventsNeedingAck needs to be
1352 // incremented here.
1353 if (!WidgetHasComposition()) {
1354 mHandlingCompositions
.LastElement().mPendingEventsNeedingAck
++;
1359 mHandlingCompositions
.LastElement().mPendingEventsNeedingAck
++;
1363 void ContentCacheInParent::OnSelectionEvent(
1364 const WidgetSelectionEvent
& aSelectionEvent
) {
1365 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
1366 ("0x%p OnSelectionEvent(aEvent={ "
1367 "mMessage=%s, mOffset=%u, mLength=%u, mReversed=%s, "
1368 "mExpandToClusterBoundary=%s, mUseNativeLineBreak=%s }), "
1369 "PendingEventsNeedingAck()=%u, WidgetHasComposition()=%s, "
1370 "mHandlingCompositions.Length()=%zu, HasPendingCommit()=%s, "
1371 "mIsChildIgnoringCompositionEvents=%s",
1372 this, ToChar(aSelectionEvent
.mMessage
), aSelectionEvent
.mOffset
,
1373 aSelectionEvent
.mLength
, GetBoolName(aSelectionEvent
.mReversed
),
1374 GetBoolName(aSelectionEvent
.mExpandToClusterBoundary
),
1375 GetBoolName(aSelectionEvent
.mUseNativeLineBreak
),
1376 PendingEventsNeedingAck(), GetBoolName(WidgetHasComposition()),
1377 mHandlingCompositions
.Length(), GetBoolName(HasPendingCommit()),
1378 GetBoolName(mIsChildIgnoringCompositionEvents
)));
1380 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1381 mDispatchedEventMessages
.AppendElement(aSelectionEvent
.mMessage
);
1382 #endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
1384 mPendingSetSelectionEventNeedingAck
++;
1387 void ContentCacheInParent::OnEventNeedingAckHandled(nsIWidget
* aWidget
,
1388 EventMessage aMessage
,
1389 uint32_t aCompositionId
) {
1390 // This is called when the child process receives WidgetCompositionEvent or
1391 // WidgetSelectionEvent.
1393 HandlingCompositionData
* handlingCompositionData
=
1394 aMessage
!= eSetSelection
? GetHandlingCompositionData(aCompositionId
)
1397 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
1398 ("0x%p OnEventNeedingAckHandled(aWidget=0x%p, aMessage=%s, "
1399 "aCompositionId=%" PRIu32
1400 "), PendingEventsNeedingAck()=%u, WidgetHasComposition()=%s, "
1401 "mHandlingCompositions.Length()=%zu, HasPendingCommit()=%s, "
1402 "mIsChildIgnoringCompositionEvents=%s, handlingCompositionData=0x%p",
1403 this, aWidget
, ToChar(aMessage
), aCompositionId
,
1404 PendingEventsNeedingAck(), GetBoolName(WidgetHasComposition()),
1405 mHandlingCompositions
.Length(), GetBoolName(HasPendingCommit()),
1406 GetBoolName(mIsChildIgnoringCompositionEvents
),
1407 handlingCompositionData
));
1409 // If we receive composition event messages for older one or invalid one,
1410 // we should ignore them.
1411 if (NS_WARN_IF(aMessage
!= eSetSelection
&& !handlingCompositionData
)) {
1415 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1416 mReceivedEventMessages
.AppendElement(aMessage
);
1417 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1419 const bool isCommittedInChild
=
1420 // Commit requester in the remote process has committed the composition.
1421 aMessage
== eCompositionCommitRequestHandled
||
1422 // The commit event has been handled normally in the remote process.
1423 (!mIsChildIgnoringCompositionEvents
&&
1424 WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage
));
1425 const bool hasPendingCommit
= HasPendingCommit();
1427 if (isCommittedInChild
) {
1428 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1429 if (mHandlingCompositions
.Length() == 1u) {
1430 RemoveUnnecessaryEventMessageLog();
1433 if (NS_WARN_IF(aMessage
!= eCompositionCommitRequestHandled
&&
1434 !handlingCompositionData
->mSentCommitEvent
)) {
1435 nsPrintfCString
info(
1436 "\nReceived unexpected commit event message (%s) which we've "
1439 AppendEventMessageLog(info
);
1440 CrashReporter::AppendAppNotesToCrashReport(info
);
1441 MOZ_DIAGNOSTIC_ASSERT(
1443 "Received unexpected commit event which has not been sent yet");
1445 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1447 // This should not occur, though. If we receive a commit notification for
1448 // not the oldest composition, we should forget all older compositions.
1449 size_t numberOfOutdatedCompositions
= 1u;
1450 for (auto& data
: mHandlingCompositions
) {
1451 if (&data
== handlingCompositionData
) {
1453 // Don't put the info into the log when we've already sent commit
1454 // event because it may be just inserting a character without
1455 // composing state, but the remote process may move focus at
1456 // eCompositionStart. This may happen with UI of IME to put only
1457 // one character, e.g., the default Emoji picker of Windows.
1458 !data
.mSentCommitEvent
&&
1459 // In the normal case, only one message should remain, however,
1460 // remaining 2 or more messages is also valid, for example, the
1461 // remote process may have a composition update listener which
1462 // takes a while. Then, we can have multiple pending messages.
1463 data
.mPendingEventsNeedingAck
>= 1u) {
1465 sContentCacheLog
, LogLevel::Debug
,
1466 (" NOTE: BrowserParent has %" PRIu32
1467 " pending composition messages for the handling composition, "
1468 "but before they are handled in the remote process, the active "
1469 "composition is commited by a request. "
1470 "OnEventNeedingAckHandled() calls for them will be ignored",
1471 data
.mPendingEventsNeedingAck
));
1475 if (MOZ_UNLIKELY(data
.mPendingEventsNeedingAck
)) {
1476 MOZ_LOG(sContentCacheLog
, LogLevel::Warning
,
1477 (" BrowserParent has %" PRIu32
1478 " pending composition messages for an older composition than "
1479 "the handling composition, but it'll be removed because newer "
1480 "composition gets comitted in the remote process",
1481 data
.mPendingEventsNeedingAck
));
1483 numberOfOutdatedCompositions
++;
1485 mHandlingCompositions
.RemoveElementsAt(0u, numberOfOutdatedCompositions
);
1486 handlingCompositionData
= nullptr;
1488 // Forget pending commit string length if it's handled in the remote
1489 // process. Note that this doesn't care too old composition's commit
1490 // string because in such case, we cannot return proper information
1491 // to IME synchronously.
1492 mPendingCommitLength
= 0;
1495 if (WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage
)) {
1496 // After the remote process receives eCompositionCommit(AsIs) event,
1497 // it'll restart to handle composition events.
1498 mIsChildIgnoringCompositionEvents
= false;
1500 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1501 if (NS_WARN_IF(!hasPendingCommit
)) {
1502 nsPrintfCString
info(
1503 "\nThere is no pending comment events but received "
1504 "%s message from the remote child\n\n",
1506 AppendEventMessageLog(info
);
1507 CrashReporter::AppendAppNotesToCrashReport(info
);
1508 MOZ_DIAGNOSTIC_ASSERT(
1510 "No pending commit events but received unexpected commit event");
1512 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1513 } else if (aMessage
== eCompositionCommitRequestHandled
&& hasPendingCommit
) {
1514 // If the remote process commits composition synchronously after
1515 // requesting commit composition and we've already sent commit composition,
1516 // it starts to ignore following composition events until receiving
1517 // eCompositionStart event.
1518 mIsChildIgnoringCompositionEvents
= true;
1521 // If neither widget (i.e., IME) nor the remote process has composition,
1522 // now, we can forget composition string informations.
1523 if (mHandlingCompositions
.IsEmpty()) {
1524 mCompositionStart
.reset();
1527 if (handlingCompositionData
) {
1528 if (NS_WARN_IF(!handlingCompositionData
->mPendingEventsNeedingAck
)) {
1529 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1530 nsPrintfCString
info(
1531 "\nThere is no pending events but received %s "
1532 "message from the remote child\n\n",
1534 AppendEventMessageLog(info
);
1535 CrashReporter::AppendAppNotesToCrashReport(info
);
1536 MOZ_DIAGNOSTIC_ASSERT(
1537 false, "No pending event message but received unexpected event");
1538 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1540 handlingCompositionData
->mPendingEventsNeedingAck
--;
1542 } else if (aMessage
== eSetSelection
) {
1543 if (NS_WARN_IF(!mPendingSetSelectionEventNeedingAck
)) {
1544 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1546 "\nThere is no pending set selection events but received from the "
1547 "remote child\n\n");
1548 AppendEventMessageLog(info
);
1549 CrashReporter::AppendAppNotesToCrashReport(info
);
1550 MOZ_DIAGNOSTIC_ASSERT(
1551 false, "No pending event message but received unexpected event");
1552 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1554 mPendingSetSelectionEventNeedingAck
--;
1558 if (!PendingEventsNeedingAck()) {
1559 FlushPendingNotifications(aWidget
);
1563 bool ContentCacheInParent::RequestIMEToCommitComposition(
1564 nsIWidget
* aWidget
, bool aCancel
, uint32_t aCompositionId
,
1565 nsAString
& aCommittedString
) {
1566 HandlingCompositionData
* const handlingCompositionData
=
1567 GetHandlingCompositionData(aCompositionId
);
1570 sContentCacheLog
, LogLevel::Info
,
1571 ("0x%p RequestToCommitComposition(aWidget=%p, "
1572 "aCancel=%s, aCompositionId=%" PRIu32
1573 "), mHandlingCompositions.Length()=%zu, HasPendingCommit()=%s, "
1574 "mIsChildIgnoringCompositionEvents=%s, "
1575 "IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)=%s, "
1576 "WidgetHasComposition()=%s, mCommitStringByRequest=%p, "
1577 "handlingCompositionData=0x%p",
1578 this, aWidget
, GetBoolName(aCancel
), aCompositionId
,
1579 mHandlingCompositions
.Length(), GetBoolName(HasPendingCommit()),
1580 GetBoolName(mIsChildIgnoringCompositionEvents
),
1582 IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent
)),
1583 GetBoolName(WidgetHasComposition()), mCommitStringByRequest
,
1584 handlingCompositionData
));
1586 MOZ_ASSERT(!mCommitStringByRequest
);
1588 // If we don't know the composition ID, it must have already been committed
1589 // in this process. In the case, we should do nothing here.
1590 if (NS_WARN_IF(!handlingCompositionData
)) {
1591 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1592 mRequestIMEToCommitCompositionResults
.AppendElement(
1593 RequestIMEToCommitCompositionResult::eToUnknownCompositionReceived
);
1594 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1598 // If we receive a commit result for not latest composition, this request is
1599 // too late for IME. The remote process should wait following composition
1600 // events for cleaning up TextComposition and handle the request as it's
1601 // handled asynchronously.
1602 if (handlingCompositionData
!= &mHandlingCompositions
.LastElement()) {
1603 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1604 mRequestIMEToCommitCompositionResults
.AppendElement(
1605 RequestIMEToCommitCompositionResult::eToOldCompositionReceived
);
1606 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1610 // If the composition has already been commit, th remote process will receive
1611 // composition events and clean up TextComposition. So, this should do
1612 // nothing and TextComposition should handle the request as it's handled
1614 // XXX Perhaps, this is wrong because TextComposition in child process
1615 // may commit the composition with current composition string in the
1616 // remote process. I.e., it may be different from actual commit string
1617 // which user typed. So, perhaps, we should return true and the commit
1619 if (handlingCompositionData
->mSentCommitEvent
) {
1620 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1621 mRequestIMEToCommitCompositionResults
.AppendElement(
1622 RequestIMEToCommitCompositionResult::eToCommittedCompositionReceived
);
1623 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1627 // If BrowserParent which has IME focus was already changed to different one,
1628 // the request shouldn't be sent to IME because it's too late.
1629 if (!IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent
)) {
1630 // Use the latest composition string which may not be handled in the
1631 // remote process for avoiding data loss.
1632 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1633 mRequestIMEToCommitCompositionResults
.AppendElement(
1634 RequestIMEToCommitCompositionResult::eReceivedAfterBrowserParentBlur
);
1635 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1636 aCommittedString
= handlingCompositionData
->mCompositionString
;
1637 // After we return true from here, i.e., without actually requesting IME
1638 // to commit composition, we will receive eCompositionCommitRequestHandled
1639 // pseudo event message from the remote process. So, we need to increment
1640 // mPendingEventsNeedingAck here.
1641 handlingCompositionData
->mPendingEventsNeedingAck
++;
1645 RefPtr
<TextComposition
> composition
=
1646 IMEStateManager::GetTextCompositionFor(aWidget
);
1647 if (NS_WARN_IF(!composition
)) {
1648 MOZ_LOG(sContentCacheLog
, LogLevel::Warning
,
1649 (" 0x%p RequestToCommitComposition(), "
1650 "does nothing due to no composition",
1652 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1653 mRequestIMEToCommitCompositionResults
.AppendElement(
1654 RequestIMEToCommitCompositionResult::eReceivedButNoTextComposition
);
1655 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1659 // If we receive a request for different composition, we must have already
1660 // sent a commit event. So the remote process should handle it.
1661 // XXX I think that this should never happen because we already checked
1662 // whether handlingCompositionData is the latest composition or not.
1663 // However, we don't want to commit different composition for the users.
1664 // Therefore, let's handle the odd case here.
1665 if (NS_WARN_IF(composition
->Id() != aCompositionId
)) {
1666 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1667 mRequestIMEToCommitCompositionResults
.AppendElement(
1668 RequestIMEToCommitCompositionResult::
1669 eReceivedButForDifferentTextComposition
);
1670 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1674 mCommitStringByRequest
= &aCommittedString
;
1676 // Request commit or cancel composition with TextComposition because we may
1677 // have already requested to commit or cancel the composition or we may
1678 // have already received eCompositionCommit(AsIs) event. Those status are
1679 // managed by composition. So, if we don't request commit composition,
1680 // we should do nothing with native IME here.
1681 composition
->RequestToCommit(aWidget
, aCancel
);
1683 mCommitStringByRequest
= nullptr;
1686 sContentCacheLog
, LogLevel::Info
,
1687 (" 0x%p RequestToCommitComposition(), "
1688 "WidgetHasComposition()=%s, the composition %s committed synchronously",
1689 this, GetBoolName(WidgetHasComposition()),
1690 composition
->Destroyed() ? "WAS" : "has NOT been"));
1692 if (!composition
->Destroyed()) {
1693 // When the composition isn't committed synchronously, the remote process's
1694 // TextComposition instance will synthesize commit events and wait to
1695 // receive delayed composition events. When TextComposition instances both
1696 // in this process and the remote process will be destroyed when delayed
1697 // composition events received. TextComposition instance in the parent
1698 // process will dispatch following composition events and be destroyed
1699 // normally. On the other hand, TextComposition instance in the remote
1700 // process won't dispatch following composition events and will be
1701 // destroyed by IMEStateManager::DispatchCompositionEvent().
1702 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1703 mRequestIMEToCommitCompositionResults
.AppendElement(
1704 RequestIMEToCommitCompositionResult::eHandledAsynchronously
);
1705 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1709 // When the composition is committed synchronously, the commit string will be
1710 // returned to the remote process. Then, PuppetWidget will dispatch
1711 // eCompositionCommit event with the returned commit string (i.e., the value
1712 // is aCommittedString of this method) and that causes destroying
1713 // TextComposition instance in the remote process (Note that TextComposition
1714 // instance in this process was already destroyed).
1715 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1716 mRequestIMEToCommitCompositionResults
.AppendElement(
1717 RequestIMEToCommitCompositionResult::eHandledSynchronously
);
1718 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1722 void ContentCacheInParent::MaybeNotifyIME(
1723 nsIWidget
* aWidget
, const IMENotification
& aNotification
) {
1724 if (!PendingEventsNeedingAck()) {
1725 IMEStateManager::NotifyIME(aNotification
, aWidget
, &mBrowserParent
);
1729 switch (aNotification
.mMessage
) {
1730 case NOTIFY_IME_OF_SELECTION_CHANGE
:
1731 mPendingSelectionChange
.MergeWith(aNotification
);
1733 case NOTIFY_IME_OF_TEXT_CHANGE
:
1734 mPendingTextChange
.MergeWith(aNotification
);
1736 case NOTIFY_IME_OF_POSITION_CHANGE
:
1737 mPendingLayoutChange
.MergeWith(aNotification
);
1739 case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED
:
1740 mPendingCompositionUpdate
.MergeWith(aNotification
);
1743 MOZ_CRASH("Unsupported notification");
1748 void ContentCacheInParent::FlushPendingNotifications(nsIWidget
* aWidget
) {
1749 MOZ_ASSERT(!PendingEventsNeedingAck());
1751 // If the BrowserParent's widget has already gone, this can do nothing since
1752 // widget is necessary to notify IME of something.
1757 // New notifications which are notified during flushing pending notifications
1758 // should be merged again.
1759 const bool pendingEventNeedingAckIncremented
=
1760 !mHandlingCompositions
.IsEmpty();
1761 if (pendingEventNeedingAckIncremented
) {
1762 mHandlingCompositions
.LastElement().mPendingEventsNeedingAck
++;
1765 nsCOMPtr
<nsIWidget
> widget
= aWidget
;
1767 // First, text change notification should be sent because selection change
1768 // notification notifies IME of current selection range in the latest content.
1769 // So, IME may need the latest content before that.
1770 if (mPendingTextChange
.HasNotification()) {
1771 IMENotification
notification(mPendingTextChange
);
1772 if (!widget
->Destroyed()) {
1773 mPendingTextChange
.Clear();
1774 IMEStateManager::NotifyIME(notification
, widget
, &mBrowserParent
);
1778 if (mPendingSelectionChange
.HasNotification()) {
1779 IMENotification
notification(mPendingSelectionChange
);
1780 if (!widget
->Destroyed()) {
1781 mPendingSelectionChange
.Clear();
1782 IMEStateManager::NotifyIME(notification
, widget
, &mBrowserParent
);
1786 // Layout change notification should be notified after selection change
1787 // notification because IME may want to query position of new caret position.
1788 if (mPendingLayoutChange
.HasNotification()) {
1789 IMENotification
notification(mPendingLayoutChange
);
1790 if (!widget
->Destroyed()) {
1791 mPendingLayoutChange
.Clear();
1792 IMEStateManager::NotifyIME(notification
, widget
, &mBrowserParent
);
1796 // Finally, send composition update notification because it notifies IME of
1797 // finishing handling whole sending events.
1798 if (mPendingCompositionUpdate
.HasNotification()) {
1799 IMENotification
notification(mPendingCompositionUpdate
);
1800 if (!widget
->Destroyed()) {
1801 mPendingCompositionUpdate
.Clear();
1802 IMEStateManager::NotifyIME(notification
, widget
, &mBrowserParent
);
1806 // Decrement it which was incremented above.
1807 if (!mHandlingCompositions
.IsEmpty() && pendingEventNeedingAckIncremented
&&
1808 mHandlingCompositions
.LastElement().mPendingEventsNeedingAck
) {
1809 mHandlingCompositions
.LastElement().mPendingEventsNeedingAck
--;
1812 if (!PendingEventsNeedingAck() && !widget
->Destroyed() &&
1813 (mPendingTextChange
.HasNotification() ||
1814 mPendingSelectionChange
.HasNotification() ||
1815 mPendingLayoutChange
.HasNotification() ||
1816 mPendingCompositionUpdate
.HasNotification())) {
1817 FlushPendingNotifications(widget
);
1821 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1823 void ContentCacheInParent::RemoveUnnecessaryEventMessageLog() {
1824 bool foundLastCompositionStart
= false;
1825 for (size_t i
= mDispatchedEventMessages
.Length(); i
> 1; i
--) {
1826 if (mDispatchedEventMessages
[i
- 1] != eCompositionStart
) {
1829 if (!foundLastCompositionStart
) {
1830 // Find previous eCompositionStart of the latest eCompositionStart.
1831 foundLastCompositionStart
= true;
1834 // Remove the messages before the last 2 sets of composition events.
1835 mDispatchedEventMessages
.RemoveElementsAt(0, i
- 1);
1838 uint32_t numberOfCompositionCommitRequestHandled
= 0;
1839 foundLastCompositionStart
= false;
1840 for (size_t i
= mReceivedEventMessages
.Length(); i
> 1; i
--) {
1841 if (mReceivedEventMessages
[i
- 1] == eCompositionCommitRequestHandled
) {
1842 numberOfCompositionCommitRequestHandled
++;
1844 if (mReceivedEventMessages
[i
- 1] != eCompositionStart
) {
1847 if (!foundLastCompositionStart
) {
1848 // Find previous eCompositionStart of the latest eCompositionStart.
1849 foundLastCompositionStart
= true;
1852 // Remove the messages before the last 2 sets of composition events.
1853 mReceivedEventMessages
.RemoveElementsAt(0, i
- 1);
1857 if (!numberOfCompositionCommitRequestHandled
) {
1858 // If there is no eCompositionCommitRequestHandled in
1859 // mReceivedEventMessages, we don't need to store log of
1860 // RequestIMEToCommmitComposition().
1861 mRequestIMEToCommitCompositionResults
.Clear();
1863 // We need to keep all reason of eCompositionCommitRequestHandled, which
1864 // is sent when mRequestIMEToCommitComposition() returns true.
1865 // So, we can discard older log than the first
1866 // eCompositionCommitRequestHandled in mReceivedEventMessages.
1867 for (size_t i
= mRequestIMEToCommitCompositionResults
.Length(); i
> 1;
1869 if (mRequestIMEToCommitCompositionResults
[i
- 1] ==
1870 RequestIMEToCommitCompositionResult::
1871 eReceivedAfterBrowserParentBlur
||
1872 mRequestIMEToCommitCompositionResults
[i
- 1] ==
1873 RequestIMEToCommitCompositionResult::eHandledSynchronously
) {
1874 --numberOfCompositionCommitRequestHandled
;
1875 if (!numberOfCompositionCommitRequestHandled
) {
1876 mRequestIMEToCommitCompositionResults
.RemoveElementsAt(0, i
- 1);
1884 void ContentCacheInParent::AppendEventMessageLog(nsACString
& aLog
) const {
1885 aLog
.AppendLiteral("Dispatched Event Message Log:\n");
1886 for (EventMessage message
: mDispatchedEventMessages
) {
1887 aLog
.AppendLiteral(" ");
1888 aLog
.Append(ToChar(message
));
1889 aLog
.AppendLiteral("\n");
1891 aLog
.AppendLiteral("\nReceived Event Message Log:\n");
1892 for (EventMessage message
: mReceivedEventMessages
) {
1893 aLog
.AppendLiteral(" ");
1894 aLog
.Append(ToChar(message
));
1895 aLog
.AppendLiteral("\n");
1897 aLog
.AppendLiteral("\nResult of RequestIMEToCommitComposition():\n");
1898 for (RequestIMEToCommitCompositionResult result
:
1899 mRequestIMEToCommitCompositionResults
) {
1900 aLog
.AppendLiteral(" ");
1901 aLog
.Append(ToReadableText(result
));
1902 aLog
.AppendLiteral("\n");
1904 aLog
.AppendLiteral("\n");
1907 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1909 /*****************************************************************************
1910 * mozilla::ContentCache::Selection
1911 *****************************************************************************/
1913 ContentCache::Selection::Selection(
1914 const WidgetQueryContentEvent
& aQuerySelectedTextEvent
)
1915 : mAnchor(UINT32_MAX
),
1917 mWritingMode(aQuerySelectedTextEvent
.mReply
->WritingModeRef()),
1918 mHasRange(aQuerySelectedTextEvent
.mReply
->mOffsetAndData
.isSome()) {
1919 MOZ_ASSERT(aQuerySelectedTextEvent
.mMessage
== eQuerySelectedText
);
1920 MOZ_ASSERT(aQuerySelectedTextEvent
.Succeeded());
1922 mAnchor
= aQuerySelectedTextEvent
.mReply
->AnchorOffset();
1923 mFocus
= aQuerySelectedTextEvent
.mReply
->FocusOffset();
1927 /*****************************************************************************
1928 * mozilla::ContentCache::TextRectArray
1929 *****************************************************************************/
1931 LayoutDeviceIntRect
ContentCache::TextRectArray::GetRect(
1932 uint32_t aOffset
) const {
1933 LayoutDeviceIntRect rect
;
1934 if (IsOffsetInRange(aOffset
)) {
1935 rect
= mRects
[aOffset
- mStart
];
1940 LayoutDeviceIntRect
ContentCache::TextRectArray::GetUnionRect(
1941 uint32_t aOffset
, uint32_t aLength
) const {
1942 LayoutDeviceIntRect rect
;
1943 if (!IsRangeCompletelyInRange(aOffset
, aLength
)) {
1946 for (uint32_t i
= 0; i
< aLength
; i
++) {
1947 rect
= rect
.Union(mRects
[aOffset
- mStart
+ i
]);
1952 LayoutDeviceIntRect
ContentCache::TextRectArray::GetUnionRectAsFarAsPossible(
1953 uint32_t aOffset
, uint32_t aLength
, bool aRoundToExistingOffset
) const {
1954 LayoutDeviceIntRect rect
;
1956 (!aRoundToExistingOffset
&& !IsOverlappingWith(aOffset
, aLength
))) {
1959 uint32_t startOffset
= std::max(aOffset
, mStart
);
1960 if (aRoundToExistingOffset
&& startOffset
>= EndOffset()) {
1961 startOffset
= EndOffset() - 1;
1963 uint32_t endOffset
= std::min(aOffset
+ aLength
, EndOffset());
1964 if (aRoundToExistingOffset
&& endOffset
< mStart
+ 1) {
1965 endOffset
= mStart
+ 1;
1967 if (NS_WARN_IF(endOffset
< startOffset
)) {
1970 for (uint32_t i
= 0; i
< endOffset
- startOffset
; i
++) {
1971 rect
= rect
.Union(mRects
[startOffset
- mStart
+ i
]);
1976 } // namespace mozilla