forked from mirrors/gecko-dev
		
	 d0f6c7fc66
			
		
	
	
		d0f6c7fc66
		
	
	
	
	
		
			
			Done with: ./mach static-analysis check --checks="-*, readability-redundant-member-init" --fix . https://clang.llvm.org/extra/clang-tidy/checks/readability/redundant-member-init.html Differential Revision: https://phabricator.services.mozilla.com/D190002
		
			
				
	
	
		
			2011 lines
		
	
	
	
		
			83 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			2011 lines
		
	
	
	
		
			83 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 | |
|  * vim: sw=2 ts=8 et :
 | |
|  */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "ContentCache.h"
 | |
| 
 | |
| #include <utility>
 | |
| 
 | |
| #include "IMEData.h"
 | |
| #include "TextEvents.h"
 | |
| 
 | |
| #include "mozilla/Assertions.h"
 | |
| #include "mozilla/IMEStateManager.h"
 | |
| #include "mozilla/IntegerPrintfMacros.h"
 | |
| #include "mozilla/Logging.h"
 | |
| #include "mozilla/RefPtr.h"
 | |
| #include "mozilla/TextComposition.h"
 | |
| #include "mozilla/dom/BrowserParent.h"
 | |
| #include "nsExceptionHandler.h"
 | |
| #include "nsIWidget.h"
 | |
| #include "nsPrintfCString.h"
 | |
| 
 | |
| namespace mozilla {
 | |
| 
 | |
| using namespace dom;
 | |
| using namespace widget;
 | |
| 
 | |
| static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; }
 | |
| 
 | |
| static const char* GetNotificationName(const IMENotification* aNotification) {
 | |
|   if (!aNotification) {
 | |
|     return "Not notification";
 | |
|   }
 | |
|   return ToChar(aNotification->mMessage);
 | |
| }
 | |
| 
 | |
| /*****************************************************************************
 | |
|  * mozilla::ContentCache
 | |
|  *****************************************************************************/
 | |
| 
 | |
| LazyLogModule sContentCacheLog("ContentCacheWidgets");
 | |
| 
 | |
