Bug 1259692 - Make TSFTextStore dispatch eKeyDown or eKeyUp event when TIP processes a WM_KEYDOWN or WM_KEYUP message r?m_kato draft
authorMasayuki Nakano <masayuki@d-toybox.com>
Wed, 28 Feb 2018 21:53:23 +0900
changeset 763573 acd01d5135d615274a9f8fe49e7513dbead2b76a
parent 763572 730d4022dcdb19bffdc58bd4a8d804ac39431e04
push id101474
push usermasayuki@d-toybox.com
push dateTue, 06 Mar 2018 05:48:04 +0000
reviewersm_kato
bugs1259692
milestone60.0a1
Bug 1259692 - Make TSFTextStore dispatch eKeyDown or eKeyUp event when TIP processes a WM_KEYDOWN or WM_KEYUP message r?m_kato TSF doesn't send WM_KEYDOWN nor WM_KEYUP to us while it handles a key message with ITfKeystrokeMgr::KeyDown() or ITfKeystrokeMgr::KeyUp(). Therefore, TSFTextStore needs to store handling key event message during calling those methods and if it does something, we need to dispatch eKeyDown event or eKeyUp event before dispatching any events. However, we shouldn't dispatch WidgetKeyboardEvent during a document lock because TSF/TIP do not assume that document is broken during a document lock. Therefore, TSFTextStore needs to put it as a pending action into the queue. So, this patch wraps this with TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(). It checks if there is a document lock when it's called. If it's locked (and not yet dispatched keyboard event for the handling key message), it adds pending action to dispatch keyboard event later. Otherwise, (and not yet dispatched one), it dispatches keyboard event directly. MozReview-Commit-ID: 9rJTJykVLyf
widget/windows/TSFTextStore.cpp
widget/windows/TSFTextStore.h
--- a/widget/windows/TSFTextStore.cpp
+++ b/widget/windows/TSFTextStore.cpp
@@ -7,16 +7,17 @@
 #define TEXTATTRS_INIT_GUID
 #include "TSFTextStore.h"
 
 #include <olectl.h>
 #include <algorithm>
 #include "nscore.h"
 
 #include "IMMHandler.h"
+#include "KeyboardLayout.h"
 #include "WinIMEHandler.h"
 #include "WinUtils.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/Logging.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/TextEventDispatcher.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/WindowsVersion.h"
@@ -1751,17 +1752,19 @@ StaticRefPtr<ITfMessagePump> TSFTextStor
 StaticRefPtr<ITfKeystrokeMgr> TSFTextStore::sKeystrokeMgr;
 StaticRefPtr<ITfDisplayAttributeMgr> TSFTextStore::sDisplayAttrMgr;
 StaticRefPtr<ITfCategoryMgr> TSFTextStore::sCategoryMgr;
 StaticRefPtr<ITfCompartment> TSFTextStore::sCompartmentForOpenClose;
 StaticRefPtr<ITfDocumentMgr> TSFTextStore::sDisabledDocumentMgr;
 StaticRefPtr<ITfContext> TSFTextStore::sDisabledContext;
 StaticRefPtr<ITfInputProcessorProfiles> TSFTextStore::sInputProcessorProfiles;
 StaticRefPtr<TSFTextStore> TSFTextStore::sEnabledTextStore;
+const MSG* TSFTextStore::sHandlingKeyMsg = nullptr;
 DWORD TSFTextStore::sClientId  = 0;
+bool TSFTextStore::sIsKeyboardEventDispatched = false;
 
 #define TEXTSTORE_DEFAULT_VIEW (1)
 
 TSFTextStore::TSFTextStore()
   : mEditCookie(0)
   , mSinkMask(0)
   , mLock(0)
   , mLockQueued(0)
