Bug 1608587 [wpt PR 21137] - Update wpt metadata, a=testonly
[gecko.git] / widget / ContentCache.cpp
blob65e32b4aa990defdea3e56f539f976efae45ea7f
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 "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"
21 namespace mozilla {
23 using namespace dom;
24 using namespace widget;
26 static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; }
28 static const char* GetNotificationName(const IMENotification* aNotification) {
29 if (!aNotification) {
30 return "Not notification";
32 return ToChar(aNotification->mMessage);
35 class GetRectText : public nsAutoCString {
36 public:
37 explicit GetRectText(const LayoutDeviceIntRect& aRect) {
38 AssignLiteral("{ x=");
39 AppendInt(aRect.X());
40 AppendLiteral(", y=");
41 AppendInt(aRect.Y());
42 AppendLiteral(", width=");
43 AppendInt(aRect.Width());
44 AppendLiteral(", height=");
45 AppendInt(aRect.Height());
46 AppendLiteral(" }");
48 virtual ~GetRectText() {}
51 class GetWritingModeName : public nsAutoCString {
52 public:
53 explicit GetWritingModeName(const WritingMode& aWritingMode) {
54 if (!aWritingMode.IsVertical()) {
55 AssignLiteral("Horizontal");
56 return;
58 if (aWritingMode.IsVerticalLR()) {
59 AssignLiteral("Vertical (LTR)");
60 return;
62 AssignLiteral("Vertical (RTL)");
64 virtual ~GetWritingModeName() {}
67 class GetEscapedUTF8String final : public NS_ConvertUTF16toUTF8 {
68 public:
69 explicit GetEscapedUTF8String(const nsAString& aString)
70 : NS_ConvertUTF16toUTF8(aString) {
71 Escape();
73 explicit GetEscapedUTF8String(const char16ptr_t aString)
74 : NS_ConvertUTF16toUTF8(aString) {
75 Escape();
77 GetEscapedUTF8String(const char16ptr_t aString, uint32_t aLength)
78 : NS_ConvertUTF16toUTF8(aString, aLength) {
79 Escape();
82 private:
83 void Escape() {
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;
108 mText.Truncate();
109 mSelection.Clear();
110 mFirstCharRect.SetEmpty();
111 mCaret.Clear();
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))) {
124 return false;
126 return true;
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)));
135 mCaret.Clear();
136 mSelection.Clear();
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",
145 this));
146 return false;
148 if (selection.mReply.mReversed) {
149 mSelection.mAnchor =
150 selection.mReply.mOffset + selection.mReply.mString.Length();
151 mSelection.mFocus = selection.mReply.mOffset;
152 } else {
153 mSelection.mAnchor = selection.mReply.mOffset;
154 mSelection.mFocus =
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)));
169 mCaret.Clear();
171 if (NS_WARN_IF(!mSelection.IsValid())) {
172 return false;
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));
187 mCaret.Clear();
188 return false;
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()));
198 return true;
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",
214 this));
215 return false;
217 mEditorRect = editorRectEvent.mReply.mRect;
218 MOZ_LOG(sContentCacheLog, LogLevel::Info,
219 ("0x%p CacheEditorRect(), Succeeded, "
220 "mEditorRect=%s",
221 this, GetRectText(mEditorRect).get()));
222 return true;
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));
238 mText.Truncate();
239 return false;
241 mText = queryText.mReply.mString;
242 MOZ_LOG(
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)) {
258 return false;
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);
269 return true;
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();
281 return false;
283 aCharRectArray = std::move(textRects.mReply.mRectArray);
284 return true;
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())) {
303 return false;
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
318 // composition too.
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",
326 this));
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);
338 } else {
339 RectArray rects;
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))) {
343 MOZ_LOG(
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());
350 } else {
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);
373 } else {
374 RectArray rects;
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))) {
378 MOZ_LOG(
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());
385 } else {
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",
406 this));
407 } else {
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);
422 } else {
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",
428 this));
429 } else {
430 mFirstCharRect = charRect;
434 MOZ_LOG(
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(),
446 mSelection.mFocus,
447 GetRectText(mSelection.mFocusCharRects[eNextCharRect]).get(),
448 GetRectText(mSelection.mFocusCharRects[ePrevCharRect]).get(),
449 GetRectText(mSelection.mRect).get(), GetRectText(mFirstCharRect).get()));
450 return true;
453 void ContentCacheInChild::SetSelection(nsIWidget* aWidget,
454 uint32_t aStartOffset, uint32_t aLength,
455 bool aReversed,
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()));
463 if (!aReversed) {
464 mSelection.mAnchor = aStartOffset;
465 mSelection.mFocus = aStartOffset + aLength;
466 } else {
467 mSelection.mAnchor = aStartOffset + aLength;
468 mSelection.mFocus = aStartOffset;
470 mSelection.mWritingMode = aWritingMode;
472 if (NS_WARN_IF(!CacheCaret(aWidget))) {
473 return;
475 Unused << NS_WARN_IF(!CacheTextRects(aWidget));
478 /*****************************************************************************
479 * mozilla::ContentCacheInParent
480 *****************************************************************************/
482 ContentCacheInParent::ContentCacheInParent(BrowserParent& aBrowserParent)
483 : ContentCache(),
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,
495 nsIWidget* aWidget,
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
515 // string.
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");
532 MOZ_LOG(
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 {
559 MOZ_ASSERT(aWidget);
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 "
570 "linebreaks",
571 this));
572 return false;
575 if (NS_WARN_IF(!aEvent.mInput.IsValidOffset())) {
576 MOZ_LOG(
577 sContentCacheLog, LogLevel::Error,
578 ("0x%p HandleQueryContentEvent(), FAILED due to invalid offset", this));
579 return false;
582 if (NS_WARN_IF(!aEvent.mInput.IsValidEventMessage(aEvent.mMessage))) {
583 MOZ_LOG(
584 sContentCacheLog, LogLevel::Error,
585 ("0x%p HandleQueryContentEvent(), FAILED due to invalid event message",
586 this));
587 return false;
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={ "
597 "mMessage=%s, "
598 "mInput={ mOffset=%" PRId64 ", mLength=%" PRIu32 " } }",
599 this, ToChar(aEvent.mMessage), aEvent.mInput.mOffset,
600 aEvent.mInput.mLength));
601 return false;
603 } else if (mWidgetHasComposition || mPendingCommitCount) {
604 if (NS_WARN_IF(!aEvent.mInput.MakeOffsetAbsolute(mCompositionStart +
605 mPendingCommitLength))) {
606 MOZ_LOG(
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));
617 return false;
619 } else if (NS_WARN_IF(!mSelection.IsValid())) {
620 MOZ_LOG(sContentCacheLog, LogLevel::Error,
621 ("0x%p HandleQueryContentEvent(), FAILED due to mSelection is "
622 "invalid",
623 this));
624 return false;
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));
637 return false;
641 switch (aEvent.mMessage) {
642 case eQuerySelectedText:
643 MOZ_LOG(sContentCacheLog, LogLevel::Info,
644 ("0x%p HandleQueryContentEvent("
645 "aEvent={ mMessage=eQuerySelectedText }, aWidget=0x%p)",
646 this, aWidget));
647 if (aWidget->PluginHasFocus()) {
648 MOZ_LOG(sContentCacheLog, LogLevel::Info,
649 ("0x%p HandleQueryContentEvent(), "
650 "return emtpy selection becasue plugin has focus",
651 this));
652 aEvent.mSucceeded = true;
653 aEvent.mReply.mOffset = 0;
654 aEvent.mReply.mReversed = false;
655 aEvent.mReply.mHasSelection = false;
656 return true;
658 if (NS_WARN_IF(!IsSelectionValid())) {
659 // If content cache hasn't been initialized properly, make the query
660 // failed.
661 MOZ_LOG(sContentCacheLog, LogLevel::Error,
662 ("0x%p HandleQueryContentEvent(), "
663 "FAILED because mSelection is not valid",
664 this));
665 return true;
667 aEvent.mReply.mOffset = mSelection.StartOffset();
668 if (mSelection.Collapsed()) {
669 aEvent.mReply.mString.Truncate(0);
670 } else {
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 "
675 "mText.Length()=%u",
676 this, mSelection.EndOffset(), mText.Length()));
677 return false;
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()));
694 break;
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,
701 mText.Length()));
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)) {
706 MOZ_LOG(
707 sContentCacheLog, LogLevel::Error,
708 ("0x%p HandleQueryContentEvent(), "
709 "FAILED because inputOffset=%u is larger than inputEndOffset=%u",
710 this, inputOffset, inputEndOffset));
711 return false;
713 aEvent.mReply.mOffset = inputOffset;
714 aEvent.mReply.mString =
715 Substring(mText, inputOffset, inputEndOffset - inputOffset);
716 MOZ_LOG(
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()));
721 break;
723 case eQueryTextRect:
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,
729 mText.Length()));
730 if (NS_WARN_IF(!IsSelectionValid())) {
731 // If content cache hasn't been initialized properly, make the query
732 // failed.
733 MOZ_LOG(sContentCacheLog, LogLevel::Error,
734 ("0x%p HandleQueryContentEvent(), "
735 "FAILED because mSelection is not valid",
736 this));
737 return true;
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
742 // at odd position.
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",
751 this));
752 return false;
754 } else {
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",
762 this));
763 return false;
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
770 : UINT32_MAX);
771 } else {
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()));
785 break;
786 case eQueryCaretRect:
787 MOZ_LOG(sContentCacheLog, LogLevel::Info,
788 ("0x%p HandleQueryContentEvent("
789 "aEvent={ mMessage=eQueryCaretRect, mInput={ mOffset=%" PRId64
790 " } }, "
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
795 // failed.
796 MOZ_LOG(sContentCacheLog, LogLevel::Error,
797 ("0x%p HandleQueryContentEvent(), "
798 "FAILED because mSelection is not valid",
799 this));
800 return true;
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
805 // at odd position.
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",
812 this));
813 return false;
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()));
821 break;
822 case eQueryEditorRect:
823 MOZ_LOG(sContentCacheLog, LogLevel::Info,
824 ("0x%p HandleQueryContentEvent("
825 "aEvent={ mMessage=eQueryEditorRect }, aWidget=0x%p)",
826 this, aWidget));
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()));
832 break;
833 default:
834 break;
836 aEvent.mSucceeded = true;
837 return 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));
852 if (!aOffset) {
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(),
859 "empty rect");
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(),
865 "empty rect");
866 aTextRect = mSelection.mAnchorCharRects[ePrevCharRect];
867 return !aTextRect.IsEmpty();
869 if (aOffset == mSelection.mFocus) {
870 NS_WARNING_ASSERTION(!mSelection.mFocusCharRects[eNextCharRect].IsEmpty(),
871 "empty rect");
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(),
877 "empty rect");
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();
886 return false;
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
891 // no composition.
892 aTextRect = mSelection.StartCharRect();
893 return !aTextRect.IsEmpty();
895 if (offset < mTextRectArray.StartOffset()) {
896 offset = mTextRectArray.StartOffset();
897 } else {
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()) {
919 return false;
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();
929 if (aLength == 1) {
930 if (!aOffset) {
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(),
949 "empty rect");
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(),
955 "empty rect");
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
964 // in most cases.
966 if (!aOffset && aOffset != mSelection.mAnchor &&
967 aOffset != mSelection.mFocus && !mTextRectArray.InRange(aOffset)) {
968 // The first character rect isn't cached.
969 return false;
972 if ((aRoundToExistingOffset && mTextRectArray.HasRects()) ||
973 mTextRectArray.IsOverlappingWith(aOffset, aLength)) {
974 aUnionTextRect = mTextRectArray.GetUnionRectAsFarAsPossible(
975 aOffset, aLength, aRoundToExistingOffset);
976 } else {
977 aUnionTextRect.SetEmpty();
980 if (!aOffset) {
981 aUnionTextRect = aUnionTextRect.Union(mFirstCharRect);
983 if (aOffset <= mSelection.mAnchor && mSelection.mAnchor < endOffset.value()) {
984 aUnionTextRect =
985 aUnionTextRect.Union(mSelection.mAnchorCharRects[eNextCharRect]);
987 if (mSelection.mAnchor && aOffset <= mSelection.mAnchor - 1 &&
988 mSelection.mAnchor - 1 < endOffset.value()) {
989 aUnionTextRect =
990 aUnionTextRect.Union(mSelection.mAnchorCharRects[ePrevCharRect]);
992 if (aOffset <= mSelection.mFocus && mSelection.mFocus < endOffset.value()) {
993 aUnionTextRect =
994 aUnionTextRect.Union(mSelection.mFocusCharRects[eNextCharRect]);
996 if (mSelection.mFocus && aOffset <= mSelection.mFocus - 1 &&
997 mSelection.mFocus - 1 < endOffset.value()) {
998 aUnionTextRect =
999 aUnionTextRect.Union(mSelection.mFocusCharRects[ePrevCharRect]);
1002 return !aUnionTextRect.IsEmpty();
1005 bool ContentCacheInParent::GetCaretRect(uint32_t aOffset,
1006 bool aRoundToExistingOffset,
1007 LayoutDeviceIntRect& aCaretRect) const {
1008 MOZ_LOG(
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;
1030 return true;
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.
1037 if (!aOffset ||
1038 !GetTextRect(aOffset - 1, aRoundToExistingOffset, aCaretRect)) {
1039 aCaretRect.SetEmpty();
1040 return false;
1043 if (mSelection.mWritingMode.IsVertical()) {
1044 aCaretRect.MoveToY(aCaretRect.YMost());
1045 } else {
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);
1056 } else {
1057 aCaretRect.SetWidth(mCaret.IsValid() ? mCaret.mRect.Width() : 1);
1059 return true;
1062 bool ContentCacheInParent::OnCompositionEvent(
1063 const WidgetCompositionEvent& aEvent) {
1064 MOZ_LOG(
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;
1093 } else {
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;
1124 } else {
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--;
1141 return false;
1144 mPendingEventsNeedingAck++;
1145 return true;
1148 void ContentCacheInParent::OnSelectionEvent(
1149 const WidgetSelectionEvent& aSelectionEvent) {
1150 MOZ_LOG(
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.
1178 MOZ_LOG(
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",
1211 ToChar(aMessage));
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",
1248 ToChar(aMessage));
1249 AppendEventMessageLog(info);
1250 CrashReporter::AppendAppNotesToCrashReport(info);
1251 MOZ_DIAGNOSTIC_ASSERT(
1252 false,
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",
1282 ToChar(aMessage));
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) {
1291 return;
1294 FlushPendingNotifications(aWidget);
1297 bool ContentCacheInParent::RequestIMEToCommitComposition(
1298 nsIWidget* aWidget, bool aCancel, nsAString& aCommittedString) {
1299 MOZ_LOG(
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),
1308 GetBoolName(
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
1324 return false;
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
1336 // string.
1337 if (mPendingCommitCount) {
1338 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1339 mRequestIMEToCommitCompositionResults.AppendElement(
1340 RequestIMEToCommitCompositionResult::eToCommittedCompositionReceived);
1341 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1342 return false;
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++;
1360 return true;
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",
1369 this));
1370 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1371 mRequestIMEToCommitCompositionResults.AppendElement(
1372 RequestIMEToCommitCompositionResult::eReceivedButNoTextComposition);
1373 #endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
1374 return false;
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;
1388 MOZ_LOG(
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
1409 return false;
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
1422 return true;
1425 void ContentCacheInParent::MaybeNotifyIME(
1426 nsIWidget* aWidget, const IMENotification& aNotification) {
1427 if (!mPendingEventsNeedingAck) {
1428 IMEStateManager::NotifyIME(aNotification, aWidget, &mBrowserParent);
1429 return;
1432 switch (aNotification.mMessage) {
1433 case NOTIFY_IME_OF_SELECTION_CHANGE:
1434 mPendingSelectionChange.MergeWith(aNotification);
1435 break;
1436 case NOTIFY_IME_OF_TEXT_CHANGE:
1437 mPendingTextChange.MergeWith(aNotification);
1438 break;
1439 case NOTIFY_IME_OF_POSITION_CHANGE:
1440 mPendingLayoutChange.MergeWith(aNotification);
1441 break;
1442 case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
1443 mPendingCompositionUpdate.MergeWith(aNotification);
1444 break;
1445 default:
1446 MOZ_CRASH("Unsupported notification");
1447 break;
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.
1456 if (!aWidget) {
1457 return;
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) {
1520 continue;
1522 if (!foundLastCompositionStart) {
1523 // Find previous eCompositionStart of the latest eCompositionStart.
1524 foundLastCompositionStart = true;
1525 continue;
1527 // Remove the messages before the last 2 sets of composition events.
1528 mDispatchedEventMessages.RemoveElementsAt(0, i - 1);
1529 break;
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) {
1538 continue;
1540 if (!foundLastCompositionStart) {
1541 // Find previous eCompositionStart of the latest eCompositionStart.
1542 foundLastCompositionStart = true;
1543 continue;
1545 // Remove the messages before the last 2 sets of composition events.
1546 mReceivedEventMessages.RemoveElementsAt(0, i - 1);
1547 break;
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();
1555 } else {
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;
1561 i--) {
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);
1570 break;
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];
1612 return rect;
1615 LayoutDeviceIntRect ContentCache::TextRectArray::GetUnionRect(
1616 uint32_t aOffset, uint32_t aLength) const {
1617 LayoutDeviceIntRect rect;
1618 if (!InRange(aOffset, aLength)) {
1619 return rect;
1621 for (uint32_t i = 0; i < aLength; i++) {
1622 rect = rect.Union(mRects[aOffset - mStart + i]);
1624 return rect;
1627 LayoutDeviceIntRect ContentCache::TextRectArray::GetUnionRectAsFarAsPossible(
1628 uint32_t aOffset, uint32_t aLength, bool aRoundToExistingOffset) const {
1629 LayoutDeviceIntRect rect;
1630 if (!HasRects() ||
1631 (!aRoundToExistingOffset && !IsOverlappingWith(aOffset, aLength))) {
1632 return rect;
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)) {
1643 return rect;
1645 for (uint32_t i = 0; i < endOffset - startOffset; i++) {
1646 rect = rect.Union(mRects[startOffset - mStart + i]);
1648 return rect;
1651 } // namespace mozilla