| bool ContentCache::IsValid() const {
 | |
|   if (mText.isNothing()) {
 | |
|     // mSelection and mCaret depend on mText.
 | |
|     if (NS_WARN_IF(mSelection.isSome()) || NS_WARN_IF(mCaret.isSome())) {
 | |
|       return false;
 | |
|     }
 | |
|   } else {
 | |
|     // mSelection depends on mText.
 | |
|     if (mSelection.isSome() && NS_WARN_IF(!mSelection->IsValidIn(*mText))) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     // mCaret depends on mSelection.
 | |
|     if (mCaret.isSome() &&
 | |
|         (NS_WARN_IF(mSelection.isNothing()) ||
 | |
|          NS_WARN_IF(!mSelection->mHasRange) ||
 | |
|          NS_WARN_IF(mSelection->StartOffset() != mCaret->Offset()))) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // mTextRectArray stores character rects around composition string.
 | |
|   // Note that even if we fail to collect the rects, we may keep storing
 | |
|   // mCompositionStart.
 | |
|   if (mTextRectArray.isSome()) {
 | |
|     if (NS_WARN_IF(mCompositionStart.isNothing())) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void ContentCache::AssertIfInvalid() const {
 | |
| #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
|   if (IsValid()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // This text will appear in the crash reports without any permissions.
 | |
|   // Do not use `ToString` here to avoid to expose unexpected data with
 | |
|   // changing the type or `operator<<()`.
 | |
|   nsPrintfCString info(
 | |
|       "ContentCache={ mText=%s, mSelection=%s, mCaret=%s, mTextRectArray=%s, "
 | |
|       "mCompositionStart=%s }\n",
 | |
|       // Don't expose mText.ref() value for protecting the user's privacy.
 | |
|       mText.isNothing()
 | |
|           ? "Nothing"
 | |
|           : nsPrintfCString("{ Length()=%zu }", mText->Length()).get(),
 | |
|       mSelection.isNothing()
 | |
|           ? "Nothing"
 | |
|           : nsPrintfCString("{ mAnchor=%u, mFocus=%u }", mSelection->mAnchor,
 | |
|                             mSelection->mFocus)
 | |
|                 .get(),
 | |
|       mCaret.isNothing()
 | |
|           ? "Nothing"
 | |
|           : nsPrintfCString("{ mOffset=%u }", mCaret->mOffset).get(),
 | |
|       mTextRectArray.isNothing()
 | |
|           ? "Nothing"
 | |
|           : nsPrintfCString("{ Length()=%u }", mTextRectArray->Length()).get(),
 | |
|       mCompositionStart.isNothing()
 | |
|           ? "Nothing"
 | |
|           : nsPrintfCString("%u", mCompositionStart.value()).get());
 | |
|   CrashReporter::AppendAppNotesToCrashReport(info);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(false, "Invalid ContentCache data");
 | |
| #endif  // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
| }
 | |
| 
 | |
| /*****************************************************************************
 | |
|  * mozilla::ContentCacheInChild
 | |
|  *****************************************************************************/
 | |
| 
 | |
| void ContentCacheInChild::Clear() {
 | |
|   MOZ_LOG(sContentCacheLog, LogLevel::Info, ("0x%p Clear()", this));
 | |
| 
 | |
|   mCompositionStart.reset();
 | |
|   mLastCommit.reset();
 | |
|   mText.reset();
 | |
|   mSelection.reset();
 | |
|   mFirstCharRect.SetEmpty();
 | |
|   mCaret.reset();
 | |
|   mTextRectArray.reset();
 | |
|   mLastCommitStringTextRectArray.reset();
 | |
|   mEditorRect.SetEmpty();
 | |
| }
 | |
| 
 | |
| void ContentCacheInChild::OnCompositionEvent(
 | |
|     const WidgetCompositionEvent& aCompositionEvent) {
 | |
|   if (aCompositionEvent.CausesDOMCompositionEndEvent()) {
 | |
|     RefPtr<TextComposition> composition =
 | |
|         IMEStateManager::GetTextCompositionFor(aCompositionEvent.mWidget);
 | |
|     if (composition) {
 | |
|       nsAutoString lastCommitString;
 | |
|       if (aCompositionEvent.mMessage == eCompositionCommitAsIs) {
 | |
|         lastCommitString = composition->CommitStringIfCommittedAsIs();
 | |
|       } else {
 | |
|         lastCommitString = aCompositionEvent.mData;
 | |
|       }
 | |
|       // We don't need to store canceling information because this is required
 | |
|       // by undoing of last commit (Kakutei-Undo of Japanese IME).
 | |
|       if (!lastCommitString.IsEmpty()) {
 | |
|         mLastCommit = Some(OffsetAndData<uint32_t>(
 | |
|             composition->NativeOffsetOfStartComposition(), lastCommitString));
 | |
|         MOZ_LOG(
 | |
|             sContentCacheLog, LogLevel::Debug,
 | |
|             ("0x%p OnCompositionEvent(), stored last composition string data "
 | |
|              "(aCompositionEvent={ mMessage=%s, mData=\"%s\"}, mLastCommit=%s)",
 | |
|              this, ToChar(aCompositionEvent.mMessage),
 | |
|              PrintStringDetail(
 | |
|                  aCompositionEvent.mData,
 | |
|                  PrintStringDetail::kMaxLengthForCompositionString)
 | |
|                  .get(),
 | |
|              ToString(mLastCommit).c_str()));
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   if (mLastCommit.isSome()) {
 | |
|     MOZ_LOG(
 | |
|         sContentCacheLog, LogLevel::Debug,
 | |
|         ("0x%p OnCompositionEvent(), resetting the last composition string "
 | |
|          "data (aCompositionEvent={ mMessage=%s, mData=\"%s\"}, "
 | |
|          "mLastCommit=%s)",
 | |
|          this, ToChar(aCompositionEvent.mMessage),
 | |
|          PrintStringDetail(aCompositionEvent.mData,
 | |
|                            PrintStringDetail::kMaxLengthForCompositionString)
 | |
|              .get(),
 | |
|          ToString(mLastCommit).c_str()));
 | |
|     mLastCommit.reset();
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool ContentCacheInChild::CacheAll(nsIWidget* aWidget,
 | |
|                                    const IMENotification* aNotification) {
 | |
|   MOZ_LOG(sContentCacheLog, LogLevel::Info,
 | |
|           ("0x%p CacheAll(aWidget=0x%p, aNotification=%s)", this, aWidget,
 | |
|            GetNotificationName(aNotification)));
 | |
| 
 | |
|   const bool textCached = CacheText(aWidget, aNotification);
 | |
|   const bool editorRectCached = CacheEditorRect(aWidget, aNotification);
 | |
|   AssertIfInvalid();
 | |
|   return (textCached || editorRectCached) && IsValid();
 | |
| }
 | |
| 
 | |
| bool ContentCacheInChild::CacheSelection(nsIWidget* aWidget,
 | |
|                                          const IMENotification* aNotification) {
 | |
|   MOZ_LOG(
 | |
|       sContentCacheLog, LogLevel::Info,
 | |
|       ("0x%p CacheSelection(aWidget=0x%p, aNotification=%s), mText=%s", this,
 | |
|        aWidget, GetNotificationName(aNotification),
 | |
|        PrintStringDetail(mText, PrintStringDetail::kMaxLengthForEditor).get()));
 | |
| 
 | |
|   mSelection.reset();
 | |
|   mCaret.reset();
 | |
| 
 | |
|   if (mText.isNothing()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsEventStatus status = nsEventStatus_eIgnore;
 | |
|   WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
 | |
|                                                  aWidget);
 | |
|   aWidget->DispatchEvent(&querySelectedTextEvent, status);
 | |
|   if (NS_WARN_IF(querySelectedTextEvent.Failed())) {
 | |
|     MOZ_LOG(
 | |
|         sContentCacheLog, LogLevel::Error,
 | |
|         ("0x%p CacheSelection(), FAILED, couldn't retrieve the selected text",
 | |
|          this));
 | |
|     // XXX Allowing selection-independent character rects makes things
 | |
|     //     complicated in the parent...
 | |
|   }
 | |
|   // ContentCache should store only editable content.  Therefore, if current
 | |
|   // selection root is not editable, we don't need to store the selection, i.e.,
 | |
|   // let's treat it as there is no selection.  However, if we already have
 | |
|   // previously editable text, let's store the selection even if it becomes
 | |
|   // uneditable because not doing so would create odd situation.  E.g., IME may
 | |
|   // fail only querying selection after succeeded querying text.
 | |
|   else if (NS_WARN_IF(!querySelectedTextEvent.mReply->mIsEditableContent)) {
 | |
|     MOZ_LOG(sContentCacheLog, LogLevel::Error,
 | |
|             ("0x%p CacheSelection(), FAILED, editable content had already been "
 | |
|              "blurred",
 | |
|              this));
 | |
|     AssertIfInvalid();
 | |
|     return false;
 | |
|   } else {
 | |
|     mSelection.emplace(querySelectedTextEvent);
 | |
|   }
 | |
| 
 | |
|   return (CacheCaretAndTextRects(aWidget, aNotification) ||
 | |
|           querySelectedTextEvent.Succeeded()) &&
 | |
|          IsValid();
 | |
| }
 | |
| 
 | |
| bool ContentCacheInChild::CacheCaret(nsIWidget* aWidget,
 | |
|                                      const IMENotification* aNotification) {
 | |
|   mCaret.reset();
 | |
| 
 | |
|   if (mSelection.isNothing()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   MOZ_LOG(sContentCacheLog, LogLevel::Info,
 | |
|           ("0x%p CacheCaret(aWidget=0x%p, aNotification=%s)", this, aWidget,
 | |
|            GetNotificationName(aNotification)));
 | |
| 
 | |
|   if (mSelection->mHasRange) {
 | |
|     // XXX Should be mSelection.mFocus?
 | |
|     const uint32_t offset = mSelection->StartOffset();
 | |
| 
 | |
|     nsEventStatus status = nsEventStatus_eIgnore;
 | |
|     WidgetQueryContentEvent queryCaretRectEvent(true, eQueryCaretRect, aWidget);
 | |
|     queryCaretRectEvent.InitForQueryCaretRect(offset);
 | |
|     aWidget->DispatchEvent(&queryCaretRectEvent, status);
 | |
|     if (NS_WARN_IF(queryCaretRectEvent.Failed())) {
 | |
|       MOZ_LOG(sContentCacheLog, LogLevel::Error,
 | |
|               ("0x%p   CacheCaret(), FAILED, couldn't retrieve the caret rect "
 | |
|                "at offset=%u",
 | |
|                this, offset));
 | |
|       return false;
 | |
|     }
 | |
|     mCaret.emplace(offset, queryCaretRectEvent.mReply->mRect);
 | |
|   }
 | |
|   MOZ_LOG(sContentCacheLog, LogLevel::Info,
 | |
|           ("0x%p   CacheCaret(), Succeeded, mSelection=%s, mCaret=%s", this,
 | |
|            ToString(mSelection).c_str(), ToString(mCaret).c_str()));
 | |
|   AssertIfInvalid();
 | |
|   return IsValid();
 | |
| }
 | |
| 
 | |
| bool ContentCacheInChild::CacheEditorRect(
 | |
|     nsIWidget* aWidget, const IMENotification* aNotification) {
 | |
|   MOZ_LOG(sContentCacheLog, LogLevel::Info,
 | |
|           ("0x%p CacheEditorRect(aWidget=0x%p, aNotification=%s)", this,
 | |
|            aWidget, GetNotificationName(aNotification)));
 | |
| 
 | |
|   nsEventStatus status = nsEventStatus_eIgnore;
 | |
|   WidgetQueryContentEvent queryEditorRectEvent(true, eQueryEditorRect, aWidget);
 | |
|   aWidget->DispatchEvent(&queryEditorRectEvent, status);
 | |
|   if (NS_WARN_IF(queryEditorRectEvent.Failed())) {
 | |
|     MOZ_LOG(
 | |
|         sContentCacheLog, LogLevel::Error,
 | |
|         ("0x%p   CacheEditorRect(), FAILED, couldn't retrieve the editor rect",
 | |
|          this));
 | |
|     return false;
 | |
|   }
 | |
|   // ContentCache should store only editable content.  Therefore, if current
 | |
|   // selection root is not editable, we don't need to store the editor rect,
 | |
|   // i.e., let's treat it as there is no focused editor.
 | |
|   if (NS_WARN_IF(!queryEditorRectEvent.mReply->mIsEditableContent)) {
 | |
|     MOZ_LOG(sContentCacheLog, LogLevel::Error,
 | |
|             ("0x%p   CacheText(), FAILED, editable content had already been "
 | |
|              "blurred",
 | |
|              this));
 | |
|     return false;
 | |
|   }
 | |
|   mEditorRect = queryEditorRectEvent.mReply->mRect;
 | |
|   MOZ_LOG(sContentCacheLog, LogLevel::Info,
 | |
|           ("0x%p   CacheEditorRect(), Succeeded, mEditorRect=%s", this,
 | |
|            ToString(mEditorRect).c_str()));
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool ContentCacheInChild::CacheCaretAndTextRects(
 | |
|     nsIWidget* aWidget, const IMENotification* aNotification) {
 | |
|   MOZ_LOG(sContentCacheLog, LogLevel::Info,
 | |
|           ("0x%p CacheCaretAndTextRects(aWidget=0x%p, aNotification=%s)", this,
 | |
|            aWidget, GetNotificationName(aNotification)));
 | |
| 
 | |
|   const bool caretCached = CacheCaret(aWidget, aNotification);
 | |
|   const bool textRectsCached = CacheTextRects(aWidget, aNotification);
 | |
|   AssertIfInvalid();
 | |
|   return (caretCached || textRectsCached) && IsValid();
 | |
| }
 | |
| 
 | |
| bool ContentCacheInChild::CacheText(nsIWidget* aWidget,
 | |
|                                     const IMENotification* aNotification) {
 | |
|   MOZ_LOG(sContentCacheLog, LogLevel::Info,
 | |
|           ("0x%p CacheText(aWidget=0x%p, aNotification=%s)", this, aWidget,
 | |
|            GetNotificationName(aNotification)));
 | |
| 
 | |
|   nsEventStatus status = nsEventStatus_eIgnore;
 | |
|   WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
 | |
|                                                 aWidget);
 | |
|   queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
 | |
|   aWidget->DispatchEvent(&queryTextContentEvent, status);
 | |
|   if (NS_WARN_IF(queryTextContentEvent.Failed())) {
 | |
|     MOZ_LOG(sContentCacheLog, LogLevel::Error,
 | |
|             ("0x%p   CacheText(), FAILED, couldn't retrieve whole text", this));
 | |
|     mText.reset();
 | |
|   }
 | |
|   // ContentCache should store only editable content.  Therefore, if current
 | |
|   // selection root is not editable, we don't need to store the text, i.e.,
 | |
|   // let's treat it as there is no editable text.
 | |
|   else if (NS_WARN_IF(!queryTextContentEvent.mReply->mIsEditableContent)) {
 | |
|     MOZ_LOG(sContentCacheLog, LogLevel::Error,
 | |
|             ("0x%p   CacheText(), FAILED, editable content had already been "
 | |
|              "blurred",
 | |
|              this));
 | |
|     mText.reset();
 | |
|   } else {
 | |
|     mText = Some(nsString(queryTextContentEvent.mReply->DataRef()));
 | |
|     MOZ_LOG(sContentCacheLog, LogLevel::Info,
 | |
|             ("0x%p   CacheText(), Succeeded, mText=%s", this,
 | |
|              PrintStringDetail(mText, PrintStringDetail::kMaxLengthForEditor)
 | |
|                  .get()));
 | |
|   }
 | |
| 
 | |
|   // Forget last commit range if string in the range is different from the
 | |
|   // last commit string.
 | |
|   if (mLastCommit.isSome() &&
 | |
|       (mText.isNothing() ||
 | |
|        nsDependentSubstring(mText.ref(), mLastCommit->StartOffset(),
 | |
|                             mLastCommit->Length()) != mLastCommit->DataRef())) {
 | |
|     MOZ_LOG(sContentCacheLog, LogLevel::Debug,
 | |
|             ("0x%p   CacheText(), resetting the last composition string data "
 | |
|              "(mLastCommit=%s, current string=\"%s\")",
 | |
|              this, ToString(mLastCommit).c_str(),
 | |
|              PrintStringDetail(
 | |
|                  nsDependentSubstring(mText.ref(), mLastCommit->StartOffset(),
 | |
|                                       mLastCommit->Length()),
 | |
|                  PrintStringDetail::kMaxLengthForCompositionString)
 | |
|                  .get()));
 | |
|     mLastCommit.reset();
 | |
|   }
 | |
| 
 | |
|   // If we fail to get editable text content, it must mean that there is no
 | |
|   // focused element anymore or focused element is not editable.  In this case,
 | |
|   // we should not get selection of non-editable content
 | |
|   if (MOZ_UNLIKELY(mText.isNothing())) {
 | |
|     mSelection.reset();
 | |
|     mCaret.reset();
 | |
|     mTextRectArray.reset();
 | |
|     AssertIfInvalid();
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return CacheSelection(aWidget, aNotification);
 | |
| }
 | |
| 
 | |
| bool ContentCacheInChild::QueryCharRect(nsIWidget* aWidget, uint32_t aOffset,
 | |
|                                         LayoutDeviceIntRect& aCharRect) const {
 | |
|   aCharRect.SetEmpty();
 | |
| 
 | |
|   nsEventStatus status = nsEventStatus_eIgnore;
 | |
|   WidgetQueryContentEvent queryTextRectEvent(true, eQueryTextRect, aWidget);
 | |
|   queryTextRectEvent.InitForQueryTextRect(aOffset, 1);
 | |
|   aWidget->DispatchEvent(&queryTextRectEvent, status);
 | |
|   if (NS_WARN_IF(queryTextRectEvent.Failed())) {
 | |
|     return false;
 | |
|   }
 | |
|   aCharRect = queryTextRectEvent.mReply->mRect;
 | |
| 
 | |
|   // Guarantee the rect is not empty.
 | |
|   if (NS_WARN_IF(!aCharRect.Height())) {
 | |
|     aCharRect.SetHeight(1);
 | |
|   }
 | |
|   if (NS_WARN_IF(!aCharRect.Width())) {
 | |
|     aCharRect.SetWidth(1);
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool ContentCacheInChild::QueryCharRectArray(nsIWidget* aWidget,
 | |
|                                              uint32_t aOffset, uint32_t aLength,
 | |
|                                              RectArray& aCharRectArray) const {
 | |
|   nsEventStatus status = nsEventStatus_eIgnore;
 | |
|   WidgetQueryContentEvent queryTextRectsEvent(true, eQueryTextRectArray,
 | |
|                                               aWidget);
 | |
|   queryTextRectsEvent.InitForQueryTextRectArray(aOffset, aLength);
 | |
|   aWidget->DispatchEvent(&queryTextRectsEvent, status);
 | |
|   if (NS_WARN_IF(queryTextRectsEvent.Failed())) {
 | |
|     aCharRectArray.Clear();
 | |
|     return false;
 | |
|   }
 | |
|   aCharRectArray = std::move(queryTextRectsEvent.mReply->mRectArray);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool ContentCacheInChild::CacheTextRects(nsIWidget* aWidget,
 | |
|                                          const IMENotification* aNotification) {
 | |
|   MOZ_LOG(
 | |
|       sContentCacheLog, LogLevel::Info,
 | |
|       ("0x%p CacheTextRects(aWidget=0x%p, aNotification=%s), mCaret=%s", this,
 | |
|        aWidget, GetNotificationName(aNotification), ToString(mCaret).c_str()));
 | |
| 
 | |
|   if (mSelection.isSome()) {
 | |
|     mSelection->ClearRects();
 | |
|   }
 | |
| 
 | |
|   // Retrieve text rects in composition string if there is.
 | |
|   RefPtr<TextComposition> textComposition =
 | |
|       IMEStateManager::GetTextCompositionFor(aWidget);
 | |
|   if (textComposition) {
 | |
|     // mCompositionStart may be updated by some composition event handlers.
 | |
|     // So, let's update it with the latest information.
 | |
|     mCompositionStart = Some(textComposition->NativeOffsetOfStartComposition());
 | |
|     // Note that TextComposition::String() may not be modified here because
 | |
|     // it's modified after all edit action listeners are performed but this
 | |
|     // is called while some of them are performed.
 | |
|     // FYI: For supporting IME which commits composition and restart new
 | |
|     //      composition immediately, we should cache next character of current
 | |
|     //      composition too.
 | |
|     uint32_t length = textComposition->LastData().Length() + 1;
 | |
|     mTextRectArray = Some(TextRectArray(mCompositionStart.value()));
 | |
|     if (NS_WARN_IF(!QueryCharRectArray(aWidget, mTextRectArray->mStart, length,
 | |
|                                        mTextRectArray->mRects))) {
 | |
|       MOZ_LOG(sContentCacheLog, LogLevel::Error,
 | |
|               ("0x%p   CacheTextRects(), FAILED, "
 | |
|                "couldn't retrieve text rect array of the composition string",
 | |
|                this));
 | |
|       mTextRectArray.reset();
 | |
|     }
 | |
|   } else {
 | |
|     mCompositionStart.reset();
 | |
|     mTextRectArray.reset();
 | |
|   }
 | |
| 
 | |
|   if (mSelection.isSome()) {
 | |
|     // Set mSelection->mAnchorCharRects
 | |
|     // If we've already have the rect in mTextRectArray, save the query cost.
 | |
|     if (mSelection->mHasRange && mTextRectArray.isSome() &&
 | |
|         mTextRectArray->IsOffsetInRange(mSelection->mAnchor) &&
 | |
|         (!mSelection->mAnchor ||
 | |
|          mTextRectArray->IsOffsetInRange(mSelection->mAnchor - 1))) {
 | |
|       mSelection->mAnchorCharRects[eNextCharRect] =
 | |
|           mTextRectArray->GetRect(mSelection->mAnchor);
 | |
|       if (mSelection->mAnchor) {
 | |
|         mSelection->mAnchorCharRects[ePrevCharRect] =
 | |
|             mTextRectArray->GetRect(mSelection->mAnchor - 1);
 | |
|       }
 | |
|     }
 | |
|     // Otherwise, get it from content even if there is no selection ranges.
 | |
|     else {
 | |
|       RectArray rects;
 | |
|       const uint32_t startOffset = mSelection->mHasRange && mSelection->mAnchor
 | |
|                                        ? mSelection->mAnchor - 1u
 | |
|                                        : 0u;
 | |
|       const uint32_t length =
 | |
|           mSelection->mHasRange && mSelection->mAnchor ? 2u : 1u;
 | |
|       if (NS_WARN_IF(
 | |
|               !QueryCharRectArray(aWidget, startOffset, length, rects))) {
 | |
|         MOZ_LOG(
 | |
|             sContentCacheLog, LogLevel::Error,
 | |
|             ("0x%p   CacheTextRects(), FAILED, couldn't retrieve text rect "
 | |
|              "array around the selection anchor (%s)",
 | |
|              this,
 | |
|              mSelection ? ToString(mSelection->mAnchor).c_str() : "Nothing"));
 | |
|         MOZ_ASSERT_IF(mSelection.isSome(),
 | |
|                       mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty());
 | |
|         MOZ_ASSERT_IF(mSelection.isSome(),
 | |
|                       mSelection->mAnchorCharRects[eNextCharRect].IsEmpty());
 | |
|       } else if (rects.Length()) {
 | |
|         if (rects.Length() > 1) {
 | |
|           mSelection->mAnchorCharRects[ePrevCharRect] = rects[0];
 | |
|           mSelection->mAnchorCharRects[eNextCharRect] = rects[1];
 | |
|         } else {
 | |
|           mSelection->mAnchorCharRects[eNextCharRect] = rects[0];
 | |
|           MOZ_ASSERT(mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty());
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Set mSelection->mFocusCharRects
 | |
|     // If selection is collapsed (including no selection case), the focus char
 | |
|     // rects are same as the anchor char rects so that we can just copy them.
 | |
|     if (mSelection->IsCollapsed()) {
 | |
|       mSelection->mFocusCharRects[0] = mSelection->mAnchorCharRects[0];
 | |
|       mSelection->mFocusCharRects[1] = mSelection->mAnchorCharRects[1];
 | |
|     }
 | |
|     // If the selection range is in mTextRectArray, save the query cost.
 | |
|     else if (mTextRectArray.isSome() &&
 | |
|              mTextRectArray->IsOffsetInRange(mSelection->mFocus) &&
 | |
|              (!mSelection->mFocus ||
 | |
|               mTextRectArray->IsOffsetInRange(mSelection->mFocus - 1))) {
 | |
|       MOZ_ASSERT(mSelection->mHasRange);
 | |
|       mSelection->mFocusCharRects[eNextCharRect] =
 | |
|           mTextRectArray->GetRect(mSelection->mFocus);
 | |
|       if (mSelection->mFocus) {
 | |
|         mSelection->mFocusCharRects[ePrevCharRect] =
 | |
|             mTextRectArray->GetRect(mSelection->mFocus - 1);
 | |
|       }
 | |
|     }
 | |
|     // Otherwise, including no selection range cases, need to query the rects.
 | |
|     else {
 | |
|       MOZ_ASSERT(mSelection->mHasRange);
 | |
|       RectArray rects;
 | |
|       const uint32_t startOffset =
 | |
|           mSelection->mFocus ? mSelection->mFocus - 1u : 0u;
 | |
|       const uint32_t length = mSelection->mFocus ? 2u : 1u;
 | |
|       if (NS_WARN_IF(
 | |
|               !QueryCharRectArray(aWidget, startOffset, length, rects))) {
 | |
|         MOZ_LOG(
 | |
|             sContentCacheLog, LogLevel::Error,
 | |
|             ("0x%p   CacheTextRects(), FAILED, couldn't retrieve text rect "
 | |
|              "array around the selection focus (%s)",
 | |
|              this,
 | |
|              mSelection ? ToString(mSelection->mFocus).c_str() : "Nothing"));
 | |
|         MOZ_ASSERT_IF(mSelection.isSome(),
 | |
|                       mSelection->mFocusCharRects[ePrevCharRect].IsEmpty());
 | |
|         MOZ_ASSERT_IF(mSelection.isSome(),
 | |
|                       mSelection->mFocusCharRects[eNextCharRect].IsEmpty());
 | |
|       } else if (NS_WARN_IF(mSelection.isNothing())) {
 | |
|         MOZ_LOG(sContentCacheLog, LogLevel::Error,
 | |
|                 ("0x%p   CacheTextRects(), FAILED, mSelection was reset during "
 | |
|                  "the call of QueryCharRectArray",
 | |
|                  this));
 | |
|       } else {
 | |
|         if (rects.Length() > 1) {
 | |
|           mSelection->mFocusCharRects[ePrevCharRect] = rects[0];
 | |
|           mSelection->mFocusCharRects[eNextCharRect] = rects[1];
 | |
|         } else if (rects.Length()) {
 | |
|           mSelection->mFocusCharRects[eNextCharRect] = rects[0];
 | |
|           MOZ_ASSERT(mSelection->mFocusCharRects[ePrevCharRect].IsEmpty());
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // If there is a non-collapsed selection range, let's query the whole selected
 | |
|   // text rect.  Note that the result cannot be computed from first character
 | |
|   // rect and last character rect of the selection because they both may be in
 | |
|   // middle of different line.
 | |
|   if (mSelection.isSome() && mSelection->mHasRange &&
 | |
|       !mSelection->IsCollapsed()) {
 | |
|     nsEventStatus status = nsEventStatus_eIgnore;
 | |
|     WidgetQueryContentEvent queryTextRectEvent(true, eQueryTextRect, aWidget);
 | |
|     queryTextRectEvent.InitForQueryTextRect(mSelection->StartOffset(),
 | |
|                                             mSelection->Length());
 | |
|     aWidget->DispatchEvent(&queryTextRectEvent, status);
 | |
|     if (NS_WARN_IF(queryTextRectEvent.Failed())) {
 | |
|       MOZ_LOG(sContentCacheLog, LogLevel::Error,
 | |
|               ("0x%p   CacheTextRects(), FAILED, "
 | |
|                "couldn't retrieve text rect of whole selected text",
 | |
|                this));
 | |
|     } else {
 | |
|       mSelection->mRect = queryTextRectEvent.mReply->mRect;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Even if there is no selection range, we should have the first character
 | |
|   // rect for the last resort of suggesting position of IME UI.
 | |
|   if (mSelection.isSome() && mSelection->mHasRange && !mSelection->mFocus) {
 | |
|     mFirstCharRect = mSelection->mFocusCharRects[eNextCharRect];
 | |
|   } else if (mSelection.isSome() && mSelection->mHasRange &&
 | |
|              mSelection->mFocus == 1) {
 | |
|     mFirstCharRect = mSelection->mFocusCharRects[ePrevCharRect];
 | |
|   } else if (mSelection.isSome() && mSelection->mHasRange &&
 | |
|              !mSelection->mAnchor) {
 | |
|     mFirstCharRect = mSelection->mAnchorCharRects[eNextCharRect];
 | |
|   } else if (mSelection.isSome() && mSelection->mHasRange &&
 | |
|              mSelection->mAnchor == 1) {
 | |
|     mFirstCharRect = mSelection->mFocusCharRects[ePrevCharRect];
 | |
|   } else if (mTextRectArray.isSome() && mTextRectArray->IsOffsetInRange(0u)) {
 | |
|     mFirstCharRect = mTextRectArray->GetRect(0u);
 | |
|   } else {
 | |
|     LayoutDeviceIntRect charRect;
 | |
|     if (MOZ_UNLIKELY(NS_WARN_IF(!QueryCharRect(aWidget, 0, charRect)))) {
 | |
|       MOZ_LOG(sContentCacheLog, LogLevel::Error,
 | |
|               ("0x%p   CacheTextRects(), FAILED, "
 | |
|                "couldn't retrieve first char rect",
 | |
|                this));
 | |
|       mFirstCharRect.SetEmpty();
 | |
|     } else {
 | |
|       mFirstCharRect = charRect;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Finally, let's cache the last commit string's character rects until
 | |
|   // selection change or something other editing because user may reconvert
 | |
|   // or undo the last commit.  Then, IME requires the character rects for
 | |
|   // positioning their UI.
 | |
|   if (mLastCommit.isSome()) {
 | |
|     mLastCommitStringTextRectArray =
 | |
|         Some(TextRectArray(mLastCommit->StartOffset()));
 | |
|     if (mLastCommit->Length() == 1 && mSelection.isSome() &&
 | |
|         mSelection->mHasRange &&
 | |
|         mSelection->mAnchor - 1 == mLastCommit->StartOffset() &&
 | |
|         !mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty()) {
 | |
|       mLastCommitStringTextRectArray->mRects.AppendElement(
 | |
|           mSelection->mAnchorCharRects[ePrevCharRect]);
 | |
|     } else if (NS_WARN_IF(!QueryCharRectArray(
 | |
|                    aWidget, mLastCommit->StartOffset(), mLastCommit->Length(),
 | |
|                    mLastCommitStringTextRectArray->mRects))) {
 | |
|       MOZ_LOG(sContentCacheLog, LogLevel::Error,
 | |
|               ("0x%p   CacheTextRects(), FAILED, "
 | |
|                "couldn't retrieve text rect array of the last commit string",
 | |
|                this));
 | |
|       mLastCommitStringTextRectArray.reset();
 | |
|       mLastCommit.reset();
 | |
|     }
 | |
|     MOZ_ASSERT((mLastCommitStringTextRectArray.isSome()
 | |
|                     ? mLastCommitStringTextRectArray->mRects.Length()
 | |
|                     : 0) == (mLastCommit.isSome() ? mLastCommit->Length() : 0));
 | |
|   } else {
 | |
|     mLastCommitStringTextRectArray.reset();
 | |
|   }
 | |
| 
 | |
|   MOZ_LOG(
 | |
|       sContentCacheLog, LogLevel::Info,
 | |
|       ("0x%p   CacheTextRects(), Succeeded, "
 | |
|        "mText=%s, mTextRectArray=%s, mSelection=%s, "
 | |
|        "mFirstCharRect=%s, mLastCommitStringTextRectArray=%s",
 | |
|        this,
 | |
|        PrintStringDetail(mText, PrintStringDetail::kMaxLengthForEditor).get(),
 | |
|        ToString(mTextRectArray).c_str(), ToString(mSelection).c_str(),
 | |
|        ToString(mFirstCharRect).c_str(),
 | |
|        ToString(mLastCommitStringTextRectArray).c_str()));
 | |
|   AssertIfInvalid();
 | |
|   return IsValid();
 | |
| }
 | |
| 
 | |
| bool ContentCacheInChild::SetSelection(
 | |
|     nsIWidget* aWidget,
 | |
|     const IMENotification::SelectionChangeDataBase& aSelectionChangeData) {
 | |
|   MOZ_LOG(
 | |
|       sContentCacheLog, LogLevel::Info,
 | |
|       ("0x%p SetSelection(aSelectionChangeData=%s), mText=%s", this,
 | |
|        ToString(aSelectionChangeData).c_str(),
 | |
|        PrintStringDetail(mText, PrintStringDetail::kMaxLengthForEditor).get()));
 | |
| 
 | |
|   if (MOZ_UNLIKELY(mText.isNothing())) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   mSelection = Some(Selection(aSelectionChangeData));
 | |
| 
 | |
|   if (mLastCommit.isSome()) {
 | |
|     // Forget last commit string range if selection is not collapsed
 | |
|     // at end of the last commit string.
 | |
|     if (!mSelection->mHasRange || !mSelection->IsCollapsed() ||
 | |
|         mSelection->mAnchor != mLastCommit->EndOffset()) {
 | |
|       MOZ_LOG(
 | |
|           sContentCacheLog, LogLevel::Debug,
 | |
|           ("0x%p   SetSelection(), forgetting last commit composition data "
 | |
|            "(mSelection=%s, mLastCommit=%s)",
 | |
|            this, ToString(mSelection).c_str(), ToString(mLastCommit).c_str()));
 | |
|       mLastCommit.reset();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   CacheCaret(aWidget);
 | |
|   CacheTextRects(aWidget);
 | |
| 
 | |
|   return mSelection.isSome() && IsValid();
 | |
| }
 | |
| 
 | |
| /*****************************************************************************
 | |
|  * mozilla::ContentCacheInParent
 | |
|  *****************************************************************************/
 | |
| 
 | |
| ContentCacheInParent::ContentCacheInParent(BrowserParent& aBrowserParent)
 | |
|     : mBrowserParent(aBrowserParent),
 | |
|       mCommitStringByRequest(nullptr),
 | |
|       mPendingCommitLength(0),
 | |
|       mIsChildIgnoringCompositionEvents(false) {}
 | |
| 
 | |
| void ContentCacheInParent::AssignContent(const ContentCache& aOther,
 | |
|                                          nsIWidget* aWidget,
 | |
|                                          const IMENotification* aNotification) {
 | |
|   MOZ_DIAGNOSTIC_ASSERT(aOther.IsValid());
 | |
| 
 | |
|   mText = aOther.mText;
 | |
|   mSelection = aOther.mSelection;
 | |
|   mFirstCharRect = aOther.mFirstCharRect;
 | |
|   mCaret = aOther.mCaret;
 | |
|   mTextRectArray = aOther.mTextRectArray;
 | |
|   mLastCommitStringTextRectArray = aOther.mLastCommitStringTextRectArray;
 | |
|   mEditorRect = aOther.mEditorRect;
 | |
| 
 | |
|   // Only when there is one composition, the TextComposition instance in this
 | |
|   // process is managing the composition in the remote process.  Therefore,
 | |
|   // we shouldn't update composition start offset of TextComposition with
 | |
|   // old composition which is still being handled by the child process.
 | |
|   if (WidgetHasComposition() && mHandlingCompositions.Length() == 1 &&
 | |
|       mCompositionStart.isSome()) {
 | |
|     IMEStateManager::MaybeStartOffsetUpdatedInChild(aWidget,
 | |
|                                                     mCompositionStart.value());
 | |
|   }
 | |
| 
 | |
|   // When this instance allows to query content relative to composition string,
 | |
|   // we should modify mCompositionStart with the latest information in the
 | |
|   // remote process because now we have the information around the composition
 | |
|   // string.
 | |
|   mCompositionStartInChild = aOther.mCompositionStart;
 | |
|   if (WidgetHasComposition() || HasPendingCommit()) {
 | |
|     if (mCompositionStartInChild.isSome()) {
 | |
|       if (mCompositionStart.valueOr(UINT32_MAX) !=
 | |
|           mCompositionStartInChild.value()) {
 | |
|         mCompositionStart = mCompositionStartInChild;
 | |
|         mPendingCommitLength = 0;
 | |
|       }
 | |
|     } else if (mCompositionStart.isSome() && mSelection.isSome() &&
 | |
|                mSelection->mHasRange &&
 | |
|                mCompositionStart.value() != mSelection->StartOffset()) {
 | |
|       mCompositionStart = Some(mSelection->StartOffset());
 | |
|       mPendingCommitLength = 0;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   MOZ_LOG(
 | |
|       sContentCacheLog, LogLevel::Info,
 | |
|       ("0x%p   AssignContent(aNotification=%s), "
 | |
|        "Succeeded, mText=%s, mSelection=%s, mFirstCharRect=%s, "
 | |
|        "mCaret=%s, mTextRectArray=%s, WidgetHasComposition()=%s, "
 | |
|        "mHandlingCompositions.Length()=%zu, mCompositionStart=%s, "
 | |
|        "mPendingCommitLength=%u, mEditorRect=%s, "
 | |
|        "mLastCommitStringTextRectArray=%s",
 | |
|        this, GetNotificationName(aNotification),
 | |
|        PrintStringDetail(mText, PrintStringDetail::kMaxLengthForEditor).get(),
 | |
|        ToString(mSelection).c_str(), ToString(mFirstCharRect).c_str(),
 | |
|        ToString(mCaret).c_str(), ToString(mTextRectArray).c_str(),
 | |
|        GetBoolName(WidgetHasComposition()), mHandlingCompositions.Length(),
 | |
|        ToString(mCompositionStart).c_str(), mPendingCommitLength,
 | |
|        ToString(mEditorRect).c_str(),
 | |
|        ToString(mLastCommitStringTextRectArray).c_str()));
 | |
| }
 | |
| 
 | |
| bool ContentCacheInParent::HandleQueryContentEvent(
 | |
|     WidgetQueryContentEvent& aEvent, nsIWidget* aWidget) const {
 | |
|   MOZ_ASSERT(aWidget);
 | |
| 
 | |
|   // ContentCache doesn't store offset of its start with XP linebreaks.
 | |
|   // So, we don't support to query contents relative to composition start
 | |
|   // offset with XP linebreaks.
 | |
|   if (NS_WARN_IF(!aEvent.mUseNativeLineBreak)) {
 | |
|     MOZ_LOG(sContentCacheLog, LogLevel::Error,
 | |
|             ("0x%p HandleQueryContentEvent(), FAILED due to query with XP "
 | |
|              "linebreaks",
 | |
|              this));
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (NS_WARN_IF(!aEvent.mInput.IsValidOffset())) {
 | |
|     MOZ_LOG(
 | |
|         sContentCacheLog, LogLevel::Error,
 | |
|         ("0x%p HandleQueryContentEvent(), FAILED due to invalid offset", this));
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (NS_WARN_IF(!aEvent.mInput.IsValidEventMessage(aEvent.mMessage))) {
 | |
|     MOZ_LOG(
 | |
|         sContentCacheLog, LogLevel::Error,
 | |
|         ("0x%p HandleQueryContentEvent(), FAILED due to invalid event message",
 | |
|          this));
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   bool isRelativeToInsertionPoint = aEvent.mInput.mRelativeToInsertionPoint;
 | |
|   if (isRelativeToInsertionPoint) {
 | |
|     MOZ_LOG(
 | |
|         sContentCacheLog, LogLevel::Debug,
 | |
|         ("0x%p HandleQueryContentEvent(), "
 | |
|          "making offset absolute... aEvent={ mMessage=%s, mInput={ "
 | |
|          "mOffset=%" PRId64 ", mLength=%" PRIu32 " } }, "
 | |
|          "WidgetHasComposition()=%s, HasPendingCommit()=%s, "
 | |
|          "mCompositionStart=%" PRIu32 ", "
 | |
|          "mPendingCommitLength=%" PRIu32 ", mSelection=%s",
 | |
|          this, ToChar(aEvent.mMessage), aEvent.mInput.mOffset,
 | |
|          aEvent.mInput.mLength, GetBoolName(WidgetHasComposition()),
 | |
|          GetBoolName(HasPendingCommit()), mCompositionStart.valueOr(UINT32_MAX),
 | |
|          mPendingCommitLength, ToString(mSelection).c_str()));
 | |
|     if (WidgetHasComposition() || HasPendingCommit()) {
 | |
|       if (NS_WARN_IF(mCompositionStart.isNothing()) ||
 | |
|           NS_WARN_IF(!aEvent.mInput.MakeOffsetAbsolute(
 | |
|               mCompositionStart.value() + mPendingCommitLength))) {
 | |
|         MOZ_LOG(
 | |
|             sContentCacheLog, LogLevel::Error,
 | |
|             ("0x%p HandleQueryContentEvent(), FAILED due to "
 | |
|              "aEvent.mInput.MakeOffsetAbsolute(mCompositionStart + "
 | |
|              "mPendingCommitLength) failure, "
 | |
|              "mCompositionStart=%" PRIu32 ", mPendingCommitLength=%" PRIu32 ", "
 | |
|              "aEvent={ mMessage=%s, mInput={ mOffset=%" PRId64
 | |
|              ", mLength=%" PRIu32 " } }",
 | |
|              this, mCompositionStart.valueOr(UINT32_MAX), mPendingCommitLength,
 | |
|              ToChar(aEvent.mMessage), aEvent.mInput.mOffset,
 | |
|              aEvent.mInput.mLength));
 | |
|         return false;
 | |
|       }
 | |
|     } else if (NS_WARN_IF(mSelection.isNothing())) {
 | |
|       MOZ_LOG(sContentCacheLog, LogLevel::Error,
 | |
|               ("0x%p HandleQueryContentEvent(), FAILED due to mSelection is "
 | |
|                "Nothing",
 | |
|                this));
 | |
|       return false;
 | |
|     } else if (NS_WARN_IF(mSelection->mHasRange)) {
 | |
|       MOZ_LOG(sContentCacheLog, LogLevel::Error,
 | |
|               ("0x%p HandleQueryContentEvent(), FAILED due to there is no "
 | |
|                "selection range, but the query requested with relative offset "
 | |
|                "from selection",
 | |
|                this));
 | |
|       return false;
 | |
|     } else if (NS_WARN_IF(!aEvent.mInput.MakeOffsetAbsolute(
 | |
|                    mSelection->StartOffset() + mPendingCommitLength))) {
 | |
|       MOZ_LOG(sContentCacheLog, LogLevel::Error,
 | |
|               ("0x%p HandleQueryContentEvent(), FAILED due to "
 | |
|                "aEvent.mInput.MakeOffsetAbsolute(mSelection->StartOffset() + "
 | |
|                "mPendingCommitLength) failure, mSelection=%s, "
 | |
|                "mPendingCommitLength=%" PRIu32 ", aEvent={ mMessage=%s, "
 | |
|                "mInput={ mOffset=%" PRId64 ", mLength=%" PRIu32 " } }",
 | |
|                this, ToString(mSelection).c_str(), mPendingCommitLength,
 | |
|                ToChar(aEvent.mMessage), aEvent.mInput.mOffset,
 | |
|                aEvent.mInput.mLength));
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   switch (aEvent.mMessage) {
 | |
|     case eQuerySelectedText:
 | |
|       MOZ_LOG(sContentCacheLog, LogLevel::Info,
 | |
|               ("0x%p HandleQueryContentEvent(aEvent={ "
 | |
|                "mMessage=eQuerySelectedText }, aWidget=0x%p)",
 | |
|                this, aWidget));
 | |
|       if (MOZ_UNLIKELY(NS_WARN_IF(mSelection.isNothing()))) {
 | |
|         // If content cache hasn't been initialized properly, make the query
 | |
|         // failed.
 | |
|         MOZ_LOG(sContentCacheLog, LogLevel::Error,
 | |
|                 ("0x%p   HandleQueryContentEvent(), FAILED because mSelection "
 | |
|                  "is Nothing",
 | |
|                  this));
 | |
|         return false;
 | |
|       }
 | |
|       MOZ_DIAGNOSTIC_ASSERT(mText.isSome());
 | |
|       MOZ_DIAGNOSTIC_ASSERT(mSelection->IsValidIn(*mText));
 | |
|       aEvent.EmplaceReply();
 | |
|       aEvent.mReply->mFocusedWidget = aWidget;
 | |
|       if (mSelection->mHasRange) {
 | |
|         if (MOZ_LIKELY(mText.isSome())) {
 | |
|           aEvent.mReply->mOffsetAndData.emplace(
 | |
|               mSelection->StartOffset(),
 | |
|               Substring(mText.ref(), mSelection->StartOffset(),
 | |
|                         mSelection->Length()),
 | |
|               OffsetAndDataFor::SelectedString);
 | |
|         } else {
 | |
|           // TODO: Investigate this case.  I find this during
 | |
|           //       test_mousecapture.xhtml on Linux.
 | |
|           aEvent.mReply->mOffsetAndData.emplace(
 | |
|               0u, EmptyString(), OffsetAndDataFor::SelectedString);
 | |
|         }
 | |
|       }
 | |
|       aEvent.mReply->mWritingMode = mSelection->mWritingMode;
 | |
|       MOZ_LOG(sContentCacheLog, LogLevel::Info,
 | |
|               ("0x%p   HandleQueryContentEvent(), Succeeded, aEvent={ "
 | |
|                "mMessage=eQuerySelectedText, mReply=%s }",
 | |
|                this, ToString(aEvent.mReply).c_str()));
 | |
|       return true;
 | |
|     case eQueryTextContent: {
 | |
|       MOZ_LOG(sContentCacheLog, LogLevel::Info,
 | |
|               ("0x%p HandleQueryContentEvent(aEvent={ "
 | |
|                "mMessage=eQueryTextContent, mInput={ mOffset=%" PRId64
 | |
|                ", mLength=%u } }, aWidget=0x%p), mText->Length()=%zu",
 | |
|                this, aEvent.mInput.mOffset, aEvent.mInput.mLength, aWidget,
 | |
|                mText.isSome() ? mText->Length() : 0u));
 | |
|       if (MOZ_UNLIKELY(NS_WARN_IF(mText.isNothing()))) {
 | |
|         MOZ_LOG(sContentCacheLog, LogLevel::Error,
 | |
|                 ("0x%p   HandleQueryContentEvent(), FAILED because "
 | |
|                  "there is no text data",
 | |
|                  this));
 | |
|         return false;
 | |
|       }
 | |
|       const uint32_t inputOffset = aEvent.mInput.mOffset;
 | |
|       const uint32_t inputEndOffset = std::min<uint32_t>(
 | |
|           aEvent.mInput.EndOffset(), mText.isSome() ? mText->Length() : 0u);
 | |
|       if (MOZ_UNLIKELY(NS_WARN_IF(inputEndOffset < inputOffset))) {
 | |
|         MOZ_LOG(sContentCacheLog, LogLevel::Error,
 | |
|                 ("0x%p   HandleQueryContentEvent(), FAILED because "
 | |
|                  "inputOffset=%u is larger than inputEndOffset=%u",
 | |
|                  this, inputOffset, inputEndOffset));
 | |
|         return false;
 | |
|       }
 | |
|       aEvent.EmplaceReply();
 | |
|       aEvent.mReply->mFocusedWidget = aWidget;
 | |
|       const nsAString& textInQueriedRange =
 | |
|           inputEndOffset > inputOffset
 | |
|               ? static_cast<const nsAString&>(Substring(
 | |
|                     mText.ref(), inputOffset, inputEndOffset - inputOffset))
 | |
|               : static_cast<const nsAString&>(EmptyString());
 | |
|       aEvent.mReply->mOffsetAndData.emplace(inputOffset, textInQueriedRange,
 | |
|                                             OffsetAndDataFor::EditorString);
 | |
|       // TODO: Support font ranges
 | |
|       MOZ_LOG(sContentCacheLog, LogLevel::Info,
 | |
|               ("0x%p   HandleQueryContentEvent(), Succeeded, aEvent={ "
 | |
|                "mMessage=eQueryTextContent, mReply=%s }",
 | |
|                this, ToString(aEvent.mReply).c_str()));
 | |
|       return true;
 | |
|     }
 | |
|     case eQueryTextRect: {
 | |
|       MOZ_LOG(sContentCacheLog, LogLevel::Info,
 | |
|               ("0x%p HandleQueryContentEvent("
 | |
|                "aEvent={ mMessage=eQueryTextRect, mInput={ mOffset=%" PRId64
 | |
|                ", mLength=%u } }, aWidget=0x%p), mText->Length()=%zu",
 | |
|                this, aEvent.mInput.mOffset, aEvent.mInput.mLength, aWidget,
 | |
|                mText.isSome() ? mText->Length() : 0u));
 | |
|       // Note that if the query is relative to insertion point, the query was
 | |
|       // probably requested by native IME.  In such case, we should return
 | |
|       // non-empty rect since returning failure causes IME showing its window
 | |
|       // at odd position.
 | |
|       LayoutDeviceIntRect textRect;
 | |
|       if (aEvent.mInput.mLength) {
 | |
|         if (MOZ_UNLIKELY(NS_WARN_IF(
 | |
|                 !GetUnionTextRects(aEvent.mInput.mOffset, aEvent.mInput.mLength,
 | |
|                                    isRelativeToInsertionPoint, textRect)))) {
 | |
|           // XXX We don't have cache for this request.
 | |
|           MOZ_LOG(sContentCacheLog, LogLevel::Error,
 | |
|                   ("0x%p   HandleQueryContentEvent(), FAILED to get union rect",
 | |
|                    this));
 | |
|           return false;
 | |
|         }
 | |
|       } else {
 | |
|         // If the length is 0, we should return caret rect instead.
 | |
|         if (NS_WARN_IF(!GetCaretRect(aEvent.mInput.mOffset,
 | |
|                                      isRelativeToInsertionPoint, textRect))) {
 | |
|           MOZ_LOG(sContentCacheLog, LogLevel::Error,
 | |
|                   ("0x%p   HandleQueryContentEvent(), FAILED to get caret rect",
 | |
|                    this));
 | |
|           return false;
 | |
|         }
 | |
|       }
 | |
|       aEvent.EmplaceReply();
 | |
|       aEvent.mReply->mFocusedWidget = aWidget;
 | |
|       aEvent.mReply->mRect = textRect;
 | |
|       const nsAString& textInQueriedRange =
 | |
|           mText.isSome() && aEvent.mInput.mOffset <
 | |
|                                 static_cast<int64_t>(
 | |
|                                     mText.isSome() ? mText->Length() : 0u)
 | |
|               ? static_cast<const nsAString&>(
 | |
|                     Substring(mText.ref(), aEvent.mInput.mOffset,
 | |
|                               mText->Length() >= aEvent.mInput.EndOffset()
 | |
|                                   ? aEvent.mInput.mLength
 | |
|                                   : UINT32_MAX))
 | |
|               : static_cast<const nsAString&>(EmptyString());
 | |
|       aEvent.mReply->mOffsetAndData.emplace(aEvent.mInput.mOffset,
 | |
|                                             textInQueriedRange,
 | |
|                                             OffsetAndDataFor::EditorString);
 | |
|       // XXX This may be wrong if storing range isn't in the selection range.
 | |
|       aEvent.mReply->mWritingMode =
 | |
|           mSelection.isSome() ? mSelection->mWritingMode : WritingMode();
 | |
|       MOZ_LOG(sContentCacheLog, LogLevel::Info,
 | |
|               ("0x%p   HandleQueryContentEvent(), Succeeded, aEvent={ "
 | |
|                "mMessage=eQueryTextRect mReply=%s }",
 | |
|                this, ToString(aEvent.mReply).c_str()));
 | |
|       return true;
 | |
|     }
 | |
|     case eQueryCaretRect: {
 | |
|       MOZ_LOG(
 | |
|           sContentCacheLog, LogLevel::Info,
 | |
|           ("0x%p HandleQueryContentEvent(aEvent={ mMessage=eQueryCaretRect, "
 | |
|            "mInput={ mOffset=%" PRId64
 | |
|            " } }, aWidget=0x%p), mText->Length()=%zu",
 | |
|            this, aEvent.mInput.mOffset, aWidget,
 | |
|            mText.isSome() ? mText->Length() : 0u));
 | |
|       // Note that if the query is relative to insertion point, the query was
 | |
|       // probably requested by native IME.  In such case, we should return
 | |
|       // non-empty rect since returning failure causes IME showing its window
 | |
|       // at odd position.
 | |
|       LayoutDeviceIntRect caretRect;
 | |
|       if (NS_WARN_IF(!GetCaretRect(aEvent.mInput.mOffset,
 | |
|                                    isRelativeToInsertionPoint, caretRect))) {
 | |
|         MOZ_LOG(sContentCacheLog, LogLevel::Error,
 | |
|                 ("0x%p   HandleQueryContentEvent(),FAILED to get caret rect",
 | |
|                  this));
 | |
|         return false;
 | |
|       }
 | |
|       aEvent.EmplaceReply();
 | |
|       aEvent.mReply->mFocusedWidget = aWidget;
 | |
|       aEvent.mReply->mRect = caretRect;
 | |
|       aEvent.mReply->mOffsetAndData.emplace(aEvent.mInput.mOffset,
 | |
|                                             EmptyString(),
 | |
|                                             OffsetAndDataFor::SelectedString);
 | |
|       // TODO: Set mWritingMode here
 | |
|       MOZ_LOG(sContentCacheLog, LogLevel::Info,
 | |
|               ("0x%p   HandleQueryContentEvent(), Succeeded, aEvent={ "
 | |
|                "mMessage=eQueryCaretRect, mReply=%s }",
 | |
|                this, ToString(aEvent.mReply).c_str()));
 | |
|       return true;
 | |
|     }
 | |
|     case eQueryEditorRect:
 | |
|       MOZ_LOG(sContentCacheLog, LogLevel::Info,
 | |
|               ("0x%p HandleQueryContentEvent(aEvent={ "
 | |
|                "mMessage=eQueryEditorRect }, aWidget=0x%p)",
 | |
|                this, aWidget));
 | |
|       // XXX This query should fail if no editable elmenet has focus.  Or,
 | |
|       //     perhaps, should return rect of the window instead.
 | |
|       aEvent.EmplaceReply();
 | |
|       aEvent.mReply->mFocusedWidget = aWidget;
 | |
|       aEvent.mReply->mRect = mEditorRect;
 | |
|       MOZ_LOG(sContentCacheLog, LogLevel::Info,
 | |
|               ("0x%p   HandleQueryContentEvent(), Succeeded, aEvent={ "
 | |
|                "mMessage=eQueryEditorRect, mReply=%s }",
 | |
|                this, ToString(aEvent.mReply).c_str()));
 | |
|       return true;
 | |
|     default:
 | |
|       aEvent.EmplaceReply();
 | |
|       aEvent.mReply->mFocusedWidget = aWidget;
 | |
|       if (NS_WARN_IF(aEvent.Failed())) {
 | |
|         MOZ_LOG(
 | |
|             sContentCacheLog, LogLevel::Error,
 | |
|             ("0x%p HandleQueryContentEvent(), FAILED due to not set enough "
 | |
|              "data, aEvent={ mMessage=%s, mReply=%s }",
 | |
|              this, ToChar(aEvent.mMessage), ToString(aEvent.mReply).c_str()));
 | |
|         return false;
 | |
|       }
 | |
|       MOZ_LOG(sContentCacheLog, LogLevel::Info,
 | |
|               ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
 | |
|                "mMessage=%s, mReply=%s }",
 | |
|                this, ToChar(aEvent.mMessage), ToString(aEvent.mReply).c_str()));
 | |
|       return true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool ContentCacheInParent::GetTextRect(uint32_t aOffset,
 | |
|                                        bool aRoundToExistingOffset,
 | |
|                                        LayoutDeviceIntRect& aTextRect) const {
 | |
|   MOZ_LOG(
 | |
|       sContentCacheLog, LogLevel::Info,
 | |
|       ("0x%p GetTextRect(aOffset=%u, aRoundToExistingOffset=%s), "
 | |
|        "mTextRectArray=%s, mSelection=%s, mLastCommitStringTextRectArray=%s",
 | |
|        this, aOffset, GetBoolName(aRoundToExistingOffset),
 | |
|        ToString(mTextRectArray).c_str(), ToString(mSelection).c_str(),
 | |
|        ToString(mLastCommitStringTextRectArray).c_str()));
 | |
| 
 | |
|   if (!aOffset) {
 | |
|     NS_WARNING_ASSERTION(!mFirstCharRect.IsEmpty(), "empty rect");
 | |
|     aTextRect = mFirstCharRect;
 | |
|     return !aTextRect.IsEmpty();
 | |
|   }
 | |
|   if (mSelection.isSome() && mSelection->mHasRange) {
 | |
|     if (aOffset == mSelection->mAnchor) {
 | |
|       NS_WARNING_ASSERTION(
 | |
|           !mSelection->mAnchorCharRects[eNextCharRect].IsEmpty(), "empty rect");
 | |
|       aTextRect = mSelection->mAnchorCharRects[eNextCharRect];
 | |
|       return !aTextRect.IsEmpty();
 | |
|     }
 | |
|     if (mSelection->mAnchor && aOffset == mSelection->mAnchor - 1) {
 | |
|       NS_WARNING_ASSERTION(
 | |
|           !mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty(), "empty rect");
 | |
|       aTextRect = mSelection->mAnchorCharRects[ePrevCharRect];
 | |
|       return !aTextRect.IsEmpty();
 | |
|     }
 | |
|     if (aOffset == mSelection->mFocus) {
 | |
|       NS_WARNING_ASSERTION(
 | |
|           !mSelection->mFocusCharRects[eNextCharRect].IsEmpty(), "empty rect");
 | |
|       aTextRect = mSelection->mFocusCharRects[eNextCharRect];
 | |
|       return !aTextRect.IsEmpty();
 | |
|     }
 | |
|     if (mSelection->mFocus && aOffset == mSelection->mFocus - 1) {
 | |
|       NS_WARNING_ASSERTION(
 | |
|           !mSelection->mFocusCharRects[ePrevCharRect].IsEmpty(), "empty rect");
 | |
|       aTextRect = mSelection->mFocusCharRects[ePrevCharRect];
 | |
|       return !aTextRect.IsEmpty();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (mTextRectArray.isSome() && mTextRectArray->IsOffsetInRange(aOffset)) {
 | |
|     aTextRect = mTextRectArray->GetRect(aOffset);
 | |
|     return !aTextRect.IsEmpty();
 | |
|   }
 | |
| 
 | |
|   if (mLastCommitStringTextRectArray.isSome() &&
 | |
|       mLastCommitStringTextRectArray->IsOffsetInRange(aOffset)) {
 | |
|     aTextRect = mLastCommitStringTextRectArray->GetRect(aOffset);
 | |
|     return !aTextRect.IsEmpty();
 | |
|   }
 | |
| 
 | |
|   if (!aRoundToExistingOffset) {
 | |
|     aTextRect.SetEmpty();
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (mTextRectArray.isNothing() || !mTextRectArray->HasRects()) {
 | |
|     // If there are no rects in mTextRectArray, we should refer the start of
 | |
|     // the selection if there is because IME must query a char rect around it if
 | |
|     // there is no composition.
 | |
|     if (mSelection.isNothing()) {
 | |
|       // Unfortunately, there is no data about text rect...
 | |
|       aTextRect.SetEmpty();
 | |
|       return false;
 | |
|     }
 | |
|     aTextRect = mSelection->StartCharRect();
 | |
|     return !aTextRect.IsEmpty();
 | |
|   }
 | |
| 
 | |
|   // Although we may have mLastCommitStringTextRectArray here and it must have
 | |
|   // previous character rects at selection.  However, we should stop using it
 | |
|   // because it's stored really short time after commiting a composition.
 | |
|   // So, multiple query may return different rect and it may cause flickerling
 | |
|   // the IME UI.
 | |
|   uint32_t offset = aOffset;
 | |
|   if (offset < mTextRectArray->StartOffset()) {
 | |
|     offset = mTextRectArray->StartOffset();
 | |
|   } else {
 | |
|     offset = mTextRectArray->EndOffset() - 1;
 | |
|   }
 | |
|   aTextRect = mTextRectArray->GetRect(offset);
 | |
|   return !aTextRect.IsEmpty();
 | |
| }
 | |
| 
 | |
| bool ContentCacheInParent::GetUnionTextRects(
 | |
|     uint32_t aOffset, uint32_t aLength, bool aRoundToExistingOffset,
 | |
|     LayoutDeviceIntRect& aUnionTextRect) const {
 | |
|   MOZ_LOG(sContentCacheLog, LogLevel::Info,
 | |
|           ("0x%p GetUnionTextRects(aOffset=%u, "
 | |
|            "aLength=%u, aRoundToExistingOffset=%s), mTextRectArray=%s, "
 | |
|            "mSelection=%s, mLastCommitStringTextRectArray=%s",
 | |
|            this, aOffset, aLength, GetBoolName(aRoundToExistingOffset),
 | |
|            ToString(mTextRectArray).c_str(), ToString(mSelection).c_str(),
 | |
|            ToString(mLastCommitStringTextRectArray).c_str()));
 | |
| 
 | |
|   CheckedInt<uint32_t> endOffset = CheckedInt<uint32_t>(aOffset) + aLength;
 | |
|   if (!endOffset.isValid()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (mSelection.isSome() && !mSelection->IsCollapsed() &&
 | |
|       aOffset == mSelection->StartOffset() && aLength == mSelection->Length()) {
 | |
|     NS_WARNING_ASSERTION(!mSelection->mRect.IsEmpty(), "empty rect");
 | |
|     aUnionTextRect = mSelection->mRect;
 | |
|     return !aUnionTextRect.IsEmpty();
 | |
|   }
 | |
| 
 | |
|   if (aLength == 1) {
 | |
|     if (!aOffset) {
 | |
|       NS_WARNING_ASSERTION(!mFirstCharRect.IsEmpty(), "empty rect");
 | |
|       aUnionTextRect = mFirstCharRect;
 | |
|       return !aUnionTextRect.IsEmpty();
 | |
|     }
 | |
|     if (mSelection.isSome() && mSelection->mHasRange) {
 | |
|       if (aOffset == mSelection->mAnchor) {
 | |
|         NS_WARNING_ASSERTION(
 | |
|             !mSelection->mAnchorCharRects[eNextCharRect].IsEmpty(),
 | |
|             "empty rect");
 | |
|         aUnionTextRect = mSelection->mAnchorCharRects[eNextCharRect];
 | |
|         return !aUnionTextRect.IsEmpty();
 | |
|       }
 | |
|       if (mSelection->mAnchor && aOffset == mSelection->mAnchor - 1) {
 | |
|         NS_WARNING_ASSERTION(
 | |
|             !mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty(),
 | |
|             "empty rect");
 | |
|         aUnionTextRect = mSelection->mAnchorCharRects[ePrevCharRect];
 | |
|         return !aUnionTextRect.IsEmpty();
 | |
|       }
 | |
|       if (aOffset == mSelection->mFocus) {
 | |
|         NS_WARNING_ASSERTION(
 | |
|             !mSelection->mFocusCharRects[eNextCharRect].IsEmpty(),
 | |
|             "empty rect");
 | |
|         aUnionTextRect = mSelection->mFocusCharRects[eNextCharRect];
 | |
|         return !aUnionTextRect.IsEmpty();
 | |
|       }
 | |
|       if (mSelection->mFocus && aOffset == mSelection->mFocus - 1) {
 | |
|         NS_WARNING_ASSERTION(
 | |
|             !mSelection->mFocusCharRects[ePrevCharRect].IsEmpty(),
 | |
|             "empty rect");
 | |
|         aUnionTextRect = mSelection->mFocusCharRects[ePrevCharRect];
 | |
|         return !aUnionTextRect.IsEmpty();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Even if some text rects are not cached of the queried range,
 | |
|   // we should return union rect when the first character's rect is cached
 | |
|   // since the first character rect is important and the others are not so
 | |
|   // in most cases.
 | |
| 
 | |
|   if (!aOffset && mSelection.isSome() && mSelection->mHasRange &&
 | |
|       aOffset != mSelection->mAnchor && aOffset != mSelection->mFocus &&
 | |
|       (mTextRectArray.isNothing() ||
 | |
|        !mTextRectArray->IsOffsetInRange(aOffset)) &&
 | |
|       (mLastCommitStringTextRectArray.isNothing() ||
 | |
|        !mLastCommitStringTextRectArray->IsOffsetInRange(aOffset))) {
 | |
|     // The first character rect isn't cached.
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Use mLastCommitStringTextRectArray only when it overlaps with aOffset
 | |
|   // even if aROundToExistingOffset is true for avoiding flickerling IME UI.
 | |
|   // See the last comment in GetTextRect() for the detail.
 | |
|   if (mLastCommitStringTextRectArray.isSome() &&
 | |
|       mLastCommitStringTextRectArray->IsOverlappingWith(aOffset, aLength)) {
 | |
|     aUnionTextRect =
 | |
|         mLastCommitStringTextRectArray->GetUnionRectAsFarAsPossible(
 | |
|             aOffset, aLength, aRoundToExistingOffset);
 | |
|   } else {
 | |
|     aUnionTextRect.SetEmpty();
 | |
|   }
 | |
| 
 | |
|   if (mTextRectArray.isSome() &&
 | |
|       ((aRoundToExistingOffset && mTextRectArray->HasRects()) ||
 | |
|        mTextRectArray->IsOverlappingWith(aOffset, aLength))) {
 | |
|     aUnionTextRect =
 | |
|         aUnionTextRect.Union(mTextRectArray->GetUnionRectAsFarAsPossible(
 | |
|             aOffset, aLength, aRoundToExistingOffset));
 | |
|   }
 | |
| 
 | |
|   if (!aOffset) {
 | |
|     aUnionTextRect = aUnionTextRect.Union(mFirstCharRect);
 | |
|   }
 | |
|   if (mSelection.isSome() && mSelection->mHasRange) {
 | |
|     if (aOffset <= mSelection->mAnchor &&
 | |
|         mSelection->mAnchor < endOffset.value()) {
 | |
|       aUnionTextRect =
 | |
|           aUnionTextRect.Union(mSelection->mAnchorCharRects[eNextCharRect]);
 | |
|     }
 | |
|     if (mSelection->mAnchor && aOffset <= mSelection->mAnchor - 1 &&
 | |
|         mSelection->mAnchor - 1 < endOffset.value()) {
 | |
|       aUnionTextRect =
 | |
|           aUnionTextRect.Union(mSelection->mAnchorCharRects[ePrevCharRect]);
 | |
|     }
 | |
|     if (aOffset <= mSelection->mFocus &&
 | |
|         mSelection->mFocus < endOffset.value()) {
 | |
|       aUnionTextRect =
 | |
|           aUnionTextRect.Union(mSelection->mFocusCharRects[eNextCharRect]);
 | |
|     }
 | |
|     if (mSelection->mFocus && aOffset <= mSelection->mFocus - 1 &&
 | |
|         mSelection->mFocus - 1 < endOffset.value()) {
 | |
|       aUnionTextRect =
 | |
|           aUnionTextRect.Union(mSelection->mFocusCharRects[ePrevCharRect]);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return !aUnionTextRect.IsEmpty();
 | |
| }
 | |
| 
 | |
| bool ContentCacheInParent::GetCaretRect(uint32_t aOffset,
 | |
|                                         bool aRoundToExistingOffset,
 | |
|                                         LayoutDeviceIntRect& aCaretRect) const {
 | |
|   MOZ_LOG(sContentCacheLog, LogLevel::Info,
 | |
|           ("0x%p GetCaretRect(aOffset=%u, aRoundToExistingOffset=%s), "
 | |
|            "mCaret=%s, mTextRectArray=%s, mSelection=%s, mFirstCharRect=%s",
 | |
|            this, aOffset, GetBoolName(aRoundToExistingOffset),
 | |
|            ToString(mCaret).c_str(), ToString(mTextRectArray).c_str(),
 | |
|            ToString(mSelection).c_str(), ToString(mFirstCharRect).c_str()));
 | |
| 
 | |
|   if (mCaret.isSome() && mCaret->mOffset == aOffset) {
 | |
|     aCaretRect = mCaret->mRect;
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // Guess caret rect from the text rect if it's stored.
 | |
|   if (!GetTextRect(aOffset, aRoundToExistingOffset, aCaretRect)) {
 | |
|     // There might be previous character rect in the cache.  If so, we can
 | |
|     // guess the caret rect with it.
 | |
|     if (!aOffset ||
 | |
|         !GetTextRect(aOffset - 1, aRoundToExistingOffset, aCaretRect)) {
 | |
|       aCaretRect.SetEmpty();
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     if (mSelection.isSome() && mSelection->mWritingMode.IsVertical()) {
 | |
|       aCaretRect.MoveToY(aCaretRect.YMost());
 | |
|     } else {
 | |
|       // XXX bidi-unaware.
 | |
|       aCaretRect.MoveToX(aCaretRect.XMost());
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // XXX This is not bidi aware because we don't cache each character's
 | |
|   //     direction.  However, this is usually used by IME, so, assuming the
 | |
|   //     character is in LRT context must not cause any problem.
 | |
|   if (mSelection.isSome() && mSelection->mWritingMode.IsVertical()) {
 | |
|     aCaretRect.SetHeight(mCaret.isSome() ? mCaret->mRect.Height() : 1);
 | |
|   } else {
 | |
|     aCaretRect.SetWidth(mCaret.isSome() ? mCaret->mRect.Width() : 1);
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool ContentCacheInParent::OnCompositionEvent(
 | |
|     const WidgetCompositionEvent& aCompositionEvent) {
 | |
|   MOZ_LOG(
 | |
|       sContentCacheLog, LogLevel::Info,
 | |
|       ("0x%p OnCompositionEvent(aCompositionEvent={ "
 | |
|        "mMessage=%s, mData=\"%s\", mRanges->Length()=%zu }), "
 | |
|        "PendingEventsNeedingAck()=%u, WidgetHasComposition()=%s, "
 | |
|        "mHandlingCompositions.Length()=%zu, HasPendingCommit()=%s, "
 | |
|        "mIsChildIgnoringCompositionEvents=%s, mCommitStringByRequest=0x%p",
 | |
|        this, ToChar(aCompositionEvent.mMessage),
 | |
|        PrintStringDetail(aCompositionEvent.mData,
 | |
|                          PrintStringDetail::kMaxLengthForCompositionString)
 | |
|            .get(),
 | |
|        aCompositionEvent.mRanges ? aCompositionEvent.mRanges->Length() : 0,
 | |
|        PendingEventsNeedingAck(), GetBoolName(WidgetHasComposition()),
 | |
|        mHandlingCompositions.Length(), GetBoolName(HasPendingCommit()),
 | |
|        GetBoolName(mIsChildIgnoringCompositionEvents), mCommitStringByRequest));
 | |
| 
 | |
| #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
|   mDispatchedEventMessages.AppendElement(aCompositionEvent.mMessage);
 | |
| #endif  // #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
| 
 | |
|   // We must be able to simulate the selection because
 | |
|   // we might not receive selection updates in time
 | |
|   if (!WidgetHasComposition()) {
 | |
|     if (mCompositionStartInChild.isSome()) {
 | |
|       // If there is pending composition in the remote process, let's use
 | |
|       // its start offset temporarily because this stores a lot of information
 | |
|       // around it and the user must look around there, so, showing some UI
 | |
|       // around it must make sense.
 | |
|       mCompositionStart = mCompositionStartInChild;
 | |
|     } else {
 | |
|       mCompositionStart = Some(mSelection.isSome() && mSelection->mHasRange
 | |
|                                    ? mSelection->StartOffset()
 | |
|                                    : 0u);
 | |
|     }
 | |
|     MOZ_ASSERT(aCompositionEvent.mMessage == eCompositionStart);
 | |
|     mHandlingCompositions.AppendElement(
 | |
|         HandlingCompositionData(aCompositionEvent.mCompositionId));
 | |
|   }
 | |
| 
 | |
|   mHandlingCompositions.LastElement().mSentCommitEvent =
 | |
|       aCompositionEvent.CausesDOMCompositionEndEvent();
 | |
|   MOZ_ASSERT(mHandlingCompositions.LastElement().mCompositionId ==
 | |
|              aCompositionEvent.mCompositionId);
 | |
| 
 | |
|   if (!WidgetHasComposition()) {
 | |
|     // mCompositionStart will be reset when commit event is completely handled
 | |
|     // in the remote process.
 | |
|     if (mHandlingCompositions.Length() == 1u) {
 | |
|       mPendingCommitLength = aCompositionEvent.mData.Length();
 | |
|     }
 | |
|     MOZ_ASSERT(HasPendingCommit());
 | |
|   } else if (aCompositionEvent.mMessage != eCompositionStart) {
 | |
|     mHandlingCompositions.LastElement().mCompositionString =
 | |
|         aCompositionEvent.mData;
 | |
|   }
 | |
| 
 | |
|   // During REQUEST_TO_COMMIT_COMPOSITION or REQUEST_TO_CANCEL_COMPOSITION,
 | |
|   // widget usually sends a eCompositionChange and/or eCompositionCommit event
 | |
|   // to finalize or clear the composition, respectively.  In this time,
 | |
|   // we need to intercept all composition events here and pass the commit
 | |
|   // string for returning to the remote process as a result of
 | |
|   // RequestIMEToCommitComposition().  Then, eCommitComposition event will
 | |
|   // be dispatched with the committed string in the remote process internally.
 | |
|   if (mCommitStringByRequest) {
 | |
|     if (aCompositionEvent.mMessage == eCompositionCommitAsIs) {
 | |
|       *mCommitStringByRequest =
 | |
|           mHandlingCompositions.LastElement().mCompositionString;
 | |
|     } else {
 | |
|       MOZ_ASSERT(aCompositionEvent.mMessage == eCompositionChange ||
 | |
|                  aCompositionEvent.mMessage == eCompositionCommit);
 | |
|       *mCommitStringByRequest = aCompositionEvent.mData;
 | |
|     }
 | |
|     // We need to wait eCompositionCommitRequestHandled from the remote process
 | |
|     // in this case.  Therefore, mPendingEventsNeedingAck needs to be
 | |
|     // incremented here.
 | |
|     if (!WidgetHasComposition()) {
 | |
|       mHandlingCompositions.LastElement().mPendingEventsNeedingAck++;
 | |
|     }
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   mHandlingCompositions.LastElement().mPendingEventsNeedingAck++;
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void ContentCacheInParent::OnSelectionEvent(
 | |
|     const WidgetSelectionEvent& aSelectionEvent) {
 | |
|   MOZ_LOG(sContentCacheLog, LogLevel::Info,
 | |
|           ("0x%p OnSelectionEvent(aEvent={ "
 | |
|            "mMessage=%s, mOffset=%u, mLength=%u, mReversed=%s, "
 | |
|            "mExpandToClusterBoundary=%s, mUseNativeLineBreak=%s }), "
 | |
|            "PendingEventsNeedingAck()=%u, WidgetHasComposition()=%s, "
 | |
|            "mHandlingCompositions.Length()=%zu, HasPendingCommit()=%s, "
 | |
|            "mIsChildIgnoringCompositionEvents=%s",
 | |
|            this, ToChar(aSelectionEvent.mMessage), aSelectionEvent.mOffset,
 | |
|            aSelectionEvent.mLength, GetBoolName(aSelectionEvent.mReversed),
 | |
|            GetBoolName(aSelectionEvent.mExpandToClusterBoundary),
 | |
|            GetBoolName(aSelectionEvent.mUseNativeLineBreak),
 | |
|            PendingEventsNeedingAck(), GetBoolName(WidgetHasComposition()),
 | |
|            mHandlingCompositions.Length(), GetBoolName(HasPendingCommit()),
 | |
|            GetBoolName(mIsChildIgnoringCompositionEvents)));
 | |
| 
 | |
| #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
 | |
|   mDispatchedEventMessages.AppendElement(aSelectionEvent.mMessage);
 | |
| #endif  // MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
| 
 | |
|   mPendingSetSelectionEventNeedingAck++;
 | |
| }
 | |
| 
 | |
| void ContentCacheInParent::OnEventNeedingAckHandled(nsIWidget* aWidget,
 | |
|                                                     EventMessage aMessage,
 | |
|                                                     uint32_t aCompositionId) {
 | |
|   // This is called when the child process receives WidgetCompositionEvent or
 | |
|   // WidgetSelectionEvent.
 | |
| 
 | |
|   HandlingCompositionData* handlingCompositionData =
 | |
|       aMessage != eSetSelection ? GetHandlingCompositionData(aCompositionId)
 | |
|                                 : nullptr;
 | |
| 
 | |
|   MOZ_LOG(sContentCacheLog, LogLevel::Info,
 | |
|           ("0x%p OnEventNeedingAckHandled(aWidget=0x%p, aMessage=%s, "
 | |
|            "aCompositionId=%" PRIu32
 | |
|            "), PendingEventsNeedingAck()=%u, WidgetHasComposition()=%s, "
 | |
|            "mHandlingCompositions.Length()=%zu, HasPendingCommit()=%s, "
 | |
|            "mIsChildIgnoringCompositionEvents=%s, handlingCompositionData=0x%p",
 | |
|            this, aWidget, ToChar(aMessage), aCompositionId,
 | |
|            PendingEventsNeedingAck(), GetBoolName(WidgetHasComposition()),
 | |
|            mHandlingCompositions.Length(), GetBoolName(HasPendingCommit()),
 | |
|            GetBoolName(mIsChildIgnoringCompositionEvents),
 | |
|            handlingCompositionData));
 | |
| 
 | |
|   // If we receive composition event messages for older one or invalid one,
 | |
|   // we should ignore them.
 | |
|   if (NS_WARN_IF(aMessage != eSetSelection && !handlingCompositionData)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
| #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
 | |
|   mReceivedEventMessages.AppendElement(aMessage);
 | |
| #endif  // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
| 
 | |
|   const bool isCommittedInChild =
 | |
|       // Commit requester in the remote process has committed the composition.
 | |
|       aMessage == eCompositionCommitRequestHandled ||
 | |
|       // The commit event has been handled normally in the remote process.
 | |
|       (!mIsChildIgnoringCompositionEvents &&
 | |
|        WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage));
 | |
|   const bool hasPendingCommit = HasPendingCommit();
 | |
| 
 | |
|   if (isCommittedInChild) {
 | |
| #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
 | |
|     if (mHandlingCompositions.Length() == 1u) {
 | |
|       RemoveUnnecessaryEventMessageLog();
 | |
|     }
 | |
| 
 | |
|     if (NS_WARN_IF(aMessage != eCompositionCommitRequestHandled &&
 | |
|                    !handlingCompositionData->mSentCommitEvent)) {
 | |
|       nsPrintfCString info(
 | |
|           "\nReceived unexpected commit event message (%s) which we've "
 | |
|           "not sent yet\n\n",
 | |
|           ToChar(aMessage));
 | |
|       AppendEventMessageLog(info);
 | |
|       CrashReporter::AppendAppNotesToCrashReport(info);
 | |
|       MOZ_DIAGNOSTIC_ASSERT(
 | |
|           false,
 | |
|           "Received unexpected commit event which has not been sent yet");
 | |
|     }
 | |
| #endif  // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
| 
 | |
|     // This should not occur, though.  If we receive a commit notification for
 | |
|     // not the oldest composition, we should forget all older compositions.
 | |
|     size_t numberOfOutdatedCompositions = 1u;
 | |
|     for (auto& data : mHandlingCompositions) {
 | |
|       if (&data == handlingCompositionData) {
 | |
|         if (
 | |
|             // Don't put the info into the log when we've already sent commit
 | |
|             // event because it may be just inserting a character without
 | |
|             // composing state, but the remote process may move focus at
 | |
|             // eCompositionStart.  This may happen with UI of IME to put only
 | |
|             // one character, e.g., the default Emoji picker of Windows.
 | |
|             !data.mSentCommitEvent &&
 | |
|             // In the normal case, only one message should remain, however,
 | |
|             // remaining 2 or more messages is also valid, for example, the
 | |
|             // remote process may have a composition update listener which
 | |
|             // takes a while.  Then, we can have multiple pending messages.
 | |
|             data.mPendingEventsNeedingAck >= 1u) {
 | |
|           MOZ_LOG(
 | |
|               sContentCacheLog, LogLevel::Debug,
 | |
|               ("    NOTE: BrowserParent has %" PRIu32
 | |
|                " pending composition messages for the handling composition, "
 | |
|                "but before they are handled in the remote process, the active "
 | |
|                "composition is commited by a request.  "
 | |
|                "OnEventNeedingAckHandled() calls for them will be ignored",
 | |
|                data.mPendingEventsNeedingAck));
 | |
|         }
 | |
|         break;
 | |
|       }
 | |
|       if (MOZ_UNLIKELY(data.mPendingEventsNeedingAck)) {
 | |
|         MOZ_LOG(sContentCacheLog, LogLevel::Warning,
 | |
|                 ("    BrowserParent has %" PRIu32
 | |
|                  " pending composition messages for an older composition than "
 | |
|                  "the handling composition, but it'll be removed because newer "
 | |
|                  "composition gets comitted in the remote process",
 | |
|                  data.mPendingEventsNeedingAck));
 | |
|       }
 | |
|       numberOfOutdatedCompositions++;
 | |
|     }
 | |
|     mHandlingCompositions.RemoveElementsAt(0u, numberOfOutdatedCompositions);
 | |
|     handlingCompositionData = nullptr;
 | |
| 
 | |
|     // Forget pending commit string length if it's handled in the remote
 | |
|     // process.  Note that this doesn't care too old composition's commit
 | |
|     // string because in such case, we cannot return proper information
 | |
|     // to IME synchronously.
 | |
|     mPendingCommitLength = 0;
 | |
|   }
 | |
| 
 | |
|   if (WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage)) {
 | |
|     // After the remote process receives eCompositionCommit(AsIs) event,
 | |
|     // it'll restart to handle composition events.
 | |
|     mIsChildIgnoringCompositionEvents = false;
 | |
| 
 | |
| #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
 | |
|     if (NS_WARN_IF(!hasPendingCommit)) {
 | |
|       nsPrintfCString info(
 | |
|           "\nThere is no pending comment events but received "
 | |
|           "%s message from the remote child\n\n",
 | |
|           ToChar(aMessage));
 | |
|       AppendEventMessageLog(info);
 | |
|       CrashReporter::AppendAppNotesToCrashReport(info);
 | |
|       MOZ_DIAGNOSTIC_ASSERT(
 | |
|           false,
 | |
|           "No pending commit events but received unexpected commit event");
 | |
|     }
 | |
| #endif  // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
|   } else if (aMessage == eCompositionCommitRequestHandled && hasPendingCommit) {
 | |
|     // If the remote process commits composition synchronously after
 | |
|     // requesting commit composition and we've already sent commit composition,
 | |
|     // it starts to ignore following composition events until receiving
 | |
|     // eCompositionStart event.
 | |
|     mIsChildIgnoringCompositionEvents = true;
 | |
|   }
 | |
| 
 | |
|   // If neither widget (i.e., IME) nor the remote process has composition,
 | |
|   // now, we can forget composition string informations.
 | |
|   if (mHandlingCompositions.IsEmpty()) {
 | |
|     mCompositionStart.reset();
 | |
|   }
 | |
| 
 | |
|   if (handlingCompositionData) {
 | |
|     if (NS_WARN_IF(!handlingCompositionData->mPendingEventsNeedingAck)) {
 | |
| #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
 | |
|       nsPrintfCString info(
 | |
|           "\nThere is no pending events but received %s "
 | |
|           "message from the remote child\n\n",
 | |
|           ToChar(aMessage));
 | |
|       AppendEventMessageLog(info);
 | |
|       CrashReporter::AppendAppNotesToCrashReport(info);
 | |
|       MOZ_DIAGNOSTIC_ASSERT(
 | |
|           false, "No pending event message but received unexpected event");
 | |
| #endif  // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
|     } else {
 | |
|       handlingCompositionData->mPendingEventsNeedingAck--;
 | |
|     }
 | |
|   } else if (aMessage == eSetSelection) {
 | |
|     if (NS_WARN_IF(!mPendingSetSelectionEventNeedingAck)) {
 | |
| #if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
 | |
|       nsAutoCString info(
 | |
|           "\nThere is no pending set selection events but received from the "
 | |
|           "remote child\n\n");
 | |
|       AppendEventMessageLog(info);
 | |
|       CrashReporter::AppendAppNotesToCrashReport(info);
 | |
|       MOZ_DIAGNOSTIC_ASSERT(
 | |
|           false, "No pending event message but received unexpected event");
 | |
| #endif  // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
|     } else {
 | |
|       mPendingSetSelectionEventNeedingAck--;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (!PendingEventsNeedingAck()) {
 | |
|     FlushPendingNotifications(aWidget);
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool ContentCacheInParent::RequestIMEToCommitComposition(
 | |
|     nsIWidget* aWidget, bool aCancel, uint32_t aCompositionId,
 | |
|     nsAString& aCommittedString) {
 | |
|   HandlingCompositionData* const handlingCompositionData =
 | |
|       GetHandlingCompositionData(aCompositionId);
 | |
| 
 | |
|   MOZ_LOG(
 | |
|       sContentCacheLog, LogLevel::Info,
 | |
|       ("0x%p RequestToCommitComposition(aWidget=%p, "
 | |
|        "aCancel=%s, aCompositionId=%" PRIu32
 | |
|        "), mHandlingCompositions.Length()=%zu, HasPendingCommit()=%s, "
 | |
|        "mIsChildIgnoringCompositionEvents=%s, "
 | |
|        "IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)=%s, "
 | |
|        "WidgetHasComposition()=%s, mCommitStringByRequest=%p, "
 | |
|        "handlingCompositionData=0x%p",
 | |
|        this, aWidget, GetBoolName(aCancel), aCompositionId,
 | |
|        mHandlingCompositions.Length(), GetBoolName(HasPendingCommit()),
 | |
|        GetBoolName(mIsChildIgnoringCompositionEvents),
 | |
|        GetBoolName(
 | |
|            IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)),
 | |
|        GetBoolName(WidgetHasComposition()), mCommitStringByRequest,
 | |
|        handlingCompositionData));
 | |
| 
 | |
|   MOZ_ASSERT(!mCommitStringByRequest);
 | |
| 
 | |
|   // If we don't know the composition ID, it must have already been committed
 | |
|   // in this process.  In the case, we should do nothing here.
 | |
|   if (NS_WARN_IF(!handlingCompositionData)) {
 | |
| #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
|     mRequestIMEToCommitCompositionResults.AppendElement(
 | |
|         RequestIMEToCommitCompositionResult::eToUnknownCompositionReceived);
 | |
| #endif  // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // If we receive a commit result for not latest composition, this request is
 | |
|   // too late for IME.  The remote process should wait following composition
 | |
|   // events for cleaning up TextComposition and handle the request as it's
 | |
|   // handled asynchronously.
 | |
|   if (handlingCompositionData != &mHandlingCompositions.LastElement()) {
 | |
| #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
|     mRequestIMEToCommitCompositionResults.AppendElement(
 | |
|         RequestIMEToCommitCompositionResult::eToOldCompositionReceived);
 | |
| #endif  // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // If the composition has already been commit, th remote process will receive
 | |
|   // composition events and clean up TextComposition.  So, this should do
 | |
|   // nothing and TextComposition should handle the request as it's handled
 | |
|   // asynchronously.
 | |
|   // XXX Perhaps, this is wrong because TextComposition in child process
 | |
|   //     may commit the composition with current composition string in the
 | |
|   //     remote process.  I.e., it may be different from actual commit string
 | |
|   //     which user typed.  So, perhaps, we should return true and the commit
 | |
|   //     string.
 | |
|   if (handlingCompositionData->mSentCommitEvent) {
 | |
| #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
|     mRequestIMEToCommitCompositionResults.AppendElement(
 | |
|         RequestIMEToCommitCompositionResult::eToCommittedCompositionReceived);
 | |
| #endif  // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // If BrowserParent which has IME focus was already changed to different one,
 | |
|   // the request shouldn't be sent to IME because it's too late.
 | |
|   if (!IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)) {
 | |
|     // Use the latest composition string which may not be handled in the
 | |
|     // remote process for avoiding data loss.
 | |
| #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
|     mRequestIMEToCommitCompositionResults.AppendElement(
 | |
|         RequestIMEToCommitCompositionResult::eReceivedAfterBrowserParentBlur);
 | |
| #endif  // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
|     aCommittedString = handlingCompositionData->mCompositionString;
 | |
|     // After we return true from here, i.e., without actually requesting IME
 | |
|     // to commit composition, we will receive eCompositionCommitRequestHandled
 | |
|     // pseudo event message from the remote process.  So, we need to increment
 | |
|     // mPendingEventsNeedingAck here.
 | |
|     handlingCompositionData->mPendingEventsNeedingAck++;
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   RefPtr<TextComposition> composition =
 | |
|       IMEStateManager::GetTextCompositionFor(aWidget);
 | |
|   if (NS_WARN_IF(!composition)) {
 | |
|     MOZ_LOG(sContentCacheLog, LogLevel::Warning,
 | |
|             ("  0x%p RequestToCommitComposition(), "
 | |
|              "does nothing due to no composition",
 | |
|              this));
 | |
| #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
|     mRequestIMEToCommitCompositionResults.AppendElement(
 | |
|         RequestIMEToCommitCompositionResult::eReceivedButNoTextComposition);
 | |
| #endif  // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // If we receive a request for different composition, we must have already
 | |
|   // sent a commit event. So the remote process should handle it.
 | |
|   // XXX I think that this should never happen because we already checked
 | |
|   // whether handlingCompositionData is the latest composition or not.
 | |
|   // However, we don't want to commit different composition for the users.
 | |
|   // Therefore, let's handle the odd case here.
 | |
|   if (NS_WARN_IF(composition->Id() != aCompositionId)) {
 | |
| #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
|     mRequestIMEToCommitCompositionResults.AppendElement(
 | |
|         RequestIMEToCommitCompositionResult::
 | |
|             eReceivedButForDifferentTextComposition);
 | |
| #endif  // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   mCommitStringByRequest = &aCommittedString;
 | |
| 
 | |
|   // Request commit or cancel composition with TextComposition because we may
 | |
|   // have already requested to commit or cancel the composition or we may
 | |
|   // have already received eCompositionCommit(AsIs) event.  Those status are
 | |
|   // managed by composition.  So, if we don't request commit composition,
 | |
|   // we should do nothing with native IME here.
 | |
|   composition->RequestToCommit(aWidget, aCancel);
 | |
| 
 | |
|   mCommitStringByRequest = nullptr;
 | |
| 
 | |
|   MOZ_LOG(
 | |
|       sContentCacheLog, LogLevel::Info,
 | |
|       ("  0x%p RequestToCommitComposition(), "
 | |
|        "WidgetHasComposition()=%s, the composition %s committed synchronously",
 | |
|        this, GetBoolName(WidgetHasComposition()),
 | |
|        composition->Destroyed() ? "WAS" : "has NOT been"));
 | |
| 
 | |
|   if (!composition->Destroyed()) {
 | |
|     // When the composition isn't committed synchronously, the remote process's
 | |
|     // TextComposition instance will synthesize commit events and wait to
 | |
|     // receive delayed composition events.  When TextComposition instances both
 | |
|     // in this process and the remote process will be destroyed when delayed
 | |
|     // composition events received. TextComposition instance in the parent
 | |
|     // process will dispatch following composition events and be destroyed
 | |
|     // normally. On the other hand, TextComposition instance in the remote
 | |
|     // process won't dispatch following composition events and will be
 | |
|     // destroyed by IMEStateManager::DispatchCompositionEvent().
 | |
| #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
|     mRequestIMEToCommitCompositionResults.AppendElement(
 | |
|         RequestIMEToCommitCompositionResult::eHandledAsynchronously);
 | |
| #endif  // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // When the composition is committed synchronously, the commit string will be
 | |
|   // returned to the remote process.  Then, PuppetWidget will dispatch
 | |
|   // eCompositionCommit event with the returned commit string (i.e., the value
 | |
|   // is aCommittedString of this method) and that causes destroying
 | |
|   // TextComposition instance in the remote process (Note that TextComposition
 | |
|   // instance in this process was already destroyed).
 | |
| #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
|   mRequestIMEToCommitCompositionResults.AppendElement(
 | |
|       RequestIMEToCommitCompositionResult::eHandledSynchronously);
 | |
| #endif  // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void ContentCacheInParent::MaybeNotifyIME(
 | |
|     nsIWidget* aWidget, const IMENotification& aNotification) {
 | |
|   if (!PendingEventsNeedingAck()) {
 | |
|     IMEStateManager::NotifyIME(aNotification, aWidget, &mBrowserParent);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   switch (aNotification.mMessage) {
 | |
|     case NOTIFY_IME_OF_SELECTION_CHANGE:
 | |
|       mPendingSelectionChange.MergeWith(aNotification);
 | |
|       break;
 | |
|     case NOTIFY_IME_OF_TEXT_CHANGE:
 | |
|       mPendingTextChange.MergeWith(aNotification);
 | |
|       break;
 | |
|     case NOTIFY_IME_OF_POSITION_CHANGE:
 | |
|       mPendingLayoutChange.MergeWith(aNotification);
 | |
|       break;
 | |
|     case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
 | |
|       mPendingCompositionUpdate.MergeWith(aNotification);
 | |
|       break;
 | |
|     default:
 | |
|       MOZ_CRASH("Unsupported notification");
 | |
|       break;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ContentCacheInParent::FlushPendingNotifications(nsIWidget* aWidget) {
 | |
|   MOZ_ASSERT(!PendingEventsNeedingAck());
 | |
| 
 | |
|   // If the BrowserParent's widget has already gone, this can do nothing since
 | |
|   // widget is necessary to notify IME of something.
 | |
|   if (!aWidget) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // New notifications which are notified during flushing pending notifications
 | |
|   // should be merged again.
 | |
|   const bool pendingEventNeedingAckIncremented =
 | |
|       !mHandlingCompositions.IsEmpty();
 | |
|   if (pendingEventNeedingAckIncremented) {
 | |
|     mHandlingCompositions.LastElement().mPendingEventsNeedingAck++;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIWidget> widget = aWidget;
 | |
| 
 | |
|   // First, text change notification should be sent because selection change
 | |
|   // notification notifies IME of current selection range in the latest content.
 | |
|   // So, IME may need the latest content before that.
 | |
|   if (mPendingTextChange.HasNotification()) {
 | |
|     IMENotification notification(mPendingTextChange);
 | |
|     if (!widget->Destroyed()) {
 | |
|       mPendingTextChange.Clear();
 | |
|       IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (mPendingSelectionChange.HasNotification()) {
 | |
|     IMENotification notification(mPendingSelectionChange);
 | |
|     if (!widget->Destroyed()) {
 | |
|       mPendingSelectionChange.Clear();
 | |
|       IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Layout change notification should be notified after selection change
 | |
|   // notification because IME may want to query position of new caret position.
 | |
|   if (mPendingLayoutChange.HasNotification()) {
 | |
|     IMENotification notification(mPendingLayoutChange);
 | |
|     if (!widget->Destroyed()) {
 | |
|       mPendingLayoutChange.Clear();
 | |
|       IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Finally, send composition update notification because it notifies IME of
 | |
|   // finishing handling whole sending events.
 | |
|   if (mPendingCompositionUpdate.HasNotification()) {
 | |
|     IMENotification notification(mPendingCompositionUpdate);
 | |
|     if (!widget->Destroyed()) {
 | |
|       mPendingCompositionUpdate.Clear();
 | |
|       IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Decrement it which was incremented above.
 | |
|   if (!mHandlingCompositions.IsEmpty() && pendingEventNeedingAckIncremented &&
 | |
|       mHandlingCompositions.LastElement().mPendingEventsNeedingAck) {
 | |
|     mHandlingCompositions.LastElement().mPendingEventsNeedingAck--;
 | |
|   }
 | |
| 
 | |
|   if (!PendingEventsNeedingAck() && !widget->Destroyed() &&
 | |
|       (mPendingTextChange.HasNotification() ||
 | |
|        mPendingSelectionChange.HasNotification() ||
 | |
|        mPendingLayoutChange.HasNotification() ||
 | |
|        mPendingCompositionUpdate.HasNotification())) {
 | |
|     FlushPendingNotifications(widget);
 | |
|   }
 | |
| }
 | |
| 
 | |
| #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
| 
 | |
| void ContentCacheInParent::RemoveUnnecessaryEventMessageLog() {
 | |
|   bool foundLastCompositionStart = false;
 | |
|   for (size_t i = mDispatchedEventMessages.Length(); i > 1; i--) {
 | |
|     if (mDispatchedEventMessages[i - 1] != eCompositionStart) {
 | |
|       continue;
 | |
|     }
 | |
|     if (!foundLastCompositionStart) {
 | |
|       // Find previous eCompositionStart of the latest eCompositionStart.
 | |
|       foundLastCompositionStart = true;
 | |
|       continue;
 | |
|     }
 | |
|     // Remove the messages before the last 2 sets of composition events.
 | |
|     mDispatchedEventMessages.RemoveElementsAt(0, i - 1);
 | |
|     break;
 | |
|   }
 | |
|   uint32_t numberOfCompositionCommitRequestHandled = 0;
 | |
|   foundLastCompositionStart = false;
 | |
|   for (size_t i = mReceivedEventMessages.Length(); i > 1; i--) {
 | |
|     if (mReceivedEventMessages[i - 1] == eCompositionCommitRequestHandled) {
 | |
|       numberOfCompositionCommitRequestHandled++;
 | |
|     }
 | |
|     if (mReceivedEventMessages[i - 1] != eCompositionStart) {
 | |
|       continue;
 | |
|     }
 | |
|     if (!foundLastCompositionStart) {
 | |
|       // Find previous eCompositionStart of the latest eCompositionStart.
 | |
|       foundLastCompositionStart = true;
 | |
|       continue;
 | |
|     }
 | |
|     // Remove the messages before the last 2 sets of composition events.
 | |
|     mReceivedEventMessages.RemoveElementsAt(0, i - 1);
 | |
|     break;
 | |
|   }
 | |
| 
 | |
|   if (!numberOfCompositionCommitRequestHandled) {
 | |
|     // If there is no eCompositionCommitRequestHandled in
 | |
|     // mReceivedEventMessages, we don't need to store log of
 | |
|     // RequestIMEToCommmitComposition().
 | |
|     mRequestIMEToCommitCompositionResults.Clear();
 | |
|   } else {
 | |
|     // We need to keep all reason of eCompositionCommitRequestHandled, which
 | |
|     // is sent when mRequestIMEToCommitComposition() returns true.
 | |
|     // So, we can discard older log than the first
 | |
|     // eCompositionCommitRequestHandled in mReceivedEventMessages.
 | |
|     for (size_t i = mRequestIMEToCommitCompositionResults.Length(); i > 1;
 | |
|          i--) {
 | |
|       if (mRequestIMEToCommitCompositionResults[i - 1] ==
 | |
|               RequestIMEToCommitCompositionResult::
 | |
|                   eReceivedAfterBrowserParentBlur ||
 | |
|           mRequestIMEToCommitCompositionResults[i - 1] ==
 | |
|               RequestIMEToCommitCompositionResult::eHandledSynchronously) {
 | |
|         --numberOfCompositionCommitRequestHandled;
 | |
|         if (!numberOfCompositionCommitRequestHandled) {
 | |
|           mRequestIMEToCommitCompositionResults.RemoveElementsAt(0, i - 1);
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ContentCacheInParent::AppendEventMessageLog(nsACString& aLog) const {
 | |
|   aLog.AppendLiteral("Dispatched Event Message Log:\n");
 | |
|   for (EventMessage message : mDispatchedEventMessages) {
 | |
|     aLog.AppendLiteral("  ");
 | |
|     aLog.Append(ToChar(message));
 | |
|     aLog.AppendLiteral("\n");
 | |
|   }
 | |
|   aLog.AppendLiteral("\nReceived Event Message Log:\n");
 | |
|   for (EventMessage message : mReceivedEventMessages) {
 | |
|     aLog.AppendLiteral("  ");
 | |
|     aLog.Append(ToChar(message));
 | |
|     aLog.AppendLiteral("\n");
 | |
|   }
 | |
|   aLog.AppendLiteral("\nResult of RequestIMEToCommitComposition():\n");
 | |
|   for (RequestIMEToCommitCompositionResult result :
 | |
|        mRequestIMEToCommitCompositionResults) {
 | |
|     aLog.AppendLiteral("  ");
 | |
|     aLog.Append(ToReadableText(result));
 | |
|     aLog.AppendLiteral("\n");
 | |
|   }
 | |
|   aLog.AppendLiteral("\n");
 | |
| }
 | |
| 
 | |
| #endif  // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
| 
 | |
| /*****************************************************************************
 | |
|  * mozilla::ContentCache::Selection
 | |
|  *****************************************************************************/
 | |
| 
 | |
| ContentCache::Selection::Selection(
 | |
|     const WidgetQueryContentEvent& aQuerySelectedTextEvent)
 | |
|     : mAnchor(UINT32_MAX),
 | |
|       mFocus(UINT32_MAX),
 | |
|       mWritingMode(aQuerySelectedTextEvent.mReply->WritingModeRef()),
 | |
|       mHasRange(aQuerySelectedTextEvent.mReply->mOffsetAndData.isSome()) {
 | |
|   MOZ_ASSERT(aQuerySelectedTextEvent.mMessage == eQuerySelectedText);
 | |
|   MOZ_ASSERT(aQuerySelectedTextEvent.Succeeded());
 | |
|   if (mHasRange) {
 | |
|     mAnchor = aQuerySelectedTextEvent.mReply->AnchorOffset();
 | |
|     mFocus = aQuerySelectedTextEvent.mReply->FocusOffset();
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*****************************************************************************
 | |
|  * mozilla::ContentCache::TextRectArray
 | |
|  *****************************************************************************/
 | |
| 
 | |
| LayoutDeviceIntRect ContentCache::TextRectArray::GetRect(
 | |
|     uint32_t aOffset) const {
 | |
|   LayoutDeviceIntRect rect;
 | |
|   if (IsOffsetInRange(aOffset)) {
 | |
|     rect = mRects[aOffset - mStart];
 | |
|   }
 | |
|   return rect;
 | |
| }
 | |
| 
 | |
| LayoutDeviceIntRect ContentCache::TextRectArray::GetUnionRect(
 | |
|     uint32_t aOffset, uint32_t aLength) const {
 | |
|   LayoutDeviceIntRect rect;
 | |
|   if (!IsRangeCompletelyInRange(aOffset, aLength)) {
 | |
|     return rect;
 | |
|   }
 | |
|   for (uint32_t i = 0; i < aLength; i++) {
 | |
|     rect = rect.Union(mRects[aOffset - mStart + i]);
 | |
|   }
 | |
|   return rect;
 | |
| }
 | |
| 
 | |
| LayoutDeviceIntRect ContentCache::TextRectArray::GetUnionRectAsFarAsPossible(
 | |
|     uint32_t aOffset, uint32_t aLength, bool aRoundToExistingOffset) const {
 | |
|   LayoutDeviceIntRect rect;
 | |
|   if (!HasRects() ||
 | |
|       (!aRoundToExistingOffset && !IsOverlappingWith(aOffset, aLength))) {
 | |
|     return rect;
 | |
|   }
 | |
|   uint32_t startOffset = std::max(aOffset, mStart);
 | |
|   if (aRoundToExistingOffset && startOffset >= EndOffset()) {
 | |
|     startOffset = EndOffset() - 1;
 | |
|   }
 | |
|   uint32_t endOffset = std::min(aOffset + aLength, EndOffset());
 | |
|   if (aRoundToExistingOffset && endOffset < mStart + 1) {
 | |
|     endOffset = mStart + 1;
 | |
|   }
 | |
|   if (NS_WARN_IF(endOffset < startOffset)) {
 | |
|     return rect;
 | |
|   }
 | |
|   for (uint32_t i = 0; i < endOffset - startOffset; i++) {
 | |
|     rect = rect.Union(mRects[startOffset - mStart + i]);
 | |
|   }
 | |
|   return rect;
 | |
| }
 | |
| 
 | |
| }  // namespace mozilla
 |