1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "mozilla/ContentCache.h"
10 #include "mozilla/IMEStateManager.h"
11 #include "mozilla/IntegerPrintfMacros.h"
12 #include "mozilla/Logging.h"
13 #include "mozilla/Move.h"
14 #include "mozilla/RefPtr.h"
15 #include "mozilla/TextComposition.h"
16 #include "mozilla/TextEvents.h"
17 #include "mozilla/dom/BrowserParent.h"
18 #include "nsExceptionHandler.h"
19 #include "nsIWidget.h"
24 using namespace widget
;
26 static const char* GetBoolName(bool aBool
) { return aBool
? "true" : "false"; }
28 static const char* GetNotificationName(const IMENotification
* aNotification
) {
30 return "Not notification";
32 return ToChar(aNotification
->mMessage
);
35 class GetRectText
: public nsAutoCString
{
37 explicit GetRectText(const LayoutDeviceIntRect
& aRect
) {
38 AssignLiteral("{ x=");
40 AppendLiteral(", y=");
42 AppendLiteral(", width=");
43 AppendInt(aRect
.Width());
44 AppendLiteral(", height=");
45 AppendInt(aRect
.Height());
48 virtual ~GetRectText() {}
51 class GetWritingModeName
: public nsAutoCString
{
53 explicit GetWritingModeName(const WritingMode
& aWritingMode
) {
54 if (!aWritingMode
.IsVertical()) {
55 AssignLiteral("Horizontal");
58 if (aWritingMode
.IsVerticalLR()) {
59 AssignLiteral("Vertical (LTR)");
62 AssignLiteral("Vertical (RTL)");
64 virtual ~GetWritingModeName() {}
67 class GetEscapedUTF8String final
: public NS_ConvertUTF16toUTF8
{
69 explicit GetEscapedUTF8String(const nsAString
& aString
)
70 : NS_ConvertUTF16toUTF8(aString
) {
73 explicit GetEscapedUTF8String(const char16ptr_t aString
)
74 : NS_ConvertUTF16toUTF8(aString
) {
77 GetEscapedUTF8String(const char16ptr_t aString
, uint32_t aLength
)
78 : NS_ConvertUTF16toUTF8(aString
, aLength
) {
84 ReplaceSubstring("\r", "\\r");
85 ReplaceSubstring("\n", "\\n");
86 ReplaceSubstring("\t", "\\t");
90 /*****************************************************************************
91 * mozilla::ContentCache
92 *****************************************************************************/
94 LazyLogModule
sContentCacheLog("ContentCacheWidgets");
96 ContentCache::ContentCache() : mCompositionStart(UINT32_MAX
) {}
98 /*****************************************************************************
99 * mozilla::ContentCacheInChild
100 *****************************************************************************/
102 ContentCacheInChild::ContentCacheInChild() : ContentCache() {}
104 void ContentCacheInChild::Clear() {
105 MOZ_LOG(sContentCacheLog
, LogLevel::Info
, ("0x%p Clear()", this));
107 mCompositionStart
= UINT32_MAX
;
110 mFirstCharRect
.SetEmpty();
112 mTextRectArray
.Clear();
113 mEditorRect
.SetEmpty();
116 bool ContentCacheInChild::CacheAll(nsIWidget
* aWidget
,
117 const IMENotification
* aNotification
) {
118 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
119 ("0x%p CacheAll(aWidget=0x%p, aNotification=%s)", this, aWidget
,
120 GetNotificationName(aNotification
)));
122 if (NS_WARN_IF(!CacheText(aWidget
, aNotification
)) ||
123 NS_WARN_IF(!CacheEditorRect(aWidget
, aNotification
))) {
129 bool ContentCacheInChild::CacheSelection(nsIWidget
* aWidget
,
130 const IMENotification
* aNotification
) {
131 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
132 ("0x%p CacheSelection(aWidget=0x%p, aNotification=%s)", this, aWidget
,
133 GetNotificationName(aNotification
)));
138 nsEventStatus status
= nsEventStatus_eIgnore
;
139 WidgetQueryContentEvent
selection(true, eQuerySelectedText
, aWidget
);
140 aWidget
->DispatchEvent(&selection
, status
);
141 if (NS_WARN_IF(!selection
.mSucceeded
)) {
142 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
143 ("0x%p CacheSelection(), FAILED, "
144 "couldn't retrieve the selected text",
148 if (selection
.mReply
.mReversed
) {
150 selection
.mReply
.mOffset
+ selection
.mReply
.mString
.Length();
151 mSelection
.mFocus
= selection
.mReply
.mOffset
;
153 mSelection
.mAnchor
= selection
.mReply
.mOffset
;
155 selection
.mReply
.mOffset
+ selection
.mReply
.mString
.Length();
157 mSelection
.mWritingMode
= selection
.GetWritingMode();
159 return CacheCaret(aWidget
, aNotification
) &&
160 CacheTextRects(aWidget
, aNotification
);
163 bool ContentCacheInChild::CacheCaret(nsIWidget
* aWidget
,
164 const IMENotification
* aNotification
) {
165 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
166 ("0x%p CacheCaret(aWidget=0x%p, aNotification=%s)", this, aWidget
,
167 GetNotificationName(aNotification
)));
171 if (NS_WARN_IF(!mSelection
.IsValid())) {
175 // XXX Should be mSelection.mFocus?
176 mCaret
.mOffset
= mSelection
.StartOffset();
178 nsEventStatus status
= nsEventStatus_eIgnore
;
179 WidgetQueryContentEvent
caretRect(true, eQueryCaretRect
, aWidget
);
180 caretRect
.InitForQueryCaretRect(mCaret
.mOffset
);
181 aWidget
->DispatchEvent(&caretRect
, status
);
182 if (NS_WARN_IF(!caretRect
.mSucceeded
)) {
183 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
184 ("0x%p CacheCaret(), FAILED, "
185 "couldn't retrieve the caret rect at offset=%u",
186 this, mCaret
.mOffset
));
190 mCaret
.mRect
= caretRect
.mReply
.mRect
;
191 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
192 ("0x%p CacheCaret(), Succeeded, "
193 "mSelection={ mAnchor=%u, mFocus=%u, mWritingMode=%s }, "
194 "mCaret={ mOffset=%u, mRect=%s }",
195 this, mSelection
.mAnchor
, mSelection
.mFocus
,
196 GetWritingModeName(mSelection
.mWritingMode
).get(), mCaret
.mOffset
,
197 GetRectText(mCaret
.mRect
).get()));
201 bool ContentCacheInChild::CacheEditorRect(
202 nsIWidget
* aWidget
, const IMENotification
* aNotification
) {
203 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
204 ("0x%p CacheEditorRect(aWidget=0x%p, aNotification=%s)", this,
205 aWidget
, GetNotificationName(aNotification
)));
207 nsEventStatus status
= nsEventStatus_eIgnore
;
208 WidgetQueryContentEvent
editorRectEvent(true, eQueryEditorRect
, aWidget
);
209 aWidget
->DispatchEvent(&editorRectEvent
, status
);
210 if (NS_WARN_IF(!editorRectEvent
.mSucceeded
)) {
211 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
212 ("0x%p CacheEditorRect(), FAILED, "
213 "couldn't retrieve the editor rect",
217 mEditorRect
= editorRectEvent
.mReply
.mRect
;
218 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
219 ("0x%p CacheEditorRect(), Succeeded, "
221 this, GetRectText(mEditorRect
).get()));
225 bool ContentCacheInChild::CacheText(nsIWidget
* aWidget
,
226 const IMENotification
* aNotification
) {
227 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
228 ("0x%p CacheText(aWidget=0x%p, aNotification=%s)", this, aWidget
,
229 GetNotificationName(aNotification
)));
231 nsEventStatus status
= nsEventStatus_eIgnore
;
232 WidgetQueryContentEvent
queryText(true, eQueryTextContent
, aWidget
);
233 queryText
.InitForQueryTextContent(0, UINT32_MAX
);
234 aWidget
->DispatchEvent(&queryText
, status
);
235 if (NS_WARN_IF(!queryText
.mSucceeded
)) {
236 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
237 ("0x%p CacheText(), FAILED, couldn't retrieve whole text", this));
241 mText
= queryText
.mReply
.mString
;
243 sContentCacheLog
, LogLevel::Info
,
244 ("0x%p CacheText(), Succeeded, mText.Length()=%u", this, mText
.Length()));
246 return CacheSelection(aWidget
, aNotification
);
249 bool ContentCacheInChild::QueryCharRect(nsIWidget
* aWidget
, uint32_t aOffset
,
250 LayoutDeviceIntRect
& aCharRect
) const {
251 aCharRect
.SetEmpty();
253 nsEventStatus status
= nsEventStatus_eIgnore
;
254 WidgetQueryContentEvent
textRect(true, eQueryTextRect
, aWidget
);
255 textRect
.InitForQueryTextRect(aOffset
, 1);
256 aWidget
->DispatchEvent(&textRect
, status
);
257 if (NS_WARN_IF(!textRect
.mSucceeded
)) {
260 aCharRect
= textRect
.mReply
.mRect
;
262 // Guarantee the rect is not empty.
263 if (NS_WARN_IF(!aCharRect
.Height())) {
264 aCharRect
.SetHeight(1);
266 if (NS_WARN_IF(!aCharRect
.Width())) {
267 aCharRect
.SetWidth(1);
272 bool ContentCacheInChild::QueryCharRectArray(nsIWidget
* aWidget
,
273 uint32_t aOffset
, uint32_t aLength
,
274 RectArray
& aCharRectArray
) const {
275 nsEventStatus status
= nsEventStatus_eIgnore
;
276 WidgetQueryContentEvent
textRects(true, eQueryTextRectArray
, aWidget
);
277 textRects
.InitForQueryTextRectArray(aOffset
, aLength
);
278 aWidget
->DispatchEvent(&textRects
, status
);
279 if (NS_WARN_IF(!textRects
.mSucceeded
)) {
280 aCharRectArray
.Clear();
283 aCharRectArray
= std::move(textRects
.mReply
.mRectArray
);
287 bool ContentCacheInChild::CacheTextRects(nsIWidget
* aWidget
,
288 const IMENotification
* aNotification
) {
289 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
290 ("0x%p CacheTextRects(aWidget=0x%p, aNotification=%s), "
291 "mCaret={ mOffset=%u, IsValid()=%s }",
292 this, aWidget
, GetNotificationName(aNotification
), mCaret
.mOffset
,
293 GetBoolName(mCaret
.IsValid())));
295 mCompositionStart
= UINT32_MAX
;
296 mTextRectArray
.Clear();
297 mSelection
.ClearAnchorCharRects();
298 mSelection
.ClearFocusCharRects();
299 mSelection
.mRect
.SetEmpty();
300 mFirstCharRect
.SetEmpty();
302 if (NS_WARN_IF(!mSelection
.IsValid())) {
306 // Retrieve text rects in composition string if there is.
307 RefPtr
<TextComposition
> textComposition
=
308 IMEStateManager::GetTextCompositionFor(aWidget
);
309 if (textComposition
) {
310 // mCompositionStart may be updated by some composition event handlers.
311 // So, let's update it with the latest information.
312 mCompositionStart
= textComposition
->NativeOffsetOfStartComposition();
313 // Note that TextComposition::String() may not be modified here because
314 // it's modified after all edit action listeners are performed but this
315 // is called while some of them are performed.
316 // FYI: For supporting IME which commits composition and restart new
317 // composition immediately, we should cache next character of current
319 uint32_t length
= textComposition
->LastData().Length() + 1;
320 mTextRectArray
.mStart
= mCompositionStart
;
321 if (NS_WARN_IF(!QueryCharRectArray(aWidget
, mTextRectArray
.mStart
, length
,
322 mTextRectArray
.mRects
))) {
323 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
324 ("0x%p CacheTextRects(), FAILED, "
325 "couldn't retrieve text rect array of the composition string",
330 if (mTextRectArray
.InRange(mSelection
.mAnchor
) &&
331 (!mSelection
.mAnchor
|| mTextRectArray
.InRange(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);
340 uint32_t startOffset
= mSelection
.mAnchor
? mSelection
.mAnchor
- 1 : 0;
341 uint32_t length
= mSelection
.mAnchor
? 2 : 1;
342 if (NS_WARN_IF(!QueryCharRectArray(aWidget
, startOffset
, length
, rects
))) {
344 sContentCacheLog
, LogLevel::Error
,
345 ("0x%p CacheTextRects(), FAILED, "
346 "couldn't retrieve text rect array around the selection anchor (%u)",
347 this, mSelection
.mAnchor
));
348 MOZ_ASSERT(mSelection
.mAnchorCharRects
[ePrevCharRect
].IsEmpty());
349 MOZ_ASSERT(mSelection
.mAnchorCharRects
[eNextCharRect
].IsEmpty());
351 if (rects
.Length() > 1) {
352 mSelection
.mAnchorCharRects
[ePrevCharRect
] = rects
[0];
353 mSelection
.mAnchorCharRects
[eNextCharRect
] = rects
[1];
354 } else if (rects
.Length()) {
355 mSelection
.mAnchorCharRects
[eNextCharRect
] = rects
[0];
356 MOZ_ASSERT(mSelection
.mAnchorCharRects
[ePrevCharRect
].IsEmpty());
361 if (mSelection
.Collapsed()) {
362 mSelection
.mFocusCharRects
[0] = mSelection
.mAnchorCharRects
[0];
363 mSelection
.mFocusCharRects
[1] = mSelection
.mAnchorCharRects
[1];
364 } else if (mTextRectArray
.InRange(mSelection
.mFocus
) &&
365 (!mSelection
.mFocus
||
366 mTextRectArray
.InRange(mSelection
.mFocus
- 1))) {
367 mSelection
.mFocusCharRects
[eNextCharRect
] =
368 mTextRectArray
.GetRect(mSelection
.mFocus
);
369 if (mSelection
.mFocus
) {
370 mSelection
.mFocusCharRects
[ePrevCharRect
] =
371 mTextRectArray
.GetRect(mSelection
.mFocus
- 1);
375 uint32_t startOffset
= mSelection
.mFocus
? mSelection
.mFocus
- 1 : 0;
376 uint32_t length
= mSelection
.mFocus
? 2 : 1;
377 if (NS_WARN_IF(!QueryCharRectArray(aWidget
, startOffset
, length
, rects
))) {
379 sContentCacheLog
, LogLevel::Error
,
380 ("0x%p CacheTextRects(), FAILED, "
381 "couldn't retrieve text rect array around the selection focus (%u)",
382 this, mSelection
.mFocus
));
383 MOZ_ASSERT(mSelection
.mFocusCharRects
[ePrevCharRect
].IsEmpty());
384 MOZ_ASSERT(mSelection
.mFocusCharRects
[eNextCharRect
].IsEmpty());
386 if (rects
.Length() > 1) {
387 mSelection
.mFocusCharRects
[ePrevCharRect
] = rects
[0];
388 mSelection
.mFocusCharRects
[eNextCharRect
] = rects
[1];
389 } else if (rects
.Length()) {
390 mSelection
.mFocusCharRects
[eNextCharRect
] = rects
[0];
391 MOZ_ASSERT(mSelection
.mFocusCharRects
[ePrevCharRect
].IsEmpty());
396 if (!mSelection
.Collapsed()) {
397 nsEventStatus status
= nsEventStatus_eIgnore
;
398 WidgetQueryContentEvent
textRect(true, eQueryTextRect
, aWidget
);
399 textRect
.InitForQueryTextRect(mSelection
.StartOffset(),
400 mSelection
.Length());
401 aWidget
->DispatchEvent(&textRect
, status
);
402 if (NS_WARN_IF(!textRect
.mSucceeded
)) {
403 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
404 ("0x%p CacheTextRects(), FAILED, "
405 "couldn't retrieve text rect of whole selected text",
408 mSelection
.mRect
= textRect
.mReply
.mRect
;
412 if (!mSelection
.mFocus
) {
413 mFirstCharRect
= mSelection
.mFocusCharRects
[eNextCharRect
];
414 } else if (mSelection
.mFocus
== 1) {
415 mFirstCharRect
= mSelection
.mFocusCharRects
[ePrevCharRect
];
416 } else if (!mSelection
.mAnchor
) {
417 mFirstCharRect
= mSelection
.mAnchorCharRects
[eNextCharRect
];
418 } else if (mSelection
.mAnchor
== 1) {
419 mFirstCharRect
= mSelection
.mFocusCharRects
[ePrevCharRect
];
420 } else if (mTextRectArray
.InRange(0)) {
421 mFirstCharRect
= mTextRectArray
.GetRect(0);
423 LayoutDeviceIntRect charRect
;
424 if (NS_WARN_IF(!QueryCharRect(aWidget
, 0, charRect
))) {
425 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
426 ("0x%p CacheTextRects(), FAILED, "
427 "couldn't retrieve first char rect",
430 mFirstCharRect
= charRect
;
435 sContentCacheLog
, LogLevel::Info
,
436 ("0x%p CacheTextRects(), Succeeded, "
437 "mText.Length()=%x, mTextRectArray={ mStart=%u, mRects.Length()=%zu"
438 " }, mSelection={ mAnchor=%u, mAnchorCharRects[eNextCharRect]=%s, "
439 "mAnchorCharRects[ePrevCharRect]=%s, mFocus=%u, "
440 "mFocusCharRects[eNextCharRect]=%s, mFocusCharRects[ePrevCharRect]=%s, "
441 "mRect=%s }, mFirstCharRect=%s",
442 this, mText
.Length(), mTextRectArray
.mStart
,
443 mTextRectArray
.mRects
.Length(), mSelection
.mAnchor
,
444 GetRectText(mSelection
.mAnchorCharRects
[eNextCharRect
]).get(),
445 GetRectText(mSelection
.mAnchorCharRects
[ePrevCharRect
]).get(),
447 GetRectText(mSelection
.mFocusCharRects
[eNextCharRect
]).get(),
448 GetRectText(mSelection
.mFocusCharRects
[ePrevCharRect
]).get(),
449 GetRectText(mSelection
.mRect
).get(), GetRectText(mFirstCharRect
).get()));
453 void ContentCacheInChild::SetSelection(nsIWidget
* aWidget
,
454 uint32_t aStartOffset
, uint32_t aLength
,
456 const WritingMode
& aWritingMode
) {
457 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
458 ("0x%p SetSelection(aStartOffset=%u, "
459 "aLength=%u, aReversed=%s, aWritingMode=%s), mText.Length()=%u",
460 this, aStartOffset
, aLength
, GetBoolName(aReversed
),
461 GetWritingModeName(aWritingMode
).get(), mText
.Length()));
464 mSelection
.mAnchor
= aStartOffset
;
465 mSelection
.mFocus
= aStartOffset
+ aLength
;
467 mSelection
.mAnchor
= aStartOffset
+ aLength
;
468 mSelection
.mFocus
= aStartOffset
;
470 mSelection
.mWritingMode
= aWritingMode
;
472 if (NS_WARN_IF(!CacheCaret(aWidget
))) {
475 Unused
<< NS_WARN_IF(!CacheTextRects(aWidget
));
478 /*****************************************************************************
479 * mozilla::ContentCacheInParent
480 *****************************************************************************/
482 ContentCacheInParent::ContentCacheInParent(BrowserParent
& aBrowserParent
)
484 mBrowserParent(aBrowserParent
),
485 mCommitStringByRequest(nullptr),
486 mPendingEventsNeedingAck(0),
487 mCompositionStartInChild(UINT32_MAX
),
488 mPendingCommitLength(0),
489 mPendingCompositionCount(0),
490 mPendingCommitCount(0),
491 mWidgetHasComposition(false),
492 mIsChildIgnoringCompositionEvents(false) {}
494 void ContentCacheInParent::AssignContent(const ContentCache
& aOther
,
496 const IMENotification
* aNotification
) {
497 mText
= aOther
.mText
;
498 mSelection
= aOther
.mSelection
;
499 mFirstCharRect
= aOther
.mFirstCharRect
;
500 mCaret
= aOther
.mCaret
;
501 mTextRectArray
= aOther
.mTextRectArray
;
502 mEditorRect
= aOther
.mEditorRect
;
504 // Only when there is one composition, the TextComposition instance in this
505 // process is managing the composition in the remote process. Therefore,
506 // we shouldn't update composition start offset of TextComposition with
507 // old composition which is still being handled by the child process.
508 if (mWidgetHasComposition
&& mPendingCompositionCount
== 1) {
509 IMEStateManager::MaybeStartOffsetUpdatedInChild(aWidget
, mCompositionStart
);
512 // When this instance allows to query content relative to composition string,
513 // we should modify mCompositionStart with the latest information in the
514 // remote process because now we have the information around the composition
516 mCompositionStartInChild
= aOther
.mCompositionStart
;
517 if (mWidgetHasComposition
|| mPendingCommitCount
) {
518 if (aOther
.mCompositionStart
!= UINT32_MAX
) {
519 if (mCompositionStart
!= aOther
.mCompositionStart
) {
520 mCompositionStart
= aOther
.mCompositionStart
;
521 mPendingCommitLength
= 0;
523 } else if (mCompositionStart
!= mSelection
.StartOffset()) {
524 mCompositionStart
= mSelection
.StartOffset();
525 mPendingCommitLength
= 0;
526 NS_WARNING_ASSERTION(mCompositionStart
!= UINT32_MAX
,
527 "mCompositionStart shouldn't be invalid offset when "
528 "the widget has composition");
533 sContentCacheLog
, LogLevel::Info
,
534 ("0x%p AssignContent(aNotification=%s), "
535 "Succeeded, mText.Length()=%u, mSelection={ mAnchor=%u, mFocus=%u, "
536 "mWritingMode=%s, mAnchorCharRects[eNextCharRect]=%s, "
537 "mAnchorCharRects[ePrevCharRect]=%s, mFocusCharRects[eNextCharRect]=%s, "
538 "mFocusCharRects[ePrevCharRect]=%s, mRect=%s }, "
539 "mFirstCharRect=%s, mCaret={ mOffset=%u, mRect=%s }, mTextRectArray={ "
540 "mStart=%u, mRects.Length()=%zu }, mWidgetHasComposition=%s, "
541 "mPendingCompositionCount=%u, mCompositionStart=%u, "
542 "mPendingCommitLength=%u, mEditorRect=%s",
543 this, GetNotificationName(aNotification
), mText
.Length(),
544 mSelection
.mAnchor
, mSelection
.mFocus
,
545 GetWritingModeName(mSelection
.mWritingMode
).get(),
546 GetRectText(mSelection
.mAnchorCharRects
[eNextCharRect
]).get(),
547 GetRectText(mSelection
.mAnchorCharRects
[ePrevCharRect
]).get(),
548 GetRectText(mSelection
.mFocusCharRects
[eNextCharRect
]).get(),
549 GetRectText(mSelection
.mFocusCharRects
[ePrevCharRect
]).get(),
550 GetRectText(mSelection
.mRect
).get(), GetRectText(mFirstCharRect
).get(),
551 mCaret
.mOffset
, GetRectText(mCaret
.mRect
).get(), mTextRectArray
.mStart
,
552 mTextRectArray
.mRects
.Length(), GetBoolName(mWidgetHasComposition
),
553 mPendingCompositionCount
, mCompositionStart
, mPendingCommitLength
,
554 GetRectText(mEditorRect
).get()));
557 bool ContentCacheInParent::HandleQueryContentEvent(
558 WidgetQueryContentEvent
& aEvent
, nsIWidget
* aWidget
) const {
561 aEvent
.mSucceeded
= false;
562 aEvent
.mReply
.mFocusedWidget
= aWidget
;
564 // ContentCache doesn't store offset of its start with XP linebreaks.
565 // So, we don't support to query contents relative to composition start
566 // offset with XP linebreaks.
567 if (NS_WARN_IF(!aEvent
.mUseNativeLineBreak
)) {
568 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
569 ("0x%p HandleQueryContentEvent(), FAILED due to query with XP "
575 if (NS_WARN_IF(!aEvent
.mInput
.IsValidOffset())) {
577 sContentCacheLog
, LogLevel::Error
,
578 ("0x%p HandleQueryContentEvent(), FAILED due to invalid offset", this));
582 if (NS_WARN_IF(!aEvent
.mInput
.IsValidEventMessage(aEvent
.mMessage
))) {
584 sContentCacheLog
, LogLevel::Error
,
585 ("0x%p HandleQueryContentEvent(), FAILED due to invalid event message",
590 bool isRelativeToInsertionPoint
= aEvent
.mInput
.mRelativeToInsertionPoint
;
591 if (isRelativeToInsertionPoint
) {
592 if (aWidget
->PluginHasFocus()) {
593 if (NS_WARN_IF(!aEvent
.mInput
.MakeOffsetAbsolute(0))) {
594 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
595 ("0x%p HandleQueryContentEvent(), FAILED due to "
596 "aEvent.mInput.MakeOffsetAbsolute(0) failure, aEvent={ "
598 "mInput={ mOffset=%" PRId64
", mLength=%" PRIu32
" } }",
599 this, ToChar(aEvent
.mMessage
), aEvent
.mInput
.mOffset
,
600 aEvent
.mInput
.mLength
));
603 } else if (mWidgetHasComposition
|| mPendingCommitCount
) {
604 if (NS_WARN_IF(!aEvent
.mInput
.MakeOffsetAbsolute(mCompositionStart
+
605 mPendingCommitLength
))) {
607 sContentCacheLog
, LogLevel::Error
,
608 ("0x%p HandleQueryContentEvent(), FAILED due to "
609 "aEvent.mInput.MakeOffsetAbsolute(mCompositionStart + "
610 "mPendingCommitLength) failure, "
611 "mCompositionStart=%" PRIu32
", mPendingCommitLength=%" PRIu32
", "
612 "aEvent={ mMessage=%s, mInput={ mOffset=%" PRId64
613 ", mLength=%" PRIu32
" } }",
614 this, mCompositionStart
, mPendingCommitLength
,
615 ToChar(aEvent
.mMessage
), aEvent
.mInput
.mOffset
,
616 aEvent
.mInput
.mLength
));
619 } else if (NS_WARN_IF(!mSelection
.IsValid())) {
620 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
621 ("0x%p HandleQueryContentEvent(), FAILED due to mSelection is "
625 } else if (NS_WARN_IF(!aEvent
.mInput
.MakeOffsetAbsolute(
626 mSelection
.StartOffset() + mPendingCommitLength
))) {
627 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
628 ("0x%p HandleQueryContentEvent(), FAILED due to "
629 "aEvent.mInput.MakeOffsetAbsolute(mSelection.StartOffset() + "
630 "mPendingCommitLength) failure, "
631 "mSelection={ StartOffset()=%d, Length()=%d }, "
632 "mPendingCommitLength=%" PRIu32
", aEvent={ mMessage=%s, "
633 "mInput={ mOffset=%" PRId64
", mLength=%" PRIu32
" } }",
634 this, mSelection
.StartOffset(), mSelection
.Length(),
635 mPendingCommitLength
, ToChar(aEvent
.mMessage
),
636 aEvent
.mInput
.mOffset
, aEvent
.mInput
.mLength
));
641 switch (aEvent
.mMessage
) {
642 case eQuerySelectedText
:
643 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
644 ("0x%p HandleQueryContentEvent("
645 "aEvent={ mMessage=eQuerySelectedText }, aWidget=0x%p)",
647 if (aWidget
->PluginHasFocus()) {
648 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
649 ("0x%p HandleQueryContentEvent(), "
650 "return emtpy selection becasue plugin has focus",
652 aEvent
.mSucceeded
= true;
653 aEvent
.mReply
.mOffset
= 0;
654 aEvent
.mReply
.mReversed
= false;
655 aEvent
.mReply
.mHasSelection
= false;
658 if (NS_WARN_IF(!IsSelectionValid())) {
659 // If content cache hasn't been initialized properly, make the query
661 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
662 ("0x%p HandleQueryContentEvent(), "
663 "FAILED because mSelection is not valid",
667 aEvent
.mReply
.mOffset
= mSelection
.StartOffset();
668 if (mSelection
.Collapsed()) {
669 aEvent
.mReply
.mString
.Truncate(0);
671 if (NS_WARN_IF(mSelection
.EndOffset() > mText
.Length())) {
672 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
673 ("0x%p HandleQueryContentEvent(), "
674 "FAILED because mSelection.EndOffset()=%u is larger than "
676 this, mSelection
.EndOffset(), mText
.Length()));
679 aEvent
.mReply
.mString
=
680 Substring(mText
, aEvent
.mReply
.mOffset
, mSelection
.Length());
682 aEvent
.mReply
.mReversed
= mSelection
.Reversed();
683 aEvent
.mReply
.mHasSelection
= true;
684 aEvent
.mReply
.mWritingMode
= mSelection
.mWritingMode
;
685 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
686 ("0x%p HandleQueryContentEvent(), "
687 "Succeeded, aEvent={ mReply={ mOffset=%u, mString=\"%s\", "
688 "mReversed=%s, mHasSelection=%s, mWritingMode=%s } }",
689 this, aEvent
.mReply
.mOffset
,
690 GetEscapedUTF8String(aEvent
.mReply
.mString
).get(),
691 GetBoolName(aEvent
.mReply
.mReversed
),
692 GetBoolName(aEvent
.mReply
.mHasSelection
),
693 GetWritingModeName(aEvent
.mReply
.mWritingMode
).get()));
695 case eQueryTextContent
: {
696 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
697 ("0x%p HandleQueryContentEvent("
698 "aEvent={ mMessage=eQueryTextContent, mInput={ mOffset=%" PRId64
699 ", mLength=%u } }, aWidget=0x%p), mText.Length()=%u",
700 this, aEvent
.mInput
.mOffset
, aEvent
.mInput
.mLength
, aWidget
,
702 uint32_t inputOffset
= aEvent
.mInput
.mOffset
;
703 uint32_t inputEndOffset
=
704 std::min(aEvent
.mInput
.EndOffset(), mText
.Length());
705 if (NS_WARN_IF(inputEndOffset
< inputOffset
)) {
707 sContentCacheLog
, LogLevel::Error
,
708 ("0x%p HandleQueryContentEvent(), "
709 "FAILED because inputOffset=%u is larger than inputEndOffset=%u",
710 this, inputOffset
, inputEndOffset
));
713 aEvent
.mReply
.mOffset
= inputOffset
;
714 aEvent
.mReply
.mString
=
715 Substring(mText
, inputOffset
, inputEndOffset
- inputOffset
);
717 sContentCacheLog
, LogLevel::Info
,
718 ("0x%p HandleQueryContentEvent(), "
719 "Succeeded, aEvent={ mReply={ mOffset=%u, mString.Length()=%u } }",
720 this, aEvent
.mReply
.mOffset
, aEvent
.mReply
.mString
.Length()));
724 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
725 ("0x%p HandleQueryContentEvent("
726 "aEvent={ mMessage=eQueryTextRect, mInput={ mOffset=%" PRId64
727 ", mLength=%u } }, aWidget=0x%p), mText.Length()=%u",
728 this, aEvent
.mInput
.mOffset
, aEvent
.mInput
.mLength
, aWidget
,
730 if (NS_WARN_IF(!IsSelectionValid())) {
731 // If content cache hasn't been initialized properly, make the query
733 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
734 ("0x%p HandleQueryContentEvent(), "
735 "FAILED because mSelection is not valid",
739 // Note that if the query is relative to insertion point, the query was
740 // probably requested by native IME. In such case, we should return
741 // non-empty rect since returning failure causes IME showing its window
743 if (aEvent
.mInput
.mLength
) {
744 if (NS_WARN_IF(!GetUnionTextRects(
745 aEvent
.mInput
.mOffset
, aEvent
.mInput
.mLength
,
746 isRelativeToInsertionPoint
, aEvent
.mReply
.mRect
))) {
747 // XXX We don't have cache for this request.
748 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
749 ("0x%p HandleQueryContentEvent(), "
750 "FAILED to get union rect",
755 // If the length is 0, we should return caret rect instead.
756 if (NS_WARN_IF(!GetCaretRect(aEvent
.mInput
.mOffset
,
757 isRelativeToInsertionPoint
,
758 aEvent
.mReply
.mRect
))) {
759 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
760 ("0x%p HandleQueryContentEvent(), "
761 "FAILED to get caret rect",
766 if (aEvent
.mInput
.mOffset
< mText
.Length()) {
767 aEvent
.mReply
.mString
= Substring(
768 mText
, aEvent
.mInput
.mOffset
,
769 mText
.Length() >= aEvent
.mInput
.EndOffset() ? aEvent
.mInput
.mLength
772 aEvent
.mReply
.mString
.Truncate(0);
774 aEvent
.mReply
.mOffset
= aEvent
.mInput
.mOffset
;
775 // XXX This may be wrong if storing range isn't in the selection range.
776 aEvent
.mReply
.mWritingMode
= mSelection
.mWritingMode
;
777 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
778 ("0x%p HandleQueryContentEvent(), "
779 "Succeeded, aEvent={ mReply={ mOffset=%u, mString=\"%s\", "
780 "mWritingMode=%s, mRect=%s } }",
781 this, aEvent
.mReply
.mOffset
,
782 GetEscapedUTF8String(aEvent
.mReply
.mString
).get(),
783 GetWritingModeName(aEvent
.mReply
.mWritingMode
).get(),
784 GetRectText(aEvent
.mReply
.mRect
).get()));
786 case eQueryCaretRect
:
787 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
788 ("0x%p HandleQueryContentEvent("
789 "aEvent={ mMessage=eQueryCaretRect, mInput={ mOffset=%" PRId64
791 "aWidget=0x%p), mText.Length()=%u",
792 this, aEvent
.mInput
.mOffset
, aWidget
, mText
.Length()));
793 if (NS_WARN_IF(!IsSelectionValid())) {
794 // If content cache hasn't been initialized properly, make the query
796 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
797 ("0x%p HandleQueryContentEvent(), "
798 "FAILED because mSelection is not valid",
802 // Note that if the query is relative to insertion point, the query was
803 // probably requested by native IME. In such case, we should return
804 // non-empty rect since returning failure causes IME showing its window
806 if (NS_WARN_IF(!GetCaretRect(aEvent
.mInput
.mOffset
,
807 isRelativeToInsertionPoint
,
808 aEvent
.mReply
.mRect
))) {
809 MOZ_LOG(sContentCacheLog
, LogLevel::Error
,
810 ("0x%p HandleQueryContentEvent(), "
811 "FAILED to get caret rect",
815 aEvent
.mReply
.mOffset
= aEvent
.mInput
.mOffset
;
816 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
817 ("0x%p HandleQueryContentEvent(), "
818 "Succeeded, aEvent={ mReply={ mOffset=%u, mRect=%s } }",
819 this, aEvent
.mReply
.mOffset
,
820 GetRectText(aEvent
.mReply
.mRect
).get()));
822 case eQueryEditorRect
:
823 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
824 ("0x%p HandleQueryContentEvent("
825 "aEvent={ mMessage=eQueryEditorRect }, aWidget=0x%p)",
827 aEvent
.mReply
.mRect
= mEditorRect
;
828 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
829 ("0x%p HandleQueryContentEvent(), "
830 "Succeeded, aEvent={ mReply={ mRect=%s } }",
831 this, GetRectText(aEvent
.mReply
.mRect
).get()));
836 aEvent
.mSucceeded
= true;
840 bool ContentCacheInParent::GetTextRect(uint32_t aOffset
,
841 bool aRoundToExistingOffset
,
842 LayoutDeviceIntRect
& aTextRect
) const {
843 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
844 ("0x%p GetTextRect(aOffset=%u, "
845 "aRoundToExistingOffset=%s), "
846 "mTextRectArray={ mStart=%u, mRects.Length()=%zu }, "
847 "mSelection={ mAnchor=%u, mFocus=%u }",
848 this, aOffset
, GetBoolName(aRoundToExistingOffset
),
849 mTextRectArray
.mStart
, mTextRectArray
.mRects
.Length(),
850 mSelection
.mAnchor
, mSelection
.mFocus
));
853 NS_WARNING_ASSERTION(!mFirstCharRect
.IsEmpty(), "empty rect");
854 aTextRect
= mFirstCharRect
;
855 return !aTextRect
.IsEmpty();
857 if (aOffset
== mSelection
.mAnchor
) {
858 NS_WARNING_ASSERTION(!mSelection
.mAnchorCharRects
[eNextCharRect
].IsEmpty(),
860 aTextRect
= mSelection
.mAnchorCharRects
[eNextCharRect
];
861 return !aTextRect
.IsEmpty();
863 if (mSelection
.mAnchor
&& aOffset
== mSelection
.mAnchor
- 1) {
864 NS_WARNING_ASSERTION(!mSelection
.mAnchorCharRects
[ePrevCharRect
].IsEmpty(),
866 aTextRect
= mSelection
.mAnchorCharRects
[ePrevCharRect
];
867 return !aTextRect
.IsEmpty();
869 if (aOffset
== mSelection
.mFocus
) {
870 NS_WARNING_ASSERTION(!mSelection
.mFocusCharRects
[eNextCharRect
].IsEmpty(),
872 aTextRect
= mSelection
.mFocusCharRects
[eNextCharRect
];
873 return !aTextRect
.IsEmpty();
875 if (mSelection
.mFocus
&& aOffset
== mSelection
.mFocus
- 1) {
876 NS_WARNING_ASSERTION(!mSelection
.mFocusCharRects
[ePrevCharRect
].IsEmpty(),
878 aTextRect
= mSelection
.mFocusCharRects
[ePrevCharRect
];
879 return !aTextRect
.IsEmpty();
882 uint32_t offset
= aOffset
;
883 if (!mTextRectArray
.InRange(aOffset
)) {
884 if (!aRoundToExistingOffset
) {
885 aTextRect
.SetEmpty();
888 if (!mTextRectArray
.IsValid()) {
889 // If there are no rects in mTextRectArray, we should refer the start of
890 // the selection because IME must query a char rect around it if there is
892 aTextRect
= mSelection
.StartCharRect();
893 return !aTextRect
.IsEmpty();
895 if (offset
< mTextRectArray
.StartOffset()) {
896 offset
= mTextRectArray
.StartOffset();
898 offset
= mTextRectArray
.EndOffset() - 1;
901 aTextRect
= mTextRectArray
.GetRect(offset
);
902 return !aTextRect
.IsEmpty();
905 bool ContentCacheInParent::GetUnionTextRects(
906 uint32_t aOffset
, uint32_t aLength
, bool aRoundToExistingOffset
,
907 LayoutDeviceIntRect
& aUnionTextRect
) const {
908 MOZ_LOG(sContentCacheLog
, LogLevel::Info
,
909 ("0x%p GetUnionTextRects(aOffset=%u, "
910 "aLength=%u, aRoundToExistingOffset=%s), mTextRectArray={ "
911 "mStart=%u, mRects.Length()=%zu }, "
912 "mSelection={ mAnchor=%u, mFocus=%u }",
913 this, aOffset
, aLength
, GetBoolName(aRoundToExistingOffset
),
914 mTextRectArray
.mStart
, mTextRectArray
.mRects
.Length(),
915 mSelection
.mAnchor
, mSelection
.mFocus
));
917 CheckedInt
<uint32_t> endOffset
= CheckedInt
<uint32_t>(aOffset
) + aLength
;
918 if (!endOffset
.isValid()) {
922 if (!mSelection
.Collapsed() && aOffset
== mSelection
.StartOffset() &&
923 aLength
== mSelection
.Length()) {
924 NS_WARNING_ASSERTION(!mSelection
.mRect
.IsEmpty(), "empty rect");
925 aUnionTextRect
= mSelection
.mRect
;
926 return !aUnionTextRect
.IsEmpty();
931 NS_WARNING_ASSERTION(!mFirstCharRect
.IsEmpty(), "empty rect");
932 aUnionTextRect
= mFirstCharRect
;
933 return !aUnionTextRect
.IsEmpty();
935 if (aOffset
== mSelection
.mAnchor
) {
936 NS_WARNING_ASSERTION(
937 !mSelection
.mAnchorCharRects
[eNextCharRect
].IsEmpty(), "empty rect");
938 aUnionTextRect
= mSelection
.mAnchorCharRects
[eNextCharRect
];
939 return !aUnionTextRect
.IsEmpty();
941 if (mSelection
.mAnchor
&& aOffset
== mSelection
.mAnchor
- 1) {
942 NS_WARNING_ASSERTION(
943 !mSelection
.mAnchorCharRects
[ePrevCharRect
].IsEmpty(), "empty rect");
944 aUnionTextRect
= mSelection
.mAnchorCharRects
[ePrevCharRect
];
945 return !aUnionTextRect
.IsEmpty();
947 if (aOffset
== mSelection
.mFocus
) {
948 NS_WARNING_ASSERTION(!mSelection
.mFocusCharRects
[eNextCharRect
].IsEmpty(),
950 aUnionTextRect
= mSelection
.mFocusCharRects
[eNextCharRect
];
951 return !aUnionTextRect
.IsEmpty();
953 if (mSelection
.mFocus
&& aOffset
== mSelection
.mFocus
- 1) {
954 NS_WARNING_ASSERTION(!mSelection
.mFocusCharRects
[ePrevCharRect
].IsEmpty(),
956 aUnionTextRect
= mSelection
.mFocusCharRects
[ePrevCharRect
];
957 return !aUnionTextRect
.IsEmpty();
961 // Even if some text rects are not cached of the queried range,
962 // we should return union rect when the first character's rect is cached
963 // since the first character rect is important and the others are not so
966 if (!aOffset
&& aOffset
!= mSelection
.mAnchor
&&
967 aOffset
!= mSelection
.mFocus
&& !mTextRectArray
.InRange(aOffset
)) {
968 // The first character rect isn't cached.
972 if ((aRoundToExistingOffset
&& mTextRectArray
.HasRects()) ||
973 mTextRectArray
.IsOverlappingWith(aOffset
, aLength
)) {
974 aUnionTextRect
= mTextRectArray
.GetUnionRectAsFarAsPossible(
975 aOffset
, aLength
, aRoundToExistingOffset
);
977 aUnionTextRect
.SetEmpty();
981 aUnionTextRect
= aUnionTextRect
.Union(mFirstCharRect
);
983 if (aOffset
<= mSelection
.mAnchor
&& mSelection
.mAnchor
< endOffset
.value()) {
985 aUnionTextRect
.Union(mSelection
.mAnchorCharRects
[eNextCharRect
]);
987 if (mSelection
.mAnchor
&& aOffset
<= mSelection
.mAnchor
- 1 &&
988 mSelection
.mAnchor
- 1 < endOffset
.value()) {
990 aUnionTextRect
.Union(mSelection
.mAnchorCharRects
[ePrevCharRect
]);
992 if (aOffset
<= mSelection
.mFocus
&& mSelection
.mFocus
< endOffset
.value()) {
994 aUnionTextRect
.Union(mSelection
.mFocusCharRects
[eNextCharRect
]);
996 if (mSelection
.mFocus
&& aOffset
<= mSelection
.mFocus
- 1 &&
997 mSelection
.mFocus
- 1 < endOffset
.value()) {
999 aUnionTextRect
.Union(mSelection
.mFocusCharRects
[ePrevCharRect
]);
1002 return !aUnionTextRect
.IsEmpty();
1005 bool ContentCacheInParent::GetCaretRect(uint32_t aOffset
,
1006 bool aRoundToExistingOffset
,
1007 LayoutDeviceIntRect
& aCaretRect
) const {
1009 sContentCacheLog
, LogLevel::Info
,
1010 ("0x%p GetCaretRect(aOffset=%u, "
1011 "aRoundToExistingOffset=%s), "
1012 "mCaret={ mOffset=%u, mRect=%s, IsValid()=%s }, mTextRectArray={ "
1013 "mStart=%u, mRects.Length()=%zu }, mSelection={ mAnchor=%u, mFocus=%u, "
1014 "mWritingMode=%s, mAnchorCharRects[eNextCharRect]=%s, "
1015 "mAnchorCharRects[ePrevCharRect]=%s, mFocusCharRects[eNextCharRect]=%s, "
1016 "mFocusCharRects[ePrevCharRect]=%s }, mFirstCharRect=%s",
1017 this, aOffset
, GetBoolName(aRoundToExistingOffset
), mCaret
.mOffset
,
1018 GetRectText(mCaret
.mRect
).get(), GetBoolName(mCaret
.IsValid()),
1019 mTextRectArray
.mStart
, mTextRectArray
.mRects
.Length(),
1020 mSelection
.mAnchor
, mSelection
.mFocus
,
1021 GetWritingModeName(mSelection
.mWritingMode
).get(),
1022 GetRectText(mSelection
.mAnchorCharRects
[eNextCharRect
]).get(),
1023 GetRectText(mSelection
.mAnchorCharRects
[ePrevCharRect
]).get(),
1024 GetRectText(mSelection
.mFocusCharRects
[eNextCharRect
]).get(),
1025 GetRectText(mSelection
.mFocusCharRects
[ePrevCharRect
]).get(),
1026 GetRectText(mFirstCharRect
).get()));
1028 if (mCaret
.IsValid() && mCaret
.mOffset
== aOffset
) {
1029 aCaretRect
= mCaret
.mRect
;
1033 // Guess caret rect from the text rect if it's stored.
1034 if (!GetTextRect(aOffset
, aRoundToExistingOffset
, aCaretRect
)) {
1035 // There might be previous character rect in the cache. If so, we can
1036 // guess the caret rect with it.
1038 !GetTextRect(aOffset
- 1, aRoundToExistingOffset
, aCaretRect
)) {
1039 aCaretRect
.SetEmpty();
1043 if (mSelection
.mWritingMode
.IsVertical()) {
1044 aCaretRect
.MoveToY(aCaretRect
.YMost());
1046 // XXX bidi-unaware.
1047 aCaretRect
.MoveToX(aCaretRect
.XMost());
1051 // XXX This is not bidi aware because we don't cache each character's
1052 // direction. However, this is usually used by IME, so, assuming the
1053 // character is in LRT context must not cause any problem.
1054 if (mSelection
.mWritingMode
.IsVertical()) {
1055 aCaretRect
.SetHeight(mCaret
.IsValid() ? mCaret
.mRect
.Height() : 1);
1057 aCaretRect
.SetWidth(mCaret
.IsValid() ? mCaret
.mRect
.Width() : 1);
1062 bool ContentCacheInParent::OnCompositionEvent(
1063 const WidgetCompositionEvent
& aEvent
) {
1065 sContentCacheLog
, LogLevel::Info
,
1066 ("0x%p OnCompositionEvent(aEvent={ "
1067 "mMessage=%s, mData=\"%s\" (Length()=%u), mRanges->Length()=%zu }), "
1068 "mPendingEventsNeedingAck=%u, mWidgetHasComposition=%s, "
1069 "mPendingCompositionCount=%" PRIu8
", mPendingCommitCount=%" PRIu8
", "
1070 "mIsChildIgnoringCompositionEvents=%s, mCommitStringByRequest=0x%p",
1071 this, ToChar(aEvent
.mMessage
), GetEscapedUTF8String(aEvent
.mData
).get(),
1072 aEvent
.mData
.Length(), aEvent
.mRanges
? aEvent
.mRanges
->Length() : 0,
1073 mPendingEventsNeedingAck
, GetBoolName(mWidgetHasComposition
),
1074 mPendingCompositionCount
, mPendingCommitCount
,
1075 GetBoolName(mIsChildIgnoringCompositionEvents
), mCommitStringByRequest
));
1077 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1078 mDispatchedEventMessages
.AppendElement(aEvent
.mMessage
);
1079 #endif // #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1081 // We must be able to simulate the selection because
1082 // we might not receive selection updates in time
1083 if (!mWidgetHasComposition
) {
1084 if (aEvent
.mWidget
&& aEvent
.mWidget
->PluginHasFocus()) {
1085 // If focus is on plugin, we cannot get selection range
1086 mCompositionStart
= 0;
1087 } else if (mCompositionStartInChild
!= UINT32_MAX
) {
1088 // If there is pending composition in the remote process, let's use
1089 // its start offset temporarily because this stores a lot of information
1090 // around it and the user must look around there, so, showing some UI
1091 // around it must make sense.
1092 mCompositionStart
= mCompositionStartInChild
;
1094 mCompositionStart
= mSelection
.StartOffset();
1096 MOZ_ASSERT(aEvent
.mMessage
== eCompositionStart
);
1097 MOZ_RELEASE_ASSERT(mPendingCompositionCount
< UINT8_MAX
);
1098 mPendingCompositionCount
++;
1101 mWidgetHasComposition
= !aEvent
.CausesDOMCompositionEndEvent();
1103 if (!mWidgetHasComposition
) {
1104 // mCompositionStart will be reset when commit event is completely handled
1105 // in the remote process.
1106 if (mPendingCompositionCount
== 1) {
1107 mPendingCommitLength
= aEvent
.mData
.Length();
1109 mPendingCommitCount
++;
1110 } else if (aEvent
.mMessage
!= eCompositionStart
) {
1111 mCompositionString
= aEvent
.mData
;
1114 // During REQUEST_TO_COMMIT_COMPOSITION or REQUEST_TO_CANCEL_COMPOSITION,
1115 // widget usually sends a eCompositionChange and/or eCompositionCommit event
1116 // to finalize or clear the composition, respectively. In this time,
1117 // we need to intercept all composition events here and pass the commit
1118 // string for returning to the remote process as a result of
1119 // RequestIMEToCommitComposition(). Then, eCommitComposition event will
1120 // be dispatched with the committed string in the remote process internally.
1121 if (mCommitStringByRequest
) {
1122 if (aEvent
.mMessage
== eCompositionCommitAsIs
) {
1123 *mCommitStringByRequest
= mCompositionString
;
1125 MOZ_ASSERT(aEvent
.mMessage
== eCompositionChange
||
1126 aEvent
.mMessage
== eCompositionCommit
);
1127 *mCommitStringByRequest
= aEvent
.mData
;
1129 // We need to wait eCompositionCommitRequestHandled from the remote process
1130 // in this case. Therefore, mPendingEventsNeedingAck needs to be
1131 // incremented here. Additionally, we stop sending eCompositionCommit(AsIs)
1132 // event. Therefore, we need to decrement mPendingCommitCount which has
1133 // been incremented above.
1134 if (!mWidgetHasComposition
) {
1135 mPendingEventsNeedingAck
++;
1136 MOZ_DIAGNOSTIC_ASSERT(mPendingCommitCount
);
1137 if (mPendingCommitCount
) {
1138 mPendingCommitCount
--;
1144 mPendingEventsNeedingAck
++;
1148 void ContentCacheInParent::OnSelectionEvent(
1149 const WidgetSelectionEvent
& aSelectionEvent
) {
1151 sContentCacheLog
, LogLevel::Info
,
1152 ("0x%p OnSelectionEvent(aEvent={ "
1153 "mMessage=%s, mOffset=%u, mLength=%u, mReversed=%s, "
1154 "mExpandToClusterBoundary=%s, mUseNativeLineBreak=%s }), "
1155 "mPendingEventsNeedingAck=%u, mWidgetHasComposition=%s, "
1156 "mPendingCompositionCount=%" PRIu8
", mPendingCommitCount=%" PRIu8
", "
1157 "mIsChildIgnoringCompositionEvents=%s",
1158 this, ToChar(aSelectionEvent
.mMessage
), aSelectionEvent
.mOffset
,
1159 aSelectionEvent
.mLength
, GetBoolName(aSelectionEvent
.mReversed
),
1160 GetBoolName(aSelectionEvent
.mExpandToClusterBoundary
),
1161 GetBoolName(aSelectionEvent
.mUseNativeLineBreak
),
1162 mPendingEventsNeedingAck
, GetBoolName(mWidgetHasComposition
),
1163 mPendingCompositionCount
, mPendingCommitCount
,
1164 GetBoolName(mIsChildIgnoringCompositionEvents
)));
1166 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1167 mDispatchedEventMessages
.AppendElement(aSelectionEvent
.mMessage
);
1168 #endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
1170 mPendingEventsNeedingAck
++;
1173 void ContentCacheInParent::OnEventNeedingAckHandled(nsIWidget
* aWidget
,
1174 EventMessage aMessage
) {
1175 // This is called when the child process receives WidgetCompositionEvent or
1176 // WidgetSelectionEvent.
1179 sContentCacheLog
, LogLevel::Info
,
1180 ("0x%p OnEventNeedingAckHandled(aWidget=0x%p, "
1181 "aMessage=%s), mPendingEventsNeedingAck=%u, "
1182 "mWidgetHasComposition=%s, mPendingCompositionCount=%" PRIu8
", "
1183 "mPendingCommitCount=%" PRIu8
", mIsChildIgnoringCompositionEvents=%s",
1184 this, aWidget
, ToChar(aMessage
), mPendingEventsNeedingAck
,
1185 GetBoolName(mWidgetHasComposition
), mPendingCompositionCount
,
1186 mPendingCommitCount
, GetBoolName(mIsChildIgnoringCompositionEvents
)));
1188 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1189 mReceivedEventMessages
.AppendElement(aMessage
);
1190 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1192 bool isCommittedInChild
=
1193 // Commit requester in the remote process has committed the composition.
1194 aMessage
== eCompositionCommitRequestHandled
||
1195 // The commit event has been handled normally in the remote process.
1196 (!mIsChildIgnoringCompositionEvents
&&
1197 WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage
));
1199 if (isCommittedInChild
) {
1200 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1201 if (mPendingCompositionCount
== 1) {
1202 RemoveUnnecessaryEventMessageLog();
1204 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1206 if (NS_WARN_IF(!mPendingCompositionCount
)) {
1207 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1208 nsPrintfCString
info(
1209 "\nThere is no pending composition but received %s "
1210 "message from the remote child\n\n",
1212 AppendEventMessageLog(info
);
1213 CrashReporter::AppendAppNotesToCrashReport(info
);
1214 MOZ_DIAGNOSTIC_ASSERT(
1215 false, "No pending composition but received unexpected commit event");
1216 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1218 // Prevent odd behavior in release channel.
1219 mPendingCompositionCount
= 1;
1222 mPendingCompositionCount
--;
1224 // Forget composition string only when the latest composition string is
1225 // handled in the remote process because if there is 2 or more pending
1226 // composition, this value shouldn't be referred.
1227 if (!mPendingCompositionCount
) {
1228 mCompositionString
.Truncate();
1231 // Forget pending commit string length if it's handled in the remote
1232 // process. Note that this doesn't care too old composition's commit
1233 // string because in such case, we cannot return proper information
1234 // to IME synchornously.
1235 mPendingCommitLength
= 0;
1238 if (WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage
)) {
1239 // After the remote process receives eCompositionCommit(AsIs) event,
1240 // it'll restart to handle composition events.
1241 mIsChildIgnoringCompositionEvents
= false;
1243 if (NS_WARN_IF(!mPendingCommitCount
)) {
1244 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1245 nsPrintfCString
info(
1246 "\nThere is no pending comment events but received "
1247 "%s message from the remote child\n\n",
1249 AppendEventMessageLog(info
);
1250 CrashReporter::AppendAppNotesToCrashReport(info
);
1251 MOZ_DIAGNOSTIC_ASSERT(
1253 "No pending commit events but received unexpected commit event");
1254 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1256 // Prevent odd behavior in release channel.
1257 mPendingCommitCount
= 1;
1260 mPendingCommitCount
--;
1261 } else if (aMessage
== eCompositionCommitRequestHandled
&&
1262 mPendingCommitCount
) {
1263 // If the remote process commits composition synchronously after
1264 // requesting commit composition and we've already sent commit composition,
1265 // it starts to ignore following composition events until receiving
1266 // eCompositionStart event.
1267 mIsChildIgnoringCompositionEvents
= true;
1270 // If neither widget (i.e., IME) nor the remote process has composition,
1271 // now, we can forget composition string informations.
1272 if (!mWidgetHasComposition
&& !mPendingCompositionCount
&&
1273 !mPendingCommitCount
) {
1274 mCompositionStart
= UINT32_MAX
;
1277 if (NS_WARN_IF(!mPendingEventsNeedingAck
)) {
1278 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1279 nsPrintfCString
info(
1280 "\nThere is no pending events but received %s "
1281 "message from the remote child\n\n",
1283 AppendEventMessageLog(info
);
1284 CrashReporter::AppendAppNotesToCrashReport(info
);
1285 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1286 MOZ_DIAGNOSTIC_ASSERT(
1287 false, "No pending event message but received unexpected event");
1288 mPendingEventsNeedingAck
= 1;
1290 if (--mPendingEventsNeedingAck
) {
1294 FlushPendingNotifications(aWidget
);
1297 bool ContentCacheInParent::RequestIMEToCommitComposition(
1298 nsIWidget
* aWidget
, bool aCancel
, nsAString
& aCommittedString
) {
1300 sContentCacheLog
, LogLevel::Info
,
1301 ("0x%p RequestToCommitComposition(aWidget=%p, "
1302 "aCancel=%s), mPendingCompositionCount=%" PRIu8
", "
1303 "mPendingCommitCount=%" PRIu8
", mIsChildIgnoringCompositionEvents=%s, "
1304 "IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)=%s, "
1305 "mWidgetHasComposition=%s, mCommitStringByRequest=%p",
1306 this, aWidget
, GetBoolName(aCancel
), mPendingCompositionCount
,
1307 mPendingCommitCount
, GetBoolName(mIsChildIgnoringCompositionEvents
),
1309 IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent
)),
1310 GetBoolName(mWidgetHasComposition
), mCommitStringByRequest
));
1312 MOZ_ASSERT(!mCommitStringByRequest
);
1314 // If there are 2 or more pending compositions, we already sent
1315 // eCompositionCommit(AsIs) to the remote process. So, this request is
1316 // too late for IME. The remote process should wait following
1317 // composition events for cleaning up TextComposition and handle the
1318 // request as it's handled asynchronously.
1319 if (mPendingCompositionCount
> 1) {
1320 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1321 mRequestIMEToCommitCompositionResults
.AppendElement(
1322 RequestIMEToCommitCompositionResult::eToOldCompositionReceived
);
1323 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1327 // If there is no pending composition, we may have already sent
1328 // eCompositionCommit(AsIs) event for the active composition. If so, the
1329 // remote process will receive composition events which causes cleaning up
1330 // TextComposition. So, this shouldn't do nothing and TextComposition
1331 // should handle the request as it's handled asynchronously.
1332 // XXX Perhaps, this is wrong because TextComposition in child process
1333 // may commit the composition with current composition string in the
1334 // remote process. I.e., it may be different from actual commit string
1335 // which user typed. So, perhaps, we should return true and the commit
1337 if (mPendingCommitCount
) {
1338 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1339 mRequestIMEToCommitCompositionResults
.AppendElement(
1340 RequestIMEToCommitCompositionResult::eToCommittedCompositionReceived
);
1341 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1345 // If BrowserParent which has IME focus was already changed to different one,
1346 // the request shouldn't be sent to IME because it's too late.
1347 if (!IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent
)) {
1348 // Use the latest composition string which may not be handled in the
1349 // remote process for avoiding data loss.
1350 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1351 mRequestIMEToCommitCompositionResults
.AppendElement(
1352 RequestIMEToCommitCompositionResult::eReceivedAfterBrowserParentBlur
);
1353 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1354 aCommittedString
= mCompositionString
;
1355 // After we return true from here, i.e., without actually requesting IME
1356 // to commit composition, we will receive eCompositionCommitRequestHandled
1357 // pseudo event message from the remote process. So, we need to increment
1358 // mPendingEventsNeedingAck here.
1359 mPendingEventsNeedingAck
++;
1363 RefPtr
<TextComposition
> composition
=
1364 IMEStateManager::GetTextCompositionFor(aWidget
);
1365 if (NS_WARN_IF(!composition
)) {
1366 MOZ_LOG(sContentCacheLog
, LogLevel::Warning
,
1367 (" 0x%p RequestToCommitComposition(), "
1368 "does nothing due to no composition",
1370 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1371 mRequestIMEToCommitCompositionResults
.AppendElement(
1372 RequestIMEToCommitCompositionResult::eReceivedButNoTextComposition
);
1373 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1377 mCommitStringByRequest
= &aCommittedString
;
1379 // Request commit or cancel composition with TextComposition because we may
1380 // have already requested to commit or cancel the composition or we may
1381 // have already received eCompositionCommit(AsIs) event. Those status are
1382 // managed by composition. So, if we don't request commit composition,
1383 // we should do nothing with native IME here.
1384 composition
->RequestToCommit(aWidget
, aCancel
);
1386 mCommitStringByRequest
= nullptr;
1389 sContentCacheLog
, LogLevel::Info
,
1390 (" 0x%p RequestToCommitComposition(), "
1391 "mWidgetHasComposition=%s, the composition %s committed synchronously",
1392 this, GetBoolName(mWidgetHasComposition
),
1393 composition
->Destroyed() ? "WAS" : "has NOT been"));
1395 if (!composition
->Destroyed()) {
1396 // When the composition isn't committed synchronously, the remote process's
1397 // TextComposition instance will synthesize commit events and wait to
1398 // receive delayed composition events. When TextComposition instances both
1399 // in this process and the remote process will be destroyed when delayed
1400 // composition events received. TextComposition instance in the parent
1401 // process will dispatch following composition events and be destroyed
1402 // normally. On the other hand, TextComposition instance in the remote
1403 // process won't dispatch following composition events and will be
1404 // destroyed by IMEStateManager::DispatchCompositionEvent().
1405 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1406 mRequestIMEToCommitCompositionResults
.AppendElement(
1407 RequestIMEToCommitCompositionResult::eHandledAsynchronously
);
1408 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1412 // When the composition is committed synchronously, the commit string will be
1413 // returned to the remote process. Then, PuppetWidget will dispatch
1414 // eCompositionCommit event with the returned commit string (i.e., the value
1415 // is aCommittedString of this method) and that causes destroying
1416 // TextComposition instance in the remote process (Note that TextComposition
1417 // instance in this process was already destroyed).
1418 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1419 mRequestIMEToCommitCompositionResults
.AppendElement(
1420 RequestIMEToCommitCompositionResult::eHandledSynchronously
);
1421 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1425 void ContentCacheInParent::MaybeNotifyIME(
1426 nsIWidget
* aWidget
, const IMENotification
& aNotification
) {
1427 if (!mPendingEventsNeedingAck
) {
1428 IMEStateManager::NotifyIME(aNotification
, aWidget
, &mBrowserParent
);
1432 switch (aNotification
.mMessage
) {
1433 case NOTIFY_IME_OF_SELECTION_CHANGE
:
1434 mPendingSelectionChange
.MergeWith(aNotification
);
1436 case NOTIFY_IME_OF_TEXT_CHANGE
:
1437 mPendingTextChange
.MergeWith(aNotification
);
1439 case NOTIFY_IME_OF_POSITION_CHANGE
:
1440 mPendingLayoutChange
.MergeWith(aNotification
);
1442 case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED
:
1443 mPendingCompositionUpdate
.MergeWith(aNotification
);
1446 MOZ_CRASH("Unsupported notification");
1451 void ContentCacheInParent::FlushPendingNotifications(nsIWidget
* aWidget
) {
1452 MOZ_ASSERT(!mPendingEventsNeedingAck
);
1454 // If the BrowserParent's widget has already gone, this can do nothing since
1455 // widget is necessary to notify IME of something.
1460 // New notifications which are notified during flushing pending notifications
1461 // should be merged again.
1462 mPendingEventsNeedingAck
++;
1464 nsCOMPtr
<nsIWidget
> widget
= aWidget
;
1466 // First, text change notification should be sent because selection change
1467 // notification notifies IME of current selection range in the latest content.
1468 // So, IME may need the latest content before that.
1469 if (mPendingTextChange
.HasNotification()) {
1470 IMENotification
notification(mPendingTextChange
);
1471 if (!widget
->Destroyed()) {
1472 mPendingTextChange
.Clear();
1473 IMEStateManager::NotifyIME(notification
, widget
, &mBrowserParent
);
1477 if (mPendingSelectionChange
.HasNotification()) {
1478 IMENotification
notification(mPendingSelectionChange
);
1479 if (!widget
->Destroyed()) {
1480 mPendingSelectionChange
.Clear();
1481 IMEStateManager::NotifyIME(notification
, widget
, &mBrowserParent
);
1485 // Layout change notification should be notified after selection change
1486 // notification because IME may want to query position of new caret position.
1487 if (mPendingLayoutChange
.HasNotification()) {
1488 IMENotification
notification(mPendingLayoutChange
);
1489 if (!widget
->Destroyed()) {
1490 mPendingLayoutChange
.Clear();
1491 IMEStateManager::NotifyIME(notification
, widget
, &mBrowserParent
);
1495 // Finally, send composition update notification because it notifies IME of
1496 // finishing handling whole sending events.
1497 if (mPendingCompositionUpdate
.HasNotification()) {
1498 IMENotification
notification(mPendingCompositionUpdate
);
1499 if (!widget
->Destroyed()) {
1500 mPendingCompositionUpdate
.Clear();
1501 IMEStateManager::NotifyIME(notification
, widget
, &mBrowserParent
);
1505 if (!--mPendingEventsNeedingAck
&& !widget
->Destroyed() &&
1506 (mPendingTextChange
.HasNotification() ||
1507 mPendingSelectionChange
.HasNotification() ||
1508 mPendingLayoutChange
.HasNotification() ||
1509 mPendingCompositionUpdate
.HasNotification())) {
1510 FlushPendingNotifications(widget
);
1514 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1516 void ContentCacheInParent::RemoveUnnecessaryEventMessageLog() {
1517 bool foundLastCompositionStart
= false;
1518 for (size_t i
= mDispatchedEventMessages
.Length(); i
> 1; i
--) {
1519 if (mDispatchedEventMessages
[i
- 1] != eCompositionStart
) {
1522 if (!foundLastCompositionStart
) {
1523 // Find previous eCompositionStart of the latest eCompositionStart.
1524 foundLastCompositionStart
= true;
1527 // Remove the messages before the last 2 sets of composition events.
1528 mDispatchedEventMessages
.RemoveElementsAt(0, i
- 1);
1531 uint32_t numberOfCompositionCommitRequestHandled
= 0;
1532 foundLastCompositionStart
= false;
1533 for (size_t i
= mReceivedEventMessages
.Length(); i
> 1; i
--) {
1534 if (mReceivedEventMessages
[i
- 1] == eCompositionCommitRequestHandled
) {
1535 numberOfCompositionCommitRequestHandled
++;
1537 if (mReceivedEventMessages
[i
- 1] != eCompositionStart
) {
1540 if (!foundLastCompositionStart
) {
1541 // Find previous eCompositionStart of the latest eCompositionStart.
1542 foundLastCompositionStart
= true;
1545 // Remove the messages before the last 2 sets of composition events.
1546 mReceivedEventMessages
.RemoveElementsAt(0, i
- 1);
1550 if (!numberOfCompositionCommitRequestHandled
) {
1551 // If there is no eCompositionCommitRequestHandled in
1552 // mReceivedEventMessages, we don't need to store log of
1553 // RequestIMEToCommmitComposition().
1554 mRequestIMEToCommitCompositionResults
.Clear();
1556 // We need to keep all reason of eCompositionCommitRequestHandled, which
1557 // is sent when mRequestIMEToCommitComposition() returns true.
1558 // So, we can discard older log than the first
1559 // eCompositionCommitRequestHandled in mReceivedEventMessages.
1560 for (size_t i
= mRequestIMEToCommitCompositionResults
.Length(); i
> 1;
1562 if (mRequestIMEToCommitCompositionResults
[i
- 1] ==
1563 RequestIMEToCommitCompositionResult::
1564 eReceivedAfterBrowserParentBlur
||
1565 mRequestIMEToCommitCompositionResults
[i
- 1] ==
1566 RequestIMEToCommitCompositionResult::eHandledSynchronously
) {
1567 --numberOfCompositionCommitRequestHandled
;
1568 if (!numberOfCompositionCommitRequestHandled
) {
1569 mRequestIMEToCommitCompositionResults
.RemoveElementsAt(0, i
- 1);
1577 void ContentCacheInParent::AppendEventMessageLog(nsACString
& aLog
) const {
1578 aLog
.AppendLiteral("Dispatched Event Message Log:\n");
1579 for (EventMessage message
: mDispatchedEventMessages
) {
1580 aLog
.AppendLiteral(" ");
1581 aLog
.Append(ToChar(message
));
1582 aLog
.AppendLiteral("\n");
1584 aLog
.AppendLiteral("\nReceived Event Message Log:\n");
1585 for (EventMessage message
: mReceivedEventMessages
) {
1586 aLog
.AppendLiteral(" ");
1587 aLog
.Append(ToChar(message
));
1588 aLog
.AppendLiteral("\n");
1590 aLog
.AppendLiteral("\nResult of RequestIMEToCommitComposition():\n");
1591 for (RequestIMEToCommitCompositionResult result
:
1592 mRequestIMEToCommitCompositionResults
) {
1593 aLog
.AppendLiteral(" ");
1594 aLog
.Append(ToReadableText(result
));
1595 aLog
.AppendLiteral("\n");
1597 aLog
.AppendLiteral("\n");
1600 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1602 /*****************************************************************************
1603 * mozilla::ContentCache::TextRectArray
1604 *****************************************************************************/
1606 LayoutDeviceIntRect
ContentCache::TextRectArray::GetRect(
1607 uint32_t aOffset
) const {
1608 LayoutDeviceIntRect rect
;
1609 if (InRange(aOffset
)) {
1610 rect
= mRects
[aOffset
- mStart
];
1615 LayoutDeviceIntRect
ContentCache::TextRectArray::GetUnionRect(
1616 uint32_t aOffset
, uint32_t aLength
) const {
1617 LayoutDeviceIntRect rect
;
1618 if (!InRange(aOffset
, aLength
)) {
1621 for (uint32_t i
= 0; i
< aLength
; i
++) {
1622 rect
= rect
.Union(mRects
[aOffset
- mStart
+ i
]);
1627 LayoutDeviceIntRect
ContentCache::TextRectArray::GetUnionRectAsFarAsPossible(
1628 uint32_t aOffset
, uint32_t aLength
, bool aRoundToExistingOffset
) const {
1629 LayoutDeviceIntRect rect
;
1631 (!aRoundToExistingOffset
&& !IsOverlappingWith(aOffset
, aLength
))) {
1634 uint32_t startOffset
= std::max(aOffset
, mStart
);
1635 if (aRoundToExistingOffset
&& startOffset
>= EndOffset()) {
1636 startOffset
= EndOffset() - 1;
1638 uint32_t endOffset
= std::min(aOffset
+ aLength
, EndOffset());
1639 if (aRoundToExistingOffset
&& endOffset
< mStart
+ 1) {
1640 endOffset
= mStart
+ 1;
1642 if (NS_WARN_IF(endOffset
< startOffset
)) {
1645 for (uint32_t i
= 0; i
< endOffset
- startOffset
; i
++) {
1646 rect
= rect
.Union(mRects
[startOffset
- mStart
+ i
]);
1651 } // namespace mozilla