Bug 1776056 - Switch to the tab an animation is running and make sure the animation...
[gecko.git] / widget / ContentCache.cpp
blobbfe7e701a5a8f66cec6cd27e9f21ee724319ff57
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/IMEStateManager.h"
16 #include "mozilla/IntegerPrintfMacros.h"
17 #include "mozilla/Logging.h"
18 #include "mozilla/RefPtr.h"
19 #include "mozilla/TextComposition.h"
20 #include "mozilla/dom/BrowserParent.h"
21 #include "nsExceptionHandler.h"
22 #include "nsIWidget.h"
24 namespace mozilla {
26 using namespace dom;
27 using namespace widget;
29 static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; }
31 static const char* GetNotificationName(const IMENotification* aNotification) {
32 if (!aNotification) {
33 return "Not notification";
35 return ToChar(aNotification->mMessage);
38 /*****************************************************************************
39 * mozilla::ContentCache
40 *****************************************************************************/
42 LazyLogModule sContentCacheLog("ContentCacheWidgets");
44 /*****************************************************************************
45 * mozilla::ContentCacheInChild
46 *****************************************************************************/
48 void ContentCacheInChild::Clear() {
49 MOZ_LOG(sContentCacheLog, LogLevel::Info, ("0x%p Clear()", this));
51 mCompositionStart.reset();
52 mLastCommit.reset();
53 mText.reset();
54 mSelection.reset();
55 mFirstCharRect.SetEmpty();
56 mCaret.reset();
57 mTextRectArray.reset();
58 mLastCommitStringTextRectArray.reset();
59 mEditorRect.SetEmpty();
62 void ContentCacheInChild::OnCompositionEvent(
63 const WidgetCompositionEvent& aCompositionEvent) {
64 if (aCompositionEvent.CausesDOMCompositionEndEvent()) {
65 RefPtr<TextComposition> composition =
66 IMEStateManager::GetTextCompositionFor(aCompositionEvent.mWidget);
67 if (composition) {
68 nsAutoString lastCommitString;
69 if (aCompositionEvent.mMessage == eCompositionCommitAsIs) {
70 lastCommitString = composition->CommitStringIfCommittedAsIs();
71 } else {
72 lastCommitString = aCompositionEvent.mData;
74 // We don't need to store canceling information because this is required
75 // by undoing of last commit (Kakutei-Undo of Japanese IME).
76 if (!lastCommitString.IsEmpty()) {
77 mLastCommit = Some(OffsetAndData<uint32_t>(
78 composition->NativeOffsetOfStartComposition(), lastCommitString));
79 MOZ_LOG(
80 sContentCacheLog, LogLevel::Debug,
81 ("0x%p OnCompositionEvent(), stored last composition string data "
82 "(aCompositionEvent={ mMessage=%s, mData=\"%s\"}, mLastCommit=%s)",
83 this, ToChar(aCompositionEvent.mMessage),
84 PrintStringDetail(
85 aCompositionEvent.mData,
86 PrintStringDetail::kMaxLengthForCompositionString)
87 .get(),
88 ToString(mLastCommit).c_str()));
89 return;
93 if (mLastCommit.isSome()) {
94 MOZ_LOG(
95 sContentCacheLog, LogLevel::Debug,
96 ("0x%p OnCompositionEvent(), resetting the last composition string "
97 "data (aCompositionEvent={ mMessage=%s, mData=\"%s\"}, "
98 "mLastCommit=%s)",
99 this, ToChar(aCompositionEvent.mMessage),
100 PrintStringDetail(aCompositionEvent.mData,
101 PrintStringDetail::kMaxLengthForCompositionString)
102 .get(),
103 ToString(mLastCommit).c_str()));
104 mLastCommit.reset();
108 bool ContentCacheInChild::CacheAll(nsIWidget* aWidget,
109 const IMENotification* aNotification) {
110 MOZ_LOG(sContentCacheLog, LogLevel::Info,
111 ("0x%p CacheAll(aWidget=0x%p, aNotification=%s)", this, aWidget,
112 GetNotificationName(aNotification)));
114 const bool textCached = CacheText(aWidget, aNotification);
115 const bool editorRectCached = CacheEditorRect(aWidget, aNotification);
116 return textCached || editorRectCached;
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 mSelection.reset();
127 nsEventStatus status = nsEventStatus_eIgnore;
128 WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
129 aWidget);
130 aWidget->DispatchEvent(&querySelectedTextEvent, status);
131 if (NS_WARN_IF(querySelectedTextEvent.Failed())) {
132 MOZ_LOG(
133 sContentCacheLog, LogLevel::Error,
134 ("0x%p CacheSelection(), FAILED, couldn't retrieve the selected text",
135 this));
137 // ContentCache should store only editable content. Therefore, if current
138 // selection root is not editable, we don't need to store the selection, i.e.,
139 // let's treat it as there is no selection. However, if we already have
140 // previously editable text, let's store the selection even if it becomes
141 // uneditable because not doing so would create odd situation. E.g., IME may
142 // fail only querying selection after succeeded querying text.
143 else if (NS_WARN_IF(mText.isNothing() &&
144 !querySelectedTextEvent.mReply->mIsEditableContent)) {
145 MOZ_LOG(sContentCacheLog, LogLevel::Error,
146 ("0x%p CacheSelection(), FAILED, editable content had already been "
147 "blurred",
148 this));
149 return false;
150 } else {
151 mSelection.emplace(querySelectedTextEvent);
154 const bool caretCached = CacheCaret(aWidget, aNotification);
155 const bool textRectsCached = CacheTextRects(aWidget, aNotification);
156 return caretCached || textRectsCached || querySelectedTextEvent.Succeeded();
159 bool ContentCacheInChild::CacheCaret(nsIWidget* aWidget,
160 const IMENotification* aNotification) {
161 mCaret.reset();
163 if (MOZ_UNLIKELY(mSelection.isNothing())) {
164 return false;
167 MOZ_LOG(sContentCacheLog, LogLevel::Info,
168 ("0x%p CacheCaret(aWidget=0x%p, aNotification=%s)", this, aWidget,
169 GetNotificationName(aNotification)));
171 if (mSelection->mHasRange) {
172 // XXX Should be mSelection.mFocus?
173 const uint32_t offset = mSelection->StartOffset();
175 nsEventStatus status = nsEventStatus_eIgnore;
176 WidgetQueryContentEvent queryCaretRectEvet(true, eQueryCaretRect, aWidget);
177 queryCaretRectEvet.InitForQueryCaretRect(offset);
178 aWidget->DispatchEvent(&queryCaretRectEvet, status);
179 if (NS_WARN_IF(queryCaretRectEvet.Failed())) {
180 MOZ_LOG(sContentCacheLog, LogLevel::Error,
181 ("0x%p CacheCaret(), FAILED, couldn't retrieve the caret rect "
182 "at offset=%u",
183 this, offset));
184 return false;
186 mCaret.emplace(offset, queryCaretRectEvet.mReply->mRect);
188 MOZ_LOG(sContentCacheLog, LogLevel::Info,
189 ("0x%p CacheCaret(), Succeeded, mSelection=%s, mCaret=%s", this,
190 ToString(mSelection).c_str(), ToString(mCaret).c_str()));
191 return true;
194 bool ContentCacheInChild::CacheEditorRect(
195 nsIWidget* aWidget, const IMENotification* aNotification) {
196 MOZ_LOG(sContentCacheLog, LogLevel::Info,
197 ("0x%p CacheEditorRect(aWidget=0x%p, aNotification=%s)", this,
198 aWidget, GetNotificationName(aNotification)));
200 nsEventStatus status = nsEventStatus_eIgnore;
201 WidgetQueryContentEvent queryEditorRectEvent(true, eQueryEditorRect, aWidget);
202 aWidget->DispatchEvent(&queryEditorRectEvent, status);
203 if (NS_WARN_IF(queryEditorRectEvent.Failed())) {
204 MOZ_LOG(
205 sContentCacheLog, LogLevel::Error,
206 ("0x%p CacheEditorRect(), FAILED, couldn't retrieve the editor rect",
207 this));
208 return false;
210 // ContentCache should store only editable content. Therefore, if current
211 // selection root is not editable, we don't need to store the editor rect,
212 // i.e., let's treat it as there is no focused editor.
213 if (NS_WARN_IF(!queryEditorRectEvent.mReply->mIsEditableContent)) {
214 MOZ_LOG(sContentCacheLog, LogLevel::Error,
215 ("0x%p CacheText(), FAILED, editable content had already been "
216 "blurred",
217 this));
218 return false;
220 mEditorRect = queryEditorRectEvent.mReply->mRect;
221 MOZ_LOG(sContentCacheLog, LogLevel::Info,
222 ("0x%p CacheEditorRect(), Succeeded, mEditorRect=%s", this,
223 ToString(mEditorRect).c_str()));
224 return true;
227 bool ContentCacheInChild::CacheText(nsIWidget* aWidget,
228 const IMENotification* aNotification) {
229 MOZ_LOG(sContentCacheLog, LogLevel::Info,
230 ("0x%p CacheText(aWidget=0x%p, aNotification=%s)", this, aWidget,
231 GetNotificationName(aNotification)));
233 nsEventStatus status = nsEventStatus_eIgnore;
234 WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
235 aWidget);
236 queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
237 aWidget->DispatchEvent(&queryTextContentEvent, status);
238 if (NS_WARN_IF(queryTextContentEvent.Failed())) {
239 MOZ_LOG(sContentCacheLog, LogLevel::Error,
240 ("0x%p CacheText(), FAILED, couldn't retrieve whole text", this));
241 mText.reset();
243 // ContentCache should store only editable content. Therefore, if current
244 // selection root is not editable, we don't need to store the text, i.e.,
245 // let's treat it as there is no editable text.
246 else if (NS_WARN_IF(!queryTextContentEvent.mReply->mIsEditableContent)) {
247 MOZ_LOG(sContentCacheLog, LogLevel::Error,
248 ("0x%p CacheText(), FAILED, editable content had already been "
249 "blurred",
250 this));
251 mText.reset();
252 } else {
253 mText = Some(nsString(queryTextContentEvent.mReply->DataRef()));
254 MOZ_LOG(sContentCacheLog, LogLevel::Info,
255 ("0x%p CacheText(), Succeeded, mText=%s", this,
256 PrintStringDetail(mText, PrintStringDetail::kMaxLengthForEditor)
257 .get()));
260 // Forget last commit range if string in the range is different from the
261 // last commit string.
262 if (mLastCommit.isSome() &&
263 (mText.isNothing() ||
264 nsDependentSubstring(mText.ref(), mLastCommit->StartOffset(),
265 mLastCommit->Length()) != mLastCommit->DataRef())) {
266 MOZ_LOG(sContentCacheLog, LogLevel::Debug,
267 ("0x%p CacheText(), resetting the last composition string data "
268 "(mLastCommit=%s, current string=\"%s\")",
269 this, ToString(mLastCommit).c_str(),
270 PrintStringDetail(
271 nsDependentSubstring(mText.ref(), mLastCommit->StartOffset(),
272 mLastCommit->Length()),
273 PrintStringDetail::kMaxLengthForCompositionString)
274 .get()));
275 mLastCommit.reset();
278 // If we fail to get editable text content, it must mean that there is no
279 // focused element anymore or focused element is not editable. In this case,
280 // we should not get selection of non-editable content
281 if (MOZ_UNLIKELY(queryTextContentEvent.Failed() ||
282 !queryTextContentEvent.mReply->mIsEditableContent)) {
283 mSelection.reset();
284 mCaret.reset();
285 mTextRectArray.reset();
286 return false;
289 return CacheSelection(aWidget, aNotification);
292 bool ContentCacheInChild::QueryCharRect(nsIWidget* aWidget, uint32_t aOffset,
293 LayoutDeviceIntRect& aCharRect) const {
294 aCharRect.SetEmpty();
296 nsEventStatus status = nsEventStatus_eIgnore;
297 WidgetQueryContentEvent queryTextRectEvent(true, eQueryTextRect, aWidget);
298 queryTextRectEvent.InitForQueryTextRect(aOffset, 1);
299 aWidget->DispatchEvent(&queryTextRectEvent, status);
300 if (NS_WARN_IF(queryTextRectEvent.Failed())) {
301 return false;
303 aCharRect = queryTextRectEvent.mReply->mRect;
305 // Guarantee the rect is not empty.
306 if (NS_WARN_IF(!aCharRect.Height())) {
307 aCharRect.SetHeight(1);
309 if (NS_WARN_IF(!aCharRect.Width())) {
310 aCharRect.SetWidth(1);
312 return true;
315 bool ContentCacheInChild::QueryCharRectArray(nsIWidget* aWidget,
316 uint32_t aOffset, uint32_t aLength,
317 RectArray& aCharRectArray) const {
318 nsEventStatus status = nsEventStatus_eIgnore;
319 WidgetQueryContentEvent queryTextRectsEvent(true, eQueryTextRectArray,
320 aWidget);
321 queryTextRectsEvent.InitForQueryTextRectArray(aOffset, aLength);
322 aWidget->DispatchEvent(&queryTextRectsEvent, status);
323 if (NS_WARN_IF(queryTextRectsEvent.Failed())) {
324 aCharRectArray.Clear();
325 return false;
327 aCharRectArray = std::move(queryTextRectsEvent.mReply->mRectArray);
328 return true;
331 bool ContentCacheInChild::CacheTextRects(nsIWidget* aWidget,
332 const IMENotification* aNotification) {
333 MOZ_LOG(
334 sContentCacheLog, LogLevel::Info,
335 ("0x%p CacheTextRects(aWidget=0x%p, aNotification=%s), mCaret=%s", this,
336 aWidget, GetNotificationName(aNotification), ToString(mCaret).c_str()));
338 if (mSelection.isSome()) {
339 mSelection->ClearRects();
342 // Retrieve text rects in composition string if there is.
343 RefPtr<TextComposition> textComposition =
344 IMEStateManager::GetTextCompositionFor(aWidget);
345 if (textComposition) {
346 // mCompositionStart may be updated by some composition event handlers.
347 // So, let's update it with the latest information.
348 mCompositionStart = Some(textComposition->NativeOffsetOfStartComposition());
349 // Note that TextComposition::String() may not be modified here because
350 // it's modified after all edit action listeners are performed but this
351 // is called while some of them are performed.
352 // FYI: For supporting IME which commits composition and restart new
353 // composition immediately, we should cache next character of current
354 // composition too.
355 uint32_t length = textComposition->LastData().Length() + 1;
356 mTextRectArray = Some(TextRectArray(mCompositionStart.value()));
357 if (NS_WARN_IF(!QueryCharRectArray(aWidget, mTextRectArray->mStart, length,
358 mTextRectArray->mRects))) {
359 MOZ_LOG(sContentCacheLog, LogLevel::Error,
360 ("0x%p CacheTextRects(), FAILED, "
361 "couldn't retrieve text rect array of the composition string",
362 this));
363 mTextRectArray.reset();
365 } else {
366 mCompositionStart.reset();
367 mTextRectArray.reset();
370 // Set mSelection->mAnchorCharRects
371 // If we've already have the rect in mTextRectArray, save the query cost.
372 if (mSelection.isSome() && mSelection->mHasRange && mTextRectArray.isSome() &&
373 mTextRectArray->IsOffsetInRange(mSelection->mAnchor) &&
374 (!mSelection->mAnchor ||
375 mTextRectArray->IsOffsetInRange(mSelection->mAnchor - 1))) {
376 mSelection->mAnchorCharRects[eNextCharRect] =
377 mTextRectArray->GetRect(mSelection->mAnchor);
378 if (mSelection->mAnchor) {
379 mSelection->mAnchorCharRects[ePrevCharRect] =
380 mTextRectArray->GetRect(mSelection->mAnchor - 1);
383 // Otherwise, get it from content even if there is no selection ranges.
384 else {
385 RectArray rects;
386 const uint32_t startOffset =
387 mSelection.isSome() && mSelection->mHasRange && mSelection->mAnchor
388 ? mSelection->mAnchor - 1u
389 : 0u;
390 const uint32_t length =
391 mSelection.isSome() && mSelection->mHasRange && mSelection->mAnchor
392 ? 2u
393 : 1u;
394 if (NS_WARN_IF(!QueryCharRectArray(aWidget, startOffset, length, rects))) {
395 MOZ_LOG(
396 sContentCacheLog, LogLevel::Error,
397 ("0x%p CacheTextRects(), FAILED, "
398 "couldn't retrieve text rect array around the selection anchor (%u)",
399 this, mSelection->mAnchor));
400 MOZ_ASSERT_IF(mSelection.isSome(),
401 mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty());
402 MOZ_ASSERT_IF(mSelection.isSome(),
403 mSelection->mAnchorCharRects[eNextCharRect].IsEmpty());
404 } else if (rects.Length()) {
405 if (mSelection.isNothing()) {
406 mSelection.emplace(); // With no range
408 if (rects.Length() > 1) {
409 mSelection->mAnchorCharRects[ePrevCharRect] = rects[0];
410 mSelection->mAnchorCharRects[eNextCharRect] = rects[1];
411 } else {
412 mSelection->mAnchorCharRects[eNextCharRect] = rects[0];
413 MOZ_ASSERT(mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty());
418 // Note that if mSelection is Nothing here, we've already failed to get
419 // rects in the `else` block above. In such case, we cannot get character
420 // rects around focus point.
421 if (mSelection.isSome()) {
422 // Set mSelection->mFocusCharRects
423 // If selection is collapsed (including no selection case), the focus char
424 // rects are same as the anchor char rects so that we can just copy them.
425 if (mSelection->IsCollapsed()) {
426 mSelection->mFocusCharRects[0] = mSelection->mAnchorCharRects[0];
427 mSelection->mFocusCharRects[1] = mSelection->mAnchorCharRects[1];
429 // If the selection range is in mTextRectArray, save the query cost.
430 else if (mTextRectArray.isSome() &&
431 mTextRectArray->IsOffsetInRange(mSelection->mFocus) &&
432 (!mSelection->mFocus ||
433 mTextRectArray->IsOffsetInRange(mSelection->mFocus - 1))) {
434 MOZ_ASSERT(mSelection->mHasRange);
435 mSelection->mFocusCharRects[eNextCharRect] =
436 mTextRectArray->GetRect(mSelection->mFocus);
437 if (mSelection->mFocus) {
438 mSelection->mFocusCharRects[ePrevCharRect] =
439 mTextRectArray->GetRect(mSelection->mFocus - 1);
442 // Otherwise, including no selection range cases, need to query the rects.
443 else {
444 MOZ_ASSERT(mSelection->mHasRange);
445 RectArray rects;
446 const uint32_t startOffset =
447 mSelection->mFocus ? mSelection->mFocus - 1u : 0u;
448 const uint32_t length = mSelection->mFocus ? 2u : 1u;
449 if (NS_WARN_IF(
450 !QueryCharRectArray(aWidget, startOffset, length, rects))) {
451 MOZ_LOG(sContentCacheLog, LogLevel::Error,
452 ("0x%p CacheTextRects(), FAILED, "
453 "couldn't retrieve text rect array around the selection focus "
454 "(%u)",
455 this, mSelection->mFocus));
456 MOZ_ASSERT(mSelection->mFocusCharRects[ePrevCharRect].IsEmpty());
457 MOZ_ASSERT(mSelection->mFocusCharRects[eNextCharRect].IsEmpty());
458 } else {
459 if (rects.Length() > 1) {
460 mSelection->mFocusCharRects[ePrevCharRect] = rects[0];
461 mSelection->mFocusCharRects[eNextCharRect] = rects[1];
462 } else if (rects.Length()) {
463 mSelection->mFocusCharRects[eNextCharRect] = rects[0];
464 MOZ_ASSERT(mSelection->mFocusCharRects[ePrevCharRect].IsEmpty());
470 // If there is a non-collapsed selection range, let's query the whole selected
471 // text rect. Note that the result cannot be computed from first character
472 // rect and last character rect of the selection because they both may be in
473 // middle of different line.
474 if (mSelection.isSome() && mSelection->mHasRange &&
475 !mSelection->IsCollapsed()) {
476 nsEventStatus status = nsEventStatus_eIgnore;
477 WidgetQueryContentEvent queryTextRectEvent(true, eQueryTextRect, aWidget);
478 queryTextRectEvent.InitForQueryTextRect(mSelection->StartOffset(),
479 mSelection->Length());
480 aWidget->DispatchEvent(&queryTextRectEvent, status);
481 if (NS_WARN_IF(queryTextRectEvent.Failed())) {
482 MOZ_LOG(sContentCacheLog, LogLevel::Error,
483 ("0x%p CacheTextRects(), FAILED, "
484 "couldn't retrieve text rect of whole selected text",
485 this));
486 } else {
487 mSelection->mRect = queryTextRectEvent.mReply->mRect;
491 // Even if there is no selection range, we should have the first character
492 // rect for the last resort of suggesting position of IME UI.
493 if (mSelection.isSome() && mSelection->mHasRange && !mSelection->mFocus) {
494 mFirstCharRect = mSelection->mFocusCharRects[eNextCharRect];
495 } else if (mSelection.isSome() && mSelection->mHasRange &&
496 mSelection->mFocus == 1) {
497 mFirstCharRect = mSelection->mFocusCharRects[ePrevCharRect];
498 } else if (mSelection.isSome() && mSelection->mHasRange &&
499 !mSelection->mAnchor) {
500 mFirstCharRect = mSelection->mAnchorCharRects[eNextCharRect];
501 } else if (mSelection.isSome() && mSelection->mHasRange &&
502 mSelection->mAnchor == 1) {
503 mFirstCharRect = mSelection->mFocusCharRects[ePrevCharRect];
504 } else if (mTextRectArray.isSome() && mTextRectArray->IsOffsetInRange(0u)) {
505 mFirstCharRect = mTextRectArray->GetRect(0u);
506 } else {
507 LayoutDeviceIntRect charRect;
508 if (MOZ_UNLIKELY(NS_WARN_IF(!QueryCharRect(aWidget, 0, charRect)))) {
509 MOZ_LOG(sContentCacheLog, LogLevel::Error,
510 ("0x%p CacheTextRects(), FAILED, "
511 "couldn't retrieve first char rect",
512 this));
513 mFirstCharRect.SetEmpty();
514 } else {
515 mFirstCharRect = charRect;
519 // Finally, let's cache the last commit string's character rects until
520 // selection change or something other editing because user may reconvert
521 // or undo the last commit. Then, IME requires the character rects for
522 // positioning their UI.
523 if (mLastCommit.isSome()) {
524 mLastCommitStringTextRectArray =
525 Some(TextRectArray(mLastCommit->StartOffset()));
526 if (mLastCommit->Length() == 1 && mSelection.isSome() &&
527 mSelection->mHasRange &&
528 mSelection->mAnchor - 1 == mLastCommit->StartOffset() &&
529 !mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty()) {
530 mLastCommitStringTextRectArray->mRects.AppendElement(
531 mSelection->mAnchorCharRects[ePrevCharRect]);
532 } else if (NS_WARN_IF(!QueryCharRectArray(
533 aWidget, mLastCommit->StartOffset(), mLastCommit->Length(),
534 mLastCommitStringTextRectArray->mRects))) {
535 MOZ_LOG(sContentCacheLog, LogLevel::Error,
536 ("0x%p CacheTextRects(), FAILED, "
537 "couldn't retrieve text rect array of the last commit string",
538 this));
539 mLastCommitStringTextRectArray.reset();
540 mLastCommit.reset();
542 MOZ_ASSERT((mLastCommitStringTextRectArray.isSome()
543 ? mLastCommitStringTextRectArray->mRects.Length()
544 : 0) == (mLastCommit.isSome() ? mLastCommit->Length() : 0));
545 } else {
546 mLastCommitStringTextRectArray.reset();
549 MOZ_LOG(
550 sContentCacheLog, LogLevel::Info,
551 ("0x%p CacheTextRects(), Succeeded, "
552 "mText=%s, mTextRectArray=%s, mSelection=%s, "
553 "mFirstCharRect=%s, mLastCommitStringTextRectArray=%s",
554 this,
555 PrintStringDetail(mText, PrintStringDetail::kMaxLengthForEditor).get(),
556 ToString(mTextRectArray).c_str(), ToString(mSelection).c_str(),
557 ToString(mFirstCharRect).c_str(),
558 ToString(mLastCommitStringTextRectArray).c_str()));
559 return true;
562 void ContentCacheInChild::SetSelection(
563 nsIWidget* aWidget,
564 const IMENotification::SelectionChangeDataBase& aSelectionChangeData) {
565 MOZ_LOG(
566 sContentCacheLog, LogLevel::Info,
567 ("0x%p SetSelection(aSelectionChangeData=%s), mText=%s", this,
568 ToString(aSelectionChangeData).c_str(),
569 PrintStringDetail(mText, PrintStringDetail::kMaxLengthForEditor).get()));
571 mSelection = Some(Selection(aSelectionChangeData));
573 if (mLastCommit.isSome()) {
574 // Forget last commit string range if selection is not collapsed
575 // at end of the last commit string.
576 if (!mSelection->mHasRange || !mSelection->IsCollapsed() ||
577 mSelection->mAnchor != mLastCommit->EndOffset()) {
578 MOZ_LOG(
579 sContentCacheLog, LogLevel::Debug,
580 ("0x%p SetSelection(), forgetting last commit composition data "
581 "(mSelection=%s, mLastCommit=%s)",
582 this, ToString(mSelection).c_str(), ToString(mLastCommit).c_str()));
583 mLastCommit.reset();
587 CacheCaret(aWidget);
588 CacheTextRects(aWidget);
591 /*****************************************************************************
592 * mozilla::ContentCacheInParent
593 *****************************************************************************/
595 ContentCacheInParent::ContentCacheInParent(BrowserParent& aBrowserParent)
596 : ContentCache(),
597 mBrowserParent(aBrowserParent),
598 mCommitStringByRequest(nullptr),
599 mPendingEventsNeedingAck(0),
600 mPendingCommitLength(0),
601 mPendingCompositionCount(0),
602 mPendingCommitCount(0),
603 mWidgetHasComposition(false),
604 mIsChildIgnoringCompositionEvents(false) {}
606 void ContentCacheInParent::AssignContent(const ContentCache& aOther,
607 nsIWidget* aWidget,
608 const IMENotification* aNotification) {
609 mText = aOther.mText;
610 mSelection = aOther.mSelection;
611 mFirstCharRect = aOther.mFirstCharRect;
612 mCaret = aOther.mCaret;
613 mTextRectArray = aOther.mTextRectArray;
614 mLastCommitStringTextRectArray = aOther.mLastCommitStringTextRectArray;
615 mEditorRect = aOther.mEditorRect;
617 // Only when there is one composition, the TextComposition instance in this
618 // process is managing the composition in the remote process. Therefore,
619 // we shouldn't update composition start offset of TextComposition with
620 // old composition which is still being handled by the child process.
621 if (mWidgetHasComposition && mPendingCompositionCount == 1 &&
622 mCompositionStart.isSome()) {
623 IMEStateManager::MaybeStartOffsetUpdatedInChild(aWidget,
624 mCompositionStart.value());
627 // When this instance allows to query content relative to composition string,
628 // we should modify mCompositionStart with the latest information in the
629 // remote process because now we have the information around the composition
630 // string.
631 mCompositionStartInChild = aOther.mCompositionStart;
632 if (mWidgetHasComposition || mPendingCommitCount) {
633 if (mCompositionStartInChild.isSome()) {
634 if (mCompositionStart.valueOr(UINT32_MAX) !=
635 mCompositionStartInChild.value()) {
636 mCompositionStart = mCompositionStartInChild;
637 mPendingCommitLength = 0;
639 } else if (mCompositionStart.isSome() && mSelection.isSome() &&
640 mSelection->mHasRange &&
641 mCompositionStart.value() != mSelection->StartOffset()) {
642 mCompositionStart = Some(mSelection->StartOffset());
643 mPendingCommitLength = 0;
647 MOZ_LOG(
648 sContentCacheLog, LogLevel::Info,
649 ("0x%p AssignContent(aNotification=%s), "
650 "Succeeded, mText=%s, mSelection=%s, mFirstCharRect=%s, "
651 "mCaret=%s, mTextRectArray=%s, mWidgetHasComposition=%s, "
652 "mPendingCompositionCount=%u, mCompositionStart=%s, "
653 "mPendingCommitLength=%u, mEditorRect=%s, "
654 "mLastCommitStringTextRectArray=%s",
655 this, GetNotificationName(aNotification),
656 PrintStringDetail(mText, PrintStringDetail::kMaxLengthForEditor).get(),
657 ToString(mSelection).c_str(), ToString(mFirstCharRect).c_str(),
658 ToString(mCaret).c_str(), ToString(mTextRectArray).c_str(),
659 GetBoolName(mWidgetHasComposition), mPendingCompositionCount,
660 ToString(mCompositionStart).c_str(), mPendingCommitLength,
661 ToString(mEditorRect).c_str(),
662 ToString(mLastCommitStringTextRectArray).c_str()));
665 bool ContentCacheInParent::HandleQueryContentEvent(
666 WidgetQueryContentEvent& aEvent, nsIWidget* aWidget) const {
667 MOZ_ASSERT(aWidget);
669 // ContentCache doesn't store offset of its start with XP linebreaks.
670 // So, we don't support to query contents relative to composition start
671 // offset with XP linebreaks.
672 if (NS_WARN_IF(!aEvent.mUseNativeLineBreak)) {
673 MOZ_LOG(sContentCacheLog, LogLevel::Error,
674 ("0x%p HandleQueryContentEvent(), FAILED due to query with XP "
675 "linebreaks",
676 this));
677 return false;
680 if (NS_WARN_IF(!aEvent.mInput.IsValidOffset())) {
681 MOZ_LOG(
682 sContentCacheLog, LogLevel::Error,
683 ("0x%p HandleQueryContentEvent(), FAILED due to invalid offset", this));
684 return false;
687 if (NS_WARN_IF(!aEvent.mInput.IsValidEventMessage(aEvent.mMessage))) {
688 MOZ_LOG(
689 sContentCacheLog, LogLevel::Error,
690 ("0x%p HandleQueryContentEvent(), FAILED due to invalid event message",
691 this));
692 return false;
695 bool isRelativeToInsertionPoint = aEvent.mInput.mRelativeToInsertionPoint;
696 if (isRelativeToInsertionPoint) {
697 MOZ_LOG(sContentCacheLog, LogLevel::Debug,
698 ("0x%p HandleQueryContentEvent(), "
699 "making offset absolute... aEvent={ mMessage=%s, mInput={ "
700 "mOffset=%" PRId64 ", mLength=%" PRIu32 " } }, "
701 "mWidgetHasComposition=%s, mPendingCommitCount=%" PRIu8
702 ", mCompositionStart=%" PRIu32 ", "
703 "mPendingCommitLength=%" PRIu32 ", mSelection=%s",
704 this, ToChar(aEvent.mMessage), aEvent.mInput.mOffset,
705 aEvent.mInput.mLength, GetBoolName(mWidgetHasComposition),
706 mPendingCommitCount, mCompositionStart.valueOr(UINT32_MAX),
707 mPendingCommitLength, ToString(mSelection).c_str()));
708 if (mWidgetHasComposition || mPendingCommitCount) {
709 if (NS_WARN_IF(mCompositionStart.isNothing()) ||
710 NS_WARN_IF(!aEvent.mInput.MakeOffsetAbsolute(
711 mCompositionStart.value() + mPendingCommitLength))) {
712 MOZ_LOG(
713 sContentCacheLog, LogLevel::Error,
714 ("0x%p HandleQueryContentEvent(), FAILED due to "
715 "aEvent.mInput.MakeOffsetAbsolute(mCompositionStart + "
716 "mPendingCommitLength) failure, "
717 "mCompositionStart=%" PRIu32 ", mPendingCommitLength=%" PRIu32 ", "
718 "aEvent={ mMessage=%s, mInput={ mOffset=%" PRId64
719 ", mLength=%" PRIu32 " } }",
720 this, mCompositionStart.valueOr(UINT32_MAX), mPendingCommitLength,
721 ToChar(aEvent.mMessage), aEvent.mInput.mOffset,
722 aEvent.mInput.mLength));
723 return false;
725 } else if (NS_WARN_IF(mSelection.isNothing())) {
726 MOZ_LOG(sContentCacheLog, LogLevel::Error,
727 ("0x%p HandleQueryContentEvent(), FAILED due to mSelection is "
728 "Nothing",
729 this));
730 return false;
731 } else if (NS_WARN_IF(mSelection->mHasRange)) {
732 MOZ_LOG(sContentCacheLog, LogLevel::Error,
733 ("0x%p HandleQueryContentEvent(), FAILED due to there is no "
734 "selection range, but the query requested with relative offset "
735 "from selection",
736 this));
737 return false;
738 } else if (NS_WARN_IF(!aEvent.mInput.MakeOffsetAbsolute(
739 mSelection->StartOffset() + mPendingCommitLength))) {
740 MOZ_LOG(sContentCacheLog, LogLevel::Error,
741 ("0x%p HandleQueryContentEvent(), FAILED due to "
742 "aEvent.mInput.MakeOffsetAbsolute(mSelection->StartOffset() + "
743 "mPendingCommitLength) failure, mSelection=%s, "
744 "mPendingCommitLength=%" PRIu32 ", aEvent={ mMessage=%s, "
745 "mInput={ mOffset=%" PRId64 ", mLength=%" PRIu32 " } }",
746 this, ToString(mSelection).c_str(), mPendingCommitLength,
747 ToChar(aEvent.mMessage), aEvent.mInput.mOffset,
748 aEvent.mInput.mLength));
749 return false;
753 switch (aEvent.mMessage) {
754 case eQuerySelectedText:
755 MOZ_LOG(sContentCacheLog, LogLevel::Info,
756 ("0x%p HandleQueryContentEvent(aEvent={ "
757 "mMessage=eQuerySelectedText }, aWidget=0x%p)",
758 this, aWidget));
759 if (MOZ_UNLIKELY(NS_WARN_IF(mSelection.isNothing()))) {
760 // If content cache hasn't been initialized properly, make the query
761 // failed.
762 MOZ_LOG(sContentCacheLog, LogLevel::Error,
763 ("0x%p HandleQueryContentEvent(), FAILED because mSelection "
764 "is Nothing",
765 this));
766 return false;
768 MOZ_DIAGNOSTIC_ASSERT_IF(!mSelection->IsCollapsed(), mText.isSome());
769 MOZ_DIAGNOSTIC_ASSERT_IF(!mSelection->IsCollapsed(),
770 mSelection->EndOffset() <= mText->Length());
771 aEvent.EmplaceReply();
772 aEvent.mReply->mFocusedWidget = aWidget;
773 if (mSelection->mHasRange) {
774 if (MOZ_LIKELY(mText.isSome())) {
775 aEvent.mReply->mOffsetAndData.emplace(
776 mSelection->StartOffset(),
777 Substring(mText.ref(), mSelection->StartOffset(),
778 mSelection->Length()),
779 OffsetAndDataFor::SelectedString);
780 } else {
781 // TODO: Investigate this case. I find this during
782 // test_mousecapture.xhtml on Linux.
783 aEvent.mReply->mOffsetAndData.emplace(
784 0u, EmptyString(), OffsetAndDataFor::SelectedString);
787 aEvent.mReply->mWritingMode = mSelection->mWritingMode;
788 MOZ_LOG(sContentCacheLog, LogLevel::Info,
789 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
790 "mMessage=eQuerySelectedText, mReply=%s }",
791 this, ToString(aEvent.mReply).c_str()));
792 return true;
793 case eQueryTextContent: {
794 MOZ_LOG(sContentCacheLog, LogLevel::Info,
795 ("0x%p HandleQueryContentEvent(aEvent={ "
796 "mMessage=eQueryTextContent, mInput={ mOffset=%" PRId64
797 ", mLength=%u } }, aWidget=0x%p), mText->Length()=%zu",
798 this, aEvent.mInput.mOffset, aEvent.mInput.mLength, aWidget,
799 mText.isSome() ? mText->Length() : 0u));
800 if (MOZ_UNLIKELY(NS_WARN_IF(mText.isNothing()))) {
801 MOZ_LOG(sContentCacheLog, LogLevel::Error,
802 ("0x%p HandleQueryContentEvent(), FAILED because "
803 "there is no text data",
804 this));
805 return false;
807 const uint32_t inputOffset = aEvent.mInput.mOffset;
808 const uint32_t inputEndOffset = std::min<uint32_t>(
809 aEvent.mInput.EndOffset(), mText.isSome() ? mText->Length() : 0u);
810 if (MOZ_UNLIKELY(NS_WARN_IF(inputEndOffset < inputOffset))) {
811 MOZ_LOG(sContentCacheLog, LogLevel::Error,
812 ("0x%p HandleQueryContentEvent(), FAILED because "
813 "inputOffset=%u is larger than inputEndOffset=%u",
814 this, inputOffset, inputEndOffset));
815 return false;
817 aEvent.EmplaceReply();
818 aEvent.mReply->mFocusedWidget = aWidget;
819 const nsAString& textInQueriedRange =
820 inputEndOffset > inputOffset
821 ? static_cast<const nsAString&>(Substring(
822 mText.ref(), inputOffset, inputEndOffset - inputOffset))
823 : static_cast<const nsAString&>(EmptyString());
824 aEvent.mReply->mOffsetAndData.emplace(inputOffset, textInQueriedRange,
825 OffsetAndDataFor::EditorString);
826 // TODO: Support font ranges
827 MOZ_LOG(sContentCacheLog, LogLevel::Info,
828 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
829 "mMessage=eQueryTextContent, mReply=%s }",
830 this, ToString(aEvent.mReply).c_str()));
831 return true;
833 case eQueryTextRect: {
834 MOZ_LOG(sContentCacheLog, LogLevel::Info,
835 ("0x%p HandleQueryContentEvent("
836 "aEvent={ mMessage=eQueryTextRect, mInput={ mOffset=%" PRId64
837 ", mLength=%u } }, aWidget=0x%p), mText->Length()=%zu",
838 this, aEvent.mInput.mOffset, aEvent.mInput.mLength, aWidget,
839 mText.isSome() ? mText->Length() : 0u));
840 // Note that if the query is relative to insertion point, the query was
841 // probably requested by native IME. In such case, we should return
842 // non-empty rect since returning failure causes IME showing its window
843 // at odd position.
844 LayoutDeviceIntRect textRect;
845 if (aEvent.mInput.mLength) {
846 if (MOZ_UNLIKELY(NS_WARN_IF(
847 !GetUnionTextRects(aEvent.mInput.mOffset, aEvent.mInput.mLength,
848 isRelativeToInsertionPoint, textRect)))) {
849 // XXX We don't have cache for this request.
850 MOZ_LOG(sContentCacheLog, LogLevel::Error,
851 ("0x%p HandleQueryContentEvent(), FAILED to get union rect",
852 this));
853 return false;
855 } else {
856 // If the length is 0, we should return caret rect instead.
857 if (NS_WARN_IF(!GetCaretRect(aEvent.mInput.mOffset,
858 isRelativeToInsertionPoint, textRect))) {
859 MOZ_LOG(sContentCacheLog, LogLevel::Error,
860 ("0x%p HandleQueryContentEvent(), FAILED to get caret rect",
861 this));
862 return false;
865 aEvent.EmplaceReply();
866 aEvent.mReply->mFocusedWidget = aWidget;
867 aEvent.mReply->mRect = textRect;
868 const nsAString& textInQueriedRange =
869 mText.isSome() && aEvent.mInput.mOffset <
870 static_cast<int64_t>(
871 mText.isSome() ? mText->Length() : 0u)
872 ? static_cast<const nsAString&>(
873 Substring(mText.ref(), aEvent.mInput.mOffset,
874 mText->Length() >= aEvent.mInput.EndOffset()
875 ? aEvent.mInput.mLength
876 : UINT32_MAX))
877 : static_cast<const nsAString&>(EmptyString());
878 aEvent.mReply->mOffsetAndData.emplace(aEvent.mInput.mOffset,
879 textInQueriedRange,
880 OffsetAndDataFor::EditorString);
881 // XXX This may be wrong if storing range isn't in the selection range.
882 aEvent.mReply->mWritingMode = mSelection->mWritingMode;
883 MOZ_LOG(sContentCacheLog, LogLevel::Info,
884 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
885 "mMessage=eQueryTextRect mReply=%s }",
886 this, ToString(aEvent.mReply).c_str()));
887 return true;
889 case eQueryCaretRect: {
890 MOZ_LOG(
891 sContentCacheLog, LogLevel::Info,
892 ("0x%p HandleQueryContentEvent(aEvent={ mMessage=eQueryCaretRect, "
893 "mInput={ mOffset=%" PRId64
894 " } }, aWidget=0x%p), mText->Length()=%zu",
895 this, aEvent.mInput.mOffset, aWidget,
896 mText.isSome() ? mText->Length() : 0u));
897 // Note that if the query is relative to insertion point, the query was
898 // probably requested by native IME. In such case, we should return
899 // non-empty rect since returning failure causes IME showing its window
900 // at odd position.
901 LayoutDeviceIntRect caretRect;
902 if (NS_WARN_IF(!GetCaretRect(aEvent.mInput.mOffset,
903 isRelativeToInsertionPoint, caretRect))) {
904 MOZ_LOG(sContentCacheLog, LogLevel::Error,
905 ("0x%p HandleQueryContentEvent(),FAILED to get caret rect",
906 this));
907 return false;
909 aEvent.EmplaceReply();
910 aEvent.mReply->mFocusedWidget = aWidget;
911 aEvent.mReply->mRect = caretRect;
912 aEvent.mReply->mOffsetAndData.emplace(aEvent.mInput.mOffset,
913 EmptyString(),
914 OffsetAndDataFor::SelectedString);
915 // TODO: Set mWritingMode here
916 MOZ_LOG(sContentCacheLog, LogLevel::Info,
917 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
918 "mMessage=eQueryCaretRect, mReply=%s }",
919 this, ToString(aEvent.mReply).c_str()));
920 return true;
922 case eQueryEditorRect:
923 MOZ_LOG(sContentCacheLog, LogLevel::Info,
924 ("0x%p HandleQueryContentEvent(aEvent={ "
925 "mMessage=eQueryEditorRect }, aWidget=0x%p)",
926 this, aWidget));
927 // XXX This query should fail if no editable elmenet has focus. Or,
928 // perhaps, should return rect of the window instead.
929 aEvent.EmplaceReply();
930 aEvent.mReply->mFocusedWidget = aWidget;
931 aEvent.mReply->mRect = mEditorRect;
932 MOZ_LOG(sContentCacheLog, LogLevel::Info,
933 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
934 "mMessage=eQueryEditorRect, mReply=%s }",
935 this, ToString(aEvent.mReply).c_str()));
936 return true;
937 default:
938 aEvent.EmplaceReply();
939 aEvent.mReply->mFocusedWidget = aWidget;
940 if (NS_WARN_IF(aEvent.Failed())) {
941 MOZ_LOG(
942 sContentCacheLog, LogLevel::Error,
943 ("0x%p HandleQueryContentEvent(), FAILED due to not set enough "
944 "data, aEvent={ mMessage=%s, mReply=%s }",
945 this, ToChar(aEvent.mMessage), ToString(aEvent.mReply).c_str()));
946 return false;
948 MOZ_LOG(sContentCacheLog, LogLevel::Info,
949 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
950 "mMessage=%s, mReply=%s }",
951 this, ToChar(aEvent.mMessage), ToString(aEvent.mReply).c_str()));
952 return true;
956 bool ContentCacheInParent::GetTextRect(uint32_t aOffset,
957 bool aRoundToExistingOffset,
958 LayoutDeviceIntRect& aTextRect) const {
959 MOZ_LOG(
960 sContentCacheLog, LogLevel::Info,
961 ("0x%p GetTextRect(aOffset=%u, aRoundToExistingOffset=%s), "
962 "mTextRectArray=%s, mSelection=%s, mLastCommitStringTextRectArray=%s",
963 this, aOffset, GetBoolName(aRoundToExistingOffset),
964 ToString(mTextRectArray).c_str(), ToString(mSelection).c_str(),
965 ToString(mLastCommitStringTextRectArray).c_str()));
967 if (!aOffset) {
968 NS_WARNING_ASSERTION(!mFirstCharRect.IsEmpty(), "empty rect");
969 aTextRect = mFirstCharRect;
970 return !aTextRect.IsEmpty();
972 if (mSelection.isSome() && mSelection->mHasRange) {
973 if (aOffset == mSelection->mAnchor) {
974 NS_WARNING_ASSERTION(
975 !mSelection->mAnchorCharRects[eNextCharRect].IsEmpty(), "empty rect");
976 aTextRect = mSelection->mAnchorCharRects[eNextCharRect];
977 return !aTextRect.IsEmpty();
979 if (mSelection->mAnchor && aOffset == mSelection->mAnchor - 1) {
980 NS_WARNING_ASSERTION(
981 !mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty(), "empty rect");
982 aTextRect = mSelection->mAnchorCharRects[ePrevCharRect];
983 return !aTextRect.IsEmpty();
985 if (aOffset == mSelection->mFocus) {
986 NS_WARNING_ASSERTION(
987 !mSelection->mFocusCharRects[eNextCharRect].IsEmpty(), "empty rect");
988 aTextRect = mSelection->mFocusCharRects[eNextCharRect];
989 return !aTextRect.IsEmpty();
991 if (mSelection->mFocus && aOffset == mSelection->mFocus - 1) {
992 NS_WARNING_ASSERTION(
993 !mSelection->mFocusCharRects[ePrevCharRect].IsEmpty(), "empty rect");
994 aTextRect = mSelection->mFocusCharRects[ePrevCharRect];
995 return !aTextRect.IsEmpty();
999 if (mTextRectArray.isSome() && mTextRectArray->IsOffsetInRange(aOffset)) {
1000 aTextRect = mTextRectArray->GetRect(aOffset);
1001 return !aTextRect.IsEmpty();
1004 if (mLastCommitStringTextRectArray.isSome() &&
1005 mLastCommitStringTextRectArray->IsOffsetInRange(aOffset)) {
1006 aTextRect = mLastCommitStringTextRectArray->GetRect(aOffset);
1007 return !aTextRect.IsEmpty();
1010 if (!aRoundToExistingOffset) {
1011 aTextRect.SetEmpty();
1012 return false;
1015 if (mTextRectArray.isNothing() || !mTextRectArray->HasRects()) {
1016 // If there are no rects in mTextRectArray, we should refer the start of
1017 // the selection if there is because IME must query a char rect around it if
1018 // there is no composition.
1019 if (mSelection.isNothing()) {
1020 // Unfortunately, there is no data about text rect...
1021 aTextRect.SetEmpty();
1022 return false;
1024 aTextRect = mSelection->StartCharRect();
1025 return !aTextRect.IsEmpty();
1028 // Although we may have mLastCommitStringTextRectArray here and it must have
1029 // previous character rects at selection. However, we should stop using it
1030 // because it's stored really short time after commiting a composition.
1031 // So, multiple query may return different rect and it may cause flickerling
1032 // the IME UI.
1033 uint32_t offset = aOffset;
1034 if (offset < mTextRectArray->StartOffset()) {
1035 offset = mTextRectArray->StartOffset();
1036 } else {
1037 offset = mTextRectArray->EndOffset() - 1;
1039 aTextRect = mTextRectArray->GetRect(offset);
1040 return !aTextRect.IsEmpty();
1043 bool ContentCacheInParent::GetUnionTextRects(
1044 uint32_t aOffset, uint32_t aLength, bool aRoundToExistingOffset,
1045 LayoutDeviceIntRect& aUnionTextRect) const {
1046 MOZ_LOG(sContentCacheLog, LogLevel::Info,
1047 ("0x%p GetUnionTextRects(aOffset=%u, "
1048 "aLength=%u, aRoundToExistingOffset=%s), mTextRectArray=%s, "
1049 "mSelection=%s, mLastCommitStringTextRectArray=%s",
1050 this, aOffset, aLength, GetBoolName(aRoundToExistingOffset),
1051 ToString(mTextRectArray).c_str(), ToString(mSelection).c_str(),
1052 ToString(mLastCommitStringTextRectArray).c_str()));
1054 CheckedInt<uint32_t> endOffset = CheckedInt<uint32_t>(aOffset) + aLength;
1055 if (!endOffset.isValid()) {
1056 return false;
1059 if (mSelection.isSome() && !mSelection->IsCollapsed() &&
1060 aOffset == mSelection->StartOffset() && aLength == mSelection->Length()) {
1061 NS_WARNING_ASSERTION(!mSelection->mRect.IsEmpty(), "empty rect");
1062 aUnionTextRect = mSelection->mRect;
1063 return !aUnionTextRect.IsEmpty();
1066 if (aLength == 1) {
1067 if (!aOffset) {
1068 NS_WARNING_ASSERTION(!mFirstCharRect.IsEmpty(), "empty rect");
1069 aUnionTextRect = mFirstCharRect;
1070 return !aUnionTextRect.IsEmpty();
1072 if (mSelection.isSome() && mSelection->mHasRange) {
1073 if (aOffset == mSelection->mAnchor) {
1074 NS_WARNING_ASSERTION(
1075 !mSelection->mAnchorCharRects[eNextCharRect].IsEmpty(),
1076 "empty rect");
1077 aUnionTextRect = mSelection->mAnchorCharRects[eNextCharRect];
1078 return !aUnionTextRect.IsEmpty();
1080 if (mSelection->mAnchor && aOffset == mSelection->mAnchor - 1) {
1081 NS_WARNING_ASSERTION(
1082 !mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty(),
1083 "empty rect");
1084 aUnionTextRect = mSelection->mAnchorCharRects[ePrevCharRect];
1085 return !aUnionTextRect.IsEmpty();
1087 if (aOffset == mSelection->mFocus) {
1088 NS_WARNING_ASSERTION(
1089 !mSelection->mFocusCharRects[eNextCharRect].IsEmpty(),
1090 "empty rect");
1091 aUnionTextRect = mSelection->mFocusCharRects[eNextCharRect];
1092 return !aUnionTextRect.IsEmpty();
1094 if (mSelection->mFocus && aOffset == mSelection->mFocus - 1) {
1095 NS_WARNING_ASSERTION(
1096 !mSelection->mFocusCharRects[ePrevCharRect].IsEmpty(),
1097 "empty rect");
1098 aUnionTextRect = mSelection->mFocusCharRects[ePrevCharRect];
1099 return !aUnionTextRect.IsEmpty();
1104 // Even if some text rects are not cached of the queried range,
1105 // we should return union rect when the first character's rect is cached
1106 // since the first character rect is important and the others are not so
1107 // in most cases.
1109 if (!aOffset && mSelection.isSome() && mSelection->mHasRange &&
1110 aOffset != mSelection->mAnchor && aOffset != mSelection->mFocus &&
1111 (mTextRectArray.isNothing() ||
1112 !mTextRectArray->IsOffsetInRange(aOffset)) &&
1113 (mLastCommitStringTextRectArray.isNothing() ||
1114 !mLastCommitStringTextRectArray->IsOffsetInRange(aOffset))) {
1115 // The first character rect isn't cached.
1116 return false;
1119 // Use mLastCommitStringTextRectArray only when it overlaps with aOffset
1120 // even if aROundToExistingOffset is true for avoiding flickerling IME UI.
1121 // See the last comment in GetTextRect() for the detail.
1122 if (mLastCommitStringTextRectArray.isSome() &&
1123 mLastCommitStringTextRectArray->IsOverlappingWith(aOffset, aLength)) {
1124 aUnionTextRect =
1125 mLastCommitStringTextRectArray->GetUnionRectAsFarAsPossible(
1126 aOffset, aLength, aRoundToExistingOffset);
1127 } else {
1128 aUnionTextRect.SetEmpty();
1131 if (mTextRectArray.isSome() &&
1132 ((aRoundToExistingOffset && mTextRectArray->HasRects()) ||
1133 mTextRectArray->IsOverlappingWith(aOffset, aLength))) {
1134 aUnionTextRect =
1135 aUnionTextRect.Union(mTextRectArray->GetUnionRectAsFarAsPossible(
1136 aOffset, aLength, aRoundToExistingOffset));
1139 if (!aOffset) {
1140 aUnionTextRect = aUnionTextRect.Union(mFirstCharRect);
1142 if (mSelection.isSome() && mSelection->mHasRange) {
1143 if (aOffset <= mSelection->mAnchor &&
1144 mSelection->mAnchor < endOffset.value()) {
1145 aUnionTextRect =
1146 aUnionTextRect.Union(mSelection->mAnchorCharRects[eNextCharRect]);
1148 if (mSelection->mAnchor && aOffset <= mSelection->mAnchor - 1 &&
1149 mSelection->mAnchor - 1 < endOffset.value()) {
1150 aUnionTextRect =
1151 aUnionTextRect.Union(mSelection->mAnchorCharRects[ePrevCharRect]);
1153 if (aOffset <= mSelection->mFocus &&
1154 mSelection->mFocus < endOffset.value()) {
1155 aUnionTextRect =
1156 aUnionTextRect.Union(mSelection->mFocusCharRects[eNextCharRect]);
1158 if (mSelection->mFocus && aOffset <= mSelection->mFocus - 1 &&
1159 mSelection->mFocus - 1 < endOffset.value()) {
1160 aUnionTextRect =
1161 aUnionTextRect.Union(mSelection->mFocusCharRects[ePrevCharRect]);
1165 return !aUnionTextRect.IsEmpty();
1168 bool ContentCacheInParent::GetCaretRect(uint32_t aOffset,
1169 bool aRoundToExistingOffset,
1170 LayoutDeviceIntRect& aCaretRect) const {
1171 MOZ_LOG(sContentCacheLog, LogLevel::Info,
1172 ("0x%p GetCaretRect(aOffset=%u, aRoundToExistingOffset=%s), "
1173 "mCaret=%s, mTextRectArray=%s, mSelection=%s, mFirstCharRect=%s",
1174 this, aOffset, GetBoolName(aRoundToExistingOffset),
1175 ToString(mCaret).c_str(), ToString(mTextRectArray).c_str(),
1176 ToString(mSelection).c_str(), ToString(mFirstCharRect).c_str()));
1178 if (mCaret.isSome() && mCaret->mOffset == aOffset) {
1179 aCaretRect = mCaret->mRect;
1180 return true;
1183 // Guess caret rect from the text rect if it's stored.
1184 if (!GetTextRect(aOffset, aRoundToExistingOffset, aCaretRect)) {
1185 // There might be previous character rect in the cache. If so, we can
1186 // guess the caret rect with it.
1187 if (!aOffset ||
1188 !GetTextRect(aOffset - 1, aRoundToExistingOffset, aCaretRect)) {
1189 aCaretRect.SetEmpty();
1190 return false;
1193 if (mSelection.isSome() && mSelection->mWritingMode.IsVertical()) {
1194 aCaretRect.MoveToY(aCaretRect.YMost());
1195 } else {
1196 // XXX bidi-unaware.
1197 aCaretRect.MoveToX(aCaretRect.XMost());
1201 // XXX This is not bidi aware because we don't cache each character's
1202 // direction. However, this is usually used by IME, so, assuming the
1203 // character is in LRT context must not cause any problem.
1204 if (mSelection.isSome() && mSelection->mWritingMode.IsVertical()) {
1205 aCaretRect.SetHeight(mCaret.isSome() ? mCaret->mRect.Height() : 1);
1206 } else {
1207 aCaretRect.SetWidth(mCaret.isSome() ? mCaret->mRect.Width() : 1);
1209 return true;
1212 bool ContentCacheInParent::OnCompositionEvent(
1213 const WidgetCompositionEvent& aEvent) {
1214 MOZ_LOG(
1215 sContentCacheLog, LogLevel::Info,
1216 ("0x%p OnCompositionEvent(aEvent={ "
1217 "mMessage=%s, mData=\"%s\", mRanges->Length()=%zu }), "
1218 "mPendingEventsNeedingAck=%u, mWidgetHasComposition=%s, "
1219 "mPendingCompositionCount=%" PRIu8 ", mPendingCommitCount=%" PRIu8 ", "
1220 "mIsChildIgnoringCompositionEvents=%s, mCommitStringByRequest=0x%p",
1221 this, ToChar(aEvent.mMessage),
1222 PrintStringDetail(aEvent.mData,
1223 PrintStringDetail::kMaxLengthForCompositionString)
1224 .get(),
1225 aEvent.mRanges ? aEvent.mRanges->Length() : 0, mPendingEventsNeedingAck,
1226 GetBoolName(mWidgetHasComposition), mPendingCompositionCount,
1227 mPendingCommitCount, GetBoolName(mIsChildIgnoringCompositionEvents),
1228 mCommitStringByRequest));
1230 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1231 mDispatchedEventMessages.AppendElement(aEvent.mMessage);
1232 #endif // #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1234 // We must be able to simulate the selection because
1235 // we might not receive selection updates in time
1236 if (!mWidgetHasComposition) {
1237 if (mCompositionStartInChild.isSome()) {
1238 // If there is pending composition in the remote process, let's use
1239 // its start offset temporarily because this stores a lot of information
1240 // around it and the user must look around there, so, showing some UI
1241 // around it must make sense.
1242 mCompositionStart = mCompositionStartInChild;
1243 } else {
1244 mCompositionStart = Some(mSelection.isSome() && mSelection->mHasRange
1245 ? mSelection->StartOffset()
1246 : 0u);
1248 MOZ_ASSERT(aEvent.mMessage == eCompositionStart);
1249 MOZ_RELEASE_ASSERT(mPendingCompositionCount < UINT8_MAX);
1250 mPendingCompositionCount++;
1253 mWidgetHasComposition = !aEvent.CausesDOMCompositionEndEvent();
1255 if (!mWidgetHasComposition) {
1256 // mCompositionStart will be reset when commit event is completely handled
1257 // in the remote process.
1258 if (mPendingCompositionCount == 1) {
1259 mPendingCommitLength = aEvent.mData.Length();
1261 mPendingCommitCount++;
1262 } else if (aEvent.mMessage != eCompositionStart) {
1263 mCompositionString = aEvent.mData;
1266 // During REQUEST_TO_COMMIT_COMPOSITION or REQUEST_TO_CANCEL_COMPOSITION,
1267 // widget usually sends a eCompositionChange and/or eCompositionCommit event
1268 // to finalize or clear the composition, respectively. In this time,
1269 // we need to intercept all composition events here and pass the commit
1270 // string for returning to the remote process as a result of
1271 // RequestIMEToCommitComposition(). Then, eCommitComposition event will
1272 // be dispatched with the committed string in the remote process internally.
1273 if (mCommitStringByRequest) {
1274 if (aEvent.mMessage == eCompositionCommitAsIs) {
1275 *mCommitStringByRequest = mCompositionString;
1276 } else {
1277 MOZ_ASSERT(aEvent.mMessage == eCompositionChange ||
1278 aEvent.mMessage == eCompositionCommit);
1279 *mCommitStringByRequest = aEvent.mData;
1281 // We need to wait eCompositionCommitRequestHandled from the remote process
1282 // in this case. Therefore, mPendingEventsNeedingAck needs to be
1283 // incremented here. Additionally, we stop sending eCompositionCommit(AsIs)
1284 // event. Therefore, we need to decrement mPendingCommitCount which has
1285 // been incremented above.
1286 if (!mWidgetHasComposition) {
1287 mPendingEventsNeedingAck++;
1288 MOZ_DIAGNOSTIC_ASSERT(mPendingCommitCount);
1289 if (mPendingCommitCount) {
1290 mPendingCommitCount--;
1293 return false;
1296 mPendingEventsNeedingAck++;
1297 return true;
1300 void ContentCacheInParent::OnSelectionEvent(
1301 const WidgetSelectionEvent& aSelectionEvent) {
1302 MOZ_LOG(
1303 sContentCacheLog, LogLevel::Info,
1304 ("0x%p OnSelectionEvent(aEvent={ "
1305 "mMessage=%s, mOffset=%u, mLength=%u, mReversed=%s, "
1306 "mExpandToClusterBoundary=%s, mUseNativeLineBreak=%s }), "
1307 "mPendingEventsNeedingAck=%u, mWidgetHasComposition=%s, "
1308 "mPendingCompositionCount=%" PRIu8 ", mPendingCommitCount=%" PRIu8 ", "
1309 "mIsChildIgnoringCompositionEvents=%s",
1310 this, ToChar(aSelectionEvent.mMessage), aSelectionEvent.mOffset,
1311 aSelectionEvent.mLength, GetBoolName(aSelectionEvent.mReversed),
1312 GetBoolName(aSelectionEvent.mExpandToClusterBoundary),
1313 GetBoolName(aSelectionEvent.mUseNativeLineBreak),
1314 mPendingEventsNeedingAck, GetBoolName(mWidgetHasComposition),
1315 mPendingCompositionCount, mPendingCommitCount,
1316 GetBoolName(mIsChildIgnoringCompositionEvents)));
1318 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1319 mDispatchedEventMessages.AppendElement(aSelectionEvent.mMessage);
1320 #endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
1322 mPendingEventsNeedingAck++;
1325 void ContentCacheInParent::OnEventNeedingAckHandled(nsIWidget* aWidget,
1326 EventMessage aMessage) {
1327 // This is called when the child process receives WidgetCompositionEvent or
1328 // WidgetSelectionEvent.
1330 MOZ_LOG(
1331 sContentCacheLog, LogLevel::Info,
1332 ("0x%p OnEventNeedingAckHandled(aWidget=0x%p, "
1333 "aMessage=%s), mPendingEventsNeedingAck=%u, "
1334 "mWidgetHasComposition=%s, mPendingCompositionCount=%" PRIu8 ", "
1335 "mPendingCommitCount=%" PRIu8 ", mIsChildIgnoringCompositionEvents=%s",
1336 this, aWidget, ToChar(aMessage), mPendingEventsNeedingAck,
1337 GetBoolName(mWidgetHasComposition), mPendingCompositionCount,
1338 mPendingCommitCount, GetBoolName(mIsChildIgnoringCompositionEvents)));
1340 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1341 mReceivedEventMessages.AppendElement(aMessage);
1342 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1344 bool isCommittedInChild =
1345 // Commit requester in the remote process has committed the composition.
1346 aMessage == eCompositionCommitRequestHandled ||
1347 // The commit event has been handled normally in the remote process.
1348 (!mIsChildIgnoringCompositionEvents &&
1349 WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage));
1351 if (isCommittedInChild) {
1352 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1353 if (mPendingCompositionCount == 1) {
1354 RemoveUnnecessaryEventMessageLog();
1356 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1358 if (NS_WARN_IF(!mPendingCompositionCount)) {
1359 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1360 nsPrintfCString info(
1361 "\nThere is no pending composition but received %s "
1362 "message from the remote child\n\n",
1363 ToChar(aMessage));
1364 AppendEventMessageLog(info);
1365 CrashReporter::AppendAppNotesToCrashReport(info);
1366 MOZ_DIAGNOSTIC_ASSERT(
1367 false, "No pending composition but received unexpected commit event");
1368 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1370 // Prevent odd behavior in release channel.
1371 mPendingCompositionCount = 1;
1374 mPendingCompositionCount--;
1376 // Forget composition string only when the latest composition string is
1377 // handled in the remote process because if there is 2 or more pending
1378 // composition, this value shouldn't be referred.
1379 if (!mPendingCompositionCount) {
1380 mCompositionString.Truncate();
1383 // Forget pending commit string length if it's handled in the remote
1384 // process. Note that this doesn't care too old composition's commit
1385 // string because in such case, we cannot return proper information
1386 // to IME synchornously.
1387 mPendingCommitLength = 0;
1390 if (WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage)) {
1391 // After the remote process receives eCompositionCommit(AsIs) event,
1392 // it'll restart to handle composition events.
1393 mIsChildIgnoringCompositionEvents = false;
1395 if (NS_WARN_IF(!mPendingCommitCount)) {
1396 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1397 nsPrintfCString info(
1398 "\nThere is no pending comment events but received "
1399 "%s message from the remote child\n\n",
1400 ToChar(aMessage));
1401 AppendEventMessageLog(info);
1402 CrashReporter::AppendAppNotesToCrashReport(info);
1403 MOZ_DIAGNOSTIC_ASSERT(
1404 false,
1405 "No pending commit events but received unexpected commit event");
1406 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1408 // Prevent odd behavior in release channel.
1409 mPendingCommitCount = 1;
1412 mPendingCommitCount--;
1413 } else if (aMessage == eCompositionCommitRequestHandled &&
1414 mPendingCommitCount) {
1415 // If the remote process commits composition synchronously after
1416 // requesting commit composition and we've already sent commit composition,
1417 // it starts to ignore following composition events until receiving
1418 // eCompositionStart event.
1419 mIsChildIgnoringCompositionEvents = true;
1422 // If neither widget (i.e., IME) nor the remote process has composition,
1423 // now, we can forget composition string informations.
1424 if (!mWidgetHasComposition && !mPendingCompositionCount &&
1425 !mPendingCommitCount) {
1426 mCompositionStart.reset();
1429 if (NS_WARN_IF(!mPendingEventsNeedingAck)) {
1430 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
1431 nsPrintfCString info(
1432 "\nThere is no pending events but received %s "
1433 "message from the remote child\n\n",
1434 ToChar(aMessage));
1435 AppendEventMessageLog(info);
1436 CrashReporter::AppendAppNotesToCrashReport(info);
1437 MOZ_DIAGNOSTIC_ASSERT(
1438 false, "No pending event message but received unexpected event");
1439 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1440 mPendingEventsNeedingAck = 1;
1442 if (--mPendingEventsNeedingAck) {
1443 return;
1446 FlushPendingNotifications(aWidget);
1449 bool ContentCacheInParent::RequestIMEToCommitComposition(
1450 nsIWidget* aWidget, bool aCancel, nsAString& aCommittedString) {
1451 MOZ_LOG(
1452 sContentCacheLog, LogLevel::Info,
1453 ("0x%p RequestToCommitComposition(aWidget=%p, "
1454 "aCancel=%s), mPendingCompositionCount=%" PRIu8 ", "
1455 "mPendingCommitCount=%" PRIu8 ", mIsChildIgnoringCompositionEvents=%s, "
1456 "IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)=%s, "
1457 "mWidgetHasComposition=%s, mCommitStringByRequest=%p",
1458 this, aWidget, GetBoolName(aCancel), mPendingCompositionCount,
1459 mPendingCommitCount, GetBoolName(mIsChildIgnoringCompositionEvents),
1460 GetBoolName(
1461 IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)),
1462 GetBoolName(mWidgetHasComposition), mCommitStringByRequest));
1464 MOZ_ASSERT(!mCommitStringByRequest);
1466 // If there are 2 or more pending compositions, we already sent
1467 // eCompositionCommit(AsIs) to the remote process. So, this request is
1468 // too late for IME. The remote process should wait following
1469 // composition events for cleaning up TextComposition and handle the
1470 // request as it's handled asynchronously.
1471 if (mPendingCompositionCount > 1) {
1472 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1473 mRequestIMEToCommitCompositionResults.AppendElement(
1474 RequestIMEToCommitCompositionResult::eToOldCompositionReceived);
1475 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1476 return false;
1479 // If there is no pending composition, we may have already sent
1480 // eCompositionCommit(AsIs) event for the active composition. If so, the
1481 // remote process will receive composition events which causes cleaning up
1482 // TextComposition. So, this shouldn't do nothing and TextComposition
1483 // should handle the request as it's handled asynchronously.
1484 // XXX Perhaps, this is wrong because TextComposition in child process
1485 // may commit the composition with current composition string in the
1486 // remote process. I.e., it may be different from actual commit string
1487 // which user typed. So, perhaps, we should return true and the commit
1488 // string.
1489 if (mPendingCommitCount) {
1490 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1491 mRequestIMEToCommitCompositionResults.AppendElement(
1492 RequestIMEToCommitCompositionResult::eToCommittedCompositionReceived);
1493 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1494 return false;
1497 // If BrowserParent which has IME focus was already changed to different one,
1498 // the request shouldn't be sent to IME because it's too late.
1499 if (!IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)) {
1500 // Use the latest composition string which may not be handled in the
1501 // remote process for avoiding data loss.
1502 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1503 mRequestIMEToCommitCompositionResults.AppendElement(
1504 RequestIMEToCommitCompositionResult::eReceivedAfterBrowserParentBlur);
1505 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1506 aCommittedString = mCompositionString;
1507 // After we return true from here, i.e., without actually requesting IME
1508 // to commit composition, we will receive eCompositionCommitRequestHandled
1509 // pseudo event message from the remote process. So, we need to increment
1510 // mPendingEventsNeedingAck here.
1511 mPendingEventsNeedingAck++;
1512 return true;
1515 RefPtr<TextComposition> composition =
1516 IMEStateManager::GetTextCompositionFor(aWidget);
1517 if (NS_WARN_IF(!composition)) {
1518 MOZ_LOG(sContentCacheLog, LogLevel::Warning,
1519 (" 0x%p RequestToCommitComposition(), "
1520 "does nothing due to no composition",
1521 this));
1522 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1523 mRequestIMEToCommitCompositionResults.AppendElement(
1524 RequestIMEToCommitCompositionResult::eReceivedButNoTextComposition);
1525 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1526 return false;
1529 mCommitStringByRequest = &aCommittedString;
1531 // Request commit or cancel composition with TextComposition because we may
1532 // have already requested to commit or cancel the composition or we may
1533 // have already received eCompositionCommit(AsIs) event. Those status are
1534 // managed by composition. So, if we don't request commit composition,
1535 // we should do nothing with native IME here.
1536 composition->RequestToCommit(aWidget, aCancel);
1538 mCommitStringByRequest = nullptr;
1540 MOZ_LOG(
1541 sContentCacheLog, LogLevel::Info,
1542 (" 0x%p RequestToCommitComposition(), "
1543 "mWidgetHasComposition=%s, the composition %s committed synchronously",
1544 this, GetBoolName(mWidgetHasComposition),
1545 composition->Destroyed() ? "WAS" : "has NOT been"));
1547 if (!composition->Destroyed()) {
1548 // When the composition isn't committed synchronously, the remote process's
1549 // TextComposition instance will synthesize commit events and wait to
1550 // receive delayed composition events. When TextComposition instances both
1551 // in this process and the remote process will be destroyed when delayed
1552 // composition events received. TextComposition instance in the parent
1553 // process will dispatch following composition events and be destroyed
1554 // normally. On the other hand, TextComposition instance in the remote
1555 // process won't dispatch following composition events and will be
1556 // destroyed by IMEStateManager::DispatchCompositionEvent().
1557 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1558 mRequestIMEToCommitCompositionResults.AppendElement(
1559 RequestIMEToCommitCompositionResult::eHandledAsynchronously);
1560 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1561 return false;
1564 // When the composition is committed synchronously, the commit string will be
1565 // returned to the remote process. Then, PuppetWidget will dispatch
1566 // eCompositionCommit event with the returned commit string (i.e., the value
1567 // is aCommittedString of this method) and that causes destroying
1568 // TextComposition instance in the remote process (Note that TextComposition
1569 // instance in this process was already destroyed).
1570 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1571 mRequestIMEToCommitCompositionResults.AppendElement(
1572 RequestIMEToCommitCompositionResult::eHandledSynchronously);
1573 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1574 return true;
1577 void ContentCacheInParent::MaybeNotifyIME(
1578 nsIWidget* aWidget, const IMENotification& aNotification) {
1579 if (!mPendingEventsNeedingAck) {
1580 IMEStateManager::NotifyIME(aNotification, aWidget, &mBrowserParent);
1581 return;
1584 switch (aNotification.mMessage) {
1585 case NOTIFY_IME_OF_SELECTION_CHANGE:
1586 mPendingSelectionChange.MergeWith(aNotification);
1587 break;
1588 case NOTIFY_IME_OF_TEXT_CHANGE:
1589 mPendingTextChange.MergeWith(aNotification);
1590 break;
1591 case NOTIFY_IME_OF_POSITION_CHANGE:
1592 mPendingLayoutChange.MergeWith(aNotification);
1593 break;
1594 case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
1595 mPendingCompositionUpdate.MergeWith(aNotification);
1596 break;
1597 default:
1598 MOZ_CRASH("Unsupported notification");
1599 break;
1603 void ContentCacheInParent::FlushPendingNotifications(nsIWidget* aWidget) {
1604 MOZ_ASSERT(!mPendingEventsNeedingAck);
1606 // If the BrowserParent's widget has already gone, this can do nothing since
1607 // widget is necessary to notify IME of something.
1608 if (!aWidget) {
1609 return;
1612 // New notifications which are notified during flushing pending notifications
1613 // should be merged again.
1614 mPendingEventsNeedingAck++;
1616 nsCOMPtr<nsIWidget> widget = aWidget;
1618 // First, text change notification should be sent because selection change
1619 // notification notifies IME of current selection range in the latest content.
1620 // So, IME may need the latest content before that.
1621 if (mPendingTextChange.HasNotification()) {
1622 IMENotification notification(mPendingTextChange);
1623 if (!widget->Destroyed()) {
1624 mPendingTextChange.Clear();
1625 IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
1629 if (mPendingSelectionChange.HasNotification()) {
1630 IMENotification notification(mPendingSelectionChange);
1631 if (!widget->Destroyed()) {
1632 mPendingSelectionChange.Clear();
1633 IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
1637 // Layout change notification should be notified after selection change
1638 // notification because IME may want to query position of new caret position.
1639 if (mPendingLayoutChange.HasNotification()) {
1640 IMENotification notification(mPendingLayoutChange);
1641 if (!widget->Destroyed()) {
1642 mPendingLayoutChange.Clear();
1643 IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
1647 // Finally, send composition update notification because it notifies IME of
1648 // finishing handling whole sending events.
1649 if (mPendingCompositionUpdate.HasNotification()) {
1650 IMENotification notification(mPendingCompositionUpdate);
1651 if (!widget->Destroyed()) {
1652 mPendingCompositionUpdate.Clear();
1653 IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
1657 if (!--mPendingEventsNeedingAck && !widget->Destroyed() &&
1658 (mPendingTextChange.HasNotification() ||
1659 mPendingSelectionChange.HasNotification() ||
1660 mPendingLayoutChange.HasNotification() ||
1661 mPendingCompositionUpdate.HasNotification())) {
1662 FlushPendingNotifications(widget);
1666 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1668 void ContentCacheInParent::RemoveUnnecessaryEventMessageLog() {
1669 bool foundLastCompositionStart = false;
1670 for (size_t i = mDispatchedEventMessages.Length(); i > 1; i--) {
1671 if (mDispatchedEventMessages[i - 1] != eCompositionStart) {
1672 continue;
1674 if (!foundLastCompositionStart) {
1675 // Find previous eCompositionStart of the latest eCompositionStart.
1676 foundLastCompositionStart = true;
1677 continue;
1679 // Remove the messages before the last 2 sets of composition events.
1680 mDispatchedEventMessages.RemoveElementsAt(0, i - 1);
1681 break;
1683 uint32_t numberOfCompositionCommitRequestHandled = 0;
1684 foundLastCompositionStart = false;
1685 for (size_t i = mReceivedEventMessages.Length(); i > 1; i--) {
1686 if (mReceivedEventMessages[i - 1] == eCompositionCommitRequestHandled) {
1687 numberOfCompositionCommitRequestHandled++;
1689 if (mReceivedEventMessages[i - 1] != eCompositionStart) {
1690 continue;
1692 if (!foundLastCompositionStart) {
1693 // Find previous eCompositionStart of the latest eCompositionStart.
1694 foundLastCompositionStart = true;
1695 continue;
1697 // Remove the messages before the last 2 sets of composition events.
1698 mReceivedEventMessages.RemoveElementsAt(0, i - 1);
1699 break;
1702 if (!numberOfCompositionCommitRequestHandled) {
1703 // If there is no eCompositionCommitRequestHandled in
1704 // mReceivedEventMessages, we don't need to store log of
1705 // RequestIMEToCommmitComposition().
1706 mRequestIMEToCommitCompositionResults.Clear();
1707 } else {
1708 // We need to keep all reason of eCompositionCommitRequestHandled, which
1709 // is sent when mRequestIMEToCommitComposition() returns true.
1710 // So, we can discard older log than the first
1711 // eCompositionCommitRequestHandled in mReceivedEventMessages.
1712 for (size_t i = mRequestIMEToCommitCompositionResults.Length(); i > 1;
1713 i--) {
1714 if (mRequestIMEToCommitCompositionResults[i - 1] ==
1715 RequestIMEToCommitCompositionResult::
1716 eReceivedAfterBrowserParentBlur ||
1717 mRequestIMEToCommitCompositionResults[i - 1] ==
1718 RequestIMEToCommitCompositionResult::eHandledSynchronously) {
1719 --numberOfCompositionCommitRequestHandled;
1720 if (!numberOfCompositionCommitRequestHandled) {
1721 mRequestIMEToCommitCompositionResults.RemoveElementsAt(0, i - 1);
1722 break;
1729 void ContentCacheInParent::AppendEventMessageLog(nsACString& aLog) const {
1730 aLog.AppendLiteral("Dispatched Event Message Log:\n");
1731 for (EventMessage message : mDispatchedEventMessages) {
1732 aLog.AppendLiteral(" ");
1733 aLog.Append(ToChar(message));
1734 aLog.AppendLiteral("\n");
1736 aLog.AppendLiteral("\nReceived Event Message Log:\n");
1737 for (EventMessage message : mReceivedEventMessages) {
1738 aLog.AppendLiteral(" ");
1739 aLog.Append(ToChar(message));
1740 aLog.AppendLiteral("\n");
1742 aLog.AppendLiteral("\nResult of RequestIMEToCommitComposition():\n");
1743 for (RequestIMEToCommitCompositionResult result :
1744 mRequestIMEToCommitCompositionResults) {
1745 aLog.AppendLiteral(" ");
1746 aLog.Append(ToReadableText(result));
1747 aLog.AppendLiteral("\n");
1749 aLog.AppendLiteral("\n");
1752 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1754 /*****************************************************************************
1755 * mozilla::ContentCache::Selection
1756 *****************************************************************************/
1758 ContentCache::Selection::Selection(
1759 const WidgetQueryContentEvent& aQuerySelectedTextEvent)
1760 : mAnchor(UINT32_MAX),
1761 mFocus(UINT32_MAX),
1762 mWritingMode(aQuerySelectedTextEvent.mReply->WritingModeRef()),
1763 mHasRange(aQuerySelectedTextEvent.mReply->mOffsetAndData.isSome()) {
1764 MOZ_ASSERT(aQuerySelectedTextEvent.mMessage == eQuerySelectedText);
1765 MOZ_ASSERT(aQuerySelectedTextEvent.Succeeded());
1766 if (mHasRange) {
1767 mAnchor = aQuerySelectedTextEvent.mReply->AnchorOffset();
1768 mFocus = aQuerySelectedTextEvent.mReply->FocusOffset();
1772 /*****************************************************************************
1773 * mozilla::ContentCache::TextRectArray
1774 *****************************************************************************/
1776 LayoutDeviceIntRect ContentCache::TextRectArray::GetRect(
1777 uint32_t aOffset) const {
1778 LayoutDeviceIntRect rect;
1779 if (IsOffsetInRange(aOffset)) {
1780 rect = mRects[aOffset - mStart];
1782 return rect;
1785 LayoutDeviceIntRect ContentCache::TextRectArray::GetUnionRect(
1786 uint32_t aOffset, uint32_t aLength) const {
1787 LayoutDeviceIntRect rect;
1788 if (!IsRangeCompletelyInRange(aOffset, aLength)) {
1789 return rect;
1791 for (uint32_t i = 0; i < aLength; i++) {
1792 rect = rect.Union(mRects[aOffset - mStart + i]);
1794 return rect;
1797 LayoutDeviceIntRect ContentCache::TextRectArray::GetUnionRectAsFarAsPossible(
1798 uint32_t aOffset, uint32_t aLength, bool aRoundToExistingOffset) const {
1799 LayoutDeviceIntRect rect;
1800 if (!HasRects() ||
1801 (!aRoundToExistingOffset && !IsOverlappingWith(aOffset, aLength))) {
1802 return rect;
1804 uint32_t startOffset = std::max(aOffset, mStart);
1805 if (aRoundToExistingOffset && startOffset >= EndOffset()) {
1806 startOffset = EndOffset() - 1;
1808 uint32_t endOffset = std::min(aOffset + aLength, EndOffset());
1809 if (aRoundToExistingOffset && endOffset < mStart + 1) {
1810 endOffset = mStart + 1;
1812 if (NS_WARN_IF(endOffset < startOffset)) {
1813 return rect;
1815 for (uint32_t i = 0; i < endOffset - startOffset; i++) {
1816 rect = rect.Union(mRects[startOffset - mStart + i]);
1818 return rect;
1821 } // namespace mozilla