Bug 1472338: part 1) Add Chrome tests for the async Clipboard API. r=NeilDeakin
[gecko.git] / widget / ContentCache.cpp
blobdc4bc7e480f06cd45b38cd50e32455b8a2a36b11
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 // XXX This query should fail if no editable elmenet has focus. Or,
884 // perhaps, should return rect of the window instead.
885 aEvent.EmplaceReply();
886 aEvent.mReply->mFocusedWidget = aWidget;
887 aEvent.mReply->mRect = mEditorRect;
888 MOZ_LOG(sContentCacheLog, LogLevel::Info,
889 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
890 "mMessage=eQueryEditorRect, mReply=%s }",
891 this, ToString(aEvent.mReply).c_str()));
892 return true;
893 default:
894 aEvent.EmplaceReply();
895 aEvent.mReply->mFocusedWidget = aWidget;
896 if (NS_WARN_IF(aEvent.Failed())) {
897 MOZ_LOG(
898 sContentCacheLog, LogLevel::Error,
899 ("0x%p HandleQueryContentEvent(), FAILED due to not set enough "
900 "data, aEvent={ mMessage=%s, mReply=%s }",
901 this, ToChar(aEvent.mMessage), ToString(aEvent.mReply).c_str()));
902 return false;
904 MOZ_LOG(sContentCacheLog, LogLevel::Info,
905 ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
906 "mMessage=%s, mReply=%s }",
907 this, ToChar(aEvent.mMessage), ToString(aEvent.mReply).c_str()));
908 return true;
912 bool ContentCacheInParent::GetTextRect(uint32_t aOffset,
913 bool aRoundToExistingOffset,
914 LayoutDeviceIntRect& aTextRect) const {
915 MOZ_LOG(
916 sContentCacheLog, LogLevel::Info,
917 ("0x%p GetTextRect(aOffset=%u, aRoundToExistingOffset=%s), "
918 "mTextRectArray=%s, mSelection=%s, mLastCommitStringTextRectArray=%s",
919 this, aOffset, GetBoolName(aRoundToExistingOffset),
920 ToString(mTextRectArray).c_str(), ToString(mSelection).c_str(),
921 ToString(mLastCommitStringTextRectArray).c_str()));
923 if (!aOffset) {
924 NS_WARNING_ASSERTION(!mFirstCharRect.IsEmpty(), "empty rect");
925 aTextRect = mFirstCharRect;
926 return !aTextRect.IsEmpty();
928 if (mSelection.isSome() && mSelection->mHasRange) {
929 if (aOffset == mSelection->mAnchor) {
930 NS_WARNING_ASSERTION(
931 !mSelection->mAnchorCharRects[eNextCharRect].IsEmpty(), "empty rect");
932 aTextRect = mSelection->mAnchorCharRects[eNextCharRect];
933 return !aTextRect.IsEmpty();
935 if (mSelection->mAnchor && aOffset == mSelection->mAnchor - 1) {
936 NS_WARNING_ASSERTION(
937 !mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty(), "empty rect");
938 aTextRect = mSelection->mAnchorCharRects[ePrevCharRect];
939 return !aTextRect.IsEmpty();
941 if (aOffset == mSelection->mFocus) {
942 NS_WARNING_ASSERTION(
943 !mSelection->mFocusCharRects[eNextCharRect].IsEmpty(), "empty rect");
944 aTextRect = mSelection->mFocusCharRects[eNextCharRect];
945 return !aTextRect.IsEmpty();
947 if (mSelection->mFocus && aOffset == mSelection->mFocus - 1) {
948 NS_WARNING_ASSERTION(
949 !mSelection->mFocusCharRects[ePrevCharRect].IsEmpty(), "empty rect");
950 aTextRect = mSelection->mFocusCharRects[ePrevCharRect];
951 return !aTextRect.IsEmpty();
955 if (mTextRectArray.isSome() && mTextRectArray->IsOffsetInRange(aOffset)) {
956 aTextRect = mTextRectArray->GetRect(aOffset);
957 return !aTextRect.IsEmpty();
960 if (mLastCommitStringTextRectArray.isSome() &&
961 mLastCommitStringTextRectArray->IsOffsetInRange(aOffset)) {
962 aTextRect = mLastCommitStringTextRectArray->GetRect(aOffset);
963 return !aTextRect.IsEmpty();
966 if (!aRoundToExistingOffset) {
967 aTextRect.SetEmpty();
968 return false;
971 if (mTextRectArray.isNothing() || !mTextRectArray->HasRects()) {
972 // If there are no rects in mTextRectArray, we should refer the start of
973 // the selection if there is because IME must query a char rect around it if
974 // there is no composition.
975 if (mSelection.isNothing()) {
976 // Unfortunately, there is no data about text rect...
977 aTextRect.SetEmpty();
978 return false;
980 aTextRect = mSelection->StartCharRect();
981 return !aTextRect.IsEmpty();
984 // Although we may have mLastCommitStringTextRectArray here and it must have
985 // previous character rects at selection. However, we should stop using it
986 // because it's stored really short time after commiting a composition.
987 // So, multiple query may return different rect and it may cause flickerling
988 // the IME UI.
989 uint32_t offset = aOffset;
990 if (offset < mTextRectArray->StartOffset()) {
991 offset = mTextRectArray->StartOffset();
992 } else {
993 offset = mTextRectArray->EndOffset() - 1;
995 aTextRect = mTextRectArray->GetRect(offset);
996 return !aTextRect.IsEmpty();
999 bool ContentCacheInParent::GetUnionTextRects(
1000 uint32_t aOffset, uint32_t aLength, bool aRoundToExistingOffset,
1001 LayoutDeviceIntRect& aUnionTextRect) const {
1002 MOZ_LOG(sContentCacheLog, LogLevel::Info,
1003 ("0x%p GetUnionTextRects(aOffset=%u, "
1004 "aLength=%u, aRoundToExistingOffset=%s), mTextRectArray=%s, "
1005 "mSelection=%s, mLastCommitStringTextRectArray=%s",
1006 this, aOffset, aLength, GetBoolName(aRoundToExistingOffset),
1007 ToString(mTextRectArray).c_str(), ToString(mSelection).c_str(),
1008 ToString(mLastCommitStringTextRectArray).c_str()));
1010 CheckedInt<uint32_t> endOffset = CheckedInt<uint32_t>(aOffset) + aLength;
1011 if (!endOffset.isValid()) {
1012 return false;
1015 if (mSelection.isSome() && !mSelection->IsCollapsed() &&
1016 aOffset == mSelection->StartOffset() && aLength == mSelection->Length()) {
1017 NS_WARNING_ASSERTION(!mSelection->mRect.IsEmpty(), "empty rect");
1018 aUnionTextRect = mSelection->mRect;
1019 return !aUnionTextRect.IsEmpty();
1022 if (aLength == 1) {
1023 if (!aOffset) {
1024 NS_WARNING_ASSERTION(!mFirstCharRect.IsEmpty(), "empty rect");
1025 aUnionTextRect = mFirstCharRect;
1026 return !aUnionTextRect.IsEmpty();
1028 if (mSelection.isSome() && mSelection->mHasRange) {
1029 if (aOffset == mSelection->mAnchor) {
1030 NS_WARNING_ASSERTION(
1031 !mSelection->mAnchorCharRects[eNextCharRect].IsEmpty(),
1032 "empty rect");
1033 aUnionTextRect = mSelection->mAnchorCharRects[eNextCharRect];
1034 return !aUnionTextRect.IsEmpty();
1036 if (mSelection->mAnchor && aOffset == mSelection->mAnchor - 1) {
1037 NS_WARNING_ASSERTION(
1038 !mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty(),
1039 "empty rect");
1040 aUnionTextRect = mSelection->mAnchorCharRects[ePrevCharRect];
1041 return !aUnionTextRect.IsEmpty();
1043 if (aOffset == mSelection->mFocus) {
1044 NS_WARNING_ASSERTION(
1045 !mSelection->mFocusCharRects[eNextCharRect].IsEmpty(),
1046 "empty rect");
1047 aUnionTextRect = mSelection->mFocusCharRects[eNextCharRect];
1048 return !aUnionTextRect.IsEmpty();
1050 if (mSelection->mFocus && aOffset == mSelection->mFocus - 1) {
1051 NS_WARNING_ASSERTION(
1052 !mSelection->mFocusCharRects[ePrevCharRect].IsEmpty(),
1053 "empty rect");
1054 aUnionTextRect = mSelection->mFocusCharRects[ePrevCharRect];
1055 return !aUnionTextRect.IsEmpty();
1060 // Even if some text rects are not cached of the queried range,
1061 // we should return union rect when the first character's rect is cached
1062 // since the first character rect is important and the others are not so
1063 // in most cases.
1065 if (!aOffset && mSelection.isSome() && mSelection->mHasRange &&
1066 aOffset != mSelection->mAnchor && aOffset != mSelection->mFocus &&
1067 (mTextRectArray.isNothing() ||
1068 !mTextRectArray->IsOffsetInRange(aOffset)) &&
1069 (mLastCommitStringTextRectArray.isNothing() ||
1070 !mLastCommitStringTextRectArray->IsOffsetInRange(aOffset))) {
1071 // The first character rect isn't cached.
1072 return false;
1075 // Use mLastCommitStringTextRectArray only when it overlaps with aOffset
1076 // even if aROundToExistingOffset is true for avoiding flickerling IME UI.
1077 // See the last comment in GetTextRect() for the detail.
1078 if (mLastCommitStringTextRectArray.isSome() &&
1079 mLastCommitStringTextRectArray->IsOverlappingWith(aOffset, aLength)) {
1080 aUnionTextRect =
1081 mLastCommitStringTextRectArray->GetUnionRectAsFarAsPossible(
1082 aOffset, aLength, aRoundToExistingOffset);
1083 } else {
1084 aUnionTextRect.SetEmpty();
1087 if (mTextRectArray.isSome() &&
1088 ((aRoundToExistingOffset && mTextRectArray->HasRects()) ||
1089 mTextRectArray->IsOverlappingWith(aOffset, aLength))) {
1090 aUnionTextRect =
1091 aUnionTextRect.Union(mTextRectArray->GetUnionRectAsFarAsPossible(
1092 aOffset, aLength, aRoundToExistingOffset));
1095 if (!aOffset) {
1096 aUnionTextRect = aUnionTextRect.Union(mFirstCharRect);
1098 if (mSelection.isSome() && mSelection->mHasRange) {
1099 if (aOffset <= mSelection->mAnchor &&
1100 mSelection->mAnchor < endOffset.value()) {
1101 aUnionTextRect =
1102 aUnionTextRect.Union(mSelection->mAnchorCharRects[eNextCharRect]);
1104 if (mSelection->mAnchor && aOffset <= mSelection->mAnchor - 1 &&
1105 mSelection->mAnchor - 1 < endOffset.value()) {
1106 aUnionTextRect =
1107 aUnionTextRect.Union(mSelection->mAnchorCharRects[ePrevCharRect]);
1109 if (aOffset <= mSelection->mFocus &&
1110 mSelection->mFocus < endOffset.value()) {
1111 aUnionTextRect =
1112 aUnionTextRect.Union(mSelection->mFocusCharRects[eNextCharRect]);
1114 if (mSelection->mFocus && aOffset <= mSelection->mFocus - 1 &&
1115 mSelection->mFocus - 1 < endOffset.value()) {
1116 aUnionTextRect =
1117 aUnionTextRect.Union(mSelection->mFocusCharRects[ePrevCharRect]);
1121 return !aUnionTextRect.IsEmpty();
1124 bool ContentCacheInParent::GetCaretRect(uint32_t aOffset,
1125 bool aRoundToExistingOffset,
1126 LayoutDeviceIntRect& aCaretRect) const {
1127 MOZ_LOG(sContentCacheLog, LogLevel::Info,
1128 ("0x%p GetCaretRect(aOffset=%u, aRoundToExistingOffset=%s), "
1129 "mCaret=%s, mTextRectArray=%s, mSelection=%s, mFirstCharRect=%s",
1130 this, aOffset, GetBoolName(aRoundToExistingOffset),
1131 ToString(mCaret).c_str(), ToString(mTextRectArray).c_str(),
1132 ToString(mSelection).c_str(), ToString(mFirstCharRect).c_str()));
1134 if (mCaret.isSome() && mCaret->mOffset == aOffset) {
1135 aCaretRect = mCaret->mRect;
1136 return true;
1139 // Guess caret rect from the text rect if it's stored.
1140 if (!GetTextRect(aOffset, aRoundToExistingOffset, aCaretRect)) {
1141 // There might be previous character rect in the cache. If so, we can
1142 // guess the caret rect with it.
1143 if (!aOffset ||
1144 !GetTextRect(aOffset - 1, aRoundToExistingOffset, aCaretRect)) {
1145 aCaretRect.SetEmpty();
1146 return false;
1149 if (mSelection.isSome() && mSelection->mWritingMode.IsVertical()) {
1150 aCaretRect.MoveToY(aCaretRect.YMost());
1151 } else {
1152 // XXX bidi-unaware.
1153 aCaretRect.MoveToX(aCaretRect.XMost());
1157 // XXX This is not bidi aware because we don't cache each character's
1158 // direction. However, this is usually used by IME, so, assuming the
1159 // character is in LRT context must not cause any problem.
1160 if (mSelection.isSome() && mSelection->mWritingMode.IsVertical()) {
1161 aCaretRect.SetHeight(mCaret.isSome() ? mCaret->mRect.Height() : 1);
1162 } else {
1163 aCaretRect.SetWidth(mCaret.isSome() ? mCaret->mRect.Width() : 1);
1165 return true;
1168 bool ContentCacheInParent::OnCompositionEvent(
1169 const WidgetCompositionEvent& aEvent) {
1170 MOZ_LOG(
1171 sContentCacheLog, LogLevel::Info,
1172 ("0x%p OnCompositionEvent(aEvent={ "
1173 "mMessage=%s, mData=\"%s\", mRanges->Length()=%zu }), "
1174 "mPendingEventsNeedingAck=%u, mWidgetHasComposition=%s, "
1175 "mPendingCompositionCount=%" PRIu8 ", mPendingCommitCount=%" PRIu8 ", "
1176 "mIsChildIgnoringCompositionEvents=%s, mCommitStringByRequest=0x%p",
1177 this, ToChar(aEvent.mMessage),
1178 PrintStringDetail(aEvent.mData,
1179 PrintStringDetail::kMaxLengthForCompositionString)
1180 .get(),
1181 aEvent.mRanges ? aEvent.mRanges->Length() : 0, mPendingEventsNeedingAck,
1182 GetBoolName(mWidgetHasComposition), mPendingCompositionCount,
1183 mPendingCommitCount, GetBoolName(mIsChildIgnoringCompositionEvents),
1184 mCommitStringByRequest));
1186 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1187 mDispatchedEventMessages.AppendElement(aEvent.mMessage);
1188 #endif // #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1190 // We must be able to simulate the selection because
1191 // we might not receive selection updates in time
1192 if (!mWidgetHasComposition) {
1193 if (mCompositionStartInChild.isSome()) {
1194 // If there is pending composition in the remote process, let's use
1195 // its start offset temporarily because this stores a lot of information
1196 // around it and the user must look around there, so, showing some UI
1197 // around it must make sense.
1198 mCompositionStart = mCompositionStartInChild;
1199 } else {
1200 mCompositionStart = Some(mSelection.isSome() && mSelection->mHasRange
1201 ? mSelection->StartOffset()
1202 : 0u);
1204 MOZ_ASSERT(aEvent.mMessage == eCompositionStart);
1205 MOZ_RELEASE_ASSERT(mPendingCompositionCount < UINT8_MAX);
1206 mPendingCompositionCount++;
1209 mWidgetHasComposition = !aEvent.CausesDOMCompositionEndEvent();
1211 if (!mWidgetHasComposition) {
1212 // mCompositionStart will be reset when commit event is completely handled
1213 // in the remote process.
1214 if (mPendingCompositionCount == 1) {
1215 mPendingCommitLength = aEvent.mData.Length();
1217 mPendingCommitCount++;
1218 } else if (aEvent.mMessage != eCompositionStart) {
1219 mCompositionString = aEvent.mData;
1222 // During REQUEST_TO_COMMIT_COMPOSITION or REQUEST_TO_CANCEL_COMPOSITION,
1223 // widget usually sends a eCompositionChange and/or eCompositionCommit event
1224 // to finalize or clear the composition, respectively. In this time,
1225 // we need to intercept all composition events here and pass the commit
1226 // string for returning to the remote process as a result of
1227 // RequestIMEToCommitComposition(). Then, eCommitComposition event will
1228 // be dispatched with the committed string in the remote process internally.
1229 if (mCommitStringByRequest) {
1230 if (aEvent.mMessage == eCompositionCommitAsIs) {
1231 *mCommitStringByRequest = mCompositionString;
1232 } else {
1233 MOZ_ASSERT(aEvent.mMessage == eCompositionChange ||
1234 aEvent.mMessage == eCompositionCommit);
1235 *mCommitStringByRequest = aEvent.mData;
1237 // We need to wait eCompositionCommitRequestHandled from the remote process
1238 // in this case. Therefore, mPendingEventsNeedingAck needs to be
1239 // incremented here. Additionally, we stop sending eCompositionCommit(AsIs)
1240 // event. Therefore, we need to decrement mPendingCommitCount which has
1241 // been incremented above.
1242 if (!mWidgetHasComposition) {
1243 mPendingEventsNeedingAck++;
1244 MOZ_DIAGNOSTIC_ASSERT(mPendingCommitCount);
1245 if (mPendingCommitCount) {
1246 mPendingCommitCount--;
1249 return false;
1252 mPendingEventsNeedingAck++;
1253 return true;
1256 void ContentCacheInParent::OnSelectionEvent(
1257 const WidgetSelectionEvent& aSelectionEvent) {
1258 MOZ_LOG(
1259 sContentCacheLog, LogLevel::Info,
1260 ("0x%p OnSelectionEvent(aEvent={ "
1261 "mMessage=%s, mOffset=%u, mLength=%u, mReversed=%s, "
1262 "mExpandToClusterBoundary=%s, mUseNativeLineBreak=%s }), "
1263 "mPendingEventsNeedingAck=%u, mWidgetHasComposition=%s, "
1264 "mPendingCompositionCount=%" PRIu8 ", mPendingCommitCount=%" PRIu8 ", "
1265 "mIsChildIgnoringCompositionEvents=%s",
1266 this, ToChar(aSelectionEvent.mMessage), aSelectionEvent.mOffset,
1267 aSelectionEvent.mLength, GetBoolName(aSelectionEvent.mReversed),
1268 GetBoolName(aSelectionEvent.mExpandToClusterBoundary),
1269 GetBoolName(aSelectionEvent.mUseNativeLineBreak),
1270 mPendingEventsNeedingAck, GetBoolName(mWidgetHasComposition),
1271 mPendingCompositionCount, mPendingCommitCount,
1272 GetBoolName(mIsChildIgnoringCompositionEvents)));
1274 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1275 mDispatchedEventMessages.AppendElement(aSelectionEvent.mMessage);
1276 #endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
1278 mPendingEventsNeedingAck++;
1281 void ContentCacheInParent::OnEventNeedingAckHandled(nsIWidget* aWidget,
1282 EventMessage aMessage) {
1283 // This is called when the child process receives WidgetCompositionEvent or
1284 // WidgetSelectionEvent.
1286 MOZ_LOG(
1287 sContentCacheLog, LogLevel::Info,
1288 ("0x%p OnEventNeedingAckHandled(aWidget=0x%p, "
1289 "aMessage=%s), mPendingEventsNeedingAck=%u, "
1290 "mWidgetHasComposition=%s, mPendingCompositionCount=%" PRIu8 ", "
1291 "mPendingCommitCount=%" PRIu8 ", mIsChildIgnoringCompositionEvents=%s",
1292 this, aWidget, ToChar(aMessage), mPendingEventsNeedingAck,
1293 GetBoolName(mWidgetHasComposition), mPendingCompositionCount,
1294 mPendingCommitCount, GetBoolName(mIsChildIgnoringCompositionEvents)));
1296 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1297 mReceivedEventMessages.AppendElement(aMessage);
1298 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1300 bool isCommittedInChild =
1301 // Commit requester in the remote process has committed the composition.
1302 aMessage == eCompositionCommitRequestHandled ||
1303 // The commit event has been handled normally in the remote process.
1304 (!mIsChildIgnoringCompositionEvents &&
1305 WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage));
1307 if (isCommittedInChild) {
1308 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1309 if (mPendingCompositionCount == 1) {
1310 RemoveUnnecessaryEventMessageLog();
1312 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1314 if (NS_WARN_IF(!mPendingCompositionCount)) {
1315 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1316 nsPrintfCString info(
1317 "\nThere is no pending composition but received %s "
1318 "message from the remote child\n\n",
1319 ToChar(aMessage));
1320 AppendEventMessageLog(info);
1321 CrashReporter::AppendAppNotesToCrashReport(info);
1322 MOZ_DIAGNOSTIC_ASSERT(
1323 false, "No pending composition but received unexpected commit event");
1324 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1326 // Prevent odd behavior in release channel.
1327 mPendingCompositionCount = 1;
1330 mPendingCompositionCount--;
1332 // Forget composition string only when the latest composition string is
1333 // handled in the remote process because if there is 2 or more pending
1334 // composition, this value shouldn't be referred.
1335 if (!mPendingCompositionCount) {
1336 mCompositionString.Truncate();
1339 // Forget pending commit string length if it's handled in the remote
1340 // process. Note that this doesn't care too old composition's commit
1341 // string because in such case, we cannot return proper information
1342 // to IME synchornously.
1343 mPendingCommitLength = 0;
1346 if (WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage)) {
1347 // After the remote process receives eCompositionCommit(AsIs) event,
1348 // it'll restart to handle composition events.
1349 mIsChildIgnoringCompositionEvents = false;
1351 if (NS_WARN_IF(!mPendingCommitCount)) {
1352 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1353 nsPrintfCString info(
1354 "\nThere is no pending comment events but received "
1355 "%s message from the remote child\n\n",
1356 ToChar(aMessage));
1357 AppendEventMessageLog(info);
1358 CrashReporter::AppendAppNotesToCrashReport(info);
1359 MOZ_DIAGNOSTIC_ASSERT(
1360 false,
1361 "No pending commit events but received unexpected commit event");
1362 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1364 // Prevent odd behavior in release channel.
1365 mPendingCommitCount = 1;
1368 mPendingCommitCount--;
1369 } else if (aMessage == eCompositionCommitRequestHandled &&
1370 mPendingCommitCount) {
1371 // If the remote process commits composition synchronously after
1372 // requesting commit composition and we've already sent commit composition,
1373 // it starts to ignore following composition events until receiving
1374 // eCompositionStart event.
1375 mIsChildIgnoringCompositionEvents = true;
1378 // If neither widget (i.e., IME) nor the remote process has composition,
1379 // now, we can forget composition string informations.
1380 if (!mWidgetHasComposition && !mPendingCompositionCount &&
1381 !mPendingCommitCount) {
1382 mCompositionStart.reset();
1385 if (NS_WARN_IF(!mPendingEventsNeedingAck)) {
1386 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1387 nsPrintfCString info(
1388 "\nThere is no pending events but received %s "
1389 "message from the remote child\n\n",
1390 ToChar(aMessage));
1391 AppendEventMessageLog(info);
1392 CrashReporter::AppendAppNotesToCrashReport(info);
1393 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1394 MOZ_DIAGNOSTIC_ASSERT(
1395 false, "No pending event message but received unexpected event");
1396 mPendingEventsNeedingAck = 1;
1398 if (--mPendingEventsNeedingAck) {
1399 return;
1402 FlushPendingNotifications(aWidget);
1405 bool ContentCacheInParent::RequestIMEToCommitComposition(
1406 nsIWidget* aWidget, bool aCancel, nsAString& aCommittedString) {
1407 MOZ_LOG(
1408 sContentCacheLog, LogLevel::Info,
1409 ("0x%p RequestToCommitComposition(aWidget=%p, "
1410 "aCancel=%s), mPendingCompositionCount=%" PRIu8 ", "
1411 "mPendingCommitCount=%" PRIu8 ", mIsChildIgnoringCompositionEvents=%s, "
1412 "IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)=%s, "
1413 "mWidgetHasComposition=%s, mCommitStringByRequest=%p",
1414 this, aWidget, GetBoolName(aCancel), mPendingCompositionCount,
1415 mPendingCommitCount, GetBoolName(mIsChildIgnoringCompositionEvents),
1416 GetBoolName(
1417 IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)),
1418 GetBoolName(mWidgetHasComposition), mCommitStringByRequest));
1420 MOZ_ASSERT(!mCommitStringByRequest);
1422 // If there are 2 or more pending compositions, we already sent
1423 // eCompositionCommit(AsIs) to the remote process. So, this request is
1424 // too late for IME. The remote process should wait following
1425 // composition events for cleaning up TextComposition and handle the
1426 // request as it's handled asynchronously.
1427 if (mPendingCompositionCount > 1) {
1428 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1429 mRequestIMEToCommitCompositionResults.AppendElement(
1430 RequestIMEToCommitCompositionResult::eToOldCompositionReceived);
1431 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1432 return false;
1435 // If there is no pending composition, we may have already sent
1436 // eCompositionCommit(AsIs) event for the active composition. If so, the
1437 // remote process will receive composition events which causes cleaning up
1438 // TextComposition. So, this shouldn't do nothing and TextComposition
1439 // should handle the request as it's handled asynchronously.
1440 // XXX Perhaps, this is wrong because TextComposition in child process
1441 // may commit the composition with current composition string in the
1442 // remote process. I.e., it may be different from actual commit string
1443 // which user typed. So, perhaps, we should return true and the commit
1444 // string.
1445 if (mPendingCommitCount) {
1446 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1447 mRequestIMEToCommitCompositionResults.AppendElement(
1448 RequestIMEToCommitCompositionResult::eToCommittedCompositionReceived);
1449 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1450 return false;
1453 // If BrowserParent which has IME focus was already changed to different one,
1454 // the request shouldn't be sent to IME because it's too late.
1455 if (!IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)) {
1456 // Use the latest composition string which may not be handled in the
1457 // remote process for avoiding data loss.
1458 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1459 mRequestIMEToCommitCompositionResults.AppendElement(
1460 RequestIMEToCommitCompositionResult::eReceivedAfterBrowserParentBlur);
1461 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1462 aCommittedString = mCompositionString;
1463 // After we return true from here, i.e., without actually requesting IME
1464 // to commit composition, we will receive eCompositionCommitRequestHandled
1465 // pseudo event message from the remote process. So, we need to increment
1466 // mPendingEventsNeedingAck here.
1467 mPendingEventsNeedingAck++;
1468 return true;
1471 RefPtr<TextComposition> composition =
1472 IMEStateManager::GetTextCompositionFor(aWidget);
1473 if (NS_WARN_IF(!composition)) {
1474 MOZ_LOG(sContentCacheLog, LogLevel::Warning,
1475 (" 0x%p RequestToCommitComposition(), "
1476 "does nothing due to no composition",
1477 this));
1478 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1479 mRequestIMEToCommitCompositionResults.AppendElement(
1480 RequestIMEToCommitCompositionResult::eReceivedButNoTextComposition);
1481 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1482 return false;
1485 mCommitStringByRequest = &aCommittedString;
1487 // Request commit or cancel composition with TextComposition because we may
1488 // have already requested to commit or cancel the composition or we may
1489 // have already received eCompositionCommit(AsIs) event. Those status are
1490 // managed by composition. So, if we don't request commit composition,
1491 // we should do nothing with native IME here.
1492 composition->RequestToCommit(aWidget, aCancel);
1494 mCommitStringByRequest = nullptr;
1496 MOZ_LOG(
1497 sContentCacheLog, LogLevel::Info,
1498 (" 0x%p RequestToCommitComposition(), "
1499 "mWidgetHasComposition=%s, the composition %s committed synchronously",
1500 this, GetBoolName(mWidgetHasComposition),
1501 composition->Destroyed() ? "WAS" : "has NOT been"));
1503 if (!composition->Destroyed()) {
1504 // When the composition isn't committed synchronously, the remote process's
1505 // TextComposition instance will synthesize commit events and wait to
1506 // receive delayed composition events. When TextComposition instances both
1507 // in this process and the remote process will be destroyed when delayed
1508 // composition events received. TextComposition instance in the parent
1509 // process will dispatch following composition events and be destroyed
1510 // normally. On the other hand, TextComposition instance in the remote
1511 // process won't dispatch following composition events and will be
1512 // destroyed by IMEStateManager::DispatchCompositionEvent().
1513 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1514 mRequestIMEToCommitCompositionResults.AppendElement(
1515 RequestIMEToCommitCompositionResult::eHandledAsynchronously);
1516 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1517 return false;
1520 // When the composition is committed synchronously, the commit string will be
1521 // returned to the remote process. Then, PuppetWidget will dispatch
1522 // eCompositionCommit event with the returned commit string (i.e., the value
1523 // is aCommittedString of this method) and that causes destroying
1524 // TextComposition instance in the remote process (Note that TextComposition
1525 // instance in this process was already destroyed).
1526 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1527 mRequestIMEToCommitCompositionResults.AppendElement(
1528 RequestIMEToCommitCompositionResult::eHandledSynchronously);
1529 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1530 return true;
1533 void ContentCacheInParent::MaybeNotifyIME(
1534 nsIWidget* aWidget, const IMENotification& aNotification) {
1535 if (!mPendingEventsNeedingAck) {
1536 IMEStateManager::NotifyIME(aNotification, aWidget, &mBrowserParent);
1537 return;
1540 switch (aNotification.mMessage) {
1541 case NOTIFY_IME_OF_SELECTION_CHANGE:
1542 mPendingSelectionChange.MergeWith(aNotification);
1543 break;
1544 case NOTIFY_IME_OF_TEXT_CHANGE:
1545 mPendingTextChange.MergeWith(aNotification);
1546 break;
1547 case NOTIFY_IME_OF_POSITION_CHANGE:
1548 mPendingLayoutChange.MergeWith(aNotification);
1549 break;
1550 case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
1551 mPendingCompositionUpdate.MergeWith(aNotification);
1552 break;
1553 default:
1554 MOZ_CRASH("Unsupported notification");
1555 break;
1559 void ContentCacheInParent::FlushPendingNotifications(nsIWidget* aWidget) {
1560 MOZ_ASSERT(!mPendingEventsNeedingAck);
1562 // If the BrowserParent's widget has already gone, this can do nothing since
1563 // widget is necessary to notify IME of something.
1564 if (!aWidget) {
1565 return;
1568 // New notifications which are notified during flushing pending notifications
1569 // should be merged again.
1570 mPendingEventsNeedingAck++;
1572 nsCOMPtr<nsIWidget> widget = aWidget;
1574 // First, text change notification should be sent because selection change
1575 // notification notifies IME of current selection range in the latest content.
1576 // So, IME may need the latest content before that.
1577 if (mPendingTextChange.HasNotification()) {
1578 IMENotification notification(mPendingTextChange);
1579 if (!widget->Destroyed()) {
1580 mPendingTextChange.Clear();
1581 IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
1585 if (mPendingSelectionChange.HasNotification()) {
1586 IMENotification notification(mPendingSelectionChange);
1587 if (!widget->Destroyed()) {
1588 mPendingSelectionChange.Clear();
1589 IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
1593 // Layout change notification should be notified after selection change
1594 // notification because IME may want to query position of new caret position.
1595 if (mPendingLayoutChange.HasNotification()) {
1596 IMENotification notification(mPendingLayoutChange);
1597 if (!widget->Destroyed()) {
1598 mPendingLayoutChange.Clear();
1599 IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
1603 // Finally, send composition update notification because it notifies IME of
1604 // finishing handling whole sending events.
1605 if (mPendingCompositionUpdate.HasNotification()) {
1606 IMENotification notification(mPendingCompositionUpdate);
1607 if (!widget->Destroyed()) {
1608 mPendingCompositionUpdate.Clear();
1609 IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
1613 if (!--mPendingEventsNeedingAck && !widget->Destroyed() &&
1614 (mPendingTextChange.HasNotification() ||
1615 mPendingSelectionChange.HasNotification() ||
1616 mPendingLayoutChange.HasNotification() ||
1617 mPendingCompositionUpdate.HasNotification())) {
1618 FlushPendingNotifications(widget);
1622 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1624 void ContentCacheInParent::RemoveUnnecessaryEventMessageLog() {
1625 bool foundLastCompositionStart = false;
1626 for (size_t i = mDispatchedEventMessages.Length(); i > 1; i--) {
1627 if (mDispatchedEventMessages[i - 1] != eCompositionStart) {
1628 continue;
1630 if (!foundLastCompositionStart) {
1631 // Find previous eCompositionStart of the latest eCompositionStart.
1632 foundLastCompositionStart = true;
1633 continue;
1635 // Remove the messages before the last 2 sets of composition events.
1636 mDispatchedEventMessages.RemoveElementsAt(0, i - 1);
1637 break;
1639 uint32_t numberOfCompositionCommitRequestHandled = 0;
1640 foundLastCompositionStart = false;
1641 for (size_t i = mReceivedEventMessages.Length(); i > 1; i--) {
1642 if (mReceivedEventMessages[i - 1] == eCompositionCommitRequestHandled) {
1643 numberOfCompositionCommitRequestHandled++;
1645 if (mReceivedEventMessages[i - 1] != eCompositionStart) {
1646 continue;
1648 if (!foundLastCompositionStart) {
1649 // Find previous eCompositionStart of the latest eCompositionStart.
1650 foundLastCompositionStart = true;
1651 continue;
1653 // Remove the messages before the last 2 sets of composition events.
1654 mReceivedEventMessages.RemoveElementsAt(0, i - 1);
1655 break;
1658 if (!numberOfCompositionCommitRequestHandled) {
1659 // If there is no eCompositionCommitRequestHandled in
1660 // mReceivedEventMessages, we don't need to store log of
1661 // RequestIMEToCommmitComposition().
1662 mRequestIMEToCommitCompositionResults.Clear();
1663 } else {
1664 // We need to keep all reason of eCompositionCommitRequestHandled, which
1665 // is sent when mRequestIMEToCommitComposition() returns true.
1666 // So, we can discard older log than the first
1667 // eCompositionCommitRequestHandled in mReceivedEventMessages.
1668 for (size_t i = mRequestIMEToCommitCompositionResults.Length(); i > 1;
1669 i--) {
1670 if (mRequestIMEToCommitCompositionResults[i - 1] ==
1671 RequestIMEToCommitCompositionResult::
1672 eReceivedAfterBrowserParentBlur ||
1673 mRequestIMEToCommitCompositionResults[i - 1] ==
1674 RequestIMEToCommitCompositionResult::eHandledSynchronously) {
1675 --numberOfCompositionCommitRequestHandled;
1676 if (!numberOfCompositionCommitRequestHandled) {
1677 mRequestIMEToCommitCompositionResults.RemoveElementsAt(0, i - 1);
1678 break;
1685 void ContentCacheInParent::AppendEventMessageLog(nsACString& aLog) const {
1686 aLog.AppendLiteral("Dispatched Event Message Log:\n");
1687 for (EventMessage message : mDispatchedEventMessages) {
1688 aLog.AppendLiteral(" ");
1689 aLog.Append(ToChar(message));
1690 aLog.AppendLiteral("\n");
1692 aLog.AppendLiteral("\nReceived Event Message Log:\n");
1693 for (EventMessage message : mReceivedEventMessages) {
1694 aLog.AppendLiteral(" ");
1695 aLog.Append(ToChar(message));
1696 aLog.AppendLiteral("\n");
1698 aLog.AppendLiteral("\nResult of RequestIMEToCommitComposition():\n");
1699 for (RequestIMEToCommitCompositionResult result :
1700 mRequestIMEToCommitCompositionResults) {
1701 aLog.AppendLiteral(" ");
1702 aLog.Append(ToReadableText(result));
1703 aLog.AppendLiteral("\n");
1705 aLog.AppendLiteral("\n");
1708 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1710 /*****************************************************************************
1711 * mozilla::ContentCache::Selection
1712 *****************************************************************************/
1714 ContentCache::Selection::Selection(
1715 const WidgetQueryContentEvent& aQuerySelectedTextEvent)
1716 : mAnchor(UINT32_MAX),
1717 mFocus(UINT32_MAX),
1718 mWritingMode(aQuerySelectedTextEvent.mReply->WritingModeRef()),
1719 mHasRange(aQuerySelectedTextEvent.mReply->mOffsetAndData.isSome()) {
1720 MOZ_ASSERT(aQuerySelectedTextEvent.mMessage == eQuerySelectedText);
1721 MOZ_ASSERT(aQuerySelectedTextEvent.Succeeded());
1722 if (mHasRange) {
1723 mAnchor = aQuerySelectedTextEvent.mReply->AnchorOffset();
1724 mFocus = aQuerySelectedTextEvent.mReply->FocusOffset();
1728 /*****************************************************************************
1729 * mozilla::ContentCache::TextRectArray
1730 *****************************************************************************/
1732 LayoutDeviceIntRect ContentCache::TextRectArray::GetRect(
1733 uint32_t aOffset) const {
1734 LayoutDeviceIntRect rect;
1735 if (IsOffsetInRange(aOffset)) {
1736 rect = mRects[aOffset - mStart];
1738 return rect;
1741 LayoutDeviceIntRect ContentCache::TextRectArray::GetUnionRect(
1742 uint32_t aOffset, uint32_t aLength) const {
1743 LayoutDeviceIntRect rect;
1744 if (!IsRangeCompletelyInRange(aOffset, aLength)) {
1745 return rect;
1747 for (uint32_t i = 0; i < aLength; i++) {
1748 rect = rect.Union(mRects[aOffset - mStart + i]);
1750 return rect;
1753 LayoutDeviceIntRect ContentCache::TextRectArray::GetUnionRectAsFarAsPossible(
1754 uint32_t aOffset, uint32_t aLength, bool aRoundToExistingOffset) const {
1755 LayoutDeviceIntRect rect;
1756 if (!HasRects() ||
1757 (!aRoundToExistingOffset && !IsOverlappingWith(aOffset, aLength))) {
1758 return rect;
1760 uint32_t startOffset = std::max(aOffset, mStart);
1761 if (aRoundToExistingOffset && startOffset >= EndOffset()) {
1762 startOffset = EndOffset() - 1;
1764 uint32_t endOffset = std::min(aOffset + aLength, EndOffset());
1765 if (aRoundToExistingOffset && endOffset < mStart + 1) {
1766 endOffset = mStart + 1;
1768 if (NS_WARN_IF(endOffset < startOffset)) {
1769 return rect;
1771 for (uint32_t i = 0; i < endOffset - startOffset; i++) {
1772 rect = rect.Union(mRects[startOffset - mStart + i]);
1774 return rect;
1777 } // namespace mozilla