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 // 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 "
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
) {
163 if (MOZ_UNLIKELY(mSelection
.isNothing())) {
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 "
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()));
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())) {
205 sContentCacheLog
, LogLevel::Error
,
206 ("0x%p CacheEditorRect(), FAILED, couldn't retrieve the editor rect",
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 "
220 mEditorRect
= queryEditorRectEvent
.mReply
->mRect
;
221 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
222 ("0x%p CacheEditorRect(), Succeeded, mEditorRect=%s", this,
223 ToString(mEditorRect
).c_str()));
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
,
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));
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 "
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
)
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(),
271 nsDependentSubstring(mText
.ref(), mLastCommit
->StartOffset(),
272 mLastCommit
->Length()),
273 PrintStringDetail::kMaxLengthForCompositionString
)
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
)) {
285 mTextRectArray
.reset();
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())) {
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);
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
,
321 queryTextRectsEvent
.InitForQueryTextRectArray(aOffset
, aLength
);
322 aWidget
->DispatchEvent(&queryTextRectsEvent
, status
);
323 if (NS_WARN_IF(queryTextRectsEvent
.Failed())) {
324 aCharRectArray
.Clear();
327 aCharRectArray
= std::move(queryTextRectsEvent
.mReply
->mRectArray
);
331 bool ContentCacheInChild::CacheTextRects(nsIWidget
* aWidget
,
332 const IMENotification
* aNotification
) {
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
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",
363 mTextRectArray
.reset();
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.
386 const uint32_t startOffset
=
387 mSelection
.isSome() && mSelection
->mHasRange
&& mSelection
->mAnchor
388 ? mSelection
->mAnchor
- 1u
390 const uint32_t length
=
391 mSelection
.isSome() && mSelection
->mHasRange
&& mSelection
->mAnchor
394 if (NS_WARN_IF(!QueryCharRectArray(aWidget
, startOffset
, length
, rects
))) {
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];
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.
444 MOZ_ASSERT(mSelection
->mHasRange
);
446 const uint32_t startOffset
=
447 mSelection
->mFocus
? mSelection
->mFocus
- 1u : 0u;
448 const uint32_t length
= mSelection
->mFocus
? 2u : 1u;
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 "
455 this, mSelection
->mFocus
));
456 MOZ_ASSERT(mSelection
->mFocusCharRects
[ePrevCharRect
].IsEmpty());
457 MOZ_ASSERT(mSelection
->mFocusCharRects
[eNextCharRect
].IsEmpty());
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",
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);
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",
513 mFirstCharRect
.SetEmpty();
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",
539 mLastCommitStringTextRectArray
.reset();
542 MOZ_ASSERT((mLastCommitStringTextRectArray
.isSome()
543 ? mLastCommitStringTextRectArray
->mRects
.Length()
544 : 0) == (mLastCommit
.isSome() ? mLastCommit
->Length() : 0));
546 mLastCommitStringTextRectArray
.reset();
550 sContentCacheLog
, LogLevel::Info
,
551 ("0x%p CacheTextRects(), Succeeded, "
552 "mText=%s, mTextRectArray=%s, mSelection=%s, "
553 "mFirstCharRect=%s, mLastCommitStringTextRectArray=%s",
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()));
562 void ContentCacheInChild::SetSelection(
564 const IMENotification::SelectionChangeDataBase
& aSelectionChangeData
) {
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()) {
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()));
588 CacheTextRects(aWidget
);
591 /*****************************************************************************
592 * mozilla::ContentCacheInParent
593 *****************************************************************************/
595 ContentCacheInParent::ContentCacheInParent(BrowserParent
& aBrowserParent
)
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
,
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
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;
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 {
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 "
680 if (NS_WARN_IF(!aEvent
.mInput
.IsValidOffset())) {
682 sContentCacheLog
, LogLevel::Error
,
683 ("0x%p HandleQueryContentEvent(), FAILED due to invalid offset", this));
687 if (NS_WARN_IF(!aEvent
.mInput
.IsValidEventMessage(aEvent
.mMessage
))) {
689 sContentCacheLog
, LogLevel::Error
,
690 ("0x%p HandleQueryContentEvent(), FAILED due to invalid event message",
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
))) {
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
));
725 } else if (NS_WARN_IF(mSelection
.isNothing())) {
726 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
727 ("0x%p HandleQueryContentEvent(), FAILED due to mSelection is "
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 "
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
));
753 switch (aEvent
.mMessage
) {
754 case eQuerySelectedText
:
755 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
756 ("0x%p HandleQueryContentEvent(aEvent={ "
757 "mMessage=eQuerySelectedText }, aWidget=0x%p)",
759 if (MOZ_UNLIKELY(NS_WARN_IF(mSelection
.isNothing()))) {
760 // If content cache hasn't been initialized properly, make the query
762 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
763 ("0x%p HandleQueryContentEvent(), FAILED because mSelection "
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
);
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()));
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",
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
));
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()));
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
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",
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",
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
877 : static_cast<const nsAString
&>(EmptyString());
878 aEvent
.mReply
->mOffsetAndData
.emplace(aEvent
.mInput
.mOffset
,
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()));
889 case eQueryCaretRect
: {
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
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",
909 aEvent
.EmplaceReply();
910 aEvent
.mReply
->mFocusedWidget
= aWidget
;
911 aEvent
.mReply
->mRect
= caretRect
;
912 aEvent
.mReply
->mOffsetAndData
.emplace(aEvent
.mInput
.mOffset
,
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()));
922 case eQueryEditorRect
:
923 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
924 ("0x%p HandleQueryContentEvent(aEvent={ "
925 "mMessage=eQueryEditorRect }, aWidget=0x%p)",
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()));
938 aEvent
.EmplaceReply();
939 aEvent
.mReply
->mFocusedWidget
= aWidget
;
940 if (NS_WARN_IF(aEvent
.Failed())) {
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()));
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()));
956 bool ContentCacheInParent::GetTextRect(uint32_t aOffset
,
957 bool aRoundToExistingOffset
,
958 LayoutDeviceIntRect
& aTextRect
) const {
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()));
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();
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();
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
1033 uint32_t offset
= aOffset
;
1034 if (offset
< mTextRectArray
->StartOffset()) {
1035 offset
= mTextRectArray
->StartOffset();
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()) {
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();
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(),
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(),
1084 aUnionTextRect
= mSelection
->mAnchorCharRects
[ePrevCharRect
];
1085 return !aUnionTextRect
.IsEmpty();
1087 if (aOffset
== mSelection
->mFocus
) {
1088 NS_WARNING_ASSERTION(
1089 !mSelection
->mFocusCharRects
[eNextCharRect
].IsEmpty(),
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(),
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
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.
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
)) {
1125 mLastCommitStringTextRectArray
->GetUnionRectAsFarAsPossible(
1126 aOffset
, aLength
, aRoundToExistingOffset
);
1128 aUnionTextRect
.SetEmpty();
1131 if (mTextRectArray
.isSome() &&
1132 ((aRoundToExistingOffset
&& mTextRectArray
->HasRects()) ||
1133 mTextRectArray
->IsOverlappingWith(aOffset
, aLength
))) {
1135 aUnionTextRect
.Union(mTextRectArray
->GetUnionRectAsFarAsPossible(
1136 aOffset
, aLength
, aRoundToExistingOffset
));
1140 aUnionTextRect
= aUnionTextRect
.Union(mFirstCharRect
);
1142 if (mSelection
.isSome() && mSelection
->mHasRange
) {
1143 if (aOffset
<= mSelection
->mAnchor
&&
1144 mSelection
->mAnchor
< endOffset
.value()) {
1146 aUnionTextRect
.Union(mSelection
->mAnchorCharRects
[eNextCharRect
]);
1148 if (mSelection
->mAnchor
&& aOffset
<= mSelection
->mAnchor
- 1 &&
1149 mSelection
->mAnchor
- 1 < endOffset
.value()) {
1151 aUnionTextRect
.Union(mSelection
->mAnchorCharRects
[ePrevCharRect
]);
1153 if (aOffset
<= mSelection
->mFocus
&&
1154 mSelection
->mFocus
< endOffset
.value()) {
1156 aUnionTextRect
.Union(mSelection
->mFocusCharRects
[eNextCharRect
]);
1158 if (mSelection
->mFocus
&& aOffset
<= mSelection
->mFocus
- 1 &&
1159 mSelection
->mFocus
- 1 < endOffset
.value()) {
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
;
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.
1188 !GetTextRect(aOffset
- 1, aRoundToExistingOffset
, aCaretRect
)) {
1189 aCaretRect
.SetEmpty();
1193 if (mSelection
.isSome() && mSelection
->mWritingMode
.IsVertical()) {
1194 aCaretRect
.MoveToY(aCaretRect
.YMost());
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);
1207 aCaretRect
.SetWidth(mCaret
.isSome() ? mCaret
->mRect
.Width() : 1);
1212 bool ContentCacheInParent::OnCompositionEvent(
1213 const WidgetCompositionEvent
& aEvent
) {
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
)
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
;
1244 mCompositionStart
= Some(mSelection
.isSome() && mSelection
->mHasRange
1245 ? mSelection
->StartOffset()
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
;
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
--;
1296 mPendingEventsNeedingAck
++;
1300 void ContentCacheInParent::OnSelectionEvent(
1301 const WidgetSelectionEvent
& aSelectionEvent
) {
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
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.
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
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
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
1360 nsPrintfCString
info(
1361 "\nThere is no pending composition but received %s "
1362 "message from the remote child\n\n",
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
1397 nsPrintfCString
info(
1398 "\nThere is no pending comment events but received "
1399 "%s message from the remote child\n\n",
1401 AppendEventMessageLog(info
);
1402 CrashReporter::AppendAppNotesToCrashReport(info
);
1403 MOZ_DIAGNOSTIC_ASSERT(
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
1431 nsPrintfCString
info(
1432 "\nThere is no pending events but received %s "
1433 "message from the remote child\n\n",
1435 AppendEventMessageLog(info
);
1436 CrashReporter::AppendAppNotesToCrashReport(info
);
1437 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1438 MOZ_DIAGNOSTIC_ASSERT(
1439 false, "No pending event message but received unexpected event");
1440 mPendingEventsNeedingAck
= 1;
1442 if (--mPendingEventsNeedingAck
) {
1446 FlushPendingNotifications(aWidget
);
1449 bool ContentCacheInParent::RequestIMEToCommitComposition(
1450 nsIWidget
* aWidget
, bool aCancel
, nsAString
& aCommittedString
) {
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
),
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
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
1489 if (mPendingCommitCount
) {
1490 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1491 mRequestIMEToCommitCompositionResults
.AppendElement(
1492 RequestIMEToCommitCompositionResult::eToCommittedCompositionReceived
);
1493 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
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
++;
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",
1522 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1523 mRequestIMEToCommitCompositionResults
.AppendElement(
1524 RequestIMEToCommitCompositionResult::eReceivedButNoTextComposition
);
1525 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
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;
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
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
1577 void ContentCacheInParent::MaybeNotifyIME(
1578 nsIWidget
* aWidget
, const IMENotification
& aNotification
) {
1579 if (!mPendingEventsNeedingAck
) {
1580 IMEStateManager::NotifyIME(aNotification
, aWidget
, &mBrowserParent
);
1584 switch (aNotification
.mMessage
) {
1585 case NOTIFY_IME_OF_SELECTION_CHANGE
:
1586 mPendingSelectionChange
.MergeWith(aNotification
);
1588 case NOTIFY_IME_OF_TEXT_CHANGE
:
1589 mPendingTextChange
.MergeWith(aNotification
);
1591 case NOTIFY_IME_OF_POSITION_CHANGE
:
1592 mPendingLayoutChange
.MergeWith(aNotification
);
1594 case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED
:
1595 mPendingCompositionUpdate
.MergeWith(aNotification
);
1598 MOZ_CRASH("Unsupported notification");
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.
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
) {
1674 if (!foundLastCompositionStart
) {
1675 // Find previous eCompositionStart of the latest eCompositionStart.
1676 foundLastCompositionStart
= true;
1679 // Remove the messages before the last 2 sets of composition events.
1680 mDispatchedEventMessages
.RemoveElementsAt(0, i
- 1);
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
) {
1692 if (!foundLastCompositionStart
) {
1693 // Find previous eCompositionStart of the latest eCompositionStart.
1694 foundLastCompositionStart
= true;
1697 // Remove the messages before the last 2 sets of composition events.
1698 mReceivedEventMessages
.RemoveElementsAt(0, i
- 1);
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();
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;
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);
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
),
1762 mWritingMode(aQuerySelectedTextEvent
.mReply
->WritingModeRef()),
1763 mHasRange(aQuerySelectedTextEvent
.mReply
->mOffsetAndData
.isSome()) {
1764 MOZ_ASSERT(aQuerySelectedTextEvent
.mMessage
== eQuerySelectedText
);
1765 MOZ_ASSERT(aQuerySelectedTextEvent
.Succeeded());
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
];
1785 LayoutDeviceIntRect
ContentCache::TextRectArray::GetUnionRect(
1786 uint32_t aOffset
, uint32_t aLength
) const {
1787 LayoutDeviceIntRect rect
;
1788 if (!IsRangeCompletelyInRange(aOffset
, aLength
)) {
1791 for (uint32_t i
= 0; i
< aLength
; i
++) {
1792 rect
= rect
.Union(mRects
[aOffset
- mStart
+ i
]);
1797 LayoutDeviceIntRect
ContentCache::TextRectArray::GetUnionRectAsFarAsPossible(
1798 uint32_t aOffset
, uint32_t aLength
, bool aRoundToExistingOffset
) const {
1799 LayoutDeviceIntRect rect
;
1801 (!aRoundToExistingOffset
&& !IsOverlappingWith(aOffset
, aLength
))) {
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
)) {
1815 for (uint32_t i
= 0; i
< endOffset
- startOffset
; i
++) {
1816 rect
= rect
.Union(mRects
[startOffset
- mStart
+ i
]);
1821 } // namespace mozilla