@@ -2249,20 +2252,34 @@ TSFTextStore::FlushPendingActions()
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
       ("0x%p   TSFTextStore::FlushPendingActions() "
        "FAILED due to BeginNativeInputTransaction() failure", this));
     return;
   }
   for (uint32_t i = 0; i < mPendingActions.Length(); i++) {
     PendingAction& action = mPendingActions[i];
     switch (action.mType) {
-      case PendingAction::COMPOSITION_START: {
+      case PendingAction::Type::eKeyboardEvent:
+        if (mDestroyed) {
+          MOZ_LOG(sTextStoreLog, LogLevel::Warning,
+            ("0x%p   TSFTextStore::FlushPendingActions() "
+             "IGNORED pending KeyboardEvent(%s) due to already destroyed",
+             action.mKeyMsg->message == WM_KEYDOWN ? "eKeyDown" : "eKeyUp",
+             this));
+        }
+        MOZ_DIAGNOSTIC_ASSERT(action.mKeyMsg);
+        DispatchKeyboardEventAsProcessedByIME(*action.mKeyMsg);
+        if (!widget || widget->Destroyed()) {
+          break;
+        }
+        break;
+      case PendingAction::Type::eCompositionStart: {
         MOZ_LOG(sTextStoreLog, LogLevel::Debug,
           ("0x%p   TSFTextStore::FlushPendingActions() "
-           "flushing COMPOSITION_START={ mSelectionStart=%d, "
+           "flushing Type::eCompositionStart={ mSelectionStart=%d, "
            "mSelectionLength=%d }, mDestroyed=%s",
            this, action.mSelectionStart, action.mSelectionLength,
            GetBoolName(mDestroyed)));
 
         if (mDestroyed) {
           MOZ_LOG(sTextStoreLog, LogLevel::Warning,
             ("0x%p   TSFTextStore::FlushPendingActions() "
              "IGNORED pending compositionstart due to already destroyed",
@@ -2307,20 +2324,20 @@ TSFTextStore::FlushPendingActions()
           //     shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
           mDeferClearingContentForTSF = !IsHandlingComposition();
         }
         if (!widget || widget->Destroyed()) {
           break;
         }
         break;
       }
-      case PendingAction::COMPOSITION_UPDATE: {
+      case PendingAction::Type::eCompositionUpdate: {
         MOZ_LOG(sTextStoreLog, LogLevel::Debug,
           ("0x%p   TSFTextStore::FlushPendingActions() "
-           "flushing COMPOSITION_UPDATE={ mData=\"%s\", "
+           "flushing Type::eCompositionUpdate={ mData=\"%s\", "
            "mRanges=0x%p, mRanges->Length()=%d }",
            this, GetEscapedUTF8String(action.mData).get(),
            action.mRanges.get(),
            action.mRanges ? action.mRanges->Length() : 0));
 
         // eCompositionChange causes a DOM text event, the IME will be notified
         // of NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED.  In this case, we
         // should not clear mContentForTSF until we notify the IME of the
@@ -2354,20 +2371,20 @@ TSFTextStore::FlushPendingActions()
             // XXX Is this right? If there is a composition in content,
             //     shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
             mDeferClearingContentForTSF = !IsHandlingComposition();
           }
           // Be aware, the mWidget might already have been destroyed.
         }
         break;
       }
-      case PendingAction::COMPOSITION_END: {
+      case PendingAction::Type::eCompositionEnd: {
         MOZ_LOG(sTextStoreLog, LogLevel::Debug,
           ("0x%p   TSFTextStore::FlushPendingActions() "
-           "flushing COMPOSITION_END={ mData=\"%s\" }",
+           "flushing Type::eCompositionEnd={ mData=\"%s\" }",
            this, GetEscapedUTF8String(action.mData).get()));
 
         // Dispatching eCompositionCommit causes a DOM text event, then,
         // the IME will be notified of NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED
         // when focused content actually handles the event.  For example,
         // when focused content is in a remote process, it's sent when
         // all dispatched composition events have been handled in the remote
         // process.  So, until then, we don't have newer content information.
@@ -2387,20 +2404,20 @@ TSFTextStore::FlushPendingActions()
              "IsHandlingComposition()=%s",
              this, GetBoolName(IsHandlingComposition())));
           // XXX Is this right? If there is a composition in content,
           //     shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
           mDeferClearingContentForTSF = !IsHandlingComposition();
         }
         break;
       }
-      case PendingAction::SET_SELECTION: {
+      case PendingAction::Type::eSetSelection: {
         MOZ_LOG(sTextStoreLog, LogLevel::Debug,
           ("0x%p   TSFTextStore::FlushPendingActions() "
-           "flushing SET_SELECTION={ mSelectionStart=%d, "
+           "flushing Type::eSetSelection={ mSelectionStart=%d, "
            "mSelectionLength=%d, mSelectionReversed=%s }, "
            "mDestroyed=%s",
            this, action.mSelectionStart, action.mSelectionLength,
            GetBoolName(action.mSelectionReversed),
            GetBoolName(mDestroyed)));
 
         if (mDestroyed) {
           MOZ_LOG(sTextStoreLog, LogLevel::Warning,
@@ -2517,16 +2534,88 @@ TSFTextStore::MaybeFlushPendingNotificat
   if (mHasReturnedNoLayoutError) {
     MOZ_LOG(sTextStoreLog, LogLevel::Info,
       ("0x%p   TSFTextStore::MaybeFlushPendingNotifications(), "
        "calling TSFTextStore::NotifyTSFOfLayoutChange()...", this));
     NotifyTSFOfLayoutChange();
   }
 }
 
+void
+TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME()
+{
+  // If we've already been destroyed, we cannot do anything.
+  if (mDestroyed) {
+    MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+      ("0x%p   TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
+       "does nothing because it's already been destroyed", this));
+    return;
+  }
+
+  // If we're not handling key message or we've already dispatched a keyboard
+  // event for the handling key message, we should do nothing anymore.
+  if (!sHandlingKeyMsg || sIsKeyboardEventDispatched) {
+    MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+      ("0x%p   TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
+       "does nothing because not necessary to dispatch keyboard event", this));
+    return;
+  }
+
+  sIsKeyboardEventDispatched = true;
+  // If the document is locked, just adding the task to dispatching an event
+  // to the queue.
+  if (IsReadLocked()) {
+    MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+      ("0x%p   TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
+       "adding to dispatch a keyboard event into the queue...", this));
+    PendingAction* action = mPendingActions.AppendElement();
+    action->mType = PendingAction::Type::eKeyboardEvent;
+    action->mKeyMsg = sHandlingKeyMsg;
+    return;
+  }
+
+  // Otherwise, dispatch a keyboard event.
+  MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+    ("0x%p   TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
+     "trying to dispatch a keyboard event...", this));
+  DispatchKeyboardEventAsProcessedByIME(*sHandlingKeyMsg);
+}
+
+void
+TSFTextStore::DispatchKeyboardEventAsProcessedByIME(const MSG& aMsg)
+{
+  MOZ_ASSERT(mWidget);
+  MOZ_ASSERT(!mWidget->Destroyed());
+  MOZ_ASSERT(!mDestroyed);
+
+  ModifierKeyState modKeyState;
+  MSG msg(aMsg);
+  msg.wParam = VK_PROCESSKEY;
+  NativeKey nativeKey(mWidget, msg, modKeyState);
+  switch (aMsg.message) {
+    case WM_KEYDOWN:
+      MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+        ("0x%p   TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
+         "dispatching an eKeyDown event...", this));
+      nativeKey.HandleKeyDownMessage();
+      break;
+    case WM_KEYUP:
+      MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+        ("0x%p   TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
+         "dispatching an eKeyUp event...", this));
+      nativeKey.HandleKeyUpMessage();
+      break;
+    default:
+      MOZ_LOG(sTextStoreLog, LogLevel::Error,
+        ("0x%p   TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
+         "ERROR, it doesn't handle the message", this));
+      break;
+  }
+}
+
 STDMETHODIMP
 TSFTextStore::GetStatus(TS_STATUS* pdcs)
 {
   MOZ_LOG(sTextStoreLog, LogLevel::Info,
     ("0x%p TSFTextStore::GetStatus(pdcs=0x%p)", this, pdcs));
 
   if (!pdcs) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
@@ -3370,16 +3459,24 @@ TSFTextStore::SetSelectionInternal(const
   Selection& selectionForTSF = SelectionForTSFRef();
   if (selectionForTSF.IsDirty()) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
       ("0x%p   TSFTextStore::SetSelectionInternal() FAILED due to "
        "SelectionForTSFRef() failure", this));
     return E_FAIL;
   }
 
