Bug 1278084 part.2 TSFTextStore should allow TSF to lock the document even during destroying r?m_kato draft
authorMasayuki Nakano <masayuki@d-toybox.com>
Wed, 08 Jun 2016 19:12:07 +0900
changeset 376567 5aa76723d276a4f33c663cd80b254d1a291c2bc2
parent 376346 e19e5a438e8d0ef69ccfe99bf748db6c30a2e4c8
child 376568 a57348d0a4df65e6ac8ed977ddb8f0c3b20cfcd9
push id20624
push usermasayuki@d-toybox.com
push dateWed, 08 Jun 2016 10:45:47 +0000
reviewersm_kato
bugs1278084
milestone49.0a1
Bug 1278084 part.2 TSFTextStore should allow TSF to lock the document even during destroying r?m_kato While a TSFTextStore instance is being destroyed, TSFTextStore::Destroy() tries to commit remaining composition and notify TSF of destroying the view. At this moment, TSF/TIP may try to commit the composition or retrieve the contents with calling ITextStoreACP::RequestLock() but currently TSFTextStore disallows the requests to lock of them. This means that TSFTextStore never sends composition commit events asynchronously. Therefore, TextComposition may keep waiting remaining composition events but this causes odd behavior because they won't be fired. For avoiding this issue, TSFTextStore should behave as normal even while it's being destroyed. Fortunately, if there is a composition, it always has mLockedContent and mSelection. So, it can compute expected results of TSF/TIP with them. MozReview-Commit-ID: 2DSCGXXkLx1
widget/windows/TSFTextStore.cpp
widget/windows/TSFTextStore.h
--- a/widget/windows/TSFTextStore.cpp
+++ b/widget/windows/TSFTextStore.cpp
@@ -7,16 +7,17 @@
 #include <algorithm>
 
 #include "mozilla/Logging.h"
 
 #include "nscore.h"
 #include "nsWindow.h"
 #include "nsPrintfCString.h"
 #include "WinUtils.h"
+#include "mozilla/AutoRestore.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/TextEventDispatcher.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/WindowsVersion.h"
 #include "nsIXULRuntime.h"
 
 #define INPUTSCOPE_INIT_GUID
 #define TEXTATTRS_INIT_GUID
@@ -1191,16 +1192,17 @@ TSFTextStore::TSFTextStore()
   , mWaitingQueryLayout(false)
   , mPendingDestroy(false)
   , mDeferClearingLockedContent(false)
   , mNativeCaretIsCreated(false)
   , mDeferNotifyingTSF(false)
   , mDeferCommittingComposition(false)
   , mDeferCancellingComposition(false)
   , mDestroyed(false)
+  , mBeingDestroyed(false)
 {
   for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
     mRequestedAttrs[i] = false;
   }
 
   // We hope that 5 or more actions don't occur at once.
   mPendingActions.SetCapacity(5);
 
@@ -1279,64 +1281,68 @@ TSFTextStore::Init(nsWindowBase* aWidget
   MOZ_LOG(sTextStoreLog, LogLevel::Info,
     ("TSF: 0x%p   TSFTextStore::Init() succeeded: "
      "mDocumentMgr=0x%p, mContext=0x%p, mEditCookie=0x%08X",
      this, mDocumentMgr.get(), mContext.get(), mEditCookie));
 
   return true;
 }
 
-bool
+void
 TSFTextStore::Destroy()
 {
+  if (mBeingDestroyed) {
+    return;
+  }
+
   MOZ_LOG(sTextStoreLog, LogLevel::Info,
     ("TSF: 0x%p TSFTextStore::Destroy(), mLock=%s, "
      "mComposition.IsComposing()=%s, mHandlingKeyMessage=%u",
      this, GetLockFlagNameStr(mLock).get(),
      GetBoolName(mComposition.IsComposing()),
      mHandlingKeyMessage));
 
   mDestroyed = true;
 
+  // Destroy native caret first because it's not directly related to TSF and
+  // there may be another textstore which gets focus.  So, we should avoid
+  // to destroy caret after the new one recreates caret.
+  MaybeDestroyNativeCaret();
+
   if (mLock) {
     mPendingDestroy = true;
-    return true;
-  }
+    return;
+  }
+
+  AutoRestore<bool> savedBeingDestroyed(mBeingDestroyed);
+  mBeingDestroyed = true;
 
   // If there is composition, TSF keeps the composition even after the text
   // store destroyed.  So, we should clear the composition here.
   if (mComposition.IsComposing()) {
-    NS_WARNING("Composition is still alive at destroying the text store");
     CommitCompositionInternal(false);
   }
 
