Bug 1343451 - part 4: Make TextInputHandler dispatches eKeyDown event with marking it as "processed by IME" r?m_kato draft
authorMasayuki Nakano <masayuki@d-toybox.com>
Fri, 23 Feb 2018 23:09:43 +0900
changeset 762367 a7b388528b45d436892767c9667672f6b055200b
parent 762366 ca08aa39309aa58a429d3a19000630a5f0e78cac
child 762368 59e1833f8bb4782573fbebb800338cae37b49ef7
push id101161
push usermasayuki@d-toybox.com
push dateFri, 02 Mar 2018 09:53:57 +0000
reviewersm_kato
bugs1343451
milestone60.0a1
Bug 1343451 - part 4: Make TextInputHandler dispatches eKeyDown event with marking it as "processed by IME" r?m_kato First of all, TextInputHandler::HandleKeyDown() dispatches an eKeyDown event before sending IME. This is different behavior from Gecko on the other platforms and it means TextInputHandler does not know if the eKeyDown event will be handled by IME. Therefore, we need to make TextInputHandler dispatch an eKeyDown event dispatch when it needs to dispatch another event. Therefore, this patch makes TextInputHandler not dispatch an eKeyDown even from its HandleKeyDown() unless it's already has composition (because if it's already had composition, any eKeyDown event except modifier keys should be marked as "processed by IME" for compatibility with the other browsers). For dispatching eKeyDown event only once for a native keydown event, this patch implements TextInputHandlerBase::MaybeDispatchCurrentKeydownEvent() to check whether eKeyDown event has already been dispatched for the event and if it's not so, dispatches eKeyDown event. Note that on macOS, dead keys are implemented as IME. However, we need to treat dead keys as is. Therefore, if current keydown event is a dead keydown event, MaybeDispatchCurrentKeydownEvent() should NOT mark dispatching eKeyDown event and its following eKeyDown event as "processed by IME". MozReview-Commit-ID: 7epk8wdAznd
widget/cocoa/TextInputHandler.h
widget/cocoa/TextInputHandler.mm
--- a/widget/cocoa/TextInputHandler.h
+++ b/widget/cocoa/TextInputHandler.h
@@ -238,24 +238,28 @@ public:
 
   /**
    * InitKeyEvent() initializes aKeyEvent for aNativeKeyEvent.
    *
    * @param aNativeKeyEvent       A native key event for which you want to
    *                              dispatch a Gecko key event.
    * @param aKeyEvent             The result -- a Gecko key event initialized
    *                              from the native key event.
+   * @param aIsProcessedByIME     true if aNativeKeyEvent has been handled
+   *                              by IME (but except if the composition was
+   *                              started with dead key).
    * @param aInsertString         If caller expects that the event will cause
    *                              a character to be input (say in an editor),
    *                              the caller should set this.  Otherwise,
    *                              if caller sets null to this, this method will
    *                              compute the character to be input from
    *                              characters of aNativeKeyEvent.
    */
   void InitKeyEvent(NSEvent *aNativeKeyEvent, WidgetKeyboardEvent& aKeyEvent,
+                    bool aIsProcessedByIME,
                     const nsAString *aInsertString = nullptr);
 
   /**
    * WillDispatchKeyboardEvent() computes aKeyEvent.mAlternativeCharCodes and
    * recompute aKeyEvent.mCharCode if it's necessary.
    *
    * @param aNativeKeyEvent       A native key event for which you want to
    *                              dispatch a Gecko key event.
@@ -298,16 +302,25 @@ public:
    *
    * @param aNativeKeyCode        A native keycode.
    * @param aKbType               A native Keyboard Type value.  Typically,
    *                              this is a result of ::LMGetKbdType().
    */
   static CodeNameIndex ComputeGeckoCodeNameIndex(UInt32 aNativeKeyCode,
                                                  UInt32 aKbType);
 
+  /**
+   * TranslateToChar() checks if aNativeKeyEvent is a dead key.
+   *
+   * @param aNativeKeyEvent       A native key event.
+   * @return                      Returns true if the key event is a dead key
+   *                              event.  Otherwise, false.
+   */
+  bool IsDeadKey(NSEvent* aNativeKeyEvent);
+
 protected:
   /**
    * TranslateToString() computes the inputted text from the native keyCode,
    * modifier flags and keyboard type.
    *
    * @param aKeyCode              A native keyCode.
    * @param aModifiers            Combination of native modifier flags.
    * @param aKbType               A native Keyboard Type value.  Typically,
@@ -338,17 +351,17 @@ protected:
   /**
    * TranslateToChar() checks if aKeyCode with aModifiers is a dead key.
    *
    * @param aKeyCode              A native keyCode.
    * @param aModifiers            Combination of native modifier flags.
    * @param aKbType               A native Keyboard Type value.  Typically,
    *                              this is a result of ::LMGetKbdType().
    * @return                      Returns true if the key with specified
-   *                              modifier state isa dead key.  Otherwise,
+   *                              modifier state is a dead key.  Otherwise,
    *                              false.
    */
   bool IsDeadKey(UInt32 aKeyCode, UInt32 aModifiers, UInt32 aKbType);
 
   /**
    * ComputeInsertString() computes string to be inserted with the key event.
    *
    * @param aNativeKeyEvent     The native key event which causes our keyboard
@@ -428,24 +441,28 @@ public:
 
   /**
    * InitKeyEvent() initializes aKeyEvent for aNativeKeyEvent.
    *
    * @param aNativeKeyEvent       A native key event for which you want to
    *                              dispatch a Gecko key event.
    * @param aKeyEvent             The result -- a Gecko key event initialized
    *                              from the native key event.
+   * @param aIsProcessedByIME     true if aNativeKeyEvent has been handled
+   *                              by IME (but except if the composition was
+   *                              started with dead key).
    * @param aInsertString         If caller expects that the event will cause
    *                              a character to be input (say in an editor),
    *                              the caller should set this.  Otherwise,
    *                              if caller sets null to this, this method will
    *                              compute the character to be input from
    *                              characters of aNativeKeyEvent.
    */
   void InitKeyEvent(NSEvent *aNativeKeyEvent, WidgetKeyboardEvent& aKeyEvent,
+                    bool aIsProcessedByIME,
                     const nsAString *aInsertString = nullptr);
 
   /**
    * SynthesizeNativeKeyEvent() is an implementation of
    * nsIWidget::SynthesizeNativeKeyEvent().  See the document in nsIWidget.h
    * for the detail.
    */
   nsresult SynthesizeNativeKeyEvent(int32_t aNativeKeyboardLayout,
@@ -547,16 +564,18 @@ protected:
     // call of InsertText().
     nsAString* mInsertString;
     // String which are included in [mKeyEvent characters] and already handled
     // by InsertText() call(s).
     nsString mInsertedString;
     // Unique id associated with a keydown / keypress event. It's ok if this
     // wraps over long periods.
     uint32_t mUniqueId;
+    // Whether keydown event was dispatched for mKeyEvent.
+    bool mKeyDownDispatched;
     // Whether keydown event was consumed by web contents or chrome contents.
     bool mKeyDownHandled;
     // Whether keypress event was dispatched for mKeyEvent.
     bool mKeyPressDispatched;
     // Whether keypress event was consumed by web contents or chrome contents.
     bool mKeyPressHandled;
     // Whether the key event causes other key events via IME or something.
     bool mCausedOtherKeyEvents;
@@ -599,29 +618,35 @@ protected:
     {
       if (mKeyEvent) {
         [mKeyEvent release];
         mKeyEvent = nullptr;
         mUniqueId = 0;
       }
       mInsertString = nullptr;
       mInsertedString.Truncate();
+      mKeyDownDispatched = false;
       mKeyDownHandled = false;
       mKeyPressDispatched = false;
       mKeyPressHandled = false;
       mCausedOtherKeyEvents = false;
       mCompositionDispatched = false;
     }
 
     bool IsDefaultPrevented() const
     {
       return mKeyDownHandled || mKeyPressHandled || mCausedOtherKeyEvents ||
              mCompositionDispatched;
     }
 
+    bool CanDispatchKeyDownEvent() const
+    {
+      return !mKeyDownDispatched;
+    }
+
     bool CanDispatchKeyPressEvent() const
     {
       return !mKeyPressDispatched && !IsDefaultPrevented();
     }
 
     bool CanHandleCommand() const
     {
       return !mKeyDownHandled && !mKeyPressHandled;
@@ -760,17 +785,18 @@ protected:
                  (modifiers == MODIFIER_ALT ||
                   modifiers == (MODIFIER_ALT | MODIFIER_SHIFT));
         default:
           return false;
       }
     }
 
     void InitKeyEvent(TextInputHandlerBase* aHandler,
-                      WidgetKeyboardEvent& aKeyEvent);
+                      WidgetKeyboardEvent& aKeyEvent,
+                      bool aIsProcessedByIME);
 
     /**
      * GetUnhandledString() returns characters of the event which have not been
      * handled with InsertText() yet. For example, if there is a composition
      * caused by a dead key press like '`' and it's committed by some key
      * combinations like |Cmd+v|, then, the |v|'s KeyDown event's |characters|
      * is |`v|.  Then, after |`| is committed with a call of InsertString(),
      * this returns only 'v'.
@@ -1054,16 +1080,17 @@ public:
    * @return                      Always empty array for now.
    */
   NSArray* GetValidAttributesForMarkedText();
 
   bool HasMarkedText();
   NSRange MarkedRange();
 
   bool IsIMEComposing() { return mIsIMEComposing; }