+  MaybeDispatchKeyboardEventAsProcessedByIME();
+  if (mDestroyed) {
+    MOZ_LOG(sTextStoreLog, LogLevel::Error,
+      ("0x%p   TSFTextStore::SetSelectionInternal() FAILED due to "
+       "destroyed during dispatching a keyboard event", this));
+    return E_FAIL;
+  }
+
   // If actually the range is not changing, we should do nothing.
   // Perhaps, we can ignore the difference change because it must not be
   // important for following edit.
   if (selectionForTSF.EqualsExceptDirection(*pSelection)) {
     MOZ_LOG(sTextStoreLog, LogLevel::Warning,
       ("0x%p   TSFTextStore::SetSelectionInternal() Succeeded but "
        "did nothing because the selection range isn't changing", this));
     selectionForTSF.SetSelection(*pSelection);
@@ -3449,17 +3546,17 @@ TSFTextStore::SetSelectionInternal(const
         ("0x%p   TSFTextStore::SetSelectionInternal() FAILED due to "
          "there is unknown content change", this));
       return E_FAIL;
     }
   }
 
   CompleteLastActionIfStillIncomplete();
   PendingAction* action = mPendingActions.AppendElement();
-  action->mType = PendingAction::SET_SELECTION;
+  action->mType = PendingAction::Type::eSetSelection;
   action->mSelectionStart = selectionInContent.acpStart;
   action->mSelectionLength =
     selectionInContent.acpEnd - selectionInContent.acpStart;
   action->mSelectionReversed = (selectionInContent.style.ase == TS_AE_START);
 
   // Use TSF specified selection for updating mSelectionForTSF.
   selectionForTSF.SetSelection(*pSelection);
 