-  MaybeDestroyNativeCaret();
-
   if (mSink) {
     MOZ_LOG(sTextStoreLog, LogLevel::Debug,
       ("TSF: 0x%p   TSFTextStore::Destroy(), calling "
        "ITextStoreACPSink::OnLayoutChange(TS_LC_DESTROY)...",
        this));
     mSink->OnLayoutChange(TS_LC_DESTROY, TEXTSTORE_DEFAULT_VIEW);
   }
 
-  mLockedContent.Clear();
-  mSelection.MarkDirty();
-
   // If this is called during handling a keydown or keyup message, we should
   // put off to release TSF objects until it completely finishes since
   // MS-IME for Japanese refers some objects without grabbing them.
   if (!mHandlingKeyMessage) {
     ReleaseTSFObjects();
   }
 
   MOZ_LOG(sTextStoreLog, LogLevel::Info,
     ("TSF: 0x%p   TSFTextStore::Destroy() succeeded", this));
-
-  return true;
 }
 
 void
 TSFTextStore::ReleaseTSFObjects()
 {
   MOZ_ASSERT(!mHandlingKeyMessage);
 
   MOZ_LOG(sTextStoreLog, LogLevel::Info,
@@ -1483,20 +1489,21 @@ TSFTextStore::RequestLock(DWORD dwLockFl
      phrSession, GetLockFlagNameStr(mLock).get(), GetBoolName(mDestroyed)));
 
   if (!mSink) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
       ("TSF: 0x%p   TSFTextStore::RequestLock() FAILED due to "
        "any sink not stored", this));
     return E_FAIL;
   }
-  if (mDestroyed) {
+  if (mDestroyed &&
+      (!mLockedContent.IsInitialized() || mSelection.IsDirty())) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
       ("TSF: 0x%p   TSFTextStore::RequestLock() FAILED due to "
-       "being destroyed", this));
+       "being destroyed and no information of the contents", this));
     return E_FAIL;
   }
   if (!phrSession) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
       ("TSF: 0x%p   TSFTextStore::RequestLock() FAILED due to "
        "null phrSession", this));
     return E_INVALIDARG;
   }
@@ -1574,17 +1581,17 @@ TSFTextStore::DidLockGranted()
     // OnUpdateComposition() without new range.  Therefore, let's record the
     // composition update information here.
     CompleteLastActionIfStillIncomplete();
 
     FlushPendingActions();
   }
 
   // If the widget has gone, we don't need to notify anything.
