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