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