-  if (!mWidget || mWidget->Destroyed()) {
+  if (mDestroyed || !mWidget || mWidget->Destroyed()) {
     mPendingOnSelectionChange = false;
     mHasReturnedNoLayoutError = false;
   }
 }
 
 void
 TSFTextStore::DispatchEvent(WidgetGUIEvent& aEvent)
 {
@@ -1598,18 +1605,20 @@ TSFTextStore::DispatchEvent(WidgetGUIEve
   }
   mWidget->DispatchWindowEvent(&aEvent);
 }
 
 void
 TSFTextStore::FlushPendingActions()
 {
   if (!mWidget || mWidget->Destroyed()) {
+    // Note that don't clear the locked contents because TIP may try to commit
+    // composition with a document lock.  In such case, TSFTextStore needs to
+    // behave as expected by TIP.
     mPendingActions.Clear();
-    mLockedContent.Clear();
     mPendingOnSelectionChange = false;
     mHasReturnedNoLayoutError = false;
     return;
   }
 
   RefPtr<nsWindowBase> kungFuDeathGrip(mWidget);
   nsresult rv = mDispatcher->BeginNativeInputTransaction();
   if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -1620,18 +1629,27 @@ TSFTextStore::FlushPendingActions()
   }
   for (uint32_t i = 0; i < mPendingActions.Length(); i++) {
     PendingAction& action = mPendingActions[i];
     switch (action.mType) {
       case PendingAction::COMPOSITION_START: {
         MOZ_LOG(sTextStoreLog, LogLevel::Debug,
                ("TSF: 0x%p   TSFTextStore::FlushPendingActions() "
                 "flushing COMPOSITION_START={ mSelectionStart=%d, "
-                "mSelectionLength=%d }",
-                this, action.mSelectionStart, action.mSelectionLength));
+                "mSelectionLength=%d }, mDestroyed=%s",
+                this, action.mSelectionStart, action.mSelectionLength,
+                GetBoolName(mDestroyed)));
+
+        if (mDestroyed) {
+          MOZ_LOG(sTextStoreLog, LogLevel::Warning,
+            ("TSF: 0x%p   TSFTextStore::FlushPendingActions() "
+             "IGNORED pending compositionstart due to already destroyed",
+             this));
+          break;
+        }
 
         if (action.mAdjustSelection) {
           // Select composition range so the new composition replaces the range
           WidgetSelectionEvent selectionSet(true, eSetSelection, mWidget);
           mWidget->InitEvent(selectionSet);
           selectionSet.mOffset = static_cast<uint32_t>(action.mSelectionStart);
           selectionSet.mLength = static_cast<uint32_t>(action.mSelectionLength);
           selectionSet.mReversed = false;
@@ -1729,19 +1747,29 @@ TSFTextStore::FlushPendingActions()
           mDeferClearingLockedContent = false;
         }
         break;
       }
       case PendingAction::SET_SELECTION: {
         MOZ_LOG(sTextStoreLog, LogLevel::Debug,
                ("TSF: 0x%p   TSFTextStore::FlushPendingActions() "
                 "flushing SET_SELECTION={ mSelectionStart=%d, "
-                "mSelectionLength=%d, mSelectionReversed=%s }",
+                "mSelectionLength=%d, mSelectionReversed=%s }, "
+                "mDestroyed=%s",
                 this, action.mSelectionStart, action.mSelectionLength,
-                GetBoolName(action.mSelectionReversed)));
+                GetBoolName(action.mSelectionReversed),
+                GetBoolName(mDestroyed)));
+
+        if (mDestroyed) {
+          MOZ_LOG(sTextStoreLog, LogLevel::Warning,
+            ("TSF: 0x%p   TSFTextStore::FlushPendingActions() "
+             "IGNORED pending selectionset due to already destroyed",
+             this));
+          break;
+        }
 
         WidgetSelectionEvent selectionSet(true, eSetSelection, mWidget);
         selectionSet.mOffset = 
           static_cast<uint32_t>(action.mSelectionStart);
         selectionSet.mLength =
           static_cast<uint32_t>(action.mSelectionLength);
         selectionSet.mReversed = action.mSelectionReversed;
         break;
@@ -1795,16 +1823,25 @@ TSFTextStore::MaybeFlushPendingNotificat
     return;
   }
 
   if (mPendingDestroy) {
     Destroy();
     return;
   }
 
+  if (mDestroyed) {
+    // If it's already been destroyed completely, this shouldn't notify TSF of
+    // anything anymore.
+    MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+      ("TSF: 0x%p   TSFTextStore::MaybeFlushPendingNotifications(), "
+       "does nothing because this has already destroyed completely...", this));
+    return;
+  }
+
   if (!mDeferClearingLockedContent && mLockedContent.IsInitialized()) {
     mLockedContent.Clear();
     MOZ_LOG(sTextStoreLog, LogLevel::Debug,
            ("TSF: 0x%p   TSFTextStore::MaybeFlushPendingNotifications(), "
             "mLockedContent is cleared", this));
   }
 
   if (mHasReturnedNoLayoutError) {
@@ -2001,16 +2038,17 @@ TSFTextStore::LockedContent()
 bool
 TSFTextStore::GetCurrentText(nsAString& aTextContent)
 {
   if (mLockedContent.IsInitialized()) {
     aTextContent = mLockedContent.Text();
     return true;
   }
 
+  MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(mWidget && !mWidget->Destroyed());
 
   MOZ_LOG(sTextStoreLog, LogLevel::Debug,
          ("TSF: 0x%p   TSFTextStore::GetCurrentText(): "
           "retrieving text from the content...", this));
 
   WidgetQueryContentEvent queryText(true, eQueryTextContent, mWidget);
   queryText.InitForQueryTextContent(0, UINT32_MAX);
@@ -2027,16 +2065,17 @@ TSFTextStore::GetCurrentText(nsAString& 
   aTextContent = queryText.mReply.mString;
   return true;
 }
 
 TSFTextStore::Selection&
 TSFTextStore::CurrentSelection()
 {
   if (mSelection.IsDirty()) {
+    MOZ_ASSERT(!mDestroyed);
     // If the window has never been available, we should crash since working
     // with broken values may make TIP confused.
     if (!mWidget || mWidget->Destroyed()) {
       MOZ_CRASH();
     }
 
     WidgetQueryContentEvent querySelection(true, eQuerySelectedText, mWidget);
     mWidget->InitEvent(querySelection);
@@ -3281,17 +3320,17 @@ TSFTextStore::GetACPFromPoint(TsViewCook
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::GetACPFromPoint() FAILED due to "
             "null pacp", this));
     return E_INVALIDARG;
   }
 
   mWaitingQueryLayout = false;
 
-  if (mLockedContent.IsLayoutChanged()) {
+  if (mDestroyed || mLockedContent.IsLayoutChanged()) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::GetACPFromPoint() returned "
             "TS_E_NOLAYOUT", this));
     mHasReturnedNoLayoutError = true;
     return TS_E_NOLAYOUT;
   }
 
   LayoutDeviceIntPoint ourPt(pt->x, pt->y);