@@ -4891,28 +4988,36 @@ TSFTextStore::InsertTextAtSelectionInter
   Content& contentForTSF = ContentForTSFRef();
   if (!contentForTSF.IsInitialized()) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
       ("0x%p   TSFTextStore::InsertTextAtSelectionInternal() failed "
        "due to ContentForTSFRef() failure()", this));
     return false;
   }
 
+  MaybeDispatchKeyboardEventAsProcessedByIME();
+  if (mDestroyed) {
+    MOZ_LOG(sTextStoreLog, LogLevel::Error,
+      ("0x%p   TSFTextStore::InsertTextAtSelectionInternal() FAILED due to "
+       "destroyed during dispatching a keyboard event", this));
+    return false;
+  }
+
   TS_SELECTION_ACP oldSelection = contentForTSF.Selection().ACP();
   if (!mComposition.IsComposing()) {
     // Use a temporary composition to contain the text
     PendingAction* compositionStart = mPendingActions.AppendElement();
-    compositionStart->mType = PendingAction::COMPOSITION_START;
+    compositionStart->mType = PendingAction::Type::eCompositionStart;
     compositionStart->mSelectionStart = oldSelection.acpStart;
     compositionStart->mSelectionLength =
       oldSelection.acpEnd - oldSelection.acpStart;
     compositionStart->mAdjustSelection = false;
 
     PendingAction* compositionEnd = mPendingActions.AppendElement();
-    compositionEnd->mType = PendingAction::COMPOSITION_END;
+    compositionEnd->mType = PendingAction::Type::eCompositionEnd;
     compositionEnd->mData = aInsertStr;
 
     MOZ_LOG(sTextStoreLog, LogLevel::Debug,
       ("0x%p   TSFTextStore::InsertTextAtSelectionInternal() "
        "appending pending compositionstart and compositionend... "
        "PendingCompositionStart={ mSelectionStart=%d, "
        "mSelectionLength=%d }, PendingCompositionEnd={ mData=\"%s\" "
        "(Length()=%u) }",
@@ -4998,16 +5103,24 @@ TSFTextStore::RecordCompositionStartActi
   Content& contentForTSF = ContentForTSFRef();
   if (!contentForTSF.IsInitialized()) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
       ("0x%p   TSFTextStore::RecordCompositionStartAction() FAILED "
        "due to ContentForTSFRef() failure", this));
     return E_FAIL;
   }
 
+  MaybeDispatchKeyboardEventAsProcessedByIME();
+  if (mDestroyed) {
+    MOZ_LOG(sTextStoreLog, LogLevel::Error,
+      ("0x%p   TSFTextStore::RecordCompositionStartAction() FAILED due to "
+       "destroyed during dispatching a keyboard event", this));
+    return false;
+  }
+
   CompleteLastActionIfStillIncomplete();
 
   // TIP may have inserted text at selection before calling
   // OnStartComposition().  In this case, we've already created a pair of
   // pending compositionstart and pending compositionend.  If the pending
   // compositionstart occurred same range as this composition, it was the
   // start of this composition.  In such case, we should cancel the pending
   // compositionend and start composition normally.
@@ -5028,17 +5141,17 @@ TSFTextStore::RecordCompositionStartActi
        this, mComposition.mStart, mComposition.mString.Length(),
        mSelectionForTSF.StartOffset(), mSelectionForTSF.EndOffset(),
        GetActiveSelEndName(mSelectionForTSF.ActiveSelEnd()),
        GetBoolName(mSelectionForTSF.IsInterimChar())));
     return S_OK;
   }
 
   PendingAction* action = mPendingActions.AppendElement();
-  action->mType = PendingAction::COMPOSITION_START;
+  action->mType = PendingAction::Type::eCompositionStart;
   action->mSelectionStart = aStart;
   action->mSelectionLength = aLength;
 
   Selection& selectionForTSF = SelectionForTSFRef();
   if (selectionForTSF.IsDirty()) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
       ("0x%p   TSFTextStore::RecordCompositionStartAction() FAILED "
        "due to SelectionForTSFRef() failure", this));
