Bug 1444572 - IMContextWrapper should dispatch fake eKeyDown event during composition if active IM is uim r?m_kato draft
authorMasayuki Nakano <masayuki@d-toybox.com>
Mon, 12 Mar 2018 15:41:39 +0900
changeset 766652 f3ee8b306169a06b619c649e73a6d846d863ab6c
parent 766635 6e4cb88881be1831389fda2959b75b0d962d2ba3
child 766653 3351731162e6e670510a0972c32360e32465d147
push id102376
push usermasayuki@d-toybox.com
push dateTue, 13 Mar 2018 06:38:28 +0000
reviewersm_kato
bugs1444572
milestone61.0a1
Bug 1444572 - IMContextWrapper should dispatch fake eKeyDown event during composition if active IM is uim r?m_kato uim is an old IM which uses key snooper to listen to key events rather than via filter key event API which should be called by applications. It's still used by Debian 9.x, so, we still need to support this. Unfortunately, we cannot detect if uim actually uses key snooper because it's switch by build option of uim. Currently, Debian builds uim as using key snooper. So, we should assume uim uses key snooper always. On the other hand, somebody *might* use uim built as not using key snooper, so, let's decide if uim uses key snooper with new pref, "intl.ime.hack.uim.using_key_snooper", but its default should be true. Note that ibus and Fcitx also have the mode to use key snooper (perhaps for backward compatibility with uim). However, it's not enabled in default settings and even if it's enabled, Firefox is in whitelist in the default settings of them for stop using key snooper. Therefore, we don't need to support key snooper mode for them unless we'll get some requests to support their key snooping mode. MozReview-Commit-ID: 6fTsfKrHzvo
modules/libpref/init/all.js
widget/gtk/IMContextWrapper.cpp
widget/gtk/IMContextWrapper.h
widget/gtk/nsGtkKeyUtils.cpp
widget/gtk/nsWindow.cpp
widget/gtk/nsWindow.h
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4562,16 +4562,23 @@ pref("font.name-list.monospace.zh-TW", "
 // 2. The parent of non-topmost panel is not activated when the panel is hidden.
 // So, we have no reasons we should use non-toplevel window for popup.
 pref("ui.panel.default_level_parent", true);
 
 pref("mousewheel.system_scroll_override_on_root_content.enabled", false);
 
 pref("intl.ime.use_simple_context_on_password_field", false);
 
+// uim may use key snooper to listen to key events.  Unfortunately, we cannot
+// know whether it uses or not since it's a build option.  So, we need to make
+// distribution switchable whether we think uim uses key snooper or not with
+// this pref.  Debian 9.x still uses uim as their default IM and it uses key
+// snooper.  So, let's use true for its default value.
+pref("intl.ime.hack.uim.using_key_snooper", true);
+
 #ifdef MOZ_WIDGET_GTK
 // maximum number of fonts to substitute for a generic
 pref("gfx.font_rendering.fontconfig.max_generic_substitutions", 3);
 #endif
 
 # XP_UNIX
 #endif
 #endif
--- a/widget/gtk/IMContextWrapper.cpp
+++ b/widget/gtk/IMContextWrapper.cpp
@@ -339,31 +339,52 @@ IMContextWrapper::Init()
         G_CALLBACK(IMContextWrapper::OnStartCompositionCallback), this);
     g_signal_connect(mContext, "preedit_end",
         G_CALLBACK(IMContextWrapper::OnEndCompositionCallback), this);
     nsDependentCString contextID(
         gtk_im_multicontext_get_context_id(GTK_IM_MULTICONTEXT(mContext)));
     if (contextID.EqualsLiteral("ibus")) {
         mIMContextID = IMContextID::eIBus;
         mIsIMInAsyncKeyHandlingMode = !IsIBusInSyncMode();
+        // Although ibus has key snooper mode, it's forcibly disabled on Firefox
+        // in default settings by its whitelist since we always send key events
+        // to IME before handling shortcut keys.  The whitelist can be
+        // customized with env, IBUS_NO_SNOOPER_APPS, but we don't need to
+        // support such rare cases for reducing maintenance cost.
+        mIsKeySnooped = false;
     } else if (contextID.EqualsLiteral("fcitx")) {
         mIMContextID = IMContextID::eFcitx;
         mIsIMInAsyncKeyHandlingMode = !IsFcitxInSyncMode();
+        // Although Fcitx has key snooper mode similar to ibus, it's also
+        // disabled on Firefox in default settings by its whitelist.  The
+        // whitelist can be customized with env, IBUS_NO_SNOOPER_APPS or
+        // FCITX_NO_SNOOPER_APPS, but we don't need to support such rare cases
+        // for reducing maintenance cost.
+        mIsKeySnooped = false;
     } else if (contextID.EqualsLiteral("uim")) {
         mIMContextID = IMContextID::eUim;
         mIsIMInAsyncKeyHandlingMode = false;
+        // We cannot know if uim uses key snooper since it's build option of
+        // uim.  Therefore, we need to retrieve the consideration from the
+        // pref for making users and distributions allowed to choose their
+        // preferred value.
+        mIsKeySnooped =
+            Preferences::GetBool("intl.ime.hack.uim.using_key_snooper", true);
     } else if (contextID.EqualsLiteral("scim")) {
         mIMContextID = IMContextID::eScim;
         mIsIMInAsyncKeyHandlingMode = false;
+        mIsKeySnooped = false;
     } else if (contextID.EqualsLiteral("iiim")) {
         mIMContextID = IMContextID::eIIIMF;
         mIsIMInAsyncKeyHandlingMode = false;
+        mIsKeySnooped = false;
     } else {
         mIMContextID = IMContextID::eUnknown;
         mIsIMInAsyncKeyHandlingMode = false;
+        mIsKeySnooped = false;
     }
 
     // Simple context
     if (sUseSimpleContext) {
         mSimpleContext = gtk_im_context_simple_new();
         gtk_im_context_set_client_window(mSimpleContext, gdkWindow);
         g_signal_connect(mSimpleContext, "preedit_changed",
             G_CALLBACK(&IMContextWrapper::OnChangeCompositionCallback),
@@ -386,20 +407,21 @@ IMContextWrapper::Init()
     }
 
     // Dummy context
     mDummyContext = gtk_im_multicontext_new();
     gtk_im_context_set_client_window(mDummyContext, gdkWindow);
 
     MOZ_LOG(gGtkIMLog, LogLevel::Info,
         ("0x%p Init(), mOwnerWindow=%p, mContext=%p (%s), "
-         "mIsIMInAsyncKeyHandlingMode=%s, mSimpleContext=%p, "
-         "mDummyContext=%p",
+         "mIsIMInAsyncKeyHandlingMode=%s, mIsKeySnooped=%s, "
+         "mSimpleContext=%p, mDummyContext=%p",
          this, mOwnerWindow, mContext, contextID.get(),
-         ToChar(mIsIMInAsyncKeyHandlingMode), mSimpleContext, mDummyContext));
+         ToChar(mIsIMInAsyncKeyHandlingMode), ToChar(mIsKeySnooped),
+         mSimpleContext, mDummyContext));
 }
 
 IMContextWrapper::~IMContextWrapper()
 {
     if (this == sLastFocusedContext) {
         sLastFocusedContext = nullptr;
     }
     MOZ_LOG(gGtkIMLog, LogLevel::Info,
@@ -795,17 +817,17 @@ IMContextWrapper::OnKeyEvent(nsWindow* a
         filterThisEvent = false;
     }
 
     if (filterThisEvent && !mKeyboardEventWasDispatched) {
         // If IME handled the key event but we've not dispatched eKeyDown nor
         // eKeyUp event yet, we need to dispatch here unless the key event is
         // now being handled by other IME process.
         if (!maybeHandledAsynchronously) {
-            MaybeDispatchKeyEventAsProcessedByIME();
+            MaybeDispatchKeyEventAsProcessedByIME(eVoidEvent);
             // Be aware, the widget might have been gone here.
         }
         // If we need to wait reply from IM, IM may send some signals to us
         // without sending the key event again.  In such case, we need to
         // dispatch keyboard events with a copy of aEvent.  Therefore, we
         // need to use information of this key event to dispatch an KeyDown
         // or eKeyUp event later.
         else {
@@ -1694,81 +1716,152 @@ IMContextWrapper::GetCompositionString(G
          "aCompositionString=\"%s\"",
          this, aContext, preedit_string));
 
     pango_attr_list_unref(feedback_list);
     g_free(preedit_string);
 }
 
 bool
-IMContextWrapper::MaybeDispatchKeyEventAsProcessedByIME()
+IMContextWrapper::MaybeDispatchKeyEventAsProcessedByIME(
+                      EventMessage aFollowingEvent)
 {
-    if ((!mProcessingKeyEvent && mPostingKeyEvents.IsEmpty()) ||
-        (mProcessingKeyEvent && mKeyboardEventWasDispatched) ||
-        !mLastFocusedWindow) {
+    if (!mLastFocusedWindow) {
+        return false;
+    }
+
+    if (!mIsKeySnooped &&
+        ((!mProcessingKeyEvent && mPostingKeyEvents.IsEmpty()) ||
+         (mProcessingKeyEvent && mKeyboardEventWasDispatched))) {
         return true;
     }
 
     // A "keydown" or "keyup" event handler may change focus with the
     // following event.  In such case, we need to cancel this composition.
     // So, we need to store IM context now because mComposingContext may be
     // overwritten with different context if calling this method recursively.
     // Note that we don't need to grab the context here because |context|
     // will be used only for checking if it's same as mComposingContext.
     GtkIMContext* oldCurrentContext = GetCurrentContext();
     GtkIMContext* oldComposingContext = mComposingContext;
 
     RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
-    if (mProcessingKeyEvent) {
-        mKeyboardEventWasDispatched = true;
-    }
+
+    if (mProcessingKeyEvent || !mPostingKeyEvents.IsEmpty()) {
+        if (mProcessingKeyEvent) {
+            mKeyboardEventWasDispatched = true;
+        }
+        // If we're not handling a key event synchronously, the signal may be
+        // sent by IME without sending key event to us.  In such case, we
+        // should dispatch keyboard event for the last key event which was
+        // posted to other IME process.
+        GdkEventKey* sourceEvent =
+            mProcessingKeyEvent ? mProcessingKeyEvent :
+                                  mPostingKeyEvents.GetFirstEvent();
 
-    // If we're not handling a key event synchronously, the signal may be
-    // sent by IME without sending key event to us.  In such case, we should
-    // dispatch keyboard event for the last key event which was posted to
-    // other IME process.
-    GdkEventKey* sourceEvent =
-        mProcessingKeyEvent ? mProcessingKeyEvent :
-                              mPostingKeyEvents.GetFirstEvent();
+        MOZ_LOG(gGtkIMLog, LogLevel::Info,
+            ("0x%p MaybeDispatchKeyEventAsProcessedByIME("
+             "aFollowingEvent=%s), dispatch %s %s "
+             "event: { type=%s, keyval=%s, unicode=0x%X, state=%s, "
+             "time=%u, hardware_keycode=%u, group=%u }",
+             this, ToChar(aFollowingEvent),
+             ToChar(sourceEvent->type == GDK_KEY_PRESS ? eKeyDown : eKeyUp),
+             mProcessingKeyEvent ? "processing" : "posted",
+             GetEventType(sourceEvent), gdk_keyval_name(sourceEvent->keyval),
+             gdk_keyval_to_unicode(sourceEvent->keyval),
+             GetEventStateName(sourceEvent->state, mIMContextID).get(),
+             sourceEvent->time, sourceEvent->hardware_keycode,
+             sourceEvent->group));
 
-    MOZ_LOG(gGtkIMLog, LogLevel::Info,
-        ("0x%p MaybeDispatchKeyEventAsProcessedByIME(), dispatch %s %s "
-         "event: { type=%s, keyval=%s, unicode=0x%X, state=%s, "
-         "time=%u, hardware_keycode=%u, group=%u }",
-         this, ToChar(sourceEvent->type == GDK_KEY_PRESS ? eKeyDown : eKeyUp),
-         mProcessingKeyEvent ? "processing" : "posted",
-         GetEventType(sourceEvent), gdk_keyval_name(sourceEvent->keyval),
-         gdk_keyval_to_unicode(sourceEvent->keyval),
-         GetEventStateName(sourceEvent->state, mIMContextID).get(),
-         sourceEvent->time, sourceEvent->hardware_keycode, sourceEvent->group));
+        // Let's dispatch eKeyDown event or eKeyUp event now.  Note that only
+        // when we're not in a dead key composition, we should mark the
+        // eKeyDown and eKeyUp event as "processed by IME" since we should
+        // expose raw keyCode and key value to web apps the key event is a
+        // part of a dead key sequence.
+        // FYI: We should ignore if default of preceding keydown or keyup
+        //      event is prevented since even on the other browsers, web
+        //      applications cannot cancel the following composition event.
+        //      Spec bug: https://github.com/w3c/uievents/issues/180
+        bool isCancelled;
+        lastFocusedWindow->DispatchKeyDownOrKeyUpEvent(sourceEvent,
+                                                       !mMaybeInDeadKeySequence,
+                                                       &isCancelled);
+        MOZ_LOG(gGtkIMLog, LogLevel::Info,
+            ("0x%p   MaybeDispatchKeyEventAsProcessedByIME(), keydown or keyup "
+             "event is dispatched",
+             this));
 
-    // Let's dispatch eKeyDown event or eKeyUp event now.  Note that only when
-    // we're not in a dead key composition, we should mark the eKeyDown and
-    // eKeyUp event as "processed by IME" since we should expose raw keyCode
-    // and key value to web apps the key event is a part of a dead key
-    // sequence.
-    // FYI: We should ignore if default of preceding keydown or keyup event is
-    //      prevented since even on the other browsers, web applications
-    //      cannot cancel the following composition event.
-    //      Spec bug: https://github.com/w3c/uievents/issues/180
-    bool isCancelled;
-    lastFocusedWindow->DispatchKeyDownOrKeyUpEvent(sourceEvent,
-                                                   !mMaybeInDeadKeySequence,
-                                                   &isCancelled);
-    MOZ_LOG(gGtkIMLog, LogLevel::Info,
-        ("0x%p   MaybeDispatchKeyEventAsProcessedByIME(), keydown or keyup "
-         "event is dispatched",
-         this));
+        if (!mProcessingKeyEvent) {
+            MOZ_LOG(gGtkIMLog, LogLevel::Info,
+                ("0x%p   MaybeDispatchKeyEventAsProcessedByIME(), removing first "
+                 "event from the queue",
+                 this));
+            mPostingKeyEvents.RemoveEvent(sourceEvent);
+        }
+    } else {
+        MOZ_ASSERT(mIsKeySnooped);
+        // Currently, we support key snooper mode of uim only.
+        MOZ_ASSERT(mIMContextID == IMContextID::eUim);
+        // uim sends "preedit_start" signal and "preedit_changed" separately
+        // at starting composition, "commit" and "preedit_end" separately at
+        // committing composition.
 
-    if (!mProcessingKeyEvent) {
-        MOZ_LOG(gGtkIMLog, LogLevel::Info,
-            ("0x%p   MaybeDispatchKeyEventAsProcessedByIME(), removing first "
-             "event from the queue",
-             this));
-        mPostingKeyEvents.RemoveEvent(sourceEvent);
+        // Currently, we should dispatch only fake eKeyDown event because
+        // we cannot decide which is the last signal of each key operation
+        // and Chromium also dispatches only "keydown" event in this case.
+        bool dispatchFakeKeyDown = false;
+        switch (aFollowingEvent) {
+            case eCompositionStart:
+            case eCompositionCommit:
+            case eCompositionCommitAsIs:
+                dispatchFakeKeyDown = true;
+                break;
+            // XXX Unfortunately, I don't have a good idea to prevent to
+            //     dispatch redundant eKeyDown event for eCompositionStart
+            //     immediately after "delete_surrounding" signal.  However,
+            //     not dispatching eKeyDown event is worse than dispatching
+            //     redundant eKeyDown events.
+            case eContentCommandDelete:
+                dispatchFakeKeyDown = true;
+                break;
+            // We need to prevent to dispatch redundant eKeyDown event for
+            // eCompositionChange immediately after eCompositionStart.  So,
+            // We should not dispatch eKeyDown event if dispatched composition
+            // string is still empty string.
+            case eCompositionChange:
+                dispatchFakeKeyDown = !mDispatchedCompositionString.IsEmpty();
+                break;
+            default:
+                MOZ_ASSERT_UNREACHABLE("Do you forget to handle the case?");
+                break;
+        }
+
+        if (dispatchFakeKeyDown) {
+            WidgetKeyboardEvent fakeKeyDownEvent(true, eKeyDown,
+                                                 lastFocusedWindow);
+            fakeKeyDownEvent.mKeyCode = NS_VK_PROCESSKEY;
+            fakeKeyDownEvent.mKeyNameIndex = KEY_NAME_INDEX_Process;
+            // It's impossible to get physical key information in this case but
+            // this should be okay since web apps shouldn't do anything with
+            // physical key information during composition.
+            fakeKeyDownEvent.mCodeNameIndex = CODE_NAME_INDEX_UNKNOWN;
+
+            MOZ_LOG(gGtkIMLog, LogLevel::Info,
+                ("0x%p MaybeDispatchKeyEventAsProcessedByIME("
+                 "aFollowingEvent=%s), dispatch fake eKeyDown event",
+                 this, ToChar(aFollowingEvent)));
+
+            bool isCancelled;
+            lastFocusedWindow->DispatchKeyDownOrKeyUpEvent(fakeKeyDownEvent,
+                                                           &isCancelled);
+            MOZ_LOG(gGtkIMLog, LogLevel::Info,
+                ("0x%p   MaybeDispatchKeyEventAsProcessedByIME(), "
+                 "fake keydown event is dispatched",
+                 this));
+        }
     }
 
     if (lastFocusedWindow->IsDestroyed() ||
         lastFocusedWindow != mLastFocusedWindow) {
         MOZ_LOG(gGtkIMLog, LogLevel::Warning,
             ("0x%p   MaybeDispatchKeyEventAsProcessedByIME(), Warning, the "
              "focused widget was destroyed/changed by a key event",
              this));
@@ -1837,17 +1930,17 @@ IMContextWrapper::DispatchCompositionSta
     //     because it may high cost if we query the offset every time.
     mCompositionStart = mSelection.mOffset;
     mDispatchedCompositionString.Truncate();
 
     // If this composition is started by a key press, we need to dispatch
     // eKeyDown or eKeyUp event before dispatching eCompositionStart event.
     // Note that dispatching a keyboard event which is marked as "processed
     // by IME" is okay since Chromium also dispatches keyboard event as so.
-    if (!MaybeDispatchKeyEventAsProcessedByIME()) {
+    if (!MaybeDispatchKeyEventAsProcessedByIME(eCompositionStart)) {
         MOZ_LOG(gGtkIMLog, LogLevel::Warning,
             ("0x%p   DispatchCompositionStart(), Warning, "
              "MaybeDispatchKeyEventAsProcessedByIME() returned false",
              this));
         return false;
     }
 
     RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
@@ -1902,17 +1995,17 @@ IMContextWrapper::DispatchCompositionCha
              "wasn't started, force starting...",
              this));
         if (!DispatchCompositionStart(aContext)) {
             return false;
         }
     }
     // If this composition string change caused by a key press, we need to
     // dispatch eKeyDown or eKeyUp before dispatching eCompositionChange event.
-    else if (!MaybeDispatchKeyEventAsProcessedByIME()) {
+    else if (!MaybeDispatchKeyEventAsProcessedByIME(eCompositionChange)) {
         MOZ_LOG(gGtkIMLog, LogLevel::Warning,
             ("0x%p   DispatchCompositionChangeEvent(), Warning, "
              "MaybeDispatchKeyEventAsProcessedByIME() returned false",
              this));
         return false;
     }
 
     RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
@@ -2024,17 +2117,18 @@ IMContextWrapper::DispatchCompositionCom
              "the composition wasn't started, force starting...",
              this));
         if (!DispatchCompositionStart(aContext)) {
             return false;
         }
     }
     // If this commit caused by a key press, we need to dispatch eKeyDown or
     // eKeyUp before dispatching composition events.
-    else if (!MaybeDispatchKeyEventAsProcessedByIME()) {
+    else if (!MaybeDispatchKeyEventAsProcessedByIME(
+                 aCommitString ? eCompositionCommit : eCompositionCommitAsIs)) {
         MOZ_LOG(gGtkIMLog, LogLevel::Warning,
             ("0x%p   DispatchCompositionCommitEvent(), Warning, "
              "MaybeDispatchKeyEventAsProcessedByIME() returned false",
              this));
         mCompositionState = eCompositionState_NotComposing;
         return false;
     }
 
@@ -2733,17 +2827,17 @@ IMContextWrapper::DeleteText(GtkIMContex
             ("0x%p   DeleteText(), FAILED, setting selection caused "
              "focus change or window destroyed",
              this));
         return NS_ERROR_FAILURE;
     }
 
     // If this deleting text caused by a key press, we need to dispatch
     // eKeyDown or eKeyUp before dispatching eContentCommandDelete event.
-    if (!MaybeDispatchKeyEventAsProcessedByIME()) {
+    if (!MaybeDispatchKeyEventAsProcessedByIME(eContentCommandDelete)) {
         MOZ_LOG(gGtkIMLog, LogLevel::Warning,
             ("0x%p   DeleteText(), Warning, "
              "MaybeDispatchKeyEventAsProcessedByIME() returned false",
              this));
         return NS_ERROR_FAILURE;
     }
 
     // Delete the selection
--- a/widget/gtk/IMContextWrapper.h
+++ b/widget/gtk/IMContextWrapper.h
@@ -450,16 +450,19 @@ protected:
     // by gtk_im_context_filter_keypress() normally.  On the other hand, when
     // pressing "Shift" key causes exactly same behavior but dead key sequence
     // isn't finished yet.
     bool mMaybeInDeadKeySequence;
     // mIsIMInAsyncKeyHandlingMode is set to true if we know that IM handles
     // key events asynchronously.  I.e., filtered key event may come again
     // later.
     bool mIsIMInAsyncKeyHandlingMode;
+    // mIsKeySnooped is set to true if IM uses key snooper to listen key events.
+    // In such case, we won't receive key events if IME consumes the event.
+    bool mIsKeySnooped;
 
     // sLastFocusedContext is a pointer to the last focused instance of this
     // class.  When a instance is destroyed and sLastFocusedContext refers it,
     // this is cleared.  So, this refers valid pointer always.
     static IMContextWrapper* sLastFocusedContext;
 
     // sUseSimpleContext indeicates if password editors and editors with
     // |ime-mode: disabled;| should use GtkIMContextSimple.
@@ -615,23 +618,24 @@ protected:
      * Dispatch an eKeyDown or eKeyUp event whose mKeyCode value is
      * NS_VK_PROCESSKEY and mKeyNameIndex is KEY_NAME_INDEX_Process if
      * we're not in a dead key sequence, mProcessingKeyEvent is nullptr
      * but mPostingKeyEvents is not empty or mProcessingKeyEvent is not
      * nullptr and mKeyboardEventWasDispatched is still false.  If this
      * dispatches a keyboard event, this sets mKeyboardEventWasDispatched
      * to true.
      *
+     * @param aFollowingEvent       The following event message.
      * @return                      If the caller can continue to handle
      *                              composition, returns true.  Otherwise,
      *                              false.  For example, if focus is moved
      *                              by dispatched keyboard event, returns
      *                              false.
      */
-    bool MaybeDispatchKeyEventAsProcessedByIME();
+    bool MaybeDispatchKeyEventAsProcessedByIME(EventMessage aFollowingEvent);
 
     /**
      * Dispatches a composition start event.
      *
      * @param aContext              A GtkIMContext which is being handled.
      * @return                      true if the focused widget is neither
      *                              destroyed nor changed.  Otherwise, false.
      */
--- a/widget/gtk/nsGtkKeyUtils.cpp
+++ b/widget/gtk/nsGtkKeyUtils.cpp
@@ -1406,16 +1406,24 @@ KeymapWrapper::WillDispatchKeyboardEvent
 {
     GetInstance()->WillDispatchKeyboardEventInternal(aKeyEvent, aGdkKeyEvent);
 }
 
 void
 KeymapWrapper::WillDispatchKeyboardEventInternal(WidgetKeyboardEvent& aKeyEvent,
                                                  GdkEventKey* aGdkKeyEvent)
 {
+    if (!aGdkKeyEvent) {
+        // If aGdkKeyEvent is nullptr, we're trying to dispatch a fake keyboard
+        // event in such case, we don't need to set alternative char codes.
+        // So, we don't need to do nothing here.  This case is typically we're
+        // dispatching eKeyDown or eKeyUp event during composition.
+        return;
+    }
+
     uint32_t charCode = GetCharCodeFor(aGdkKeyEvent);
     if (!charCode) {
         MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
             ("%p WillDispatchKeyboardEventInternal, "
              "mKeyCode=0x%02X, charCode=0x%08X",
              this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode));
         return;
     }
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -2936,39 +2936,50 @@ IsCtrlAltTab(GdkEventKey *aEvent)
             KeymapWrapper::CTRL | KeymapWrapper::ALT, aEvent->state);
 }
 
 bool
 nsWindow::DispatchKeyDownOrKeyUpEvent(GdkEventKey* aEvent,
                                       bool aIsProcessedByIME,
                                       bool* aIsCancelled)
 {
-    MOZ_ASSERT(aIsCancelled, "aCancelled must not be null");
+    MOZ_ASSERT(aIsCancelled, "aIsCancelled must not be nullptr");
 
     *aIsCancelled = false;
 
     if (aEvent->type == GDK_KEY_PRESS && IsCtrlAltTab(aEvent)) {
         return false;
     }
 
-    RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
-    nsresult rv = dispatcher->BeginNativeInputTransaction();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-        return FALSE;
-    }
-
     EventMessage message =
         aEvent->type == GDK_KEY_PRESS ? eKeyDown : eKeyUp;
     WidgetKeyboardEvent keyEvent(true, message, this);
     KeymapWrapper::InitKeyEvent(keyEvent, aEvent, aIsProcessedByIME);
+    return DispatchKeyDownOrKeyUpEvent(keyEvent, aIsCancelled);
+}
+bool
+nsWindow::DispatchKeyDownOrKeyUpEvent(WidgetKeyboardEvent& aKeyboardEvent,
+                                      bool* aIsCancelled)
+{
+    MOZ_ASSERT(aIsCancelled, "aIsCancelled must not be nullptr");
+
+    *aIsCancelled = false;
+
+    RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
+    nsresult rv = dispatcher->BeginNativeInputTransaction();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+        return FALSE;
+    }
+
     nsEventStatus status = nsEventStatus_eIgnore;
     bool dispatched =