@@ -3528,16 +3567,25 @@ TSFTextStore::GetTextExt(TsViewCookie vc
   if (mLockedContent.IsLayoutChangedAfter(acpEnd)) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::GetTextExt() returned TS_E_NOLAYOUT "
             "(acpEnd=%d)", this, acpEnd));
     mHasReturnedNoLayoutError = true;
     return TS_E_NOLAYOUT;
   }
 
+  if (mDestroyed) {
+    MOZ_LOG(sTextStoreLog, LogLevel::Error,
+           ("TSF: 0x%p   TSFTextStore::GetTextExt() returned TS_E_NOLAYOUT "
+            "(acpEnd=%d) because this has already been destroyed",
+            this, acpEnd));
+    mHasReturnedNoLayoutError = true;
+    return TS_E_NOLAYOUT;
+  }
+
   // use eQueryTextRect to get rect in system, screen coordinates
   WidgetQueryContentEvent event(true, eQueryTextRect, mWidget);
   mWidget->InitEvent(event);
   event.InitForQueryTextRect(acpStart, acpEnd - acpStart);
   DispatchEvent(event);
   if (!event.mSucceeded) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::GetTextExt() FAILED due to "
@@ -3619,16 +3667,24 @@ TSFTextStore::GetScreenExt(TsViewCookie 
 
   if (!prc) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::GetScreenExt() FAILED due to "
             "null argument", this));
     return E_INVALIDARG;
   }
 
+  if (mDestroyed) {
+    MOZ_LOG(sTextStoreLog, LogLevel::Error,
+           ("TSF: 0x%p   TSFTextStore::GetScreenExt() returns empty rect "
+            "due to already destroyed", this));
+    prc->left = prc->top = prc->right = prc->left = 0;
+    return S_OK;
+  }
+
   if (!GetScreenExtInternal(*prc)) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::GetScreenExt() FAILED due to "
             "GetScreenExtInternal() failure", this));
     return E_FAIL;
   }
 
   MOZ_LOG(sTextStoreLog, LogLevel::Info,
@@ -3639,16 +3695,18 @@ TSFTextStore::GetScreenExt(TsViewCookie 
 }
 
 bool
 TSFTextStore::GetScreenExtInternal(RECT& aScreenExt)
 {
   MOZ_LOG(sTextStoreLog, LogLevel::Debug,
          ("TSF: 0x%p   TSFTextStore::GetScreenExtInternal()", this));
 
+  MOZ_ASSERT(!mDestroyed);
+
   // use NS_QUERY_EDITOR_RECT to get rect in system, screen coordinates
   WidgetQueryContentEvent event(true, eQueryEditorRect, mWidget);
   mWidget->InitEvent(event);
   DispatchEvent(event);
   if (!event.mSucceeded) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::GetScreenExtInternal() FAILED due to "
             "eQueryEditorRect failure", this));
@@ -3713,17 +3771,17 @@ TSFTextStore::GetWnd(TsViewCookie vcView
 
   if (!phwnd) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::GetScreenExt() FAILED due to "
             "null argument", this));
     return E_INVALIDARG;
   }
 