+  bool IsDeadKeyComposing() { return mIsDeadKeyComposing; }
   bool IsIMEOpened();
   bool IsIMEEnabled() { return mIsIMEEnabled; }
   bool IsASCIICapableOnly() { return mIsASCIICapableOnly; }
   bool IgnoreIMECommit() { return mIgnoreIMECommit; }
 
   void CommitIMEComposition();
   void CancelIMEComposition();
 
@@ -1107,29 +1134,45 @@ protected:
    *
    * @param aAttrString           A string which is committed.
    * @param aReplacementRange     The range which will be replaced with the
    *                              aAttrString instead of current selection.
    */
   void InsertTextAsCommittingComposition(NSAttributedString* aAttrString,
                                          NSRange* aReplacementRange);
 
+  /**
+   * MaybeDispatchCurrentKeydownEvent() dispatches eKeyDown event for current
+   * key event.  If eKeyDown for current key event has already been dispatched,
+   * this does nothing.
+   *
+   * @param aIsProcessedByIME   true if current key event is handled by IME.
+   * @return                    true if the caller can continue to handle
+   *                            current key event.  Otherwise, false.  E.g.,
+   *                            focus is moved, the widget has been destroyed
+   *                            or something.
+   */
+  bool MaybeDispatchCurrentKeydownEvent(bool aIsProcessedByIME);
+
 private:
   // If mIsIMEComposing is true, the composition string is stored here.
   NSString* mIMECompositionString;
   // If mIsIMEComposing is true, the start offset of the composition string.
   uint32_t mIMECompositionStart;
 
   NSRange mMarkedRange;
   NSRange mSelectedRange;
 
   NSRange mRangeForWritingMode; // range within which mWritingMode applies
   mozilla::WritingMode mWritingMode;
 
   bool mIsIMEComposing;
+  // If the composition started with dead key, mIsDeadKeyComposing is set to
+  // true.
+  bool mIsDeadKeyComposing;
   bool mIsIMEEnabled;
   bool mIsASCIICapableOnly;
   bool mIgnoreIMECommit;
   bool mIMEHasFocus;
 
   void KillIMEComposition();
   void SendCommittedText(NSString *aString);
   void OpenSystemPreferredLanguageIME();
--- a/widget/cocoa/TextInputHandler.mm
+++ b/widget/cocoa/TextInputHandler.mm
@@ -395,16 +395,94 @@ TISInputSourceWrapper::TranslateToChar(U
   if (!TranslateToString(aKeyCode, aModifiers, aKbType, str) ||
       str.Length() != 1) {
     return 0;
   }
   return static_cast<uint32_t>(str.CharAt(0));
 }
 
 bool
