Bug 1731994: part 7) Update documentation of `nsIContentPermissionPrompt`. r=edgar...
[gecko.git] / widget / ContentCache.cpp
blob99f757bc3510c2f31e2a75907c6342c7bf86f4bd
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 "mozilla/ContentCache.h"
10 #include <utility>
12 #include "mozilla/IMEStateManager.h"
13 #include "mozilla/IntegerPrintfMacros.h"
14 #include "mozilla/Logging.h"
15 #include "mozilla/RefPtr.h"
16 #include "mozilla/TextComposition.h"
17 #include "mozilla/TextEvents.h"
18 #include "mozilla/dom/BrowserParent.h"
19 #include "nsExceptionHandler.h"
20 #include "nsIWidget.h"
22 namespace mozilla {
24 using namespace dom;
25 using namespace widget;
27 static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; }
29 static const char* GetNotificationName(const IMENotification* aNotification) {
30 if (!aNotification) {
31 return "Not notification";
33 return ToChar(aNotification->mMessage);
36 /*****************************************************************************
37 * mozilla::ContentCache
38 *****************************************************************************/
40 LazyLogModule sContentCacheLog("ContentCacheWidgets");
42 /*****************************************************************************
43 * mozilla::ContentCacheInChild
44 *****************************************************************************/
46 void ContentCacheInChild::Clear() {
47 MOZ_LOG(sContentCacheLog, LogLevel::Info, ("0x%p Clear()", this));
49 mCompositionStart.reset();
50 mLastCommit.reset();
51 mText.Truncate();
52 mSelection.reset();
53 mFirstCharRect.SetEmpty();
54 mCaret.reset();
55 mTextRectArray.reset();
56 mLastCommitStringTextRectArray.reset();
57 mEditorRect.SetEmpty();
60 void ContentCacheInChild::OnCompositionEvent(
61 const WidgetCompositionEvent& aCompositionEvent) {
62 if (aCompositionEvent.CausesDOMCompositionEndEvent()) {
63 RefPtr<TextComposition> composition =
64 IMEStateManager::GetTextCompositionFor(aCompositionEvent.mWidget);
65 if (composition) {
66 nsAutoString lastCommitString;
67 if (aCompositionEvent.mMessage == eCompositionCommitAsIs) {
68 lastCommitString = composition->CommitStringIfCommittedAsIs();
69 } else {
70 lastCommitString = aCompositionEvent.mData;
72 // We don't need to store canceling information because this is required
73 // by undoing of last commit (Kakutei-Undo of Japanese IME).
74 if (!lastCommitString.IsEmpty()) {
75 mLastCommit = Some(OffsetAndData<uint32_t>(
76 composition->NativeOffsetOfStartComposition(), lastCommitString));
77 MOZ_LOG(
78 sContentCacheLog, LogLevel::Debug,
79 ("0x%p OnCompositionEvent(), stored last composition string data "
80 "(aCompositionEvent={ mMessage=%s, mData=\"%s\"}, mLastCommit=%s)",
81 this, ToChar(aCompositionEvent.mMessage),
82 PrintStringDetail(
83 aCompositionEvent.mData,
84 PrintStringDetail::kMaxLengthForCompositionString)
85 .get(),
86 ToString(mLastCommit).c_str()));
87 return;
91 if (mLastCommit.isSome()) {
92 MOZ_LOG(
93 sContentCacheLog, LogLevel::Debug,
94 ("0x%p OnCompositionEvent(), resetting the last composition string "
95 "data (aCompositionEvent={ mMessage=%s, mData=\"%s\"}, "
96 "mLastCommit=%s)",
97 this, ToChar(aCompositionEvent.mMessage),
98 PrintStringDetail(aCompositionEvent.mData,
99 PrintStringDetail::kMaxLengthForCompositionString)
100 .get(),
101 ToString(mLastCommit).c_str()));
102 mLastCommit.reset();
106 bool ContentCacheInChild::CacheAll(nsIWidget* aWidget,
107 const IMENotification* aNotification) {
108 MOZ_LOG(sContentCacheLog, LogLevel::Info,
109 ("0x%p CacheAll(aWidget=0x%p, aNotification=%s)", this, aWidget,
110 GetNotificationName(aNotification)));
112 if (NS_WARN_IF(!CacheText(aWidget, aNotification)) ||
113 NS_WARN_IF(!CacheEditorRect(aWidget, aNotification))) {
114 return false;
116 return true;
119 bool ContentCacheInChild::CacheSelection(nsIWidget* aWidget,
120 const IMENotification* aNotification) {
121 MOZ_LOG(sContentCacheLog, LogLevel::Info,
122 ("0x%p CacheSelection(aWidget=0x%p, aNotification=%s)", this, aWidget,
123 GetNotificationName(aNotification)));
125 mCaret.reset();
126 mSelection.reset();
128 nsEventStatus status = nsEventStatus_eIgnore;
129 WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
130 aWidget);
131 aWidget->DispatchEvent(&querySelectedTextEvent, status);
132 if (NS_WARN_IF(querySelectedTextEvent.DidNotFindSelection())) {
133 MOZ_LOG(
134 sContentCacheLog, LogLevel::Error,
135 ("0x%p CacheSelection(), FAILED, couldn't retrieve the selected text",
136 this));
137 return false;
139 MOZ_ASSERT(querySelectedTextEvent.mReply->mOffsetAndData.isSome());
140 if (querySelectedTextEvent.mReply->mReversed) {
141 mSelection.emplace(querySelectedTextEvent.mReply->EndOffset(),
142 querySelectedTextEvent.mReply->StartOffset(),
143 querySelectedTextEvent.mReply->WritingModeRef());
144 } else {
145 mSelection.emplace(querySelectedTextEvent.mReply->StartOffset(),
146 querySelectedTextEvent.mReply->EndOffset(),
147 querySelectedTextEvent.mReply->WritingModeRef());
150 return CacheCaret(aWidget, aNotification) &&
151 CacheTextRects(aWidget, aNotification);
154 bool ContentCacheInChild::CacheCaret(nsIWidget* aWidget,
155 const IMENotification* aNotification) {
156 MOZ_LOG(sContentCacheLog, LogLevel::Info,
157 ("0x%p CacheCaret(aWidget=0x%p, aNotification=%s)", this, aWidget,
158 GetNotificationName(aNotification)));
160 mCaret.reset();
162 if (NS_WARN_IF(mSelection.isNothing())) {
163 return false;
166 // XXX Should be mSelection.mFocus?
167 uint32_t offset = mSelection->StartOffset();
169 nsEventStatus status = nsEventStatus_eIgnore;
170 WidgetQueryContentEvent queryCaretRectEvet(true, eQueryCaretRect, aWidget);
171 queryCaretRectEvet.InitForQueryCaretRect(offset);
172 aWidget->DispatchEvent(&queryCaretRectEvet, status);
173 if (NS_WARN_IF(queryCaretRectEvet.Failed())) {
174 MOZ_LOG(sContentCacheLog, LogLevel::Error,
175 ("0x%p CacheCaret(), FAILED, couldn't retrieve the caret rect at "
176 "offset=%u",
177 this, offset));
178 return false;
180 mCaret.emplace(offset, queryCaretRectEvet.mReply->mRect);
181 MOZ_LOG(sContentCacheLog, LogLevel::Info,
182 ("0x%p CacheCaret(), Succeeded, "
183 "mSelection=%s, mCaret=%s",
184 this, ToString(mSelection).c_str(), ToString(mCaret).c_str()));
185 return true;
188 bool ContentCacheInChild::CacheEditorRect(
189 nsIWidget* aWidget, const IMENotification* aNotification) {
190 MOZ_LOG(sContentCacheLog, LogLevel::Info,
191 ("0x%p CacheEditorRect(aWidget=0x%p, aNotification=%s)", this,
192 aWidget, GetNotificationName(aNotification)));
194 nsEventStatus status = nsEventStatus_eIgnore;
195 WidgetQueryContentEvent queryEditorRectEvent(true, eQueryEditorRect, aWidget);
196 aWidget->DispatchEvent(&queryEditorRectEvent, status);
197 if (NS_WARN_IF(queryEditorRectEvent.Failed())) {
198 MOZ_LOG(sContentCacheLog, LogLevel::Error,
199 ("0x%p CacheEditorRect(), FAILED, "
200 "couldn't retrieve the editor rect",
201 this));
202 return false;
204 mEditorRect = queryEditorRectEvent.mReply->mRect;
205 MOZ_LOG(sContentCacheLog, LogLevel::Info,
206 ("0x%p CacheEditorRect(), Succeeded, "
207 "mEditorRect=%s",
208 this, ToString(mEditorRect).c_str()));
209 return true;
212 bool ContentCacheInChild::CacheText(nsIWidget* aWidget,
213 const IMENotification* aNotification) {
214 MOZ_LOG(sContentCacheLog, LogLevel::Info,
215 ("0x%p CacheText(aWidget=0x%p, aNotification=%s)", this, aWidget,
216 GetNotificationName(aNotification)));
218 nsEventStatus status = nsEventStatus_eIgnore;
219 WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
220 aWidget);
221 queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
222 aWidget->DispatchEvent(&queryTextContentEvent, status);
223 if (NS_WARN_IF(queryTextContentEvent.Failed())) {
224 MOZ_LOG(sContentCacheLog, LogLevel::Error,
225 ("0x%p CacheText(), FAILED, couldn't retrieve whole text", this));
226 mText.Truncate();
227 return false;
229 mText = queryTextContentEvent.mReply->DataRef();
230 MOZ_LOG(
231 sContentCacheLog, LogLevel::Info,
232 ("0x%p CacheText(), Succeeded, mText.Length()=%u", this, mText.Length()));
234 // Forget last commit range if string in the range is different from the
235 // last commit string.
236 if (mLastCommit.isSome() &&
237 nsDependentSubstring(mText, mLastCommit->StartOffset(),
238 mLastCommit->Length()) != mLastCommit->DataRef()) {
239 MOZ_LOG(sContentCacheLog, LogLevel::Debug,
240 ("0x%p CacheText(), resetting the last composition string data "
241 "(mLastCommit=%s, current string=\"%s\")",
242 this, ToString(mLastCommit).c_str(),
243 PrintStringDetail(
244 nsDependentSubstring(mText, mLastCommit->StartOffset(),
245 mLastCommit->Length()),
246 PrintStringDetail::kMaxLengthForCompositionString)
247 .get()));
248 mLastCommit.reset();
251 return CacheSelection(aWidget, aNotification);
254 bool ContentCacheInChild::QueryCharRect(nsIWidget* aWidget, uint32_t aOffset,
255 LayoutDeviceIntRect& aCharRect) const {
256 aCharRect.SetEmpty();
258 nsEventStatus status = nsEventStatus_eIgnore;
259 WidgetQueryContentEvent queryTextRectEvent(true, eQueryTextRect, aWidget);
260 queryTextRectEvent.InitForQueryTextRect(aOffset, 1);
261 aWidget->DispatchEvent(&queryTextRectEvent, status);
262 if (NS_WARN_IF(queryTextRectEvent.Failed())) {
263 return false;
265 aCharRect = queryTextRectEvent.mReply->mRect;
267 // Guarantee the rect is not empty.
268 if (NS_WARN_IF(!aCharRect.Height())) {
269 aCharRect.SetHeight(1);
271 if (NS_WARN_IF(!aCharRect.Width())) {
272 aCharRect.SetWidth(1);
274 return true;
277 bool ContentCacheInChild::QueryCharRectArray(nsIWidget* aWidget,
278 uint32_t aOffset, uint32_t aLength,
279 RectArray& aCharRectArray) const {
280 nsEventStatus status = nsEventStatus_eIgnore;
281 WidgetQueryContentEvent queryTextRectsEvent(true, eQueryTextRectArray,
282 aWidget);
283 queryTextRectsEvent.InitForQueryTextRectArray(aOffset, aLength);
284 aWidget->DispatchEvent(&queryTextRectsEvent, status);
285 if (NS_WARN_IF(queryTextRectsEvent.Failed())) {
286 aCharRectArray.Clear();
287 return false;
289 aCharRectArray = std::move(queryTextRectsEvent.mReply->mRectArray);
290 return true;
293 bool ContentCacheInChild::CacheTextRects(nsIWidget* aWidget,
294 const IMENotification* aNotification) {
295 MOZ_LOG(
296 sContentCacheLog, LogLevel::Info,
297 ("0x%p CacheTextRects(aWidget=0x%p, aNotification=%s), mCaret=%s", this,
298 aWidget, GetNotificationName(aNotification), ToString(mCaret).c_str()));
300 mCompositionStart.reset();
301 mTextRectArray.reset();
302 mLastCommitStringTextRectArray.reset();
303 mFirstCharRect.SetEmpty();
305 if (NS_WARN_IF(mSelection.isNothing())) {
306 return false;
309 mSelection->ClearRects();
311 // Retrieve text rects in composition string if there is.
312 RefPtr<TextComposition> textComposition =
313 IMEStateManager::GetTextCompositionFor(aWidget);
314 if (textComposition) {
315 // mCompositionStart may be updated by some composition event handlers.
316 // So, let's update it with the latest information.
317 mCompositionStart = Some(textComposition->NativeOffsetOfStartComposition());
318 // Note that TextComposition::String() may not be modified here because
319 // it's modified after all edit action listeners are performed but this
320 // is called while some of them are performed.
321 // FYI: For supporting IME which commits composition and restart new
322 // composition immediately, we should cache next character of current
323 // composition too.
324 uint32_t length = textComposition->LastData().Length() + 1;
325 mTextRectArray.emplace(mCompositionStart.value());
326 if (NS_WARN_IF(!QueryCharRectArray(aWidget, mTextRectArray->mStart, length,
327 mTextRectArray->mRects))) {
328 MOZ_LOG(sContentCacheLog, LogLevel::Error,
329 ("0x%p CacheTextRects(), FAILED, "
330 "couldn't retrieve text rect array of the composition string",
331 this));
332 mTextRectArray.reset();
336 if (mTextRectArray.isSome() &&
337 mTextRectArray->IsOffsetInRange(mSelection->mAnchor) &&
338 (!mSelection->mAnchor ||
339 mTextRectArray->IsOffsetInRange(mSelection->mAnchor - 1))) {
340 mSelection->mAnchorCharRects[eNextCharRect] =
341 mTextRectArray->GetRect(mSelection->mAnchor);
342 if (mSelection->mAnchor) {
343 mSelection->mAnchorCharRects[ePrevCharRect] =
344 mTextRectArray->GetRect(mSelection->mAnchor - 1);
346 } else {
347 RectArray rects;
348 uint32_t startOffset = mSelection->mAnchor ? mSelection->mAnchor - 1 : 0;
349 uint32_t length = mSelection->mAnchor ? 2 : 1;
350 if (NS_WARN_IF(!QueryCharRectArray(aWidget, startOffset, length, rects))) {
351 MOZ_LOG(
352 sContentCacheLog, LogLevel::Error,
353 ("0x%p CacheTextRects(), FAILED, "
354 "couldn't retrieve text rect array around the selection anchor (%u)",
355 this, mSelection->mAnchor));
356 MOZ_ASSERT(mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty());
357 MOZ_ASSERT(mSelection->mAnchorCharRects[eNextCharRect].IsEmpty());
358 } else {
359 if (rects.Length() > 1) {
360 mSelection->mAnchorCharRects[ePrevCharRect] = rects[0];
361 mSelection->mAnchorCharRects[eNextCharRect] = rects[1];
362 } else if (rects.Length()) {
363 mSelection->mAnchorCharRects[eNextCharRect] = rects[0];
364 MOZ_ASSERT(mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty());
369 if (mSelection->Collapsed()) {
370 mSelection->mFocusCharRects[0] = mSelection->mAnchorCharRects[0];
371 mSelection->mFocusCharRects[1] = mSelection->mAnchorCharRects[1];
372 } else if (mTextRectArray.isSome() &&
373 mTextRectArray->IsOffsetInRange(mSelection->mFocus) &&
374 (!mSelection->mFocus ||
375 mTextRectArray->IsOffsetInRange(mSelection->mFocus - 1))) {
376 mSelection->mFocusCharRects[eNextCharRect] =
377 mTextRectArray->GetRect(mSelection->mFocus);
378 if (mSelection->mFocus) {
379 mSelection->mFocusCharRects[ePrevCharRect] =
380 mTextRectArray->GetRect(mSelection->mFocus - 1);
382 } else {
383 RectArray rects;
384 uint32_t startOffset = mSelection->mFocus ? mSelection->mFocus - 1 : 0;
385 uint32_t length = mSelection->mFocus ? 2 : 1;
386 if (NS_WARN_IF(!QueryCharRectArray(aWidget, startOffset, length, rects))) {
387 MOZ_LOG(
388 sContentCacheLog, LogLevel::Error,
389 ("0x%p CacheTextRects(), FAILED, "
390 "couldn't retrieve text rect array around the selection focus (%u)",
391 this, mSelection->mFocus));
392 MOZ_ASSERT(mSelection->mFocusCharRects[ePrevCharRect].IsEmpty());
393 MOZ_ASSERT(mSelection->mFocusCharRects[eNextCharRect].IsEmpty());
394 } else {
395 if (rects.Length() > 1) {
396 mSelection->mFocusCharRects[ePrevCharRect] = rects[0];
397 mSelection->mFocusCharRects[eNextCharRect] = rects[1];
398 } else if (rects.Length()) {
399 mSelection->mFocusCharRects[eNextCharRect] = rects[0];
400 MOZ_ASSERT(mSelection->mFocusCharRects[ePrevCharRect].IsEmpty());
405 if (!mSelection->Collapsed()) {
406 nsEventStatus status = nsEventStatus_eIgnore;
407 WidgetQueryContentEvent queryTextRectEvent(true, eQueryTextRect, aWidget);
408 queryTextRectEvent.InitForQueryTextRect(mSelection->StartOffset(),
409 mSelection->Length());
410 aWidget->DispatchEvent(&queryTextRectEvent, status);
411 if (NS_WARN_IF(queryTextRectEvent.Failed())) {
412 MOZ_LOG(sContentCacheLog, LogLevel::Error,
413 ("0x%p CacheTextRects(), FAILED, "
414 "couldn't retrieve text rect of whole selected text",
415 this));
416 } else {
417 mSelection->mRect = queryTextRectEvent.mReply->mRect;
421 if (!mSelection->mFocus) {
422 mFirstCharRect = mSelection->mFocusCharRects[eNextCharRect];
423 } else if (mSelection->mFocus == 1) {
424 mFirstCharRect = mSelection->mFocusCharRects[ePrevCharRect];
425 } else if (!mSelection->mAnchor) {
426 mFirstCharRect = mSelection->mAnchorCharRects[eNextCharRect];
427 } else if (mSelection->mAnchor == 1) {
428 mFirstCharRect = mSelection->mFocusCharRects[ePrevCharRect];
429 } else if (mTextRectArray.isSome() && mTextRectArray->IsOffsetInRange(0)) {
430 mFirstCharRect = mTextRectArray->GetRect(0);
431 } else {
432 LayoutDeviceIntRect charRect;
433 if (NS_WARN_IF(!QueryCharRect(aWidget, 0, charRect))) {
434 MOZ_LOG(sContentCacheLog, LogLevel::Error,
435 ("0x%p CacheTextRects(), FAILED, "
436 "couldn't retrieve first char rect",
437 this));
438 } else {
439 mFirstCharRect = charRect;
443 if (mLastCommit.isSome()) {
444 mLastCommitStringTextRectArray.emplace(mLastCommit->StartOffset());
445 if (mLastCommit->Length() == 1) {
446 MOZ_ASSERT(mSelection->Collapsed());
447 MOZ_ASSERT(mSelection->mAnchor - 1 == mLastCommit->StartOffset());
448 mLastCommitStringTextRectArray->mRects.AppendElement(
449 mSelection->mAnchorCharRects[ePrevCharRect]);
450 } else if (NS_WARN_IF(!QueryCharRectArray(
451 aWidget, mLastCommit->StartOffset(), mLastCommit->Length(),
452 mLastCommitStringTextRectArray->mRects))) {
453 MOZ_LOG(sContentCacheLog, LogLevel::Error,
454 ("0x%p CacheTextRects(), FAILED, "
455 "couldn't retrieve text rect array of the last commit string",
456 this));
457 mLastCommitStringTextRectArray.reset();
458 mLastCommit.reset();
460 MOZ_ASSERT((mLastCommitStringTextRectArray.isSome()
461 ? mLastCommitStringTextRectArray->mRects.Length()
462 : 0) == (mLastCommit.isSome() ? mLastCommit->Length() : 0));
465 MOZ_LOG(sContentCacheLog, LogLevel::Info,
466 ("0x%p CacheTextRects(), Succeeded, "
467 "mText.Length()=%x, mTextRectArray=%s, mSelection=%s, "
468 "mFirstCharRect=%s, mLastCommitStringTextRectArray=%s",
469 this, mText.Length(), ToString(mTextRectArray).c_str(),
470 ToString(mSelection).c_str(), ToString(mFirstCharRect).c_str(),
471 ToString(mLastCommitStringTextRectArray).c_str()));
472 return true;
475 void ContentCacheInChild::SetSelection(nsIWidget* aWidget,
476 uint32_t aStartOffset, uint32_t aLength,
477 bool aReversed,
478 const WritingMode& aWritingMode) {
479 MOZ_LOG(sContentCacheLog, LogLevel::Info,
480 ("0x%p SetSelection(aStartOffset=%u, "
481 "aLength=%u, aReversed=%s, aWritingMode=%s), mText.Length()=%u",
482 this, aStartOffset, aLength, GetBoolName(aReversed),
483 ToString(aWritingMode).c_str(), mText.Length()));
485 mSelection = Some(Selection(
486 !aReversed ? aStartOffset : aStartOffset + aLength,
487 !aReversed ? aStartOffset + aLength : aStartOffset, aWritingMode));
489 if (mLastCommit.isSome()) {
490 // Forget last commit string range if selection is not collapsed
491 // at end of the last commit string.
492 if (!mSelection->Collapsed() ||
493 mSelection->mAnchor != mLastCommit->EndOffset()) {
494 MOZ_LOG(
495 sContentCacheLog, LogLevel::Debug,
496 ("0x%p SetSelection(), forgetting last commit composition data "
497 "(mSelection=%s, mLastCommit=%s)",
498 this, ToString(mSelection).c_str(), ToString(mLastCommit).c_str()));
499 mLastCommit.reset();
503 if (NS_WARN_IF(!CacheCaret(aWidget))) {
504 return;
506 Unused << NS_WARN_IF(!CacheTextRects(aWidget));
509 /*****************************************************************************
510 * mozilla::ContentCacheInParent
511 *****************************************************************************/
513 ContentCacheInParent::ContentCacheInParent(BrowserParent& aBrowserParent)
514 : ContentCache(),
515 mBrowserParent(aBrowserParent),
516 mCommitStringByRequest(nullptr),
517 mPendingEventsNeedingAck(0),
518 mPendingCommitLength(0),
519 mPendingCompositionCount(0),
520 mPendingCommitCount(0),
521 mWidgetHasComposition(false),
522 mIsChildIgnoringCompositionEvents(false) {}
524 void ContentCacheInParent::AssignContent(const ContentCache& aOther,
525 nsIWidget* aWidget,
526 const IMENotification* aNotification) {
527 mText = aOther.mText;
528 mSelection = aOther.mSelection;
529 mFirstCharRect = aOther.mFirstCharRect;
530 mCaret = aOther.mCaret;
531 mTextRectArray = aOther.mTextRectArray;
532 mLastCommitStringTextRectArray = aOther.mLastCommitStringTextRectArray;
533 mEditorRect = aOther.mEditorRect;
535 // Only when there is one composition, the TextComposition instance in this
536 // process is managing the composition in the remote process. Therefore,
537 // we shouldn't update composition start offset of TextComposition with
538 // old composition which is still being handled by the child process.
539 if (mWidgetHasComposition && mPendingCompositionCount == 1 &&
540 mCompositionStart.isSome()) {
541 IMEStateManager::MaybeStartOffsetUpdatedInChild(aWidget,
542 mCompositionStart.value());
545 // When this instance allows to query content relative to composition string,
546 // we should modify mCompositionStart with the latest information in the
547 // remote process because now we have the information around the composition
548 // string.
549 mCompositionStartInChild = aOther.mCompositionStart;
550 if (mWidgetHasComposition || mPendingCommitCount) {
551 if (mCompositionStartInChild.isSome()) {
552 if (mCompositionStart.valueOr(UINT32_MAX) !=
553 mCompositionStartInChild.value()) {
554 mCompositionStart = mCompositionStartInChild;
555 mPendingCommitLength = 0;
557 } else if (mCompositionStart.isSome() && mSelection.isSome() &&
558 mCompositionStart.value() != mSelection->StartOffset()) {
559 mCompositionStart = Some(mSelection->StartOffset());
560 mPendingCommitLength = 0;
564 MOZ_LOG(sContentCacheLog, LogLevel::Info,
565 ("0x%p AssignContent(aNotification=%s), "
566 "Succeeded, mText.Length()=%u, mSelection=%s, mFirstCharRect=%s, "
567 "mCaret=%s, mTextRectArray=%s, mWidgetHasComposition=%s, "
568 "mPendingCompositionCount=%u, mCompositionStart=%s, "
569 "mPendingCommitLength=%u, mEditorRect=%s, "
570 "mLastCommitStringTextRectArray=%s",
571 this, GetNotificationName(aNotification), mText.Length(),
572 ToString(mSelection).c_str(), ToString(mFirstCharRect).c_str(),
573 ToString(mCaret).c_str(), ToString(mTextRectArray).c_str(),
574 GetBoolName(mWidgetHasComposition), mPendingCompositionCount,
575 ToString(mCompositionStart).c_str(), mPendingCommitLength,
576 ToString(mEditorRect).c_str(),
577 ToString(mLastCommitStringTextRectArray).c_str()));
580 bool ContentCacheInParent::HandleQueryContentEvent(
581 WidgetQueryContentEvent& aEvent, nsIWidget* aWidget) const {
582 MOZ_ASSERT(aWidget);
584 // ContentCache doesn't store offset of its start with XP linebreaks.
585 // So, we don't support to query contents relative to composition start
586 // offset with XP linebreaks.
587 if (NS_WARN_IF(!aEvent.mUseNativeLineBreak)) {
588 MOZ_LOG(sContentCacheLog, LogLevel::Error,
589 ("0x%p HandleQueryContentEvent(), FAILED due to query with XP "
590 "linebreaks",
591 this));
592 return false;
595 if (NS_WARN_IF(!aEvent.mInput.IsValidOffset())) {
596 MOZ_LOG(
597 sContentCacheLog, LogLevel::Error,
598 ("0x%p HandleQueryContentEvent(), FAILED due to invalid offset", this));
599 return false;
602 if (NS_WARN_IF(!aEvent.mInput.IsValidEventMessage(aEvent.mMessage))) {
603 MOZ_LOG(
604 sContentCacheLog, LogLevel::Error,
605 ("0x%p HandleQueryContentEvent(), FAILED due to invalid event message",
606 this));
607 return false;
610 bool isRelativeToInsertionPoint = aEvent.mInput.mRelativeToInsertionPoint;
611 if (isRelativeToInsertionPoint) {
612 MOZ_LOG(sContentCacheLog, LogLevel::Debug,
613 ("0x%p HandleQueryContentEvent(), "
614 "making offset absolute... aEvent={ mMessage=%s, mInput={ "
615 "mOffset=%" PRId64 ", mLength=%" PRIu32 " } }, "
616 "mWidgetHasComposition=%s, mPendingCommitCount=%" PRIu8
617 ", mCompositionStart=%" PRIu32 ", "
618 "mPendingCommitLength=%" PRIu32 ", mSelection=%s",
619 this, ToChar(aEvent.mMessage), aEvent.mInput.mOffset,
620 aEvent.mInput.mLength, GetBoolName(mWidgetHasComposition),
621 mPendingCommitCount, mCompositionStart.valueOr(UINT32_MAX),
622 mPendingCommitLength, ToString(mSelection).c_str()));
623 if (mWidgetHasComposition || mPendingCommitCount) {
624 if (NS_WARN_IF(mCompositionStart.isNothing()) ||
625 NS_WARN_IF(!aEvent.mInput.MakeOffsetAbsolute(
626 mCompositionStart.value() + mPendingCommitLength))) {
627 MOZ_LOG(
628 sContentCacheLog, LogLevel::Error,
629 ("0x%p HandleQueryContentEvent(), FAILED due to "
630 "aEvent.mInput.MakeOffsetAbsolute(mCompositionStart + "
631 "mPendingCommitLength) failure, "
632 "mCompositionStart=%" PRIu32 ", mPendingCommitLength=%" PRIu32 ", "
633 "aEvent={ mMessage=%s, mInput={ mOffset=%" PRId64
634 ", mLength=%" PRIu32 " } }",
635 this, mCompositionStart.valueOr(UINT32_MAX), mPendingCommitLength,
636 ToChar(aEvent.mMessage), aEvent.mInput.mOffset,
637 aEvent.mInput.mLength));
638 return false;
640 } else if (NS_WARN_IF(mSelection.isNothing())) {
641 MOZ_LOG(sContentCacheLog, LogLevel::Error,
642 ("0x%p HandleQueryContentEvent(), FAILED due to mSelection is "
643 "not set",
644 this));
645 return false;
646 } else if (NS_WARN_IF(!aEvent.mInput.MakeOffsetAbsolute(
647 mSelection->StartOffset() + mPendingCommitLength))) {
648 MOZ_LOG(sContentCacheLog, LogLevel::Error,
649 ("0x%p HandleQueryContentEvent(), FAILED due to "
650 "aEvent.mInput.MakeOffsetAbsolute(mSelection.StartOffset() + "
651 "mPendingCommitLength) failure, mSelection=%s, "
652 "mPendingCommitLength=%" PRIu32 ", aEvent={ mMessage=%s, "
653 "mInput={ mOffset=%" PRId64 ", mLength=%" PRIu32 " } }",
654 this, ToString(mSelection).c_str(), mPendingCommitLength,
655 ToChar(aEvent.mMessage), aEvent.mInput.mOffset,
656 aEvent.mInput.mLength));
657 return false;
661 switch (aEvent.mMessage) {
662 case eQuerySelectedText:
663 MOZ_LOG(sContentCacheLog, LogLevel::Info,
664 ("0x%p HandleQueryContentEvent(aEvent={ "
665 "mMessage=eQuerySelectedText }, aWidget=0x%p)",
666 this, aWidget));
667 if (NS_WARN_IF(!IsSelectionValid())) {
668 // If content cache hasn't been initialized properly, make the query
669 // failed.
670 MOZ_LOG(sContentCacheLog, LogLevel::Error,
671 ("0x%p HandleQueryContentEvent(), FAILED because mSelection is "
672 "not valid",
673 this));
674 return false;
676 if (!mSelection->Collapsed() &&
677 NS_WARN_IF(mSelection->EndOffset() > mText.Length())) {
678 MOZ_LOG(sContentCacheLog, LogLevel::Error,
679 ("0x%p HandleQueryContentEvent(), FAILED because "
680 "mSelection->EndOffset()=%u is larger than mText.Length()=%u",
681 this, mSelection->EndOffset(), mText.Length()));
682 return false;
684 aEvent.EmplaceReply();
685 aEvent.mReply->mFocusedWidget = aWidget;
686 aEvent.mReply->mOffsetAndData.emplace(
687 mSelection->StartOffset(),
688 Substring(mText, mSelection->StartOffset(), mSelection->Length()),
689 OffsetAndDataFor::SelectedString);
690 aEvent.mReply->mReversed = mSelection->Reversed();
691 aEvent.mReply->mHasSelection = true;
692 aEvent.mReply->mWritingMode = mSelection->mWritingMode;
693 MOZ_LOG(sContentCacheLog, LogLevel::Info,
694 ("0x%p HandleQueryContentEvent(), "
695 "Succeeded, aEvent={ mMessage=eQuerySelectedText, mReply=%s }",
696 this, ToString(aEvent.mReply).c_str()));
697 return true;
698 case eQueryTextContent: {
699 MOZ_LOG(sContentCacheLog, LogLevel::Info,
700 ("0x%p HandleQueryContentEvent("
701 "aEvent={ mMessage=eQueryTextContent, mInput={ mOffset=%" PRId64
702 ", mLength=%u } }, aWidget=0x%p), mText.Length()=%u",
703 this, aEvent.mInput.mOffset, aEvent.mInput.mLength, aWidget,
704 mText.Length()));
705 uint32_t inputOffset = aEvent.mInput.mOffset;
706 uint32_t inputEndOffset =
707 std::min(aEvent.mInput.EndOffset(), mText.Length());
708 if (NS_WARN_IF(inputEndOffset < inputOffset)) {
709 MOZ_LOG(sContentCacheLog, LogLevel::Error,
710 ("0x%p HandleQueryContentEvent(), FAILED because "
711 "inputOffset=%u is larger than inputEndOffset=%u",
712 this, inputOffset, inputEndOffset));
713 return false;
715 aEvent.EmplaceReply();
716 aEvent.mReply->mFocusedWidget = aWidget;
717 aEvent.mReply->mOffsetAndData.emplace(
718 inputOffset,
719 Substring(mText, inputOffset, inputEndOffset - inputOffset),
720 OffsetAndDataFor::EditorString);
721 // TODO: Support font ranges
722 MOZ_LOG(sContentCacheLog, LogLevel::Info,
723 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
724 "mMessage=eQueryTextContent, mReply=%s }",
725 this, ToString(aEvent.mReply).c_str()));
726 return true;
728 case eQueryTextRect: {
729 MOZ_LOG(sContentCacheLog, LogLevel::Info,
730 ("0x%p HandleQueryContentEvent("
731 "aEvent={ mMessage=eQueryTextRect, mInput={ mOffset=%" PRId64
732 ", mLength=%u } }, aWidget=0x%p), mText.Length()=%u",
733 this, aEvent.mInput.mOffset, aEvent.mInput.mLength, aWidget,
734 mText.Length()));
735 if (NS_WARN_IF(!IsSelectionValid())) {
736 // If content cache hasn't been initialized properly, make the query
737 // failed.
738 MOZ_LOG(sContentCacheLog, LogLevel::Error,
739 ("0x%p HandleQueryContentEvent(), FAILED because mSelection is "
740 "not valid",
741 this));
742 return false;
744 // Note that if the query is relative to insertion point, the query was
745 // probably requested by native IME. In such case, we should return
746 // non-empty rect since returning failure causes IME showing its window
747 // at odd position.
748 LayoutDeviceIntRect textRect;
749 if (aEvent.mInput.mLength) {
750 if (NS_WARN_IF(
751 !GetUnionTextRects(aEvent.mInput.mOffset, aEvent.mInput.mLength,
752 isRelativeToInsertionPoint, textRect))) {
753 // XXX We don't have cache for this request.
754 MOZ_LOG(sContentCacheLog, LogLevel::Error,
755 ("0x%p HandleQueryContentEvent(), FAILED to get union rect",
756 this));
757 return false;
759 } else {
760 // If the length is 0, we should return caret rect instead.
761 if (NS_WARN_IF(!GetCaretRect(aEvent.mInput.mOffset,
762 isRelativeToInsertionPoint, textRect))) {
763 MOZ_LOG(sContentCacheLog, LogLevel::Error,
764 ("0x%p HandleQueryContentEvent(), FAILED to get caret rect",
765 this));
766 return false;
769 aEvent.EmplaceReply();
770 aEvent.mReply->mFocusedWidget = aWidget;
771 aEvent.mReply->mRect = textRect;
772 aEvent.mReply->mOffsetAndData.emplace(
773 aEvent.mInput.mOffset,
774 aEvent.mInput.mOffset < mText.Length()
775 ? static_cast<const nsAString&>(
776 Substring(mText, aEvent.mInput.mOffset,
777 mText.Length() >= aEvent.mInput.EndOffset()
778 ? aEvent.mInput.mLength
779 : UINT32_MAX))
780 : static_cast<const nsAString&>(EmptyString()),
781 OffsetAndDataFor::EditorString);
782 // XXX This may be wrong if storing range isn't in the selection range.
783 aEvent.mReply->mWritingMode = mSelection->mWritingMode;
784 MOZ_LOG(sContentCacheLog, LogLevel::Info,
785 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
786 "mMessage=eQueryTextRect mReply=%s }",
787 this, ToString(aEvent.mReply).c_str()));
788 return true;
790 case eQueryCaretRect: {
791 MOZ_LOG(
792 sContentCacheLog, LogLevel::Info,
793 ("0x%p HandleQueryContentEvent(aEvent={ mMessage=eQueryCaretRect, "
794 "mInput={ mOffset=%" PRId64 " } }, aWidget=0x%p), mText.Length()=%u",
795 this, aEvent.mInput.mOffset, aWidget, mText.Length()));
796 if (NS_WARN_IF(!IsSelectionValid())) {
797 // If content cache hasn't been initialized properly, make the query
798 // failed.
799 MOZ_LOG(sContentCacheLog, LogLevel::Error,
800 ("0x%p HandleQueryContentEvent(), FAILED because mSelection is "
801 "not valid",
802 this));
803 return false;
805 // Note that if the query is relative to insertion point, the query was
806 // probably requested by native IME. In such case, we should return
807 // non-empty rect since returning failure causes IME showing its window
808 // at odd position.
809 LayoutDeviceIntRect caretRect;
810 if (NS_WARN_IF(!GetCaretRect(aEvent.mInput.mOffset,
811 isRelativeToInsertionPoint, caretRect))) {
812 MOZ_LOG(
813 sContentCacheLog, LogLevel::Error,
814 ("0x%p HandleQueryContentEvent(),FAILED to get caret rect", this));
815 return false;
817 aEvent.EmplaceReply();
818 aEvent.mReply->mFocusedWidget = aWidget;
819 aEvent.mReply->mRect = caretRect;
820 aEvent.mReply->mOffsetAndData.emplace(aEvent.mInput.mOffset,
821 EmptyString(),
822 OffsetAndDataFor::SelectedString);
823 // TODO: Set mWritingMode here
824 MOZ_LOG(sContentCacheLog, LogLevel::Info,
825 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
826 "mMessage=eQueryCaretRect, mReply=%s }",
827 this, ToString(aEvent.mReply).c_str()));
828 return true;
830 case eQueryEditorRect:
831 MOZ_LOG(sContentCacheLog, LogLevel::Info,
832 ("0x%p HandleQueryContentEvent(aEvent={ "
833 "mMessage=eQueryEditorRect }, aWidget=0x%p)",
834 this, aWidget));
835 aEvent.EmplaceReply();
836 aEvent.mReply->mFocusedWidget = aWidget;
837 aEvent.mReply->mRect = mEditorRect;
838 MOZ_LOG(sContentCacheLog, LogLevel::Info,
839 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
840 "mMessage=eQueryEditorRect, mReply=%s }",
841 this, ToString(aEvent.mReply).c_str()));
842 return true;
843 default:
844 aEvent.EmplaceReply();
845 aEvent.mReply->mFocusedWidget = aWidget;
846 if (NS_WARN_IF(aEvent.Failed())) {
847 MOZ_LOG(
848 sContentCacheLog, LogLevel::Error,
849 ("0x%p HandleQueryContentEvent(), FAILED due to not set enough "
850 "data, aEvent={ mMessage=%s, mReply=%s }",
851 this, ToChar(aEvent.mMessage), ToString(aEvent.mReply).c_str()));
852 return false;
854 MOZ_LOG(sContentCacheLog, LogLevel::Info,
855 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
856 "mMessage=%s, mReply=%s }",
857 this, ToChar(aEvent.mMessage), ToString(aEvent.mReply).c_str()));
858 return true;
862 bool ContentCacheInParent::GetTextRect(uint32_t aOffset,
863 bool aRoundToExistingOffset,
864 LayoutDeviceIntRect& aTextRect) const {
865 MOZ_LOG(
866 sContentCacheLog, LogLevel::Info,
867 ("0x%p GetTextRect(aOffset=%u, aRoundToExistingOffset=%s), "
868 "mTextRectArray=%s, mSelection=%s, mLastCommitStringTextRectArray=%s",
869 this, aOffset, GetBoolName(aRoundToExistingOffset),
870 ToString(mTextRectArray).c_str(), ToString(mSelection).c_str(),
871 ToString(mLastCommitStringTextRectArray).c_str()));
873 if (!aOffset) {
874 NS_WARNING_ASSERTION(!mFirstCharRect.IsEmpty(), "empty rect");
875 aTextRect = mFirstCharRect;
876 return !aTextRect.IsEmpty();
878 if (mSelection.isSome()) {
879 if (aOffset == mSelection->mAnchor) {
880 NS_WARNING_ASSERTION(
881 !mSelection->mAnchorCharRects[eNextCharRect].IsEmpty(), "empty rect");
882 aTextRect = mSelection->mAnchorCharRects[eNextCharRect];
883 return !aTextRect.IsEmpty();
885 if (mSelection->mAnchor && aOffset == mSelection->mAnchor - 1) {
886 NS_WARNING_ASSERTION(
887 !mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty(), "empty rect");
888 aTextRect = mSelection->mAnchorCharRects[ePrevCharRect];
889 return !aTextRect.IsEmpty();
891 if (aOffset == mSelection->mFocus) {
892 NS_WARNING_ASSERTION(
893 !mSelection->mFocusCharRects[eNextCharRect].IsEmpty(), "empty rect");
894 aTextRect = mSelection->mFocusCharRects[eNextCharRect];
895 return !aTextRect.IsEmpty();
897 if (mSelection->mFocus && aOffset == mSelection->mFocus - 1) {
898 NS_WARNING_ASSERTION(
899 !mSelection->mFocusCharRects[ePrevCharRect].IsEmpty(), "empty rect");
900 aTextRect = mSelection->mFocusCharRects[ePrevCharRect];
901 return !aTextRect.IsEmpty();
905 if (mTextRectArray.isSome() && mTextRectArray->IsOffsetInRange(aOffset)) {
906 aTextRect = mTextRectArray->GetRect(aOffset);
907 return !aTextRect.IsEmpty();
910 if (mLastCommitStringTextRectArray.isSome() &&
911 mLastCommitStringTextRectArray->IsOffsetInRange(aOffset)) {
912 aTextRect = mLastCommitStringTextRectArray->GetRect(aOffset);
913 return !aTextRect.IsEmpty();
916 if (!aRoundToExistingOffset) {
917 aTextRect.SetEmpty();
918 return false;
921 if (mTextRectArray.isNothing() || !mTextRectArray->HasRects()) {
922 // If there are no rects in mTextRectArray, we should refer the start of
923 // the selection because IME must query a char rect around it if there is
924 // no composition.
925 aTextRect = mSelection->StartCharRect();
926 return !aTextRect.IsEmpty();
929 // Although we may have mLastCommitStringTextRectArray here and it must have
930 // previous character rects at selection. However, we should stop using it
931 // because it's stored really short time after commiting a composition.
932 // So, multiple query may return different rect and it may cause flickerling
933 // the IME UI.
934 uint32_t offset = aOffset;
935 if (offset < mTextRectArray->StartOffset()) {
936 offset = mTextRectArray->StartOffset();
937 } else {
938 offset = mTextRectArray->EndOffset() - 1;
940 aTextRect = mTextRectArray->GetRect(offset);
941 return !aTextRect.IsEmpty();
944 bool ContentCacheInParent::GetUnionTextRects(
945 uint32_t aOffset, uint32_t aLength, bool aRoundToExistingOffset,
946 LayoutDeviceIntRect& aUnionTextRect) const {
947 MOZ_LOG(sContentCacheLog, LogLevel::Info,
948 ("0x%p GetUnionTextRects(aOffset=%u, "
949 "aLength=%u, aRoundToExistingOffset=%s), mTextRectArray=%s, "
950 "mSelection=%s, mLastCommitStringTextRectArray=%s",
951 this, aOffset, aLength, GetBoolName(aRoundToExistingOffset),
952 ToString(mTextRectArray).c_str(), ToString(mSelection).c_str(),
953 ToString(mLastCommitStringTextRectArray).c_str()));
955 CheckedInt<uint32_t> endOffset = CheckedInt<uint32_t>(aOffset) + aLength;
956 if (!endOffset.isValid()) {
957 return false;
960 if (mSelection.isSome() && !mSelection->Collapsed() &&
961 aOffset == mSelection->StartOffset() && aLength == mSelection->Length()) {
962 NS_WARNING_ASSERTION(!mSelection->mRect.IsEmpty(), "empty rect");
963 aUnionTextRect = mSelection->mRect;
964 return !aUnionTextRect.IsEmpty();
967 if (aLength == 1) {
968 if (!aOffset) {
969 NS_WARNING_ASSERTION(!mFirstCharRect.IsEmpty(), "empty rect");
970 aUnionTextRect = mFirstCharRect;
971 return !aUnionTextRect.IsEmpty();
973 if (mSelection.isSome()) {
974 if (aOffset == mSelection->mAnchor) {
975 NS_WARNING_ASSERTION(
976 !mSelection->mAnchorCharRects[eNextCharRect].IsEmpty(),
977 "empty rect");
978 aUnionTextRect = mSelection->mAnchorCharRects[eNextCharRect];
979 return !aUnionTextRect.IsEmpty();
981 if (mSelection->mAnchor && aOffset == mSelection->mAnchor - 1) {
982 NS_WARNING_ASSERTION(
983 !mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty(),
984 "empty rect");
985 aUnionTextRect = mSelection->mAnchorCharRects[ePrevCharRect];
986 return !aUnionTextRect.IsEmpty();
988 if (aOffset == mSelection->mFocus) {
989 NS_WARNING_ASSERTION(
990 !mSelection->mFocusCharRects[eNextCharRect].IsEmpty(),
991 "empty rect");
992 aUnionTextRect = mSelection->mFocusCharRects[eNextCharRect];
993 return !aUnionTextRect.IsEmpty();
995 if (mSelection->mFocus && aOffset == mSelection->mFocus - 1) {
996 NS_WARNING_ASSERTION(
997 !mSelection->mFocusCharRects[ePrevCharRect].IsEmpty(),
998 "empty rect");
999 aUnionTextRect = mSelection->mFocusCharRects[ePrevCharRect];
1000 return !aUnionTextRect.IsEmpty();
1005 // Even if some text rects are not cached of the queried range,
1006 // we should return union rect when the first character's rect is cached
1007 // since the first character rect is important and the others are not so
1008 // in most cases.
1010 if (!aOffset && mSelection.isSome() && aOffset != mSelection->mAnchor &&
1011 aOffset != mSelection->mFocus &&
1012 (mTextRectArray.isNothing() ||
1013 !mTextRectArray->IsOffsetInRange(aOffset)) &&
1014 (mLastCommitStringTextRectArray.isNothing() ||
1015 !mLastCommitStringTextRectArray->IsOffsetInRange(aOffset))) {
1016 // The first character rect isn't cached.
1017 return false;
1020 // Use mLastCommitStringTextRectArray only when it overlaps with aOffset
1021 // even if aROundToExistingOffset is true for avoiding flickerling IME UI.
1022 // See the last comment in GetTextRect() for the detail.
1023 if (mLastCommitStringTextRectArray.isSome() &&
1024 mLastCommitStringTextRectArray->IsOverlappingWith(aOffset, aLength)) {
1025 aUnionTextRect =
1026 mLastCommitStringTextRectArray->GetUnionRectAsFarAsPossible(
1027 aOffset, aLength, aRoundToExistingOffset);
1028 } else {
1029 aUnionTextRect.SetEmpty();
1032 if (mTextRectArray.isSome() &&
1033 ((aRoundToExistingOffset && mTextRectArray->HasRects()) ||
1034 mTextRectArray->IsOverlappingWith(aOffset, aLength))) {
1035 aUnionTextRect =
1036 aUnionTextRect.Union(mTextRectArray->GetUnionRectAsFarAsPossible(
1037 aOffset, aLength, aRoundToExistingOffset));
1040 if (!aOffset) {
1041 aUnionTextRect = aUnionTextRect.Union(mFirstCharRect);
1043 if (mSelection.isSome()) {
1044 if (aOffset <= mSelection->mAnchor &&
1045 mSelection->mAnchor < endOffset.value()) {
1046 aUnionTextRect =
1047 aUnionTextRect.Union(mSelection->mAnchorCharRects[eNextCharRect]);
1049 if (mSelection->mAnchor && aOffset <= mSelection->mAnchor - 1 &&
1050 mSelection->mAnchor - 1 < endOffset.value()) {
1051 aUnionTextRect =
1052 aUnionTextRect.Union(mSelection->mAnchorCharRects[ePrevCharRect]);
1054 if (aOffset <= mSelection->mFocus &&
1055 mSelection->mFocus < endOffset.value()) {
1056 aUnionTextRect =
1057 aUnionTextRect.Union(mSelection->mFocusCharRects[eNextCharRect]);
1059 if (mSelection->mFocus && aOffset <= mSelection->mFocus - 1 &&
1060 mSelection->mFocus - 1 < endOffset.value()) {
1061 aUnionTextRect =
1062 aUnionTextRect.Union(mSelection->mFocusCharRects[ePrevCharRect]);
1066 return !aUnionTextRect.IsEmpty();
1069 bool ContentCacheInParent::GetCaretRect(uint32_t aOffset,
1070 bool aRoundToExistingOffset,
1071 LayoutDeviceIntRect& aCaretRect) const {
1072 MOZ_LOG(sContentCacheLog, LogLevel::Info,
1073 ("0x%p GetCaretRect(aOffset=%u, aRoundToExistingOffset=%s), "
1074 "mCaret=%s, mTextRectArray=%s, mSelection=%s, mFirstCharRect=%s",
1075 this, aOffset, GetBoolName(aRoundToExistingOffset),
1076 ToString(mCaret).c_str(), ToString(mTextRectArray).c_str(),
1077 ToString(mSelection).c_str(), ToString(mFirstCharRect).c_str()));
1079 if (mCaret.isSome() && mCaret->mOffset == aOffset) {
1080 aCaretRect = mCaret->mRect;
1081 return true;
1084 // Guess caret rect from the text rect if it's stored.
1085 if (!GetTextRect(aOffset, aRoundToExistingOffset, aCaretRect)) {
1086 // There might be previous character rect in the cache. If so, we can
1087 // guess the caret rect with it.
1088 if (!aOffset ||
1089 !GetTextRect(aOffset - 1, aRoundToExistingOffset, aCaretRect)) {
1090 aCaretRect.SetEmpty();
1091 return false;
1094 if (mSelection.isSome() && mSelection->mWritingMode.IsVertical()) {
1095 aCaretRect.MoveToY(aCaretRect.YMost());
1096 } else {
1097 // XXX bidi-unaware.
1098 aCaretRect.MoveToX(aCaretRect.XMost());
1102 // XXX This is not bidi aware because we don't cache each character's
1103 // direction. However, this is usually used by IME, so, assuming the
1104 // character is in LRT context must not cause any problem.
1105 if (mSelection.isSome() && mSelection->mWritingMode.IsVertical()) {
1106 aCaretRect.SetHeight(mCaret.isSome() ? mCaret->mRect.Height() : 1);
1107 } else {
1108 aCaretRect.SetWidth(mCaret.isSome() ? mCaret->mRect.Width() : 1);
1110 return true;
1113 bool ContentCacheInParent::OnCompositionEvent(
1114 const WidgetCompositionEvent& aEvent) {
1115 MOZ_LOG(
1116 sContentCacheLog, LogLevel::Info,
1117 ("0x%p OnCompositionEvent(aEvent={ "
1118 "mMessage=%s, mData=\"%s\" (Length()=%u), mRanges->Length()=%zu }), "
1119 "mPendingEventsNeedingAck=%u, mWidgetHasComposition=%s, "
1120 "mPendingCompositionCount=%" PRIu8 ", mPendingCommitCount=%" PRIu8 ", "
1121 "mIsChildIgnoringCompositionEvents=%s, mCommitStringByRequest=0x%p",
1122 this, ToChar(aEvent.mMessage),
1123 PrintStringDetail(aEvent.mData,
1124 PrintStringDetail::kMaxLengthForCompositionString)
1125 .get(),
1126 aEvent.mData.Length(), aEvent.mRanges ? aEvent.mRanges->Length() : 0,
1127 mPendingEventsNeedingAck, GetBoolName(mWidgetHasComposition),
1128 mPendingCompositionCount, mPendingCommitCount,
1129 GetBoolName(mIsChildIgnoringCompositionEvents), mCommitStringByRequest));
1131 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1132 mDispatchedEventMessages.AppendElement(aEvent.mMessage);
1133 #endif // #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1135 // We must be able to simulate the selection because
1136 // we might not receive selection updates in time
1137 if (!mWidgetHasComposition) {
1138 if (mCompositionStartInChild.isSome()) {
1139 // If there is pending composition in the remote process, let's use
1140 // its start offset temporarily because this stores a lot of information
1141 // around it and the user must look around there, so, showing some UI
1142 // around it must make sense.
1143 mCompositionStart = mCompositionStartInChild;
1144 } else {
1145 mCompositionStart =
1146 Some(mSelection.isSome() ? mSelection->StartOffset() : 0);
1148 MOZ_ASSERT(aEvent.mMessage == eCompositionStart);
1149 MOZ_RELEASE_ASSERT(mPendingCompositionCount < UINT8_MAX);
1150 mPendingCompositionCount++;
1153 mWidgetHasComposition = !aEvent.CausesDOMCompositionEndEvent();
1155 if (!mWidgetHasComposition) {
1156 // mCompositionStart will be reset when commit event is completely handled
1157 // in the remote process.
1158 if (mPendingCompositionCount == 1) {
1159 mPendingCommitLength = aEvent.mData.Length();
1161 mPendingCommitCount++;
1162 } else if (aEvent.mMessage != eCompositionStart) {
1163 mCompositionString = aEvent.mData;
1166 // During REQUEST_TO_COMMIT_COMPOSITION or REQUEST_TO_CANCEL_COMPOSITION,
1167 // widget usually sends a eCompositionChange and/or eCompositionCommit event
1168 // to finalize or clear the composition, respectively. In this time,
1169 // we need to intercept all composition events here and pass the commit
1170 // string for returning to the remote process as a result of
1171 // RequestIMEToCommitComposition(). Then, eCommitComposition event will
1172 // be dispatched with the committed string in the remote process internally.
1173 if (mCommitStringByRequest) {
1174 if (aEvent.mMessage == eCompositionCommitAsIs) {
1175 *mCommitStringByRequest = mCompositionString;
1176 } else {
1177 MOZ_ASSERT(aEvent.mMessage == eCompositionChange ||
1178 aEvent.mMessage == eCompositionCommit);
1179 *mCommitStringByRequest = aEvent.mData;
1181 // We need to wait eCompositionCommitRequestHandled from the remote process
1182 // in this case. Therefore, mPendingEventsNeedingAck needs to be
1183 // incremented here. Additionally, we stop sending eCompositionCommit(AsIs)
1184 // event. Therefore, we need to decrement mPendingCommitCount which has
1185 // been incremented above.
1186 if (!mWidgetHasComposition) {
1187 mPendingEventsNeedingAck++;
1188 MOZ_DIAGNOSTIC_ASSERT(mPendingCommitCount);
1189 if (mPendingCommitCount) {
1190 mPendingCommitCount--;
1193 return false;
1196 mPendingEventsNeedingAck++;
1197 return true;
1200 void ContentCacheInParent::OnSelectionEvent(
1201 const WidgetSelectionEvent& aSelectionEvent) {
1202 MOZ_LOG(
1203 sContentCacheLog, LogLevel::Info,
1204 ("0x%p OnSelectionEvent(aEvent={ "
1205 "mMessage=%s, mOffset=%u, mLength=%u, mReversed=%s, "
1206 "mExpandToClusterBoundary=%s, mUseNativeLineBreak=%s }), "
1207 "mPendingEventsNeedingAck=%u, mWidgetHasComposition=%s, "
1208 "mPendingCompositionCount=%" PRIu8 ", mPendingCommitCount=%" PRIu8 ", "
1209 "mIsChildIgnoringCompositionEvents=%s",
1210 this, ToChar(aSelectionEvent.mMessage), aSelectionEvent.mOffset,
1211 aSelectionEvent.mLength, GetBoolName(aSelectionEvent.mReversed),
1212 GetBoolName(aSelectionEvent.mExpandToClusterBoundary),
1213 GetBoolName(aSelectionEvent.mUseNativeLineBreak),
1214 mPendingEventsNeedingAck, GetBoolName(mWidgetHasComposition),
1215 mPendingCompositionCount, mPendingCommitCount,
1216 GetBoolName(mIsChildIgnoringCompositionEvents)));
1218 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1219 mDispatchedEventMessages.AppendElement(aSelectionEvent.mMessage);
1220 #endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
1222 mPendingEventsNeedingAck++;
1225 void ContentCacheInParent::OnEventNeedingAckHandled(nsIWidget* aWidget,
1226 EventMessage aMessage) {
1227 // This is called when the child process receives WidgetCompositionEvent or
1228 // WidgetSelectionEvent.
1230 MOZ_LOG(
1231 sContentCacheLog, LogLevel::Info,
1232 ("0x%p OnEventNeedingAckHandled(aWidget=0x%p, "
1233 "aMessage=%s), mPendingEventsNeedingAck=%u, "
1234 "mWidgetHasComposition=%s, mPendingCompositionCount=%" PRIu8 ", "
1235 "mPendingCommitCount=%" PRIu8 ", mIsChildIgnoringCompositionEvents=%s",
1236 this, aWidget, ToChar(aMessage), mPendingEventsNeedingAck,
1237 GetBoolName(mWidgetHasComposition), mPendingCompositionCount,
1238 mPendingCommitCount, GetBoolName(mIsChildIgnoringCompositionEvents)));
1240 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1241 mReceivedEventMessages.AppendElement(aMessage);
1242 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1244 bool isCommittedInChild =
1245 // Commit requester in the remote process has committed the composition.
1246 aMessage == eCompositionCommitRequestHandled ||
1247 // The commit event has been handled normally in the remote process.
1248 (!mIsChildIgnoringCompositionEvents &&
1249 WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage));
1251 if (isCommittedInChild) {
1252 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1253 if (mPendingCompositionCount == 1) {
1254 RemoveUnnecessaryEventMessageLog();
1256 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1258 if (NS_WARN_IF(!mPendingCompositionCount)) {
1259 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1260 nsPrintfCString info(
1261 "\nThere is no pending composition but received %s "
1262 "message from the remote child\n\n",
1263 ToChar(aMessage));
1264 AppendEventMessageLog(info);
1265 CrashReporter::AppendAppNotesToCrashReport(info);
1266 MOZ_DIAGNOSTIC_ASSERT(
1267 false, "No pending composition but received unexpected commit event");
1268 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1270 // Prevent odd behavior in release channel.
1271 mPendingCompositionCount = 1;
1274 mPendingCompositionCount--;
1276 // Forget composition string only when the latest composition string is
1277 // handled in the remote process because if there is 2 or more pending
1278 // composition, this value shouldn't be referred.
1279 if (!mPendingCompositionCount) {
1280 mCompositionString.Truncate();
1283 // Forget pending commit string length if it's handled in the remote
1284 // process. Note that this doesn't care too old composition's commit
1285 // string because in such case, we cannot return proper information
1286 // to IME synchornously.
1287 mPendingCommitLength = 0;
1290 if (WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage)) {
1291 // After the remote process receives eCompositionCommit(AsIs) event,
1292 // it'll restart to handle composition events.
1293 mIsChildIgnoringCompositionEvents = false;
1295 if (NS_WARN_IF(!mPendingCommitCount)) {
1296 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1297 nsPrintfCString info(
1298 "\nThere is no pending comment events but received "
1299 "%s message from the remote child\n\n",
1300 ToChar(aMessage));
1301 AppendEventMessageLog(info);
1302 CrashReporter::AppendAppNotesToCrashReport(info);
1303 MOZ_DIAGNOSTIC_ASSERT(
1304 false,
1305 "No pending commit events but received unexpected commit event");
1306 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1308 // Prevent odd behavior in release channel.
1309 mPendingCommitCount = 1;
1312 mPendingCommitCount--;
1313 } else if (aMessage == eCompositionCommitRequestHandled &&
1314 mPendingCommitCount) {
1315 // If the remote process commits composition synchronously after
1316 // requesting commit composition and we've already sent commit composition,
1317 // it starts to ignore following composition events until receiving
1318 // eCompositionStart event.
1319 mIsChildIgnoringCompositionEvents = true;
1322 // If neither widget (i.e., IME) nor the remote process has composition,
1323 // now, we can forget composition string informations.
1324 if (!mWidgetHasComposition && !mPendingCompositionCount &&
1325 !mPendingCommitCount) {
1326 mCompositionStart.reset();
1329 if (NS_WARN_IF(!mPendingEventsNeedingAck)) {
1330 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1331 nsPrintfCString info(
1332 "\nThere is no pending events but received %s "
1333 "message from the remote child\n\n",
1334 ToChar(aMessage));
1335 AppendEventMessageLog(info);
1336 CrashReporter::AppendAppNotesToCrashReport(info);
1337 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1338 MOZ_DIAGNOSTIC_ASSERT(
1339 false, "No pending event message but received unexpected event");
1340 mPendingEventsNeedingAck = 1;
1342 if (--mPendingEventsNeedingAck) {
1343 return;
1346 FlushPendingNotifications(aWidget);
1349 bool ContentCacheInParent::RequestIMEToCommitComposition(
1350 nsIWidget* aWidget, bool aCancel, nsAString& aCommittedString) {
1351 MOZ_LOG(
1352 sContentCacheLog, LogLevel::Info,
1353 ("0x%p RequestToCommitComposition(aWidget=%p, "
1354 "aCancel=%s), mPendingCompositionCount=%" PRIu8 ", "
1355 "mPendingCommitCount=%" PRIu8 ", mIsChildIgnoringCompositionEvents=%s, "
1356 "IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)=%s, "
1357 "mWidgetHasComposition=%s, mCommitStringByRequest=%p",
1358 this, aWidget, GetBoolName(aCancel), mPendingCompositionCount,
1359 mPendingCommitCount, GetBoolName(mIsChildIgnoringCompositionEvents),
1360 GetBoolName(
1361 IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)),
1362 GetBoolName(mWidgetHasComposition), mCommitStringByRequest));
1364 MOZ_ASSERT(!mCommitStringByRequest);
1366 // If there are 2 or more pending compositions, we already sent
1367 // eCompositionCommit(AsIs) to the remote process. So, this request is
1368 // too late for IME. The remote process should wait following
1369 // composition events for cleaning up TextComposition and handle the
1370 // request as it's handled asynchronously.
1371 if (mPendingCompositionCount > 1) {
1372 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1373 mRequestIMEToCommitCompositionResults.AppendElement(
1374 RequestIMEToCommitCompositionResult::eToOldCompositionReceived);
1375 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1376 return false;
1379 // If there is no pending composition, we may have already sent
1380 // eCompositionCommit(AsIs) event for the active composition. If so, the
1381 // remote process will receive composition events which causes cleaning up
1382 // TextComposition. So, this shouldn't do nothing and TextComposition
1383 // should handle the request as it's handled asynchronously.
1384 // XXX Perhaps, this is wrong because TextComposition in child process
1385 // may commit the composition with current composition string in the
1386 // remote process. I.e., it may be different from actual commit string
1387 // which user typed. So, perhaps, we should return true and the commit
1388 // string.
1389 if (mPendingCommitCount) {
1390 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1391 mRequestIMEToCommitCompositionResults.AppendElement(
1392 RequestIMEToCommitCompositionResult::eToCommittedCompositionReceived);
1393 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1394 return false;
1397 // If BrowserParent which has IME focus was already changed to different one,
1398 // the request shouldn't be sent to IME because it's too late.
1399 if (!IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)) {
1400 // Use the latest composition string which may not be handled in the
1401 // remote process for avoiding data loss.
1402 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1403 mRequestIMEToCommitCompositionResults.AppendElement(
1404 RequestIMEToCommitCompositionResult::eReceivedAfterBrowserParentBlur);
1405 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1406 aCommittedString = mCompositionString;
1407 // After we return true from here, i.e., without actually requesting IME
1408 // to commit composition, we will receive eCompositionCommitRequestHandled
1409 // pseudo event message from the remote process. So, we need to increment
1410 // mPendingEventsNeedingAck here.
1411 mPendingEventsNeedingAck++;
1412 return true;
1415 RefPtr<TextComposition> composition =
1416 IMEStateManager::GetTextCompositionFor(aWidget);
1417 if (NS_WARN_IF(!composition)) {
1418 MOZ_LOG(sContentCacheLog, LogLevel::Warning,
1419 (" 0x%p RequestToCommitComposition(), "
1420 "does nothing due to no composition",
1421 this));
1422 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1423 mRequestIMEToCommitCompositionResults.AppendElement(
1424 RequestIMEToCommitCompositionResult::eReceivedButNoTextComposition);
1425 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1426 return false;
1429 mCommitStringByRequest = &aCommittedString;
1431 // Request commit or cancel composition with TextComposition because we may
1432 // have already requested to commit or cancel the composition or we may
1433 // have already received eCompositionCommit(AsIs) event. Those status are
1434 // managed by composition. So, if we don't request commit composition,
1435 // we should do nothing with native IME here.
1436 composition->RequestToCommit(aWidget, aCancel);
1438 mCommitStringByRequest = nullptr;
1440 MOZ_LOG(
1441 sContentCacheLog, LogLevel::Info,
1442 (" 0x%p RequestToCommitComposition(), "
1443 "mWidgetHasComposition=%s, the composition %s committed synchronously",
1444 this, GetBoolName(mWidgetHasComposition),
1445 composition->Destroyed() ? "WAS" : "has NOT been"));
1447 if (!composition->Destroyed()) {
1448 // When the composition isn't committed synchronously, the remote process's
1449 // TextComposition instance will synthesize commit events and wait to
1450 // receive delayed composition events. When TextComposition instances both
1451 // in this process and the remote process will be destroyed when delayed
1452 // composition events received. TextComposition instance in the parent
1453 // process will dispatch following composition events and be destroyed
1454 // normally. On the other hand, TextComposition instance in the remote
1455 // process won't dispatch following composition events and will be
1456 // destroyed by IMEStateManager::DispatchCompositionEvent().
1457 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1458 mRequestIMEToCommitCompositionResults.AppendElement(
1459 RequestIMEToCommitCompositionResult::eHandledAsynchronously);
1460 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1461 return false;
1464 // When the composition is committed synchronously, the commit string will be
1465 // returned to the remote process. Then, PuppetWidget will dispatch
1466 // eCompositionCommit event with the returned commit string (i.e., the value
1467 // is aCommittedString of this method) and that causes destroying
1468 // TextComposition instance in the remote process (Note that TextComposition
1469 // instance in this process was already destroyed).
1470 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1471 mRequestIMEToCommitCompositionResults.AppendElement(
1472 RequestIMEToCommitCompositionResult::eHandledSynchronously);
1473 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1474 return true;
1477 void ContentCacheInParent::MaybeNotifyIME(
1478 nsIWidget* aWidget, const IMENotification& aNotification) {
1479 if (!mPendingEventsNeedingAck) {
1480 IMEStateManager::NotifyIME(aNotification, aWidget, &mBrowserParent);
1481 return;
1484 switch (aNotification.mMessage) {
1485 case NOTIFY_IME_OF_SELECTION_CHANGE:
1486 mPendingSelectionChange.MergeWith(aNotification);
1487 break;
1488 case NOTIFY_IME_OF_TEXT_CHANGE:
1489 mPendingTextChange.MergeWith(aNotification);
1490 break;
1491 case NOTIFY_IME_OF_POSITION_CHANGE:
1492 mPendingLayoutChange.MergeWith(aNotification);
1493 break;
1494 case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
1495 mPendingCompositionUpdate.MergeWith(aNotification);
1496 break;
1497 default:
1498 MOZ_CRASH("Unsupported notification");
1499 break;
1503 void ContentCacheInParent::FlushPendingNotifications(nsIWidget* aWidget) {
1504 MOZ_ASSERT(!mPendingEventsNeedingAck);
1506 // If the BrowserParent's widget has already gone, this can do nothing since
1507 // widget is necessary to notify IME of something.
1508 if (!aWidget) {
1509 return;
1512 // New notifications which are notified during flushing pending notifications
1513 // should be merged again.
1514 mPendingEventsNeedingAck++;
1516 nsCOMPtr<nsIWidget> widget = aWidget;
1518 // First, text change notification should be sent because selection change
1519 // notification notifies IME of current selection range in the latest content.
1520 // So, IME may need the latest content before that.
1521 if (mPendingTextChange.HasNotification()) {
1522 IMENotification notification(mPendingTextChange);
1523 if (!widget->Destroyed()) {
1524 mPendingTextChange.Clear();
1525 IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
1529 if (mPendingSelectionChange.HasNotification()) {
1530 IMENotification notification(mPendingSelectionChange);
1531 if (!widget->Destroyed()) {
1532 mPendingSelectionChange.Clear();
1533 IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
1537 // Layout change notification should be notified after selection change
1538 // notification because IME may want to query position of new caret position.
1539 if (mPendingLayoutChange.HasNotification()) {
1540 IMENotification notification(mPendingLayoutChange);
1541 if (!widget->Destroyed()) {
1542 mPendingLayoutChange.Clear();
1543 IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
1547 // Finally, send composition update notification because it notifies IME of
1548 // finishing handling whole sending events.
1549 if (mPendingCompositionUpdate.HasNotification()) {
1550 IMENotification notification(mPendingCompositionUpdate);
1551 if (!widget->Destroyed()) {
1552 mPendingCompositionUpdate.Clear();
1553 IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
1557 if (!--mPendingEventsNeedingAck && !widget->Destroyed() &&
1558 (mPendingTextChange.HasNotification() ||
1559 mPendingSelectionChange.HasNotification() ||
1560 mPendingLayoutChange.HasNotification() ||
1561 mPendingCompositionUpdate.HasNotification())) {
1562 FlushPendingNotifications(widget);
1566 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1568 void ContentCacheInParent::RemoveUnnecessaryEventMessageLog() {
1569 bool foundLastCompositionStart = false;
1570 for (size_t i = mDispatchedEventMessages.Length(); i > 1; i--) {
1571 if (mDispatchedEventMessages[i - 1] != eCompositionStart) {
1572 continue;
1574 if (!foundLastCompositionStart) {
1575 // Find previous eCompositionStart of the latest eCompositionStart.
1576 foundLastCompositionStart = true;
1577 continue;
1579 // Remove the messages before the last 2 sets of composition events.
1580 mDispatchedEventMessages.RemoveElementsAt(0, i - 1);
1581 break;
1583 uint32_t numberOfCompositionCommitRequestHandled = 0;
1584 foundLastCompositionStart = false;
1585 for (size_t i = mReceivedEventMessages.Length(); i > 1; i--) {
1586 if (mReceivedEventMessages[i - 1] == eCompositionCommitRequestHandled) {
1587 numberOfCompositionCommitRequestHandled++;
1589 if (mReceivedEventMessages[i - 1] != eCompositionStart) {
1590 continue;
1592 if (!foundLastCompositionStart) {
1593 // Find previous eCompositionStart of the latest eCompositionStart.
1594 foundLastCompositionStart = true;
1595 continue;
1597 // Remove the messages before the last 2 sets of composition events.
1598 mReceivedEventMessages.RemoveElementsAt(0, i - 1);
1599 break;
1602 if (!numberOfCompositionCommitRequestHandled) {
1603 // If there is no eCompositionCommitRequestHandled in
1604 // mReceivedEventMessages, we don't need to store log of
1605 // RequestIMEToCommmitComposition().
1606 mRequestIMEToCommitCompositionResults.Clear();
1607 } else {
1608 // We need to keep all reason of eCompositionCommitRequestHandled, which
1609 // is sent when mRequestIMEToCommitComposition() returns true.
1610 // So, we can discard older log than the first
1611 // eCompositionCommitRequestHandled in mReceivedEventMessages.
1612 for (size_t i = mRequestIMEToCommitCompositionResults.Length(); i > 1;
1613 i--) {
1614 if (mRequestIMEToCommitCompositionResults[i - 1] ==
1615 RequestIMEToCommitCompositionResult::
1616 eReceivedAfterBrowserParentBlur ||
1617 mRequestIMEToCommitCompositionResults[i - 1] ==
1618 RequestIMEToCommitCompositionResult::eHandledSynchronously) {
1619 --numberOfCompositionCommitRequestHandled;
1620 if (!numberOfCompositionCommitRequestHandled) {
1621 mRequestIMEToCommitCompositionResults.RemoveElementsAt(0, i - 1);
1622 break;
1629 void ContentCacheInParent::AppendEventMessageLog(nsACString& aLog) const {
1630 aLog.AppendLiteral("Dispatched Event Message Log:\n");
1631 for (EventMessage message : mDispatchedEventMessages) {
1632 aLog.AppendLiteral(" ");
1633 aLog.Append(ToChar(message));
1634 aLog.AppendLiteral("\n");
1636 aLog.AppendLiteral("\nReceived Event Message Log:\n");
1637 for (EventMessage message : mReceivedEventMessages) {
1638 aLog.AppendLiteral(" ");
1639 aLog.Append(ToChar(message));
1640 aLog.AppendLiteral("\n");
1642 aLog.AppendLiteral("\nResult of RequestIMEToCommitComposition():\n");
1643 for (RequestIMEToCommitCompositionResult result :
1644 mRequestIMEToCommitCompositionResults) {
1645 aLog.AppendLiteral(" ");
1646 aLog.Append(ToReadableText(result));
1647 aLog.AppendLiteral("\n");
1649 aLog.AppendLiteral("\n");
1652 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1654 /*****************************************************************************
1655 * mozilla::ContentCache::TextRectArray
1656 *****************************************************************************/
1658 LayoutDeviceIntRect ContentCache::TextRectArray::GetRect(
1659 uint32_t aOffset) const {
1660 LayoutDeviceIntRect rect;
1661 if (IsOffsetInRange(aOffset)) {
1662 rect = mRects[aOffset - mStart];
1664 return rect;
1667 LayoutDeviceIntRect ContentCache::TextRectArray::GetUnionRect(
1668 uint32_t aOffset, uint32_t aLength) const {
1669 LayoutDeviceIntRect rect;
1670 if (!IsRangeCompletelyInRange(aOffset, aLength)) {
1671 return rect;
1673 for (uint32_t i = 0; i < aLength; i++) {
1674 rect = rect.Union(mRects[aOffset - mStart + i]);
1676 return rect;
1679 LayoutDeviceIntRect ContentCache::TextRectArray::GetUnionRectAsFarAsPossible(
1680 uint32_t aOffset, uint32_t aLength, bool aRoundToExistingOffset) const {
1681 LayoutDeviceIntRect rect;
1682 if (!HasRects() ||
1683 (!aRoundToExistingOffset && !IsOverlappingWith(aOffset, aLength))) {
1684 return rect;
1686 uint32_t startOffset = std::max(aOffset, mStart);
1687 if (aRoundToExistingOffset && startOffset >= EndOffset()) {
1688 startOffset = EndOffset() - 1;
1690 uint32_t endOffset = std::min(aOffset + aLength, EndOffset());
1691 if (aRoundToExistingOffset && endOffset < mStart + 1) {
1692 endOffset = mStart + 1;
1694 if (NS_WARN_IF(endOffset < startOffset)) {
1695 return rect;
1697 for (uint32_t i = 0; i < endOffset - startOffset; i++) {
1698 rect = rect.Union(mRects[startOffset - mStart + i]);
1700 return rect;
1703 } // namespace mozilla