@@ -5077,43 +5190,51 @@ TSFTextStore::RecordCompositionEndAction
   MOZ_LOG(sTextStoreLog, LogLevel::Debug,
     ("0x%p   TSFTextStore::RecordCompositionEndAction(), "
      "mComposition={ mView=0x%p, mString=\"%s\" }",
      this, mComposition.mView.get(),
      GetEscapedUTF8String(mComposition.mString).get()));
 
   MOZ_ASSERT(mComposition.IsComposing());
 
+  MaybeDispatchKeyboardEventAsProcessedByIME();
+  if (mDestroyed) {
+    MOZ_LOG(sTextStoreLog, LogLevel::Error,
+      ("0x%p   TSFTextStore::RecordCompositionEndAction() FAILED due to "
+       "destroyed during dispatching a keyboard event", this));
+    return false;
+  }
+
   CompleteLastActionIfStillIncomplete();
   PendingAction* action = mPendingActions.AppendElement();
-  action->mType = PendingAction::COMPOSITION_END;
+  action->mType = PendingAction::Type::eCompositionEnd;
   action->mData = mComposition.mString;
 
   Content& contentForTSF = ContentForTSFRef();
   if (!contentForTSF.IsInitialized()) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
       ("0x%p   TSFTextStore::RecordCompositionEndAction() FAILED due "
        "to ContentForTSFRef() failure", this));
     return E_FAIL;
   }
   contentForTSF.EndComposition(*action);
 
   // If this composition was restart but the composition doesn't modify
   // anything, we should remove the pending composition for preventing to
   // dispatch redundant composition events.
   for (size_t i = mPendingActions.Length(), j = 1; i > 0; --i, ++j) {
     PendingAction& pendingAction = mPendingActions[i - 1];
-    if (pendingAction.mType == PendingAction::COMPOSITION_START) {
+    if (pendingAction.mType == PendingAction::Type::eCompositionStart) {
       if (pendingAction.mData != action->mData) {
         break;
       }
       // When only setting selection is necessary, we should append it.
       if (pendingAction.mAdjustSelection) {
         PendingAction* setSelection = mPendingActions.AppendElement();
-        setSelection->mType = PendingAction::SET_SELECTION;
+        setSelection->mType = PendingAction::Type::eSetSelection;
         setSelection->mSelectionStart = pendingAction.mSelectionStart;
         setSelection->mSelectionLength = pendingAction.mSelectionLength;
         setSelection->mSelectionReversed = false;
       }
       // Remove the redundant pending composition.
       mPendingActions.RemoveElementsAt(i - 1, j);
       MOZ_LOG(sTextStoreLog, LogLevel::Info,
         ("0x%p   TSFTextStore::RecordCompositionEndAction(), "
@@ -6718,48 +6839,66 @@ TSFTextStore::ProcessRawKeyMessage(const
         ("TSFTextStore::ProcessRawKeyMessage() FAILED to "
          "QI keystroke manager from the thread manager, hr=0x%08X", hr));
       return false;
     }
     sKeystrokeMgr = keystrokeMgr.forget();
   }
 
   if (aMsg.message == WM_KEYDOWN) {
+    RefPtr<TSFTextStore> textStore(sEnabledTextStore);
+    if (textStore) {
+      textStore->OnStartToHandleKeyMessage();
+      if (NS_WARN_IF(textStore != sEnabledTextStore)) {
+        // Let's handle the key message with new focused TSFTextStore.
+        textStore = sEnabledTextStore;
+      }
+    }
+    AutoRestore<const MSG*> savePreviousKeyMsg(sHandlingKeyMsg);
+    AutoRestore<bool> saveKeyEventDispatched(sIsKeyboardEventDispatched);
+    sHandlingKeyMsg = &aMsg;
+    sIsKeyboardEventDispatched = false;
     BOOL eaten;
     RefPtr<ITfKeystrokeMgr> keystrokeMgr = sKeystrokeMgr;
     HRESULT hr = keystrokeMgr->TestKeyDown(aMsg.wParam, aMsg.lParam, &eaten);
     if (FAILED(hr) || !sKeystrokeMgr || !eaten) {
       return false;
     }
+    hr = keystrokeMgr->KeyDown(aMsg.wParam, aMsg.lParam, &eaten);
+    if (textStore) {
+      textStore->OnEndHandlingKeyMessage(!!eaten);
+    }
+    return SUCCEEDED(hr) &&
+           (eaten || !sKeystrokeMgr || sIsKeyboardEventDispatched);
+  }
+  if (aMsg.message == WM_KEYUP) {
     RefPtr<TSFTextStore> textStore(sEnabledTextStore);
     if (textStore) {
       textStore->OnStartToHandleKeyMessage();
+      if (NS_WARN_IF(textStore != sEnabledTextStore)) {
+        // Let's handle the key message with new focused TSFTextStore.
+        textStore = sEnabledTextStore;
+      }
     }
-    hr = keystrokeMgr->KeyDown(aMsg.wParam, aMsg.lParam, &eaten);
-    if (textStore) {
-      textStore->OnEndHandlingKeyMessage();
-    }
-    return SUCCEEDED(hr) && (eaten || !sKeystrokeMgr);
-  }
-  if (aMsg.message == WM_KEYUP) {
+    AutoRestore<const MSG*> savePreviousKeyMsg(sHandlingKeyMsg);
+    AutoRestore<bool> saveKeyEventDispatched(sIsKeyboardEventDispatched);
+    sHandlingKeyMsg = &aMsg;
+    sIsKeyboardEventDispatched = false;
     BOOL eaten;
     RefPtr<ITfKeystrokeMgr> keystrokeMgr = sKeystrokeMgr;
     HRESULT hr = keystrokeMgr->TestKeyUp(aMsg.wParam, aMsg.lParam, &eaten);
     if (FAILED(hr) || !sKeystrokeMgr || !eaten) {
       return false;
     }
-    RefPtr<TSFTextStore> textStore(sEnabledTextStore);
-    if (textStore) {
-      textStore->OnStartToHandleKeyMessage();
-    }
     hr = keystrokeMgr->KeyUp(aMsg.wParam, aMsg.lParam, &eaten);
     if (textStore) {
-      textStore->OnEndHandlingKeyMessage();
+      textStore->OnEndHandlingKeyMessage(!!eaten);
     }
-    return SUCCEEDED(hr) && (eaten || !sKeystrokeMgr);
+    return SUCCEEDED(hr) &&
+           (eaten || !sKeystrokeMgr || sIsKeyboardEventDispatched);
   }
   return false;
 }
 
 // static
 void
 TSFTextStore::ProcessMessage(nsWindowBase* aWindow,
                              UINT aMessage,
@@ -6949,17 +7088,17 @@ TSFTextStore::Content::ReplaceTextWith(L
 void
 TSFTextStore::Content::StartComposition(ITfCompositionView* aCompositionView,
                                         const PendingAction& aCompStart,
                                         bool aPreserveSelection)
 {
   MOZ_ASSERT(mInitialized);
   MOZ_ASSERT(aCompositionView);
   MOZ_ASSERT(!mComposition.mView);
-  MOZ_ASSERT(aCompStart.mType == PendingAction::COMPOSITION_START);
+  MOZ_ASSERT(aCompStart.mType == PendingAction::Type::eCompositionStart);
 
   mComposition.Start(aCompositionView, aCompStart.mSelectionStart,
     GetSubstring(static_cast<uint32_t>(aCompStart.mSelectionStart),
                  static_cast<uint32_t>(aCompStart.mSelectionLength)));
   mLatestCompositionStartOffset = mComposition.mStart;
   mLatestCompositionEndOffset = mComposition.EndOffset();
   if (!aPreserveSelection) {
     // XXX Do we need to set a new writing-mode here when setting a new
@@ -6974,19 +7113,19 @@ TSFTextStore::Content::RestoreCommittedC
                          ITfCompositionView* aCompositionView,
                          const PendingAction& aPendingCompositionStart,
                          const PendingAction& aCanceledCompositionEnd)
 {
   MOZ_ASSERT(mInitialized);
   MOZ_ASSERT(aCompositionView);
   MOZ_ASSERT(!mComposition.mView);
   MOZ_ASSERT(aPendingCompositionStart.mType ==
-               PendingAction::COMPOSITION_START);
+               PendingAction::Type::eCompositionStart);
   MOZ_ASSERT(aCanceledCompositionEnd.mType ==
-               PendingAction::COMPOSITION_END);
+               PendingAction::Type::eCompositionEnd);
   MOZ_ASSERT(GetSubstring(
                static_cast<uint32_t>(aPendingCompositionStart.mSelectionStart),
                static_cast<uint32_t>(aCanceledCompositionEnd.mData.Length())) ==
                aCanceledCompositionEnd.mData);
 
   // Restore the committed string as composing string.
   mComposition.Start(aCompositionView,
                      aPendingCompositionStart.mSelectionStart,
@@ -6995,17 +7134,17 @@ TSFTextStore::Content::RestoreCommittedC
   mLatestCompositionEndOffset = mComposition.EndOffset();
 }
 
 void
 TSFTextStore::Content::EndComposition(const PendingAction& aCompEnd)
 {
   MOZ_ASSERT(mInitialized);
   MOZ_ASSERT(mComposition.mView);
-  MOZ_ASSERT(aCompEnd.mType == PendingAction::COMPOSITION_END);
+  MOZ_ASSERT(aCompEnd.mType == PendingAction::Type::eCompositionEnd);
 
   mSelection.CollapseAt(mComposition.mStart + aCompEnd.mData.Length());
   mComposition.End();
 }
 
 /******************************************************************************
  *  TSFTextStore::MouseTracker
  *****************************************************************************/
--- a/widget/windows/TSFTextStore.h
+++ b/widget/windows/TSFTextStore.h
@@ -394,30 +394,67 @@ protected:
   // TS_AS_* mask of what events to notify
   DWORD                        mSinkMask;
   // 0 if not locked, otherwise TS_LF_* indicating the current lock
   DWORD                        mLock;
   // 0 if no lock is queued, otherwise TS_LF_* indicating the queue lock
   DWORD                        mLockQueued;
 
   uint32_t mHandlingKeyMessage;
-  void OnStartToHandleKeyMessage() { ++mHandlingKeyMessage; }
-  void OnEndHandlingKeyMessage()
+  void OnStartToHandleKeyMessage()
   {
+    // If we're starting to handle another key message during handling a
+    // key message, let's assume that the handling key message is handled by
+    // TIP and it sends another key message for hacking something.
+    // Let's try to dispatch a keyboard event now.
+    // FYI: All callers of this method grab this instance with local variable.
+    //      So, even after calling MaybeDispatchKeyboardEventAsProcessedByIME(),
+    //      we're safe to access any members.
+    if (!mDestroyed && sHandlingKeyMsg && !sIsKeyboardEventDispatched) {
+      MaybeDispatchKeyboardEventAsProcessedByIME();
+    }
+    ++mHandlingKeyMessage;
+  }
+  void OnEndHandlingKeyMessage(bool aIsProcessedByTSF)
+  {
+    // If sHandlingKeyMsg has been handled by TSF or TIP and we're still
+    // alive, but we haven't dispatch keyboard event for it, let's fire it now.
+    // FYI: All callers of this method grab this instance with local variable.
+    //      So, even after calling MaybeDispatchKeyboardEventAsProcessedByIME(),
+    //      we're safe to access any members.
+    if (!mDestroyed && sHandlingKeyMsg &&
+        aIsProcessedByTSF && !sIsKeyboardEventDispatched) {
+      MaybeDispatchKeyboardEventAsProcessedByIME();
+    }
     MOZ_ASSERT(mHandlingKeyMessage);
     if (--mHandlingKeyMessage) {
       return;
     }
     // If TSFTextStore instance is destroyed during handling key message(s),
     // release all TSF objects when all nested key messages have been handled.
     if (mDestroyed) {
       ReleaseTSFObjects();
     }
   }
 
+  /**
+   * MaybeDispatchKeyboardEventAsProcessedByIME() tries to dispatch eKeyDown
+   * event or eKeyUp event for sHandlingKeyMsg and marking the dispatching
+   * event as "processed by IME".  Note that if the document is locked, this
+   * just adds a pending action into the queue and sets
+   * sIsKeyboardEventDispatched to true.
+   */
+  void MaybeDispatchKeyboardEventAsProcessedByIME();
+
+  /**
+   * DispatchKeyboardEventAsProcessedByIME() dispatches an eKeyDown or
+   * eKeyUp event with NativeKey class and aMsg.
+   */
+  void DispatchKeyboardEventAsProcessedByIME(const MSG& aMsg);
+
   class Composition final
   {
   public:
     // nullptr if no composition is active, otherwise the current composition
     RefPtr<ITfCompositionView> mView;
 
     // Current copy of the active composition string. Only mString is
     // changed during a InsertTextAtSelection call if we have a composition.
@@ -649,92 +686,96 @@ protected:
 
  private:
     Selection& mSelection;
     bool mDirty;
   };
 
   struct PendingAction final
   {
-    enum ActionType : uint8_t
+    enum class Type : uint8_t
     {
-      COMPOSITION_START,
-      COMPOSITION_UPDATE,
-      COMPOSITION_END,
-      SET_SELECTION
+      eCompositionStart,
+      eCompositionUpdate,
+      eCompositionEnd,
+      eSetSelection,
+      eKeyboardEvent,
     };
-    ActionType mType;
-    // For compositionstart and selectionset
+    Type mType;
+    // For eCompositionStart and eSetSelection
     LONG mSelectionStart;
     LONG mSelectionLength;
-    // For compositionstart, compositionupdate and compositionend
+    // For eCompositionStart, eCompositionUpdate and eCompositionEnd
     nsString mData;
-    // For compositionupdate
+    // For eCompositionUpdate
     RefPtr<TextRangeArray> mRanges;
-    // For selectionset
+    // For eKeyboardEvent
+    const MSG* mKeyMsg;
+    // For eSetSelection
     bool mSelectionReversed;
-    // For compositionupdate
+    // For eCompositionUpdate
     bool mIncomplete;
-    // For compositionstart
+    // For eCompositionStart
     bool mAdjustSelection;
   };
   // Items of mPendingActions are appended when TSF tells us to need to dispatch
   // DOM composition events.  However, we cannot dispatch while the document is
   // locked because it can cause modifying the locked document.  So, the pending
   // actions should be performed when document lock is unlocked.
   nsTArray<PendingAction> mPendingActions;
 
   PendingAction* LastOrNewPendingCompositionUpdate()
   {
     if (!mPendingActions.IsEmpty()) {
       PendingAction& lastAction = mPendingActions.LastElement();
-      if (lastAction.mType == PendingAction::COMPOSITION_UPDATE) {
+      if (lastAction.mType == PendingAction::Type::eCompositionUpdate) {
         return &lastAction;
       }
     }
     PendingAction* newAction = mPendingActions.AppendElement();
-    newAction->mType = PendingAction::COMPOSITION_UPDATE;
+    newAction->mType = PendingAction::Type::eCompositionUpdate;
     newAction->mRanges = new TextRangeArray();
     newAction->mIncomplete = true;
     return newAction;
   }
 
   /**
    * WasTextInsertedWithoutCompositionAt() checks if text was inserted without
    * composition immediately before (e.g., see InsertTextAtSelectionInternal()).
    *
    * @param aStart              The inserted offset you expected.
    * @param aLength             The inserted text length you expected.
    * @return                    true if the last pending actions are
-   *                            COMPOSITION_START and COMPOSITION_END and
+   *                            eCompositionStart and eCompositionEnd and
    *                            aStart and aLength match their information.
    */
   bool WasTextInsertedWithoutCompositionAt(LONG aStart, LONG aLength) const
   {
     if (mPendingActions.Length() < 2) {
       return false;
     }
     const PendingAction& pendingLastAction = mPendingActions.LastElement();
-    if (pendingLastAction.mType != PendingAction::COMPOSITION_END ||
+    if (pendingLastAction.mType != PendingAction::Type::eCompositionEnd ||
         pendingLastAction.mData.Length() != ULONG(aLength)) {
       return false;
     }
     const PendingAction& pendingPreLastAction =
       mPendingActions[mPendingActions.Length() - 2];
-    return pendingPreLastAction.mType == PendingAction::COMPOSITION_START &&
+    return pendingPreLastAction.mType ==
+             PendingAction::Type::eCompositionStart &&
            pendingPreLastAction.mSelectionStart == aStart;
   }
 
   bool IsPendingCompositionUpdateIncomplete() const
   {
     if (mPendingActions.IsEmpty()) {
       return false;
     }
     const PendingAction& lastAction = mPendingActions.LastElement();
-    return lastAction.mType == PendingAction::COMPOSITION_UPDATE &&
+    return lastAction.mType == PendingAction::Type::eCompositionUpdate &&
            lastAction.mIncomplete;
   }
 
   void CompleteLastActionIfStillIncomplete()
   {
     if (!IsPendingCompositionUpdateIncomplete()) {
       return;
     }
@@ -1107,16 +1148,23 @@ private:
   // For IME (keyboard) disabled state:
   static StaticRefPtr<ITfDocumentMgr> sDisabledDocumentMgr;
   static StaticRefPtr<ITfContext> sDisabledContext;
 
   static StaticRefPtr<ITfInputProcessorProfiles> sInputProcessorProfiles;
   static already_AddRefed<ITfInputProcessorProfiles>
            GetInputProcessorProfiles();
 
+  // Handling key message.
+  static const MSG* sHandlingKeyMsg;
+
   // TSF client ID for the current application
   static DWORD sClientId;
+
+  // true if an eKeyDown or eKeyUp event for sHandlingKeyMsg has already
+  // been dispatched.
+  static bool sIsKeyboardEventDispatched;
 };
 
 } // namespace widget
 } // namespace mozilla
 
 #endif // #ifndef TSFTextStore_h_