+TISInputSourceWrapper::IsDeadKey(NSEvent* aNativeKeyEvent)
+{
+  if ([[aNativeKeyEvent characters] length]) {
+    return false;
+  }
+
+  // Assmue that if control key or command key is pressed, it's not a dead key.
+  NSUInteger cocoaState = [aNativeKeyEvent modifierFlags];
+  if (cocoaState & (NSControlKeyMask | NSCommandKeyMask)) {
+    return false;
+  }
+
+  UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
+  switch (nativeKeyCode) {
+    case kVK_ANSI_A:
+    case kVK_ANSI_B:
+    case kVK_ANSI_C:
+    case kVK_ANSI_D:
+    case kVK_ANSI_E:
+    case kVK_ANSI_F:
+    case kVK_ANSI_G:
+    case kVK_ANSI_H:
+    case kVK_ANSI_I:
+    case kVK_ANSI_J:
+    case kVK_ANSI_K:
+    case kVK_ANSI_L:
+    case kVK_ANSI_M:
+    case kVK_ANSI_N:
+    case kVK_ANSI_O:
+    case kVK_ANSI_P:
+    case kVK_ANSI_Q:
+    case kVK_ANSI_R:
+    case kVK_ANSI_S:
+    case kVK_ANSI_T:
+    case kVK_ANSI_U:
+    case kVK_ANSI_V:
+    case kVK_ANSI_W:
+    case kVK_ANSI_X:
+    case kVK_ANSI_Y:
+    case kVK_ANSI_Z:
+    case kVK_ANSI_1:
+    case kVK_ANSI_2:
+    case kVK_ANSI_3:
+    case kVK_ANSI_4:
+    case kVK_ANSI_5:
+    case kVK_ANSI_6:
+    case kVK_ANSI_7:
+    case kVK_ANSI_8:
+    case kVK_ANSI_9:
+    case kVK_ANSI_0:
+    case kVK_ANSI_Equal:
+    case kVK_ANSI_Minus:
+    case kVK_ANSI_RightBracket:
+    case kVK_ANSI_LeftBracket:
+    case kVK_ANSI_Quote:
+    case kVK_ANSI_Semicolon:
+    case kVK_ANSI_Backslash:
+    case kVK_ANSI_Comma:
+    case kVK_ANSI_Slash:
+    case kVK_ANSI_Period:
+    case kVK_ANSI_Grave:
+    case kVK_JIS_Yen:
+    case kVK_JIS_Underscore:
+      break;
+    default:
+      // Let's assume that dead key can be only a printable key in standard
+      // position.
+      return false;
+  }
+
+  // If TranslateToChar() returns non-zero value, that means that
+  // the key may input a character with different dead key state.
+  UInt32 kbType = GetKbdType();
+  UInt32 carbonState = nsCocoaUtils::ConvertToCarbonModifier(cocoaState);
+  return IsDeadKey(nativeKeyCode, carbonState, kbType);
+}
+
+bool
 TISInputSourceWrapper::IsDeadKey(UInt32 aKeyCode,
                                  UInt32 aModifiers,
                                  UInt32 aKbType)
 {
   const UCKeyboardLayout* UCKey = GetUCKeyboardLayout();
 
   MOZ_LOG(gLog, LogLevel::Info,
     ("%p TISInputSourceWrapper::IsDeadKey, aKeyCode=0x%X, "
@@ -901,27 +979,35 @@ TISInputSourceWrapper::ComputeInsertStri
   if (!aResult.IsEmpty() && IsControlChar(aResult[0])) {
     aResult.Truncate();
   }
 }
 
 void
 TISInputSourceWrapper::InitKeyEvent(NSEvent *aNativeKeyEvent,
                                     WidgetKeyboardEvent& aKeyEvent,
+                                    bool aIsProcessedByIME,
                                     const nsAString *aInsertString)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
+  MOZ_ASSERT(!aIsProcessedByIME || aKeyEvent.mMessage != eKeyPress,
+    "eKeyPress event should not be marked as proccessed by IME");
+
   MOZ_LOG(gLog, LogLevel::Info,
     ("%p TISInputSourceWrapper::InitKeyEvent, aNativeKeyEvent=%p, "
-     "aKeyEvent.mMessage=%s, aInsertString=%p, IsOpenedIMEMode()=%s",
-     this, aNativeKeyEvent, GetGeckoKeyEventType(aKeyEvent), aInsertString,
+     "aKeyEvent.mMessage=%s, aProcessedByIME=%s, aInsertString=%p, "
+     "IsOpenedIMEMode()=%s",
+     this, aNativeKeyEvent, GetGeckoKeyEventType(aKeyEvent),
+     TrueOrFalse(aIsProcessedByIME), aInsertString,
      TrueOrFalse(IsOpenedIMEMode())));
 
-  NS_ENSURE_TRUE(aNativeKeyEvent, );
+  if (NS_WARN_IF(!aNativeKeyEvent)) {
+    return;
+  }
 
   nsCocoaUtils::InitInputEvent(aKeyEvent, aNativeKeyEvent);
 
   // This is used only while dispatching the event (which is a synchronous
   // call), so there is no need to retain and release this data.
   aKeyEvent.mNativeKeyEvent = aNativeKeyEvent;
 
   // Fill in fields used for Cocoa NPAPI plugins
@@ -940,18 +1026,28 @@ TISInputSourceWrapper::InitKeyEvent(NSEv
     aKeyEvent.mNativeModifierFlags = [aNativeKeyEvent modifierFlags];
   }
 
   aKeyEvent.mRefPoint = LayoutDeviceIntPoint(0, 0);
 
   UInt32 kbType = GetKbdType();
   UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
 
+  // macOS handles dead key as IME.  If the key is first key press of dead
+  // key, we should use KEY_NAME_INDEX_Dead for first (dead) key event.
+  // So, if aIsProcessedByIME is true, it may be dead key.  Let's check
+  // if current key event is a dead key's keydown event.
+  bool isProcessedByIME =
+    aIsProcessedByIME &&
+    !TISInputSourceWrapper::CurrentInputSource().IsDeadKey(aNativeKeyEvent);
+
   aKeyEvent.mKeyCode =
-    ComputeGeckoKeyCode(nativeKeyCode, kbType, aKeyEvent.IsMeta());
+    isProcessedByIME ?
+      NS_VK_PROCESSKEY :
+      ComputeGeckoKeyCode(nativeKeyCode, kbType, aKeyEvent.IsMeta());
 
   switch (nativeKeyCode) {
     case kVK_Command:
     case kVK_Shift:
     case kVK_Option:
     case kVK_Control:
       aKeyEvent.mLocation = eKeyLocationLeft;
       break;
@@ -994,17 +1090,19 @@ TISInputSourceWrapper::InitKeyEvent(NSEv
     ([aNativeKeyEvent type] == NSKeyDown) ? [aNativeKeyEvent isARepeat] : false;
 
   MOZ_LOG(gLog, LogLevel::Info,
     ("%p TISInputSourceWrapper::InitKeyEvent, "
      "shift=%s, ctrl=%s, alt=%s, meta=%s",
      this, OnOrOff(aKeyEvent.IsShift()), OnOrOff(aKeyEvent.IsControl()),
      OnOrOff(aKeyEvent.IsAlt()), OnOrOff(aKeyEvent.IsMeta())));
 
-  if (IsPrintableKeyEvent(aNativeKeyEvent)) {
+  if (isProcessedByIME) {
+    aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_Process;
+  } else if (IsPrintableKeyEvent(aNativeKeyEvent)) {
     aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
     // If insertText calls this method, let's use the string.
     if (aInsertString && !aInsertString->IsEmpty() &&
         !IsControlChar((*aInsertString)[0])) {
       aKeyEvent.mKeyValue = *aInsertString;
     }
     // If meta key is pressed, the printable key layout may be switched from
     // non-ASCII capable layout to ASCII capable, or from Dvorak to QWERTY.
@@ -1623,69 +1721,37 @@ TextInputHandler::HandleKeyDownEvent(NSE
         MOZ_LOG(gLog, LogLevel::Error,
           ("%p IMEInputHandler::HandleKeyDownEvent, "
            "FAILED, due to BeginNativeInputTransaction() failure "
            "at dispatching keydown for ComplexTextInputPanel", this));
         return false;
       }
 
       WidgetKeyboardEvent imeEvent(true, eKeyDown, widget);
-      currentKeyEvent->InitKeyEvent(this, imeEvent);
+      currentKeyEvent->InitKeyEvent(this, imeEvent, false);
       imeEvent.mPluginTextEventString.Assign(committed);
       nsEventStatus status = nsEventStatus_eIgnore;
       mDispatcher->DispatchKeyboardEvent(eKeyDown, imeEvent, status,
                                          currentKeyEvent);
     }
     return true;
   }
 
-  NSResponder* firstResponder = [[mView window] firstResponder];
-
-  nsresult rv = mDispatcher->BeginNativeInputTransaction();
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-      MOZ_LOG(gLog, LogLevel::Error,
-        ("%p IMEInputHandler::HandleKeyDownEvent, "
-         "FAILED, due to BeginNativeInputTransaction() failure "
-         "at dispatching keydown for ordinal cases", this));
+  RefPtr<TextInputHandler> kungFuDeathGrip(this);
+
+  // When we're already in a composition, we need always to mark the eKeyDown
+  // event as "processed by IME".  So, let's dispatch eKeyDown event here in
+  // such case.
+  if (IsIMEComposing() && !MaybeDispatchCurrentKeydownEvent(true)) {
+    MOZ_LOG(gLog, LogLevel::Info,
+      ("%p IMEInputHandler::HandleKeyDownEvent, eKeyDown caused focus move or "
+       "something and canceling the composition", this));
     return false;
   }
 
-  WidgetKeyboardEvent keydownEvent(true, eKeyDown, widget);
-  currentKeyEvent->InitKeyEvent(this, keydownEvent);
-
-  nsEventStatus status = nsEventStatus_eIgnore;
-  mDispatcher->DispatchKeyboardEvent(eKeyDown, keydownEvent, status,
-                                     currentKeyEvent);
-  currentKeyEvent->mKeyDownHandled =
-    (status == nsEventStatus_eConsumeNoDefault);
-
-  if (Destroyed()) {
-    MOZ_LOG(gLog, LogLevel::Info,
-      ("%p TextInputHandler::HandleKeyDownEvent, "
-       "widget was destroyed by keydown event", this));
-    return currentKeyEvent->IsDefaultPrevented();
-  }
-
-  // The key down event may have shifted the focus, in which
-  // case we should not fire the key press.
-  // XXX This is a special code only on Cocoa widget, why is this needed?
-  if (firstResponder != [[mView window] firstResponder]) {
-    MOZ_LOG(gLog, LogLevel::Info,
-      ("%p TextInputHandler::HandleKeyDownEvent, "
-       "view lost focus by keydown event", this));
-    return currentKeyEvent->IsDefaultPrevented();
-  }
-
-  if (currentKeyEvent->IsDefaultPrevented()) {
-    MOZ_LOG(gLog, LogLevel::Info,
-      ("%p TextInputHandler::HandleKeyDownEvent, "
-       "keydown event's default is prevented", this));
-    return true;
-  }
-
   // Let Cocoa interpret the key events, caching IsIMEComposing first.
   bool wasComposing = IsIMEComposing();
   bool interpretKeyEventsCalled = false;
   // Don't call interpretKeyEvents when a plugin has focus.  If we call it,
   // for example, a character is inputted twice during a composition in e10s
   // mode.
   if (!widget->IsPluginFocused() && (IsIMEEnabled() || IsASCIICapableOnly())) {
     MOZ_LOG(gLog, LogLevel::Info,
@@ -1705,51 +1771,81 @@ TextInputHandler::HandleKeyDownEvent(NSE
     return currentKeyEvent->IsDefaultPrevented();
   }
 
   MOZ_LOG(gLog, LogLevel::Info,
     ("%p TextInputHandler::HandleKeyDownEvent, wasComposing=%s, "
      "IsIMEComposing()=%s",
      this, TrueOrFalse(wasComposing), TrueOrFalse(IsIMEComposing())));
 
+  if (currentKeyEvent->CanDispatchKeyDownEvent()) {
+    // Dispatch eKeyDown event if nobody has dispatched it yet.
+    // NOTE: Although reaching here means that the native keydown event may
+    //       not be handled by IME.  However, we cannot know if it is.
+    //       For example, Japanese IME of Apple shows candidate window for
+    //       typing window.  They, you can switch the sort order with Tab key.
+    //       However, when you choose "Symbol" of the sort order, there may
+    //       be no candiate words.  In this case, IME handles the Tab key
+    //       actually, but we cannot know it because composition string is
+    //       not updated.  So, let's mark eKeyDown event as "processed by IME"
+    //       when there is composition string.  This is same as Chrome.
+    MOZ_LOG(gLog, LogLevel::Info,
+      ("%p TextInputHandler::HandleKeyDownEvent, trying to dispatch eKeyDown "
+       "event since it's not yet dispatched",
+       this));
+    if (!MaybeDispatchCurrentKeydownEvent(IsIMEComposing())) {
+      return true; // treat the eKeydDown event as consumed.
+    }
+    MOZ_LOG(gLog, LogLevel::Info,
+      ("%p TextInputHandler::HandleKeyDownEvent, eKeyDown event has been "
+       "dispatched",
+       this));
+  }
+
   if (currentKeyEvent->CanDispatchKeyPressEvent() &&
       !wasComposing && !IsIMEComposing()) {
-    rv = mDispatcher->BeginNativeInputTransaction();
+    nsresult rv = mDispatcher->BeginNativeInputTransaction();
     if (NS_WARN_IF(NS_FAILED(rv))) {
         MOZ_LOG(gLog, LogLevel::Error,
           ("%p IMEInputHandler::HandleKeyDownEvent, "
            "FAILED, due to BeginNativeInputTransaction() failure "
            "at dispatching keypress", this));
       return false;
     }
 
     WidgetKeyboardEvent keypressEvent(true, eKeyPress, widget);
-    currentKeyEvent->InitKeyEvent(this, keypressEvent);
+    currentKeyEvent->InitKeyEvent(this, keypressEvent, false);
 
     // If we called interpretKeyEvents and this isn't normal character input
     // then IME probably ate the event for some reason. We do not want to
     // send a key press event in that case.
     // TODO:
     // There are some other cases which IME eats the current event.
     // 1. If key events were nested during calling interpretKeyEvents, it means
     //    that IME did something.  Then, we should do nothing.
     // 2. If one or more commands are called like "deleteBackward", we should
     //    dispatch keypress event at that time.  Note that the command may have
     //    been a converted or generated action by IME.  Then, we shouldn't do
     //    our default action for this key.
     if (!(interpretKeyEventsCalled &&
           IsNormalCharInputtingEvent(keypressEvent))) {
+      MOZ_LOG(gLog, LogLevel::Info,
+        ("%p TextInputHandler::HandleKeyDownEvent, trying to dispatch "
+         "eKeyPress event since it's not yet dispatched",
+         this));
+      nsEventStatus status = nsEventStatus_eIgnore;
       currentKeyEvent->mKeyPressDispatched =
         mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status,
                                                  currentKeyEvent);
       currentKeyEvent->mKeyPressHandled =
         (status == nsEventStatus_eConsumeNoDefault);
       currentKeyEvent->mKeyPressDispatched = true;
       MOZ_LOG(gLog, LogLevel::Info,
-        ("%p TextInputHandler::HandleKeyDownEvent, keypress event dispatched",
+        ("%p TextInputHandler::HandleKeyDownEvent, eKeyPress event has been "
+         "dispatched",
          this));
     }
   }
 
   // Note: mWidget might have become null here. Don't count on it from here on.
 
   MOZ_LOG(gLog, LogLevel::Info,
     ("%p TextInputHandler::HandleKeyDownEvent, "
@@ -1793,18 +1889,20 @@ TextInputHandler::HandleKeyUpEvent(NSEve
   nsresult rv = mDispatcher->BeginNativeInputTransaction();
   if (NS_WARN_IF(NS_FAILED(rv))) {
       MOZ_LOG(gLog, LogLevel::Error,
         ("%p IMEInputHandler::HandleKeyUpEvent, "
          "FAILED, due to BeginNativeInputTransaction() failure", this));
     return;
   }
 
+  // Neither Chrome for macOS nor Safari marks "keyup" event as "processed by
+  // IME" even during composition.  So, let's follow this behavior.
   WidgetKeyboardEvent keyupEvent(true, eKeyUp, mWidget);
-  InitKeyEvent(aNativeEvent, keyupEvent);
+  InitKeyEvent(aNativeEvent, keyupEvent, false);
 
   KeyEventState currentKeyEvent(aNativeEvent);
   nsEventStatus status = nsEventStatus_eIgnore;
   mDispatcher->DispatchKeyboardEvent(eKeyUp, keyupEvent, status,
                                      &currentKeyEvent);
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
@@ -2138,33 +2236,35 @@ TextInputHandler::DispatchKeyEventForFla
 
   MOZ_LOG(gLog, LogLevel::Info,
     ("%p TextInputHandler::DispatchKeyEventForFlagsChanged, aNativeEvent=%p, "
      "type=%s, keyCode=%s (0x%X), aDispatchKeyDown=%s, IsIMEComposing()=%s",
      this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
      GetKeyNameForNativeKeyCode([aNativeEvent keyCode]), [aNativeEvent keyCode],
      TrueOrFalse(aDispatchKeyDown), TrueOrFalse(IsIMEComposing())));
 
-  if ([aNativeEvent type] != NSFlagsChanged || IsIMEComposing()) {
+  if ([aNativeEvent type] != NSFlagsChanged) {
     return;
   }
 
   nsresult rv = mDispatcher->BeginNativeInputTransaction();
   if (NS_WARN_IF(NS_FAILED(rv))) {
       MOZ_LOG(gLog, LogLevel::Error,
         ("%p IMEInputHandler::DispatchKeyEventForFlagsChanged, "
          "FAILED, due to BeginNativeInputTransaction() failure", this));
     return;
   }
 
   EventMessage message = aDispatchKeyDown ? eKeyDown : eKeyUp;
 
-  // Fire a key event.
+  // Fire a key event for the modifier key.  Note that even if modifier key
+  // is pressed during composition, we shouldn't mark the keyboard event as
+  // "processed by IME" since neither Chrome for macOS nor Safari does it.
   WidgetKeyboardEvent keyEvent(true, message, mWidget);
-  InitKeyEvent(aNativeEvent, keyEvent);
+  InitKeyEvent(aNativeEvent, keyEvent, false);
 
   // Attach a plugin event, in case keyEvent gets dispatched to a plugin.  Only
   // one field is needed -- the type.  The other fields can be constructed as
   // the need arises.  But Gecko doesn't have anything equivalent to the
   // NPCocoaEventFlagsChanged type, and this needs to be passed accurately to
   // any plugin to which this event is sent.
   NPCocoaEvent cocoaEvent;
   nsCocoaUtils::InitNPCocoaEvent(&cocoaEvent);
@@ -2190,24 +2290,27 @@ TextInputHandler::InsertText(NSAttribute
   }
 
   KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
 
   MOZ_LOG(gLog, LogLevel::Info,
     ("%p TextInputHandler::InsertText, aAttrString=\"%s\", "
      "aReplacementRange=%p { location=%lu, length=%lu }, "
      "IsIMEComposing()=%s, "
-     "keyevent=%p, keydownHandled=%s, keypressDispatched=%s, "
+     "keyevent=%p, keydownDispatched=%s, "
+     "keydownHandled=%s, keypressDispatched=%s, "
      "causedOtherKeyEvents=%s, compositionDispatched=%s",
      this, GetCharacters([aAttrString string]), aReplacementRange,
      static_cast<unsigned long>(aReplacementRange ? aReplacementRange->location : 0),
      static_cast<unsigned long>(aReplacementRange ? aReplacementRange->length : 0),
      TrueOrFalse(IsIMEComposing()),
      currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr,
      currentKeyEvent ?
+       TrueOrFalse(currentKeyEvent->mKeyDownDispatched) : "N/A",
+     currentKeyEvent ?
        TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
      currentKeyEvent ?
        TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A",
      currentKeyEvent ?
        TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A",
      currentKeyEvent ?
        TrueOrFalse(currentKeyEvent->mCompositionDispatched) : "N/A"));
 
@@ -2248,18 +2351,31 @@ TextInputHandler::InsertText(NSAttribute
       return; // nothing to do
     }
     // If this is caused by a key input, the keypress event which will be
     // dispatched later should cause the delete.  Therefore, nothing to do here.
     // Although, we're not sure if such case is actually possible.
     if (!currentKeyEvent) {
       return;
     }
+
+    // When current keydown event causes this empty text input, let's
+    // dispatch eKeyDown event before any other events.  Note that if we're
+    // in a composition, we've already dispatched eKeyDown event from
+    // TextInputHandler::HandleKeyDownEvent().
+    // XXX Should we mark this eKeyDown event as "processed by IME"?
+    RefPtr<TextInputHandler> kungFuDeathGrip(this);
+    if (!IsIMEComposing() && !MaybeDispatchCurrentKeydownEvent(false)) {
+      MOZ_LOG(gLog, LogLevel::Info,
+        ("%p IMEInputHandler::InsertText, eKeyDown caused focus move or "
+         "something and canceling the composition", this));
+      return;
+    }
+
     // Delete the selected range.
-    RefPtr<TextInputHandler> kungFuDeathGrip(this);
     WidgetContentCommandEvent deleteCommandEvent(true, eContentCommandDelete,
                                                  mWidget);
     DispatchEvent(deleteCommandEvent);
     NS_ENSURE_TRUE_VOID(deleteCommandEvent.mSucceeded);
     // Be aware! The widget might be destroyed here.
     return;
   }
 
@@ -2298,39 +2414,49 @@ TextInputHandler::InsertText(NSAttribute
   // printable characters, we should ignore current key event state even
   // after the keydown has already caused dispatching composition event.
   // XXX Anyway, we should sort out around this at fixing bug 1338460.
   if (currentKeyEvent && !currentKeyEvent->CanDispatchKeyPressEvent() &&
       (str.IsEmpty() || (str.Length() == 1 && !IsPrintableChar(str[0])))) {
     return;
   }
 
+  // This is the normal path to input a character when you press a key.
+  // Let's dispatch eKeyDown event now.
+  RefPtr<TextInputHandler> kungFuDeathGrip(this);
+  if (!MaybeDispatchCurrentKeydownEvent(false)) {
+    MOZ_LOG(gLog, LogLevel::Info,
+      ("%p IMEInputHandler::InsertText, eKeyDown caused focus move or "
+       "something and canceling the composition", this));
+    return;
+  }
+
   // XXX Shouldn't we hold mDispatcher instead of mWidget?
   RefPtr<nsChildView> widget(mWidget);
   nsresult rv = mDispatcher->BeginNativeInputTransaction();
   if (NS_WARN_IF(NS_FAILED(rv))) {
-      MOZ_LOG(gLog, LogLevel::Error,
-        ("%p IMEInputHandler::HandleKeyUpEvent, "
-         "FAILED, due to BeginNativeInputTransaction() failure", this));
+    MOZ_LOG(gLog, LogLevel::Error,
+      ("%p IMEInputHandler::InsertText, "
+       "FAILED, due to BeginNativeInputTransaction() failure", this));
     return;
   }
 
   // Dispatch keypress event with char instead of compositionchange event
   WidgetKeyboardEvent keypressEvent(true, eKeyPress, widget);
   // XXX Why do we need to dispatch keypress event for not inputting any
   //     string?  If it wants to delete the specified range, should we
   //     dispatch an eContentCommandDelete event instead?  Because this
   //     must not be caused by a key operation, a part of IME's processing.
 
   // Don't set other modifiers from the current event, because here in
   // -insertText: they've already been taken into account in creating
   // the input string.
 
   if (currentKeyEvent) {
-    currentKeyEvent->InitKeyEvent(this, keypressEvent);
+    currentKeyEvent->InitKeyEvent(this, keypressEvent, false);
   } else {
     nsCocoaUtils::InitInputEvent(keypressEvent, static_cast<NSEvent*>(nullptr));
     keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
     keypressEvent.mKeyValue = str;
     // FYI: TextEventDispatcher will set mKeyCode to 0 for printable key's
     //      keypress events even if they don't cause inputting non-empty string.
   }
 
@@ -2386,16 +2512,28 @@ TextInputHandler::HandleCommand(Command 
      currentKeyEvent ?
        TrueOrFalse(currentKeyEvent->mCompositionDispatched) : "N/A"));
 
   // The command shouldn't be handled, let's ignore it.
   if (currentKeyEvent && !currentKeyEvent->CanHandleCommand()) {
     return false;
   }
 
+  // When current keydown event causes this command, let's dispatch
+  // eKeyDown event before any other events.  Note that if we're in a
+  // composition, we've already dispatched eKeyDown event from
+  // TextInputHandler::HandleKeyDownEvent().
+  RefPtr<TextInputHandler> kungFuDeathGrip(this);
+  if (!IsIMEComposing() && !MaybeDispatchCurrentKeydownEvent(false)) {
+    MOZ_LOG(gLog, LogLevel::Info,
+      ("%p IMEInputHandler::SetMarkedText, eKeyDown caused focus move or "
+       "something and canceling the composition", this));
+    return false;
+  }
+
   // If it's in composition, we cannot dispatch keypress event.
   // Therefore, we should use different approach or give up to handle
   // the command.
   if (IsIMEComposing()) {
     switch (aCommand) {
       case CommandInsertLineBreak:
       case CommandInsertParagraph: {
         // Insert '\n' as committing composition.
@@ -2482,17 +2620,17 @@ TextInputHandler::HandleCommand(Command 
   // editor may behave differently if some of them are active.
   bool dispatchFakeKeyPress =
     !(currentKeyEvent && currentKeyEvent->IsProperKeyEvent(aCommand));
 
   WidgetKeyboardEvent keypressEvent(true, eKeyPress, widget);
   if (!dispatchFakeKeyPress) {
     // If we're acutally handling a key press, we should dispatch
     // the keypress event as-is.
-    currentKeyEvent->InitKeyEvent(this, keypressEvent);
+    currentKeyEvent->InitKeyEvent(this, keypressEvent, false);
   } else {
     // Otherwise, we should dispatch "fake" keypress event.
     // However, for making it possible to compute edit commands, we need to
     // set current native key event to the fake keyboard event even if it's
     // not same as what we expect since the native keyboard event caused
     // this command.
     NSEvent* keyEvent =
       currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr;
@@ -2748,32 +2886,48 @@ bool
 TextInputHandler::DoCommandBySelector(const char* aSelector)
 {
   RefPtr<nsChildView> widget(mWidget);
 
   KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
 
   MOZ_LOG(gLog, LogLevel::Info,
     ("%p TextInputHandler::DoCommandBySelector, aSelector=\"%s\", "
-     "Destroyed()=%s, keydownHandled=%s, keypressHandled=%s, "
-     "causedOtherKeyEvents=%s",
+     "Destroyed()=%s, keydownDispatched=%s, keydownHandled=%s, "
+     "keypressDispatched=%s, keypressHandled=%s, causedOtherKeyEvents=%s",
      this, aSelector ? aSelector : "", TrueOrFalse(Destroyed()),
      currentKeyEvent ?
+       TrueOrFalse(currentKeyEvent->mKeyDownDispatched) : "N/A",
+     currentKeyEvent ?
        TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
      currentKeyEvent ?
+       TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A",
+     currentKeyEvent ?
        TrueOrFalse(currentKeyEvent->mKeyPressHandled) : "N/A",
      currentKeyEvent ?
        TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A"));
 
   // If the command isn't caused by key operation, the command should
   // be handled in the super class of the caller.
   if (!currentKeyEvent) {
     return Destroyed();
   }
 
+  // When current keydown event causes this command, let's dispatch
+  // eKeyDown event before any other events.  Note that if we're in a
+  // composition, we've already dispatched eKeyDown event from
+  // TextInputHandler::HandleKeyDownEvent().
+  RefPtr<TextInputHandler> kungFuDeathGrip(this);
+  if (!IsIMEComposing() && !MaybeDispatchCurrentKeydownEvent(false)) {
+    MOZ_LOG(gLog, LogLevel::Info,
+      ("%p IMEInputHandler::SetMarkedText, eKeyDown caused focus move or "
+       "something and canceling the composition", this));
+    return true;
+  }
+
   // If the key operation causes this command, should dispatch a keypress
   // event.
   // XXX This must be worng.  Even if this command is caused by the key
   //     operation, its our default action can be different from the
   //     command.  So, in this case, we should dispatch a keypress event
   //     which have the command and editor should handle it.
   if (currentKeyEvent->CanDispatchKeyPressEvent()) {
     nsresult rv = mDispatcher->BeginNativeInputTransaction();
@@ -2781,17 +2935,17 @@ TextInputHandler::DoCommandBySelector(co
         MOZ_LOG(gLog, LogLevel::Error,
           ("%p IMEInputHandler::DoCommandBySelector, "
            "FAILED, due to BeginNativeInputTransaction() failure "
            "at dispatching keypress", this));
       return Destroyed();
     }
 
     WidgetKeyboardEvent keypressEvent(true, eKeyPress, widget);
-    currentKeyEvent->InitKeyEvent(this, keypressEvent);
+    currentKeyEvent->InitKeyEvent(this, keypressEvent, false);
 
     nsEventStatus status = nsEventStatus_eIgnore;
     currentKeyEvent->mKeyPressDispatched =
       mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status,
                                                currentKeyEvent);
     currentKeyEvent->mKeyPressHandled =
       (status == nsEventStatus_eConsumeNoDefault);
     MOZ_LOG(gLog, LogLevel::Info,
@@ -3394,16 +3548,21 @@ IMEInputHandler::DispatchCompositionStar
     MOZ_LOG(gLog, LogLevel::Error,
       ("%p IMEInputHandler::DispatchCompositionStartEvent, "
        "FAILED, due to BeginNativeInputTransaction() failure", this));
     return false;
   }
 
   NS_ASSERTION(!mIsIMEComposing, "There is a composition already");
   mIsIMEComposing = true;
+  KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
+  mIsDeadKeyComposing =
+     currentKeyEvent && currentKeyEvent->mKeyEvent &&
+     TISInputSourceWrapper::CurrentInputSource().
+                              IsDeadKey(currentKeyEvent->mKeyEvent);
 
   nsEventStatus status;
   rv = mDispatcher->StartComposition(status);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     MOZ_LOG(gLog, LogLevel::Error,
       ("%p IMEInputHandler::DispatchCompositionStartEvent, "
        "FAILED, due to StartComposition() failure", this));
     return false;
@@ -3544,17 +3703,17 @@ IMEInputHandler::DispatchCompositionComm
       if (NS_WARN_IF(NS_FAILED(rv))) {
         MOZ_LOG(gLog, LogLevel::Error,
           ("%p IMEInputHandler::DispatchCompositionCommitEvent, "
            "FAILED, due to BeginNativeInputTransaction() failure", this));
       }
     }
   }
 
-  mIsIMEComposing = false;
+  mIsIMEComposing = mIsDeadKeyComposing = false;
   mIMECompositionStart = UINT32_MAX;
   if (mIMECompositionString) {
     [mIMECompositionString release];
     mIMECompositionString = nullptr;
   }
 
   if (Destroyed()) {
     MOZ_LOG(gLog, LogLevel::Info,
@@ -3563,16 +3722,99 @@ IMEInputHandler::DispatchCompositionComm
     return false;
   }
 
   return true;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
 }
 
+bool
+IMEInputHandler::MaybeDispatchCurrentKeydownEvent(bool aIsProcessedByIME)
+{
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+  if (Destroyed()) {
+    return false;
+  }
+  MOZ_ASSERT(mWidget);
+
+  KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
+  if (!currentKeyEvent ||
+      !currentKeyEvent->CanDispatchKeyDownEvent()) {
+    return true;
+  }
+
+  NSEvent* nativeEvent = currentKeyEvent->mKeyEvent;
+  if (NS_WARN_IF(!nativeEvent) ||
+      [nativeEvent type] != NSKeyDown) {
+    return true;
+  }
+
+  MOZ_LOG(gLog, LogLevel::Info,
+    ("%p IMEInputHandler::MaybeDispatchKeydownEvent, aIsProcessedByIME=%s "
+     "currentKeyEvent={ mKeyEvent(%p)={ type=%s, keyCode=%s (0x%X) } }, "
+     "aIsProcesedBy=%s, IsDeadKeyComposing()=%s",
+     this, TrueOrFalse(aIsProcessedByIME), nativeEvent,
+     GetNativeKeyEventType(nativeEvent),
+     GetKeyNameForNativeKeyCode([nativeEvent keyCode]), [nativeEvent keyCode],
+     TrueOrFalse(IsIMEComposing()), TrueOrFalse(IsDeadKeyComposing())));
+
+  RefPtr<IMEInputHandler> kungFuDeathGrip(this);
+  RefPtr<TextEventDispatcher> dispatcher(mDispatcher);
+  nsresult rv = dispatcher->BeginNativeInputTransaction();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+      MOZ_LOG(gLog, LogLevel::Error,
+        ("%p IMEInputHandler::DispatchKeyEventForFlagsChanged, "
+         "FAILED, due to BeginNativeInputTransaction() failure", this));
+    return false;
+  }
+
+  NSResponder* firstResponder = [[mView window] firstResponder];
+
+  // Mark currentKeyEvent as "dispatched eKeyDown event" and actually do it.
+  currentKeyEvent->mKeyDownDispatched = true;
+
+  RefPtr<nsChildView> widget(mWidget);
+
+  WidgetKeyboardEvent keydownEvent(true, eKeyDown, widget);
+  // Don't mark the eKeyDown event as "processed by IME" if the composition
+  // is started with dead key.
+  currentKeyEvent->InitKeyEvent(this, keydownEvent,
+                                aIsProcessedByIME && !IsDeadKeyComposing());
+
+  nsEventStatus status = nsEventStatus_eIgnore;
+  dispatcher->DispatchKeyboardEvent(eKeyDown, keydownEvent, status,
+                                    currentKeyEvent);
+  currentKeyEvent->mKeyDownHandled =
+    (status == nsEventStatus_eConsumeNoDefault);
+
+  if (Destroyed()) {
+    MOZ_LOG(gLog, LogLevel::Info,
+      ("%p IMEInputHandler::MaybeDispatchKeydownEvent, "
+       "widget was destroyed by keydown event", this));
+    return false;
+  }
+
+  // The key down event may have shifted the focus, in which case, we should
+  // not continue to handle current key sequence and let's commit current
+  // composition.
+  if (firstResponder != [[mView window] firstResponder]) {
+    MOZ_LOG(gLog, LogLevel::Info,
+      ("%p IMEInputHandler::MaybeDispatchKeydownEvent, "
+       "view lost focus by keydown event", this));
+    CommitIMEComposition();
+    return false;
+  }
+
+  return true;
+
+  NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
 void
 IMEInputHandler::InsertTextAsCommittingComposition(
                    NSAttributedString* aAttrString,
                    NSRange* aReplacementRange)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   MOZ_LOG(gLog, LogLevel::Info,
@@ -3591,32 +3833,46 @@ IMEInputHandler::InsertTextAsCommittingC
     MOZ_CRASH("IMEInputHandler::InsertTextAsCommittingComposition() must not"
               "be called while canceling the composition");
   }
 
   if (Destroyed()) {
     return;
   }
 
+  // When current keydown event causes this text input, let's dispatch
+  // eKeyDown event before any other events.  Note that if we're in a
+  // composition, we've already dispatched eKeyDown event from
+  // TextInputHandler::HandleKeyDownEvent().
+  // XXX Should we mark the eKeyDown event as "processed by IME"?
+  //     However, if the key causes two or more Unicode characters as
+  //     UTF-16 string, this is used.  So, perhaps, we need to improve
+  //     HandleKeyDownEvent() before do that.
+  RefPtr<IMEInputHandler> kungFuDeathGrip(this);
+  if (!IsIMEComposing() && !MaybeDispatchCurrentKeydownEvent(false)) {
+    MOZ_LOG(gLog, LogLevel::Info,
+      ("%p IMEInputHandler::InsertTextAsCommittingComposition, eKeyDown "
+       "caused focus move or something and canceling the composition", this));
+    return;
+  }
+
   // First, commit current composition with the latest composition string if the
   // replacement range is different from marked range.
   if (IsIMEComposing() && aReplacementRange &&
       aReplacementRange->location != NSNotFound &&
       !NSEqualRanges(MarkedRange(), *aReplacementRange)) {
     if (!DispatchCompositionCommitEvent()) {
       MOZ_LOG(gLog, LogLevel::Info,
         ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
          "destroyed by commiting composition for setting replacement range",
          this));
       return;
     }
   }
 
-  RefPtr<IMEInputHandler> kungFuDeathGrip(this);
-
   nsString str;
   nsCocoaUtils::GetStringForNSString([aAttrString string], str);
 
   if (!IsIMEComposing()) {
     // If there is no selection and replacement range is specified, set the
     // range as selection.
     if (aReplacementRange && aReplacementRange->location != NSNotFound &&
         !NSEqualRanges(SelectedRange(), *aReplacementRange)) {
@@ -3653,49 +3909,65 @@ IMEInputHandler::SetMarkedText(NSAttribu
   KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
 
   MOZ_LOG(gLog, LogLevel::Info,
     ("%p IMEInputHandler::SetMarkedText, "
      "aAttrString=\"%s\", aSelectedRange={ location=%lu, length=%lu }, "
      "aReplacementRange=%p { location=%lu, length=%lu }, "
      "Destroyed()=%s, IsIMEComposing()=%s, "
      "mMarkedRange={ location=%lu, length=%lu }, keyevent=%p, "
-     "keydownHandled=%s, keypressDispatched=%s, causedOtherKeyEvents=%s, "
+     "keydownDispatched=%s, keydownHandled=%s, "
+     "keypressDispatched=%s, causedOtherKeyEvents=%s, "
      "compositionDispatched=%s",
      this, GetCharacters([aAttrString string]),
      static_cast<unsigned long>(aSelectedRange.location),
      static_cast<unsigned long>(aSelectedRange.length), aReplacementRange,
      static_cast<unsigned long>(aReplacementRange ? aReplacementRange->location : 0),
      static_cast<unsigned long>(aReplacementRange ? aReplacementRange->length : 0),
      TrueOrFalse(Destroyed()), TrueOrFalse(IsIMEComposing()),
      static_cast<unsigned long>(mMarkedRange.location),
      static_cast<unsigned long>(mMarkedRange.length),
      currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr,
      currentKeyEvent ?
+       TrueOrFalse(currentKeyEvent->mKeyDownDispatched) : "N/A",
+     currentKeyEvent ?
        TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
      currentKeyEvent ?
        TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A",
      currentKeyEvent ?
        TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A",
      currentKeyEvent ?
        TrueOrFalse(currentKeyEvent->mCompositionDispatched) : "N/A"));
 
+  RefPtr<IMEInputHandler> kungFuDeathGrip(this);
+
   // If SetMarkedText() is called during handling a key press, that means that
   // the key event caused this composition.  So, keypress event shouldn't
   // be dispatched later, let's mark the key event causing composition event.
   if (currentKeyEvent) {
     currentKeyEvent->mCompositionDispatched = true;
+
+    // When current keydown event causes this text input, let's dispatch
+    // eKeyDown event before any other events.  Note that if we're in a
+    // composition, we've already dispatched eKeyDown event from
+    // TextInputHandler::HandleKeyDownEvent().  On the other hand, if we're
+    // not in composition, the key event starts new composition.  So, we
+    // need to mark the eKeyDown event as "processed by IME".
+    if (!IsIMEComposing() && !MaybeDispatchCurrentKeydownEvent(true)) {
+      MOZ_LOG(gLog, LogLevel::Info,
+        ("%p IMEInputHandler::SetMarkedText, eKeyDown caused focus move or "
+         "something and canceling the composition", this));
+      return;
+    }
   }
 
   if (Destroyed()) {
     return;
   }
 
-  RefPtr<IMEInputHandler> kungFuDeathGrip(this);
-
   // First, commit current composition with the latest composition string if the
   // replacement range is different from marked range.
   if (IsIMEComposing() && aReplacementRange &&
       aReplacementRange->location != NSNotFound &&
       !NSEqualRanges(MarkedRange(), *aReplacementRange)) {
     AutoRestore<bool> ignoreIMECommit(mIgnoreIMECommit);
     mIgnoreIMECommit = false;
     if (!DispatchCompositionCommitEvent()) {
@@ -4142,16 +4414,17 @@ IMEInputHandler::GetValidAttributesForMa
 
 IMEInputHandler::IMEInputHandler(nsChildView* aWidget,
                                  NSView<mozView> *aNativeView)
   : TextInputHandlerBase(aWidget, aNativeView)
   , mPendingMethods(0)
   , mIMECompositionString(nullptr)
   , mIMECompositionStart(UINT32_MAX)
   , mIsIMEComposing(false)
+  , mIsDeadKeyComposing(false)
   , mIsIMEEnabled(true)
   , mIsASCIICapableOnly(false)
   , mIgnoreIMECommit(false)
   , mIMEHasFocus(false)
 {
   InitStaticMembers();
 
   mMarkedRange.location = NSNotFound;
@@ -4300,23 +4573,25 @@ IMEInputHandler::KillIMEComposition()
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 void
 IMEInputHandler::CommitIMEComposition()
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
-  if (!IsIMEComposing())
-    return;
-
   MOZ_LOG(gLog, LogLevel::Info,
     ("%p IMEInputHandler::CommitIMEComposition, mIMECompositionString=%s",
      this, GetCharacters(mIMECompositionString)));
 
+  // If this is called before dispatching eCompositionStart, IsIMEComposing()
+  // returns false.  Even in such case, we need to commit composition *in*
+  // IME if this is called by preceding eKeyDown event of eCompositionStart.
+  // So, we need to call KillIMEComposition() even when IsIMEComposing()
+  // returns false.
   KillIMEComposition();
 
   if (!IsIMEComposing())
     return;
 
   // If the composition is still there, KillIMEComposition only kills the
   // composition in TSM.  We also need to finish the our composition too.
   SendCommittedText(mIMECompositionString);
@@ -4580,28 +4855,30 @@ bool
 TextInputHandlerBase::DispatchEvent(WidgetGUIEvent& aEvent)
 {
   return mWidget->DispatchWindowEvent(aEvent);
 }
 
 void
 TextInputHandlerBase::InitKeyEvent(NSEvent *aNativeKeyEvent,
                                    WidgetKeyboardEvent& aKeyEvent,
+                                   bool aIsProcessedByIME,
                                    const nsAString* aInsertString)
 {
   NS_ASSERTION(aNativeKeyEvent, "aNativeKeyEvent must not be NULL");
 
   if (mKeyboardOverride.mOverrideEnabled) {
     TISInputSourceWrapper tis;
     tis.InitByLayoutID(mKeyboardOverride.mKeyboardLayout, true);
-    tis.InitKeyEvent(aNativeKeyEvent, aKeyEvent, aInsertString);
+    tis.InitKeyEvent(aNativeKeyEvent, aKeyEvent, aIsProcessedByIME,
+                     aInsertString);
     return;
   }
   TISInputSourceWrapper::CurrentInputSource().
-    InitKeyEvent(aNativeKeyEvent, aKeyEvent, aInsertString);
+    InitKeyEvent(aNativeKeyEvent, aKeyEvent, aIsProcessedByIME, aInsertString);
 }
 
 nsresult
 TextInputHandlerBase::SynthesizeNativeKeyEvent(
                         int32_t aNativeKeyboardLayout,
                         int32_t aNativeKeyCode,
                         uint32_t aModifierFlags,
                         const nsAString& aCharacters,
@@ -4884,17 +5161,18 @@ TextInputHandlerBase::EnsureSecureEventI
  *
  *  TextInputHandlerBase::KeyEventState implementation
  *
  ******************************************************************************/
 
 void
 TextInputHandlerBase::KeyEventState::InitKeyEvent(
                                        TextInputHandlerBase* aHandler,
-                                       WidgetKeyboardEvent& aKeyEvent)
+                                       WidgetKeyboardEvent& aKeyEvent,
+                                       bool aIsProcessedByIME)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   MOZ_ASSERT(aHandler);
   MOZ_RELEASE_ASSERT(mKeyEvent);
 
   NSEvent* nativeEvent = mKeyEvent;
   if (!mInsertedString.IsEmpty()) {
@@ -4914,17 +5192,18 @@ TextInputHandlerBase::KeyEventState::Ini
                         context:[mKeyEvent context]
                      characters:unhandledNSString
     charactersIgnoringModifiers:[mKeyEvent charactersIgnoringModifiers]
                       isARepeat:[mKeyEvent isARepeat]
                         keyCode:[mKeyEvent keyCode]];
   }
 
   aKeyEvent.mUniqueId = mUniqueId;
-  aHandler->InitKeyEvent(nativeEvent, aKeyEvent, mInsertString);
+  aHandler->InitKeyEvent(nativeEvent, aKeyEvent, aIsProcessedByIME,
+                         mInsertString);
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 void
 TextInputHandlerBase::KeyEventState::GetUnhandledString(
                                        nsAString& aUnhandledString) const
 {