Bug 1942006 - Upstream a variety of Servo-specific code from Servo's downstream fork...
[gecko.git] / widget / ContentCache.cpp
blob94976548aac5e711f10f089093f7d8c802a8d6c9
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/MiscEvents.h"
20 #include "mozilla/RefPtr.h"
21 #include "mozilla/TextComposition.h"
22 #include "mozilla/dom/BrowserParent.h"
23 #include "nsExceptionHandler.h"
24 #include "nsIWidget.h"
25 #include "nsPrintfCString.h"
27 namespace mozilla {
29 using namespace dom;
30 using namespace widget;
32 static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; }
34 static const char* GetNotificationName(const IMENotification* aNotification) {
35 if (!aNotification) {
36 return "Not notification";
38 return ToChar(aNotification->mMessage);
41 /*****************************************************************************
42 * mozilla::ContentCache
43 *****************************************************************************/
45 LazyLogModule sContentCacheLog("ContentCacheWidgets");
47 bool ContentCache::IsValid() const {
48 if (mText.isNothing()) {
49 // mSelection and mCaret depend on mText.
50 if (NS_WARN_IF(mSelection.isSome()) || NS_WARN_IF(mCaret.isSome())) {
51 return false;
53 } else {
54 // mSelection depends on mText.
55 if (mSelection.isSome() && NS_WARN_IF(!mSelection->IsValidIn(*mText))) {
56 return false;
59 // mCaret depends on mSelection.
60 if (mCaret.isSome() &&
61 (NS_WARN_IF(mSelection.isNothing()) ||
62 NS_WARN_IF(!mSelection->mHasRange) ||
63 NS_WARN_IF(mSelection->StartOffset() != mCaret->Offset()))) {
64 return false;
68 // mTextRectArray stores character rects around composition string.
69 // Note that even if we fail to collect the rects, we may keep storing
70 // mCompositionStart.
71 if (mTextRectArray.isSome()) {
72 if (NS_WARN_IF(mCompositionStart.isNothing())) {
73 return false;
77 return true;
80 void ContentCache::AssertIfInvalid() const {
81 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
82 if (IsValid()) {
83 return;
86 // This text will appear in the crash reports without any permissions.
87 // Do not use `ToString` here to avoid to expose unexpected data with
88 // changing the type or `operator<<()`.
89 nsPrintfCString info(
90 "ContentCache={ mText=%s, mSelection=%s, mCaret=%s, mTextRectArray=%s, "
91 "mCompositionStart=%s }\n",
92 // Don't expose mText.ref() value for protecting the user's privacy.
93 mText.isNothing()
94 ? "Nothing"
95 : nsPrintfCString("{ Length()=%zu }", mText->Length()).get(),
96 mSelection.isNothing()
97 ? "Nothing"
98 : nsPrintfCString("{ mAnchor=%u, mFocus=%u }", mSelection->mAnchor,
99 mSelection->mFocus)
100 .get(),
101 mCaret.isNothing()
102 ? "Nothing"
103 : nsPrintfCString("{ mOffset=%u }", mCaret->mOffset).get(),
104 mTextRectArray.isNothing()
105 ? "Nothing"
106 : nsPrintfCString("{ Length()=%u }", mTextRectArray->Length()).get(),
107 mCompositionStart.isNothing()
108 ? "Nothing"
109 : nsPrintfCString("%u", mCompositionStart.value()).get());
110 CrashReporter::AppendAppNotesToCrashReport(info);
111 MOZ_DIAGNOSTIC_CRASH("Invalid ContentCache data");
112 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
115 /*****************************************************************************
116 * mozilla::ContentCacheInChild
117 *****************************************************************************/
119 void ContentCacheInChild::Clear() {
120 MOZ_LOG(sContentCacheLog, LogLevel::Info, ("0x%p Clear()", this));
122 mCompositionStart.reset();
123 mLastCommit.reset();
124 mText.reset();
125 mSelection.reset();
126 mFirstCharRect.SetEmpty();
127 mCaret.reset();
128 mTextRectArray.reset();
129 mLastCommitStringTextRectArray.reset();
130 mEditorRect.SetEmpty();
133 void ContentCacheInChild::OnCompositionEvent(
134 const WidgetCompositionEvent& aCompositionEvent) {
135 if (aCompositionEvent.CausesDOMCompositionEndEvent()) {
136 RefPtr<TextComposition> composition =
137 IMEStateManager::GetTextCompositionFor(aCompositionEvent.mWidget);
138 if (composition) {
139 nsAutoString lastCommitString;
140 if (aCompositionEvent.mMessage == eCompositionCommitAsIs) {
141 lastCommitString = composition->CommitStringIfCommittedAsIs();
142 } else {
143 lastCommitString = aCompositionEvent.mData;
145 // We don't need to store canceling information because this is required
146 // by undoing of last commit (Kakutei-Undo of Japanese IME).
147 if (!lastCommitString.IsEmpty()) {
148 mLastCommit = Some(OffsetAndData<uint32_t>(
149 composition->NativeOffsetOfStartComposition(), lastCommitString));
150 MOZ_LOG(
151 sContentCacheLog, LogLevel::Debug,
152 ("0x%p OnCompositionEvent(), stored last composition string data "
153 "(aCompositionEvent={ mMessage=%s, mData=\"%s\"}, mLastCommit=%s)",
154 this, ToChar(aCompositionEvent.mMessage),
155 PrintStringDetail(
156 aCompositionEvent.mData,
157 PrintStringDetail::kMaxLengthForCompositionString)
158 .get(),
159 ToString(mLastCommit).c_str()));
160 return;
164 if (mLastCommit.isSome()) {
165 MOZ_LOG(
166 sContentCacheLog, LogLevel::Debug,
167 ("0x%p OnCompositionEvent(), resetting the last composition string "
168 "data (aCompositionEvent={ mMessage=%s, mData=\"%s\"}, "
169 "mLastCommit=%s)",
170 this, ToChar(aCompositionEvent.mMessage),
171 PrintStringDetail(aCompositionEvent.mData,
172 PrintStringDetail::kMaxLengthForCompositionString)
173 .get(),
174 ToString(mLastCommit).c_str()));
175 mLastCommit.reset();
179 bool ContentCacheInChild::CacheAll(nsIWidget* aWidget,
180 const IMENotification* aNotification) {
181 MOZ_LOG(sContentCacheLog, LogLevel::Info,
182 ("0x%p CacheAll(aWidget=0x%p, aNotification=%s)", this, aWidget,
183 GetNotificationName(aNotification)));
185 const bool textCached = CacheText(aWidget, aNotification);
186 const bool editorRectCached = CacheEditorRect(aWidget, aNotification);
187 AssertIfInvalid();
188 return (textCached || editorRectCached) && IsValid();
191 bool ContentCacheInChild::CacheSelection(nsIWidget* aWidget,
192 const IMENotification* aNotification) {
193 MOZ_LOG(
194 sContentCacheLog, LogLevel::Info,
195 ("0x%p CacheSelection(aWidget=0x%p, aNotification=%s), mText=%s", this,
196 aWidget, GetNotificationName(aNotification),
197 PrintStringDetail(mText, PrintStringDetail::kMaxLengthForEditor).get()));
199 mSelection.reset();
200 mCaret.reset();
202 if (mText.isNothing()) {
203 return false;
206 nsEventStatus status = nsEventStatus_eIgnore;
207 WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
208 aWidget);
209 aWidget->DispatchEvent(&querySelectedTextEvent, status);
210 if (NS_WARN_IF(querySelectedTextEvent.Failed())) {
211 MOZ_LOG(
212 sContentCacheLog, LogLevel::Error,
213 ("0x%p CacheSelection(), FAILED, couldn't retrieve the selected text",
214 this));
215 // XXX Allowing selection-independent character rects makes things
216 // complicated in the parent...
218 // ContentCache should store only editable content. Therefore, if current
219 // selection root is not editable, we don't need to store the selection, i.e.,
220 // let's treat it as there is no selection. However, if we already have
221 // previously editable text, let's store the selection even if it becomes
222 // uneditable because not doing so would create odd situation. E.g., IME may
223 // fail only querying selection after succeeded querying text.
224 else if (NS_WARN_IF(!querySelectedTextEvent.mReply->mIsEditableContent)) {
225 MOZ_LOG(sContentCacheLog, LogLevel::Error,
226 ("0x%p CacheSelection(), FAILED, editable content had already been "
227 "blurred",
228 this));
229 AssertIfInvalid();
230 return false;
231 } else {
232 mSelection.emplace(querySelectedTextEvent);
235 return (CacheCaretAndTextRects(aWidget, aNotification) ||
236 querySelectedTextEvent.Succeeded()) &&
237 IsValid();
240 bool ContentCacheInChild::CacheCaret(nsIWidget* aWidget,
241 const IMENotification* aNotification) {
242 mCaret.reset();
244 if (mSelection.isNothing()) {
245 return false;
248 MOZ_LOG(sContentCacheLog, LogLevel::Info,
249 ("0x%p CacheCaret(aWidget=0x%p, aNotification=%s)", this, aWidget,
250 GetNotificationName(aNotification)));
252 if (mSelection->mHasRange) {
253 // XXX Should be mSelection.mFocus?
254 const uint32_t offset = mSelection->StartOffset();
256 nsEventStatus status = nsEventStatus_eIgnore;
257 WidgetQueryContentEvent queryCaretRectEvent(true, eQueryCaretRect, aWidget);
258 queryCaretRectEvent.InitForQueryCaretRect(offset);
259 aWidget->DispatchEvent(&queryCaretRectEvent, status);
260 if (NS_WARN_IF(queryCaretRectEvent.Failed())) {
261 MOZ_LOG(sContentCacheLog, LogLevel::Error,
262 ("0x%p CacheCaret(), FAILED, couldn't retrieve the caret rect "
263 "at offset=%u",
264 this, offset));
265 return false;
267 mCaret.emplace(offset, queryCaretRectEvent.mReply->mRect);
269 MOZ_LOG(sContentCacheLog, LogLevel::Info,
270 ("0x%p CacheCaret(), Succeeded, mSelection=%s, mCaret=%s", this,
271 ToString(mSelection).c_str(), ToString(mCaret).c_str()));
272 AssertIfInvalid();
273 return IsValid();
276 bool ContentCacheInChild::CacheEditorRect(
277 nsIWidget* aWidget, const IMENotification* aNotification) {
278 MOZ_LOG(sContentCacheLog, LogLevel::Info,
279 ("0x%p CacheEditorRect(aWidget=0x%p, aNotification=%s)", this,
280 aWidget, GetNotificationName(aNotification)));
282 nsEventStatus status = nsEventStatus_eIgnore;
283 WidgetQueryContentEvent queryEditorRectEvent(true, eQueryEditorRect, aWidget);
284 aWidget->DispatchEvent(&queryEditorRectEvent, status);
285 if (NS_WARN_IF(queryEditorRectEvent.Failed())) {
286 MOZ_LOG(
287 sContentCacheLog, LogLevel::Error,
288 ("0x%p CacheEditorRect(), FAILED, couldn't retrieve the editor rect",
289 this));
290 return false;
292 // ContentCache should store only editable content. Therefore, if current
293 // selection root is not editable, we don't need to store the editor rect,
294 // i.e., let's treat it as there is no focused editor.
295 if (NS_WARN_IF(!queryEditorRectEvent.mReply->mIsEditableContent)) {
296 MOZ_LOG(sContentCacheLog, LogLevel::Error,
297 ("0x%p CacheText(), FAILED, editable content had already been "
298 "blurred",
299 this));
300 return false;
302 mEditorRect = queryEditorRectEvent.mReply->mRect;
303 MOZ_LOG(sContentCacheLog, LogLevel::Info,
304 ("0x%p CacheEditorRect(), Succeeded, mEditorRect=%s", this,
305 ToString(mEditorRect).c_str()));
306 return true;
309 bool ContentCacheInChild::CacheCaretAndTextRects(
310 nsIWidget* aWidget, const IMENotification* aNotification) {
311 MOZ_LOG(sContentCacheLog, LogLevel::Info,
312 ("0x%p CacheCaretAndTextRects(aWidget=0x%p, aNotification=%s)", this,
313 aWidget, GetNotificationName(aNotification)));
315 const bool caretCached = CacheCaret(aWidget, aNotification);
316 const bool textRectsCached = CacheTextRects(aWidget, aNotification);
317 AssertIfInvalid();
318 return (caretCached || textRectsCached) && IsValid();
321 bool ContentCacheInChild::CacheText(nsIWidget* aWidget,
322 const IMENotification* aNotification) {
323 MOZ_LOG(sContentCacheLog, LogLevel::Info,
324 ("0x%p CacheText(aWidget=0x%p, aNotification=%s)", this, aWidget,
325 GetNotificationName(aNotification)));
327 nsEventStatus status = nsEventStatus_eIgnore;
328 WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
329 aWidget);
330 queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
331 aWidget->DispatchEvent(&queryTextContentEvent, status);
332 if (NS_WARN_IF(queryTextContentEvent.Failed())) {
333 MOZ_LOG(sContentCacheLog, LogLevel::Error,
334 ("0x%p CacheText(), FAILED, couldn't retrieve whole text", this));
335 mText.reset();
337 // ContentCache should store only editable content. Therefore, if current
338 // selection root is not editable, we don't need to store the text, i.e.,
339 // let's treat it as there is no editable text.
340 else if (NS_WARN_IF(!queryTextContentEvent.mReply->mIsEditableContent)) {
341 MOZ_LOG(sContentCacheLog, LogLevel::Error,
342 ("0x%p CacheText(), FAILED, editable content had already been "
343 "blurred",
344 this));
345 mText.reset();
346 } else {
347 mText = Some(nsString(queryTextContentEvent.mReply->DataRef()));
348 MOZ_LOG(sContentCacheLog, LogLevel::Info,
349 ("0x%p CacheText(), Succeeded, mText=%s", this,
350 PrintStringDetail(mText, PrintStringDetail::kMaxLengthForEditor)
351 .get()));
354 // Forget last commit range if string in the range is different from the
355 // last commit string.
356 if (mLastCommit.isSome() &&
357 (mText.isNothing() ||
358 nsDependentSubstring(mText.ref(), mLastCommit->StartOffset(),
359 mLastCommit->Length()) != mLastCommit->DataRef())) {
360 MOZ_LOG(sContentCacheLog, LogLevel::Debug,
361 ("0x%p CacheText(), resetting the last composition string data "
362 "(mLastCommit=%s, current string=\"%s\")",
363 this, ToString(mLastCommit).c_str(),
364 PrintStringDetail(
365 nsDependentSubstring(mText.ref(), mLastCommit->StartOffset(),
366 mLastCommit->Length()),
367 PrintStringDetail::kMaxLengthForCompositionString)
368 .get()));
369 mLastCommit.reset();
372 // If we fail to get editable text content, it must mean that there is no
373 // focused element anymore or focused element is not editable. In this case,
374 // we should not get selection of non-editable content
375 if (MOZ_UNLIKELY(mText.isNothing())) {
376 mSelection.reset();
377 mCaret.reset();
378 mTextRectArray.reset();
379 AssertIfInvalid();
380 return false;
383 return CacheSelection(aWidget, aNotification);
386 bool ContentCacheInChild::QueryCharRect(nsIWidget* aWidget, uint32_t aOffset,
387 LayoutDeviceIntRect& aCharRect) const {
388 aCharRect.SetEmpty();
390 nsEventStatus status = nsEventStatus_eIgnore;
391 WidgetQueryContentEvent queryTextRectEvent(true, eQueryTextRect, aWidget);
392 queryTextRectEvent.InitForQueryTextRect(aOffset, 1);
393 aWidget->DispatchEvent(&queryTextRectEvent, status);
394 if (NS_WARN_IF(queryTextRectEvent.Failed())) {
395 return false;
397 aCharRect = queryTextRectEvent.mReply->mRect;
399 // Guarantee the rect is not empty.
400 if (NS_WARN_IF(!aCharRect.Height())) {
401 aCharRect.SetHeight(1);
403 if (NS_WARN_IF(!aCharRect.Width())) {
404 aCharRect.SetWidth(1);
406 return true;
409 bool ContentCacheInChild::QueryCharRectArray(nsIWidget* aWidget,
410 uint32_t aOffset, uint32_t aLength,
411 RectArray& aCharRectArray) const {
412 nsEventStatus status = nsEventStatus_eIgnore;
413 WidgetQueryContentEvent queryTextRectsEvent(true, eQueryTextRectArray,
414 aWidget);
415 queryTextRectsEvent.InitForQueryTextRectArray(aOffset, aLength);
416 aWidget->DispatchEvent(&queryTextRectsEvent, status);
417 if (NS_WARN_IF(queryTextRectsEvent.Failed())) {
418 aCharRectArray.Clear();
419 return false;
421 aCharRectArray = std::move(queryTextRectsEvent.mReply->mRectArray);
422 return true;
425 bool ContentCacheInChild::CacheTextRects(nsIWidget* aWidget,
426 const IMENotification* aNotification) {
427 MOZ_LOG(
428 sContentCacheLog, LogLevel::Info,
429 ("0x%p CacheTextRects(aWidget=0x%p, aNotification=%s), mCaret=%s", this,
430 aWidget, GetNotificationName(aNotification), ToString(mCaret).c_str()));
432 if (mSelection.isSome()) {
433 mSelection->ClearRects();
436 // Retrieve text rects in composition string if there is.
437 RefPtr<TextComposition> textComposition =
438 IMEStateManager::GetTextCompositionFor(aWidget);
439 if (textComposition) {
440 // mCompositionStart may be updated by some composition event handlers.
441 // So, let's update it with the latest information.
442 mCompositionStart = Some(textComposition->NativeOffsetOfStartComposition());
443 // Note that TextComposition::String() may not be modified here because
444 // it's modified after all edit action listeners are performed but this
445 // is called while some of them are performed.
446 // FYI: For supporting IME which commits composition and restart new
447 // composition immediately, we should cache next character of current
448 // composition too.
449 uint32_t length = textComposition->LastData().Length() + 1;
450 mTextRectArray = Some(TextRectArray(mCompositionStart.value()));
451 if (NS_WARN_IF(!QueryCharRectArray(aWidget, mTextRectArray->mStart, length,
452 mTextRectArray->mRects))) {
453 MOZ_LOG(sContentCacheLog, LogLevel::Error,
454 ("0x%p CacheTextRects(), FAILED, "
455 "couldn't retrieve text rect array of the composition string",
456 this));
457 mTextRectArray.reset();
459 } else {
460 mCompositionStart.reset();
461 mTextRectArray.reset();
464 if (mSelection.isSome()) {
465 // Set mSelection->mAnchorCharRects
466 // If we've already have the rect in mTextRectArray, save the query cost.
467 if (mSelection->mHasRange && mTextRectArray.isSome() &&
468 mTextRectArray->IsOffsetInRange(mSelection->mAnchor) &&
469 (!mSelection->mAnchor ||
470 mTextRectArray->IsOffsetInRange(mSelection->mAnchor - 1))) {
471 mSelection->mAnchorCharRects[eNextCharRect] =
472 mTextRectArray->GetRect(mSelection->mAnchor);
473 if (mSelection->mAnchor) {
474 mSelection->mAnchorCharRects[ePrevCharRect] =
475 mTextRectArray->GetRect(mSelection->mAnchor - 1);
478 // Otherwise, get it from content even if there is no selection ranges.
479 else {
480 RectArray rects;
481 const uint32_t startOffset = mSelection->mHasRange && mSelection->mAnchor
482 ? mSelection->mAnchor - 1u
483 : 0u;
484 const uint32_t length =
485 mSelection->mHasRange && mSelection->mAnchor ? 2u : 1u;
486 if (NS_WARN_IF(
487 !QueryCharRectArray(aWidget, startOffset, length, rects))) {
488 MOZ_LOG(
489 sContentCacheLog, LogLevel::Error,
490 ("0x%p CacheTextRects(), FAILED, couldn't retrieve text rect "
491 "array around the selection anchor (%s)",
492 this,
493 mSelection ? ToString(mSelection->mAnchor).c_str() : "Nothing"));
494 MOZ_ASSERT_IF(mSelection.isSome(),
495 mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty());
496 MOZ_ASSERT_IF(mSelection.isSome(),
497 mSelection->mAnchorCharRects[eNextCharRect].IsEmpty());
498 } else if (rects.Length()) {
499 if (rects.Length() > 1) {
500 mSelection->mAnchorCharRects[ePrevCharRect] = rects[0];
501 mSelection->mAnchorCharRects[eNextCharRect] = rects[1];
502 } else {
503 mSelection->mAnchorCharRects[eNextCharRect] = rects[0];
504 MOZ_ASSERT(mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty());
509 // Set mSelection->mFocusCharRects
510 // If selection is collapsed (including no selection case), the focus char
511 // rects are same as the anchor char rects so that we can just copy them.
512 if (mSelection->IsCollapsed()) {
513 mSelection->mFocusCharRects[0] = mSelection->mAnchorCharRects[0];
514 mSelection->mFocusCharRects[1] = mSelection->mAnchorCharRects[1];
516 // If the selection range is in mTextRectArray, save the query cost.
517 else if (mTextRectArray.isSome() &&
518 mTextRectArray->IsOffsetInRange(mSelection->mFocus) &&
519 (!mSelection->mFocus ||
520 mTextRectArray->IsOffsetInRange(mSelection->mFocus - 1))) {
521 MOZ_ASSERT(mSelection->mHasRange);
522 mSelection->mFocusCharRects[eNextCharRect] =
523 mTextRectArray->GetRect(mSelection->mFocus);
524 if (mSelection->mFocus) {
525 mSelection->mFocusCharRects[ePrevCharRect] =
526 mTextRectArray->GetRect(mSelection->mFocus - 1);
529 // Otherwise, including no selection range cases, need to query the rects.
530 else {
531 MOZ_ASSERT(mSelection->mHasRange);
532 RectArray rects;
533 const uint32_t startOffset =
534 mSelection->mFocus ? mSelection->mFocus - 1u : 0u;
535 const uint32_t length = mSelection->mFocus ? 2u : 1u;
536 if (NS_WARN_IF(
537 !QueryCharRectArray(aWidget, startOffset, length, rects))) {
538 MOZ_LOG(
539 sContentCacheLog, LogLevel::Error,
540 ("0x%p CacheTextRects(), FAILED, couldn't retrieve text rect "
541 "array around the selection focus (%s)",
542 this,
543 mSelection ? ToString(mSelection->mFocus).c_str() : "Nothing"));
544 MOZ_ASSERT_IF(mSelection.isSome(),
545 mSelection->mFocusCharRects[ePrevCharRect].IsEmpty());
546 MOZ_ASSERT_IF(mSelection.isSome(),
547 mSelection->mFocusCharRects[eNextCharRect].IsEmpty());
548 } else if (NS_WARN_IF(mSelection.isNothing())) {
549 MOZ_LOG(sContentCacheLog, LogLevel::Error,
550 ("0x%p CacheTextRects(), FAILED, mSelection was reset during "
551 "the call of QueryCharRectArray",
552 this));
553 } else {
554 if (rects.Length() > 1) {
555 mSelection->mFocusCharRects[ePrevCharRect] = rects[0];
556 mSelection->mFocusCharRects[eNextCharRect] = rects[1];
557 } else if (rects.Length()) {
558 mSelection->mFocusCharRects[eNextCharRect] = rects[0];
559 MOZ_ASSERT(mSelection->mFocusCharRects[ePrevCharRect].IsEmpty());
565 // If there is a non-collapsed selection range, let's query the whole selected
566 // text rect. Note that the result cannot be computed from first character
567 // rect and last character rect of the selection because they both may be in
568 // middle of different line.
569 if (mSelection.isSome() && mSelection->mHasRange &&
570 !mSelection->IsCollapsed()) {
571 nsEventStatus status = nsEventStatus_eIgnore;
572 WidgetQueryContentEvent queryTextRectEvent(true, eQueryTextRect, aWidget);
573 queryTextRectEvent.InitForQueryTextRect(mSelection->StartOffset(),
574 mSelection->Length());
575 aWidget->DispatchEvent(&queryTextRectEvent, status);
576 if (NS_WARN_IF(queryTextRectEvent.Failed())) {
577 MOZ_LOG(sContentCacheLog, LogLevel::Error,
578 ("0x%p CacheTextRects(), FAILED, "
579 "couldn't retrieve text rect of whole selected text",
580 this));
581 } else {
582 mSelection->mRect = queryTextRectEvent.mReply->mRect;
586 // Even if there is no selection range, we should have the first character
587 // rect for the last resort of suggesting position of IME UI.
588 if (mSelection.isSome() && mSelection->mHasRange && !mSelection->mFocus) {
589 mFirstCharRect = mSelection->mFocusCharRects[eNextCharRect];
590 } else if (mSelection.isSome() && mSelection->mHasRange &&
591 mSelection->mFocus == 1) {
592 mFirstCharRect = mSelection->mFocusCharRects[ePrevCharRect];
593 } else if (mSelection.isSome() && mSelection->mHasRange &&
594 !mSelection->mAnchor) {
595 mFirstCharRect = mSelection->mAnchorCharRects[eNextCharRect];
596 } else if (mSelection.isSome() && mSelection->mHasRange &&
597 mSelection->mAnchor == 1) {
598 mFirstCharRect = mSelection->mFocusCharRects[ePrevCharRect];
599 } else if (mTextRectArray.isSome() && mTextRectArray->IsOffsetInRange(0u)) {
600 mFirstCharRect = mTextRectArray->GetRect(0u);
601 } else {
602 LayoutDeviceIntRect charRect;
603 if (MOZ_UNLIKELY(NS_WARN_IF(!QueryCharRect(aWidget, 0, charRect)))) {
604 MOZ_LOG(sContentCacheLog, LogLevel::Error,
605 ("0x%p CacheTextRects(), FAILED, "
606 "couldn't retrieve first char rect",
607 this));
608 mFirstCharRect.SetEmpty();
609 } else {
610 mFirstCharRect = charRect;
614 // Finally, let's cache the last commit string's character rects until
615 // selection change or something other editing because user may reconvert
616 // or undo the last commit. Then, IME requires the character rects for
617 // positioning their UI.
618 if (mLastCommit.isSome()) {
619 mLastCommitStringTextRectArray =
620 Some(TextRectArray(mLastCommit->StartOffset()));
621 if (mLastCommit->Length() == 1 && mSelection.isSome() &&
622 mSelection->mHasRange &&
623 mSelection->mAnchor - 1 == mLastCommit->StartOffset() &&
624 !mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty()) {
625 mLastCommitStringTextRectArray->mRects.AppendElement(
626 mSelection->mAnchorCharRects[ePrevCharRect]);
627 } else if (NS_WARN_IF(!QueryCharRectArray(
628 aWidget, mLastCommit->StartOffset(), mLastCommit->Length(),
629 mLastCommitStringTextRectArray->mRects))) {
630 MOZ_LOG(sContentCacheLog, LogLevel::Error,
631 ("0x%p CacheTextRects(), FAILED, "
632 "couldn't retrieve text rect array of the last commit string",
633 this));
634 mLastCommitStringTextRectArray.reset();
635 mLastCommit.reset();
637 MOZ_ASSERT((mLastCommitStringTextRectArray.isSome()
638 ? mLastCommitStringTextRectArray->mRects.Length()
639 : 0) == (mLastCommit.isSome() ? mLastCommit->Length() : 0));
640 } else {
641 mLastCommitStringTextRectArray.reset();
644 MOZ_LOG(
645 sContentCacheLog, LogLevel::Info,
646 ("0x%p CacheTextRects(), Succeeded, "
647 "mText=%s, mTextRectArray=%s, mSelection=%s, "
648 "mFirstCharRect=%s, mLastCommitStringTextRectArray=%s",
649 this,
650 PrintStringDetail(mText, PrintStringDetail::kMaxLengthForEditor).get(),
651 ToString(mTextRectArray).c_str(), ToString(mSelection).c_str(),
652 ToString(mFirstCharRect).c_str(),
653 ToString(mLastCommitStringTextRectArray).c_str()));
654 AssertIfInvalid();
655 return IsValid();
658 bool ContentCacheInChild::SetSelection(
659 nsIWidget* aWidget,
660 const IMENotification::SelectionChangeDataBase& aSelectionChangeData) {
661 MOZ_LOG(
662 sContentCacheLog, LogLevel::Info,
663 ("0x%p SetSelection(aSelectionChangeData=%s), mText=%s", this,
664 ToString(aSelectionChangeData).c_str(),
665 PrintStringDetail(mText, PrintStringDetail::kMaxLengthForEditor).get()));
667 if (MOZ_UNLIKELY(mText.isNothing())) {
668 return false;
671 mSelection = Some(Selection(aSelectionChangeData));
673 if (mLastCommit.isSome()) {
674 // Forget last commit string range if selection is not collapsed
675 // at end of the last commit string.
676 if (!mSelection->mHasRange || !mSelection->IsCollapsed() ||
677 mSelection->mAnchor != mLastCommit->EndOffset()) {
678 MOZ_LOG(
679 sContentCacheLog, LogLevel::Debug,
680 ("0x%p SetSelection(), forgetting last commit composition data "
681 "(mSelection=%s, mLastCommit=%s)",
682 this, ToString(mSelection).c_str(), ToString(mLastCommit).c_str()));
683 mLastCommit.reset();
687 CacheCaret(aWidget);
688 CacheTextRects(aWidget);
690 return mSelection.isSome() && IsValid();
693 /*****************************************************************************
694 * mozilla::ContentCacheInParent
695 *****************************************************************************/
697 ContentCacheInParent::ContentCacheInParent(BrowserParent& aBrowserParent)
698 : mBrowserParent(aBrowserParent),
699 mCommitStringByRequest(nullptr),
700 mPendingCommitLength(0),
701 mIsChildIgnoringCompositionEvents(false) {}
703 void ContentCacheInParent::AssignContent(const ContentCache& aOther,
704 nsIWidget* aWidget,
705 const IMENotification* aNotification) {
706 MOZ_DIAGNOSTIC_ASSERT(aOther.IsValid());
708 mText = aOther.mText;
709 mSelection = aOther.mSelection;
710 mFirstCharRect = aOther.mFirstCharRect;
711 mCaret = aOther.mCaret;
712 mTextRectArray = aOther.mTextRectArray;
713 mLastCommitStringTextRectArray = aOther.mLastCommitStringTextRectArray;
714 mEditorRect = aOther.mEditorRect;
716 // Only when there is one composition, the TextComposition instance in this
717 // process is managing the composition in the remote process. Therefore,
718 // we shouldn't update composition start offset of TextComposition with
719 // old composition which is still being handled by the child process.
720 if (WidgetHasComposition() && mHandlingCompositions.Length() == 1 &&
721 mCompositionStart.isSome()) {
722 IMEStateManager::MaybeStartOffsetUpdatedInChild(aWidget,
723 mCompositionStart.value());
726 // When this instance allows to query content relative to composition string,
727 // we should modify mCompositionStart with the latest information in the
728 // remote process because now we have the information around the composition
729 // string.
730 mCompositionStartInChild = aOther.mCompositionStart;
731 if (WidgetHasComposition() || HasPendingCommit()) {
732 if (mCompositionStartInChild.isSome()) {
733 if (mCompositionStart.valueOr(UINT32_MAX) !=
734 mCompositionStartInChild.value()) {
735 mCompositionStart = mCompositionStartInChild;
736 mPendingCommitLength = 0;
738 } else if (mCompositionStart.isSome() && mSelection.isSome() &&
739 mSelection->mHasRange &&
740 mCompositionStart.value() != mSelection->StartOffset()) {
741 mCompositionStart = Some(mSelection->StartOffset());
742 mPendingCommitLength = 0;
746 MOZ_LOG(
747 sContentCacheLog, LogLevel::Info,
748 ("0x%p AssignContent(aNotification=%s), "
749 "Succeeded, mText=%s, mSelection=%s, mFirstCharRect=%s, "
750 "mCaret=%s, mTextRectArray=%s, WidgetHasComposition()=%s, "
751 "mHandlingCompositions.Length()=%zu, mCompositionStart=%s, "
752 "mPendingCommitLength=%u, mEditorRect=%s, "
753 "mLastCommitStringTextRectArray=%s",
754 this, GetNotificationName(aNotification),
755 PrintStringDetail(mText, PrintStringDetail::kMaxLengthForEditor).get(),
756 ToString(mSelection).c_str(), ToString(mFirstCharRect).c_str(),
757 ToString(mCaret).c_str(), ToString(mTextRectArray).c_str(),
758 GetBoolName(WidgetHasComposition()), mHandlingCompositions.Length(),
759 ToString(mCompositionStart).c_str(), mPendingCommitLength,
760 ToString(mEditorRect).c_str(),
761 ToString(mLastCommitStringTextRectArray).c_str()));
764 bool ContentCacheInParent::HandleQueryContentEvent(
765 WidgetQueryContentEvent& aEvent, nsIWidget* aWidget) const {
766 MOZ_ASSERT(aWidget);
768 // ContentCache doesn't store offset of its start with XP linebreaks.
769 // So, we don't support to query contents relative to composition start
770 // offset with XP linebreaks.
771 if (NS_WARN_IF(!aEvent.mUseNativeLineBreak)) {
772 MOZ_LOG(sContentCacheLog, LogLevel::Error,
773 ("0x%p HandleQueryContentEvent(), FAILED due to query with XP "
774 "linebreaks",
775 this));
776 return false;
779 if (NS_WARN_IF(!aEvent.mInput.IsValidOffset())) {
780 MOZ_LOG(
781 sContentCacheLog, LogLevel::Error,
782 ("0x%p HandleQueryContentEvent(), FAILED due to invalid offset", this));
783 return false;
786 if (NS_WARN_IF(!aEvent.mInput.IsValidEventMessage(aEvent.mMessage))) {
787 MOZ_LOG(
788 sContentCacheLog, LogLevel::Error,
789 ("0x%p HandleQueryContentEvent(), FAILED due to invalid event message",
790 this));
791 return false;
794 bool isRelativeToInsertionPoint = aEvent.mInput.mRelativeToInsertionPoint;
795 if (isRelativeToInsertionPoint) {
796 MOZ_LOG(
797 sContentCacheLog, LogLevel::Debug,
798 ("0x%p HandleQueryContentEvent(), "
799 "making offset absolute... aEvent={ mMessage=%s, mInput={ "
800 "mOffset=%" PRId64 ", mLength=%" PRIu32 " } }, "
801 "WidgetHasComposition()=%s, HasPendingCommit()=%s, "
802 "mCompositionStart=%" PRIu32 ", "
803 "mPendingCommitLength=%" PRIu32 ", mSelection=%s",
804 this, ToChar(aEvent.mMessage), aEvent.mInput.mOffset,
805 aEvent.mInput.mLength, GetBoolName(WidgetHasComposition()),
806 GetBoolName(HasPendingCommit()), mCompositionStart.valueOr(UINT32_MAX),
807 mPendingCommitLength, ToString(mSelection).c_str()));
808 if (WidgetHasComposition() || HasPendingCommit()) {
809 if (NS_WARN_IF(mCompositionStart.isNothing()) ||
810 NS_WARN_IF(!aEvent.mInput.MakeOffsetAbsolute(
811 mCompositionStart.value() + mPendingCommitLength))) {
812 MOZ_LOG(
813 sContentCacheLog, LogLevel::Error,
814 ("0x%p HandleQueryContentEvent(), FAILED due to "
815 "aEvent.mInput.MakeOffsetAbsolute(mCompositionStart + "
816 "mPendingCommitLength) failure, "
817 "mCompositionStart=%" PRIu32 ", mPendingCommitLength=%" PRIu32 ", "
818 "aEvent={ mMessage=%s, mInput={ mOffset=%" PRId64
819 ", mLength=%" PRIu32 " } }",
820 this, mCompositionStart.valueOr(UINT32_MAX), mPendingCommitLength,
821 ToChar(aEvent.mMessage), aEvent.mInput.mOffset,
822 aEvent.mInput.mLength));
823 return false;
825 } else if (NS_WARN_IF(mSelection.isNothing())) {
826 MOZ_LOG(sContentCacheLog, LogLevel::Error,
827 ("0x%p HandleQueryContentEvent(), FAILED due to mSelection is "
828 "Nothing",
829 this));
830 return false;
831 } else if (NS_WARN_IF(mSelection->mHasRange)) {
832 MOZ_LOG(sContentCacheLog, LogLevel::Error,
833 ("0x%p HandleQueryContentEvent(), FAILED due to there is no "
834 "selection range, but the query requested with relative offset "
835 "from selection",
836 this));
837 return false;
838 } else if (NS_WARN_IF(!aEvent.mInput.MakeOffsetAbsolute(
839 mSelection->StartOffset() + mPendingCommitLength))) {
840 MOZ_LOG(sContentCacheLog, LogLevel::Error,
841 ("0x%p HandleQueryContentEvent(), FAILED due to "
842 "aEvent.mInput.MakeOffsetAbsolute(mSelection->StartOffset() + "
843 "mPendingCommitLength) failure, mSelection=%s, "
844 "mPendingCommitLength=%" PRIu32 ", aEvent={ mMessage=%s, "
845 "mInput={ mOffset=%" PRId64 ", mLength=%" PRIu32 " } }",
846 this, ToString(mSelection).c_str(), mPendingCommitLength,
847 ToChar(aEvent.mMessage), aEvent.mInput.mOffset,
848 aEvent.mInput.mLength));
849 return false;
853 switch (aEvent.mMessage) {
854 case eQuerySelectedText:
855 MOZ_LOG(sContentCacheLog, LogLevel::Info,
856 ("0x%p HandleQueryContentEvent(aEvent={ "
857 "mMessage=eQuerySelectedText }, aWidget=0x%p)",
858 this, aWidget));
859 if (MOZ_UNLIKELY(NS_WARN_IF(mSelection.isNothing()))) {
860 // If content cache hasn't been initialized properly, make the query
861 // failed.
862 MOZ_LOG(sContentCacheLog, LogLevel::Error,
863 ("0x%p HandleQueryContentEvent(), FAILED because mSelection "
864 "is Nothing",
865 this));
866 return false;
868 MOZ_DIAGNOSTIC_ASSERT(mText.isSome());
869 MOZ_DIAGNOSTIC_ASSERT(mSelection->IsValidIn(*mText));
870 aEvent.EmplaceReply();
871 aEvent.mReply->mFocusedWidget = aWidget;
872 if (mSelection->mHasRange) {
873 if (MOZ_LIKELY(mText.isSome())) {
874 aEvent.mReply->mOffsetAndData.emplace(
875 mSelection->StartOffset(),
876 Substring(mText.ref(), mSelection->StartOffset(),
877 mSelection->Length()),
878 OffsetAndDataFor::SelectedString);
879 } else {
880 // TODO: Investigate this case. I find this during
881 // test_mousecapture.xhtml on Linux.
882 aEvent.mReply->mOffsetAndData.emplace(
883 0u, EmptyString(), OffsetAndDataFor::SelectedString);
886 aEvent.mReply->mWritingMode = mSelection->mWritingMode;
887 MOZ_LOG(sContentCacheLog, LogLevel::Info,
888 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
889 "mMessage=eQuerySelectedText, mReply=%s }",
890 this, ToString(aEvent.mReply).c_str()));
891 return true;
892 case eQueryTextContent: {
893 MOZ_LOG(sContentCacheLog, LogLevel::Info,
894 ("0x%p HandleQueryContentEvent(aEvent={ "
895 "mMessage=eQueryTextContent, mInput={ mOffset=%" PRId64
896 ", mLength=%u } }, aWidget=0x%p), mText->Length()=%zu",
897 this, aEvent.mInput.mOffset, aEvent.mInput.mLength, aWidget,
898 mText.isSome() ? mText->Length() : 0u));
899 if (MOZ_UNLIKELY(NS_WARN_IF(mText.isNothing()))) {
900 MOZ_LOG(sContentCacheLog, LogLevel::Error,
901 ("0x%p HandleQueryContentEvent(), FAILED because "
902 "there is no text data",
903 this));
904 return false;
906 const uint32_t inputOffset = aEvent.mInput.mOffset;
907 const uint32_t inputEndOffset = std::min<uint32_t>(
908 aEvent.mInput.EndOffset(), mText.isSome() ? mText->Length() : 0u);
909 if (MOZ_UNLIKELY(NS_WARN_IF(inputEndOffset < inputOffset))) {
910 MOZ_LOG(sContentCacheLog, LogLevel::Error,
911 ("0x%p HandleQueryContentEvent(), FAILED because "
912 "inputOffset=%u is larger than inputEndOffset=%u",
913 this, inputOffset, inputEndOffset));
914 return false;
916 aEvent.EmplaceReply();
917 aEvent.mReply->mFocusedWidget = aWidget;
918 const nsAString& textInQueriedRange =
919 inputEndOffset > inputOffset
920 ? static_cast<const nsAString&>(Substring(
921 mText.ref(), inputOffset, inputEndOffset - inputOffset))
922 : static_cast<const nsAString&>(EmptyString());
923 aEvent.mReply->mOffsetAndData.emplace(inputOffset, textInQueriedRange,
924 OffsetAndDataFor::EditorString);
925 // TODO: Support font ranges
926 MOZ_LOG(sContentCacheLog, LogLevel::Info,
927 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
928 "mMessage=eQueryTextContent, mReply=%s }",
929 this, ToString(aEvent.mReply).c_str()));
930 return true;
932 case eQueryTextRect: {
933 MOZ_LOG(sContentCacheLog, LogLevel::Info,
934 ("0x%p HandleQueryContentEvent("
935 "aEvent={ mMessage=eQueryTextRect, mInput={ mOffset=%" PRId64
936 ", mLength=%u } }, aWidget=0x%p), mText->Length()=%zu",
937 this, aEvent.mInput.mOffset, aEvent.mInput.mLength, aWidget,
938 mText.isSome() ? mText->Length() : 0u));
939 // Note that if the query is relative to insertion point, the query was
940 // probably requested by native IME. In such case, we should return
941 // non-empty rect since returning failure causes IME showing its window
942 // at odd position.
943 LayoutDeviceIntRect textRect;
944 if (aEvent.mInput.mLength) {
945 if (MOZ_UNLIKELY(NS_WARN_IF(
946 !GetUnionTextRects(aEvent.mInput.mOffset, aEvent.mInput.mLength,
947 isRelativeToInsertionPoint, textRect)))) {
948 // XXX We don't have cache for this request.
949 MOZ_LOG(sContentCacheLog, LogLevel::Error,
950 ("0x%p HandleQueryContentEvent(), FAILED to get union rect",
951 this));
952 return false;
954 } else {
955 // If the length is 0, we should return caret rect instead.
956 if (NS_WARN_IF(!GetCaretRect(aEvent.mInput.mOffset,
957 isRelativeToInsertionPoint, textRect))) {
958 MOZ_LOG(sContentCacheLog, LogLevel::Error,
959 ("0x%p HandleQueryContentEvent(), FAILED to get caret rect",
960 this));
961 return false;
964 aEvent.EmplaceReply();
965 aEvent.mReply->mFocusedWidget = aWidget;
966 aEvent.mReply->mRect = textRect;
967 const nsAString& textInQueriedRange =
968 mText.isSome() && aEvent.mInput.mOffset <
969 static_cast<int64_t>(
970 mText.isSome() ? mText->Length() : 0u)
971 ? static_cast<const nsAString&>(
972 Substring(mText.ref(), aEvent.mInput.mOffset,
973 mText->Length() >= aEvent.mInput.EndOffset()
974 ? aEvent.mInput.mLength
975 : UINT32_MAX))
976 : static_cast<const nsAString&>(EmptyString());
977 aEvent.mReply->mOffsetAndData.emplace(aEvent.mInput.mOffset,
978 textInQueriedRange,
979 OffsetAndDataFor::EditorString);
980 // XXX This may be wrong if storing range isn't in the selection range.
981 aEvent.mReply->mWritingMode =
982 mSelection.isSome() ? mSelection->mWritingMode : WritingMode();
983 MOZ_LOG(sContentCacheLog, LogLevel::Info,
984 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
985 "mMessage=eQueryTextRect mReply=%s }",
986 this, ToString(aEvent.mReply).c_str()));
987 return true;
989 case eQueryCaretRect: {
990 MOZ_LOG(
991 sContentCacheLog, LogLevel::Info,
992 ("0x%p HandleQueryContentEvent(aEvent={ mMessage=eQueryCaretRect, "
993 "mInput={ mOffset=%" PRId64
994 " } }, aWidget=0x%p), mText->Length()=%zu",
995 this, aEvent.mInput.mOffset, aWidget,
996 mText.isSome() ? mText->Length() : 0u));
997 // Note that if the query is relative to insertion point, the query was
998 // probably requested by native IME. In such case, we should return
999 // non-empty rect since returning failure causes IME showing its window
1000 // at odd position.
1001 LayoutDeviceIntRect caretRect;
1002 if (NS_WARN_IF(!GetCaretRect(aEvent.mInput.mOffset,
1003 isRelativeToInsertionPoint, caretRect))) {
1004 MOZ_LOG(sContentCacheLog, LogLevel::Error,
1005 ("0x%p HandleQueryContentEvent(),FAILED to get caret rect",
1006 this));
1007 return false;
1009 aEvent.EmplaceReply();
1010 aEvent.mReply->mFocusedWidget = aWidget;
1011 aEvent.mReply->mRect = caretRect;
1012 aEvent.mReply->mOffsetAndData.emplace(aEvent.mInput.mOffset,
1013 EmptyString(),
1014 OffsetAndDataFor::SelectedString);
1015 // TODO: Set mWritingMode here
1016 MOZ_LOG(sContentCacheLog, LogLevel::Info,
1017 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
1018 "mMessage=eQueryCaretRect, mReply=%s }",
1019 this, ToString(aEvent.mReply).c_str()));
1020 return true;
1022 case eQueryEditorRect:
1023 MOZ_LOG(sContentCacheLog, LogLevel::Info,
1024 ("0x%p HandleQueryContentEvent(aEvent={ "
1025 "mMessage=eQueryEditorRect }, aWidget=0x%p)",
1026 this, aWidget));
1027 // XXX This query should fail if no editable elmenet has focus. Or,
1028 // perhaps, should return rect of the window instead.
1029 aEvent.EmplaceReply();
1030 aEvent.mReply->mFocusedWidget = aWidget;
1031 aEvent.mReply->mRect = mEditorRect;
1032 MOZ_LOG(sContentCacheLog, LogLevel::Info,
1033 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
1034 "mMessage=eQueryEditorRect, mReply=%s }",
1035 this, ToString(aEvent.mReply).c_str()));
1036 return true;
1037 default:
1038 aEvent.EmplaceReply();
1039 aEvent.mReply->mFocusedWidget = aWidget;
1040 if (NS_WARN_IF(aEvent.Failed())) {
1041 MOZ_LOG(
1042 sContentCacheLog, LogLevel::Error,
1043 ("0x%p HandleQueryContentEvent(), FAILED due to not set enough "
1044 "data, aEvent={ mMessage=%s, mReply=%s }",
1045 this, ToChar(aEvent.mMessage), ToString(aEvent.mReply).c_str()));
1046 return false;
1048 MOZ_LOG(sContentCacheLog, LogLevel::Info,
1049 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
1050 "mMessage=%s, mReply=%s }",
1051 this, ToChar(aEvent.mMessage), ToString(aEvent.mReply).c_str()));
1052 return true;
1056 bool ContentCacheInParent::GetTextRect(uint32_t aOffset,
1057 bool aRoundToExistingOffset,
1058 LayoutDeviceIntRect& aTextRect) const {
1059 MOZ_LOG(
1060 sContentCacheLog, LogLevel::Info,
1061 ("0x%p GetTextRect(aOffset=%u, aRoundToExistingOffset=%s), "
1062 "mTextRectArray=%s, mSelection=%s, mLastCommitStringTextRectArray=%s",
1063 this, aOffset, GetBoolName(aRoundToExistingOffset),
1064 ToString(mTextRectArray).c_str(), ToString(mSelection).c_str(),
1065 ToString(mLastCommitStringTextRectArray).c_str()));
1067 if (!aOffset) {
1068 NS_WARNING_ASSERTION(!mFirstCharRect.IsEmpty(), "empty rect");
1069 aTextRect = mFirstCharRect;
1070 return !aTextRect.IsEmpty();
1072 if (mSelection.isSome() && mSelection->mHasRange) {
1073 if (aOffset == mSelection->mAnchor) {
1074 NS_WARNING_ASSERTION(
1075 !mSelection->mAnchorCharRects[eNextCharRect].IsEmpty(), "empty rect");
1076 aTextRect = mSelection->mAnchorCharRects[eNextCharRect];
1077 return !aTextRect.IsEmpty();
1079 if (mSelection->mAnchor && aOffset == mSelection->mAnchor - 1) {
1080 NS_WARNING_ASSERTION(
1081 !mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty(), "empty rect");
1082 aTextRect = mSelection->mAnchorCharRects[ePrevCharRect];
1083 return !aTextRect.IsEmpty();
1085 if (aOffset == mSelection->mFocus) {
1086 NS_WARNING_ASSERTION(
1087 !mSelection->mFocusCharRects[eNextCharRect].IsEmpty(), "empty rect");
1088 aTextRect = mSelection->mFocusCharRects[eNextCharRect];
1089 return !aTextRect.IsEmpty();
1091 if (mSelection->mFocus && aOffset == mSelection->mFocus - 1) {
1092 NS_WARNING_ASSERTION(
1093 !mSelection->mFocusCharRects[ePrevCharRect].IsEmpty(), "empty rect");
1094 aTextRect = mSelection->mFocusCharRects[ePrevCharRect];
1095 return !aTextRect.IsEmpty();
1099 if (mTextRectArray.isSome() && mTextRectArray->IsOffsetInRange(aOffset)) {
1100 aTextRect = mTextRectArray->GetRect(aOffset);
1101 return !aTextRect.IsEmpty();
1104 if (mLastCommitStringTextRectArray.isSome() &&
1105 mLastCommitStringTextRectArray->IsOffsetInRange(aOffset)) {
1106 aTextRect = mLastCommitStringTextRectArray->GetRect(aOffset);
1107 return !aTextRect.IsEmpty();
1110 if (!aRoundToExistingOffset) {
1111 aTextRect.SetEmpty();
1112 return false;
1115 if (mTextRectArray.isNothing() || !mTextRectArray->HasRects()) {
1116 // If there are no rects in mTextRectArray, we should refer the start of
1117 // the selection if there is because IME must query a char rect around it if
1118 // there is no composition.
1119 if (mSelection.isNothing()) {
1120 // Unfortunately, there is no data about text rect...
1121 aTextRect.SetEmpty();
1122 return false;
1124 aTextRect = mSelection->StartCharRect();
1125 return !aTextRect.IsEmpty();
1128 // Although we may have mLastCommitStringTextRectArray here and it must have
1129 // previous character rects at selection. However, we should stop using it
1130 // because it's stored really short time after commiting a composition.
1131 // So, multiple query may return different rect and it may cause flickerling
1132 // the IME UI.
1133 uint32_t offset = aOffset;
1134 if (offset < mTextRectArray->StartOffset()) {
1135 offset = mTextRectArray->StartOffset();
1136 } else {
1137 offset = mTextRectArray->EndOffset() - 1;
1139 aTextRect = mTextRectArray->GetRect(offset);
1140 return !aTextRect.IsEmpty();
1143 bool ContentCacheInParent::GetUnionTextRects(
1144 uint32_t aOffset, uint32_t aLength, bool aRoundToExistingOffset,
1145 LayoutDeviceIntRect& aUnionTextRect) const {
1146 MOZ_LOG(sContentCacheLog, LogLevel::Info,
1147 ("0x%p GetUnionTextRects(aOffset=%u, "
1148 "aLength=%u, aRoundToExistingOffset=%s), mTextRectArray=%s, "
1149 "mSelection=%s, mLastCommitStringTextRectArray=%s",
1150 this, aOffset, aLength, GetBoolName(aRoundToExistingOffset),
1151 ToString(mTextRectArray).c_str(), ToString(mSelection).c_str(),
1152 ToString(mLastCommitStringTextRectArray).c_str()));
1154 CheckedInt<uint32_t> endOffset = CheckedInt<uint32_t>(aOffset) + aLength;
1155 if (!endOffset.isValid()) {
1156 return false;
1159 if (mSelection.isSome() && !mSelection->IsCollapsed() &&
1160 aOffset == mSelection->StartOffset() && aLength == mSelection->Length()) {
1161 NS_WARNING_ASSERTION(!mSelection->mRect.IsEmpty(), "empty rect");
1162 aUnionTextRect = mSelection->mRect;
1163 return !aUnionTextRect.IsEmpty();
1166 if (aLength == 1) {
1167 if (!aOffset) {
1168 NS_WARNING_ASSERTION(!mFirstCharRect.IsEmpty(), "empty rect");
1169 aUnionTextRect = mFirstCharRect;
1170 return !aUnionTextRect.IsEmpty();
1172 if (mSelection.isSome() && mSelection->mHasRange) {
1173 if (aOffset == mSelection->mAnchor) {
1174 NS_WARNING_ASSERTION(
1175 !mSelection->mAnchorCharRects[eNextCharRect].IsEmpty(),
1176 "empty rect");
1177 aUnionTextRect = mSelection->mAnchorCharRects[eNextCharRect];
1178 return !aUnionTextRect.IsEmpty();
1180 if (mSelection->mAnchor && aOffset == mSelection->mAnchor - 1) {
1181 NS_WARNING_ASSERTION(
1182 !mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty(),
1183 "empty rect");
1184 aUnionTextRect = mSelection->mAnchorCharRects[ePrevCharRect];
1185 return !aUnionTextRect.IsEmpty();
1187 if (aOffset == mSelection->mFocus) {
1188 NS_WARNING_ASSERTION(
1189 !mSelection->mFocusCharRects[eNextCharRect].IsEmpty(),
1190 "empty rect");
1191 aUnionTextRect = mSelection->mFocusCharRects[eNextCharRect];
1192 return !aUnionTextRect.IsEmpty();
1194 if (mSelection->mFocus && aOffset == mSelection->mFocus - 1) {
1195 NS_WARNING_ASSERTION(
1196 !mSelection->mFocusCharRects[ePrevCharRect].IsEmpty(),
1197 "empty rect");
1198 aUnionTextRect = mSelection->mFocusCharRects[ePrevCharRect];
1199 return !aUnionTextRect.IsEmpty();
1204 // Even if some text rects are not cached of the queried range,
1205 // we should return union rect when the first character's rect is cached
1206 // since the first character rect is important and the others are not so
1207 // in most cases.
1209 if (!aOffset && mSelection.isSome() && mSelection->mHasRange &&
1210 aOffset != mSelection->mAnchor && aOffset != mSelection->mFocus &&
1211 (mTextRectArray.isNothing() ||
1212 !mTextRectArray->IsOffsetInRange(aOffset)) &&
1213 (mLastCommitStringTextRectArray.isNothing() ||
1214 !mLastCommitStringTextRectArray->IsOffsetInRange(aOffset))) {
1215 // The first character rect isn't cached.
1216 return false;
1219 // Use mLastCommitStringTextRectArray only when it overlaps with aOffset
1220 // even if aROundToExistingOffset is true for avoiding flickerling IME UI.
1221 // See the last comment in GetTextRect() for the detail.
1222 if (mLastCommitStringTextRectArray.isSome() &&
1223 mLastCommitStringTextRectArray->IsOverlappingWith(aOffset, aLength)) {
1224 aUnionTextRect =
1225 mLastCommitStringTextRectArray->GetUnionRectAsFarAsPossible(
1226 aOffset, aLength, aRoundToExistingOffset);
1227 } else {
1228 aUnionTextRect.SetEmpty();
1231 if (mTextRectArray.isSome() &&
1232 ((aRoundToExistingOffset && mTextRectArray->HasRects()) ||
1233 mTextRectArray->IsOverlappingWith(aOffset, aLength))) {
1234 aUnionTextRect =
1235 aUnionTextRect.Union(mTextRectArray->GetUnionRectAsFarAsPossible(
1236 aOffset, aLength, aRoundToExistingOffset));
1239 if (!aOffset) {
1240 aUnionTextRect = aUnionTextRect.Union(mFirstCharRect);
1242 if (mSelection.isSome() && mSelection->mHasRange) {
1243 if (aOffset <= mSelection->mAnchor &&
1244 mSelection->mAnchor < endOffset.value()) {
1245 aUnionTextRect =
1246 aUnionTextRect.Union(mSelection->mAnchorCharRects[eNextCharRect]);
1248 if (mSelection->mAnchor && aOffset <= mSelection->mAnchor - 1 &&
1249 mSelection->mAnchor - 1 < endOffset.value()) {
1250 aUnionTextRect =
1251 aUnionTextRect.Union(mSelection->mAnchorCharRects[ePrevCharRect]);
1253 if (aOffset <= mSelection->mFocus &&
1254 mSelection->mFocus < endOffset.value()) {
1255 aUnionTextRect =
1256 aUnionTextRect.Union(mSelection->mFocusCharRects[eNextCharRect]);
1258 if (mSelection->mFocus && aOffset <= mSelection->mFocus - 1 &&
1259 mSelection->mFocus - 1 < endOffset.value()) {
1260 aUnionTextRect =
1261 aUnionTextRect.Union(mSelection->mFocusCharRects[ePrevCharRect]);
1265 return !aUnionTextRect.IsEmpty();
1268 bool ContentCacheInParent::GetCaretRect(uint32_t aOffset,
1269 bool aRoundToExistingOffset,
1270 LayoutDeviceIntRect& aCaretRect) const {
1271 MOZ_LOG(sContentCacheLog, LogLevel::Info,
1272 ("0x%p GetCaretRect(aOffset=%u, aRoundToExistingOffset=%s), "
1273 "mCaret=%s, mTextRectArray=%s, mSelection=%s, mFirstCharRect=%s",
1274 this, aOffset, GetBoolName(aRoundToExistingOffset),
1275 ToString(mCaret).c_str(), ToString(mTextRectArray).c_str(),
1276 ToString(mSelection).c_str(), ToString(mFirstCharRect).c_str()));
1278 if (mCaret.isSome() && mCaret->mOffset == aOffset) {
1279 aCaretRect = mCaret->mRect;
1280 return true;
1283 // Guess caret rect from the text rect if it's stored.
1284 if (!GetTextRect(aOffset, aRoundToExistingOffset, aCaretRect)) {
1285 // There might be previous character rect in the cache. If so, we can
1286 // guess the caret rect with it.
1287 if (!aOffset ||
1288 !GetTextRect(aOffset - 1, aRoundToExistingOffset, aCaretRect)) {
1289 aCaretRect.SetEmpty();
1290 return false;
1293 if (mSelection.isSome() && mSelection->mWritingMode.IsVertical()) {
1294 aCaretRect.MoveToY(aCaretRect.YMost());
1295 } else {
1296 // XXX bidi-unaware.
1297 aCaretRect.MoveToX(aCaretRect.XMost());
1301 // XXX This is not bidi aware because we don't cache each character's
1302 // direction. However, this is usually used by IME, so, assuming the
1303 // character is in LRT context must not cause any problem.
1304 if (mSelection.isSome() && mSelection->mWritingMode.IsVertical()) {
1305 aCaretRect.SetHeight(mCaret.isSome() ? mCaret->mRect.Height() : 1);
1306 } else {
1307 aCaretRect.SetWidth(mCaret.isSome() ? mCaret->mRect.Width() : 1);
1309 return true;
1312 bool ContentCacheInParent::OnCompositionEvent(
1313 const WidgetCompositionEvent& aCompositionEvent) {
1314 MOZ_LOG(
1315 sContentCacheLog, LogLevel::Info,
1316 ("0x%p OnCompositionEvent(aCompositionEvent={ "
1317 "mMessage=%s, mData=\"%s\", mRanges->Length()=%zu }), "
1318 "PendingEventsNeedingAck()=%u, WidgetHasComposition()=%s, "
1319 "mHandlingCompositions.Length()=%zu, HasPendingCommit()=%s, "
1320 "mIsChildIgnoringCompositionEvents=%s, mCommitStringByRequest=0x%p",
1321 this, ToChar(aCompositionEvent.mMessage),
1322 PrintStringDetail(aCompositionEvent.mData,
1323 PrintStringDetail::kMaxLengthForCompositionString)
1324 .get(),
1325 aCompositionEvent.mRanges ? aCompositionEvent.mRanges->Length() : 0,
1326 PendingEventsNeedingAck(), GetBoolName(WidgetHasComposition()),
1327 mHandlingCompositions.Length(), GetBoolName(HasPendingCommit()),
1328 GetBoolName(mIsChildIgnoringCompositionEvents), mCommitStringByRequest));
1330 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1331 mDispatchedEventMessages.AppendElement(aCompositionEvent.mMessage);
1332 #endif // #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1334 // We must be able to simulate the selection because
1335 // we might not receive selection updates in time
1336 if (!WidgetHasComposition()) {
1337 if (mCompositionStartInChild.isSome()) {
1338 // If there is pending composition in the remote process, let's use
1339 // its start offset temporarily because this stores a lot of information
1340 // around it and the user must look around there, so, showing some UI
1341 // around it must make sense.
1342 mCompositionStart = mCompositionStartInChild;
1343 } else {
1344 mCompositionStart = Some(mSelection.isSome() && mSelection->mHasRange
1345 ? mSelection->StartOffset()
1346 : 0u);
1348 MOZ_ASSERT(aCompositionEvent.mMessage == eCompositionStart);
1349 mHandlingCompositions.AppendElement(
1350 HandlingCompositionData(aCompositionEvent.mCompositionId));
1353 mHandlingCompositions.LastElement().mSentCommitEvent =
1354 aCompositionEvent.CausesDOMCompositionEndEvent();
1355 MOZ_ASSERT(mHandlingCompositions.LastElement().mCompositionId ==
1356 aCompositionEvent.mCompositionId);
1358 if (!WidgetHasComposition()) {
1359 // mCompositionStart will be reset when commit event is completely handled
1360 // in the remote process.
1361 if (mHandlingCompositions.Length() == 1u) {
1362 mPendingCommitLength = aCompositionEvent.mData.Length();
1364 MOZ_ASSERT(HasPendingCommit());
1365 } else if (aCompositionEvent.mMessage != eCompositionStart) {
1366 mHandlingCompositions.LastElement().mCompositionString =
1367 aCompositionEvent.mData;
1370 // During REQUEST_TO_COMMIT_COMPOSITION or REQUEST_TO_CANCEL_COMPOSITION,
1371 // widget usually sends a eCompositionChange and/or eCompositionCommit event
1372 // to finalize or clear the composition, respectively. In this time,
1373 // we need to intercept all composition events here and pass the commit
1374 // string for returning to the remote process as a result of
1375 // RequestIMEToCommitComposition(). Then, eCommitComposition event will
1376 // be dispatched with the committed string in the remote process internally.
1377 if (mCommitStringByRequest) {
1378 if (aCompositionEvent.mMessage == eCompositionCommitAsIs) {
1379 *mCommitStringByRequest =
1380 mHandlingCompositions.LastElement().mCompositionString;
1381 } else {
1382 MOZ_ASSERT(aCompositionEvent.mMessage == eCompositionChange ||
1383 aCompositionEvent.mMessage == eCompositionCommit);
1384 *mCommitStringByRequest = aCompositionEvent.mData;
1386 // We need to wait eCompositionCommitRequestHandled from the remote process
1387 // in this case. Therefore, mPendingEventsNeedingAck needs to be
1388 // incremented here.
1389 if (!WidgetHasComposition()) {
1390 mHandlingCompositions.LastElement().mPendingEventsNeedingAck++;
1392 return false;
1395 mHandlingCompositions.LastElement().mPendingEventsNeedingAck++;
1396 return true;
1399 void ContentCacheInParent::OnSelectionEvent(
1400 const WidgetSelectionEvent& aSelectionEvent) {
1401 MOZ_LOG(sContentCacheLog, LogLevel::Info,
1402 ("0x%p OnSelectionEvent(aEvent={ "
1403 "mMessage=%s, mOffset=%u, mLength=%u, mReversed=%s, "
1404 "mExpandToClusterBoundary=%s, mUseNativeLineBreak=%s }), "
1405 "PendingEventsNeedingAck()=%u, WidgetHasComposition()=%s, "
1406 "mHandlingCompositions.Length()=%zu, HasPendingCommit()=%s, "
1407 "mIsChildIgnoringCompositionEvents=%s",
1408 this, ToChar(aSelectionEvent.mMessage), aSelectionEvent.mOffset,
1409 aSelectionEvent.mLength, GetBoolName(aSelectionEvent.mReversed),
1410 GetBoolName(aSelectionEvent.mExpandToClusterBoundary),
1411 GetBoolName(aSelectionEvent.mUseNativeLineBreak),
1412 PendingEventsNeedingAck(), GetBoolName(WidgetHasComposition()),
1413 mHandlingCompositions.Length(), GetBoolName(HasPendingCommit()),
1414 GetBoolName(mIsChildIgnoringCompositionEvents)));
1416 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1417 mDispatchedEventMessages.AppendElement(aSelectionEvent.mMessage);
1418 #endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
1420 mPendingSetSelectionEventNeedingAck++;
1423 void ContentCacheInParent::OnContentCommandEvent(
1424 const WidgetContentCommandEvent& aContentCommandEvent) {
1425 MOZ_LOG(sContentCacheLog, LogLevel::Info,
1426 ("0x%p OnContentCommandEvent(aEvent={ "
1427 "mMessage=%s, mString=\"%s\", mSelection={ mReplaceSrcString=\"%s\" "
1428 "mOffset=%u, mPreventSetSelection=%s }, mOnlyEnabledCheck=%s })",
1429 this, ToChar(aContentCommandEvent.mMessage),
1430 ToString(aContentCommandEvent.mString).c_str(),
1431 ToString(aContentCommandEvent.mSelection.mReplaceSrcString).c_str(),
1432 aContentCommandEvent.mSelection.mOffset,
1433 GetBoolName(aContentCommandEvent.mSelection.mPreventSetSelection),
1434 GetBoolName(aContentCommandEvent.mOnlyEnabledCheck)));
1436 MOZ_ASSERT(!aContentCommandEvent.mOnlyEnabledCheck);
1438 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1439 mDispatchedEventMessages.AppendElement(aContentCommandEvent.mMessage);
1440 #endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
1442 mPendingContentCommandEventNeedingAck++;
1445 void ContentCacheInParent::OnEventNeedingAckHandled(nsIWidget* aWidget,
1446 EventMessage aMessage,
1447 uint32_t aCompositionId) {
1448 // This is called when the child process receives WidgetCompositionEvent,
1449 // WidgetSelectionEvent or WidgetContentCommandEvent.
1451 const bool isCompositionEvent = [&]() {
1452 switch (aMessage) {
1453 case eCompositionStart:
1454 case eCompositionEnd:
1455 case eCompositionChange:
1456 case eCompositionCommitAsIs:
1457 case eCompositionCommit:
1458 case eCompositionCommitRequestHandled:
1459 return true;
1460 case eSetSelection:
1461 case eContentCommandCut:
1462 case eContentCommandCopy:
1463 case eContentCommandPaste:
1464 case eContentCommandDelete:
1465 case eContentCommandUndo:
1466 case eContentCommandRedo:
1467 case eContentCommandInsertText:
1468 case eContentCommandReplaceText:
1469 return false;
1470 default:
1471 NS_ASSERTION(
1472 false, nsPrintfCString(
1473 "%s message is NOT expected in OnEventNeedingAckHandled",
1474 ToChar(aMessage))
1475 .get());
1476 return false;
1478 }();
1480 HandlingCompositionData* handlingCompositionData =
1481 isCompositionEvent ? GetHandlingCompositionData(aCompositionId) : nullptr;
1483 MOZ_LOG(sContentCacheLog, LogLevel::Info,
1484 ("0x%p OnEventNeedingAckHandled(aWidget=0x%p, aMessage=%s, "
1485 "aCompositionId=%" PRIu32
1486 "), PendingEventsNeedingAck()=%u, WidgetHasComposition()=%s, "
1487 "mHandlingCompositions.Length()=%zu, HasPendingCommit()=%s, "
1488 "mIsChildIgnoringCompositionEvents=%s, handlingCompositionData=0x%p",
1489 this, aWidget, ToChar(aMessage), aCompositionId,
1490 PendingEventsNeedingAck(), GetBoolName(WidgetHasComposition()),
1491 mHandlingCompositions.Length(), GetBoolName(HasPendingCommit()),
1492 GetBoolName(mIsChildIgnoringCompositionEvents),
1493 handlingCompositionData));
1495 // If we receive composition event messages for older one or invalid one,
1496 // we should ignore them.
1497 if (NS_WARN_IF(isCompositionEvent && !handlingCompositionData)) {
1498 return;
1501 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1502 mReceivedEventMessages.AppendElement(aMessage);
1503 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1505 const bool isCommittedInChild =
1506 // Commit requester in the remote process has committed the composition.
1507 aMessage == eCompositionCommitRequestHandled ||
1508 // The commit event has been handled normally in the remote process.
1509 (!mIsChildIgnoringCompositionEvents &&
1510 WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage));
1511 const bool hasPendingCommit = HasPendingCommit();
1513 if (isCommittedInChild) {
1514 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1515 if (mHandlingCompositions.Length() == 1u) {
1516 RemoveUnnecessaryEventMessageLog();
1519 if (NS_WARN_IF(aMessage != eCompositionCommitRequestHandled &&
1520 !handlingCompositionData->mSentCommitEvent)) {
1521 nsPrintfCString info(
1522 "\nReceived unexpected commit event message (%s) which we've "
1523 "not sent yet\n\n",
1524 ToChar(aMessage));
1525 AppendEventMessageLog(info);
1526 CrashReporter::AppendAppNotesToCrashReport(info);
1527 MOZ_DIAGNOSTIC_ASSERT(
1528 false,
1529 "Received unexpected commit event which has not been sent yet");
1531 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1533 // This should not occur, though. If we receive a commit notification for
1534 // not the oldest composition, we should forget all older compositions.
1535 size_t numberOfOutdatedCompositions = 1u;
1536 for (auto& data : mHandlingCompositions) {
1537 if (&data == handlingCompositionData) {
1538 if (
1539 // Don't put the info into the log when we've already sent commit
1540 // event because it may be just inserting a character without
1541 // composing state, but the remote process may move focus at
1542 // eCompositionStart. This may happen with UI of IME to put only
1543 // one character, e.g., the default Emoji picker of Windows.
1544 !data.mSentCommitEvent &&
1545 // In the normal case, only one message should remain, however,
1546 // remaining 2 or more messages is also valid, for example, the
1547 // remote process may have a composition update listener which
1548 // takes a while. Then, we can have multiple pending messages.
1549 data.mPendingEventsNeedingAck >= 1u) {
1550 MOZ_LOG(
1551 sContentCacheLog, LogLevel::Debug,
1552 (" NOTE: BrowserParent has %" PRIu32
1553 " pending composition messages for the handling composition, "
1554 "but before they are handled in the remote process, the active "
1555 "composition is commited by a request. "
1556 "OnEventNeedingAckHandled() calls for them will be ignored",
1557 data.mPendingEventsNeedingAck));
1559 break;
1561 if (MOZ_UNLIKELY(data.mPendingEventsNeedingAck)) {
1562 MOZ_LOG(sContentCacheLog, LogLevel::Warning,
1563 (" BrowserParent has %" PRIu32
1564 " pending composition messages for an older composition than "
1565 "the handling composition, but it'll be removed because newer "
1566 "composition gets comitted in the remote process",
1567 data.mPendingEventsNeedingAck));
1569 numberOfOutdatedCompositions++;
1571 mHandlingCompositions.RemoveElementsAt(0u, numberOfOutdatedCompositions);
1572 handlingCompositionData = nullptr;
1574 // Forget pending commit string length if it's handled in the remote
1575 // process. Note that this doesn't care too old composition's commit
1576 // string because in such case, we cannot return proper information
1577 // to IME synchronously.
1578 mPendingCommitLength = 0;
1581 if (WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage)) {
1582 // After the remote process receives eCompositionCommit(AsIs) event,
1583 // it'll restart to handle composition events.
1584 mIsChildIgnoringCompositionEvents = false;
1586 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1587 if (NS_WARN_IF(!hasPendingCommit)) {
1588 nsPrintfCString info(
1589 "\nThere is no pending comment events but received "
1590 "%s message from the remote child\n\n",
1591 ToChar(aMessage));
1592 AppendEventMessageLog(info);
1593 CrashReporter::AppendAppNotesToCrashReport(info);
1594 MOZ_DIAGNOSTIC_ASSERT(
1595 false,
1596 "No pending commit events but received unexpected commit event");
1598 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1599 } else if (aMessage == eCompositionCommitRequestHandled && hasPendingCommit) {
1600 // If the remote process commits composition synchronously after
1601 // requesting commit composition and we've already sent commit composition,
1602 // it starts to ignore following composition events until receiving
1603 // eCompositionStart event.
1604 mIsChildIgnoringCompositionEvents = true;
1607 // If neither widget (i.e., IME) nor the remote process has composition,
1608 // now, we can forget composition string informations.
1609 if (mHandlingCompositions.IsEmpty()) {
1610 mCompositionStart.reset();
1613 if (handlingCompositionData) {
1614 if (NS_WARN_IF(!handlingCompositionData->mPendingEventsNeedingAck)) {
1615 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1616 nsPrintfCString info(
1617 "\nThere is no pending events but received %s "
1618 "message from the remote child\n\n",
1619 ToChar(aMessage));
1620 AppendEventMessageLog(info);
1621 CrashReporter::AppendAppNotesToCrashReport(info);
1622 MOZ_DIAGNOSTIC_ASSERT(
1623 false, "No pending event message but received unexpected event");
1624 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1625 } else {
1626 handlingCompositionData->mPendingEventsNeedingAck--;
1628 } else if (aMessage == eSetSelection) {
1629 if (NS_WARN_IF(!mPendingSetSelectionEventNeedingAck)) {
1630 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1631 nsAutoCString info(
1632 "\nThere is no pending set selection events but received from the "
1633 "remote child\n\n");
1634 AppendEventMessageLog(info);
1635 CrashReporter::AppendAppNotesToCrashReport(info);
1636 MOZ_DIAGNOSTIC_ASSERT(
1637 false, "No pending event message but received unexpected event");
1638 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1639 } else {
1640 mPendingSetSelectionEventNeedingAck--;
1642 } else if (aMessage >= eContentCommandEventFirst &&
1643 aMessage <= eContentCommandEventLast) {
1644 if (NS_WARN_IF(!mPendingContentCommandEventNeedingAck)) {
1645 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1646 nsAutoCString info(
1647 "\nThere is no pending content command events but received from the "
1648 "remote child\n\n");
1649 AppendEventMessageLog(info);
1650 CrashReporter::AppendAppNotesToCrashReport(info);
1651 MOZ_DIAGNOSTIC_ASSERT(
1652 false, "No pending event message but received unexpected event");
1653 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1654 } else {
1655 mPendingContentCommandEventNeedingAck--;
1659 if (!PendingEventsNeedingAck()) {
1660 FlushPendingNotifications(aWidget);
1664 bool ContentCacheInParent::RequestIMEToCommitComposition(
1665 nsIWidget* aWidget, bool aCancel, uint32_t aCompositionId,
1666 nsAString& aCommittedString) {
1667 HandlingCompositionData* const handlingCompositionData =
1668 GetHandlingCompositionData(aCompositionId);
1670 MOZ_LOG(
1671 sContentCacheLog, LogLevel::Info,
1672 ("0x%p RequestToCommitComposition(aWidget=%p, "
1673 "aCancel=%s, aCompositionId=%" PRIu32
1674 "), mHandlingCompositions.Length()=%zu, HasPendingCommit()=%s, "
1675 "mIsChildIgnoringCompositionEvents=%s, "
1676 "IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)=%s, "
1677 "WidgetHasComposition()=%s, mCommitStringByRequest=%p, "
1678 "handlingCompositionData=0x%p",
1679 this, aWidget, GetBoolName(aCancel), aCompositionId,
1680 mHandlingCompositions.Length(), GetBoolName(HasPendingCommit()),
1681 GetBoolName(mIsChildIgnoringCompositionEvents),
1682 GetBoolName(
1683 IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)),
1684 GetBoolName(WidgetHasComposition()), mCommitStringByRequest,
1685 handlingCompositionData));
1687 MOZ_ASSERT(!mCommitStringByRequest);
1689 // If we don't know the composition ID, it must have already been committed
1690 // in this process. In the case, we should do nothing here.
1691 if (NS_WARN_IF(!handlingCompositionData)) {
1692 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1693 mRequestIMEToCommitCompositionResults.AppendElement(
1694 RequestIMEToCommitCompositionResult::eToUnknownCompositionReceived);
1695 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1696 return false;
1699 // If we receive a commit result for not latest composition, this request is
1700 // too late for IME. The remote process should wait following composition
1701 // events for cleaning up TextComposition and handle the request as it's
1702 // handled asynchronously.
1703 if (handlingCompositionData != &mHandlingCompositions.LastElement()) {
1704 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1705 mRequestIMEToCommitCompositionResults.AppendElement(
1706 RequestIMEToCommitCompositionResult::eToOldCompositionReceived);
1707 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1708 return false;
1711 // If the composition has already been commit, th remote process will receive
1712 // composition events and clean up TextComposition. So, this should do
1713 // nothing and TextComposition should handle the request as it's handled
1714 // asynchronously.
1715 // XXX Perhaps, this is wrong because TextComposition in child process
1716 // may commit the composition with current composition string in the
1717 // remote process. I.e., it may be different from actual commit string
1718 // which user typed. So, perhaps, we should return true and the commit
1719 // string.
1720 if (handlingCompositionData->mSentCommitEvent) {
1721 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1722 mRequestIMEToCommitCompositionResults.AppendElement(
1723 RequestIMEToCommitCompositionResult::eToCommittedCompositionReceived);
1724 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1725 return false;
1728 // If BrowserParent which has IME focus was already changed to different one,
1729 // the request shouldn't be sent to IME because it's too late.
1730 if (!IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)) {
1731 // Use the latest composition string which may not be handled in the
1732 // remote process for avoiding data loss.
1733 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1734 mRequestIMEToCommitCompositionResults.AppendElement(
1735 RequestIMEToCommitCompositionResult::eReceivedAfterBrowserParentBlur);
1736 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1737 aCommittedString = handlingCompositionData->mCompositionString;
1738 // After we return true from here, i.e., without actually requesting IME
1739 // to commit composition, we will receive eCompositionCommitRequestHandled
1740 // pseudo event message from the remote process. So, we need to increment
1741 // mPendingEventsNeedingAck here.
1742 handlingCompositionData->mPendingEventsNeedingAck++;
1743 return true;
1746 RefPtr<TextComposition> composition =
1747 IMEStateManager::GetTextCompositionFor(aWidget);
1748 if (NS_WARN_IF(!composition)) {
1749 MOZ_LOG(sContentCacheLog, LogLevel::Warning,
1750 (" 0x%p RequestToCommitComposition(), "
1751 "does nothing due to no composition",
1752 this));
1753 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1754 mRequestIMEToCommitCompositionResults.AppendElement(
1755 RequestIMEToCommitCompositionResult::eReceivedButNoTextComposition);
1756 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1757 return false;
1760 // If we receive a request for different composition, we must have already
1761 // sent a commit event. So the remote process should handle it.
1762 // XXX I think that this should never happen because we already checked
1763 // whether handlingCompositionData is the latest composition or not.
1764 // However, we don't want to commit different composition for the users.
1765 // Therefore, let's handle the odd case here.
1766 if (NS_WARN_IF(composition->Id() != aCompositionId)) {
1767 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1768 mRequestIMEToCommitCompositionResults.AppendElement(
1769 RequestIMEToCommitCompositionResult::
1770 eReceivedButForDifferentTextComposition);
1771 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1772 return false;
1775 mCommitStringByRequest = &aCommittedString;
1777 // Request commit or cancel composition with TextComposition because we may
1778 // have already requested to commit or cancel the composition or we may
1779 // have already received eCompositionCommit(AsIs) event. Those status are
1780 // managed by composition. So, if we don't request commit composition,
1781 // we should do nothing with native IME here.
1782 composition->RequestToCommit(aWidget, aCancel);
1784 mCommitStringByRequest = nullptr;
1786 MOZ_LOG(
1787 sContentCacheLog, LogLevel::Info,
1788 (" 0x%p RequestToCommitComposition(), "
1789 "WidgetHasComposition()=%s, the composition %s committed synchronously",
1790 this, GetBoolName(WidgetHasComposition()),
1791 composition->Destroyed() ? "WAS" : "has NOT been"));
1793 if (!composition->Destroyed()) {
1794 // When the composition isn't committed synchronously, the remote process's
1795 // TextComposition instance will synthesize commit events and wait to
1796 // receive delayed composition events. When TextComposition instances both
1797 // in this process and the remote process will be destroyed when delayed
1798 // composition events received. TextComposition instance in the parent
1799 // process will dispatch following composition events and be destroyed
1800 // normally. On the other hand, TextComposition instance in the remote
1801 // process won't dispatch following composition events and will be
1802 // destroyed by IMEStateManager::DispatchCompositionEvent().
1803 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1804 mRequestIMEToCommitCompositionResults.AppendElement(
1805 RequestIMEToCommitCompositionResult::eHandledAsynchronously);
1806 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1807 return false;
1810 // When the composition is committed synchronously, the commit string will be
1811 // returned to the remote process. Then, PuppetWidget will dispatch
1812 // eCompositionCommit event with the returned commit string (i.e., the value
1813 // is aCommittedString of this method) and that causes destroying
1814 // TextComposition instance in the remote process (Note that TextComposition
1815 // instance in this process was already destroyed).
1816 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1817 mRequestIMEToCommitCompositionResults.AppendElement(
1818 RequestIMEToCommitCompositionResult::eHandledSynchronously);
1819 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1820 return true;
1823 void ContentCacheInParent::MaybeNotifyIME(
1824 nsIWidget* aWidget, const IMENotification& aNotification) {
1825 if (!PendingEventsNeedingAck()) {
1826 IMEStateManager::NotifyIME(aNotification, aWidget, &mBrowserParent);
1827 return;
1830 switch (aNotification.mMessage) {
1831 case NOTIFY_IME_OF_SELECTION_CHANGE:
1832 mPendingSelectionChange.MergeWith(aNotification);
1833 break;
1834 case NOTIFY_IME_OF_TEXT_CHANGE:
1835 mPendingTextChange.MergeWith(aNotification);
1836 break;
1837 case NOTIFY_IME_OF_POSITION_CHANGE:
1838 mPendingLayoutChange.MergeWith(aNotification);
1839 break;
1840 case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
1841 mPendingCompositionUpdate.MergeWith(aNotification);
1842 break;
1843 default:
1844 MOZ_CRASH("Unsupported notification");
1845 break;
1849 void ContentCacheInParent::FlushPendingNotifications(nsIWidget* aWidget) {
1850 MOZ_ASSERT(!PendingEventsNeedingAck());
1852 // If the BrowserParent's widget has already gone, this can do nothing since
1853 // widget is necessary to notify IME of something.
1854 if (!aWidget) {
1855 return;
1858 // New notifications which are notified during flushing pending notifications
1859 // should be merged again.
1860 const bool pendingEventNeedingAckIncremented =
1861 !mHandlingCompositions.IsEmpty();
1862 if (pendingEventNeedingAckIncremented) {
1863 mHandlingCompositions.LastElement().mPendingEventsNeedingAck++;
1866 nsCOMPtr<nsIWidget> widget = aWidget;
1868 // First, text change notification should be sent because selection change
1869 // notification notifies IME of current selection range in the latest content.
1870 // So, IME may need the latest content before that.
1871 if (mPendingTextChange.HasNotification()) {
1872 IMENotification notification(mPendingTextChange);
1873 if (!widget->Destroyed()) {
1874 mPendingTextChange.Clear();
1875 IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
1879 if (mPendingSelectionChange.HasNotification()) {
1880 IMENotification notification(mPendingSelectionChange);
1881 if (!widget->Destroyed()) {
1882 mPendingSelectionChange.Clear();
1883 IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
1887 // Layout change notification should be notified after selection change
1888 // notification because IME may want to query position of new caret position.
1889 if (mPendingLayoutChange.HasNotification()) {
1890 IMENotification notification(mPendingLayoutChange);
1891 if (!widget->Destroyed()) {
1892 mPendingLayoutChange.Clear();
1893 IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
1897 // Finally, send composition update notification because it notifies IME of
1898 // finishing handling whole sending events.
1899 if (mPendingCompositionUpdate.HasNotification()) {
1900 IMENotification notification(mPendingCompositionUpdate);
1901 if (!widget->Destroyed()) {
1902 mPendingCompositionUpdate.Clear();
1903 IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
1907 // Decrement it which was incremented above.
1908 if (!mHandlingCompositions.IsEmpty() && pendingEventNeedingAckIncremented &&
1909 mHandlingCompositions.LastElement().mPendingEventsNeedingAck) {
1910 mHandlingCompositions.LastElement().mPendingEventsNeedingAck--;
1913 if (!PendingEventsNeedingAck() && !widget->Destroyed() &&
1914 (mPendingTextChange.HasNotification() ||
1915 mPendingSelectionChange.HasNotification() ||
1916 mPendingLayoutChange.HasNotification() ||
1917 mPendingCompositionUpdate.HasNotification())) {
1918 FlushPendingNotifications(widget);
1922 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1924 void ContentCacheInParent::RemoveUnnecessaryEventMessageLog() {
1925 bool foundLastCompositionStart = false;
1926 for (size_t i = mDispatchedEventMessages.Length(); i > 1; i--) {
1927 if (mDispatchedEventMessages[i - 1] != eCompositionStart) {
1928 continue;
1930 if (!foundLastCompositionStart) {
1931 // Find previous eCompositionStart of the latest eCompositionStart.
1932 foundLastCompositionStart = true;
1933 continue;
1935 // Remove the messages before the last 2 sets of composition events.
1936 mDispatchedEventMessages.RemoveElementsAt(0, i - 1);
1937 break;
1939 uint32_t numberOfCompositionCommitRequestHandled = 0;
1940 foundLastCompositionStart = false;
1941 for (size_t i = mReceivedEventMessages.Length(); i > 1; i--) {
1942 if (mReceivedEventMessages[i - 1] == eCompositionCommitRequestHandled) {
1943 numberOfCompositionCommitRequestHandled++;
1945 if (mReceivedEventMessages[i - 1] != eCompositionStart) {
1946 continue;
1948 if (!foundLastCompositionStart) {
1949 // Find previous eCompositionStart of the latest eCompositionStart.
1950 foundLastCompositionStart = true;
1951 continue;
1953 // Remove the messages before the last 2 sets of composition events.
1954 mReceivedEventMessages.RemoveElementsAt(0, i - 1);
1955 break;
1958 if (!numberOfCompositionCommitRequestHandled) {
1959 // If there is no eCompositionCommitRequestHandled in
1960 // mReceivedEventMessages, we don't need to store log of
1961 // RequestIMEToCommmitComposition().
1962 mRequestIMEToCommitCompositionResults.Clear();
1963 } else {
1964 // We need to keep all reason of eCompositionCommitRequestHandled, which
1965 // is sent when mRequestIMEToCommitComposition() returns true.
1966 // So, we can discard older log than the first
1967 // eCompositionCommitRequestHandled in mReceivedEventMessages.
1968 for (size_t i = mRequestIMEToCommitCompositionResults.Length(); i > 1;
1969 i--) {
1970 if (mRequestIMEToCommitCompositionResults[i - 1] ==
1971 RequestIMEToCommitCompositionResult::
1972 eReceivedAfterBrowserParentBlur ||
1973 mRequestIMEToCommitCompositionResults[i - 1] ==
1974 RequestIMEToCommitCompositionResult::eHandledSynchronously) {
1975 --numberOfCompositionCommitRequestHandled;
1976 if (!numberOfCompositionCommitRequestHandled) {
1977 mRequestIMEToCommitCompositionResults.RemoveElementsAt(0, i - 1);
1978 break;
1985 void ContentCacheInParent::AppendEventMessageLog(nsACString& aLog) const {
1986 aLog.AppendLiteral("Dispatched Event Message Log:\n");
1987 for (EventMessage message : mDispatchedEventMessages) {
1988 aLog.AppendLiteral(" ");
1989 aLog.Append(ToChar(message));
1990 aLog.AppendLiteral("\n");
1992 aLog.AppendLiteral("\nReceived Event Message Log:\n");
1993 for (EventMessage message : mReceivedEventMessages) {
1994 aLog.AppendLiteral(" ");
1995 aLog.Append(ToChar(message));
1996 aLog.AppendLiteral("\n");
1998 aLog.AppendLiteral("\nResult of RequestIMEToCommitComposition():\n");
1999 for (RequestIMEToCommitCompositionResult result :
2000 mRequestIMEToCommitCompositionResults) {
2001 aLog.AppendLiteral(" ");
2002 aLog.Append(ToReadableText(result));
2003 aLog.AppendLiteral("\n");
2005 aLog.AppendLiteral("\n");
2008 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
2010 /*****************************************************************************
2011 * mozilla::ContentCache::Selection
2012 *****************************************************************************/
2014 ContentCache::Selection::Selection(
2015 const WidgetQueryContentEvent& aQuerySelectedTextEvent)
2016 : mAnchor(UINT32_MAX),
2017 mFocus(UINT32_MAX),
2018 mWritingMode(aQuerySelectedTextEvent.mReply->WritingModeRef()),
2019 mHasRange(aQuerySelectedTextEvent.mReply->mOffsetAndData.isSome()) {
2020 MOZ_ASSERT(aQuerySelectedTextEvent.mMessage == eQuerySelectedText);
2021 MOZ_ASSERT(aQuerySelectedTextEvent.Succeeded());
2022 if (mHasRange) {
2023 mAnchor = aQuerySelectedTextEvent.mReply->AnchorOffset();
2024 mFocus = aQuerySelectedTextEvent.mReply->FocusOffset();
2028 /*****************************************************************************
2029 * mozilla::ContentCache::TextRectArray
2030 *****************************************************************************/
2032 LayoutDeviceIntRect ContentCache::TextRectArray::GetRect(
2033 uint32_t aOffset) const {
2034 LayoutDeviceIntRect rect;
2035 if (IsOffsetInRange(aOffset)) {
2036 rect = mRects[aOffset - mStart];
2038 return rect;
2041 LayoutDeviceIntRect ContentCache::TextRectArray::GetUnionRect(
2042 uint32_t aOffset, uint32_t aLength) const {
2043 LayoutDeviceIntRect rect;
2044 if (!IsRangeCompletelyInRange(aOffset, aLength)) {
2045 return rect;
2047 for (uint32_t i = 0; i < aLength; i++) {
2048 rect = rect.Union(mRects[aOffset - mStart + i]);
2050 return rect;
2053 LayoutDeviceIntRect ContentCache::TextRectArray::GetUnionRectAsFarAsPossible(
2054 uint32_t aOffset, uint32_t aLength, bool aRoundToExistingOffset) const {
2055 LayoutDeviceIntRect rect;
2056 if (!HasRects() ||
2057 (!aRoundToExistingOffset && !IsOverlappingWith(aOffset, aLength))) {
2058 return rect;
2060 uint32_t startOffset = std::max(aOffset, mStart);
2061 if (aRoundToExistingOffset && startOffset >= EndOffset()) {
2062 startOffset = EndOffset() - 1;
2064 uint32_t endOffset = std::min(aOffset + aLength, EndOffset());
2065 if (aRoundToExistingOffset && endOffset < mStart + 1) {
2066 endOffset = mStart + 1;
2068 if (NS_WARN_IF(endOffset < startOffset)) {
2069 return rect;
2071 for (uint32_t i = 0; i < endOffset - startOffset; i++) {
2072 rect = rect.Union(mRects[startOffset - mStart + i]);
2074 return rect;
2077 } // namespace mozilla