Bug 1892041 - Part 3: Update test exclusions. r=spidermonkey-reviewers,dminor
[gecko.git] / widget / ContentCache.cpp
blobb6093fb3f78ffd9b24b110944d858d35b5fe57b6
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: sw=2 ts=8 et :
3 */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "ContentCache.h"
10 #include <utility>
12 #include "IMEData.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"
26 namespace mozilla {
28 using namespace dom;
29 using namespace widget;
31 static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; }
33 static const char* GetNotificationName(const IMENotification* aNotification) {
34 if (!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())) {
50 return false;
52 } else {
53 // mSelection depends on mText.
54 if (mSelection.isSome() && NS_WARN_IF(!mSelection->IsValidIn(*mText))) {
55 return false;
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()))) {
63 return false;
67 // mTextRectArray stores character rects around composition string.
68 // Note that even if we fail to collect the rects, we may keep storing
69 // mCompositionStart.
70 if (mTextRectArray.isSome()) {
71 if (NS_WARN_IF(mCompositionStart.isNothing())) {
72 return false;
76 return true;
79 void ContentCache::AssertIfInvalid() const {
80 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
81 if (IsValid()) {
82 return;
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<<()`.
88 nsPrintfCString info(
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.
92 mText.isNothing()
93 ? "Nothing"
94 : nsPrintfCString("{ Length()=%zu }", mText->Length()).get(),
95 mSelection.isNothing()
96 ? "Nothing"
97 : nsPrintfCString("{ mAnchor=%u, mFocus=%u }", mSelection->mAnchor,
98 mSelection->mFocus)
99 .get(),
100 mCaret.isNothing()
101 ? "Nothing"
102 : nsPrintfCString("{ mOffset=%u }", mCaret->mOffset).get(),
103 mTextRectArray.isNothing()
104 ? "Nothing"
105 : nsPrintfCString("{ Length()=%u }", mTextRectArray->Length()).get(),
106 mCompositionStart.isNothing()
107 ? "Nothing"
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();
122 mLastCommit.reset();
123 mText.reset();
124 mSelection.reset();
125 mFirstCharRect.SetEmpty();
126 mCaret.reset();
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);
137 if (composition) {
138 nsAutoString lastCommitString;
139 if (aCompositionEvent.mMessage == eCompositionCommitAsIs) {
140 lastCommitString = composition->CommitStringIfCommittedAsIs();
141 } else {
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));
149 MOZ_LOG(
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),
154 PrintStringDetail(
155 aCompositionEvent.mData,
156 PrintStringDetail::kMaxLengthForCompositionString)
157 .get(),
158 ToString(mLastCommit).c_str()));
159 return;
163 if (mLastCommit.isSome()) {
164 MOZ_LOG(
165 sContentCacheLog, LogLevel::Debug,
166 ("0x%p OnCompositionEvent(), resetting the last composition string "
167 "data (aCompositionEvent={ mMessage=%s, mData=\"%s\"}, "
168 "mLastCommit=%s)",
169 this, ToChar(aCompositionEvent.mMessage),
170 PrintStringDetail(aCompositionEvent.mData,
171 PrintStringDetail::kMaxLengthForCompositionString)
172 .get(),
173 ToString(mLastCommit).c_str()));
174 mLastCommit.reset();
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);
186 AssertIfInvalid();
187 return (textCached || editorRectCached) && IsValid();
190 bool ContentCacheInChild::CacheSelection(nsIWidget* aWidget,
191 const IMENotification* aNotification) {
192 MOZ_LOG(
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()));
198 mSelection.reset();
199 mCaret.reset();
201 if (mText.isNothing()) {
202 return false;
205 nsEventStatus status = nsEventStatus_eIgnore;
206 WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
207 aWidget);
208 aWidget->DispatchEvent(&querySelectedTextEvent, status);
209 if (NS_WARN_IF(querySelectedTextEvent.Failed())) {
210 MOZ_LOG(
211 sContentCacheLog, LogLevel::Error,
212 ("0x%p CacheSelection(), FAILED, couldn't retrieve the selected text",
213 this));
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 "
226 "blurred",
227 this));
228 AssertIfInvalid();
229 return false;
230 } else {
231 mSelection.emplace(querySelectedTextEvent);
234 return (CacheCaretAndTextRects(aWidget, aNotification) ||
235 querySelectedTextEvent.Succeeded()) &&
236 IsValid();
239 bool ContentCacheInChild::CacheCaret(nsIWidget* aWidget,
240 const IMENotification* aNotification) {
241 mCaret.reset();
243 if (mSelection.isNothing()) {
244 return false;
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 "
262 "at offset=%u",
263 this, offset));
264 return false;
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()));
271 AssertIfInvalid();
272 return IsValid();
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())) {
285 MOZ_LOG(
286 sContentCacheLog, LogLevel::Error,
287 ("0x%p CacheEditorRect(), FAILED, couldn't retrieve the editor rect",
288 this));
289 return false;
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 "
297 "blurred",
298 this));
299 return false;
301 mEditorRect = queryEditorRectEvent.mReply->mRect;
302 MOZ_LOG(sContentCacheLog, LogLevel::Info,
303 ("0x%p CacheEditorRect(), Succeeded, mEditorRect=%s", this,
304 ToString(mEditorRect).c_str()));
305 return true;
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);
316 AssertIfInvalid();
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,
328 aWidget);
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));
334 mText.reset();
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 "
342 "blurred",
343 this));
344 mText.reset();
345 } else {
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)
350 .get()));
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(),
363 PrintStringDetail(
364 nsDependentSubstring(mText.ref(), mLastCommit->StartOffset(),
365 mLastCommit->Length()),
366 PrintStringDetail::kMaxLengthForCompositionString)
367 .get()));
368 mLastCommit.reset();
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())) {
375 mSelection.reset();
376 mCaret.reset();
377 mTextRectArray.reset();
378 AssertIfInvalid();
379 return false;
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())) {
394 return false;
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);
405 return true;
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,
413 aWidget);
414 queryTextRectsEvent.InitForQueryTextRectArray(aOffset, aLength);
415 aWidget->DispatchEvent(&queryTextRectsEvent, status);
416 if (NS_WARN_IF(queryTextRectsEvent.Failed())) {
417 aCharRectArray.Clear();
418 return false;
420 aCharRectArray = std::move(queryTextRectsEvent.mReply->mRectArray);
421 return true;
424 bool ContentCacheInChild::CacheTextRects(nsIWidget* aWidget,
425 const IMENotification* aNotification) {
426 MOZ_LOG(
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
447 // composition too.
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",
455 this));
456 mTextRectArray.reset();
458 } else {
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.
478 else {
479 RectArray rects;
480 const uint32_t startOffset = mSelection->mHasRange && mSelection->mAnchor
481 ? mSelection->mAnchor - 1u
482 : 0u;
483 const uint32_t length =
484 mSelection->mHasRange && mSelection->mAnchor ? 2u : 1u;
485 if (NS_WARN_IF(
486 !QueryCharRectArray(aWidget, startOffset, length, rects))) {
487 MOZ_LOG(
488 sContentCacheLog, LogLevel::Error,
489 ("0x%p CacheTextRects(), FAILED, couldn't retrieve text rect "
490 "array around the selection anchor (%s)",
491 this,
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];
501 } else {
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.
529 else {
530 MOZ_ASSERT(mSelection->mHasRange);
531 RectArray rects;
532 const uint32_t startOffset =
533 mSelection->mFocus ? mSelection->mFocus - 1u : 0u;
534 const uint32_t length = mSelection->mFocus ? 2u : 1u;
535 if (NS_WARN_IF(
536 !QueryCharRectArray(aWidget, startOffset, length, rects))) {
537 MOZ_LOG(
538 sContentCacheLog, LogLevel::Error,
539 ("0x%p CacheTextRects(), FAILED, couldn't retrieve text rect "
540 "array around the selection focus (%s)",
541 this,
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",
551 this));
552 } else {
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",
579 this));
580 } else {
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);
600 } else {
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",
606 this));
607 mFirstCharRect.SetEmpty();
608 } else {
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",
632 this));
633 mLastCommitStringTextRectArray.reset();
634 mLastCommit.reset();
636 MOZ_ASSERT((mLastCommitStringTextRectArray.isSome()
637 ? mLastCommitStringTextRectArray->mRects.Length()
638 : 0) == (mLastCommit.isSome() ? mLastCommit->Length() : 0));
639 } else {
640 mLastCommitStringTextRectArray.reset();
643 MOZ_LOG(
644 sContentCacheLog, LogLevel::Info,
645 ("0x%p CacheTextRects(), Succeeded, "
646 "mText=%s, mTextRectArray=%s, mSelection=%s, "
647 "mFirstCharRect=%s, mLastCommitStringTextRectArray=%s",
648 this,
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()));
653 AssertIfInvalid();
654 return IsValid();
657 bool ContentCacheInChild::SetSelection(
658 nsIWidget* aWidget,
659 const IMENotification::SelectionChangeDataBase& aSelectionChangeData) {
660 MOZ_LOG(
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())) {
667 return false;
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()) {
677 MOZ_LOG(
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()));
682 mLastCommit.reset();
686 CacheCaret(aWidget);
687 CacheTextRects(aWidget);
689 return mSelection.isSome() && IsValid();
692 /*****************************************************************************
693 * mozilla::ContentCacheInParent
694 *****************************************************************************/
696 ContentCacheInParent::ContentCacheInParent(BrowserParent& aBrowserParent)
697 : mBrowserParent(aBrowserParent),
698 mCommitStringByRequest(nullptr),
699 mPendingCommitLength(0),
700 mIsChildIgnoringCompositionEvents(false) {}
702 void ContentCacheInParent::AssignContent(const ContentCache& aOther,
703 nsIWidget* aWidget,
704 const IMENotification* aNotification) {
705 MOZ_DIAGNOSTIC_ASSERT(aOther.IsValid());
707 mText = aOther.mText;
708 mSelection = aOther.mSelection;
709 mFirstCharRect = aOther.mFirstCharRect;
710 mCaret = aOther.mCaret;
711 mTextRectArray = aOther.mTextRectArray;
712 mLastCommitStringTextRectArray = aOther.mLastCommitStringTextRectArray;
713 mEditorRect = aOther.mEditorRect;
715 // Only when there is one composition, the TextComposition instance in this
716 // process is managing the composition in the remote process. Therefore,
717 // we shouldn't update composition start offset of TextComposition with
718 // old composition which is still being handled by the child process.
719 if (WidgetHasComposition() && mHandlingCompositions.Length() == 1 &&
720 mCompositionStart.isSome()) {
721 IMEStateManager::MaybeStartOffsetUpdatedInChild(aWidget,
722 mCompositionStart.value());
725 // When this instance allows to query content relative to composition string,
726 // we should modify mCompositionStart with the latest information in the
727 // remote process because now we have the information around the composition
728 // string.
729 mCompositionStartInChild = aOther.mCompositionStart;
730 if (WidgetHasComposition() || HasPendingCommit()) {
731 if (mCompositionStartInChild.isSome()) {
732 if (mCompositionStart.valueOr(UINT32_MAX) !=
733 mCompositionStartInChild.value()) {
734 mCompositionStart = mCompositionStartInChild;
735 mPendingCommitLength = 0;
737 } else if (mCompositionStart.isSome() && mSelection.isSome() &&
738 mSelection->mHasRange &&
739 mCompositionStart.value() != mSelection->StartOffset()) {
740 mCompositionStart = Some(mSelection->StartOffset());
741 mPendingCommitLength = 0;
745 MOZ_LOG(
746 sContentCacheLog, LogLevel::Info,
747 ("0x%p AssignContent(aNotification=%s), "
748 "Succeeded, mText=%s, mSelection=%s, mFirstCharRect=%s, "
749 "mCaret=%s, mTextRectArray=%s, WidgetHasComposition()=%s, "
750 "mHandlingCompositions.Length()=%zu, mCompositionStart=%s, "
751 "mPendingCommitLength=%u, mEditorRect=%s, "
752 "mLastCommitStringTextRectArray=%s",
753 this, GetNotificationName(aNotification),
754 PrintStringDetail(mText, PrintStringDetail::kMaxLengthForEditor).get(),
755 ToString(mSelection).c_str(), ToString(mFirstCharRect).c_str(),
756 ToString(mCaret).c_str(), ToString(mTextRectArray).c_str(),
757 GetBoolName(WidgetHasComposition()), mHandlingCompositions.Length(),
758 ToString(mCompositionStart).c_str(), mPendingCommitLength,
759 ToString(mEditorRect).c_str(),
760 ToString(mLastCommitStringTextRectArray).c_str()));
763 bool ContentCacheInParent::HandleQueryContentEvent(
764 WidgetQueryContentEvent& aEvent, nsIWidget* aWidget) const {
765 MOZ_ASSERT(aWidget);
767 // ContentCache doesn't store offset of its start with XP linebreaks.
768 // So, we don't support to query contents relative to composition start
769 // offset with XP linebreaks.
770 if (NS_WARN_IF(!aEvent.mUseNativeLineBreak)) {
771 MOZ_LOG(sContentCacheLog, LogLevel::Error,
772 ("0x%p HandleQueryContentEvent(), FAILED due to query with XP "
773 "linebreaks",
774 this));
775 return false;
778 if (NS_WARN_IF(!aEvent.mInput.IsValidOffset())) {
779 MOZ_LOG(
780 sContentCacheLog, LogLevel::Error,
781 ("0x%p HandleQueryContentEvent(), FAILED due to invalid offset", this));
782 return false;
785 if (NS_WARN_IF(!aEvent.mInput.IsValidEventMessage(aEvent.mMessage))) {
786 MOZ_LOG(
787 sContentCacheLog, LogLevel::Error,
788 ("0x%p HandleQueryContentEvent(), FAILED due to invalid event message",
789 this));
790 return false;
793 bool isRelativeToInsertionPoint = aEvent.mInput.mRelativeToInsertionPoint;
794 if (isRelativeToInsertionPoint) {
795 MOZ_LOG(
796 sContentCacheLog, LogLevel::Debug,
797 ("0x%p HandleQueryContentEvent(), "
798 "making offset absolute... aEvent={ mMessage=%s, mInput={ "
799 "mOffset=%" PRId64 ", mLength=%" PRIu32 " } }, "
800 "WidgetHasComposition()=%s, HasPendingCommit()=%s, "
801 "mCompositionStart=%" PRIu32 ", "
802 "mPendingCommitLength=%" PRIu32 ", mSelection=%s",
803 this, ToChar(aEvent.mMessage), aEvent.mInput.mOffset,
804 aEvent.mInput.mLength, GetBoolName(WidgetHasComposition()),
805 GetBoolName(HasPendingCommit()), mCompositionStart.valueOr(UINT32_MAX),
806 mPendingCommitLength, ToString(mSelection).c_str()));
807 if (WidgetHasComposition() || HasPendingCommit()) {
808 if (NS_WARN_IF(mCompositionStart.isNothing()) ||
809 NS_WARN_IF(!aEvent.mInput.MakeOffsetAbsolute(
810 mCompositionStart.value() + mPendingCommitLength))) {
811 MOZ_LOG(
812 sContentCacheLog, LogLevel::Error,
813 ("0x%p HandleQueryContentEvent(), FAILED due to "
814 "aEvent.mInput.MakeOffsetAbsolute(mCompositionStart + "
815 "mPendingCommitLength) failure, "
816 "mCompositionStart=%" PRIu32 ", mPendingCommitLength=%" PRIu32 ", "
817 "aEvent={ mMessage=%s, mInput={ mOffset=%" PRId64
818 ", mLength=%" PRIu32 " } }",
819 this, mCompositionStart.valueOr(UINT32_MAX), mPendingCommitLength,
820 ToChar(aEvent.mMessage), aEvent.mInput.mOffset,
821 aEvent.mInput.mLength));
822 return false;
824 } else if (NS_WARN_IF(mSelection.isNothing())) {
825 MOZ_LOG(sContentCacheLog, LogLevel::Error,
826 ("0x%p HandleQueryContentEvent(), FAILED due to mSelection is "
827 "Nothing",
828 this));
829 return false;
830 } else if (NS_WARN_IF(mSelection->mHasRange)) {
831 MOZ_LOG(sContentCacheLog, LogLevel::Error,
832 ("0x%p HandleQueryContentEvent(), FAILED due to there is no "
833 "selection range, but the query requested with relative offset "
834 "from selection",
835 this));
836 return false;
837 } else if (NS_WARN_IF(!aEvent.mInput.MakeOffsetAbsolute(
838 mSelection->StartOffset() + mPendingCommitLength))) {
839 MOZ_LOG(sContentCacheLog, LogLevel::Error,
840 ("0x%p HandleQueryContentEvent(), FAILED due to "
841 "aEvent.mInput.MakeOffsetAbsolute(mSelection->StartOffset() + "
842 "mPendingCommitLength) failure, mSelection=%s, "
843 "mPendingCommitLength=%" PRIu32 ", aEvent={ mMessage=%s, "
844 "mInput={ mOffset=%" PRId64 ", mLength=%" PRIu32 " } }",
845 this, ToString(mSelection).c_str(), mPendingCommitLength,
846 ToChar(aEvent.mMessage), aEvent.mInput.mOffset,
847 aEvent.mInput.mLength));
848 return false;
852 switch (aEvent.mMessage) {
853 case eQuerySelectedText:
854 MOZ_LOG(sContentCacheLog, LogLevel::Info,
855 ("0x%p HandleQueryContentEvent(aEvent={ "
856 "mMessage=eQuerySelectedText }, aWidget=0x%p)",
857 this, aWidget));
858 if (MOZ_UNLIKELY(NS_WARN_IF(mSelection.isNothing()))) {
859 // If content cache hasn't been initialized properly, make the query
860 // failed.
861 MOZ_LOG(sContentCacheLog, LogLevel::Error,
862 ("0x%p HandleQueryContentEvent(), FAILED because mSelection "
863 "is Nothing",
864 this));
865 return false;
867 MOZ_DIAGNOSTIC_ASSERT(mText.isSome());
868 MOZ_DIAGNOSTIC_ASSERT(mSelection->IsValidIn(*mText));
869 aEvent.EmplaceReply();
870 aEvent.mReply->mFocusedWidget = aWidget;
871 if (mSelection->mHasRange) {
872 if (MOZ_LIKELY(mText.isSome())) {
873 aEvent.mReply->mOffsetAndData.emplace(
874 mSelection->StartOffset(),
875 Substring(mText.ref(), mSelection->StartOffset(),
876 mSelection->Length()),
877 OffsetAndDataFor::SelectedString);
878 } else {
879 // TODO: Investigate this case. I find this during
880 // test_mousecapture.xhtml on Linux.
881 aEvent.mReply->mOffsetAndData.emplace(
882 0u, EmptyString(), OffsetAndDataFor::SelectedString);
885 aEvent.mReply->mWritingMode = mSelection->mWritingMode;
886 MOZ_LOG(sContentCacheLog, LogLevel::Info,
887 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
888 "mMessage=eQuerySelectedText, mReply=%s }",
889 this, ToString(aEvent.mReply).c_str()));
890 return true;
891 case eQueryTextContent: {
892 MOZ_LOG(sContentCacheLog, LogLevel::Info,
893 ("0x%p HandleQueryContentEvent(aEvent={ "
894 "mMessage=eQueryTextContent, mInput={ mOffset=%" PRId64
895 ", mLength=%u } }, aWidget=0x%p), mText->Length()=%zu",
896 this, aEvent.mInput.mOffset, aEvent.mInput.mLength, aWidget,
897 mText.isSome() ? mText->Length() : 0u));
898 if (MOZ_UNLIKELY(NS_WARN_IF(mText.isNothing()))) {
899 MOZ_LOG(sContentCacheLog, LogLevel::Error,
900 ("0x%p HandleQueryContentEvent(), FAILED because "
901 "there is no text data",
902 this));
903 return false;
905 const uint32_t inputOffset = aEvent.mInput.mOffset;
906 const uint32_t inputEndOffset = std::min<uint32_t>(
907 aEvent.mInput.EndOffset(), mText.isSome() ? mText->Length() : 0u);
908 if (MOZ_UNLIKELY(NS_WARN_IF(inputEndOffset < inputOffset))) {
909 MOZ_LOG(sContentCacheLog, LogLevel::Error,
910 ("0x%p HandleQueryContentEvent(), FAILED because "
911 "inputOffset=%u is larger than inputEndOffset=%u",
912 this, inputOffset, inputEndOffset));
913 return false;
915 aEvent.EmplaceReply();
916 aEvent.mReply->mFocusedWidget = aWidget;
917 const nsAString& textInQueriedRange =
918 inputEndOffset > inputOffset
919 ? static_cast<const nsAString&>(Substring(
920 mText.ref(), inputOffset, inputEndOffset - inputOffset))
921 : static_cast<const nsAString&>(EmptyString());
922 aEvent.mReply->mOffsetAndData.emplace(inputOffset, textInQueriedRange,
923 OffsetAndDataFor::EditorString);
924 // TODO: Support font ranges
925 MOZ_LOG(sContentCacheLog, LogLevel::Info,
926 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
927 "mMessage=eQueryTextContent, mReply=%s }",
928 this, ToString(aEvent.mReply).c_str()));
929 return true;
931 case eQueryTextRect: {
932 MOZ_LOG(sContentCacheLog, LogLevel::Info,
933 ("0x%p HandleQueryContentEvent("
934 "aEvent={ mMessage=eQueryTextRect, mInput={ mOffset=%" PRId64
935 ", mLength=%u } }, aWidget=0x%p), mText->Length()=%zu",
936 this, aEvent.mInput.mOffset, aEvent.mInput.mLength, aWidget,
937 mText.isSome() ? mText->Length() : 0u));
938 // Note that if the query is relative to insertion point, the query was
939 // probably requested by native IME. In such case, we should return
940 // non-empty rect since returning failure causes IME showing its window
941 // at odd position.
942 LayoutDeviceIntRect textRect;
943 if (aEvent.mInput.mLength) {
944 if (MOZ_UNLIKELY(NS_WARN_IF(
945 !GetUnionTextRects(aEvent.mInput.mOffset, aEvent.mInput.mLength,
946 isRelativeToInsertionPoint, textRect)))) {
947 // XXX We don't have cache for this request.
948 MOZ_LOG(sContentCacheLog, LogLevel::Error,
949 ("0x%p HandleQueryContentEvent(), FAILED to get union rect",
950 this));
951 return false;
953 } else {
954 // If the length is 0, we should return caret rect instead.
955 if (NS_WARN_IF(!GetCaretRect(aEvent.mInput.mOffset,
956 isRelativeToInsertionPoint, textRect))) {
957 MOZ_LOG(sContentCacheLog, LogLevel::Error,
958 ("0x%p HandleQueryContentEvent(), FAILED to get caret rect",
959 this));
960 return false;
963 aEvent.EmplaceReply();
964 aEvent.mReply->mFocusedWidget = aWidget;
965 aEvent.mReply->mRect = textRect;
966 const nsAString& textInQueriedRange =
967 mText.isSome() && aEvent.mInput.mOffset <
968 static_cast<int64_t>(
969 mText.isSome() ? mText->Length() : 0u)
970 ? static_cast<const nsAString&>(
971 Substring(mText.ref(), aEvent.mInput.mOffset,
972 mText->Length() >= aEvent.mInput.EndOffset()
973 ? aEvent.mInput.mLength
974 : UINT32_MAX))
975 : static_cast<const nsAString&>(EmptyString());
976 aEvent.mReply->mOffsetAndData.emplace(aEvent.mInput.mOffset,
977 textInQueriedRange,
978 OffsetAndDataFor::EditorString);
979 // XXX This may be wrong if storing range isn't in the selection range.
980 aEvent.mReply->mWritingMode =
981 mSelection.isSome() ? mSelection->mWritingMode : WritingMode();
982 MOZ_LOG(sContentCacheLog, LogLevel::Info,
983 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
984 "mMessage=eQueryTextRect mReply=%s }",
985 this, ToString(aEvent.mReply).c_str()));
986 return true;
988 case eQueryCaretRect: {
989 MOZ_LOG(
990 sContentCacheLog, LogLevel::Info,
991 ("0x%p HandleQueryContentEvent(aEvent={ mMessage=eQueryCaretRect, "
992 "mInput={ mOffset=%" PRId64
993 " } }, aWidget=0x%p), mText->Length()=%zu",
994 this, aEvent.mInput.mOffset, aWidget,
995 mText.isSome() ? mText->Length() : 0u));
996 // Note that if the query is relative to insertion point, the query was
997 // probably requested by native IME. In such case, we should return
998 // non-empty rect since returning failure causes IME showing its window
999 // at odd position.
1000 LayoutDeviceIntRect caretRect;
1001 if (NS_WARN_IF(!GetCaretRect(aEvent.mInput.mOffset,
1002 isRelativeToInsertionPoint, caretRect))) {
1003 MOZ_LOG(sContentCacheLog, LogLevel::Error,
1004 ("0x%p HandleQueryContentEvent(),FAILED to get caret rect",
1005 this));
1006 return false;
1008 aEvent.EmplaceReply();
1009 aEvent.mReply->mFocusedWidget = aWidget;
1010 aEvent.mReply->mRect = caretRect;
1011 aEvent.mReply->mOffsetAndData.emplace(aEvent.mInput.mOffset,
1012 EmptyString(),
1013 OffsetAndDataFor::SelectedString);
1014 // TODO: Set mWritingMode here
1015 MOZ_LOG(sContentCacheLog, LogLevel::Info,
1016 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
1017 "mMessage=eQueryCaretRect, mReply=%s }",
1018 this, ToString(aEvent.mReply).c_str()));
1019 return true;
1021 case eQueryEditorRect:
1022 MOZ_LOG(sContentCacheLog, LogLevel::Info,
1023 ("0x%p HandleQueryContentEvent(aEvent={ "
1024 "mMessage=eQueryEditorRect }, aWidget=0x%p)",
1025 this, aWidget));
1026 // XXX This query should fail if no editable elmenet has focus. Or,
1027 // perhaps, should return rect of the window instead.
1028 aEvent.EmplaceReply();
1029 aEvent.mReply->mFocusedWidget = aWidget;
1030 aEvent.mReply->mRect = mEditorRect;
1031 MOZ_LOG(sContentCacheLog, LogLevel::Info,
1032 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
1033 "mMessage=eQueryEditorRect, mReply=%s }",
1034 this, ToString(aEvent.mReply).c_str()));
1035 return true;
1036 default:
1037 aEvent.EmplaceReply();
1038 aEvent.mReply->mFocusedWidget = aWidget;
1039 if (NS_WARN_IF(aEvent.Failed())) {
1040 MOZ_LOG(
1041 sContentCacheLog, LogLevel::Error,
1042 ("0x%p HandleQueryContentEvent(), FAILED due to not set enough "
1043 "data, aEvent={ mMessage=%s, mReply=%s }",
1044 this, ToChar(aEvent.mMessage), ToString(aEvent.mReply).c_str()));
1045 return false;
1047 MOZ_LOG(sContentCacheLog, LogLevel::Info,
1048 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
1049 "mMessage=%s, mReply=%s }",
1050 this, ToChar(aEvent.mMessage), ToString(aEvent.mReply).c_str()));
1051 return true;
1055 bool ContentCacheInParent::GetTextRect(uint32_t aOffset,
1056 bool aRoundToExistingOffset,
1057 LayoutDeviceIntRect& aTextRect) const {
1058 MOZ_LOG(
1059 sContentCacheLog, LogLevel::Info,
1060 ("0x%p GetTextRect(aOffset=%u, aRoundToExistingOffset=%s), "
1061 "mTextRectArray=%s, mSelection=%s, mLastCommitStringTextRectArray=%s",
1062 this, aOffset, GetBoolName(aRoundToExistingOffset),
1063 ToString(mTextRectArray).c_str(), ToString(mSelection).c_str(),
1064 ToString(mLastCommitStringTextRectArray).c_str()));
1066 if (!aOffset) {
1067 NS_WARNING_ASSERTION(!mFirstCharRect.IsEmpty(), "empty rect");
1068 aTextRect = mFirstCharRect;
1069 return !aTextRect.IsEmpty();
1071 if (mSelection.isSome() && mSelection->mHasRange) {
1072 if (aOffset == mSelection->mAnchor) {
1073 NS_WARNING_ASSERTION(
1074 !mSelection->mAnchorCharRects[eNextCharRect].IsEmpty(), "empty rect");
1075 aTextRect = mSelection->mAnchorCharRects[eNextCharRect];
1076 return !aTextRect.IsEmpty();
1078 if (mSelection->mAnchor && aOffset == mSelection->mAnchor - 1) {
1079 NS_WARNING_ASSERTION(
1080 !mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty(), "empty rect");
1081 aTextRect = mSelection->mAnchorCharRects[ePrevCharRect];
1082 return !aTextRect.IsEmpty();
1084 if (aOffset == mSelection->mFocus) {
1085 NS_WARNING_ASSERTION(
1086 !mSelection->mFocusCharRects[eNextCharRect].IsEmpty(), "empty rect");
1087 aTextRect = mSelection->mFocusCharRects[eNextCharRect];
1088 return !aTextRect.IsEmpty();
1090 if (mSelection->mFocus && aOffset == mSelection->mFocus - 1) {
1091 NS_WARNING_ASSERTION(
1092 !mSelection->mFocusCharRects[ePrevCharRect].IsEmpty(), "empty rect");
1093 aTextRect = mSelection->mFocusCharRects[ePrevCharRect];
1094 return !aTextRect.IsEmpty();
1098 if (mTextRectArray.isSome() && mTextRectArray->IsOffsetInRange(aOffset)) {
1099 aTextRect = mTextRectArray->GetRect(aOffset);
1100 return !aTextRect.IsEmpty();
1103 if (mLastCommitStringTextRectArray.isSome() &&
1104 mLastCommitStringTextRectArray->IsOffsetInRange(aOffset)) {
1105 aTextRect = mLastCommitStringTextRectArray->GetRect(aOffset);
1106 return !aTextRect.IsEmpty();
1109 if (!aRoundToExistingOffset) {
1110 aTextRect.SetEmpty();
1111 return false;
1114 if (mTextRectArray.isNothing() || !mTextRectArray->HasRects()) {
1115 // If there are no rects in mTextRectArray, we should refer the start of
1116 // the selection if there is because IME must query a char rect around it if
1117 // there is no composition.
1118 if (mSelection.isNothing()) {
1119 // Unfortunately, there is no data about text rect...
1120 aTextRect.SetEmpty();
1121 return false;
1123 aTextRect = mSelection->StartCharRect();
1124 return !aTextRect.IsEmpty();
1127 // Although we may have mLastCommitStringTextRectArray here and it must have
1128 // previous character rects at selection. However, we should stop using it
1129 // because it's stored really short time after commiting a composition.
1130 // So, multiple query may return different rect and it may cause flickerling
1131 // the IME UI.
1132 uint32_t offset = aOffset;
1133 if (offset < mTextRectArray->StartOffset()) {
1134 offset = mTextRectArray->StartOffset();
1135 } else {
1136 offset = mTextRectArray->EndOffset() - 1;
1138 aTextRect = mTextRectArray->GetRect(offset);
1139 return !aTextRect.IsEmpty();
1142 bool ContentCacheInParent::GetUnionTextRects(
1143 uint32_t aOffset, uint32_t aLength, bool aRoundToExistingOffset,
1144 LayoutDeviceIntRect& aUnionTextRect) const {
1145 MOZ_LOG(sContentCacheLog, LogLevel::Info,
1146 ("0x%p GetUnionTextRects(aOffset=%u, "
1147 "aLength=%u, aRoundToExistingOffset=%s), mTextRectArray=%s, "
1148 "mSelection=%s, mLastCommitStringTextRectArray=%s",
1149 this, aOffset, aLength, GetBoolName(aRoundToExistingOffset),
1150 ToString(mTextRectArray).c_str(), ToString(mSelection).c_str(),
1151 ToString(mLastCommitStringTextRectArray).c_str()));
1153 CheckedInt<uint32_t> endOffset = CheckedInt<uint32_t>(aOffset) + aLength;
1154 if (!endOffset.isValid()) {
1155 return false;
1158 if (mSelection.isSome() && !mSelection->IsCollapsed() &&
1159 aOffset == mSelection->StartOffset() && aLength == mSelection->Length()) {
1160 NS_WARNING_ASSERTION(!mSelection->mRect.IsEmpty(), "empty rect");
1161 aUnionTextRect = mSelection->mRect;
1162 return !aUnionTextRect.IsEmpty();
1165 if (aLength == 1) {
1166 if (!aOffset) {
1167 NS_WARNING_ASSERTION(!mFirstCharRect.IsEmpty(), "empty rect");
1168 aUnionTextRect = mFirstCharRect;
1169 return !aUnionTextRect.IsEmpty();
1171 if (mSelection.isSome() && mSelection->mHasRange) {
1172 if (aOffset == mSelection->mAnchor) {
1173 NS_WARNING_ASSERTION(
1174 !mSelection->mAnchorCharRects[eNextCharRect].IsEmpty(),
1175 "empty rect");
1176 aUnionTextRect = mSelection->mAnchorCharRects[eNextCharRect];
1177 return !aUnionTextRect.IsEmpty();
1179 if (mSelection->mAnchor && aOffset == mSelection->mAnchor - 1) {
1180 NS_WARNING_ASSERTION(
1181 !mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty(),
1182 "empty rect");
1183 aUnionTextRect = mSelection->mAnchorCharRects[ePrevCharRect];
1184 return !aUnionTextRect.IsEmpty();
1186 if (aOffset == mSelection->mFocus) {
1187 NS_WARNING_ASSERTION(
1188 !mSelection->mFocusCharRects[eNextCharRect].IsEmpty(),
1189 "empty rect");
1190 aUnionTextRect = mSelection->mFocusCharRects[eNextCharRect];
1191 return !aUnionTextRect.IsEmpty();
1193 if (mSelection->mFocus && aOffset == mSelection->mFocus - 1) {
1194 NS_WARNING_ASSERTION(
1195 !mSelection->mFocusCharRects[ePrevCharRect].IsEmpty(),
1196 "empty rect");
1197 aUnionTextRect = mSelection->mFocusCharRects[ePrevCharRect];
1198 return !aUnionTextRect.IsEmpty();
1203 // Even if some text rects are not cached of the queried range,
1204 // we should return union rect when the first character's rect is cached
1205 // since the first character rect is important and the others are not so
1206 // in most cases.
1208 if (!aOffset && mSelection.isSome() && mSelection->mHasRange &&
1209 aOffset != mSelection->mAnchor && aOffset != mSelection->mFocus &&
1210 (mTextRectArray.isNothing() ||
1211 !mTextRectArray->IsOffsetInRange(aOffset)) &&
1212 (mLastCommitStringTextRectArray.isNothing() ||
1213 !mLastCommitStringTextRectArray->IsOffsetInRange(aOffset))) {
1214 // The first character rect isn't cached.
1215 return false;
1218 // Use mLastCommitStringTextRectArray only when it overlaps with aOffset
1219 // even if aROundToExistingOffset is true for avoiding flickerling IME UI.
1220 // See the last comment in GetTextRect() for the detail.
1221 if (mLastCommitStringTextRectArray.isSome() &&
1222 mLastCommitStringTextRectArray->IsOverlappingWith(aOffset, aLength)) {
1223 aUnionTextRect =
1224 mLastCommitStringTextRectArray->GetUnionRectAsFarAsPossible(
1225 aOffset, aLength, aRoundToExistingOffset);
1226 } else {
1227 aUnionTextRect.SetEmpty();
1230 if (mTextRectArray.isSome() &&
1231 ((aRoundToExistingOffset && mTextRectArray->HasRects()) ||
1232 mTextRectArray->IsOverlappingWith(aOffset, aLength))) {
1233 aUnionTextRect =
1234 aUnionTextRect.Union(mTextRectArray->GetUnionRectAsFarAsPossible(
1235 aOffset, aLength, aRoundToExistingOffset));
1238 if (!aOffset) {
1239 aUnionTextRect = aUnionTextRect.Union(mFirstCharRect);
1241 if (mSelection.isSome() && mSelection->mHasRange) {
1242 if (aOffset <= mSelection->mAnchor &&
1243 mSelection->mAnchor < endOffset.value()) {
1244 aUnionTextRect =
1245 aUnionTextRect.Union(mSelection->mAnchorCharRects[eNextCharRect]);
1247 if (mSelection->mAnchor && aOffset <= mSelection->mAnchor - 1 &&
1248 mSelection->mAnchor - 1 < endOffset.value()) {
1249 aUnionTextRect =
1250 aUnionTextRect.Union(mSelection->mAnchorCharRects[ePrevCharRect]);
1252 if (aOffset <= mSelection->mFocus &&
1253 mSelection->mFocus < endOffset.value()) {
1254 aUnionTextRect =
1255 aUnionTextRect.Union(mSelection->mFocusCharRects[eNextCharRect]);
1257 if (mSelection->mFocus && aOffset <= mSelection->mFocus - 1 &&
1258 mSelection->mFocus - 1 < endOffset.value()) {
1259 aUnionTextRect =
1260 aUnionTextRect.Union(mSelection->mFocusCharRects[ePrevCharRect]);
1264 return !aUnionTextRect.IsEmpty();
1267 bool ContentCacheInParent::GetCaretRect(uint32_t aOffset,
1268 bool aRoundToExistingOffset,
1269 LayoutDeviceIntRect& aCaretRect) const {
1270 MOZ_LOG(sContentCacheLog, LogLevel::Info,
1271 ("0x%p GetCaretRect(aOffset=%u, aRoundToExistingOffset=%s), "
1272 "mCaret=%s, mTextRectArray=%s, mSelection=%s, mFirstCharRect=%s",
1273 this, aOffset, GetBoolName(aRoundToExistingOffset),
1274 ToString(mCaret).c_str(), ToString(mTextRectArray).c_str(),
1275 ToString(mSelection).c_str(), ToString(mFirstCharRect).c_str()));
1277 if (mCaret.isSome() && mCaret->mOffset == aOffset) {
1278 aCaretRect = mCaret->mRect;
1279 return true;
1282 // Guess caret rect from the text rect if it's stored.
1283 if (!GetTextRect(aOffset, aRoundToExistingOffset, aCaretRect)) {
1284 // There might be previous character rect in the cache. If so, we can
1285 // guess the caret rect with it.
1286 if (!aOffset ||
1287 !GetTextRect(aOffset - 1, aRoundToExistingOffset, aCaretRect)) {
1288 aCaretRect.SetEmpty();
1289 return false;
1292 if (mSelection.isSome() && mSelection->mWritingMode.IsVertical()) {
1293 aCaretRect.MoveToY(aCaretRect.YMost());
1294 } else {
1295 // XXX bidi-unaware.
1296 aCaretRect.MoveToX(aCaretRect.XMost());
1300 // XXX This is not bidi aware because we don't cache each character's
1301 // direction. However, this is usually used by IME, so, assuming the
1302 // character is in LRT context must not cause any problem.
1303 if (mSelection.isSome() && mSelection->mWritingMode.IsVertical()) {
1304 aCaretRect.SetHeight(mCaret.isSome() ? mCaret->mRect.Height() : 1);
1305 } else {
1306 aCaretRect.SetWidth(mCaret.isSome() ? mCaret->mRect.Width() : 1);
1308 return true;
1311 bool ContentCacheInParent::OnCompositionEvent(
1312 const WidgetCompositionEvent& aCompositionEvent) {
1313 MOZ_LOG(
1314 sContentCacheLog, LogLevel::Info,
1315 ("0x%p OnCompositionEvent(aCompositionEvent={ "
1316 "mMessage=%s, mData=\"%s\", mRanges->Length()=%zu }), "
1317 "PendingEventsNeedingAck()=%u, WidgetHasComposition()=%s, "
1318 "mHandlingCompositions.Length()=%zu, HasPendingCommit()=%s, "
1319 "mIsChildIgnoringCompositionEvents=%s, mCommitStringByRequest=0x%p",
1320 this, ToChar(aCompositionEvent.mMessage),
1321 PrintStringDetail(aCompositionEvent.mData,
1322 PrintStringDetail::kMaxLengthForCompositionString)
1323 .get(),
1324 aCompositionEvent.mRanges ? aCompositionEvent.mRanges->Length() : 0,
1325 PendingEventsNeedingAck(), GetBoolName(WidgetHasComposition()),
1326 mHandlingCompositions.Length(), GetBoolName(HasPendingCommit()),
1327 GetBoolName(mIsChildIgnoringCompositionEvents), mCommitStringByRequest));
1329 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1330 mDispatchedEventMessages.AppendElement(aCompositionEvent.mMessage);
1331 #endif // #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1333 // We must be able to simulate the selection because
1334 // we might not receive selection updates in time
1335 if (!WidgetHasComposition()) {
1336 if (mCompositionStartInChild.isSome()) {
1337 // If there is pending composition in the remote process, let's use
1338 // its start offset temporarily because this stores a lot of information
1339 // around it and the user must look around there, so, showing some UI
1340 // around it must make sense.
1341 mCompositionStart = mCompositionStartInChild;
1342 } else {
1343 mCompositionStart = Some(mSelection.isSome() && mSelection->mHasRange
1344 ? mSelection->StartOffset()
1345 : 0u);
1347 MOZ_ASSERT(aCompositionEvent.mMessage == eCompositionStart);
1348 mHandlingCompositions.AppendElement(
1349 HandlingCompositionData(aCompositionEvent.mCompositionId));
1352 mHandlingCompositions.LastElement().mSentCommitEvent =
1353 aCompositionEvent.CausesDOMCompositionEndEvent();
1354 MOZ_ASSERT(mHandlingCompositions.LastElement().mCompositionId ==
1355 aCompositionEvent.mCompositionId);
1357 if (!WidgetHasComposition()) {
1358 // mCompositionStart will be reset when commit event is completely handled
1359 // in the remote process.
1360 if (mHandlingCompositions.Length() == 1u) {
1361 mPendingCommitLength = aCompositionEvent.mData.Length();
1363 MOZ_ASSERT(HasPendingCommit());
1364 } else if (aCompositionEvent.mMessage != eCompositionStart) {
1365 mHandlingCompositions.LastElement().mCompositionString =
1366 aCompositionEvent.mData;
1369 // During REQUEST_TO_COMMIT_COMPOSITION or REQUEST_TO_CANCEL_COMPOSITION,
1370 // widget usually sends a eCompositionChange and/or eCompositionCommit event
1371 // to finalize or clear the composition, respectively. In this time,
1372 // we need to intercept all composition events here and pass the commit
1373 // string for returning to the remote process as a result of
1374 // RequestIMEToCommitComposition(). Then, eCommitComposition event will
1375 // be dispatched with the committed string in the remote process internally.
1376 if (mCommitStringByRequest) {
1377 if (aCompositionEvent.mMessage == eCompositionCommitAsIs) {
1378 *mCommitStringByRequest =
1379 mHandlingCompositions.LastElement().mCompositionString;
1380 } else {
1381 MOZ_ASSERT(aCompositionEvent.mMessage == eCompositionChange ||
1382 aCompositionEvent.mMessage == eCompositionCommit);
1383 *mCommitStringByRequest = aCompositionEvent.mData;
1385 // We need to wait eCompositionCommitRequestHandled from the remote process
1386 // in this case. Therefore, mPendingEventsNeedingAck needs to be
1387 // incremented here.
1388 if (!WidgetHasComposition()) {
1389 mHandlingCompositions.LastElement().mPendingEventsNeedingAck++;
1391 return false;
1394 mHandlingCompositions.LastElement().mPendingEventsNeedingAck++;
1395 return true;
1398 void ContentCacheInParent::OnSelectionEvent(
1399 const WidgetSelectionEvent& aSelectionEvent) {
1400 MOZ_LOG(sContentCacheLog, LogLevel::Info,
1401 ("0x%p OnSelectionEvent(aEvent={ "
1402 "mMessage=%s, mOffset=%u, mLength=%u, mReversed=%s, "
1403 "mExpandToClusterBoundary=%s, mUseNativeLineBreak=%s }), "
1404 "PendingEventsNeedingAck()=%u, WidgetHasComposition()=%s, "
1405 "mHandlingCompositions.Length()=%zu, HasPendingCommit()=%s, "
1406 "mIsChildIgnoringCompositionEvents=%s",
1407 this, ToChar(aSelectionEvent.mMessage), aSelectionEvent.mOffset,
1408 aSelectionEvent.mLength, GetBoolName(aSelectionEvent.mReversed),
1409 GetBoolName(aSelectionEvent.mExpandToClusterBoundary),
1410 GetBoolName(aSelectionEvent.mUseNativeLineBreak),
1411 PendingEventsNeedingAck(), GetBoolName(WidgetHasComposition()),
1412 mHandlingCompositions.Length(), GetBoolName(HasPendingCommit()),
1413 GetBoolName(mIsChildIgnoringCompositionEvents)));
1415 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1416 mDispatchedEventMessages.AppendElement(aSelectionEvent.mMessage);
1417 #endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
1419 mPendingSetSelectionEventNeedingAck++;
1422 void ContentCacheInParent::OnEventNeedingAckHandled(nsIWidget* aWidget,
1423 EventMessage aMessage,
1424 uint32_t aCompositionId) {
1425 // This is called when the child process receives WidgetCompositionEvent or
1426 // WidgetSelectionEvent.
1428 HandlingCompositionData* handlingCompositionData =
1429 aMessage != eSetSelection ? GetHandlingCompositionData(aCompositionId)
1430 : nullptr;
1432 MOZ_LOG(sContentCacheLog, LogLevel::Info,
1433 ("0x%p OnEventNeedingAckHandled(aWidget=0x%p, aMessage=%s, "
1434 "aCompositionId=%" PRIu32
1435 "), PendingEventsNeedingAck()=%u, WidgetHasComposition()=%s, "
1436 "mHandlingCompositions.Length()=%zu, HasPendingCommit()=%s, "
1437 "mIsChildIgnoringCompositionEvents=%s, handlingCompositionData=0x%p",
1438 this, aWidget, ToChar(aMessage), aCompositionId,
1439 PendingEventsNeedingAck(), GetBoolName(WidgetHasComposition()),
1440 mHandlingCompositions.Length(), GetBoolName(HasPendingCommit()),
1441 GetBoolName(mIsChildIgnoringCompositionEvents),
1442 handlingCompositionData));
1444 // If we receive composition event messages for older one or invalid one,
1445 // we should ignore them.
1446 if (NS_WARN_IF(aMessage != eSetSelection && !handlingCompositionData)) {
1447 return;
1450 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1451 mReceivedEventMessages.AppendElement(aMessage);
1452 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1454 const bool isCommittedInChild =
1455 // Commit requester in the remote process has committed the composition.
1456 aMessage == eCompositionCommitRequestHandled ||
1457 // The commit event has been handled normally in the remote process.
1458 (!mIsChildIgnoringCompositionEvents &&
1459 WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage));
1460 const bool hasPendingCommit = HasPendingCommit();
1462 if (isCommittedInChild) {
1463 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1464 if (mHandlingCompositions.Length() == 1u) {
1465 RemoveUnnecessaryEventMessageLog();
1468 if (NS_WARN_IF(aMessage != eCompositionCommitRequestHandled &&
1469 !handlingCompositionData->mSentCommitEvent)) {
1470 nsPrintfCString info(
1471 "\nReceived unexpected commit event message (%s) which we've "
1472 "not sent yet\n\n",
1473 ToChar(aMessage));
1474 AppendEventMessageLog(info);
1475 CrashReporter::AppendAppNotesToCrashReport(info);
1476 MOZ_DIAGNOSTIC_ASSERT(
1477 false,
1478 "Received unexpected commit event which has not been sent yet");
1480 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1482 // This should not occur, though. If we receive a commit notification for
1483 // not the oldest composition, we should forget all older compositions.
1484 size_t numberOfOutdatedCompositions = 1u;
1485 for (auto& data : mHandlingCompositions) {
1486 if (&data == handlingCompositionData) {
1487 if (
1488 // Don't put the info into the log when we've already sent commit
1489 // event because it may be just inserting a character without
1490 // composing state, but the remote process may move focus at
1491 // eCompositionStart. This may happen with UI of IME to put only
1492 // one character, e.g., the default Emoji picker of Windows.
1493 !data.mSentCommitEvent &&
1494 // In the normal case, only one message should remain, however,
1495 // remaining 2 or more messages is also valid, for example, the
1496 // remote process may have a composition update listener which
1497 // takes a while. Then, we can have multiple pending messages.
1498 data.mPendingEventsNeedingAck >= 1u) {
1499 MOZ_LOG(
1500 sContentCacheLog, LogLevel::Debug,
1501 (" NOTE: BrowserParent has %" PRIu32
1502 " pending composition messages for the handling composition, "
1503 "but before they are handled in the remote process, the active "
1504 "composition is commited by a request. "
1505 "OnEventNeedingAckHandled() calls for them will be ignored",
1506 data.mPendingEventsNeedingAck));
1508 break;
1510 if (MOZ_UNLIKELY(data.mPendingEventsNeedingAck)) {
1511 MOZ_LOG(sContentCacheLog, LogLevel::Warning,
1512 (" BrowserParent has %" PRIu32
1513 " pending composition messages for an older composition than "
1514 "the handling composition, but it'll be removed because newer "
1515 "composition gets comitted in the remote process",
1516 data.mPendingEventsNeedingAck));
1518 numberOfOutdatedCompositions++;
1520 mHandlingCompositions.RemoveElementsAt(0u, numberOfOutdatedCompositions);
1521 handlingCompositionData = nullptr;
1523 // Forget pending commit string length if it's handled in the remote
1524 // process. Note that this doesn't care too old composition's commit
1525 // string because in such case, we cannot return proper information
1526 // to IME synchronously.
1527 mPendingCommitLength = 0;
1530 if (WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage)) {
1531 // After the remote process receives eCompositionCommit(AsIs) event,
1532 // it'll restart to handle composition events.
1533 mIsChildIgnoringCompositionEvents = false;
1535 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1536 if (NS_WARN_IF(!hasPendingCommit)) {
1537 nsPrintfCString info(
1538 "\nThere is no pending comment events but received "
1539 "%s message from the remote child\n\n",
1540 ToChar(aMessage));
1541 AppendEventMessageLog(info);
1542 CrashReporter::AppendAppNotesToCrashReport(info);
1543 MOZ_DIAGNOSTIC_ASSERT(
1544 false,
1545 "No pending commit events but received unexpected commit event");
1547 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1548 } else if (aMessage == eCompositionCommitRequestHandled && hasPendingCommit) {
1549 // If the remote process commits composition synchronously after
1550 // requesting commit composition and we've already sent commit composition,
1551 // it starts to ignore following composition events until receiving
1552 // eCompositionStart event.
1553 mIsChildIgnoringCompositionEvents = true;
1556 // If neither widget (i.e., IME) nor the remote process has composition,
1557 // now, we can forget composition string informations.
1558 if (mHandlingCompositions.IsEmpty()) {
1559 mCompositionStart.reset();
1562 if (handlingCompositionData) {
1563 if (NS_WARN_IF(!handlingCompositionData->mPendingEventsNeedingAck)) {
1564 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1565 nsPrintfCString info(
1566 "\nThere is no pending events but received %s "
1567 "message from the remote child\n\n",
1568 ToChar(aMessage));
1569 AppendEventMessageLog(info);
1570 CrashReporter::AppendAppNotesToCrashReport(info);
1571 MOZ_DIAGNOSTIC_ASSERT(
1572 false, "No pending event message but received unexpected event");
1573 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1574 } else {
1575 handlingCompositionData->mPendingEventsNeedingAck--;
1577 } else if (aMessage == eSetSelection) {
1578 if (NS_WARN_IF(!mPendingSetSelectionEventNeedingAck)) {
1579 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1580 nsAutoCString info(
1581 "\nThere is no pending set selection events but received from the "
1582 "remote child\n\n");
1583 AppendEventMessageLog(info);
1584 CrashReporter::AppendAppNotesToCrashReport(info);
1585 MOZ_DIAGNOSTIC_ASSERT(
1586 false, "No pending event message but received unexpected event");
1587 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1588 } else {
1589 mPendingSetSelectionEventNeedingAck--;
1593 if (!PendingEventsNeedingAck()) {
1594 FlushPendingNotifications(aWidget);
1598 bool ContentCacheInParent::RequestIMEToCommitComposition(
1599 nsIWidget* aWidget, bool aCancel, uint32_t aCompositionId,
1600 nsAString& aCommittedString) {
1601 HandlingCompositionData* const handlingCompositionData =
1602 GetHandlingCompositionData(aCompositionId);
1604 MOZ_LOG(
1605 sContentCacheLog, LogLevel::Info,
1606 ("0x%p RequestToCommitComposition(aWidget=%p, "
1607 "aCancel=%s, aCompositionId=%" PRIu32
1608 "), mHandlingCompositions.Length()=%zu, HasPendingCommit()=%s, "
1609 "mIsChildIgnoringCompositionEvents=%s, "
1610 "IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)=%s, "
1611 "WidgetHasComposition()=%s, mCommitStringByRequest=%p, "
1612 "handlingCompositionData=0x%p",
1613 this, aWidget, GetBoolName(aCancel), aCompositionId,
1614 mHandlingCompositions.Length(), GetBoolName(HasPendingCommit()),
1615 GetBoolName(mIsChildIgnoringCompositionEvents),
1616 GetBoolName(
1617 IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)),
1618 GetBoolName(WidgetHasComposition()), mCommitStringByRequest,
1619 handlingCompositionData));
1621 MOZ_ASSERT(!mCommitStringByRequest);
1623 // If we don't know the composition ID, it must have already been committed
1624 // in this process. In the case, we should do nothing here.
1625 if (NS_WARN_IF(!handlingCompositionData)) {
1626 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1627 mRequestIMEToCommitCompositionResults.AppendElement(
1628 RequestIMEToCommitCompositionResult::eToUnknownCompositionReceived);
1629 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1630 return false;
1633 // If we receive a commit result for not latest composition, this request is
1634 // too late for IME. The remote process should wait following composition
1635 // events for cleaning up TextComposition and handle the request as it's
1636 // handled asynchronously.
1637 if (handlingCompositionData != &mHandlingCompositions.LastElement()) {
1638 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1639 mRequestIMEToCommitCompositionResults.AppendElement(
1640 RequestIMEToCommitCompositionResult::eToOldCompositionReceived);
1641 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1642 return false;
1645 // If the composition has already been commit, th remote process will receive
1646 // composition events and clean up TextComposition. So, this should do
1647 // nothing and TextComposition should handle the request as it's handled
1648 // asynchronously.
1649 // XXX Perhaps, this is wrong because TextComposition in child process
1650 // may commit the composition with current composition string in the
1651 // remote process. I.e., it may be different from actual commit string
1652 // which user typed. So, perhaps, we should return true and the commit
1653 // string.
1654 if (handlingCompositionData->mSentCommitEvent) {
1655 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1656 mRequestIMEToCommitCompositionResults.AppendElement(
1657 RequestIMEToCommitCompositionResult::eToCommittedCompositionReceived);
1658 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1659 return false;
1662 // If BrowserParent which has IME focus was already changed to different one,
1663 // the request shouldn't be sent to IME because it's too late.
1664 if (!IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)) {
1665 // Use the latest composition string which may not be handled in the
1666 // remote process for avoiding data loss.
1667 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1668 mRequestIMEToCommitCompositionResults.AppendElement(
1669 RequestIMEToCommitCompositionResult::eReceivedAfterBrowserParentBlur);
1670 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1671 aCommittedString = handlingCompositionData->mCompositionString;
1672 // After we return true from here, i.e., without actually requesting IME
1673 // to commit composition, we will receive eCompositionCommitRequestHandled
1674 // pseudo event message from the remote process. So, we need to increment
1675 // mPendingEventsNeedingAck here.
1676 handlingCompositionData->mPendingEventsNeedingAck++;
1677 return true;
1680 RefPtr<TextComposition> composition =
1681 IMEStateManager::GetTextCompositionFor(aWidget);
1682 if (NS_WARN_IF(!composition)) {
1683 MOZ_LOG(sContentCacheLog, LogLevel::Warning,
1684 (" 0x%p RequestToCommitComposition(), "
1685 "does nothing due to no composition",
1686 this));
1687 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1688 mRequestIMEToCommitCompositionResults.AppendElement(
1689 RequestIMEToCommitCompositionResult::eReceivedButNoTextComposition);
1690 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1691 return false;
1694 // If we receive a request for different composition, we must have already
1695 // sent a commit event. So the remote process should handle it.
1696 // XXX I think that this should never happen because we already checked
1697 // whether handlingCompositionData is the latest composition or not.
1698 // However, we don't want to commit different composition for the users.
1699 // Therefore, let's handle the odd case here.
1700 if (NS_WARN_IF(composition->Id() != aCompositionId)) {
1701 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1702 mRequestIMEToCommitCompositionResults.AppendElement(
1703 RequestIMEToCommitCompositionResult::
1704 eReceivedButForDifferentTextComposition);
1705 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1706 return false;
1709 mCommitStringByRequest = &aCommittedString;
1711 // Request commit or cancel composition with TextComposition because we may
1712 // have already requested to commit or cancel the composition or we may
1713 // have already received eCompositionCommit(AsIs) event. Those status are
1714 // managed by composition. So, if we don't request commit composition,
1715 // we should do nothing with native IME here.
1716 composition->RequestToCommit(aWidget, aCancel);
1718 mCommitStringByRequest = nullptr;
1720 MOZ_LOG(
1721 sContentCacheLog, LogLevel::Info,
1722 (" 0x%p RequestToCommitComposition(), "
1723 "WidgetHasComposition()=%s, the composition %s committed synchronously",
1724 this, GetBoolName(WidgetHasComposition()),
1725 composition->Destroyed() ? "WAS" : "has NOT been"));
1727 if (!composition->Destroyed()) {
1728 // When the composition isn't committed synchronously, the remote process's
1729 // TextComposition instance will synthesize commit events and wait to
1730 // receive delayed composition events. When TextComposition instances both
1731 // in this process and the remote process will be destroyed when delayed
1732 // composition events received. TextComposition instance in the parent
1733 // process will dispatch following composition events and be destroyed
1734 // normally. On the other hand, TextComposition instance in the remote
1735 // process won't dispatch following composition events and will be
1736 // destroyed by IMEStateManager::DispatchCompositionEvent().
1737 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1738 mRequestIMEToCommitCompositionResults.AppendElement(
1739 RequestIMEToCommitCompositionResult::eHandledAsynchronously);
1740 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1741 return false;
1744 // When the composition is committed synchronously, the commit string will be
1745 // returned to the remote process. Then, PuppetWidget will dispatch
1746 // eCompositionCommit event with the returned commit string (i.e., the value
1747 // is aCommittedString of this method) and that causes destroying
1748 // TextComposition instance in the remote process (Note that TextComposition
1749 // instance in this process was already destroyed).
1750 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1751 mRequestIMEToCommitCompositionResults.AppendElement(
1752 RequestIMEToCommitCompositionResult::eHandledSynchronously);
1753 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1754 return true;
1757 void ContentCacheInParent::MaybeNotifyIME(
1758 nsIWidget* aWidget, const IMENotification& aNotification) {
1759 if (!PendingEventsNeedingAck()) {
1760 IMEStateManager::NotifyIME(aNotification, aWidget, &mBrowserParent);
1761 return;
1764 switch (aNotification.mMessage) {
1765 case NOTIFY_IME_OF_SELECTION_CHANGE:
1766 mPendingSelectionChange.MergeWith(aNotification);
1767 break;
1768 case NOTIFY_IME_OF_TEXT_CHANGE:
1769 mPendingTextChange.MergeWith(aNotification);
1770 break;
1771 case NOTIFY_IME_OF_POSITION_CHANGE:
1772 mPendingLayoutChange.MergeWith(aNotification);
1773 break;
1774 case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
1775 mPendingCompositionUpdate.MergeWith(aNotification);
1776 break;
1777 default:
1778 MOZ_CRASH("Unsupported notification");
1779 break;
1783 void ContentCacheInParent::FlushPendingNotifications(nsIWidget* aWidget) {
1784 MOZ_ASSERT(!PendingEventsNeedingAck());
1786 // If the BrowserParent's widget has already gone, this can do nothing since
1787 // widget is necessary to notify IME of something.
1788 if (!aWidget) {
1789 return;
1792 // New notifications which are notified during flushing pending notifications
1793 // should be merged again.
1794 const bool pendingEventNeedingAckIncremented =
1795 !mHandlingCompositions.IsEmpty();
1796 if (pendingEventNeedingAckIncremented) {
1797 mHandlingCompositions.LastElement().mPendingEventsNeedingAck++;
1800 nsCOMPtr<nsIWidget> widget = aWidget;
1802 // First, text change notification should be sent because selection change
1803 // notification notifies IME of current selection range in the latest content.
1804 // So, IME may need the latest content before that.
1805 if (mPendingTextChange.HasNotification()) {
1806 IMENotification notification(mPendingTextChange);
1807 if (!widget->Destroyed()) {
1808 mPendingTextChange.Clear();
1809 IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
1813 if (mPendingSelectionChange.HasNotification()) {
1814 IMENotification notification(mPendingSelectionChange);
1815 if (!widget->Destroyed()) {
1816 mPendingSelectionChange.Clear();
1817 IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
1821 // Layout change notification should be notified after selection change
1822 // notification because IME may want to query position of new caret position.
1823 if (mPendingLayoutChange.HasNotification()) {
1824 IMENotification notification(mPendingLayoutChange);
1825 if (!widget->Destroyed()) {
1826 mPendingLayoutChange.Clear();
1827 IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
1831 // Finally, send composition update notification because it notifies IME of
1832 // finishing handling whole sending events.
1833 if (mPendingCompositionUpdate.HasNotification()) {
1834 IMENotification notification(mPendingCompositionUpdate);
1835 if (!widget->Destroyed()) {
1836 mPendingCompositionUpdate.Clear();
1837 IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
1841 // Decrement it which was incremented above.
1842 if (!mHandlingCompositions.IsEmpty() && pendingEventNeedingAckIncremented &&
1843 mHandlingCompositions.LastElement().mPendingEventsNeedingAck) {
1844 mHandlingCompositions.LastElement().mPendingEventsNeedingAck--;
1847 if (!PendingEventsNeedingAck() && !widget->Destroyed() &&
1848 (mPendingTextChange.HasNotification() ||
1849 mPendingSelectionChange.HasNotification() ||
1850 mPendingLayoutChange.HasNotification() ||
1851 mPendingCompositionUpdate.HasNotification())) {
1852 FlushPendingNotifications(widget);
1856 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1858 void ContentCacheInParent::RemoveUnnecessaryEventMessageLog() {
1859 bool foundLastCompositionStart = false;
1860 for (size_t i = mDispatchedEventMessages.Length(); i > 1; i--) {
1861 if (mDispatchedEventMessages[i - 1] != eCompositionStart) {
1862 continue;
1864 if (!foundLastCompositionStart) {
1865 // Find previous eCompositionStart of the latest eCompositionStart.
1866 foundLastCompositionStart = true;
1867 continue;
1869 // Remove the messages before the last 2 sets of composition events.
1870 mDispatchedEventMessages.RemoveElementsAt(0, i - 1);
1871 break;
1873 uint32_t numberOfCompositionCommitRequestHandled = 0;
1874 foundLastCompositionStart = false;
1875 for (size_t i = mReceivedEventMessages.Length(); i > 1; i--) {
1876 if (mReceivedEventMessages[i - 1] == eCompositionCommitRequestHandled) {
1877 numberOfCompositionCommitRequestHandled++;
1879 if (mReceivedEventMessages[i - 1] != eCompositionStart) {
1880 continue;
1882 if (!foundLastCompositionStart) {
1883 // Find previous eCompositionStart of the latest eCompositionStart.
1884 foundLastCompositionStart = true;
1885 continue;
1887 // Remove the messages before the last 2 sets of composition events.
1888 mReceivedEventMessages.RemoveElementsAt(0, i - 1);
1889 break;
1892 if (!numberOfCompositionCommitRequestHandled) {
1893 // If there is no eCompositionCommitRequestHandled in
1894 // mReceivedEventMessages, we don't need to store log of
1895 // RequestIMEToCommmitComposition().
1896 mRequestIMEToCommitCompositionResults.Clear();
1897 } else {
1898 // We need to keep all reason of eCompositionCommitRequestHandled, which
1899 // is sent when mRequestIMEToCommitComposition() returns true.
1900 // So, we can discard older log than the first
1901 // eCompositionCommitRequestHandled in mReceivedEventMessages.
1902 for (size_t i = mRequestIMEToCommitCompositionResults.Length(); i > 1;
1903 i--) {
1904 if (mRequestIMEToCommitCompositionResults[i - 1] ==
1905 RequestIMEToCommitCompositionResult::
1906 eReceivedAfterBrowserParentBlur ||
1907 mRequestIMEToCommitCompositionResults[i - 1] ==
1908 RequestIMEToCommitCompositionResult::eHandledSynchronously) {
1909 --numberOfCompositionCommitRequestHandled;
1910 if (!numberOfCompositionCommitRequestHandled) {
1911 mRequestIMEToCommitCompositionResults.RemoveElementsAt(0, i - 1);
1912 break;
1919 void ContentCacheInParent::AppendEventMessageLog(nsACString& aLog) const {
1920 aLog.AppendLiteral("Dispatched Event Message Log:\n");
1921 for (EventMessage message : mDispatchedEventMessages) {
1922 aLog.AppendLiteral(" ");
1923 aLog.Append(ToChar(message));
1924 aLog.AppendLiteral("\n");
1926 aLog.AppendLiteral("\nReceived Event Message Log:\n");
1927 for (EventMessage message : mReceivedEventMessages) {
1928 aLog.AppendLiteral(" ");
1929 aLog.Append(ToChar(message));
1930 aLog.AppendLiteral("\n");
1932 aLog.AppendLiteral("\nResult of RequestIMEToCommitComposition():\n");
1933 for (RequestIMEToCommitCompositionResult result :
1934 mRequestIMEToCommitCompositionResults) {
1935 aLog.AppendLiteral(" ");
1936 aLog.Append(ToReadableText(result));
1937 aLog.AppendLiteral("\n");
1939 aLog.AppendLiteral("\n");
1942 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1944 /*****************************************************************************
1945 * mozilla::ContentCache::Selection
1946 *****************************************************************************/
1948 ContentCache::Selection::Selection(
1949 const WidgetQueryContentEvent& aQuerySelectedTextEvent)
1950 : mAnchor(UINT32_MAX),
1951 mFocus(UINT32_MAX),
1952 mWritingMode(aQuerySelectedTextEvent.mReply->WritingModeRef()),
1953 mHasRange(aQuerySelectedTextEvent.mReply->mOffsetAndData.isSome()) {
1954 MOZ_ASSERT(aQuerySelectedTextEvent.mMessage == eQuerySelectedText);
1955 MOZ_ASSERT(aQuerySelectedTextEvent.Succeeded());
1956 if (mHasRange) {
1957 mAnchor = aQuerySelectedTextEvent.mReply->AnchorOffset();
1958 mFocus = aQuerySelectedTextEvent.mReply->FocusOffset();
1962 /*****************************************************************************
1963 * mozilla::ContentCache::TextRectArray
1964 *****************************************************************************/
1966 LayoutDeviceIntRect ContentCache::TextRectArray::GetRect(
1967 uint32_t aOffset) const {
1968 LayoutDeviceIntRect rect;
1969 if (IsOffsetInRange(aOffset)) {
1970 rect = mRects[aOffset - mStart];
1972 return rect;
1975 LayoutDeviceIntRect ContentCache::TextRectArray::GetUnionRect(
1976 uint32_t aOffset, uint32_t aLength) const {
1977 LayoutDeviceIntRect rect;
1978 if (!IsRangeCompletelyInRange(aOffset, aLength)) {
1979 return rect;
1981 for (uint32_t i = 0; i < aLength; i++) {
1982 rect = rect.Union(mRects[aOffset - mStart + i]);
1984 return rect;
1987 LayoutDeviceIntRect ContentCache::TextRectArray::GetUnionRectAsFarAsPossible(
1988 uint32_t aOffset, uint32_t aLength, bool aRoundToExistingOffset) const {
1989 LayoutDeviceIntRect rect;
1990 if (!HasRects() ||
1991 (!aRoundToExistingOffset && !IsOverlappingWith(aOffset, aLength))) {
1992 return rect;
1994 uint32_t startOffset = std::max(aOffset, mStart);
1995 if (aRoundToExistingOffset && startOffset >= EndOffset()) {
1996 startOffset = EndOffset() - 1;
1998 uint32_t endOffset = std::min(aOffset + aLength, EndOffset());
1999 if (aRoundToExistingOffset && endOffset < mStart + 1) {
2000 endOffset = mStart + 1;
2002 if (NS_WARN_IF(endOffset < startOffset)) {
2003 return rect;
2005 for (uint32_t i = 0; i < endOffset - startOffset; i++) {
2006 rect = rect.Union(mRects[startOffset - mStart + i]);
2008 return rect;
2011 } // namespace mozilla