-        dispatcher->DispatchKeyboardEvent(message, keyEvent, status, aEvent);
+        dispatcher->DispatchKeyboardEvent(aKeyboardEvent.mMessage,
+                                          aKeyboardEvent, status, nullptr);
     *aIsCancelled = (status == nsEventStatus_eConsumeNoDefault);
-    return dispatched ? TRUE : FALSE;
+    return dispatched;
 }
 
 WidgetEventTime
 nsWindow::GetWidgetEventTime(guint32 aEventTime)
 {
   return WidgetEventTime(aEventTime, GetEventTimeStamp(aEventTime));
 }
 
--- a/widget/gtk/nsWindow.h
+++ b/widget/gtk/nsWindow.h
@@ -73,16 +73,17 @@ class TimeStamp;
 class CurrentX11TimeGetter;
 }
 
 class nsWindow final : public nsBaseWidget
 {
 public:
     typedef mozilla::gfx::DrawTarget DrawTarget;
     typedef mozilla::WidgetEventTime WidgetEventTime;
+    typedef mozilla::WidgetKeyboardEvent WidgetKeyboardEvent;
     typedef mozilla::widget::PlatformCompositorWidgetDelegate PlatformCompositorWidgetDelegate;
 
     nsWindow();
 
     static void ReleaseGlobals();
 
     NS_INLINE_DECL_REFCOUNTING_INHERITED(nsWindow, nsBaseWidget)
 
@@ -289,16 +290,28 @@ public:
      * @param aIsCancelled      [Out] true if the default is prevented.
      * @return                  true if eKeyDown event is actually dispatched.
      *                          Otherwise, false.
      */
     bool DispatchKeyDownOrKeyUpEvent(GdkEventKey* aEvent,
                                      bool aProcessedByIME,
                                      bool* aIsCancelled);
 
+    /**
+     * DispatchKeyDownOrKeyUpEvent() dispatches eKeyDown or eKeyUp event.
+     *
+     * @param aEvent            An eKeyDown or eKeyUp event.  This will be
+     *                          dispatched as is.
+     * @param aIsCancelled      [Out] true if the default is prevented.
+     * @return                  true if eKeyDown event is actually dispatched.
+     *                          Otherwise, false.
+     */
+    bool DispatchKeyDownOrKeyUpEvent(WidgetKeyboardEvent& aEvent,
+                                     bool* aIsCancelled);
+
     WidgetEventTime    GetWidgetEventTime(guint32 aEventTime);
     mozilla::TimeStamp GetEventTimeStamp(guint32 aEventTime);
     mozilla::CurrentX11TimeGetter* GetCurrentTimeGetter();
 
     virtual void SetInputContext(const InputContext& aContext,
                                  const InputContextAction& aAction) override;
     virtual InputContext GetInputContext() override;
     virtual TextEventDispatcherListener*