Bug 1625482 [wpt PR 22496] - [ScrollTimeline] Do not show scrollbar to bypass flakine...
[gecko.git] / widget / ContentCache.cpp
blobe20d0babf16869bfbd562c129485edadcc7bc7fb
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 "mozilla/ContentCache.h"
10 #include <utility>
12 #include "mozilla/IMEStateManager.h"
13 #include "mozilla/IntegerPrintfMacros.h"
14 #include "mozilla/Logging.h"
15 #include "mozilla/RefPtr.h"
16 #include "mozilla/TextComposition.h"
17 #include "mozilla/TextEvents.h"
18 #include "mozilla/dom/BrowserParent.h"
19 #include "nsExceptionHandler.h"
20 #include "nsIWidget.h"
22 namespace mozilla {
24 using namespace dom;
25 using namespace widget;
27 static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; }
29 static const char* GetNotificationName(const IMENotification* aNotification) {
30 if (!aNotification) {
31 return "Not notification";
33 return ToChar(aNotification->mMessage);
36 class GetRectText : public nsAutoCString {
37 public:
38 explicit GetRectText(const LayoutDeviceIntRect& aRect) {
39 AssignLiteral("{ x=");
40 AppendInt(aRect.X());
41 AppendLiteral(", y=");
42 AppendInt(aRect.Y());
43 AppendLiteral(", width=");
44 AppendInt(aRect.Width());
45 AppendLiteral(", height=");
46 AppendInt(aRect.Height());
47 AppendLiteral(" }");
49 virtual ~GetRectText() = default;
52 class GetWritingModeName : public nsAutoCString {
53 public:
54 explicit GetWritingModeName(const WritingMode& aWritingMode) {
55 if (!aWritingMode.IsVertical()) {
56 AssignLiteral("Horizontal");
57 return;
59 if (aWritingMode.IsVerticalLR()) {
60 AssignLiteral("Vertical (LTR)");
61 return;
63 AssignLiteral("Vertical (RTL)");
65 virtual ~GetWritingModeName() = default;
68 class GetEscapedUTF8String final : public NS_ConvertUTF16toUTF8 {
69 public:
70 explicit GetEscapedUTF8String(const nsAString& aString)
71 : NS_ConvertUTF16toUTF8(aString) {
72 Escape();
74 explicit GetEscapedUTF8String(const char16ptr_t aString)
75 : NS_ConvertUTF16toUTF8(aString) {
76 Escape();
78 GetEscapedUTF8String(const char16ptr_t aString, uint32_t aLength)
79 : NS_ConvertUTF16toUTF8(aString, aLength) {
80 Escape();
83 private:
84 void Escape() {
85 ReplaceSubstring("\r", "\\r");
86 ReplaceSubstring("\n", "\\n");
87 ReplaceSubstring("\t", "\\t");
91 /*****************************************************************************
92 * mozilla::ContentCache
93 *****************************************************************************/
95 LazyLogModule sContentCacheLog("ContentCacheWidgets");
97 ContentCache::ContentCache() : mCompositionStart(UINT32_MAX) {}
99 /*****************************************************************************
100 * mozilla::ContentCacheInChild
101 *****************************************************************************/
103 ContentCacheInChild::ContentCacheInChild() : ContentCache() {}
105 void ContentCacheInChild::Clear() {
106 MOZ_LOG(sContentCacheLog, LogLevel::Info, ("0x%p Clear()", this));
108 mCompositionStart = UINT32_MAX;
109 mText.Truncate();
110 mSelection.Clear();
111 mFirstCharRect.SetEmpty();
112 mCaret.Clear();
113 mTextRectArray.Clear();
114 mEditorRect.SetEmpty();
117 bool ContentCacheInChild::CacheAll(nsIWidget* aWidget,
118 const IMENotification* aNotification) {
119 MOZ_LOG(sContentCacheLog, LogLevel::Info,
120 ("0x%p CacheAll(aWidget=0x%p, aNotification=%s)", this, aWidget,
121 GetNotificationName(aNotification)));
123 if (NS_WARN_IF(!CacheText(aWidget, aNotification)) ||
124 NS_WARN_IF(!CacheEditorRect(aWidget, aNotification))) {
125 return false;
127 return true;
130 bool ContentCacheInChild::CacheSelection(nsIWidget* aWidget,
131 const IMENotification* aNotification) {
132 MOZ_LOG(sContentCacheLog, LogLevel::Info,
133 ("0x%p CacheSelection(aWidget=0x%p, aNotification=%s)", this, aWidget,
134 GetNotificationName(aNotification)));
136 mCaret.Clear();
137 mSelection.Clear();
139 nsEventStatus status = nsEventStatus_eIgnore;
140 WidgetQueryContentEvent selection(true, eQuerySelectedText, aWidget);
141 aWidget->DispatchEvent(&selection, status);
142 if (NS_WARN_IF(!selection.mSucceeded)) {
143 MOZ_LOG(sContentCacheLog, LogLevel::Error,
144 ("0x%p CacheSelection(), FAILED, "
145 "couldn't retrieve the selected text",
146 this));
147 return false;
149 if (selection.mReply.mReversed) {
150 mSelection.mAnchor =
151 selection.mReply.mOffset + selection.mReply.mString.Length();
152 mSelection.mFocus = selection.mReply.mOffset;
153 } else {
154 mSelection.mAnchor = selection.mReply.mOffset;
155 mSelection.mFocus =
156 selection.mReply.mOffset + selection.mReply.mString.Length();
158 mSelection.mWritingMode = selection.GetWritingMode();
160 return CacheCaret(aWidget, aNotification) &&
161 CacheTextRects(aWidget, aNotification);
164 bool ContentCacheInChild::CacheCaret(nsIWidget* aWidget,
165 const IMENotification* aNotification) {
166 MOZ_LOG(sContentCacheLog, LogLevel::Info,
167 ("0x%p CacheCaret(aWidget=0x%p, aNotification=%s)", this, aWidget,
168 GetNotificationName(aNotification)));
170 mCaret.Clear();
172 if (NS_WARN_IF(!mSelection.IsValid())) {
173 return false;
176 // XXX Should be mSelection.mFocus?
177 mCaret.mOffset = mSelection.StartOffset();
179 nsEventStatus status = nsEventStatus_eIgnore;
180 WidgetQueryContentEvent caretRect(true, eQueryCaretRect, aWidget);
181 caretRect.InitForQueryCaretRect(mCaret.mOffset);
182 aWidget->DispatchEvent(&caretRect, status);
183 if (NS_WARN_IF(!caretRect.mSucceeded)) {
184 MOZ_LOG(sContentCacheLog, LogLevel::Error,
185 ("0x%p CacheCaret(), FAILED, "
186 "couldn't retrieve the caret rect at offset=%u",
187 this, mCaret.mOffset));
188 mCaret.Clear();
189 return false;
191 mCaret.mRect = caretRect.mReply.mRect;
192 MOZ_LOG(sContentCacheLog, LogLevel::Info,
193 ("0x%p CacheCaret(), Succeeded, "
194 "mSelection={ mAnchor=%u, mFocus=%u, mWritingMode=%s }, "
195 "mCaret={ mOffset=%u, mRect=%s }",
196 this, mSelection.mAnchor, mSelection.mFocus,
197 GetWritingModeName(mSelection.mWritingMode).get(), mCaret.mOffset,
198 GetRectText(mCaret.mRect).get()));
199 return true;
202 bool ContentCacheInChild::CacheEditorRect(
203 nsIWidget* aWidget, const IMENotification* aNotification) {
204 MOZ_LOG(sContentCacheLog, LogLevel::Info,
205 ("0x%p CacheEditorRect(aWidget=0x%p, aNotification=%s)", this,
206 aWidget, GetNotificationName(aNotification)));
208 nsEventStatus status = nsEventStatus_eIgnore;
209 WidgetQueryContentEvent editorRectEvent(true, eQueryEditorRect, aWidget);
210 aWidget->DispatchEvent(&editorRectEvent, status);
211 if (NS_WARN_IF(!editorRectEvent.mSucceeded)) {
212 MOZ_LOG(sContentCacheLog, LogLevel::Error,
213 ("0x%p CacheEditorRect(), FAILED, "
214 "couldn't retrieve the editor rect",
215 this));
216 return false;
218 mEditorRect = editorRectEvent.mReply.mRect;
219 MOZ_LOG(sContentCacheLog, LogLevel::Info,
220 ("0x%p CacheEditorRect(), Succeeded, "
221 "mEditorRect=%s",
222 this, GetRectText(mEditorRect).get()));
223 return true;
226 bool ContentCacheInChild::CacheText(nsIWidget* aWidget,
227 const IMENotification* aNotification) {
228 MOZ_LOG(sContentCacheLog, LogLevel::Info,
229 ("0x%p CacheText(aWidget=0x%p, aNotification=%s)", this, aWidget,
230 GetNotificationName(aNotification)));
232 nsEventStatus status = nsEventStatus_eIgnore;
233 WidgetQueryContentEvent queryText(true, eQueryTextContent, aWidget);
234 queryText.InitForQueryTextContent(0, UINT32_MAX);
235 aWidget->DispatchEvent(&queryText, status);
236 if (NS_WARN_IF(!queryText.mSucceeded)) {
237 MOZ_LOG(sContentCacheLog, LogLevel::Error,
238 ("0x%p CacheText(), FAILED, couldn't retrieve whole text", this));
239 mText.Truncate();
240 return false;
242 mText = queryText.mReply.mString;
243 MOZ_LOG(
244 sContentCacheLog, LogLevel::Info,
245 ("0x%p CacheText(), Succeeded, mText.Length()=%u", this, mText.Length()));
247 return CacheSelection(aWidget, aNotification);
250 bool ContentCacheInChild::QueryCharRect(nsIWidget* aWidget, uint32_t aOffset,
251 LayoutDeviceIntRect& aCharRect) const {
252 aCharRect.SetEmpty();
254 nsEventStatus status = nsEventStatus_eIgnore;
255 WidgetQueryContentEvent textRect(true, eQueryTextRect, aWidget);
256 textRect.InitForQueryTextRect(aOffset, 1);
257 aWidget->DispatchEvent(&textRect, status);
258 if (NS_WARN_IF(!textRect.mSucceeded)) {
259 return false;
261 aCharRect = textRect.mReply.mRect;
263 // Guarantee the rect is not empty.
264 if (NS_WARN_IF(!aCharRect.Height())) {
265 aCharRect.SetHeight(1);
267 if (NS_WARN_IF(!aCharRect.Width())) {
268 aCharRect.SetWidth(1);
270 return true;
273 bool ContentCacheInChild::QueryCharRectArray(nsIWidget* aWidget,
274 uint32_t aOffset, uint32_t aLength,
275 RectArray& aCharRectArray) const {
276 nsEventStatus status = nsEventStatus_eIgnore;
277 WidgetQueryContentEvent textRects(true, eQueryTextRectArray, aWidget);
278 textRects.InitForQueryTextRectArray(aOffset, aLength);
279 aWidget->DispatchEvent(&textRects, status);
280 if (NS_WARN_IF(!textRects.mSucceeded)) {
281 aCharRectArray.Clear();
282 return false;
284 aCharRectArray = std::move(textRects.mReply.mRectArray);
285 return true;
288 bool ContentCacheInChild::CacheTextRects(nsIWidget* aWidget,
289 const IMENotification* aNotification) {
290 MOZ_LOG(sContentCacheLog, LogLevel::Info,
291 ("0x%p CacheTextRects(aWidget=0x%p, aNotification=%s), "
292 "mCaret={ mOffset=%u, IsValid()=%s }",
293 this, aWidget, GetNotificationName(aNotification), mCaret.mOffset,
294 GetBoolName(mCaret.IsValid())));
296 mCompositionStart = UINT32_MAX;
297 mTextRectArray.Clear();
298 mSelection.ClearAnchorCharRects();
299 mSelection.ClearFocusCharRects();
300 mSelection.mRect.SetEmpty();
301 mFirstCharRect.SetEmpty();
303 if (NS_WARN_IF(!mSelection.IsValid())) {
304 return false;
307 // Retrieve text rects in composition string if there is.
308 RefPtr<TextComposition> textComposition =
309 IMEStateManager::GetTextCompositionFor(aWidget);
310 if (textComposition) {
311 // mCompositionStart may be updated by some composition event handlers.
312 // So, let's update it with the latest information.
313 mCompositionStart = textComposition->NativeOffsetOfStartComposition();
314 // Note that TextComposition::String() may not be modified here because
315 // it's modified after all edit action listeners are performed but this
316 // is called while some of them are performed.
317 // FYI: For supporting IME which commits composition and restart new
318 // composition immediately, we should cache next character of current
319 // composition too.
320 uint32_t length = textComposition->LastData().Length() + 1;
321 mTextRectArray.mStart = mCompositionStart;
322 if (NS_WARN_IF(!QueryCharRectArray(aWidget, mTextRectArray.mStart, length,
323 mTextRectArray.mRects))) {
324 MOZ_LOG(sContentCacheLog, LogLevel::Error,
325 ("0x%p CacheTextRects(), FAILED, "
326 "couldn't retrieve text rect array of the composition string",
327 this));
331 if (mTextRectArray.InRange(mSelection.mAnchor) &&
332 (!mSelection.mAnchor || mTextRectArray.InRange(mSelection.mAnchor - 1))) {
333 mSelection.mAnchorCharRects[eNextCharRect] =
334 mTextRectArray.GetRect(mSelection.mAnchor);
335 if (mSelection.mAnchor) {
336 mSelection.mAnchorCharRects[ePrevCharRect] =
337 mTextRectArray.GetRect(mSelection.mAnchor - 1);
339 } else {
340 RectArray rects;
341 uint32_t startOffset = mSelection.mAnchor ? mSelection.mAnchor - 1 : 0;
342 uint32_t length = mSelection.mAnchor ? 2 : 1;
343 if (NS_WARN_IF(!QueryCharRectArray(aWidget, startOffset, length, rects))) {
344 MOZ_LOG(
345 sContentCacheLog, LogLevel::Error,
346 ("0x%p CacheTextRects(), FAILED, "
347 "couldn't retrieve text rect array around the selection anchor (%u)",
348 this, mSelection.mAnchor));
349 MOZ_ASSERT(mSelection.mAnchorCharRects[ePrevCharRect].IsEmpty());
350 MOZ_ASSERT(mSelection.mAnchorCharRects[eNextCharRect].IsEmpty());
351 } else {
352 if (rects.Length() > 1) {
353 mSelection.mAnchorCharRects[ePrevCharRect] = rects[0];
354 mSelection.mAnchorCharRects[eNextCharRect] = rects[1];
355 } else if (rects.Length()) {
356 mSelection.mAnchorCharRects[eNextCharRect] = rects[0];
357 MOZ_ASSERT(mSelection.mAnchorCharRects[ePrevCharRect].IsEmpty());
362 if (mSelection.Collapsed()) {
363 mSelection.mFocusCharRects[0] = mSelection.mAnchorCharRects[0];
364 mSelection.mFocusCharRects[1] = mSelection.mAnchorCharRects[1];
365 } else if (mTextRectArray.InRange(mSelection.mFocus) &&
366 (!mSelection.mFocus ||
367 mTextRectArray.InRange(mSelection.mFocus - 1))) {
368 mSelection.mFocusCharRects[eNextCharRect] =
369 mTextRectArray.GetRect(mSelection.mFocus);
370 if (mSelection.mFocus) {
371 mSelection.mFocusCharRects[ePrevCharRect] =
372 mTextRectArray.GetRect(mSelection.mFocus - 1);
374 } else {
375 RectArray rects;
376 uint32_t startOffset = mSelection.mFocus ? mSelection.mFocus - 1 : 0;
377 uint32_t length = mSelection.mFocus ? 2 : 1;
378 if (NS_WARN_IF(!QueryCharRectArray(aWidget, startOffset, length, rects))) {
379 MOZ_LOG(
380 sContentCacheLog, LogLevel::Error,
381 ("0x%p CacheTextRects(), FAILED, "
382 "couldn't retrieve text rect array around the selection focus (%u)",
383 this, mSelection.mFocus));
384 MOZ_ASSERT(mSelection.mFocusCharRects[ePrevCharRect].IsEmpty());
385 MOZ_ASSERT(mSelection.mFocusCharRects[eNextCharRect].IsEmpty());
386 } else {
387 if (rects.Length() > 1) {
388 mSelection.mFocusCharRects[ePrevCharRect] = rects[0];
389 mSelection.mFocusCharRects[eNextCharRect] = rects[1];
390 } else if (rects.Length()) {
391 mSelection.mFocusCharRects[eNextCharRect] = rects[0];
392 MOZ_ASSERT(mSelection.mFocusCharRects[ePrevCharRect].IsEmpty());
397 if (!mSelection.Collapsed()) {
398 nsEventStatus status = nsEventStatus_eIgnore;
399 WidgetQueryContentEvent textRect(true, eQueryTextRect, aWidget);
400 textRect.InitForQueryTextRect(mSelection.StartOffset(),
401 mSelection.Length());
402 aWidget->DispatchEvent(&textRect, status);
403 if (NS_WARN_IF(!textRect.mSucceeded)) {
404 MOZ_LOG(sContentCacheLog, LogLevel::Error,
405 ("0x%p CacheTextRects(), FAILED, "
406 "couldn't retrieve text rect of whole selected text",
407 this));
408 } else {
409 mSelection.mRect = textRect.mReply.mRect;
413 if (!mSelection.mFocus) {
414 mFirstCharRect = mSelection.mFocusCharRects[eNextCharRect];
415 } else if (mSelection.mFocus == 1) {
416 mFirstCharRect = mSelection.mFocusCharRects[ePrevCharRect];
417 } else if (!mSelection.mAnchor) {
418 mFirstCharRect = mSelection.mAnchorCharRects[eNextCharRect];
419 } else if (mSelection.mAnchor == 1) {
420 mFirstCharRect = mSelection.mFocusCharRects[ePrevCharRect];
421 } else if (mTextRectArray.InRange(0)) {
422 mFirstCharRect = mTextRectArray.GetRect(0);
423 } else {
424 LayoutDeviceIntRect charRect;
425 if (NS_WARN_IF(!QueryCharRect(aWidget, 0, charRect))) {
426 MOZ_LOG(sContentCacheLog, LogLevel::Error,
427 ("0x%p CacheTextRects(), FAILED, "
428 "couldn't retrieve first char rect",
429 this));
430 } else {
431 mFirstCharRect = charRect;
435 MOZ_LOG(
436 sContentCacheLog, LogLevel::Info,
437 ("0x%p CacheTextRects(), Succeeded, "
438 "mText.Length()=%x, mTextRectArray={ mStart=%u, mRects.Length()=%zu"
439 " }, mSelection={ mAnchor=%u, mAnchorCharRects[eNextCharRect]=%s, "
440 "mAnchorCharRects[ePrevCharRect]=%s, mFocus=%u, "
441 "mFocusCharRects[eNextCharRect]=%s, mFocusCharRects[ePrevCharRect]=%s, "
442 "mRect=%s }, mFirstCharRect=%s",
443 this, mText.Length(), mTextRectArray.mStart,
444 mTextRectArray.mRects.Length(), mSelection.mAnchor,
445 GetRectText(mSelection.mAnchorCharRects[eNextCharRect]).get(),
446 GetRectText(mSelection.mAnchorCharRects[ePrevCharRect]).get(),
447 mSelection.mFocus,
448 GetRectText(mSelection.mFocusCharRects[eNextCharRect]).get(),
449 GetRectText(mSelection.mFocusCharRects[ePrevCharRect]).get(),
450 GetRectText(mSelection.mRect).get(), GetRectText(mFirstCharRect).get()));
451 return true;
454 void ContentCacheInChild::SetSelection(nsIWidget* aWidget,
455 uint32_t aStartOffset, uint32_t aLength,
456 bool aReversed,
457 const WritingMode& aWritingMode) {
458 MOZ_LOG(sContentCacheLog, LogLevel::Info,
459 ("0x%p SetSelection(aStartOffset=%u, "
460 "aLength=%u, aReversed=%s, aWritingMode=%s), mText.Length()=%u",
461 this, aStartOffset, aLength, GetBoolName(aReversed),
462 GetWritingModeName(aWritingMode).get(), mText.Length()));
464 if (!aReversed) {
465 mSelection.mAnchor = aStartOffset;
466 mSelection.mFocus = aStartOffset + aLength;
467 } else {
468 mSelection.mAnchor = aStartOffset + aLength;
469 mSelection.mFocus = aStartOffset;
471 mSelection.mWritingMode = aWritingMode;
473 if (NS_WARN_IF(!CacheCaret(aWidget))) {
474 return;
476 Unused << NS_WARN_IF(!CacheTextRects(aWidget));
479 /*****************************************************************************
480 * mozilla::ContentCacheInParent
481 *****************************************************************************/
483 ContentCacheInParent::ContentCacheInParent(BrowserParent& aBrowserParent)
484 : ContentCache(),
485 mBrowserParent(aBrowserParent),
486 mCommitStringByRequest(nullptr),
487 mPendingEventsNeedingAck(0),
488 mCompositionStartInChild(UINT32_MAX),
489 mPendingCommitLength(0),
490 mPendingCompositionCount(0),
491 mPendingCommitCount(0),
492 mWidgetHasComposition(false),
493 mIsChildIgnoringCompositionEvents(false) {}
495 void ContentCacheInParent::AssignContent(const ContentCache& aOther,
496 nsIWidget* aWidget,
497 const IMENotification* aNotification) {
498 mText = aOther.mText;
499 mSelection = aOther.mSelection;
500 mFirstCharRect = aOther.mFirstCharRect;
501 mCaret = aOther.mCaret;
502 mTextRectArray = aOther.mTextRectArray;
503 mEditorRect = aOther.mEditorRect;
505 // Only when there is one composition, the TextComposition instance in this
506 // process is managing the composition in the remote process. Therefore,
507 // we shouldn't update composition start offset of TextComposition with
508 // old composition which is still being handled by the child process.
509 if (mWidgetHasComposition && mPendingCompositionCount == 1) {
510 IMEStateManager::MaybeStartOffsetUpdatedInChild(aWidget, mCompositionStart);
513 // When this instance allows to query content relative to composition string,
514 // we should modify mCompositionStart with the latest information in the
515 // remote process because now we have the information around the composition
516 // string.
517 mCompositionStartInChild = aOther.mCompositionStart;
518 if (mWidgetHasComposition || mPendingCommitCount) {
519 if (aOther.mCompositionStart != UINT32_MAX) {
520 if (mCompositionStart != aOther.mCompositionStart) {
521 mCompositionStart = aOther.mCompositionStart;
522 mPendingCommitLength = 0;
524 } else if (mCompositionStart != mSelection.StartOffset()) {
525 mCompositionStart = mSelection.StartOffset();
526 mPendingCommitLength = 0;
527 NS_WARNING_ASSERTION(mCompositionStart != UINT32_MAX,
528 "mCompositionStart shouldn't be invalid offset when "
529 "the widget has composition");
533 MOZ_LOG(
534 sContentCacheLog, LogLevel::Info,
535 ("0x%p AssignContent(aNotification=%s), "
536 "Succeeded, mText.Length()=%u, mSelection={ mAnchor=%u, mFocus=%u, "
537 "mWritingMode=%s, mAnchorCharRects[eNextCharRect]=%s, "
538 "mAnchorCharRects[ePrevCharRect]=%s, mFocusCharRects[eNextCharRect]=%s, "
539 "mFocusCharRects[ePrevCharRect]=%s, mRect=%s }, "
540 "mFirstCharRect=%s, mCaret={ mOffset=%u, mRect=%s }, mTextRectArray={ "
541 "mStart=%u, mRects.Length()=%zu }, mWidgetHasComposition=%s, "
542 "mPendingCompositionCount=%u, mCompositionStart=%u, "
543 "mPendingCommitLength=%u, mEditorRect=%s",
544 this, GetNotificationName(aNotification), mText.Length(),
545 mSelection.mAnchor, mSelection.mFocus,
546 GetWritingModeName(mSelection.mWritingMode).get(),
547 GetRectText(mSelection.mAnchorCharRects[eNextCharRect]).get(),
548 GetRectText(mSelection.mAnchorCharRects[ePrevCharRect]).get(),
549 GetRectText(mSelection.mFocusCharRects[eNextCharRect]).get(),
550 GetRectText(mSelection.mFocusCharRects[ePrevCharRect]).get(),
551 GetRectText(mSelection.mRect).get(), GetRectText(mFirstCharRect).get(),
552 mCaret.mOffset, GetRectText(mCaret.mRect).get(), mTextRectArray.mStart,
553 mTextRectArray.mRects.Length(), GetBoolName(mWidgetHasComposition),
554 mPendingCompositionCount, mCompositionStart, mPendingCommitLength,
555 GetRectText(mEditorRect).get()));
558 bool ContentCacheInParent::HandleQueryContentEvent(
559 WidgetQueryContentEvent& aEvent, nsIWidget* aWidget) const {
560 MOZ_ASSERT(aWidget);
562 aEvent.mSucceeded = false;
563 aEvent.mReply.mFocusedWidget = aWidget;
565 // ContentCache doesn't store offset of its start with XP linebreaks.
566 // So, we don't support to query contents relative to composition start
567 // offset with XP linebreaks.
568 if (NS_WARN_IF(!aEvent.mUseNativeLineBreak)) {
569 MOZ_LOG(sContentCacheLog, LogLevel::Error,
570 ("0x%p HandleQueryContentEvent(), FAILED due to query with XP "
571 "linebreaks",
572 this));
573 return false;
576 if (NS_WARN_IF(!aEvent.mInput.IsValidOffset())) {
577 MOZ_LOG(
578 sContentCacheLog, LogLevel::Error,
579 ("0x%p HandleQueryContentEvent(), FAILED due to invalid offset", this));
580 return false;
583 if (NS_WARN_IF(!aEvent.mInput.IsValidEventMessage(aEvent.mMessage))) {
584 MOZ_LOG(
585 sContentCacheLog, LogLevel::Error,
586 ("0x%p HandleQueryContentEvent(), FAILED due to invalid event message",
587 this));
588 return false;
591 bool isRelativeToInsertionPoint = aEvent.mInput.mRelativeToInsertionPoint;
592 if (isRelativeToInsertionPoint) {
593 if (aWidget->PluginHasFocus()) {
594 if (NS_WARN_IF(!aEvent.mInput.MakeOffsetAbsolute(0))) {
595 MOZ_LOG(sContentCacheLog, LogLevel::Error,
596 ("0x%p HandleQueryContentEvent(), FAILED due to "
597 "aEvent.mInput.MakeOffsetAbsolute(0) failure, aEvent={ "
598 "mMessage=%s, "
599 "mInput={ mOffset=%" PRId64 ", mLength=%" PRIu32 " } }",
600 this, ToChar(aEvent.mMessage), aEvent.mInput.mOffset,
601 aEvent.mInput.mLength));
602 return false;
604 } else if (mWidgetHasComposition || mPendingCommitCount) {
605 if (NS_WARN_IF(!aEvent.mInput.MakeOffsetAbsolute(mCompositionStart +
606 mPendingCommitLength))) {
607 MOZ_LOG(
608 sContentCacheLog, LogLevel::Error,
609 ("0x%p HandleQueryContentEvent(), FAILED due to "
610 "aEvent.mInput.MakeOffsetAbsolute(mCompositionStart + "
611 "mPendingCommitLength) failure, "
612 "mCompositionStart=%" PRIu32 ", mPendingCommitLength=%" PRIu32 ", "
613 "aEvent={ mMessage=%s, mInput={ mOffset=%" PRId64
614 ", mLength=%" PRIu32 " } }",
615 this, mCompositionStart, mPendingCommitLength,
616 ToChar(aEvent.mMessage), aEvent.mInput.mOffset,
617 aEvent.mInput.mLength));
618 return false;
620 } else if (NS_WARN_IF(!mSelection.IsValid())) {
621 MOZ_LOG(sContentCacheLog, LogLevel::Error,
622 ("0x%p HandleQueryContentEvent(), FAILED due to mSelection is "
623 "invalid",
624 this));
625 return false;
626 } else if (NS_WARN_IF(!aEvent.mInput.MakeOffsetAbsolute(
627 mSelection.StartOffset() + mPendingCommitLength))) {
628 MOZ_LOG(sContentCacheLog, LogLevel::Error,
629 ("0x%p HandleQueryContentEvent(), FAILED due to "
630 "aEvent.mInput.MakeOffsetAbsolute(mSelection.StartOffset() + "
631 "mPendingCommitLength) failure, "
632 "mSelection={ StartOffset()=%d, Length()=%d }, "
633 "mPendingCommitLength=%" PRIu32 ", aEvent={ mMessage=%s, "
634 "mInput={ mOffset=%" PRId64 ", mLength=%" PRIu32 " } }",
635 this, mSelection.StartOffset(), mSelection.Length(),
636 mPendingCommitLength, ToChar(aEvent.mMessage),
637 aEvent.mInput.mOffset, aEvent.mInput.mLength));
638 return false;
642 switch (aEvent.mMessage) {
643 case eQuerySelectedText:
644 MOZ_LOG(sContentCacheLog, LogLevel::Info,
645 ("0x%p HandleQueryContentEvent("
646 "aEvent={ mMessage=eQuerySelectedText }, aWidget=0x%p)",
647 this, aWidget));
648 if (aWidget->PluginHasFocus()) {
649 MOZ_LOG(sContentCacheLog, LogLevel::Info,
650 ("0x%p HandleQueryContentEvent(), "
651 "return emtpy selection becasue plugin has focus",
652 this));
653 aEvent.mSucceeded = true;
654 aEvent.mReply.mOffset = 0;
655 aEvent.mReply.mReversed = false;
656 aEvent.mReply.mHasSelection = false;
657 return true;
659 if (NS_WARN_IF(!IsSelectionValid())) {
660 // If content cache hasn't been initialized properly, make the query
661 // failed.
662 MOZ_LOG(sContentCacheLog, LogLevel::Error,
663 ("0x%p HandleQueryContentEvent(), "
664 "FAILED because mSelection is not valid",
665 this));
666 return true;
668 aEvent.mReply.mOffset = mSelection.StartOffset();
669 if (mSelection.Collapsed()) {
670 aEvent.mReply.mString.Truncate(0);
671 } else {
672 if (NS_WARN_IF(mSelection.EndOffset() > mText.Length())) {
673 MOZ_LOG(sContentCacheLog, LogLevel::Error,
674 ("0x%p HandleQueryContentEvent(), "
675 "FAILED because mSelection.EndOffset()=%u is larger than "
676 "mText.Length()=%u",
677 this, mSelection.EndOffset(), mText.Length()));
678 return false;
680 aEvent.mReply.mString =
681 Substring(mText, aEvent.mReply.mOffset, mSelection.Length());
683 aEvent.mReply.mReversed = mSelection.Reversed();
684 aEvent.mReply.mHasSelection = true;
685 aEvent.mReply.mWritingMode = mSelection.mWritingMode;
686 MOZ_LOG(sContentCacheLog, LogLevel::Info,
687 ("0x%p HandleQueryContentEvent(), "
688 "Succeeded, aEvent={ mReply={ mOffset=%u, mString=\"%s\", "
689 "mReversed=%s, mHasSelection=%s, mWritingMode=%s } }",
690 this, aEvent.mReply.mOffset,
691 GetEscapedUTF8String(aEvent.mReply.mString).get(),
692 GetBoolName(aEvent.mReply.mReversed),
693 GetBoolName(aEvent.mReply.mHasSelection),
694 GetWritingModeName(aEvent.mReply.mWritingMode).get()));
695 break;
696 case eQueryTextContent: {
697 MOZ_LOG(sContentCacheLog, LogLevel::Info,
698 ("0x%p HandleQueryContentEvent("
699 "aEvent={ mMessage=eQueryTextContent, mInput={ mOffset=%" PRId64
700 ", mLength=%u } }, aWidget=0x%p), mText.Length()=%u",
701 this, aEvent.mInput.mOffset, aEvent.mInput.mLength, aWidget,
702 mText.Length()));
703 uint32_t inputOffset = aEvent.mInput.mOffset;
704 uint32_t inputEndOffset =
705 std::min(aEvent.mInput.EndOffset(), mText.Length());
706 if (NS_WARN_IF(inputEndOffset < inputOffset)) {
707 MOZ_LOG(
708 sContentCacheLog, LogLevel::Error,
709 ("0x%p HandleQueryContentEvent(), "
710 "FAILED because inputOffset=%u is larger than inputEndOffset=%u",
711 this, inputOffset, inputEndOffset));
712 return false;
714 aEvent.mReply.mOffset = inputOffset;
715 aEvent.mReply.mString =
716 Substring(mText, inputOffset, inputEndOffset - inputOffset);
717 MOZ_LOG(
718 sContentCacheLog, LogLevel::Info,
719 ("0x%p HandleQueryContentEvent(), "
720 "Succeeded, aEvent={ mReply={ mOffset=%u, mString.Length()=%u } }",
721 this, aEvent.mReply.mOffset, aEvent.mReply.mString.Length()));
722 break;
724 case eQueryTextRect:
725 MOZ_LOG(sContentCacheLog, LogLevel::Info,
726 ("0x%p HandleQueryContentEvent("
727 "aEvent={ mMessage=eQueryTextRect, mInput={ mOffset=%" PRId64
728 ", mLength=%u } }, aWidget=0x%p), mText.Length()=%u",
729 this, aEvent.mInput.mOffset, aEvent.mInput.mLength, aWidget,
730 mText.Length()));
731 if (NS_WARN_IF(!IsSelectionValid())) {
732 // If content cache hasn't been initialized properly, make the query
733 // failed.
734 MOZ_LOG(sContentCacheLog, LogLevel::Error,
735 ("0x%p HandleQueryContentEvent(), "
736 "FAILED because mSelection is not valid",
737 this));
738 return true;
740 // Note that if the query is relative to insertion point, the query was
741 // probably requested by native IME. In such case, we should return
742 // non-empty rect since returning failure causes IME showing its window
743 // at odd position.
744 if (aEvent.mInput.mLength) {
745 if (NS_WARN_IF(!GetUnionTextRects(
746 aEvent.mInput.mOffset, aEvent.mInput.mLength,
747 isRelativeToInsertionPoint, aEvent.mReply.mRect))) {
748 // XXX We don't have cache for this request.
749 MOZ_LOG(sContentCacheLog, LogLevel::Error,
750 ("0x%p HandleQueryContentEvent(), "
751 "FAILED to get union rect",
752 this));
753 return false;
755 } else {
756 // If the length is 0, we should return caret rect instead.
757 if (NS_WARN_IF(!GetCaretRect(aEvent.mInput.mOffset,
758 isRelativeToInsertionPoint,
759 aEvent.mReply.mRect))) {
760 MOZ_LOG(sContentCacheLog, LogLevel::Error,
761 ("0x%p HandleQueryContentEvent(), "
762 "FAILED to get caret rect",
763 this));
764 return false;
767 if (aEvent.mInput.mOffset < mText.Length()) {
768 aEvent.mReply.mString = Substring(
769 mText, aEvent.mInput.mOffset,
770 mText.Length() >= aEvent.mInput.EndOffset() ? aEvent.mInput.mLength
771 : UINT32_MAX);
772 } else {
773 aEvent.mReply.mString.Truncate(0);
775 aEvent.mReply.mOffset = aEvent.mInput.mOffset;
776 // XXX This may be wrong if storing range isn't in the selection range.
777 aEvent.mReply.mWritingMode = mSelection.mWritingMode;
778 MOZ_LOG(sContentCacheLog, LogLevel::Info,
779 ("0x%p HandleQueryContentEvent(), "
780 "Succeeded, aEvent={ mReply={ mOffset=%u, mString=\"%s\", "
781 "mWritingMode=%s, mRect=%s } }",
782 this, aEvent.mReply.mOffset,
783 GetEscapedUTF8String(aEvent.mReply.mString).get(),
784 GetWritingModeName(aEvent.mReply.mWritingMode).get(),
785 GetRectText(aEvent.mReply.mRect).get()));
786 break;
787 case eQueryCaretRect:
788 MOZ_LOG(sContentCacheLog, LogLevel::Info,
789 ("0x%p HandleQueryContentEvent("
790 "aEvent={ mMessage=eQueryCaretRect, mInput={ mOffset=%" PRId64
791 " } }, "
792 "aWidget=0x%p), mText.Length()=%u",
793 this, aEvent.mInput.mOffset, aWidget, mText.Length()));
794 if (NS_WARN_IF(!IsSelectionValid())) {
795 // If content cache hasn't been initialized properly, make the query
796 // failed.
797 MOZ_LOG(sContentCacheLog, LogLevel::Error,
798 ("0x%p HandleQueryContentEvent(), "
799 "FAILED because mSelection is not valid",
800 this));
801 return true;
803 // Note that if the query is relative to insertion point, the query was
804 // probably requested by native IME. In such case, we should return
805 // non-empty rect since returning failure causes IME showing its window
806 // at odd position.
807 if (NS_WARN_IF(!GetCaretRect(aEvent.mInput.mOffset,
808 isRelativeToInsertionPoint,
809 aEvent.mReply.mRect))) {
810 MOZ_LOG(sContentCacheLog, LogLevel::Error,
811 ("0x%p HandleQueryContentEvent(), "
812 "FAILED to get caret rect",
813 this));
814 return false;
816 aEvent.mReply.mOffset = aEvent.mInput.mOffset;
817 MOZ_LOG(sContentCacheLog, LogLevel::Info,
818 ("0x%p HandleQueryContentEvent(), "
819 "Succeeded, aEvent={ mReply={ mOffset=%u, mRect=%s } }",
820 this, aEvent.mReply.mOffset,
821 GetRectText(aEvent.mReply.mRect).get()));
822 break;
823 case eQueryEditorRect:
824 MOZ_LOG(sContentCacheLog, LogLevel::Info,
825 ("0x%p HandleQueryContentEvent("
826 "aEvent={ mMessage=eQueryEditorRect }, aWidget=0x%p)",
827 this, aWidget));
828 aEvent.mReply.mRect = mEditorRect;
829 MOZ_LOG(sContentCacheLog, LogLevel::Info,
830 ("0x%p HandleQueryContentEvent(), "
831 "Succeeded, aEvent={ mReply={ mRect=%s } }",
832 this, GetRectText(aEvent.mReply.mRect).get()));
833 break;
834 default:
835 break;
837 aEvent.mSucceeded = true;
838 return true;
841 bool ContentCacheInParent::GetTextRect(uint32_t aOffset,
842 bool aRoundToExistingOffset,
843 LayoutDeviceIntRect& aTextRect) const {
844 MOZ_LOG(sContentCacheLog, LogLevel::Info,
845 ("0x%p GetTextRect(aOffset=%u, "
846 "aRoundToExistingOffset=%s), "
847 "mTextRectArray={ mStart=%u, mRects.Length()=%zu }, "
848 "mSelection={ mAnchor=%u, mFocus=%u }",
849 this, aOffset, GetBoolName(aRoundToExistingOffset),
850 mTextRectArray.mStart, mTextRectArray.mRects.Length(),
851 mSelection.mAnchor, mSelection.mFocus));
853 if (!aOffset) {
854 NS_WARNING_ASSERTION(!mFirstCharRect.IsEmpty(), "empty rect");
855 aTextRect = mFirstCharRect;
856 return !aTextRect.IsEmpty();
858 if (aOffset == mSelection.mAnchor) {
859 NS_WARNING_ASSERTION(!mSelection.mAnchorCharRects[eNextCharRect].IsEmpty(),
860 "empty rect");
861 aTextRect = mSelection.mAnchorCharRects[eNextCharRect];
862 return !aTextRect.IsEmpty();
864 if (mSelection.mAnchor && aOffset == mSelection.mAnchor - 1) {
865 NS_WARNING_ASSERTION(!mSelection.mAnchorCharRects[ePrevCharRect].IsEmpty(),
866 "empty rect");
867 aTextRect = mSelection.mAnchorCharRects[ePrevCharRect];
868 return !aTextRect.IsEmpty();
870 if (aOffset == mSelection.mFocus) {
871 NS_WARNING_ASSERTION(!mSelection.mFocusCharRects[eNextCharRect].IsEmpty(),
872 "empty rect");
873 aTextRect = mSelection.mFocusCharRects[eNextCharRect];
874 return !aTextRect.IsEmpty();
876 if (mSelection.mFocus && aOffset == mSelection.mFocus - 1) {
877 NS_WARNING_ASSERTION(!mSelection.mFocusCharRects[ePrevCharRect].IsEmpty(),
878 "empty rect");
879 aTextRect = mSelection.mFocusCharRects[ePrevCharRect];
880 return !aTextRect.IsEmpty();
883 uint32_t offset = aOffset;
884 if (!mTextRectArray.InRange(aOffset)) {
885 if (!aRoundToExistingOffset) {
886 aTextRect.SetEmpty();
887 return false;
889 if (!mTextRectArray.IsValid()) {
890 // If there are no rects in mTextRectArray, we should refer the start of
891 // the selection because IME must query a char rect around it if there is
892 // no composition.
893 aTextRect = mSelection.StartCharRect();
894 return !aTextRect.IsEmpty();
896 if (offset < mTextRectArray.StartOffset()) {
897 offset = mTextRectArray.StartOffset();
898 } else {
899 offset = mTextRectArray.EndOffset() - 1;
902 aTextRect = mTextRectArray.GetRect(offset);
903 return !aTextRect.IsEmpty();
906 bool ContentCacheInParent::GetUnionTextRects(
907 uint32_t aOffset, uint32_t aLength, bool aRoundToExistingOffset,
908 LayoutDeviceIntRect& aUnionTextRect) const {
909 MOZ_LOG(sContentCacheLog, LogLevel::Info,
910 ("0x%p GetUnionTextRects(aOffset=%u, "
911 "aLength=%u, aRoundToExistingOffset=%s), mTextRectArray={ "
912 "mStart=%u, mRects.Length()=%zu }, "
913 "mSelection={ mAnchor=%u, mFocus=%u }",
914 this, aOffset, aLength, GetBoolName(aRoundToExistingOffset),
915 mTextRectArray.mStart, mTextRectArray.mRects.Length(),
916 mSelection.mAnchor, mSelection.mFocus));
918 CheckedInt<uint32_t> endOffset = CheckedInt<uint32_t>(aOffset) + aLength;
919 if (!endOffset.isValid()) {
920 return false;
923 if (!mSelection.Collapsed() && aOffset == mSelection.StartOffset() &&
924 aLength == mSelection.Length()) {
925 NS_WARNING_ASSERTION(!mSelection.mRect.IsEmpty(), "empty rect");
926 aUnionTextRect = mSelection.mRect;
927 return !aUnionTextRect.IsEmpty();
930 if (aLength == 1) {
931 if (!aOffset) {
932 NS_WARNING_ASSERTION(!mFirstCharRect.IsEmpty(), "empty rect");
933 aUnionTextRect = mFirstCharRect;
934 return !aUnionTextRect.IsEmpty();
936 if (aOffset == mSelection.mAnchor) {
937 NS_WARNING_ASSERTION(
938 !mSelection.mAnchorCharRects[eNextCharRect].IsEmpty(), "empty rect");
939 aUnionTextRect = mSelection.mAnchorCharRects[eNextCharRect];
940 return !aUnionTextRect.IsEmpty();
942 if (mSelection.mAnchor && aOffset == mSelection.mAnchor - 1) {
943 NS_WARNING_ASSERTION(
944 !mSelection.mAnchorCharRects[ePrevCharRect].IsEmpty(), "empty rect");
945 aUnionTextRect = mSelection.mAnchorCharRects[ePrevCharRect];
946 return !aUnionTextRect.IsEmpty();
948 if (aOffset == mSelection.mFocus) {
949 NS_WARNING_ASSERTION(!mSelection.mFocusCharRects[eNextCharRect].IsEmpty(),
950 "empty rect");
951 aUnionTextRect = mSelection.mFocusCharRects[eNextCharRect];
952 return !aUnionTextRect.IsEmpty();
954 if (mSelection.mFocus && aOffset == mSelection.mFocus - 1) {
955 NS_WARNING_ASSERTION(!mSelection.mFocusCharRects[ePrevCharRect].IsEmpty(),
956 "empty rect");
957 aUnionTextRect = mSelection.mFocusCharRects[ePrevCharRect];
958 return !aUnionTextRect.IsEmpty();
962 // Even if some text rects are not cached of the queried range,
963 // we should return union rect when the first character's rect is cached
964 // since the first character rect is important and the others are not so
965 // in most cases.
967 if (!aOffset && aOffset != mSelection.mAnchor &&
968 aOffset != mSelection.mFocus && !mTextRectArray.InRange(aOffset)) {
969 // The first character rect isn't cached.
970 return false;
973 if ((aRoundToExistingOffset && mTextRectArray.HasRects()) ||
974 mTextRectArray.IsOverlappingWith(aOffset, aLength)) {
975 aUnionTextRect = mTextRectArray.GetUnionRectAsFarAsPossible(
976 aOffset, aLength, aRoundToExistingOffset);
977 } else {
978 aUnionTextRect.SetEmpty();
981 if (!aOffset) {
982 aUnionTextRect = aUnionTextRect.Union(mFirstCharRect);
984 if (aOffset <= mSelection.mAnchor && mSelection.mAnchor < endOffset.value()) {
985 aUnionTextRect =
986 aUnionTextRect.Union(mSelection.mAnchorCharRects[eNextCharRect]);
988 if (mSelection.mAnchor && aOffset <= mSelection.mAnchor - 1 &&
989 mSelection.mAnchor - 1 < endOffset.value()) {
990 aUnionTextRect =
991 aUnionTextRect.Union(mSelection.mAnchorCharRects[ePrevCharRect]);
993 if (aOffset <= mSelection.mFocus && mSelection.mFocus < endOffset.value()) {
994 aUnionTextRect =
995 aUnionTextRect.Union(mSelection.mFocusCharRects[eNextCharRect]);
997 if (mSelection.mFocus && aOffset <= mSelection.mFocus - 1 &&
998 mSelection.mFocus - 1 < endOffset.value()) {
999 aUnionTextRect =
1000 aUnionTextRect.Union(mSelection.mFocusCharRects[ePrevCharRect]);
1003 return !aUnionTextRect.IsEmpty();
1006 bool ContentCacheInParent::GetCaretRect(uint32_t aOffset,
1007 bool aRoundToExistingOffset,
1008 LayoutDeviceIntRect& aCaretRect) const {
1009 MOZ_LOG(
1010 sContentCacheLog, LogLevel::Info,
1011 ("0x%p GetCaretRect(aOffset=%u, "
1012 "aRoundToExistingOffset=%s), "
1013 "mCaret={ mOffset=%u, mRect=%s, IsValid()=%s }, mTextRectArray={ "
1014 "mStart=%u, mRects.Length()=%zu }, mSelection={ mAnchor=%u, mFocus=%u, "
1015 "mWritingMode=%s, mAnchorCharRects[eNextCharRect]=%s, "
1016 "mAnchorCharRects[ePrevCharRect]=%s, mFocusCharRects[eNextCharRect]=%s, "
1017 "mFocusCharRects[ePrevCharRect]=%s }, mFirstCharRect=%s",
1018 this, aOffset, GetBoolName(aRoundToExistingOffset), mCaret.mOffset,
1019 GetRectText(mCaret.mRect).get(), GetBoolName(mCaret.IsValid()),
1020 mTextRectArray.mStart, mTextRectArray.mRects.Length(),
1021 mSelection.mAnchor, mSelection.mFocus,
1022 GetWritingModeName(mSelection.mWritingMode).get(),
1023 GetRectText(mSelection.mAnchorCharRects[eNextCharRect]).get(),
1024 GetRectText(mSelection.mAnchorCharRects[ePrevCharRect]).get(),
1025 GetRectText(mSelection.mFocusCharRects[eNextCharRect]).get(),
1026 GetRectText(mSelection.mFocusCharRects[ePrevCharRect]).get(),
1027 GetRectText(mFirstCharRect).get()));
1029 if (mCaret.IsValid() && mCaret.mOffset == aOffset) {
1030 aCaretRect = mCaret.mRect;
1031 return true;
1034 // Guess caret rect from the text rect if it's stored.
1035 if (!GetTextRect(aOffset, aRoundToExistingOffset, aCaretRect)) {
1036 // There might be previous character rect in the cache. If so, we can
1037 // guess the caret rect with it.
1038 if (!aOffset ||
1039 !GetTextRect(aOffset - 1, aRoundToExistingOffset, aCaretRect)) {
1040 aCaretRect.SetEmpty();
1041 return false;
1044 if (mSelection.mWritingMode.IsVertical()) {
1045 aCaretRect.MoveToY(aCaretRect.YMost());
1046 } else {
1047 // XXX bidi-unaware.
1048 aCaretRect.MoveToX(aCaretRect.XMost());
1052 // XXX This is not bidi aware because we don't cache each character's
1053 // direction. However, this is usually used by IME, so, assuming the
1054 // character is in LRT context must not cause any problem.
1055 if (mSelection.mWritingMode.IsVertical()) {
1056 aCaretRect.SetHeight(mCaret.IsValid() ? mCaret.mRect.Height() : 1);
1057 } else {
1058 aCaretRect.SetWidth(mCaret.IsValid() ? mCaret.mRect.Width() : 1);
1060 return true;
1063 bool ContentCacheInParent::OnCompositionEvent(
1064 const WidgetCompositionEvent& aEvent) {
1065 MOZ_LOG(
1066 sContentCacheLog, LogLevel::Info,
1067 ("0x%p OnCompositionEvent(aEvent={ "
1068 "mMessage=%s, mData=\"%s\" (Length()=%u), mRanges->Length()=%zu }), "
1069 "mPendingEventsNeedingAck=%u, mWidgetHasComposition=%s, "
1070 "mPendingCompositionCount=%" PRIu8 ", mPendingCommitCount=%" PRIu8 ", "
1071 "mIsChildIgnoringCompositionEvents=%s, mCommitStringByRequest=0x%p",
1072 this, ToChar(aEvent.mMessage), GetEscapedUTF8String(aEvent.mData).get(),
1073 aEvent.mData.Length(), aEvent.mRanges ? aEvent.mRanges->Length() : 0,
1074 mPendingEventsNeedingAck, GetBoolName(mWidgetHasComposition),
1075 mPendingCompositionCount, mPendingCommitCount,
1076 GetBoolName(mIsChildIgnoringCompositionEvents), mCommitStringByRequest));
1078 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1079 mDispatchedEventMessages.AppendElement(aEvent.mMessage);
1080 #endif // #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1082 // We must be able to simulate the selection because
1083 // we might not receive selection updates in time
1084 if (!mWidgetHasComposition) {
1085 if (aEvent.mWidget && aEvent.mWidget->PluginHasFocus()) {
1086 // If focus is on plugin, we cannot get selection range
1087 mCompositionStart = 0;
1088 } else if (mCompositionStartInChild != UINT32_MAX) {
1089 // If there is pending composition in the remote process, let's use
1090 // its start offset temporarily because this stores a lot of information
1091 // around it and the user must look around there, so, showing some UI
1092 // around it must make sense.
1093 mCompositionStart = mCompositionStartInChild;
1094 } else {
1095 mCompositionStart = mSelection.StartOffset();
1097 MOZ_ASSERT(aEvent.mMessage == eCompositionStart);
1098 MOZ_RELEASE_ASSERT(mPendingCompositionCount < UINT8_MAX);
1099 mPendingCompositionCount++;
1102 mWidgetHasComposition = !aEvent.CausesDOMCompositionEndEvent();
1104 if (!mWidgetHasComposition) {
1105 // mCompositionStart will be reset when commit event is completely handled
1106 // in the remote process.
1107 if (mPendingCompositionCount == 1) {
1108 mPendingCommitLength = aEvent.mData.Length();
1110 mPendingCommitCount++;
1111 } else if (aEvent.mMessage != eCompositionStart) {
1112 mCompositionString = aEvent.mData;
1115 // During REQUEST_TO_COMMIT_COMPOSITION or REQUEST_TO_CANCEL_COMPOSITION,
1116 // widget usually sends a eCompositionChange and/or eCompositionCommit event
1117 // to finalize or clear the composition, respectively. In this time,
1118 // we need to intercept all composition events here and pass the commit
1119 // string for returning to the remote process as a result of
1120 // RequestIMEToCommitComposition(). Then, eCommitComposition event will
1121 // be dispatched with the committed string in the remote process internally.
1122 if (mCommitStringByRequest) {
1123 if (aEvent.mMessage == eCompositionCommitAsIs) {
1124 *mCommitStringByRequest = mCompositionString;
1125 } else {
1126 MOZ_ASSERT(aEvent.mMessage == eCompositionChange ||
1127 aEvent.mMessage == eCompositionCommit);
1128 *mCommitStringByRequest = aEvent.mData;
1130 // We need to wait eCompositionCommitRequestHandled from the remote process
1131 // in this case. Therefore, mPendingEventsNeedingAck needs to be
1132 // incremented here. Additionally, we stop sending eCompositionCommit(AsIs)
1133 // event. Therefore, we need to decrement mPendingCommitCount which has
1134 // been incremented above.
1135 if (!mWidgetHasComposition) {
1136 mPendingEventsNeedingAck++;
1137 MOZ_DIAGNOSTIC_ASSERT(mPendingCommitCount);
1138 if (mPendingCommitCount) {
1139 mPendingCommitCount--;
1142 return false;
1145 mPendingEventsNeedingAck++;
1146 return true;
1149 void ContentCacheInParent::OnSelectionEvent(
1150 const WidgetSelectionEvent& aSelectionEvent) {
1151 MOZ_LOG(
1152 sContentCacheLog, LogLevel::Info,
1153 ("0x%p OnSelectionEvent(aEvent={ "
1154 "mMessage=%s, mOffset=%u, mLength=%u, mReversed=%s, "
1155 "mExpandToClusterBoundary=%s, mUseNativeLineBreak=%s }), "
1156 "mPendingEventsNeedingAck=%u, mWidgetHasComposition=%s, "
1157 "mPendingCompositionCount=%" PRIu8 ", mPendingCommitCount=%" PRIu8 ", "
1158 "mIsChildIgnoringCompositionEvents=%s",
1159 this, ToChar(aSelectionEvent.mMessage), aSelectionEvent.mOffset,
1160 aSelectionEvent.mLength, GetBoolName(aSelectionEvent.mReversed),
1161 GetBoolName(aSelectionEvent.mExpandToClusterBoundary),
1162 GetBoolName(aSelectionEvent.mUseNativeLineBreak),
1163 mPendingEventsNeedingAck, GetBoolName(mWidgetHasComposition),
1164 mPendingCompositionCount, mPendingCommitCount,
1165 GetBoolName(mIsChildIgnoringCompositionEvents)));
1167 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1168 mDispatchedEventMessages.AppendElement(aSelectionEvent.mMessage);
1169 #endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
1171 mPendingEventsNeedingAck++;
1174 void ContentCacheInParent::OnEventNeedingAckHandled(nsIWidget* aWidget,
1175 EventMessage aMessage) {
1176 // This is called when the child process receives WidgetCompositionEvent or
1177 // WidgetSelectionEvent.
1179 MOZ_LOG(
1180 sContentCacheLog, LogLevel::Info,
1181 ("0x%p OnEventNeedingAckHandled(aWidget=0x%p, "
1182 "aMessage=%s), mPendingEventsNeedingAck=%u, "
1183 "mWidgetHasComposition=%s, mPendingCompositionCount=%" PRIu8 ", "
1184 "mPendingCommitCount=%" PRIu8 ", mIsChildIgnoringCompositionEvents=%s",
1185 this, aWidget, ToChar(aMessage), mPendingEventsNeedingAck,
1186 GetBoolName(mWidgetHasComposition), mPendingCompositionCount,
1187 mPendingCommitCount, GetBoolName(mIsChildIgnoringCompositionEvents)));
1189 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1190 mReceivedEventMessages.AppendElement(aMessage);
1191 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1193 bool isCommittedInChild =
1194 // Commit requester in the remote process has committed the composition.
1195 aMessage == eCompositionCommitRequestHandled ||
1196 // The commit event has been handled normally in the remote process.
1197 (!mIsChildIgnoringCompositionEvents &&
1198 WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage));
1200 if (isCommittedInChild) {
1201 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1202 if (mPendingCompositionCount == 1) {
1203 RemoveUnnecessaryEventMessageLog();
1205 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1207 if (NS_WARN_IF(!mPendingCompositionCount)) {
1208 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1209 nsPrintfCString info(
1210 "\nThere is no pending composition but received %s "
1211 "message from the remote child\n\n",
1212 ToChar(aMessage));
1213 AppendEventMessageLog(info);
1214 CrashReporter::AppendAppNotesToCrashReport(info);
1215 MOZ_DIAGNOSTIC_ASSERT(
1216 false, "No pending composition but received unexpected commit event");
1217 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1219 // Prevent odd behavior in release channel.
1220 mPendingCompositionCount = 1;
1223 mPendingCompositionCount--;
1225 // Forget composition string only when the latest composition string is
1226 // handled in the remote process because if there is 2 or more pending
1227 // composition, this value shouldn't be referred.
1228 if (!mPendingCompositionCount) {
1229 mCompositionString.Truncate();
1232 // Forget pending commit string length if it's handled in the remote
1233 // process. Note that this doesn't care too old composition's commit
1234 // string because in such case, we cannot return proper information
1235 // to IME synchornously.
1236 mPendingCommitLength = 0;
1239 if (WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage)) {
1240 // After the remote process receives eCompositionCommit(AsIs) event,
1241 // it'll restart to handle composition events.
1242 mIsChildIgnoringCompositionEvents = false;
1244 if (NS_WARN_IF(!mPendingCommitCount)) {
1245 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1246 nsPrintfCString info(
1247 "\nThere is no pending comment events but received "
1248 "%s message from the remote child\n\n",
1249 ToChar(aMessage));
1250 AppendEventMessageLog(info);
1251 CrashReporter::AppendAppNotesToCrashReport(info);
1252 MOZ_DIAGNOSTIC_ASSERT(
1253 false,
1254 "No pending commit events but received unexpected commit event");
1255 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1257 // Prevent odd behavior in release channel.
1258 mPendingCommitCount = 1;
1261 mPendingCommitCount--;
1262 } else if (aMessage == eCompositionCommitRequestHandled &&
1263 mPendingCommitCount) {
1264 // If the remote process commits composition synchronously after
1265 // requesting commit composition and we've already sent commit composition,
1266 // it starts to ignore following composition events until receiving
1267 // eCompositionStart event.
1268 mIsChildIgnoringCompositionEvents = true;
1271 // If neither widget (i.e., IME) nor the remote process has composition,
1272 // now, we can forget composition string informations.
1273 if (!mWidgetHasComposition && !mPendingCompositionCount &&
1274 !mPendingCommitCount) {
1275 mCompositionStart = UINT32_MAX;
1278 if (NS_WARN_IF(!mPendingEventsNeedingAck)) {
1279 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1280 nsPrintfCString info(
1281 "\nThere is no pending events but received %s "
1282 "message from the remote child\n\n",
1283 ToChar(aMessage));
1284 AppendEventMessageLog(info);
1285 CrashReporter::AppendAppNotesToCrashReport(info);
1286 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1287 MOZ_DIAGNOSTIC_ASSERT(
1288 false, "No pending event message but received unexpected event");
1289 mPendingEventsNeedingAck = 1;
1291 if (--mPendingEventsNeedingAck) {
1292 return;
1295 FlushPendingNotifications(aWidget);
1298 bool ContentCacheInParent::RequestIMEToCommitComposition(
1299 nsIWidget* aWidget, bool aCancel, nsAString& aCommittedString) {
1300 MOZ_LOG(
1301 sContentCacheLog, LogLevel::Info,
1302 ("0x%p RequestToCommitComposition(aWidget=%p, "
1303 "aCancel=%s), mPendingCompositionCount=%" PRIu8 ", "
1304 "mPendingCommitCount=%" PRIu8 ", mIsChildIgnoringCompositionEvents=%s, "
1305 "IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)=%s, "
1306 "mWidgetHasComposition=%s, mCommitStringByRequest=%p",
1307 this, aWidget, GetBoolName(aCancel), mPendingCompositionCount,
1308 mPendingCommitCount, GetBoolName(mIsChildIgnoringCompositionEvents),
1309 GetBoolName(
1310 IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)),
1311 GetBoolName(mWidgetHasComposition), mCommitStringByRequest));
1313 MOZ_ASSERT(!mCommitStringByRequest);
1315 // If there are 2 or more pending compositions, we already sent
1316 // eCompositionCommit(AsIs) to the remote process. So, this request is
1317 // too late for IME. The remote process should wait following
1318 // composition events for cleaning up TextComposition and handle the
1319 // request as it's handled asynchronously.
1320 if (mPendingCompositionCount > 1) {
1321 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1322 mRequestIMEToCommitCompositionResults.AppendElement(
1323 RequestIMEToCommitCompositionResult::eToOldCompositionReceived);
1324 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1325 return false;
1328 // If there is no pending composition, we may have already sent
1329 // eCompositionCommit(AsIs) event for the active composition. If so, the
1330 // remote process will receive composition events which causes cleaning up
1331 // TextComposition. So, this shouldn't do nothing and TextComposition
1332 // should handle the request as it's handled asynchronously.
1333 // XXX Perhaps, this is wrong because TextComposition in child process
1334 // may commit the composition with current composition string in the
1335 // remote process. I.e., it may be different from actual commit string
1336 // which user typed. So, perhaps, we should return true and the commit
1337 // string.
1338 if (mPendingCommitCount) {
1339 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1340 mRequestIMEToCommitCompositionResults.AppendElement(
1341 RequestIMEToCommitCompositionResult::eToCommittedCompositionReceived);
1342 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1343 return false;
1346 // If BrowserParent which has IME focus was already changed to different one,
1347 // the request shouldn't be sent to IME because it's too late.
1348 if (!IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)) {
1349 // Use the latest composition string which may not be handled in the
1350 // remote process for avoiding data loss.
1351 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1352 mRequestIMEToCommitCompositionResults.AppendElement(
1353 RequestIMEToCommitCompositionResult::eReceivedAfterBrowserParentBlur);
1354 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1355 aCommittedString = mCompositionString;
1356 // After we return true from here, i.e., without actually requesting IME
1357 // to commit composition, we will receive eCompositionCommitRequestHandled
1358 // pseudo event message from the remote process. So, we need to increment
1359 // mPendingEventsNeedingAck here.
1360 mPendingEventsNeedingAck++;
1361 return true;
1364 RefPtr<TextComposition> composition =
1365 IMEStateManager::GetTextCompositionFor(aWidget);
1366 if (NS_WARN_IF(!composition)) {
1367 MOZ_LOG(sContentCacheLog, LogLevel::Warning,
1368 (" 0x%p RequestToCommitComposition(), "
1369 "does nothing due to no composition",
1370 this));
1371 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1372 mRequestIMEToCommitCompositionResults.AppendElement(
1373 RequestIMEToCommitCompositionResult::eReceivedButNoTextComposition);
1374 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1375 return false;
1378 mCommitStringByRequest = &aCommittedString;
1380 // Request commit or cancel composition with TextComposition because we may
1381 // have already requested to commit or cancel the composition or we may
1382 // have already received eCompositionCommit(AsIs) event. Those status are
1383 // managed by composition. So, if we don't request commit composition,
1384 // we should do nothing with native IME here.
1385 composition->RequestToCommit(aWidget, aCancel);
1387 mCommitStringByRequest = nullptr;
1389 MOZ_LOG(
1390 sContentCacheLog, LogLevel::Info,
1391 (" 0x%p RequestToCommitComposition(), "
1392 "mWidgetHasComposition=%s, the composition %s committed synchronously",
1393 this, GetBoolName(mWidgetHasComposition),
1394 composition->Destroyed() ? "WAS" : "has NOT been"));
1396 if (!composition->Destroyed()) {
1397 // When the composition isn't committed synchronously, the remote process's
1398 // TextComposition instance will synthesize commit events and wait to
1399 // receive delayed composition events. When TextComposition instances both
1400 // in this process and the remote process will be destroyed when delayed
1401 // composition events received. TextComposition instance in the parent
1402 // process will dispatch following composition events and be destroyed
1403 // normally. On the other hand, TextComposition instance in the remote
1404 // process won't dispatch following composition events and will be
1405 // destroyed by IMEStateManager::DispatchCompositionEvent().
1406 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1407 mRequestIMEToCommitCompositionResults.AppendElement(
1408 RequestIMEToCommitCompositionResult::eHandledAsynchronously);
1409 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1410 return false;
1413 // When the composition is committed synchronously, the commit string will be
1414 // returned to the remote process. Then, PuppetWidget will dispatch
1415 // eCompositionCommit event with the returned commit string (i.e., the value
1416 // is aCommittedString of this method) and that causes destroying
1417 // TextComposition instance in the remote process (Note that TextComposition
1418 // instance in this process was already destroyed).
1419 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1420 mRequestIMEToCommitCompositionResults.AppendElement(
1421 RequestIMEToCommitCompositionResult::eHandledSynchronously);
1422 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1423 return true;
1426 void ContentCacheInParent::MaybeNotifyIME(
1427 nsIWidget* aWidget, const IMENotification& aNotification) {
1428 if (!mPendingEventsNeedingAck) {
1429 IMEStateManager::NotifyIME(aNotification, aWidget, &mBrowserParent);
1430 return;
1433 switch (aNotification.mMessage) {
1434 case NOTIFY_IME_OF_SELECTION_CHANGE:
1435 mPendingSelectionChange.MergeWith(aNotification);
1436 break;
1437 case NOTIFY_IME_OF_TEXT_CHANGE:
1438 mPendingTextChange.MergeWith(aNotification);
1439 break;
1440 case NOTIFY_IME_OF_POSITION_CHANGE:
1441 mPendingLayoutChange.MergeWith(aNotification);
1442 break;
1443 case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
1444 mPendingCompositionUpdate.MergeWith(aNotification);
1445 break;
1446 default:
1447 MOZ_CRASH("Unsupported notification");
1448 break;
1452 void ContentCacheInParent::FlushPendingNotifications(nsIWidget* aWidget) {
1453 MOZ_ASSERT(!mPendingEventsNeedingAck);
1455 // If the BrowserParent's widget has already gone, this can do nothing since
1456 // widget is necessary to notify IME of something.
1457 if (!aWidget) {
1458 return;
1461 // New notifications which are notified during flushing pending notifications
1462 // should be merged again.
1463 mPendingEventsNeedingAck++;
1465 nsCOMPtr<nsIWidget> widget = aWidget;
1467 // First, text change notification should be sent because selection change
1468 // notification notifies IME of current selection range in the latest content.
1469 // So, IME may need the latest content before that.
1470 if (mPendingTextChange.HasNotification()) {
1471 IMENotification notification(mPendingTextChange);
1472 if (!widget->Destroyed()) {
1473 mPendingTextChange.Clear();
1474 IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
1478 if (mPendingSelectionChange.HasNotification()) {
1479 IMENotification notification(mPendingSelectionChange);
1480 if (!widget->Destroyed()) {
1481 mPendingSelectionChange.Clear();
1482 IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
1486 // Layout change notification should be notified after selection change
1487 // notification because IME may want to query position of new caret position.
1488 if (mPendingLayoutChange.HasNotification()) {
1489 IMENotification notification(mPendingLayoutChange);
1490 if (!widget->Destroyed()) {
1491 mPendingLayoutChange.Clear();
1492 IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
1496 // Finally, send composition update notification because it notifies IME of
1497 // finishing handling whole sending events.
1498 if (mPendingCompositionUpdate.HasNotification()) {
1499 IMENotification notification(mPendingCompositionUpdate);
1500 if (!widget->Destroyed()) {
1501 mPendingCompositionUpdate.Clear();
1502 IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
1506 if (!--mPendingEventsNeedingAck && !widget->Destroyed() &&
1507 (mPendingTextChange.HasNotification() ||
1508 mPendingSelectionChange.HasNotification() ||
1509 mPendingLayoutChange.HasNotification() ||
1510 mPendingCompositionUpdate.HasNotification())) {
1511 FlushPendingNotifications(widget);
1515 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1517 void ContentCacheInParent::RemoveUnnecessaryEventMessageLog() {
1518 bool foundLastCompositionStart = false;
1519 for (size_t i = mDispatchedEventMessages.Length(); i > 1; i--) {
1520 if (mDispatchedEventMessages[i - 1] != eCompositionStart) {
1521 continue;
1523 if (!foundLastCompositionStart) {
1524 // Find previous eCompositionStart of the latest eCompositionStart.
1525 foundLastCompositionStart = true;
1526 continue;
1528 // Remove the messages before the last 2 sets of composition events.
1529 mDispatchedEventMessages.RemoveElementsAt(0, i - 1);
1530 break;
1532 uint32_t numberOfCompositionCommitRequestHandled = 0;
1533 foundLastCompositionStart = false;
1534 for (size_t i = mReceivedEventMessages.Length(); i > 1; i--) {
1535 if (mReceivedEventMessages[i - 1] == eCompositionCommitRequestHandled) {
1536 numberOfCompositionCommitRequestHandled++;
1538 if (mReceivedEventMessages[i - 1] != eCompositionStart) {
1539 continue;
1541 if (!foundLastCompositionStart) {
1542 // Find previous eCompositionStart of the latest eCompositionStart.
1543 foundLastCompositionStart = true;
1544 continue;
1546 // Remove the messages before the last 2 sets of composition events.
1547 mReceivedEventMessages.RemoveElementsAt(0, i - 1);
1548 break;
1551 if (!numberOfCompositionCommitRequestHandled) {
1552 // If there is no eCompositionCommitRequestHandled in
1553 // mReceivedEventMessages, we don't need to store log of
1554 // RequestIMEToCommmitComposition().
1555 mRequestIMEToCommitCompositionResults.Clear();
1556 } else {
1557 // We need to keep all reason of eCompositionCommitRequestHandled, which
1558 // is sent when mRequestIMEToCommitComposition() returns true.
1559 // So, we can discard older log than the first
1560 // eCompositionCommitRequestHandled in mReceivedEventMessages.
1561 for (size_t i = mRequestIMEToCommitCompositionResults.Length(); i > 1;
1562 i--) {
1563 if (mRequestIMEToCommitCompositionResults[i - 1] ==
1564 RequestIMEToCommitCompositionResult::
1565 eReceivedAfterBrowserParentBlur ||
1566 mRequestIMEToCommitCompositionResults[i - 1] ==
1567 RequestIMEToCommitCompositionResult::eHandledSynchronously) {
1568 --numberOfCompositionCommitRequestHandled;
1569 if (!numberOfCompositionCommitRequestHandled) {
1570 mRequestIMEToCommitCompositionResults.RemoveElementsAt(0, i - 1);
1571 break;
1578 void ContentCacheInParent::AppendEventMessageLog(nsACString& aLog) const {
1579 aLog.AppendLiteral("Dispatched Event Message Log:\n");
1580 for (EventMessage message : mDispatchedEventMessages) {
1581 aLog.AppendLiteral(" ");
1582 aLog.Append(ToChar(message));
1583 aLog.AppendLiteral("\n");
1585 aLog.AppendLiteral("\nReceived Event Message Log:\n");
1586 for (EventMessage message : mReceivedEventMessages) {
1587 aLog.AppendLiteral(" ");
1588 aLog.Append(ToChar(message));
1589 aLog.AppendLiteral("\n");
1591 aLog.AppendLiteral("\nResult of RequestIMEToCommitComposition():\n");
1592 for (RequestIMEToCommitCompositionResult result :
1593 mRequestIMEToCommitCompositionResults) {
1594 aLog.AppendLiteral(" ");
1595 aLog.Append(ToReadableText(result));
1596 aLog.AppendLiteral("\n");
1598 aLog.AppendLiteral("\n");
1601 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1603 /*****************************************************************************
1604 * mozilla::ContentCache::TextRectArray
1605 *****************************************************************************/
1607 LayoutDeviceIntRect ContentCache::TextRectArray::GetRect(
1608 uint32_t aOffset) const {
1609 LayoutDeviceIntRect rect;
1610 if (InRange(aOffset)) {
1611 rect = mRects[aOffset - mStart];
1613 return rect;
1616 LayoutDeviceIntRect ContentCache::TextRectArray::GetUnionRect(
1617 uint32_t aOffset, uint32_t aLength) const {
1618 LayoutDeviceIntRect rect;
1619 if (!InRange(aOffset, aLength)) {
1620 return rect;
1622 for (uint32_t i = 0; i < aLength; i++) {
1623 rect = rect.Union(mRects[aOffset - mStart + i]);
1625 return rect;
1628 LayoutDeviceIntRect ContentCache::TextRectArray::GetUnionRectAsFarAsPossible(
1629 uint32_t aOffset, uint32_t aLength, bool aRoundToExistingOffset) const {
1630 LayoutDeviceIntRect rect;
1631 if (!HasRects() ||
1632 (!aRoundToExistingOffset && !IsOverlappingWith(aOffset, aLength))) {
1633 return rect;
1635 uint32_t startOffset = std::max(aOffset, mStart);
1636 if (aRoundToExistingOffset && startOffset >= EndOffset()) {
1637 startOffset = EndOffset() - 1;
1639 uint32_t endOffset = std::min(aOffset + aLength, EndOffset());
1640 if (aRoundToExistingOffset && endOffset < mStart + 1) {
1641 endOffset = mStart + 1;
1643 if (NS_WARN_IF(endOffset < startOffset)) {
1644 return rect;
1646 for (uint32_t i = 0; i < endOffset - startOffset; i++) {
1647 rect = rect.Union(mRects[startOffset - mStart + i]);
1649 return rect;
1652 } // namespace mozilla