1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
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"
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"
27 using namespace widget
;
29 static const char* GetBoolName(bool aBool
) { return aBool
? "true" : "false"; }
31 static const char* GetNotificationName(const IMENotification
* 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();
55 mFirstCharRect
.SetEmpty();
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
);
68 nsAutoString lastCommitString
;
69 if (aCompositionEvent
.mMessage
== eCompositionCommitAsIs
) {
70 lastCommitString
= composition
->CommitStringIfCommittedAsIs();
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
));
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
),
85 aCompositionEvent
.mData
,
86 PrintStringDetail::kMaxLengthForCompositionString
)
88 ToString(mLastCommit
).c_str()));
93 if (mLastCommit
.isSome()) {
95 sContentCacheLog
, LogLevel::Debug
,
96 ("0x%p OnCompositionEvent(), resetting the last composition string "
97 "data (aCompositionEvent={ mMessage=%s, mData=\"%s\"}, "
99 this, ToChar(aCompositionEvent
.mMessage
),
100 PrintStringDetail(aCompositionEvent
.mData
,
101 PrintStringDetail::kMaxLengthForCompositionString
)
103 ToString(mLastCommit
).c_str()));
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
)));
127 nsEventStatus status
= nsEventStatus_eIgnore
;
128 WidgetQueryContentEvent
querySelectedTextEvent(true, eQuerySelectedText
,
130 aWidget
->DispatchEvent(&querySelectedTextEvent
, status
);
131 if (NS_WARN_IF(querySelectedTextEvent
.Failed())) {
133 sContentCacheLog
, LogLevel::Error
,
134 ("0x%p CacheSelection(), FAILED, couldn't retrieve the selected text",
137 mSelection
.emplace(querySelectedTextEvent
);
140 const bool caretCached
= CacheCaret(aWidget
, aNotification
);
141 const bool textRectsCached
= CacheTextRects(aWidget
, aNotification
);
142 return caretCached
|| textRectsCached
|| querySelectedTextEvent
.Succeeded();
145 bool ContentCacheInChild::CacheCaret(nsIWidget
* aWidget
,
146 const IMENotification
* aNotification
) {
149 if (MOZ_UNLIKELY(mSelection
.isNothing())) {
153 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
154 ("0x%p CacheCaret(aWidget=0x%p, aNotification=%s)", this, aWidget
,
155 GetNotificationName(aNotification
)));
157 if (mSelection
->mHasRange
) {
158 // XXX Should be mSelection.mFocus?
159 const uint32_t offset
= mSelection
->StartOffset();
161 nsEventStatus status
= nsEventStatus_eIgnore
;
162 WidgetQueryContentEvent
queryCaretRectEvet(true, eQueryCaretRect
, aWidget
);
163 queryCaretRectEvet
.InitForQueryCaretRect(offset
);
164 aWidget
->DispatchEvent(&queryCaretRectEvet
, status
);
165 if (NS_WARN_IF(queryCaretRectEvet
.Failed())) {
166 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
167 ("0x%p CacheCaret(), FAILED, couldn't retrieve the caret rect "
172 mCaret
.emplace(offset
, queryCaretRectEvet
.mReply
->mRect
);
174 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
175 ("0x%p CacheCaret(), Succeeded, mSelection=%s, mCaret=%s", this,
176 ToString(mSelection
).c_str(), ToString(mCaret
).c_str()));
180 bool ContentCacheInChild::CacheEditorRect(
181 nsIWidget
* aWidget
, const IMENotification
* aNotification
) {
182 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
183 ("0x%p CacheEditorRect(aWidget=0x%p, aNotification=%s)", this,
184 aWidget
, GetNotificationName(aNotification
)));
186 nsEventStatus status
= nsEventStatus_eIgnore
;
187 WidgetQueryContentEvent
queryEditorRectEvent(true, eQueryEditorRect
, aWidget
);
188 aWidget
->DispatchEvent(&queryEditorRectEvent
, status
);
189 if (NS_WARN_IF(queryEditorRectEvent
.Failed())) {
191 sContentCacheLog
, LogLevel::Error
,
192 ("0x%p CacheEditorRect(), FAILED, couldn't retrieve the editor rect",
196 mEditorRect
= queryEditorRectEvent
.mReply
->mRect
;
197 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
198 ("0x%p CacheEditorRect(), Succeeded, mEditorRect=%s", this,
199 ToString(mEditorRect
).c_str()));
203 bool ContentCacheInChild::CacheText(nsIWidget
* aWidget
,
204 const IMENotification
* aNotification
) {
205 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
206 ("0x%p CacheText(aWidget=0x%p, aNotification=%s)", this, aWidget
,
207 GetNotificationName(aNotification
)));
209 nsEventStatus status
= nsEventStatus_eIgnore
;
210 WidgetQueryContentEvent
queryTextContentEvent(true, eQueryTextContent
,
212 queryTextContentEvent
.InitForQueryTextContent(0, UINT32_MAX
);
213 aWidget
->DispatchEvent(&queryTextContentEvent
, status
);
214 if (NS_WARN_IF(queryTextContentEvent
.Failed())) {
215 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
216 ("0x%p CacheText(), FAILED, couldn't retrieve whole text", this));
219 mText
= Some(nsString(queryTextContentEvent
.mReply
->DataRef()));
220 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
221 ("0x%p CacheText(), Succeeded, mText=%s", this,
222 PrintStringDetail(mText
, PrintStringDetail::kMaxLengthForEditor
)
226 // Forget last commit range if string in the range is different from the
227 // last commit string.
228 if (mLastCommit
.isSome() &&
229 (mText
.isNothing() ||
230 nsDependentSubstring(mText
.ref(), mLastCommit
->StartOffset(),
231 mLastCommit
->Length()) != mLastCommit
->DataRef())) {
232 MOZ_LOG(sContentCacheLog
, LogLevel::Debug
,
233 ("0x%p CacheText(), resetting the last composition string data "
234 "(mLastCommit=%s, current string=\"%s\")",
235 this, ToString(mLastCommit
).c_str(),
237 nsDependentSubstring(mText
.ref(), mLastCommit
->StartOffset(),
238 mLastCommit
->Length()),
239 PrintStringDetail::kMaxLengthForCompositionString
)
244 const bool selectionCached
= CacheSelection(aWidget
, aNotification
);
245 return selectionCached
|| queryTextContentEvent
.Succeeded();
248 bool ContentCacheInChild::QueryCharRect(nsIWidget
* aWidget
, uint32_t aOffset
,
249 LayoutDeviceIntRect
& aCharRect
) const {
250 aCharRect
.SetEmpty();
252 nsEventStatus status
= nsEventStatus_eIgnore
;
253 WidgetQueryContentEvent
queryTextRectEvent(true, eQueryTextRect
, aWidget
);
254 queryTextRectEvent
.InitForQueryTextRect(aOffset
, 1);
255 aWidget
->DispatchEvent(&queryTextRectEvent
, status
);
256 if (NS_WARN_IF(queryTextRectEvent
.Failed())) {
259 aCharRect
= queryTextRectEvent
.mReply
->mRect
;
261 // Guarantee the rect is not empty.
262 if (NS_WARN_IF(!aCharRect
.Height())) {
263 aCharRect
.SetHeight(1);
265 if (NS_WARN_IF(!aCharRect
.Width())) {
266 aCharRect
.SetWidth(1);
271 bool ContentCacheInChild::QueryCharRectArray(nsIWidget
* aWidget
,
272 uint32_t aOffset
, uint32_t aLength
,
273 RectArray
& aCharRectArray
) const {
274 nsEventStatus status
= nsEventStatus_eIgnore
;
275 WidgetQueryContentEvent
queryTextRectsEvent(true, eQueryTextRectArray
,
277 queryTextRectsEvent
.InitForQueryTextRectArray(aOffset
, aLength
);
278 aWidget
->DispatchEvent(&queryTextRectsEvent
, status
);
279 if (NS_WARN_IF(queryTextRectsEvent
.Failed())) {
280 aCharRectArray
.Clear();
283 aCharRectArray
= std::move(queryTextRectsEvent
.mReply
->mRectArray
);
287 bool ContentCacheInChild::CacheTextRects(nsIWidget
* aWidget
,
288 const IMENotification
* aNotification
) {
290 sContentCacheLog
, LogLevel::Info
,
291 ("0x%p CacheTextRects(aWidget=0x%p, aNotification=%s), mCaret=%s", this,
292 aWidget
, GetNotificationName(aNotification
), ToString(mCaret
).c_str()));
294 if (mSelection
.isSome()) {
295 mSelection
->ClearRects();
298 // Retrieve text rects in composition string if there is.
299 RefPtr
<TextComposition
> textComposition
=
300 IMEStateManager::GetTextCompositionFor(aWidget
);
301 if (textComposition
) {
302 // mCompositionStart may be updated by some composition event handlers.
303 // So, let's update it with the latest information.
304 mCompositionStart
= Some(textComposition
->NativeOffsetOfStartComposition());
305 // Note that TextComposition::String() may not be modified here because
306 // it's modified after all edit action listeners are performed but this
307 // is called while some of them are performed.
308 // FYI: For supporting IME which commits composition and restart new
309 // composition immediately, we should cache next character of current
311 uint32_t length
= textComposition
->LastData().Length() + 1;
312 mTextRectArray
= Some(TextRectArray(mCompositionStart
.value()));
313 if (NS_WARN_IF(!QueryCharRectArray(aWidget
, mTextRectArray
->mStart
, length
,
314 mTextRectArray
->mRects
))) {
315 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
316 ("0x%p CacheTextRects(), FAILED, "
317 "couldn't retrieve text rect array of the composition string",
319 mTextRectArray
.reset();
322 mCompositionStart
.reset();
323 mTextRectArray
.reset();
326 // Set mSelection->mAnchorCharRects
327 // If we've already have the rect in mTextRectArray, save the query cost.
328 if (mSelection
.isSome() && mSelection
->mHasRange
&& mTextRectArray
.isSome() &&
329 mTextRectArray
->IsOffsetInRange(mSelection
->mAnchor
) &&
330 (!mSelection
->mAnchor
||
331 mTextRectArray
->IsOffsetInRange(mSelection
->mAnchor
- 1))) {
332 mSelection
->mAnchorCharRects
[eNextCharRect
] =
333 mTextRectArray
->GetRect(mSelection
->mAnchor
);
334 if (mSelection
->mAnchor
) {
335 mSelection
->mAnchorCharRects
[ePrevCharRect
] =
336 mTextRectArray
->GetRect(mSelection
->mAnchor
- 1);
339 // Otherwise, get it from content even if there is no selection ranges.
342 const uint32_t startOffset
=
343 mSelection
.isSome() && mSelection
->mHasRange
&& mSelection
->mAnchor
344 ? mSelection
->mAnchor
- 1u
346 const uint32_t length
=
347 mSelection
.isSome() && mSelection
->mHasRange
&& mSelection
->mAnchor
350 if (NS_WARN_IF(!QueryCharRectArray(aWidget
, startOffset
, length
, rects
))) {
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_IF(mSelection
.isSome(),
357 mSelection
->mAnchorCharRects
[ePrevCharRect
].IsEmpty());
358 MOZ_ASSERT_IF(mSelection
.isSome(),
359 mSelection
->mAnchorCharRects
[eNextCharRect
].IsEmpty());
360 } else if (rects
.Length()) {
361 if (mSelection
.isNothing()) {
362 mSelection
.emplace(); // With no range
364 if (rects
.Length() > 1) {
365 mSelection
->mAnchorCharRects
[ePrevCharRect
] = rects
[0];
366 mSelection
->mAnchorCharRects
[eNextCharRect
] = rects
[1];
368 mSelection
->mAnchorCharRects
[eNextCharRect
] = rects
[0];
369 MOZ_ASSERT(mSelection
->mAnchorCharRects
[ePrevCharRect
].IsEmpty());
374 // Note that if mSelection is Nothing here, we've already failed to get
375 // rects in the `else` block above. In such case, we cannot get character
376 // rects around focus point.
377 if (mSelection
.isSome()) {
378 // Set mSelection->mFocusCharRects
379 // If selection is collapsed (including no selection case), the focus char
380 // rects are same as the anchor char rects so that we can just copy them.
381 if (mSelection
->IsCollapsed()) {
382 mSelection
->mFocusCharRects
[0] = mSelection
->mAnchorCharRects
[0];
383 mSelection
->mFocusCharRects
[1] = mSelection
->mAnchorCharRects
[1];
385 // If the selection range is in mTextRectArray, save the query cost.
386 else if (mTextRectArray
.isSome() &&
387 mTextRectArray
->IsOffsetInRange(mSelection
->mFocus
) &&
388 (!mSelection
->mFocus
||
389 mTextRectArray
->IsOffsetInRange(mSelection
->mFocus
- 1))) {
390 MOZ_ASSERT(mSelection
->mHasRange
);
391 mSelection
->mFocusCharRects
[eNextCharRect
] =
392 mTextRectArray
->GetRect(mSelection
->mFocus
);
393 if (mSelection
->mFocus
) {
394 mSelection
->mFocusCharRects
[ePrevCharRect
] =
395 mTextRectArray
->GetRect(mSelection
->mFocus
- 1);
398 // Otherwise, including no selection range cases, need to query the rects.
400 MOZ_ASSERT(mSelection
->mHasRange
);
402 const uint32_t startOffset
=
403 mSelection
->mFocus
? mSelection
->mFocus
- 1u : 0u;
404 const uint32_t length
= mSelection
->mFocus
? 2u : 1u;
406 !QueryCharRectArray(aWidget
, startOffset
, length
, rects
))) {
407 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
408 ("0x%p CacheTextRects(), FAILED, "
409 "couldn't retrieve text rect array around the selection focus "
411 this, mSelection
->mFocus
));
412 MOZ_ASSERT(mSelection
->mFocusCharRects
[ePrevCharRect
].IsEmpty());
413 MOZ_ASSERT(mSelection
->mFocusCharRects
[eNextCharRect
].IsEmpty());
415 if (rects
.Length() > 1) {
416 mSelection
->mFocusCharRects
[ePrevCharRect
] = rects
[0];
417 mSelection
->mFocusCharRects
[eNextCharRect
] = rects
[1];
418 } else if (rects
.Length()) {
419 mSelection
->mFocusCharRects
[eNextCharRect
] = rects
[0];
420 MOZ_ASSERT(mSelection
->mFocusCharRects
[ePrevCharRect
].IsEmpty());
426 // If there is a non-collapsed selection range, let's query the whole selected
427 // text rect. Note that the result cannot be computed from first character
428 // rect and last character rect of the selection because they both may be in
429 // middle of different line.
430 if (mSelection
.isSome() && mSelection
->mHasRange
&&
431 !mSelection
->IsCollapsed()) {
432 nsEventStatus status
= nsEventStatus_eIgnore
;
433 WidgetQueryContentEvent
queryTextRectEvent(true, eQueryTextRect
, aWidget
);
434 queryTextRectEvent
.InitForQueryTextRect(mSelection
->StartOffset(),
435 mSelection
->Length());
436 aWidget
->DispatchEvent(&queryTextRectEvent
, status
);
437 if (NS_WARN_IF(queryTextRectEvent
.Failed())) {
438 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
439 ("0x%p CacheTextRects(), FAILED, "
440 "couldn't retrieve text rect of whole selected text",
443 mSelection
->mRect
= queryTextRectEvent
.mReply
->mRect
;
447 // Even if there is no selection range, we should have the first character
448 // rect for the last resort of suggesting position of IME UI.
449 if (mSelection
.isSome() && mSelection
->mHasRange
&& !mSelection
->mFocus
) {
450 mFirstCharRect
= mSelection
->mFocusCharRects
[eNextCharRect
];
451 } else if (mSelection
.isSome() && mSelection
->mHasRange
&&
452 mSelection
->mFocus
== 1) {
453 mFirstCharRect
= mSelection
->mFocusCharRects
[ePrevCharRect
];
454 } else if (mSelection
.isSome() && mSelection
->mHasRange
&&
455 !mSelection
->mAnchor
) {
456 mFirstCharRect
= mSelection
->mAnchorCharRects
[eNextCharRect
];
457 } else if (mSelection
.isSome() && mSelection
->mHasRange
&&
458 mSelection
->mAnchor
== 1) {
459 mFirstCharRect
= mSelection
->mFocusCharRects
[ePrevCharRect
];
460 } else if (mTextRectArray
.isSome() && mTextRectArray
->IsOffsetInRange(0u)) {
461 mFirstCharRect
= mTextRectArray
->GetRect(0u);
463 LayoutDeviceIntRect charRect
;
464 if (MOZ_UNLIKELY(NS_WARN_IF(!QueryCharRect(aWidget
, 0, charRect
)))) {
465 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
466 ("0x%p CacheTextRects(), FAILED, "
467 "couldn't retrieve first char rect",
469 mFirstCharRect
.SetEmpty();
471 mFirstCharRect
= charRect
;
475 // Finally, let's cache the last commit string's character rects until
476 // selection change or something other editing because user may reconvert
477 // or undo the last commit. Then, IME requires the character rects for
478 // positioning their UI.
479 if (mLastCommit
.isSome()) {
480 mLastCommitStringTextRectArray
=
481 Some(TextRectArray(mLastCommit
->StartOffset()));
482 if (mLastCommit
->Length() == 1 && mSelection
.isSome() &&
483 mSelection
->mHasRange
&&
484 mSelection
->mAnchor
- 1 == mLastCommit
->StartOffset() &&
485 !mSelection
->mAnchorCharRects
[ePrevCharRect
].IsEmpty()) {
486 mLastCommitStringTextRectArray
->mRects
.AppendElement(
487 mSelection
->mAnchorCharRects
[ePrevCharRect
]);
488 } else if (NS_WARN_IF(!QueryCharRectArray(
489 aWidget
, mLastCommit
->StartOffset(), mLastCommit
->Length(),
490 mLastCommitStringTextRectArray
->mRects
))) {
491 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
492 ("0x%p CacheTextRects(), FAILED, "
493 "couldn't retrieve text rect array of the last commit string",
495 mLastCommitStringTextRectArray
.reset();
498 MOZ_ASSERT((mLastCommitStringTextRectArray
.isSome()
499 ? mLastCommitStringTextRectArray
->mRects
.Length()
500 : 0) == (mLastCommit
.isSome() ? mLastCommit
->Length() : 0));
502 mLastCommitStringTextRectArray
.reset();
506 sContentCacheLog
, LogLevel::Info
,
507 ("0x%p CacheTextRects(), Succeeded, "
508 "mText=%s, mTextRectArray=%s, mSelection=%s, "
509 "mFirstCharRect=%s, mLastCommitStringTextRectArray=%s",
511 PrintStringDetail(mText
, PrintStringDetail::kMaxLengthForEditor
).get(),
512 ToString(mTextRectArray
).c_str(), ToString(mSelection
).c_str(),
513 ToString(mFirstCharRect
).c_str(),
514 ToString(mLastCommitStringTextRectArray
).c_str()));
518 void ContentCacheInChild::SetSelection(
520 const IMENotification::SelectionChangeDataBase
& aSelectionChangeData
) {
522 sContentCacheLog
, LogLevel::Info
,
523 ("0x%p SetSelection(aSelectionChangeData=%s), mText=%s", this,
524 ToString(aSelectionChangeData
).c_str(),
525 PrintStringDetail(mText
, PrintStringDetail::kMaxLengthForEditor
).get()));
527 mSelection
= Some(Selection(aSelectionChangeData
));
529 if (mLastCommit
.isSome()) {
530 // Forget last commit string range if selection is not collapsed
531 // at end of the last commit string.
532 if (!mSelection
->mHasRange
|| !mSelection
->IsCollapsed() ||
533 mSelection
->mAnchor
!= mLastCommit
->EndOffset()) {
535 sContentCacheLog
, LogLevel::Debug
,
536 ("0x%p SetSelection(), forgetting last commit composition data "
537 "(mSelection=%s, mLastCommit=%s)",
538 this, ToString(mSelection
).c_str(), ToString(mLastCommit
).c_str()));
544 CacheTextRects(aWidget
);
547 /*****************************************************************************
548 * mozilla::ContentCacheInParent
549 *****************************************************************************/
551 ContentCacheInParent::ContentCacheInParent(BrowserParent
& aBrowserParent
)
553 mBrowserParent(aBrowserParent
),
554 mCommitStringByRequest(nullptr),
555 mPendingEventsNeedingAck(0),
556 mPendingCommitLength(0),
557 mPendingCompositionCount(0),
558 mPendingCommitCount(0),
559 mWidgetHasComposition(false),
560 mIsChildIgnoringCompositionEvents(false) {}
562 void ContentCacheInParent::AssignContent(const ContentCache
& aOther
,
564 const IMENotification
* aNotification
) {
565 mText
= aOther
.mText
;
566 mSelection
= aOther
.mSelection
;
567 mFirstCharRect
= aOther
.mFirstCharRect
;
568 mCaret
= aOther
.mCaret
;
569 mTextRectArray
= aOther
.mTextRectArray
;
570 mLastCommitStringTextRectArray
= aOther
.mLastCommitStringTextRectArray
;
571 mEditorRect
= aOther
.mEditorRect
;
573 // Only when there is one composition, the TextComposition instance in this
574 // process is managing the composition in the remote process. Therefore,
575 // we shouldn't update composition start offset of TextComposition with
576 // old composition which is still being handled by the child process.
577 if (mWidgetHasComposition
&& mPendingCompositionCount
== 1 &&
578 mCompositionStart
.isSome()) {
579 IMEStateManager::MaybeStartOffsetUpdatedInChild(aWidget
,
580 mCompositionStart
.value());
583 // When this instance allows to query content relative to composition string,
584 // we should modify mCompositionStart with the latest information in the
585 // remote process because now we have the information around the composition
587 mCompositionStartInChild
= aOther
.mCompositionStart
;
588 if (mWidgetHasComposition
|| mPendingCommitCount
) {
589 if (mCompositionStartInChild
.isSome()) {
590 if (mCompositionStart
.valueOr(UINT32_MAX
) !=
591 mCompositionStartInChild
.value()) {
592 mCompositionStart
= mCompositionStartInChild
;
593 mPendingCommitLength
= 0;
595 } else if (mCompositionStart
.isSome() && mSelection
.isSome() &&
596 mSelection
->mHasRange
&&
597 mCompositionStart
.value() != mSelection
->StartOffset()) {
598 mCompositionStart
= Some(mSelection
->StartOffset());
599 mPendingCommitLength
= 0;
604 sContentCacheLog
, LogLevel::Info
,
605 ("0x%p AssignContent(aNotification=%s), "
606 "Succeeded, mText=%s, mSelection=%s, mFirstCharRect=%s, "
607 "mCaret=%s, mTextRectArray=%s, mWidgetHasComposition=%s, "
608 "mPendingCompositionCount=%u, mCompositionStart=%s, "
609 "mPendingCommitLength=%u, mEditorRect=%s, "
610 "mLastCommitStringTextRectArray=%s",
611 this, GetNotificationName(aNotification
),
612 PrintStringDetail(mText
, PrintStringDetail::kMaxLengthForEditor
).get(),
613 ToString(mSelection
).c_str(), ToString(mFirstCharRect
).c_str(),
614 ToString(mCaret
).c_str(), ToString(mTextRectArray
).c_str(),
615 GetBoolName(mWidgetHasComposition
), mPendingCompositionCount
,
616 ToString(mCompositionStart
).c_str(), mPendingCommitLength
,
617 ToString(mEditorRect
).c_str(),
618 ToString(mLastCommitStringTextRectArray
).c_str()));
621 bool ContentCacheInParent::HandleQueryContentEvent(
622 WidgetQueryContentEvent
& aEvent
, nsIWidget
* aWidget
) const {
625 // ContentCache doesn't store offset of its start with XP linebreaks.
626 // So, we don't support to query contents relative to composition start
627 // offset with XP linebreaks.
628 if (NS_WARN_IF(!aEvent
.mUseNativeLineBreak
)) {
629 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
630 ("0x%p HandleQueryContentEvent(), FAILED due to query with XP "
636 if (NS_WARN_IF(!aEvent
.mInput
.IsValidOffset())) {
638 sContentCacheLog
, LogLevel::Error
,
639 ("0x%p HandleQueryContentEvent(), FAILED due to invalid offset", this));
643 if (NS_WARN_IF(!aEvent
.mInput
.IsValidEventMessage(aEvent
.mMessage
))) {
645 sContentCacheLog
, LogLevel::Error
,
646 ("0x%p HandleQueryContentEvent(), FAILED due to invalid event message",
651 bool isRelativeToInsertionPoint
= aEvent
.mInput
.mRelativeToInsertionPoint
;
652 if (isRelativeToInsertionPoint
) {
653 MOZ_LOG(sContentCacheLog
, LogLevel::Debug
,
654 ("0x%p HandleQueryContentEvent(), "
655 "making offset absolute... aEvent={ mMessage=%s, mInput={ "
656 "mOffset=%" PRId64
", mLength=%" PRIu32
" } }, "
657 "mWidgetHasComposition=%s, mPendingCommitCount=%" PRIu8
658 ", mCompositionStart=%" PRIu32
", "
659 "mPendingCommitLength=%" PRIu32
", mSelection=%s",
660 this, ToChar(aEvent
.mMessage
), aEvent
.mInput
.mOffset
,
661 aEvent
.mInput
.mLength
, GetBoolName(mWidgetHasComposition
),
662 mPendingCommitCount
, mCompositionStart
.valueOr(UINT32_MAX
),
663 mPendingCommitLength
, ToString(mSelection
).c_str()));
664 if (mWidgetHasComposition
|| mPendingCommitCount
) {
665 if (NS_WARN_IF(mCompositionStart
.isNothing()) ||
666 NS_WARN_IF(!aEvent
.mInput
.MakeOffsetAbsolute(
667 mCompositionStart
.value() + mPendingCommitLength
))) {
669 sContentCacheLog
, LogLevel::Error
,
670 ("0x%p HandleQueryContentEvent(), FAILED due to "
671 "aEvent.mInput.MakeOffsetAbsolute(mCompositionStart + "
672 "mPendingCommitLength) failure, "
673 "mCompositionStart=%" PRIu32
", mPendingCommitLength=%" PRIu32
", "
674 "aEvent={ mMessage=%s, mInput={ mOffset=%" PRId64
675 ", mLength=%" PRIu32
" } }",
676 this, mCompositionStart
.valueOr(UINT32_MAX
), mPendingCommitLength
,
677 ToChar(aEvent
.mMessage
), aEvent
.mInput
.mOffset
,
678 aEvent
.mInput
.mLength
));
681 } else if (NS_WARN_IF(mSelection
.isNothing())) {
682 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
683 ("0x%p HandleQueryContentEvent(), FAILED due to mSelection is "
687 } else if (NS_WARN_IF(mSelection
->mHasRange
)) {
688 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
689 ("0x%p HandleQueryContentEvent(), FAILED due to there is no "
690 "selection range, but the query requested with relative offset "
694 } else if (NS_WARN_IF(!aEvent
.mInput
.MakeOffsetAbsolute(
695 mSelection
->StartOffset() + mPendingCommitLength
))) {
696 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
697 ("0x%p HandleQueryContentEvent(), FAILED due to "
698 "aEvent.mInput.MakeOffsetAbsolute(mSelection->StartOffset() + "
699 "mPendingCommitLength) failure, mSelection=%s, "
700 "mPendingCommitLength=%" PRIu32
", aEvent={ mMessage=%s, "
701 "mInput={ mOffset=%" PRId64
", mLength=%" PRIu32
" } }",
702 this, ToString(mSelection
).c_str(), mPendingCommitLength
,
703 ToChar(aEvent
.mMessage
), aEvent
.mInput
.mOffset
,
704 aEvent
.mInput
.mLength
));
709 switch (aEvent
.mMessage
) {
710 case eQuerySelectedText
:
711 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
712 ("0x%p HandleQueryContentEvent(aEvent={ "
713 "mMessage=eQuerySelectedText }, aWidget=0x%p)",
715 if (MOZ_UNLIKELY(NS_WARN_IF(mSelection
.isNothing()))) {
716 // If content cache hasn't been initialized properly, make the query
718 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
719 ("0x%p HandleQueryContentEvent(), FAILED because mSelection "
724 MOZ_DIAGNOSTIC_ASSERT_IF(!mSelection
->IsCollapsed(), mText
.isSome());
725 MOZ_DIAGNOSTIC_ASSERT_IF(!mSelection
->IsCollapsed(),
726 mSelection
->EndOffset() <= mText
->Length());
727 aEvent
.EmplaceReply();
728 aEvent
.mReply
->mFocusedWidget
= aWidget
;
729 if (mSelection
->mHasRange
) {
730 if (MOZ_LIKELY(mText
.isSome())) {
731 aEvent
.mReply
->mOffsetAndData
.emplace(
732 mSelection
->StartOffset(),
733 Substring(mText
.ref(), mSelection
->StartOffset(),
734 mSelection
->Length()),
735 OffsetAndDataFor::SelectedString
);
737 // TODO: Investigate this case. I find this during
738 // test_mousecapture.xhtml on Linux.
739 aEvent
.mReply
->mOffsetAndData
.emplace(
740 0u, EmptyString(), OffsetAndDataFor::SelectedString
);
743 aEvent
.mReply
->mWritingMode
= mSelection
->mWritingMode
;
744 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
745 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
746 "mMessage=eQuerySelectedText, mReply=%s }",
747 this, ToString(aEvent
.mReply
).c_str()));
749 case eQueryTextContent
: {
750 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
751 ("0x%p HandleQueryContentEvent(aEvent={ "
752 "mMessage=eQueryTextContent, mInput={ mOffset=%" PRId64
753 ", mLength=%u } }, aWidget=0x%p), mText->Length()=%zu",
754 this, aEvent
.mInput
.mOffset
, aEvent
.mInput
.mLength
, aWidget
,
755 mText
.isSome() ? mText
->Length() : 0u));
756 if (MOZ_UNLIKELY(NS_WARN_IF(mText
.isNothing()))) {
757 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
758 ("0x%p HandleQueryContentEvent(), FAILED because "
759 "there is no text data",
763 const uint32_t inputOffset
= aEvent
.mInput
.mOffset
;
764 const uint32_t inputEndOffset
= std::min
<uint32_t>(
765 aEvent
.mInput
.EndOffset(), mText
.isSome() ? mText
->Length() : 0u);
766 if (MOZ_UNLIKELY(NS_WARN_IF(inputEndOffset
< inputOffset
))) {
767 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
768 ("0x%p HandleQueryContentEvent(), FAILED because "
769 "inputOffset=%u is larger than inputEndOffset=%u",
770 this, inputOffset
, inputEndOffset
));
773 aEvent
.EmplaceReply();
774 aEvent
.mReply
->mFocusedWidget
= aWidget
;
775 const nsAString
& textInQueriedRange
=
776 inputEndOffset
> inputOffset
777 ? static_cast<const nsAString
&>(Substring(
778 mText
.ref(), inputOffset
, inputEndOffset
- inputOffset
))
779 : static_cast<const nsAString
&>(EmptyString());
780 aEvent
.mReply
->mOffsetAndData
.emplace(inputOffset
, textInQueriedRange
,
781 OffsetAndDataFor::EditorString
);
782 // TODO: Support font ranges
783 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
784 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
785 "mMessage=eQueryTextContent, mReply=%s }",
786 this, ToString(aEvent
.mReply
).c_str()));
789 case eQueryTextRect
: {
790 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
791 ("0x%p HandleQueryContentEvent("
792 "aEvent={ mMessage=eQueryTextRect, mInput={ mOffset=%" PRId64
793 ", mLength=%u } }, aWidget=0x%p), mText->Length()=%zu",
794 this, aEvent
.mInput
.mOffset
, aEvent
.mInput
.mLength
, aWidget
,
795 mText
.isSome() ? mText
->Length() : 0u));
796 // Note that if the query is relative to insertion point, the query was
797 // probably requested by native IME. In such case, we should return
798 // non-empty rect since returning failure causes IME showing its window
800 LayoutDeviceIntRect textRect
;
801 if (aEvent
.mInput
.mLength
) {
802 if (MOZ_UNLIKELY(NS_WARN_IF(
803 !GetUnionTextRects(aEvent
.mInput
.mOffset
, aEvent
.mInput
.mLength
,
804 isRelativeToInsertionPoint
, textRect
)))) {
805 // XXX We don't have cache for this request.
806 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
807 ("0x%p HandleQueryContentEvent(), FAILED to get union rect",
812 // If the length is 0, we should return caret rect instead.
813 if (NS_WARN_IF(!GetCaretRect(aEvent
.mInput
.mOffset
,
814 isRelativeToInsertionPoint
, textRect
))) {
815 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
816 ("0x%p HandleQueryContentEvent(), FAILED to get caret rect",
821 aEvent
.EmplaceReply();
822 aEvent
.mReply
->mFocusedWidget
= aWidget
;
823 aEvent
.mReply
->mRect
= textRect
;
824 const nsAString
& textInQueriedRange
=
825 mText
.isSome() && aEvent
.mInput
.mOffset
<
826 static_cast<int64_t>(
827 mText
.isSome() ? mText
->Length() : 0u)
828 ? static_cast<const nsAString
&>(
829 Substring(mText
.ref(), aEvent
.mInput
.mOffset
,
830 mText
->Length() >= aEvent
.mInput
.EndOffset()
831 ? aEvent
.mInput
.mLength
833 : static_cast<const nsAString
&>(EmptyString());
834 aEvent
.mReply
->mOffsetAndData
.emplace(aEvent
.mInput
.mOffset
,
836 OffsetAndDataFor::EditorString
);
837 // XXX This may be wrong if storing range isn't in the selection range.
838 aEvent
.mReply
->mWritingMode
= mSelection
->mWritingMode
;
839 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
840 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
841 "mMessage=eQueryTextRect mReply=%s }",
842 this, ToString(aEvent
.mReply
).c_str()));
845 case eQueryCaretRect
: {
847 sContentCacheLog
, LogLevel::Info
,
848 ("0x%p HandleQueryContentEvent(aEvent={ mMessage=eQueryCaretRect, "
849 "mInput={ mOffset=%" PRId64
850 " } }, aWidget=0x%p), mText->Length()=%zu",
851 this, aEvent
.mInput
.mOffset
, aWidget
,
852 mText
.isSome() ? mText
->Length() : 0u));
853 // Note that if the query is relative to insertion point, the query was
854 // probably requested by native IME. In such case, we should return
855 // non-empty rect since returning failure causes IME showing its window
857 LayoutDeviceIntRect caretRect
;
858 if (NS_WARN_IF(!GetCaretRect(aEvent
.mInput
.mOffset
,
859 isRelativeToInsertionPoint
, caretRect
))) {
860 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
861 ("0x%p HandleQueryContentEvent(),FAILED to get caret rect",
865 aEvent
.EmplaceReply();
866 aEvent
.mReply
->mFocusedWidget
= aWidget
;
867 aEvent
.mReply
->mRect
= caretRect
;
868 aEvent
.mReply
->mOffsetAndData
.emplace(aEvent
.mInput
.mOffset
,
870 OffsetAndDataFor::SelectedString
);
871 // TODO: Set mWritingMode here
872 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
873 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
874 "mMessage=eQueryCaretRect, mReply=%s }",
875 this, ToString(aEvent
.mReply
).c_str()));
878 case eQueryEditorRect
:
879 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
880 ("0x%p HandleQueryContentEvent(aEvent={ "
881 "mMessage=eQueryEditorRect }, aWidget=0x%p)",
883 // XXX This query should fail if no editable elmenet has focus. Or,
884 // perhaps, should return rect of the window instead.
885 aEvent
.EmplaceReply();
886 aEvent
.mReply
->mFocusedWidget
= aWidget
;
887 aEvent
.mReply
->mRect
= mEditorRect
;
888 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
889 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
890 "mMessage=eQueryEditorRect, mReply=%s }",
891 this, ToString(aEvent
.mReply
).c_str()));
894 aEvent
.EmplaceReply();
895 aEvent
.mReply
->mFocusedWidget
= aWidget
;
896 if (NS_WARN_IF(aEvent
.Failed())) {
898 sContentCacheLog
, LogLevel::Error
,
899 ("0x%p HandleQueryContentEvent(), FAILED due to not set enough "
900 "data, aEvent={ mMessage=%s, mReply=%s }",
901 this, ToChar(aEvent
.mMessage
), ToString(aEvent
.mReply
).c_str()));
904 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
905 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
906 "mMessage=%s, mReply=%s }",
907 this, ToChar(aEvent
.mMessage
), ToString(aEvent
.mReply
).c_str()));
912 bool ContentCacheInParent::GetTextRect(uint32_t aOffset
,
913 bool aRoundToExistingOffset
,
914 LayoutDeviceIntRect
& aTextRect
) const {
916 sContentCacheLog
, LogLevel::Info
,
917 ("0x%p GetTextRect(aOffset=%u, aRoundToExistingOffset=%s), "
918 "mTextRectArray=%s, mSelection=%s, mLastCommitStringTextRectArray=%s",
919 this, aOffset
, GetBoolName(aRoundToExistingOffset
),
920 ToString(mTextRectArray
).c_str(), ToString(mSelection
).c_str(),
921 ToString(mLastCommitStringTextRectArray
).c_str()));
924 NS_WARNING_ASSERTION(!mFirstCharRect
.IsEmpty(), "empty rect");
925 aTextRect
= mFirstCharRect
;
926 return !aTextRect
.IsEmpty();
928 if (mSelection
.isSome() && mSelection
->mHasRange
) {
929 if (aOffset
== mSelection
->mAnchor
) {
930 NS_WARNING_ASSERTION(
931 !mSelection
->mAnchorCharRects
[eNextCharRect
].IsEmpty(), "empty rect");
932 aTextRect
= mSelection
->mAnchorCharRects
[eNextCharRect
];
933 return !aTextRect
.IsEmpty();
935 if (mSelection
->mAnchor
&& aOffset
== mSelection
->mAnchor
- 1) {
936 NS_WARNING_ASSERTION(
937 !mSelection
->mAnchorCharRects
[ePrevCharRect
].IsEmpty(), "empty rect");
938 aTextRect
= mSelection
->mAnchorCharRects
[ePrevCharRect
];
939 return !aTextRect
.IsEmpty();
941 if (aOffset
== mSelection
->mFocus
) {
942 NS_WARNING_ASSERTION(
943 !mSelection
->mFocusCharRects
[eNextCharRect
].IsEmpty(), "empty rect");
944 aTextRect
= mSelection
->mFocusCharRects
[eNextCharRect
];
945 return !aTextRect
.IsEmpty();
947 if (mSelection
->mFocus
&& aOffset
== mSelection
->mFocus
- 1) {
948 NS_WARNING_ASSERTION(
949 !mSelection
->mFocusCharRects
[ePrevCharRect
].IsEmpty(), "empty rect");
950 aTextRect
= mSelection
->mFocusCharRects
[ePrevCharRect
];
951 return !aTextRect
.IsEmpty();
955 if (mTextRectArray
.isSome() && mTextRectArray
->IsOffsetInRange(aOffset
)) {
956 aTextRect
= mTextRectArray
->GetRect(aOffset
);
957 return !aTextRect
.IsEmpty();
960 if (mLastCommitStringTextRectArray
.isSome() &&
961 mLastCommitStringTextRectArray
->IsOffsetInRange(aOffset
)) {
962 aTextRect
= mLastCommitStringTextRectArray
->GetRect(aOffset
);
963 return !aTextRect
.IsEmpty();
966 if (!aRoundToExistingOffset
) {
967 aTextRect
.SetEmpty();
971 if (mTextRectArray
.isNothing() || !mTextRectArray
->HasRects()) {
972 // If there are no rects in mTextRectArray, we should refer the start of
973 // the selection if there is because IME must query a char rect around it if
974 // there is no composition.
975 if (mSelection
.isNothing()) {
976 // Unfortunately, there is no data about text rect...
977 aTextRect
.SetEmpty();
980 aTextRect
= mSelection
->StartCharRect();
981 return !aTextRect
.IsEmpty();
984 // Although we may have mLastCommitStringTextRectArray here and it must have
985 // previous character rects at selection. However, we should stop using it
986 // because it's stored really short time after commiting a composition.
987 // So, multiple query may return different rect and it may cause flickerling
989 uint32_t offset
= aOffset
;
990 if (offset
< mTextRectArray
->StartOffset()) {
991 offset
= mTextRectArray
->StartOffset();
993 offset
= mTextRectArray
->EndOffset() - 1;
995 aTextRect
= mTextRectArray
->GetRect(offset
);
996 return !aTextRect
.IsEmpty();
999 bool ContentCacheInParent::GetUnionTextRects(
1000 uint32_t aOffset
, uint32_t aLength
, bool aRoundToExistingOffset
,
1001 LayoutDeviceIntRect
& aUnionTextRect
) const {
1002 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
1003 ("0x%p GetUnionTextRects(aOffset=%u, "
1004 "aLength=%u, aRoundToExistingOffset=%s), mTextRectArray=%s, "
1005 "mSelection=%s, mLastCommitStringTextRectArray=%s",
1006 this, aOffset
, aLength
, GetBoolName(aRoundToExistingOffset
),
1007 ToString(mTextRectArray
).c_str(), ToString(mSelection
).c_str(),
1008 ToString(mLastCommitStringTextRectArray
).c_str()));
1010 CheckedInt
<uint32_t> endOffset
= CheckedInt
<uint32_t>(aOffset
) + aLength
;
1011 if (!endOffset
.isValid()) {
1015 if (mSelection
.isSome() && !mSelection
->IsCollapsed() &&
1016 aOffset
== mSelection
->StartOffset() && aLength
== mSelection
->Length()) {
1017 NS_WARNING_ASSERTION(!mSelection
->mRect
.IsEmpty(), "empty rect");
1018 aUnionTextRect
= mSelection
->mRect
;
1019 return !aUnionTextRect
.IsEmpty();
1024 NS_WARNING_ASSERTION(!mFirstCharRect
.IsEmpty(), "empty rect");
1025 aUnionTextRect
= mFirstCharRect
;
1026 return !aUnionTextRect
.IsEmpty();
1028 if (mSelection
.isSome() && mSelection
->mHasRange
) {
1029 if (aOffset
== mSelection
->mAnchor
) {
1030 NS_WARNING_ASSERTION(
1031 !mSelection
->mAnchorCharRects
[eNextCharRect
].IsEmpty(),
1033 aUnionTextRect
= mSelection
->mAnchorCharRects
[eNextCharRect
];
1034 return !aUnionTextRect
.IsEmpty();
1036 if (mSelection
->mAnchor
&& aOffset
== mSelection
->mAnchor
- 1) {
1037 NS_WARNING_ASSERTION(
1038 !mSelection
->mAnchorCharRects
[ePrevCharRect
].IsEmpty(),
1040 aUnionTextRect
= mSelection
->mAnchorCharRects
[ePrevCharRect
];
1041 return !aUnionTextRect
.IsEmpty();
1043 if (aOffset
== mSelection
->mFocus
) {
1044 NS_WARNING_ASSERTION(
1045 !mSelection
->mFocusCharRects
[eNextCharRect
].IsEmpty(),
1047 aUnionTextRect
= mSelection
->mFocusCharRects
[eNextCharRect
];
1048 return !aUnionTextRect
.IsEmpty();
1050 if (mSelection
->mFocus
&& aOffset
== mSelection
->mFocus
- 1) {
1051 NS_WARNING_ASSERTION(
1052 !mSelection
->mFocusCharRects
[ePrevCharRect
].IsEmpty(),
1054 aUnionTextRect
= mSelection
->mFocusCharRects
[ePrevCharRect
];
1055 return !aUnionTextRect
.IsEmpty();
1060 // Even if some text rects are not cached of the queried range,
1061 // we should return union rect when the first character's rect is cached
1062 // since the first character rect is important and the others are not so
1065 if (!aOffset
&& mSelection
.isSome() && mSelection
->mHasRange
&&
1066 aOffset
!= mSelection
->mAnchor
&& aOffset
!= mSelection
->mFocus
&&
1067 (mTextRectArray
.isNothing() ||
1068 !mTextRectArray
->IsOffsetInRange(aOffset
)) &&
1069 (mLastCommitStringTextRectArray
.isNothing() ||
1070 !mLastCommitStringTextRectArray
->IsOffsetInRange(aOffset
))) {
1071 // The first character rect isn't cached.
1075 // Use mLastCommitStringTextRectArray only when it overlaps with aOffset
1076 // even if aROundToExistingOffset is true for avoiding flickerling IME UI.
1077 // See the last comment in GetTextRect() for the detail.
1078 if (mLastCommitStringTextRectArray
.isSome() &&
1079 mLastCommitStringTextRectArray
->IsOverlappingWith(aOffset
, aLength
)) {
1081 mLastCommitStringTextRectArray
->GetUnionRectAsFarAsPossible(
1082 aOffset
, aLength
, aRoundToExistingOffset
);
1084 aUnionTextRect
.SetEmpty();
1087 if (mTextRectArray
.isSome() &&
1088 ((aRoundToExistingOffset
&& mTextRectArray
->HasRects()) ||
1089 mTextRectArray
->IsOverlappingWith(aOffset
, aLength
))) {
1091 aUnionTextRect
.Union(mTextRectArray
->GetUnionRectAsFarAsPossible(
1092 aOffset
, aLength
, aRoundToExistingOffset
));
1096 aUnionTextRect
= aUnionTextRect
.Union(mFirstCharRect
);
1098 if (mSelection
.isSome() && mSelection
->mHasRange
) {
1099 if (aOffset
<= mSelection
->mAnchor
&&
1100 mSelection
->mAnchor
< endOffset
.value()) {
1102 aUnionTextRect
.Union(mSelection
->mAnchorCharRects
[eNextCharRect
]);
1104 if (mSelection
->mAnchor
&& aOffset
<= mSelection
->mAnchor
- 1 &&
1105 mSelection
->mAnchor
- 1 < endOffset
.value()) {
1107 aUnionTextRect
.Union(mSelection
->mAnchorCharRects
[ePrevCharRect
]);
1109 if (aOffset
<= mSelection
->mFocus
&&
1110 mSelection
->mFocus
< endOffset
.value()) {
1112 aUnionTextRect
.Union(mSelection
->mFocusCharRects
[eNextCharRect
]);
1114 if (mSelection
->mFocus
&& aOffset
<= mSelection
->mFocus
- 1 &&
1115 mSelection
->mFocus
- 1 < endOffset
.value()) {
1117 aUnionTextRect
.Union(mSelection
->mFocusCharRects
[ePrevCharRect
]);
1121 return !aUnionTextRect
.IsEmpty();
1124 bool ContentCacheInParent::GetCaretRect(uint32_t aOffset
,
1125 bool aRoundToExistingOffset
,
1126 LayoutDeviceIntRect
& aCaretRect
) const {
1127 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
1128 ("0x%p GetCaretRect(aOffset=%u, aRoundToExistingOffset=%s), "
1129 "mCaret=%s, mTextRectArray=%s, mSelection=%s, mFirstCharRect=%s",
1130 this, aOffset
, GetBoolName(aRoundToExistingOffset
),
1131 ToString(mCaret
).c_str(), ToString(mTextRectArray
).c_str(),
1132 ToString(mSelection
).c_str(), ToString(mFirstCharRect
).c_str()));
1134 if (mCaret
.isSome() && mCaret
->mOffset
== aOffset
) {
1135 aCaretRect
= mCaret
->mRect
;
1139 // Guess caret rect from the text rect if it's stored.
1140 if (!GetTextRect(aOffset
, aRoundToExistingOffset
, aCaretRect
)) {
1141 // There might be previous character rect in the cache. If so, we can
1142 // guess the caret rect with it.
1144 !GetTextRect(aOffset
- 1, aRoundToExistingOffset
, aCaretRect
)) {
1145 aCaretRect
.SetEmpty();
1149 if (mSelection
.isSome() && mSelection
->mWritingMode
.IsVertical()) {
1150 aCaretRect
.MoveToY(aCaretRect
.YMost());
1152 // XXX bidi-unaware.
1153 aCaretRect
.MoveToX(aCaretRect
.XMost());
1157 // XXX This is not bidi aware because we don't cache each character's
1158 // direction. However, this is usually used by IME, so, assuming the
1159 // character is in LRT context must not cause any problem.
1160 if (mSelection
.isSome() && mSelection
->mWritingMode
.IsVertical()) {
1161 aCaretRect
.SetHeight(mCaret
.isSome() ? mCaret
->mRect
.Height() : 1);
1163 aCaretRect
.SetWidth(mCaret
.isSome() ? mCaret
->mRect
.Width() : 1);
1168 bool ContentCacheInParent::OnCompositionEvent(
1169 const WidgetCompositionEvent
& aEvent
) {
1171 sContentCacheLog
, LogLevel::Info
,
1172 ("0x%p OnCompositionEvent(aEvent={ "
1173 "mMessage=%s, mData=\"%s\", mRanges->Length()=%zu }), "
1174 "mPendingEventsNeedingAck=%u, mWidgetHasComposition=%s, "
1175 "mPendingCompositionCount=%" PRIu8
", mPendingCommitCount=%" PRIu8
", "
1176 "mIsChildIgnoringCompositionEvents=%s, mCommitStringByRequest=0x%p",
1177 this, ToChar(aEvent
.mMessage
),
1178 PrintStringDetail(aEvent
.mData
,
1179 PrintStringDetail::kMaxLengthForCompositionString
)
1181 aEvent
.mRanges
? aEvent
.mRanges
->Length() : 0, mPendingEventsNeedingAck
,
1182 GetBoolName(mWidgetHasComposition
), mPendingCompositionCount
,
1183 mPendingCommitCount
, GetBoolName(mIsChildIgnoringCompositionEvents
),
1184 mCommitStringByRequest
));
1186 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1187 mDispatchedEventMessages
.AppendElement(aEvent
.mMessage
);
1188 #endif // #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1190 // We must be able to simulate the selection because
1191 // we might not receive selection updates in time
1192 if (!mWidgetHasComposition
) {
1193 if (mCompositionStartInChild
.isSome()) {
1194 // If there is pending composition in the remote process, let's use
1195 // its start offset temporarily because this stores a lot of information
1196 // around it and the user must look around there, so, showing some UI
1197 // around it must make sense.
1198 mCompositionStart
= mCompositionStartInChild
;
1200 mCompositionStart
= Some(mSelection
.isSome() && mSelection
->mHasRange
1201 ? mSelection
->StartOffset()
1204 MOZ_ASSERT(aEvent
.mMessage
== eCompositionStart
);
1205 MOZ_RELEASE_ASSERT(mPendingCompositionCount
< UINT8_MAX
);
1206 mPendingCompositionCount
++;
1209 mWidgetHasComposition
= !aEvent
.CausesDOMCompositionEndEvent();
1211 if (!mWidgetHasComposition
) {
1212 // mCompositionStart will be reset when commit event is completely handled
1213 // in the remote process.
1214 if (mPendingCompositionCount
== 1) {
1215 mPendingCommitLength
= aEvent
.mData
.Length();
1217 mPendingCommitCount
++;
1218 } else if (aEvent
.mMessage
!= eCompositionStart
) {
1219 mCompositionString
= aEvent
.mData
;
1222 // During REQUEST_TO_COMMIT_COMPOSITION or REQUEST_TO_CANCEL_COMPOSITION,
1223 // widget usually sends a eCompositionChange and/or eCompositionCommit event
1224 // to finalize or clear the composition, respectively. In this time,
1225 // we need to intercept all composition events here and pass the commit
1226 // string for returning to the remote process as a result of
1227 // RequestIMEToCommitComposition(). Then, eCommitComposition event will
1228 // be dispatched with the committed string in the remote process internally.
1229 if (mCommitStringByRequest
) {
1230 if (aEvent
.mMessage
== eCompositionCommitAsIs
) {
1231 *mCommitStringByRequest
= mCompositionString
;
1233 MOZ_ASSERT(aEvent
.mMessage
== eCompositionChange
||
1234 aEvent
.mMessage
== eCompositionCommit
);
1235 *mCommitStringByRequest
= aEvent
.mData
;
1237 // We need to wait eCompositionCommitRequestHandled from the remote process
1238 // in this case. Therefore, mPendingEventsNeedingAck needs to be
1239 // incremented here. Additionally, we stop sending eCompositionCommit(AsIs)
1240 // event. Therefore, we need to decrement mPendingCommitCount which has
1241 // been incremented above.
1242 if (!mWidgetHasComposition
) {
1243 mPendingEventsNeedingAck
++;
1244 MOZ_DIAGNOSTIC_ASSERT(mPendingCommitCount
);
1245 if (mPendingCommitCount
) {
1246 mPendingCommitCount
--;
1252 mPendingEventsNeedingAck
++;
1256 void ContentCacheInParent::OnSelectionEvent(
1257 const WidgetSelectionEvent
& aSelectionEvent
) {
1259 sContentCacheLog
, LogLevel::Info
,
1260 ("0x%p OnSelectionEvent(aEvent={ "
1261 "mMessage=%s, mOffset=%u, mLength=%u, mReversed=%s, "
1262 "mExpandToClusterBoundary=%s, mUseNativeLineBreak=%s }), "
1263 "mPendingEventsNeedingAck=%u, mWidgetHasComposition=%s, "
1264 "mPendingCompositionCount=%" PRIu8
", mPendingCommitCount=%" PRIu8
", "
1265 "mIsChildIgnoringCompositionEvents=%s",
1266 this, ToChar(aSelectionEvent
.mMessage
), aSelectionEvent
.mOffset
,
1267 aSelectionEvent
.mLength
, GetBoolName(aSelectionEvent
.mReversed
),
1268 GetBoolName(aSelectionEvent
.mExpandToClusterBoundary
),
1269 GetBoolName(aSelectionEvent
.mUseNativeLineBreak
),
1270 mPendingEventsNeedingAck
, GetBoolName(mWidgetHasComposition
),
1271 mPendingCompositionCount
, mPendingCommitCount
,
1272 GetBoolName(mIsChildIgnoringCompositionEvents
)));
1274 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1275 mDispatchedEventMessages
.AppendElement(aSelectionEvent
.mMessage
);
1276 #endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
1278 mPendingEventsNeedingAck
++;
1281 void ContentCacheInParent::OnEventNeedingAckHandled(nsIWidget
* aWidget
,
1282 EventMessage aMessage
) {
1283 // This is called when the child process receives WidgetCompositionEvent or
1284 // WidgetSelectionEvent.
1287 sContentCacheLog
, LogLevel::Info
,
1288 ("0x%p OnEventNeedingAckHandled(aWidget=0x%p, "
1289 "aMessage=%s), mPendingEventsNeedingAck=%u, "
1290 "mWidgetHasComposition=%s, mPendingCompositionCount=%" PRIu8
", "
1291 "mPendingCommitCount=%" PRIu8
", mIsChildIgnoringCompositionEvents=%s",
1292 this, aWidget
, ToChar(aMessage
), mPendingEventsNeedingAck
,
1293 GetBoolName(mWidgetHasComposition
), mPendingCompositionCount
,
1294 mPendingCommitCount
, GetBoolName(mIsChildIgnoringCompositionEvents
)));
1296 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1297 mReceivedEventMessages
.AppendElement(aMessage
);
1298 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1300 bool isCommittedInChild
=
1301 // Commit requester in the remote process has committed the composition.
1302 aMessage
== eCompositionCommitRequestHandled
||
1303 // The commit event has been handled normally in the remote process.
1304 (!mIsChildIgnoringCompositionEvents
&&
1305 WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage
));
1307 if (isCommittedInChild
) {
1308 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1309 if (mPendingCompositionCount
== 1) {
1310 RemoveUnnecessaryEventMessageLog();
1312 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1314 if (NS_WARN_IF(!mPendingCompositionCount
)) {
1315 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1316 nsPrintfCString
info(
1317 "\nThere is no pending composition but received %s "
1318 "message from the remote child\n\n",
1320 AppendEventMessageLog(info
);
1321 CrashReporter::AppendAppNotesToCrashReport(info
);
1322 MOZ_DIAGNOSTIC_ASSERT(
1323 false, "No pending composition but received unexpected commit event");
1324 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1326 // Prevent odd behavior in release channel.
1327 mPendingCompositionCount
= 1;
1330 mPendingCompositionCount
--;
1332 // Forget composition string only when the latest composition string is
1333 // handled in the remote process because if there is 2 or more pending
1334 // composition, this value shouldn't be referred.
1335 if (!mPendingCompositionCount
) {
1336 mCompositionString
.Truncate();
1339 // Forget pending commit string length if it's handled in the remote
1340 // process. Note that this doesn't care too old composition's commit
1341 // string because in such case, we cannot return proper information
1342 // to IME synchornously.
1343 mPendingCommitLength
= 0;
1346 if (WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage
)) {
1347 // After the remote process receives eCompositionCommit(AsIs) event,
1348 // it'll restart to handle composition events.
1349 mIsChildIgnoringCompositionEvents
= false;
1351 if (NS_WARN_IF(!mPendingCommitCount
)) {
1352 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1353 nsPrintfCString
info(
1354 "\nThere is no pending comment events but received "
1355 "%s message from the remote child\n\n",
1357 AppendEventMessageLog(info
);
1358 CrashReporter::AppendAppNotesToCrashReport(info
);
1359 MOZ_DIAGNOSTIC_ASSERT(
1361 "No pending commit events but received unexpected commit event");
1362 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1364 // Prevent odd behavior in release channel.
1365 mPendingCommitCount
= 1;
1368 mPendingCommitCount
--;
1369 } else if (aMessage
== eCompositionCommitRequestHandled
&&
1370 mPendingCommitCount
) {
1371 // If the remote process commits composition synchronously after
1372 // requesting commit composition and we've already sent commit composition,
1373 // it starts to ignore following composition events until receiving
1374 // eCompositionStart event.
1375 mIsChildIgnoringCompositionEvents
= true;
1378 // If neither widget (i.e., IME) nor the remote process has composition,
1379 // now, we can forget composition string informations.
1380 if (!mWidgetHasComposition
&& !mPendingCompositionCount
&&
1381 !mPendingCommitCount
) {
1382 mCompositionStart
.reset();
1385 if (NS_WARN_IF(!mPendingEventsNeedingAck
)) {
1386 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1387 nsPrintfCString
info(
1388 "\nThere is no pending events but received %s "
1389 "message from the remote child\n\n",
1391 AppendEventMessageLog(info
);
1392 CrashReporter::AppendAppNotesToCrashReport(info
);
1393 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1394 MOZ_DIAGNOSTIC_ASSERT(
1395 false, "No pending event message but received unexpected event");
1396 mPendingEventsNeedingAck
= 1;
1398 if (--mPendingEventsNeedingAck
) {
1402 FlushPendingNotifications(aWidget
);
1405 bool ContentCacheInParent::RequestIMEToCommitComposition(
1406 nsIWidget
* aWidget
, bool aCancel
, nsAString
& aCommittedString
) {
1408 sContentCacheLog
, LogLevel::Info
,
1409 ("0x%p RequestToCommitComposition(aWidget=%p, "
1410 "aCancel=%s), mPendingCompositionCount=%" PRIu8
", "
1411 "mPendingCommitCount=%" PRIu8
", mIsChildIgnoringCompositionEvents=%s, "
1412 "IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)=%s, "
1413 "mWidgetHasComposition=%s, mCommitStringByRequest=%p",
1414 this, aWidget
, GetBoolName(aCancel
), mPendingCompositionCount
,
1415 mPendingCommitCount
, GetBoolName(mIsChildIgnoringCompositionEvents
),
1417 IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent
)),
1418 GetBoolName(mWidgetHasComposition
), mCommitStringByRequest
));
1420 MOZ_ASSERT(!mCommitStringByRequest
);
1422 // If there are 2 or more pending compositions, we already sent
1423 // eCompositionCommit(AsIs) to the remote process. So, this request is
1424 // too late for IME. The remote process should wait following
1425 // composition events for cleaning up TextComposition and handle the
1426 // request as it's handled asynchronously.
1427 if (mPendingCompositionCount
> 1) {
1428 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1429 mRequestIMEToCommitCompositionResults
.AppendElement(
1430 RequestIMEToCommitCompositionResult::eToOldCompositionReceived
);
1431 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1435 // If there is no pending composition, we may have already sent
1436 // eCompositionCommit(AsIs) event for the active composition. If so, the
1437 // remote process will receive composition events which causes cleaning up
1438 // TextComposition. So, this shouldn't do nothing and TextComposition
1439 // should handle the request as it's handled asynchronously.
1440 // XXX Perhaps, this is wrong because TextComposition in child process
1441 // may commit the composition with current composition string in the
1442 // remote process. I.e., it may be different from actual commit string
1443 // which user typed. So, perhaps, we should return true and the commit
1445 if (mPendingCommitCount
) {
1446 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1447 mRequestIMEToCommitCompositionResults
.AppendElement(
1448 RequestIMEToCommitCompositionResult::eToCommittedCompositionReceived
);
1449 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1453 // If BrowserParent which has IME focus was already changed to different one,
1454 // the request shouldn't be sent to IME because it's too late.
1455 if (!IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent
)) {
1456 // Use the latest composition string which may not be handled in the
1457 // remote process for avoiding data loss.
1458 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1459 mRequestIMEToCommitCompositionResults
.AppendElement(
1460 RequestIMEToCommitCompositionResult::eReceivedAfterBrowserParentBlur
);
1461 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1462 aCommittedString
= mCompositionString
;
1463 // After we return true from here, i.e., without actually requesting IME
1464 // to commit composition, we will receive eCompositionCommitRequestHandled
1465 // pseudo event message from the remote process. So, we need to increment
1466 // mPendingEventsNeedingAck here.
1467 mPendingEventsNeedingAck
++;
1471 RefPtr
<TextComposition
> composition
=
1472 IMEStateManager::GetTextCompositionFor(aWidget
);
1473 if (NS_WARN_IF(!composition
)) {
1474 MOZ_LOG(sContentCacheLog
, LogLevel::Warning
,
1475 (" 0x%p RequestToCommitComposition(), "
1476 "does nothing due to no composition",
1478 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1479 mRequestIMEToCommitCompositionResults
.AppendElement(
1480 RequestIMEToCommitCompositionResult::eReceivedButNoTextComposition
);
1481 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1485 mCommitStringByRequest
= &aCommittedString
;
1487 // Request commit or cancel composition with TextComposition because we may
1488 // have already requested to commit or cancel the composition or we may
1489 // have already received eCompositionCommit(AsIs) event. Those status are
1490 // managed by composition. So, if we don't request commit composition,
1491 // we should do nothing with native IME here.
1492 composition
->RequestToCommit(aWidget
, aCancel
);
1494 mCommitStringByRequest
= nullptr;
1497 sContentCacheLog
, LogLevel::Info
,
1498 (" 0x%p RequestToCommitComposition(), "
1499 "mWidgetHasComposition=%s, the composition %s committed synchronously",
1500 this, GetBoolName(mWidgetHasComposition
),
1501 composition
->Destroyed() ? "WAS" : "has NOT been"));
1503 if (!composition
->Destroyed()) {
1504 // When the composition isn't committed synchronously, the remote process's
1505 // TextComposition instance will synthesize commit events and wait to
1506 // receive delayed composition events. When TextComposition instances both
1507 // in this process and the remote process will be destroyed when delayed
1508 // composition events received. TextComposition instance in the parent
1509 // process will dispatch following composition events and be destroyed
1510 // normally. On the other hand, TextComposition instance in the remote
1511 // process won't dispatch following composition events and will be
1512 // destroyed by IMEStateManager::DispatchCompositionEvent().
1513 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1514 mRequestIMEToCommitCompositionResults
.AppendElement(
1515 RequestIMEToCommitCompositionResult::eHandledAsynchronously
);
1516 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1520 // When the composition is committed synchronously, the commit string will be
1521 // returned to the remote process. Then, PuppetWidget will dispatch
1522 // eCompositionCommit event with the returned commit string (i.e., the value
1523 // is aCommittedString of this method) and that causes destroying
1524 // TextComposition instance in the remote process (Note that TextComposition
1525 // instance in this process was already destroyed).
1526 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1527 mRequestIMEToCommitCompositionResults
.AppendElement(
1528 RequestIMEToCommitCompositionResult::eHandledSynchronously
);
1529 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1533 void ContentCacheInParent::MaybeNotifyIME(
1534 nsIWidget
* aWidget
, const IMENotification
& aNotification
) {
1535 if (!mPendingEventsNeedingAck
) {
1536 IMEStateManager::NotifyIME(aNotification
, aWidget
, &mBrowserParent
);
1540 switch (aNotification
.mMessage
) {
1541 case NOTIFY_IME_OF_SELECTION_CHANGE
:
1542 mPendingSelectionChange
.MergeWith(aNotification
);
1544 case NOTIFY_IME_OF_TEXT_CHANGE
:
1545 mPendingTextChange
.MergeWith(aNotification
);
1547 case NOTIFY_IME_OF_POSITION_CHANGE
:
1548 mPendingLayoutChange
.MergeWith(aNotification
);
1550 case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED
:
1551 mPendingCompositionUpdate
.MergeWith(aNotification
);
1554 MOZ_CRASH("Unsupported notification");
1559 void ContentCacheInParent::FlushPendingNotifications(nsIWidget
* aWidget
) {
1560 MOZ_ASSERT(!mPendingEventsNeedingAck
);
1562 // If the BrowserParent's widget has already gone, this can do nothing since
1563 // widget is necessary to notify IME of something.
1568 // New notifications which are notified during flushing pending notifications
1569 // should be merged again.
1570 mPendingEventsNeedingAck
++;
1572 nsCOMPtr
<nsIWidget
> widget
= aWidget
;
1574 // First, text change notification should be sent because selection change
1575 // notification notifies IME of current selection range in the latest content.
1576 // So, IME may need the latest content before that.
1577 if (mPendingTextChange
.HasNotification()) {
1578 IMENotification
notification(mPendingTextChange
);
1579 if (!widget
->Destroyed()) {
1580 mPendingTextChange
.Clear();
1581 IMEStateManager::NotifyIME(notification
, widget
, &mBrowserParent
);
1585 if (mPendingSelectionChange
.HasNotification()) {
1586 IMENotification
notification(mPendingSelectionChange
);
1587 if (!widget
->Destroyed()) {
1588 mPendingSelectionChange
.Clear();
1589 IMEStateManager::NotifyIME(notification
, widget
, &mBrowserParent
);
1593 // Layout change notification should be notified after selection change
1594 // notification because IME may want to query position of new caret position.
1595 if (mPendingLayoutChange
.HasNotification()) {
1596 IMENotification
notification(mPendingLayoutChange
);
1597 if (!widget
->Destroyed()) {
1598 mPendingLayoutChange
.Clear();
1599 IMEStateManager::NotifyIME(notification
, widget
, &mBrowserParent
);
1603 // Finally, send composition update notification because it notifies IME of
1604 // finishing handling whole sending events.
1605 if (mPendingCompositionUpdate
.HasNotification()) {
1606 IMENotification
notification(mPendingCompositionUpdate
);
1607 if (!widget
->Destroyed()) {
1608 mPendingCompositionUpdate
.Clear();
1609 IMEStateManager::NotifyIME(notification
, widget
, &mBrowserParent
);
1613 if (!--mPendingEventsNeedingAck
&& !widget
->Destroyed() &&
1614 (mPendingTextChange
.HasNotification() ||
1615 mPendingSelectionChange
.HasNotification() ||
1616 mPendingLayoutChange
.HasNotification() ||
1617 mPendingCompositionUpdate
.HasNotification())) {
1618 FlushPendingNotifications(widget
);
1622 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1624 void ContentCacheInParent::RemoveUnnecessaryEventMessageLog() {
1625 bool foundLastCompositionStart
= false;
1626 for (size_t i
= mDispatchedEventMessages
.Length(); i
> 1; i
--) {
1627 if (mDispatchedEventMessages
[i
- 1] != eCompositionStart
) {
1630 if (!foundLastCompositionStart
) {
1631 // Find previous eCompositionStart of the latest eCompositionStart.
1632 foundLastCompositionStart
= true;
1635 // Remove the messages before the last 2 sets of composition events.
1636 mDispatchedEventMessages
.RemoveElementsAt(0, i
- 1);
1639 uint32_t numberOfCompositionCommitRequestHandled
= 0;
1640 foundLastCompositionStart
= false;
1641 for (size_t i
= mReceivedEventMessages
.Length(); i
> 1; i
--) {
1642 if (mReceivedEventMessages
[i
- 1] == eCompositionCommitRequestHandled
) {
1643 numberOfCompositionCommitRequestHandled
++;
1645 if (mReceivedEventMessages
[i
- 1] != eCompositionStart
) {
1648 if (!foundLastCompositionStart
) {
1649 // Find previous eCompositionStart of the latest eCompositionStart.
1650 foundLastCompositionStart
= true;
1653 // Remove the messages before the last 2 sets of composition events.
1654 mReceivedEventMessages
.RemoveElementsAt(0, i
- 1);
1658 if (!numberOfCompositionCommitRequestHandled
) {
1659 // If there is no eCompositionCommitRequestHandled in
1660 // mReceivedEventMessages, we don't need to store log of
1661 // RequestIMEToCommmitComposition().
1662 mRequestIMEToCommitCompositionResults
.Clear();
1664 // We need to keep all reason of eCompositionCommitRequestHandled, which
1665 // is sent when mRequestIMEToCommitComposition() returns true.
1666 // So, we can discard older log than the first
1667 // eCompositionCommitRequestHandled in mReceivedEventMessages.
1668 for (size_t i
= mRequestIMEToCommitCompositionResults
.Length(); i
> 1;
1670 if (mRequestIMEToCommitCompositionResults
[i
- 1] ==
1671 RequestIMEToCommitCompositionResult::
1672 eReceivedAfterBrowserParentBlur
||
1673 mRequestIMEToCommitCompositionResults
[i
- 1] ==
1674 RequestIMEToCommitCompositionResult::eHandledSynchronously
) {
1675 --numberOfCompositionCommitRequestHandled
;
1676 if (!numberOfCompositionCommitRequestHandled
) {
1677 mRequestIMEToCommitCompositionResults
.RemoveElementsAt(0, i
- 1);
1685 void ContentCacheInParent::AppendEventMessageLog(nsACString
& aLog
) const {
1686 aLog
.AppendLiteral("Dispatched Event Message Log:\n");
1687 for (EventMessage message
: mDispatchedEventMessages
) {
1688 aLog
.AppendLiteral(" ");
1689 aLog
.Append(ToChar(message
));
1690 aLog
.AppendLiteral("\n");
1692 aLog
.AppendLiteral("\nReceived Event Message Log:\n");
1693 for (EventMessage message
: mReceivedEventMessages
) {
1694 aLog
.AppendLiteral(" ");
1695 aLog
.Append(ToChar(message
));
1696 aLog
.AppendLiteral("\n");
1698 aLog
.AppendLiteral("\nResult of RequestIMEToCommitComposition():\n");
1699 for (RequestIMEToCommitCompositionResult result
:
1700 mRequestIMEToCommitCompositionResults
) {
1701 aLog
.AppendLiteral(" ");
1702 aLog
.Append(ToReadableText(result
));
1703 aLog
.AppendLiteral("\n");
1705 aLog
.AppendLiteral("\n");
1708 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1710 /*****************************************************************************
1711 * mozilla::ContentCache::Selection
1712 *****************************************************************************/
1714 ContentCache::Selection::Selection(
1715 const WidgetQueryContentEvent
& aQuerySelectedTextEvent
)
1716 : mAnchor(UINT32_MAX
),
1718 mWritingMode(aQuerySelectedTextEvent
.mReply
->WritingModeRef()),
1719 mHasRange(aQuerySelectedTextEvent
.mReply
->mOffsetAndData
.isSome()) {
1720 MOZ_ASSERT(aQuerySelectedTextEvent
.mMessage
== eQuerySelectedText
);
1721 MOZ_ASSERT(aQuerySelectedTextEvent
.Succeeded());
1723 mAnchor
= aQuerySelectedTextEvent
.mReply
->AnchorOffset();
1724 mFocus
= aQuerySelectedTextEvent
.mReply
->FocusOffset();
1728 /*****************************************************************************
1729 * mozilla::ContentCache::TextRectArray
1730 *****************************************************************************/
1732 LayoutDeviceIntRect
ContentCache::TextRectArray::GetRect(
1733 uint32_t aOffset
) const {
1734 LayoutDeviceIntRect rect
;
1735 if (IsOffsetInRange(aOffset
)) {
1736 rect
= mRects
[aOffset
- mStart
];
1741 LayoutDeviceIntRect
ContentCache::TextRectArray::GetUnionRect(
1742 uint32_t aOffset
, uint32_t aLength
) const {
1743 LayoutDeviceIntRect rect
;
1744 if (!IsRangeCompletelyInRange(aOffset
, aLength
)) {
1747 for (uint32_t i
= 0; i
< aLength
; i
++) {
1748 rect
= rect
.Union(mRects
[aOffset
- mStart
+ i
]);
1753 LayoutDeviceIntRect
ContentCache::TextRectArray::GetUnionRectAsFarAsPossible(
1754 uint32_t aOffset
, uint32_t aLength
, bool aRoundToExistingOffset
) const {
1755 LayoutDeviceIntRect rect
;
1757 (!aRoundToExistingOffset
&& !IsOverlappingWith(aOffset
, aLength
))) {
1760 uint32_t startOffset
= std::max(aOffset
, mStart
);
1761 if (aRoundToExistingOffset
&& startOffset
>= EndOffset()) {
1762 startOffset
= EndOffset() - 1;
1764 uint32_t endOffset
= std::min(aOffset
+ aLength
, EndOffset());
1765 if (aRoundToExistingOffset
&& endOffset
< mStart
+ 1) {
1766 endOffset
= mStart
+ 1;
1768 if (NS_WARN_IF(endOffset
< startOffset
)) {
1771 for (uint32_t i
= 0; i
< endOffset
- startOffset
; i
++) {
1772 rect
= rect
.Union(mRects
[startOffset
- mStart
+ i
]);
1777 } // namespace mozilla