Bug 1754698 - add environment variables to interactive tasks. r=releng-reviewers...
[gecko.git] / widget / ContentCache.cpp
blob53fa7d75facc5ef3d693c5213365f258c0ac517e
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: sw=2 ts=8 et :
3 */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "ContentCache.h"
10 #include <utility>
12 #include "IMEData.h"
13 #include "TextEvents.h"
15 #include "mozilla/IMEStateManager.h"
16 #include "mozilla/IntegerPrintfMacros.h"
17 #include "mozilla/Logging.h"
18 #include "mozilla/RefPtr.h"
19 #include "mozilla/TextComposition.h"
20 #include "mozilla/dom/BrowserParent.h"
21 #include "nsExceptionHandler.h"
22 #include "nsIWidget.h"
24 namespace mozilla {
26 using namespace dom;
27 using namespace widget;
29 static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; }
31 static const char* GetNotificationName(const IMENotification* aNotification) {
32 if (!aNotification) {
33 return "Not notification";
35 return ToChar(aNotification->mMessage);
38 /*****************************************************************************
39 * mozilla::ContentCache
40 *****************************************************************************/
42 LazyLogModule sContentCacheLog("ContentCacheWidgets");
44 /*****************************************************************************
45 * mozilla::ContentCacheInChild
46 *****************************************************************************/
48 void ContentCacheInChild::Clear() {
49 MOZ_LOG(sContentCacheLog, LogLevel::Info, ("0x%p Clear()", this));
51 mCompositionStart.reset();
52 mLastCommit.reset();
53 mText.reset();
54 mSelection.reset();
55 mFirstCharRect.SetEmpty();
56 mCaret.reset();
57 mTextRectArray.reset();
58 mLastCommitStringTextRectArray.reset();
59 mEditorRect.SetEmpty();
62 void ContentCacheInChild::OnCompositionEvent(
63 const WidgetCompositionEvent& aCompositionEvent) {
64 if (aCompositionEvent.CausesDOMCompositionEndEvent()) {
65 RefPtr<TextComposition> composition =
66 IMEStateManager::GetTextCompositionFor(aCompositionEvent.mWidget);
67 if (composition) {
68 nsAutoString lastCommitString;
69 if (aCompositionEvent.mMessage == eCompositionCommitAsIs) {
70 lastCommitString = composition->CommitStringIfCommittedAsIs();
71 } else {
72 lastCommitString = aCompositionEvent.mData;
74 // We don't need to store canceling information because this is required
75 // by undoing of last commit (Kakutei-Undo of Japanese IME).
76 if (!lastCommitString.IsEmpty()) {
77 mLastCommit = Some(OffsetAndData<uint32_t>(
78 composition->NativeOffsetOfStartComposition(), lastCommitString));
79 MOZ_LOG(
80 sContentCacheLog, LogLevel::Debug,
81 ("0x%p OnCompositionEvent(), stored last composition string data "
82 "(aCompositionEvent={ mMessage=%s, mData=\"%s\"}, mLastCommit=%s)",
83 this, ToChar(aCompositionEvent.mMessage),
84 PrintStringDetail(
85 aCompositionEvent.mData,
86 PrintStringDetail::kMaxLengthForCompositionString)
87 .get(),
88 ToString(mLastCommit).c_str()));
89 return;
93 if (mLastCommit.isSome()) {
94 MOZ_LOG(
95 sContentCacheLog, LogLevel::Debug,
96 ("0x%p OnCompositionEvent(), resetting the last composition string "
97 "data (aCompositionEvent={ mMessage=%s, mData=\"%s\"}, "
98 "mLastCommit=%s)",
99 this, ToChar(aCompositionEvent.mMessage),
100 PrintStringDetail(aCompositionEvent.mData,
101 PrintStringDetail::kMaxLengthForCompositionString)
102 .get(),
103 ToString(mLastCommit).c_str()));
104 mLastCommit.reset();
108 bool ContentCacheInChild::CacheAll(nsIWidget* aWidget,
109 const IMENotification* aNotification) {
110 MOZ_LOG(sContentCacheLog, LogLevel::Info,
111 ("0x%p CacheAll(aWidget=0x%p, aNotification=%s)", this, aWidget,
112 GetNotificationName(aNotification)));
114 const bool textCached = CacheText(aWidget, aNotification);
115 const bool editorRectCached = CacheEditorRect(aWidget, aNotification);
116 return textCached || editorRectCached;
119 bool ContentCacheInChild::CacheSelection(nsIWidget* aWidget,
120 const IMENotification* aNotification) {
121 MOZ_LOG(sContentCacheLog, LogLevel::Info,
122 ("0x%p CacheSelection(aWidget=0x%p, aNotification=%s)", this, aWidget,
123 GetNotificationName(aNotification)));
125 mSelection.reset();
127 nsEventStatus status = nsEventStatus_eIgnore;
128 WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
129 aWidget);
130 aWidget->DispatchEvent(&querySelectedTextEvent, status);
131 if (NS_WARN_IF(querySelectedTextEvent.Failed())) {
132 MOZ_LOG(
133 sContentCacheLog, LogLevel::Error,
134 ("0x%p CacheSelection(), FAILED, couldn't retrieve the selected text",
135 this));
136 } else {
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) {
147 mCaret.reset();
149 if (MOZ_UNLIKELY(mSelection.isNothing())) {
150 return false;
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 "
168 "at offset=%u",
169 this, offset));
170 return false;
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()));
177 return true;
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())) {
190 MOZ_LOG(
191 sContentCacheLog, LogLevel::Error,
192 ("0x%p CacheEditorRect(), FAILED, couldn't retrieve the editor rect",
193 this));
194 return false;
196 mEditorRect = queryEditorRectEvent.mReply->mRect;
197 MOZ_LOG(sContentCacheLog, LogLevel::Info,
198 ("0x%p CacheEditorRect(), Succeeded, mEditorRect=%s", this,
199 ToString(mEditorRect).c_str()));
200 return true;
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,
211 aWidget);
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));
217 mText.reset();
218 } else {
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)
223 .get()));
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(),
236 PrintStringDetail(
237 nsDependentSubstring(mText.ref(), mLastCommit->StartOffset(),
238 mLastCommit->Length()),
239 PrintStringDetail::kMaxLengthForCompositionString)
240 .get()));
241 mLastCommit.reset();
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())) {
257 return false;
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);
268 return true;
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,
276 aWidget);
277 queryTextRectsEvent.InitForQueryTextRectArray(aOffset, aLength);
278 aWidget->DispatchEvent(&queryTextRectsEvent, status);
279 if (NS_WARN_IF(queryTextRectsEvent.Failed())) {
280 aCharRectArray.Clear();
281 return false;
283 aCharRectArray = std::move(queryTextRectsEvent.mReply->mRectArray);
284 return true;
287 bool ContentCacheInChild::CacheTextRects(nsIWidget* aWidget,
288 const IMENotification* aNotification) {
289 MOZ_LOG(
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
310 // composition too.
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",
318 this));
319 mTextRectArray.reset();
321 } else {
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.
340 else {
341 RectArray rects;
342 const uint32_t startOffset =
343 mSelection.isSome() && mSelection->mHasRange && mSelection->mAnchor
344 ? mSelection->mAnchor - 1u
345 : 0u;
346 const uint32_t length =
347 mSelection.isSome() && mSelection->mHasRange && mSelection->mAnchor
348 ? 2u
349 : 1u;
350 if (NS_WARN_IF(!QueryCharRectArray(aWidget, startOffset, length, rects))) {
351 MOZ_LOG(
352 sContentCacheLog, LogLevel::Error,
353 ("0x%p CacheTextRects(), FAILED, "
354 "couldn't retrieve text rect array around the selection anchor (%u)",
355 this, mSelection->mAnchor));
356 MOZ_ASSERT_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];
367 } else {
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.
399 else {
400 MOZ_ASSERT(mSelection->mHasRange);
401 RectArray rects;
402 const uint32_t startOffset =
403 mSelection->mFocus ? mSelection->mFocus - 1u : 0u;
404 const uint32_t length = mSelection->mFocus ? 2u : 1u;
405 if (NS_WARN_IF(
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 "
410 "(%u)",
411 this, mSelection->mFocus));
412 MOZ_ASSERT(mSelection->mFocusCharRects[ePrevCharRect].IsEmpty());
413 MOZ_ASSERT(mSelection->mFocusCharRects[eNextCharRect].IsEmpty());
414 } else {
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",
441 this));
442 } else {
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);
462 } else {
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",
468 this));
469 mFirstCharRect.SetEmpty();
470 } else {
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",
494 this));
495 mLastCommitStringTextRectArray.reset();
496 mLastCommit.reset();
498 MOZ_ASSERT((mLastCommitStringTextRectArray.isSome()
499 ? mLastCommitStringTextRectArray->mRects.Length()
500 : 0) == (mLastCommit.isSome() ? mLastCommit->Length() : 0));
501 } else {
502 mLastCommitStringTextRectArray.reset();
505 MOZ_LOG(
506 sContentCacheLog, LogLevel::Info,
507 ("0x%p CacheTextRects(), Succeeded, "
508 "mText=%s, mTextRectArray=%s, mSelection=%s, "
509 "mFirstCharRect=%s, mLastCommitStringTextRectArray=%s",
510 this,
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()));
515 return true;
518 void ContentCacheInChild::SetSelection(
519 nsIWidget* aWidget,
520 const IMENotification::SelectionChangeDataBase& aSelectionChangeData) {
521 MOZ_LOG(
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()) {
534 MOZ_LOG(
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()));
539 mLastCommit.reset();
543 CacheCaret(aWidget);
544 CacheTextRects(aWidget);
547 /*****************************************************************************
548 * mozilla::ContentCacheInParent
549 *****************************************************************************/
551 ContentCacheInParent::ContentCacheInParent(BrowserParent& aBrowserParent)
552 : ContentCache(),
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,
563 nsIWidget* aWidget,
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
586 // string.
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;
603 MOZ_LOG(
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 {
623 MOZ_ASSERT(aWidget);
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 "
631 "linebreaks",
632 this));
633 return false;
636 if (NS_WARN_IF(!aEvent.mInput.IsValidOffset())) {
637 MOZ_LOG(
638 sContentCacheLog, LogLevel::Error,
639 ("0x%p HandleQueryContentEvent(), FAILED due to invalid offset", this));
640 return false;
643 if (NS_WARN_IF(!aEvent.mInput.IsValidEventMessage(aEvent.mMessage))) {
644 MOZ_LOG(
645 sContentCacheLog, LogLevel::Error,
646 ("0x%p HandleQueryContentEvent(), FAILED due to invalid event message",
647 this));
648 return false;
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))) {
668 MOZ_LOG(
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));
679 return false;
681 } else if (NS_WARN_IF(mSelection.isNothing())) {
682 MOZ_LOG(sContentCacheLog, LogLevel::Error,
683 ("0x%p HandleQueryContentEvent(), FAILED due to mSelection is "
684 "Nothing",
685 this));
686 return false;
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 "
691 "from selection",
692 this));
693 return false;
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));
705 return false;
709 switch (aEvent.mMessage) {
710 case eQuerySelectedText:
711 MOZ_LOG(sContentCacheLog, LogLevel::Info,
712 ("0x%p HandleQueryContentEvent(aEvent={ "
713 "mMessage=eQuerySelectedText }, aWidget=0x%p)",
714 this, aWidget));
715 if (MOZ_UNLIKELY(NS_WARN_IF(mSelection.isNothing()))) {
716 // If content cache hasn't been initialized properly, make the query
717 // failed.
718 MOZ_LOG(sContentCacheLog, LogLevel::Error,
719 ("0x%p HandleQueryContentEvent(), FAILED because mSelection "
720 "is Nothing",
721 this));
722 return false;
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);
736 } else {
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()));
748 return true;
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",
760 this));
761 return false;
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));
771 return false;
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()));
787 return true;
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
799 // at odd position.
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",
808 this));
809 return false;
811 } else {
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",
817 this));
818 return false;
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
832 : UINT32_MAX))
833 : static_cast<const nsAString&>(EmptyString());
834 aEvent.mReply->mOffsetAndData.emplace(aEvent.mInput.mOffset,
835 textInQueriedRange,
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()));
843 return true;
845 case eQueryCaretRect: {
846 MOZ_LOG(
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
856 // at odd position.
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",
862 this));
863 return false;
865 aEvent.EmplaceReply();
866 aEvent.mReply->mFocusedWidget = aWidget;
867 aEvent.mReply->mRect = caretRect;
868 aEvent.mReply->mOffsetAndData.emplace(aEvent.mInput.mOffset,
869 EmptyString(),
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()));
876 return true;
878 case eQueryEditorRect:
879 MOZ_LOG(sContentCacheLog, LogLevel::Info,
880 ("0x%p HandleQueryContentEvent(aEvent={ "
881 "mMessage=eQueryEditorRect }, aWidget=0x%p)",
882 this, aWidget));
883 aEvent.EmplaceReply();
884 aEvent.mReply->mFocusedWidget = aWidget;
885 aEvent.mReply->mRect = mEditorRect;
886 MOZ_LOG(sContentCacheLog, LogLevel::Info,
887 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
888 "mMessage=eQueryEditorRect, mReply=%s }",
889 this, ToString(aEvent.mReply).c_str()));
890 return true;
891 default:
892 aEvent.EmplaceReply();
893 aEvent.mReply->mFocusedWidget = aWidget;
894 if (NS_WARN_IF(aEvent.Failed())) {
895 MOZ_LOG(
896 sContentCacheLog, LogLevel::Error,
897 ("0x%p HandleQueryContentEvent(), FAILED due to not set enough "
898 "data, aEvent={ mMessage=%s, mReply=%s }",
899 this, ToChar(aEvent.mMessage), ToString(aEvent.mReply).c_str()));
900 return false;
902 MOZ_LOG(sContentCacheLog, LogLevel::Info,
903 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
904 "mMessage=%s, mReply=%s }",
905 this, ToChar(aEvent.mMessage), ToString(aEvent.mReply).c_str()));
906 return true;
910 bool ContentCacheInParent::GetTextRect(uint32_t aOffset,
911 bool aRoundToExistingOffset,
912 LayoutDeviceIntRect& aTextRect) const {
913 MOZ_LOG(
914 sContentCacheLog, LogLevel::Info,
915 ("0x%p GetTextRect(aOffset=%u, aRoundToExistingOffset=%s), "
916 "mTextRectArray=%s, mSelection=%s, mLastCommitStringTextRectArray=%s",
917 this, aOffset, GetBoolName(aRoundToExistingOffset),
918 ToString(mTextRectArray).c_str(), ToString(mSelection).c_str(),
919 ToString(mLastCommitStringTextRectArray).c_str()));
921 if (!aOffset) {
922 NS_WARNING_ASSERTION(!mFirstCharRect.IsEmpty(), "empty rect");
923 aTextRect = mFirstCharRect;
924 return !aTextRect.IsEmpty();
926 if (mSelection.isSome() && mSelection->mHasRange) {
927 if (aOffset == mSelection->mAnchor) {
928 NS_WARNING_ASSERTION(
929 !mSelection->mAnchorCharRects[eNextCharRect].IsEmpty(), "empty rect");
930 aTextRect = mSelection->mAnchorCharRects[eNextCharRect];
931 return !aTextRect.IsEmpty();
933 if (mSelection->mAnchor && aOffset == mSelection->mAnchor - 1) {
934 NS_WARNING_ASSERTION(
935 !mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty(), "empty rect");
936 aTextRect = mSelection->mAnchorCharRects[ePrevCharRect];
937 return !aTextRect.IsEmpty();
939 if (aOffset == mSelection->mFocus) {
940 NS_WARNING_ASSERTION(
941 !mSelection->mFocusCharRects[eNextCharRect].IsEmpty(), "empty rect");
942 aTextRect = mSelection->mFocusCharRects[eNextCharRect];
943 return !aTextRect.IsEmpty();
945 if (mSelection->mFocus && aOffset == mSelection->mFocus - 1) {
946 NS_WARNING_ASSERTION(
947 !mSelection->mFocusCharRects[ePrevCharRect].IsEmpty(), "empty rect");
948 aTextRect = mSelection->mFocusCharRects[ePrevCharRect];
949 return !aTextRect.IsEmpty();
953 if (mTextRectArray.isSome() && mTextRectArray->IsOffsetInRange(aOffset)) {
954 aTextRect = mTextRectArray->GetRect(aOffset);
955 return !aTextRect.IsEmpty();
958 if (mLastCommitStringTextRectArray.isSome() &&
959 mLastCommitStringTextRectArray->IsOffsetInRange(aOffset)) {
960 aTextRect = mLastCommitStringTextRectArray->GetRect(aOffset);
961 return !aTextRect.IsEmpty();
964 if (!aRoundToExistingOffset) {
965 aTextRect.SetEmpty();
966 return false;
969 if (mTextRectArray.isNothing() || !mTextRectArray->HasRects()) {
970 // If there are no rects in mTextRectArray, we should refer the start of
971 // the selection because IME must query a char rect around it if there is
972 // no composition.
973 aTextRect = mSelection->StartCharRect();
974 return !aTextRect.IsEmpty();
977 // Although we may have mLastCommitStringTextRectArray here and it must have
978 // previous character rects at selection. However, we should stop using it
979 // because it's stored really short time after commiting a composition.
980 // So, multiple query may return different rect and it may cause flickerling
981 // the IME UI.
982 uint32_t offset = aOffset;
983 if (offset < mTextRectArray->StartOffset()) {
984 offset = mTextRectArray->StartOffset();
985 } else {
986 offset = mTextRectArray->EndOffset() - 1;
988 aTextRect = mTextRectArray->GetRect(offset);
989 return !aTextRect.IsEmpty();
992 bool ContentCacheInParent::GetUnionTextRects(
993 uint32_t aOffset, uint32_t aLength, bool aRoundToExistingOffset,
994 LayoutDeviceIntRect& aUnionTextRect) const {
995 MOZ_LOG(sContentCacheLog, LogLevel::Info,
996 ("0x%p GetUnionTextRects(aOffset=%u, "
997 "aLength=%u, aRoundToExistingOffset=%s), mTextRectArray=%s, "
998 "mSelection=%s, mLastCommitStringTextRectArray=%s",
999 this, aOffset, aLength, GetBoolName(aRoundToExistingOffset),
1000 ToString(mTextRectArray).c_str(), ToString(mSelection).c_str(),
1001 ToString(mLastCommitStringTextRectArray).c_str()));
1003 CheckedInt<uint32_t> endOffset = CheckedInt<uint32_t>(aOffset) + aLength;
1004 if (!endOffset.isValid()) {
1005 return false;
1008 if (mSelection.isSome() && !mSelection->IsCollapsed() &&
1009 aOffset == mSelection->StartOffset() && aLength == mSelection->Length()) {
1010 NS_WARNING_ASSERTION(!mSelection->mRect.IsEmpty(), "empty rect");
1011 aUnionTextRect = mSelection->mRect;
1012 return !aUnionTextRect.IsEmpty();
1015 if (aLength == 1) {
1016 if (!aOffset) {
1017 NS_WARNING_ASSERTION(!mFirstCharRect.IsEmpty(), "empty rect");
1018 aUnionTextRect = mFirstCharRect;
1019 return !aUnionTextRect.IsEmpty();
1021 if (mSelection.isSome() && mSelection->mHasRange) {
1022 if (aOffset == mSelection->mAnchor) {
1023 NS_WARNING_ASSERTION(
1024 !mSelection->mAnchorCharRects[eNextCharRect].IsEmpty(),
1025 "empty rect");
1026 aUnionTextRect = mSelection->mAnchorCharRects[eNextCharRect];
1027 return !aUnionTextRect.IsEmpty();
1029 if (mSelection->mAnchor && aOffset == mSelection->mAnchor - 1) {
1030 NS_WARNING_ASSERTION(
1031 !mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty(),
1032 "empty rect");
1033 aUnionTextRect = mSelection->mAnchorCharRects[ePrevCharRect];
1034 return !aUnionTextRect.IsEmpty();
1036 if (aOffset == mSelection->mFocus) {
1037 NS_WARNING_ASSERTION(
1038 !mSelection->mFocusCharRects[eNextCharRect].IsEmpty(),
1039 "empty rect");
1040 aUnionTextRect = mSelection->mFocusCharRects[eNextCharRect];
1041 return !aUnionTextRect.IsEmpty();
1043 if (mSelection->mFocus && aOffset == mSelection->mFocus - 1) {
1044 NS_WARNING_ASSERTION(
1045 !mSelection->mFocusCharRects[ePrevCharRect].IsEmpty(),
1046 "empty rect");
1047 aUnionTextRect = mSelection->mFocusCharRects[ePrevCharRect];
1048 return !aUnionTextRect.IsEmpty();
1053 // Even if some text rects are not cached of the queried range,
1054 // we should return union rect when the first character's rect is cached
1055 // since the first character rect is important and the others are not so
1056 // in most cases.
1058 if (!aOffset && mSelection.isSome() && mSelection->mHasRange &&
1059 aOffset != mSelection->mAnchor && aOffset != mSelection->mFocus &&
1060 (mTextRectArray.isNothing() ||
1061 !mTextRectArray->IsOffsetInRange(aOffset)) &&
1062 (mLastCommitStringTextRectArray.isNothing() ||
1063 !mLastCommitStringTextRectArray->IsOffsetInRange(aOffset))) {
1064 // The first character rect isn't cached.
1065 return false;
1068 // Use mLastCommitStringTextRectArray only when it overlaps with aOffset
1069 // even if aROundToExistingOffset is true for avoiding flickerling IME UI.
1070 // See the last comment in GetTextRect() for the detail.
1071 if (mLastCommitStringTextRectArray.isSome() &&
1072 mLastCommitStringTextRectArray->IsOverlappingWith(aOffset, aLength)) {
1073 aUnionTextRect =
1074 mLastCommitStringTextRectArray->GetUnionRectAsFarAsPossible(
1075 aOffset, aLength, aRoundToExistingOffset);
1076 } else {
1077 aUnionTextRect.SetEmpty();
1080 if (mTextRectArray.isSome() &&
1081 ((aRoundToExistingOffset && mTextRectArray->HasRects()) ||
1082 mTextRectArray->IsOverlappingWith(aOffset, aLength))) {
1083 aUnionTextRect =
1084 aUnionTextRect.Union(mTextRectArray->GetUnionRectAsFarAsPossible(
1085 aOffset, aLength, aRoundToExistingOffset));
1088 if (!aOffset) {
1089 aUnionTextRect = aUnionTextRect.Union(mFirstCharRect);
1091 if (mSelection.isSome() && mSelection->mHasRange) {
1092 if (aOffset <= mSelection->mAnchor &&
1093 mSelection->mAnchor < endOffset.value()) {
1094 aUnionTextRect =
1095 aUnionTextRect.Union(mSelection->mAnchorCharRects[eNextCharRect]);
1097 if (mSelection->mAnchor && aOffset <= mSelection->mAnchor - 1 &&
1098 mSelection->mAnchor - 1 < endOffset.value()) {
1099 aUnionTextRect =
1100 aUnionTextRect.Union(mSelection->mAnchorCharRects[ePrevCharRect]);
1102 if (aOffset <= mSelection->mFocus &&
1103 mSelection->mFocus < endOffset.value()) {
1104 aUnionTextRect =
1105 aUnionTextRect.Union(mSelection->mFocusCharRects[eNextCharRect]);
1107 if (mSelection->mFocus && aOffset <= mSelection->mFocus - 1 &&
1108 mSelection->mFocus - 1 < endOffset.value()) {
1109 aUnionTextRect =
1110 aUnionTextRect.Union(mSelection->mFocusCharRects[ePrevCharRect]);
1114 return !aUnionTextRect.IsEmpty();
1117 bool ContentCacheInParent::GetCaretRect(uint32_t aOffset,
1118 bool aRoundToExistingOffset,
1119 LayoutDeviceIntRect& aCaretRect) const {
1120 MOZ_LOG(sContentCacheLog, LogLevel::Info,
1121 ("0x%p GetCaretRect(aOffset=%u, aRoundToExistingOffset=%s), "
1122 "mCaret=%s, mTextRectArray=%s, mSelection=%s, mFirstCharRect=%s",
1123 this, aOffset, GetBoolName(aRoundToExistingOffset),
1124 ToString(mCaret).c_str(), ToString(mTextRectArray).c_str(),
1125 ToString(mSelection).c_str(), ToString(mFirstCharRect).c_str()));
1127 if (mCaret.isSome() && mCaret->mOffset == aOffset) {
1128 aCaretRect = mCaret->mRect;
1129 return true;
1132 // Guess caret rect from the text rect if it's stored.
1133 if (!GetTextRect(aOffset, aRoundToExistingOffset, aCaretRect)) {
1134 // There might be previous character rect in the cache. If so, we can
1135 // guess the caret rect with it.
1136 if (!aOffset ||
1137 !GetTextRect(aOffset - 1, aRoundToExistingOffset, aCaretRect)) {
1138 aCaretRect.SetEmpty();
1139 return false;
1142 if (mSelection.isSome() && mSelection->mWritingMode.IsVertical()) {
1143 aCaretRect.MoveToY(aCaretRect.YMost());
1144 } else {
1145 // XXX bidi-unaware.
1146 aCaretRect.MoveToX(aCaretRect.XMost());
1150 // XXX This is not bidi aware because we don't cache each character's
1151 // direction. However, this is usually used by IME, so, assuming the
1152 // character is in LRT context must not cause any problem.
1153 if (mSelection.isSome() && mSelection->mWritingMode.IsVertical()) {
1154 aCaretRect.SetHeight(mCaret.isSome() ? mCaret->mRect.Height() : 1);
1155 } else {
1156 aCaretRect.SetWidth(mCaret.isSome() ? mCaret->mRect.Width() : 1);
1158 return true;
1161 bool ContentCacheInParent::OnCompositionEvent(
1162 const WidgetCompositionEvent& aEvent) {
1163 MOZ_LOG(
1164 sContentCacheLog, LogLevel::Info,
1165 ("0x%p OnCompositionEvent(aEvent={ "
1166 "mMessage=%s, mData=\"%s\", mRanges->Length()=%zu }), "
1167 "mPendingEventsNeedingAck=%u, mWidgetHasComposition=%s, "
1168 "mPendingCompositionCount=%" PRIu8 ", mPendingCommitCount=%" PRIu8 ", "
1169 "mIsChildIgnoringCompositionEvents=%s, mCommitStringByRequest=0x%p",
1170 this, ToChar(aEvent.mMessage),
1171 PrintStringDetail(aEvent.mData,
1172 PrintStringDetail::kMaxLengthForCompositionString)
1173 .get(),
1174 aEvent.mRanges ? aEvent.mRanges->Length() : 0, mPendingEventsNeedingAck,
1175 GetBoolName(mWidgetHasComposition), mPendingCompositionCount,
1176 mPendingCommitCount, GetBoolName(mIsChildIgnoringCompositionEvents),
1177 mCommitStringByRequest));
1179 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1180 mDispatchedEventMessages.AppendElement(aEvent.mMessage);
1181 #endif // #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1183 // We must be able to simulate the selection because
1184 // we might not receive selection updates in time
1185 if (!mWidgetHasComposition) {
1186 if (mCompositionStartInChild.isSome()) {
1187 // If there is pending composition in the remote process, let's use
1188 // its start offset temporarily because this stores a lot of information
1189 // around it and the user must look around there, so, showing some UI
1190 // around it must make sense.
1191 mCompositionStart = mCompositionStartInChild;
1192 } else {
1193 mCompositionStart = Some(mSelection.isSome() && mSelection->mHasRange
1194 ? mSelection->StartOffset()
1195 : 0u);
1197 MOZ_ASSERT(aEvent.mMessage == eCompositionStart);
1198 MOZ_RELEASE_ASSERT(mPendingCompositionCount < UINT8_MAX);
1199 mPendingCompositionCount++;
1202 mWidgetHasComposition = !aEvent.CausesDOMCompositionEndEvent();
1204 if (!mWidgetHasComposition) {
1205 // mCompositionStart will be reset when commit event is completely handled
1206 // in the remote process.
1207 if (mPendingCompositionCount == 1) {
1208 mPendingCommitLength = aEvent.mData.Length();
1210 mPendingCommitCount++;
1211 } else if (aEvent.mMessage != eCompositionStart) {
1212 mCompositionString = aEvent.mData;
1215 // During REQUEST_TO_COMMIT_COMPOSITION or REQUEST_TO_CANCEL_COMPOSITION,
1216 // widget usually sends a eCompositionChange and/or eCompositionCommit event
1217 // to finalize or clear the composition, respectively. In this time,
1218 // we need to intercept all composition events here and pass the commit
1219 // string for returning to the remote process as a result of
1220 // RequestIMEToCommitComposition(). Then, eCommitComposition event will
1221 // be dispatched with the committed string in the remote process internally.
1222 if (mCommitStringByRequest) {
1223 if (aEvent.mMessage == eCompositionCommitAsIs) {
1224 *mCommitStringByRequest = mCompositionString;
1225 } else {
1226 MOZ_ASSERT(aEvent.mMessage == eCompositionChange ||
1227 aEvent.mMessage == eCompositionCommit);
1228 *mCommitStringByRequest = aEvent.mData;
1230 // We need to wait eCompositionCommitRequestHandled from the remote process
1231 // in this case. Therefore, mPendingEventsNeedingAck needs to be
1232 // incremented here. Additionally, we stop sending eCompositionCommit(AsIs)
1233 // event. Therefore, we need to decrement mPendingCommitCount which has
1234 // been incremented above.
1235 if (!mWidgetHasComposition) {
1236 mPendingEventsNeedingAck++;
1237 MOZ_DIAGNOSTIC_ASSERT(mPendingCommitCount);
1238 if (mPendingCommitCount) {
1239 mPendingCommitCount--;
1242 return false;
1245 mPendingEventsNeedingAck++;
1246 return true;
1249 void ContentCacheInParent::OnSelectionEvent(
1250 const WidgetSelectionEvent& aSelectionEvent) {
1251 MOZ_LOG(
1252 sContentCacheLog, LogLevel::Info,
1253 ("0x%p OnSelectionEvent(aEvent={ "
1254 "mMessage=%s, mOffset=%u, mLength=%u, mReversed=%s, "
1255 "mExpandToClusterBoundary=%s, mUseNativeLineBreak=%s }), "
1256 "mPendingEventsNeedingAck=%u, mWidgetHasComposition=%s, "
1257 "mPendingCompositionCount=%" PRIu8 ", mPendingCommitCount=%" PRIu8 ", "
1258 "mIsChildIgnoringCompositionEvents=%s",
1259 this, ToChar(aSelectionEvent.mMessage), aSelectionEvent.mOffset,
1260 aSelectionEvent.mLength, GetBoolName(aSelectionEvent.mReversed),
1261 GetBoolName(aSelectionEvent.mExpandToClusterBoundary),
1262 GetBoolName(aSelectionEvent.mUseNativeLineBreak),
1263 mPendingEventsNeedingAck, GetBoolName(mWidgetHasComposition),
1264 mPendingCompositionCount, mPendingCommitCount,
1265 GetBoolName(mIsChildIgnoringCompositionEvents)));
1267 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1268 mDispatchedEventMessages.AppendElement(aSelectionEvent.mMessage);
1269 #endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
1271 mPendingEventsNeedingAck++;
1274 void ContentCacheInParent::OnEventNeedingAckHandled(nsIWidget* aWidget,
1275 EventMessage aMessage) {
1276 // This is called when the child process receives WidgetCompositionEvent or
1277 // WidgetSelectionEvent.
1279 MOZ_LOG(
1280 sContentCacheLog, LogLevel::Info,
1281 ("0x%p OnEventNeedingAckHandled(aWidget=0x%p, "
1282 "aMessage=%s), mPendingEventsNeedingAck=%u, "
1283 "mWidgetHasComposition=%s, mPendingCompositionCount=%" PRIu8 ", "
1284 "mPendingCommitCount=%" PRIu8 ", mIsChildIgnoringCompositionEvents=%s",
1285 this, aWidget, ToChar(aMessage), mPendingEventsNeedingAck,
1286 GetBoolName(mWidgetHasComposition), mPendingCompositionCount,
1287 mPendingCommitCount, GetBoolName(mIsChildIgnoringCompositionEvents)));
1289 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1290 mReceivedEventMessages.AppendElement(aMessage);
1291 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1293 bool isCommittedInChild =
1294 // Commit requester in the remote process has committed the composition.
1295 aMessage == eCompositionCommitRequestHandled ||
1296 // The commit event has been handled normally in the remote process.
1297 (!mIsChildIgnoringCompositionEvents &&
1298 WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage));
1300 if (isCommittedInChild) {
1301 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1302 if (mPendingCompositionCount == 1) {
1303 RemoveUnnecessaryEventMessageLog();
1305 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1307 if (NS_WARN_IF(!mPendingCompositionCount)) {
1308 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1309 nsPrintfCString info(
1310 "\nThere is no pending composition but received %s "
1311 "message from the remote child\n\n",
1312 ToChar(aMessage));
1313 AppendEventMessageLog(info);
1314 CrashReporter::AppendAppNotesToCrashReport(info);
1315 MOZ_DIAGNOSTIC_ASSERT(
1316 false, "No pending composition but received unexpected commit event");
1317 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1319 // Prevent odd behavior in release channel.
1320 mPendingCompositionCount = 1;
1323 mPendingCompositionCount--;
1325 // Forget composition string only when the latest composition string is
1326 // handled in the remote process because if there is 2 or more pending
1327 // composition, this value shouldn't be referred.
1328 if (!mPendingCompositionCount) {
1329 mCompositionString.Truncate();
1332 // Forget pending commit string length if it's handled in the remote
1333 // process. Note that this doesn't care too old composition's commit
1334 // string because in such case, we cannot return proper information
1335 // to IME synchornously.
1336 mPendingCommitLength = 0;
1339 if (WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage)) {
1340 // After the remote process receives eCompositionCommit(AsIs) event,
1341 // it'll restart to handle composition events.
1342 mIsChildIgnoringCompositionEvents = false;
1344 if (NS_WARN_IF(!mPendingCommitCount)) {
1345 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1346 nsPrintfCString info(
1347 "\nThere is no pending comment events but received "
1348 "%s message from the remote child\n\n",
1349 ToChar(aMessage));
1350 AppendEventMessageLog(info);
1351 CrashReporter::AppendAppNotesToCrashReport(info);
1352 MOZ_DIAGNOSTIC_ASSERT(
1353 false,
1354 "No pending commit events but received unexpected commit event");
1355 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1357 // Prevent odd behavior in release channel.
1358 mPendingCommitCount = 1;
1361 mPendingCommitCount--;
1362 } else if (aMessage == eCompositionCommitRequestHandled &&
1363 mPendingCommitCount) {
1364 // If the remote process commits composition synchronously after
1365 // requesting commit composition and we've already sent commit composition,
1366 // it starts to ignore following composition events until receiving
1367 // eCompositionStart event.
1368 mIsChildIgnoringCompositionEvents = true;
1371 // If neither widget (i.e., IME) nor the remote process has composition,
1372 // now, we can forget composition string informations.
1373 if (!mWidgetHasComposition && !mPendingCompositionCount &&
1374 !mPendingCommitCount) {
1375 mCompositionStart.reset();
1378 if (NS_WARN_IF(!mPendingEventsNeedingAck)) {
1379 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1380 nsPrintfCString info(
1381 "\nThere is no pending events but received %s "
1382 "message from the remote child\n\n",
1383 ToChar(aMessage));
1384 AppendEventMessageLog(info);
1385 CrashReporter::AppendAppNotesToCrashReport(info);
1386 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1387 MOZ_DIAGNOSTIC_ASSERT(
1388 false, "No pending event message but received unexpected event");
1389 mPendingEventsNeedingAck = 1;
1391 if (--mPendingEventsNeedingAck) {
1392 return;
1395 FlushPendingNotifications(aWidget);
1398 bool ContentCacheInParent::RequestIMEToCommitComposition(
1399 nsIWidget* aWidget, bool aCancel, nsAString& aCommittedString) {
1400 MOZ_LOG(
1401 sContentCacheLog, LogLevel::Info,
1402 ("0x%p RequestToCommitComposition(aWidget=%p, "
1403 "aCancel=%s), mPendingCompositionCount=%" PRIu8 ", "
1404 "mPendingCommitCount=%" PRIu8 ", mIsChildIgnoringCompositionEvents=%s, "
1405 "IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)=%s, "
1406 "mWidgetHasComposition=%s, mCommitStringByRequest=%p",
1407 this, aWidget, GetBoolName(aCancel), mPendingCompositionCount,
1408 mPendingCommitCount, GetBoolName(mIsChildIgnoringCompositionEvents),
1409 GetBoolName(
1410 IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)),
1411 GetBoolName(mWidgetHasComposition), mCommitStringByRequest));
1413 MOZ_ASSERT(!mCommitStringByRequest);
1415 // If there are 2 or more pending compositions, we already sent
1416 // eCompositionCommit(AsIs) to the remote process. So, this request is
1417 // too late for IME. The remote process should wait following
1418 // composition events for cleaning up TextComposition and handle the
1419 // request as it's handled asynchronously.
1420 if (mPendingCompositionCount > 1) {
1421 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1422 mRequestIMEToCommitCompositionResults.AppendElement(
1423 RequestIMEToCommitCompositionResult::eToOldCompositionReceived);
1424 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1425 return false;
1428 // If there is no pending composition, we may have already sent
1429 // eCompositionCommit(AsIs) event for the active composition. If so, the
1430 // remote process will receive composition events which causes cleaning up
1431 // TextComposition. So, this shouldn't do nothing and TextComposition
1432 // should handle the request as it's handled asynchronously.
1433 // XXX Perhaps, this is wrong because TextComposition in child process
1434 // may commit the composition with current composition string in the
1435 // remote process. I.e., it may be different from actual commit string
1436 // which user typed. So, perhaps, we should return true and the commit
1437 // string.
1438 if (mPendingCommitCount) {
1439 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1440 mRequestIMEToCommitCompositionResults.AppendElement(
1441 RequestIMEToCommitCompositionResult::eToCommittedCompositionReceived);
1442 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1443 return false;
1446 // If BrowserParent which has IME focus was already changed to different one,
1447 // the request shouldn't be sent to IME because it's too late.
1448 if (!IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)) {
1449 // Use the latest composition string which may not be handled in the
1450 // remote process for avoiding data loss.
1451 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1452 mRequestIMEToCommitCompositionResults.AppendElement(
1453 RequestIMEToCommitCompositionResult::eReceivedAfterBrowserParentBlur);
1454 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1455 aCommittedString = mCompositionString;
1456 // After we return true from here, i.e., without actually requesting IME
1457 // to commit composition, we will receive eCompositionCommitRequestHandled
1458 // pseudo event message from the remote process. So, we need to increment
1459 // mPendingEventsNeedingAck here.
1460 mPendingEventsNeedingAck++;
1461 return true;
1464 RefPtr<TextComposition> composition =
1465 IMEStateManager::GetTextCompositionFor(aWidget);
1466 if (NS_WARN_IF(!composition)) {
1467 MOZ_LOG(sContentCacheLog, LogLevel::Warning,
1468 (" 0x%p RequestToCommitComposition(), "
1469 "does nothing due to no composition",
1470 this));
1471 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1472 mRequestIMEToCommitCompositionResults.AppendElement(
1473 RequestIMEToCommitCompositionResult::eReceivedButNoTextComposition);
1474 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1475 return false;
1478 mCommitStringByRequest = &aCommittedString;
1480 // Request commit or cancel composition with TextComposition because we may
1481 // have already requested to commit or cancel the composition or we may
1482 // have already received eCompositionCommit(AsIs) event. Those status are
1483 // managed by composition. So, if we don't request commit composition,
1484 // we should do nothing with native IME here.
1485 composition->RequestToCommit(aWidget, aCancel);
1487 mCommitStringByRequest = nullptr;
1489 MOZ_LOG(
1490 sContentCacheLog, LogLevel::Info,
1491 (" 0x%p RequestToCommitComposition(), "
1492 "mWidgetHasComposition=%s, the composition %s committed synchronously",
1493 this, GetBoolName(mWidgetHasComposition),
1494 composition->Destroyed() ? "WAS" : "has NOT been"));
1496 if (!composition->Destroyed()) {
1497 // When the composition isn't committed synchronously, the remote process's
1498 // TextComposition instance will synthesize commit events and wait to
1499 // receive delayed composition events. When TextComposition instances both
1500 // in this process and the remote process will be destroyed when delayed
1501 // composition events received. TextComposition instance in the parent
1502 // process will dispatch following composition events and be destroyed
1503 // normally. On the other hand, TextComposition instance in the remote
1504 // process won't dispatch following composition events and will be
1505 // destroyed by IMEStateManager::DispatchCompositionEvent().
1506 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1507 mRequestIMEToCommitCompositionResults.AppendElement(
1508 RequestIMEToCommitCompositionResult::eHandledAsynchronously);
1509 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1510 return false;
1513 // When the composition is committed synchronously, the commit string will be
1514 // returned to the remote process. Then, PuppetWidget will dispatch
1515 // eCompositionCommit event with the returned commit string (i.e., the value
1516 // is aCommittedString of this method) and that causes destroying
1517 // TextComposition instance in the remote process (Note that TextComposition
1518 // instance in this process was already destroyed).
1519 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1520 mRequestIMEToCommitCompositionResults.AppendElement(
1521 RequestIMEToCommitCompositionResult::eHandledSynchronously);
1522 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1523 return true;
1526 void ContentCacheInParent::MaybeNotifyIME(
1527 nsIWidget* aWidget, const IMENotification& aNotification) {
1528 if (!mPendingEventsNeedingAck) {
1529 IMEStateManager::NotifyIME(aNotification, aWidget, &mBrowserParent);
1530 return;
1533 switch (aNotification.mMessage) {
1534 case NOTIFY_IME_OF_SELECTION_CHANGE:
1535 mPendingSelectionChange.MergeWith(aNotification);
1536 break;
1537 case NOTIFY_IME_OF_TEXT_CHANGE:
1538 mPendingTextChange.MergeWith(aNotification);
1539 break;
1540 case NOTIFY_IME_OF_POSITION_CHANGE:
1541 mPendingLayoutChange.MergeWith(aNotification);
1542 break;
1543 case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
1544 mPendingCompositionUpdate.MergeWith(aNotification);
1545 break;
1546 default:
1547 MOZ_CRASH("Unsupported notification");
1548 break;
1552 void ContentCacheInParent::FlushPendingNotifications(nsIWidget* aWidget) {
1553 MOZ_ASSERT(!mPendingEventsNeedingAck);
1555 // If the BrowserParent's widget has already gone, this can do nothing since
1556 // widget is necessary to notify IME of something.
1557 if (!aWidget) {
1558 return;
1561 // New notifications which are notified during flushing pending notifications
1562 // should be merged again.
1563 mPendingEventsNeedingAck++;
1565 nsCOMPtr<nsIWidget> widget = aWidget;
1567 // First, text change notification should be sent because selection change
1568 // notification notifies IME of current selection range in the latest content.
1569 // So, IME may need the latest content before that.
1570 if (mPendingTextChange.HasNotification()) {
1571 IMENotification notification(mPendingTextChange);
1572 if (!widget->Destroyed()) {
1573 mPendingTextChange.Clear();
1574 IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
1578 if (mPendingSelectionChange.HasNotification()) {
1579 IMENotification notification(mPendingSelectionChange);
1580 if (!widget->Destroyed()) {
1581 mPendingSelectionChange.Clear();
1582 IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
1586 // Layout change notification should be notified after selection change
1587 // notification because IME may want to query position of new caret position.
1588 if (mPendingLayoutChange.HasNotification()) {
1589 IMENotification notification(mPendingLayoutChange);
1590 if (!widget->Destroyed()) {
1591 mPendingLayoutChange.Clear();
1592 IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
1596 // Finally, send composition update notification because it notifies IME of
1597 // finishing handling whole sending events.
1598 if (mPendingCompositionUpdate.HasNotification()) {
1599 IMENotification notification(mPendingCompositionUpdate);
1600 if (!widget->Destroyed()) {
1601 mPendingCompositionUpdate.Clear();
1602 IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
1606 if (!--mPendingEventsNeedingAck && !widget->Destroyed() &&
1607 (mPendingTextChange.HasNotification() ||
1608 mPendingSelectionChange.HasNotification() ||
1609 mPendingLayoutChange.HasNotification() ||
1610 mPendingCompositionUpdate.HasNotification())) {
1611 FlushPendingNotifications(widget);
1615 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1617 void ContentCacheInParent::RemoveUnnecessaryEventMessageLog() {
1618 bool foundLastCompositionStart = false;
1619 for (size_t i = mDispatchedEventMessages.Length(); i > 1; i--) {
1620 if (mDispatchedEventMessages[i - 1] != eCompositionStart) {
1621 continue;
1623 if (!foundLastCompositionStart) {
1624 // Find previous eCompositionStart of the latest eCompositionStart.
1625 foundLastCompositionStart = true;
1626 continue;
1628 // Remove the messages before the last 2 sets of composition events.
1629 mDispatchedEventMessages.RemoveElementsAt(0, i - 1);
1630 break;
1632 uint32_t numberOfCompositionCommitRequestHandled = 0;
1633 foundLastCompositionStart = false;
1634 for (size_t i = mReceivedEventMessages.Length(); i > 1; i--) {
1635 if (mReceivedEventMessages[i - 1] == eCompositionCommitRequestHandled) {
1636 numberOfCompositionCommitRequestHandled++;
1638 if (mReceivedEventMessages[i - 1] != eCompositionStart) {
1639 continue;
1641 if (!foundLastCompositionStart) {
1642 // Find previous eCompositionStart of the latest eCompositionStart.
1643 foundLastCompositionStart = true;
1644 continue;
1646 // Remove the messages before the last 2 sets of composition events.
1647 mReceivedEventMessages.RemoveElementsAt(0, i - 1);
1648 break;
1651 if (!numberOfCompositionCommitRequestHandled) {
1652 // If there is no eCompositionCommitRequestHandled in
1653 // mReceivedEventMessages, we don't need to store log of
1654 // RequestIMEToCommmitComposition().
1655 mRequestIMEToCommitCompositionResults.Clear();
1656 } else {
1657 // We need to keep all reason of eCompositionCommitRequestHandled, which
1658 // is sent when mRequestIMEToCommitComposition() returns true.
1659 // So, we can discard older log than the first
1660 // eCompositionCommitRequestHandled in mReceivedEventMessages.
1661 for (size_t i = mRequestIMEToCommitCompositionResults.Length(); i > 1;
1662 i--) {
1663 if (mRequestIMEToCommitCompositionResults[i - 1] ==
1664 RequestIMEToCommitCompositionResult::
1665 eReceivedAfterBrowserParentBlur ||
1666 mRequestIMEToCommitCompositionResults[i - 1] ==
1667 RequestIMEToCommitCompositionResult::eHandledSynchronously) {
1668 --numberOfCompositionCommitRequestHandled;
1669 if (!numberOfCompositionCommitRequestHandled) {
1670 mRequestIMEToCommitCompositionResults.RemoveElementsAt(0, i - 1);
1671 break;
1678 void ContentCacheInParent::AppendEventMessageLog(nsACString& aLog) const {
1679 aLog.AppendLiteral("Dispatched Event Message Log:\n");
1680 for (EventMessage message : mDispatchedEventMessages) {
1681 aLog.AppendLiteral(" ");
1682 aLog.Append(ToChar(message));
1683 aLog.AppendLiteral("\n");
1685 aLog.AppendLiteral("\nReceived Event Message Log:\n");
1686 for (EventMessage message : mReceivedEventMessages) {
1687 aLog.AppendLiteral(" ");
1688 aLog.Append(ToChar(message));
1689 aLog.AppendLiteral("\n");
1691 aLog.AppendLiteral("\nResult of RequestIMEToCommitComposition():\n");
1692 for (RequestIMEToCommitCompositionResult result :
1693 mRequestIMEToCommitCompositionResults) {
1694 aLog.AppendLiteral(" ");
1695 aLog.Append(ToReadableText(result));
1696 aLog.AppendLiteral("\n");
1698 aLog.AppendLiteral("\n");
1701 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1703 /*****************************************************************************
1704 * mozilla::ContentCache::Selection
1705 *****************************************************************************/
1707 ContentCache::Selection::Selection(
1708 const WidgetQueryContentEvent& aQuerySelectedTextEvent)
1709 : mAnchor(UINT32_MAX),
1710 mFocus(UINT32_MAX),
1711 mWritingMode(aQuerySelectedTextEvent.mReply->WritingModeRef()),
1712 mHasRange(aQuerySelectedTextEvent.mReply->mOffsetAndData.isSome()) {
1713 MOZ_ASSERT(aQuerySelectedTextEvent.mMessage == eQuerySelectedText);
1714 MOZ_ASSERT(aQuerySelectedTextEvent.Succeeded());
1715 if (mHasRange) {
1716 mAnchor = aQuerySelectedTextEvent.mReply->AnchorOffset();
1717 mFocus = aQuerySelectedTextEvent.mReply->FocusOffset();
1721 /*****************************************************************************
1722 * mozilla::ContentCache::TextRectArray
1723 *****************************************************************************/
1725 LayoutDeviceIntRect ContentCache::TextRectArray::GetRect(
1726 uint32_t aOffset) const {
1727 LayoutDeviceIntRect rect;
1728 if (IsOffsetInRange(aOffset)) {
1729 rect = mRects[aOffset - mStart];
1731 return rect;
1734 LayoutDeviceIntRect ContentCache::TextRectArray::GetUnionRect(
1735 uint32_t aOffset, uint32_t aLength) const {
1736 LayoutDeviceIntRect rect;
1737 if (!IsRangeCompletelyInRange(aOffset, aLength)) {
1738 return rect;
1740 for (uint32_t i = 0; i < aLength; i++) {
1741 rect = rect.Union(mRects[aOffset - mStart + i]);
1743 return rect;
1746 LayoutDeviceIntRect ContentCache::TextRectArray::GetUnionRectAsFarAsPossible(
1747 uint32_t aOffset, uint32_t aLength, bool aRoundToExistingOffset) const {
1748 LayoutDeviceIntRect rect;
1749 if (!HasRects() ||
1750 (!aRoundToExistingOffset && !IsOverlappingWith(aOffset, aLength))) {
1751 return rect;
1753 uint32_t startOffset = std::max(aOffset, mStart);
1754 if (aRoundToExistingOffset && startOffset >= EndOffset()) {
1755 startOffset = EndOffset() - 1;
1757 uint32_t endOffset = std::min(aOffset + aLength, EndOffset());
1758 if (aRoundToExistingOffset && endOffset < mStart + 1) {
1759 endOffset = mStart + 1;
1761 if (NS_WARN_IF(endOffset < startOffset)) {
1762 return rect;
1764 for (uint32_t i = 0; i < endOffset - startOffset; i++) {
1765 rect = rect.Union(mRects[startOffset - mStart + i]);
1767 return rect;
1770 } // namespace mozilla