-  *phwnd = mWidget->GetWindowHandle();
+  *phwnd = mWidget ? mWidget->GetWindowHandle() : nullptr;
 
   MOZ_LOG(sTextStoreLog, LogLevel::Info,
          ("TSF: 0x%p   TSFTextStore::GetWnd() succeeded: *phwnd=0x%p",
           this, static_cast<void*>(*phwnd)));
   return S_OK;
 }
 
 STDMETHODIMP
@@ -4498,28 +4556,36 @@ TSFTextStore::OnTextChangeInternal(const
 
   MOZ_LOG(sTextStoreLog, LogLevel::Debug,
          ("TSF: 0x%p   TSFTextStore::OnTextChangeInternal(aIMENotification={ "
           "mMessage=0x%08X, mTextChangeData={ mStartOffset=%lu, "
           "mRemovedEndOffset=%lu, mAddedEndOffset=%lu, "
           "mCausedOnlyByComposition=%s, "
           "mIncludingChangesDuringComposition=%s, "
           "mIncludingChangesWithoutComposition=%s }), "
-          "mSink=0x%p, mSinkMask=%s, mComposition.IsComposing()=%s",
+          "mDestroyed=%s, mSink=0x%p, mSinkMask=%s, "
+          "mComposition.IsComposing()=%s",
           this, aIMENotification.mMessage,
           textChangeData.mStartOffset,
           textChangeData.mRemovedEndOffset,
           textChangeData.mAddedEndOffset,
           GetBoolName(textChangeData.mCausedOnlyByComposition),
           GetBoolName(textChangeData.mIncludingChangesDuringComposition),
           GetBoolName(textChangeData.mIncludingChangesWithoutComposition),
+          GetBoolName(mDestroyed),
           mSink.get(),
           GetSinkMaskNameStr(mSinkMask).get(),
           GetBoolName(mComposition.IsComposing())));
 
+  if (mDestroyed) {
+    // If this instance is already destroyed, we shouldn't notify TSF of any
+    // changes.
+    return NS_OK;
+  }
+
   if (textChangeData.mCausedOnlyByComposition) {
     // Ignore text change notifications caused only by composition since it's
     // already been handled internally.
     return NS_OK;
   }
 
   if (mComposition.IsComposing() &&
       !textChangeData.mIncludingChangesDuringComposition) {
@@ -4564,16 +4630,18 @@ TSFTextStore::OnTextChangeInternal(const
   MaybeFlushPendingNotifications();
 
   return NS_OK;
 }
 
 void
 TSFTextStore::NotifyTSFOfTextChange(const TS_TEXTCHANGE& aTextChange)
 {
+  MOZ_ASSERT(!mDestroyed);
+
   // XXX We need to cache the text change ranges and notify TSF of that
   //     the document is unlocked.
   if (NS_WARN_IF(IsReadLocked())) {
     return;
   }
 
   // Some TIPs are confused by text change notification during composition.
   // Especially, some of them stop working for composition in our process.
@@ -4600,29 +4668,36 @@ TSFTextStore::OnSelectionChangeInternal(
 {
   const IMENotification::SelectionChangeDataBase& selectionChangeData =
     aIMENotification.mSelectionChangeData;
   MOZ_LOG(sTextStoreLog, LogLevel::Debug,
          ("TSF: 0x%p   TSFTextStore::OnSelectionChangeInternal("
           "aIMENotification={ mSelectionChangeData={ mOffset=%lu, "
           "Length()=%lu, mReversed=%s, mWritingMode=%s, "
           "mCausedByComposition=%s, mCausedBySelectionEvent=%s, "
-          "mOccurredDuringComposition=%s } }), "
+          "mOccurredDuringComposition=%s } }), mDestroyed=%s, "
           "mSink=0x%p, mSinkMask=%s, mIsRecordingActionsWithoutLock=%s, "
           "mComposition.IsComposing()=%s",
           this, selectionChangeData.mOffset, selectionChangeData.Length(),
           GetBoolName(selectionChangeData.mReversed),
           GetWritingModeName(selectionChangeData.GetWritingMode()).get(),
           GetBoolName(selectionChangeData.mCausedByComposition),
           GetBoolName(selectionChangeData.mCausedBySelectionEvent),
           GetBoolName(selectionChangeData.mOccurredDuringComposition),
+          GetBoolName(mDestroyed),
           mSink.get(), GetSinkMaskNameStr(mSinkMask).get(),
           GetBoolName(mIsRecordingActionsWithoutLock),
           GetBoolName(mComposition.IsComposing())));
 
+  if (mDestroyed) {
+    // If this instance is already destroyed, we shouldn't notify TSF of any
+    // changes.
+    return NS_OK;
+  }
+
   if (selectionChangeData.mCausedByComposition) {
     // Ignore selection change notifications caused by composition since it's
     // already been handled internally.
     return NS_OK;
   }
 
   mDeferNotifyingTSF = false;
 
@@ -4679,16 +4754,18 @@ TSFTextStore::OnSelectionChangeInternal(
   MaybeFlushPendingNotifications();
 
   return NS_OK;
 }
 
 void
 TSFTextStore::NotifyTSFOfSelectionChange()
 {
+  MOZ_ASSERT(!mDestroyed);
+
   if (NS_WARN_IF(IsReadLocked())) {
     return;
   }
 
   mPendingOnSelectionChange = false;
 
   if (!mSink || !(mSinkMask & TS_AS_SEL_CHANGE)) {
     return;
@@ -4710,16 +4787,22 @@ TSFTextStore::NotifyTSFOfSelectionChange
          ("TSF: 0x%p   TSFTextStore::NotifyTSFOfSelectionChange(), calling "
           "ITextStoreACPSink::OnSelectionChange()...", this));
   mSink->OnSelectionChange();
 }
 
 nsresult
 TSFTextStore::OnLayoutChangeInternal()
 {
+  if (mDestroyed) {
+    // If this instance is already destroyed, we shouldn't notify TSF of any
+    // changes.
+    return NS_OK;
+  }
+
   NS_ENSURE_TRUE(mContext, NS_ERROR_FAILURE);
   NS_ENSURE_TRUE(mSink, NS_ERROR_FAILURE);
 
   mDeferNotifyingTSF = false;
 
   nsresult rv = NS_OK;
 
   // We need to notify TSF of layout change even if the document is locked.
@@ -4738,16 +4821,18 @@ TSFTextStore::OnLayoutChangeInternal()
   MaybeFlushPendingNotifications();
 
   return rv;
 }
 
 bool
 TSFTextStore::NotifyTSFOfLayoutChange()
 {
+  MOZ_ASSERT(!mDestroyed);
+
   // If we're waiting a query of layout information from TIP, it means that
   // we've returned TS_E_NOLAYOUT error.
   bool returnedNoLayoutError =
     mHasReturnedNoLayoutError || mWaitingQueryLayout;
 
   // If we returned TS_E_NOLAYOUT, TIP should query the computed layout again.
   mWaitingQueryLayout = returnedNoLayoutError;
 
@@ -4843,16 +4928,22 @@ TSFTextStore::NotifyTSFOfLayoutChange()
                 reinterpret_cast<WPARAM>(this), 0);
 
   return true;
 }
 
 void
 TSFTextStore::NotifyTSFOfLayoutChangeAgain()
 {
+  // Don't notify TSF of layout change after destroyed.
+  if (mDestroyed) {
+    mWaitingQueryLayout = false;
+    return;
+  }
+
   // Before preforming this method, TIP has accessed our layout information by
   // itself.  In such case, we don't need to call OnLayoutChange() anymore.
   if (!mWaitingQueryLayout) {
     return;
   }
 
   MOZ_LOG(sTextStoreLog, LogLevel::Info,
          ("TSF: 0x%p   TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
@@ -4877,31 +4968,42 @@ TSFTextStore::NotifyTSFOfLayoutChangeAga
   }
 }
 
 nsresult
 TSFTextStore::OnUpdateCompositionInternal()
 {
   MOZ_LOG(sTextStoreLog, LogLevel::Debug,
     ("TSF: 0x%p   TSFTextStore::OnUpdateCompositionInternal(), "
-     "mDeferNotifyingTSF=%s",
-     this, GetBoolName(mDeferNotifyingTSF)));
+     "mDestroyed=%s, mDeferNotifyingTSF=%s",
+     this, GetBoolName(mDestroyed), GetBoolName(mDeferNotifyingTSF)));
+
+  // There are nothing to do after destroyed.
+  if (mDestroyed) {
+    return NS_OK;
+  }
 
   // Now, all sent composition events are handled by the content even in
   // e10s mode.
   mDeferClearingLockedContent = false;
   mDeferNotifyingTSF = false;
   MaybeFlushPendingNotifications();
   return NS_OK;
 }
 
 nsresult
 TSFTextStore::OnMouseButtonEventInternal(
                 const IMENotification& aIMENotification)
 {
+  if (mDestroyed) {
+    // If this instance is already destroyed, we shouldn't notify TSF of any
+    // events.
+    return NS_OK;
+  }
+
   if (mMouseTrackers.IsEmpty()) {
     return NS_OK;
   }
 
   MOZ_LOG(sTextStoreLog, LogLevel::Debug,
          ("TSF: 0x%p   TSFTextStore::OnMouseButtonEventInternal("
           "aIMENotification={ mEventMessage=%s, mOffset=%u, mCursorPos={ "
           "mX=%d, mY=%d }, mCharRect={ mX=%d, mY=%d, mWidth=%d, mHeight=%d }, "
@@ -4967,16 +5069,21 @@ TSFTextStore::OnMouseButtonEventInternal
   return NS_OK;
 }
 
 void
 TSFTextStore::CreateNativeCaret()
 {
   MaybeDestroyNativeCaret();
 
+  // Don't create native caret after destroyed.
+  if (mDestroyed) {
+    return;
+  }
+
   MOZ_LOG(sTextStoreLog, LogLevel::Debug,
          ("TSF: 0x%p   TSFTextStore::CreateNativeCaret(), "
           "mComposition.IsComposing()=%s",
           this, GetBoolName(mComposition.IsComposing())));
 
   Selection& currentSel = CurrentSelection();
   if (currentSel.IsDirty()) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
@@ -5076,17 +5183,18 @@ TSFTextStore::CommitCompositionInternal(
        "putting off to request to %s composition after unlocking the document",
        this, aDiscard ? "cancel" : "commit"));
     return;
   }
 
   if (mComposition.IsComposing() && aDiscard) {
     LONG endOffset = mComposition.EndOffset();
     mComposition.mString.Truncate(0);
-    if (mSink) {
+    // Note that don't notify TSF of text change after this is destroyed.
+    if (mSink && !mDestroyed) {
       TS_TEXTCHANGE textChange;
       textChange.acpStart = mComposition.mStart;
       textChange.acpOldEnd = endOffset;
       textChange.acpNewEnd = mComposition.mStart;
       MOZ_LOG(sTextStoreLog, LogLevel::Info,
              ("TSF: 0x%p   TSFTextStore::CommitCompositionInternal(), calling"
               "mSink->OnTextChange(0, { acpStart=%ld, acpOldEnd=%ld, "
               "acpNewEnd=%ld })...", this, textChange.acpStart,
--- a/widget/windows/TSFTextStore.h
+++ b/widget/windows/TSFTextStore.h
@@ -242,17 +242,17 @@ protected:
   ~TSFTextStore();
 
   static bool CreateAndSetFocus(nsWindowBase* aFocusedWidget,
                                 const InputContext& aContext);
   static void MarkContextAsKeyboardDisabled(ITfContext* aContext);
   static void MarkContextAsEmpty(ITfContext* aContext);
 
   bool     Init(nsWindowBase* aWidget);
-  bool     Destroy();
+  void     Destroy();
   void     ReleaseTSFObjects();
 
   bool     IsReadLock(DWORD aLock) const
   {
     return (TS_LF_READ == (aLock & TS_LF_READ));
   }
   bool     IsReadWriteLock(DWORD aLock) const
   {
@@ -899,16 +899,19 @@ protected:
   // TSF needs another document lock for modifying the composition, selection
   // and etc.  So, committing composition should be performed after the
   // document is unlocked.
   bool                         mDeferCommittingComposition;
   bool                         mDeferCancellingComposition;
   // Immediately after a call of Destroy(), mDestroyed becomes true.  If this
   // is true, the instance shouldn't grant any requests from the TIP anymore.
   bool                         mDestroyed;
+  // While the instance is being destroyed, this is set to true for avoiding
+  // recursive Destroy() calls.
+  bool                         mBeingDestroyed;
 
 
   // TSF thread manager object for the current application
   static StaticRefPtr<ITfThreadMgr> sThreadMgr;
   // sMessagePump is QI'ed from sThreadMgr
   static StaticRefPtr<ITfMessagePump> sMessagePump;
   // sKeystrokeMgr is QI'ed from sThreadMgr
   static StaticRefPtr<ITfKeystrokeMgr> sKeystrokeMgr;