Bug 1443421 - part 1: Make IMContextWrapper not dispatch eKeyDown and eKeyUp event if the native key event is being handled by other IME process r?m_kato draft
authorMasayuki Nakano <masayuki@d-toybox.com>
Fri, 09 Mar 2018 00:46:52 +0900
changeset 767418 69fd8068cc93b6f459e5982dac13c480db127b06
parent 767417 28455fcbb6252574737c3512c8fcb3d6c4f0e072
child 767419 67a18058386aac4508c56d8d6a9a5a79a683d5b2
push id102599
push usermasayuki@d-toybox.com
push dateWed, 14 Mar 2018 16:19:57 +0000
reviewersm_kato
bugs1443421
milestone61.0a1
Bug 1443421 - part 1: Make IMContextWrapper not dispatch eKeyDown and eKeyUp event if the native key event is being handled by other IME process r?m_kato ibus and fcitx have asynchronous key event handling mode and it's enabled in default settings. That is, when they receive a key event from application via a call of gtk_im_context_filter_keypress(), they may post the key event information to other IME process, then does nothing but store the copy of the event with gdk_event_copy() and returns true for the result of gtk_im_context_filter_keypress(). When the other IME process handles the event, returns the result to them in our process. Then, they send the stored key event to us again. Finally, they actually handles the event in our process actually. Therefore, we may receive every key event twice. So, this causes dispatching eKeyDown event and eKeyUp event twice. Preceding key event is always marked as "processed by IME" since gtk_im_context_filter_keypress() returns true temporarily and following key event is dispatched as expected. So, we need to ignore the first event only when gtk_im_context_filter_keypress() returns true but the event is posted to different process. Unfortunately, we cannot know if the key event is actually posted to different process directly. However, we can know if active IM is ibus, fcitx or another one and if ibus or fcitx is in asynchronous key handling mode. The former information is provided by gtk_im_multicontext_get_context_id(). It returns a string which is set to the IM multicontext instance by creator. We'll get "ibus" if IM is ibus, get "fcitx" if IM is fcitx. The latter information is not provided. However, they consider the mode from env value. ibus checks IBUS_ENABLE_SYNC_MODE. fcitx checks both IBUS_ENABLE_SYNC_MODE and FCITX_ENABLE_SYNC_MODE. Additionally, we can know if received key event has already been posted to other IME process. They use undefined bit of GdkEventKey::state to store if the key event has already been posted (1 << 25, they called "ignored" flag). Although their approach is really hacky but we can refer the information at least for now. Finally, when we guess a key event is posted to other IME process, let's IMContextWrapper::OnKeyEvent() not dispatch eKeyDown nor eKeyUp event. Note that if it's handled synchronously as unexpected, it may causes dispatching one or more composition events and/or delete content event. So, in such case, we dispatch a keyboard event for processing key event anyway. There is only once case we'll fail to dispatch keyboard event. If we receive signals to dispatch composition events or delete content command event when IM receives the result from other IME process but it doesn't send the key event to us. This will be fixed by the following patch. MozReview-Commit-ID: 94PrlnmQ3uJ
widget/gtk/IMContextWrapper.cpp
widget/gtk/IMContextWrapper.h
widget/gtk/mozgtk/mozgtk.c
--- a/widget/gtk/IMContextWrapper.cpp
+++ b/widget/gtk/IMContextWrapper.cpp
@@ -54,16 +54,83 @@ GetEventType(GdkEventKey* aKeyEvent)
             return "GDK_KEY_PRESS";
         case GDK_KEY_RELEASE:
             return "GDK_KEY_RELEASE";
         default:
             return "Unknown";
     }
 }
 
+class GetEventStateName : public nsAutoCString
+{
+public:
+    explicit GetEventStateName(guint aState,
+                               IMContextWrapper::IMContextID aIMContextID =
+                                   IMContextWrapper::IMContextID::eUnknown)
+    {
+        if (aState & GDK_SHIFT_MASK) {
+            AppendModifier("shift");
+        }
+        if (aState & GDK_CONTROL_MASK) {
+            AppendModifier("control");
+        }
+        if (aState & GDK_MOD1_MASK) {
+            AppendModifier("mod1");
+        }
+        if (aState & GDK_MOD2_MASK) {
+            AppendModifier("mod2");
+        }
+        if (aState & GDK_MOD3_MASK) {
+            AppendModifier("mod3");
+        }
+        if (aState & GDK_MOD4_MASK) {
+            AppendModifier("mod4");
+        }
+        if (aState & GDK_MOD4_MASK) {
+            AppendModifier("mod5");
+        }
+        if (aState & GDK_MOD4_MASK) {
+            AppendModifier("mod5");
+        }
+        switch (aIMContextID) {
+            case IMContextWrapper::IMContextID::eIBus:
+                static const guint IBUS_HANDLED_MASK = 1 << 24;
+                static const guint IBUS_IGNORED_MASK = 1 << 25;
+                if (aState & IBUS_HANDLED_MASK) {
+                    AppendModifier("IBUS_HANDLED_MASK");
+                }
+                if (aState & IBUS_IGNORED_MASK) {
+                    AppendModifier("IBUS_IGNORED_MASK");
+                }
+                break;
+            case IMContextWrapper::IMContextID::eFcitx:
+                static const guint FcitxKeyState_HandledMask = 1 << 24;
+                static const guint FcitxKeyState_IgnoredMask = 1 << 25;
+                if (aState & FcitxKeyState_HandledMask) {
+                    AppendModifier("FcitxKeyState_HandledMask");
+                }
+                if (aState & FcitxKeyState_IgnoredMask) {
+                    AppendModifier("FcitxKeyState_IgnoredMask");
+                }
+                break;
+            default:
+                break;
+        }
+    }
+
+private:
+    void AppendModifier(const char* aModifierName)
+    {
+        if (!IsEmpty()) {
+            AppendLiteral(" + ");
+        }
+        Append(aModifierName);
+    }
+};
+
 class GetWritingModeName : public nsAutoCString
 {
 public:
   explicit GetWritingModeName(const WritingMode& aWritingMode)
   {
     if (!aWritingMode.IsVertical()) {
       AssignLiteral("Horizontal");
       return;
@@ -169,44 +236,92 @@ IMContextWrapper::IMContextWrapper(nsWin
     , mLastFocusedWindow(nullptr)
     , mContext(nullptr)
     , mSimpleContext(nullptr)
     , mDummyContext(nullptr)
     , mComposingContext(nullptr)
     , mCompositionStart(UINT32_MAX)
     , mProcessingKeyEvent(nullptr)
     , mCompositionState(eCompositionState_NotComposing)
+    , mIMContextID(IMContextID::eUnknown)
     , mIsIMFocused(false)
     , mFallbackToKeyEvent(false)
     , mKeyboardEventWasDispatched(false)
     , mIsDeletingSurrounding(false)
     , mLayoutChanged(false)
     , mSetCursorPositionOnKeyEvent(true)
     , mPendingResettingIMContext(false)
     , mRetrieveSurroundingSignalReceived(false)
     , mMaybeInDeadKeySequence(false)
+    , mIsIMInAsyncKeyHandlingMode(false)
 {
     static bool sFirstInstance = true;
     if (sFirstInstance) {
         sFirstInstance = false;
         sUseSimpleContext =
             Preferences::GetBool(
                 "intl.ime.use_simple_context_on_password_field",
                 kUseSimpleContextDefault);
     }
     Init();
 }
 
+static bool
+IsIBusInSyncMode()
+{
+    // See ibus_im_context_class_init() in client/gtk2/ibusimcontext.c
+    // https://github.com/ibus/ibus/blob/86963f2f94d1e4fc213b01c2bc2ba9dcf4b22219/client/gtk2/ibusimcontext.c#L610
+    const char* env = PR_GetEnv("IBUS_ENABLE_SYNC_MODE");
+
+    // See _get_boolean_env() in client/gtk2/ibusimcontext.c
+    // https://github.com/ibus/ibus/blob/86963f2f94d1e4fc213b01c2bc2ba9dcf4b22219/client/gtk2/ibusimcontext.c#L520-L537
+    if (!env) {
+        return false;
+    }
+    nsDependentCString envStr(env);
+    if (envStr.IsEmpty() ||
+        envStr.EqualsLiteral("0") ||
+        envStr.EqualsLiteral("false") ||
+        envStr.EqualsLiteral("False") ||
+        envStr.EqualsLiteral("FALSE")) {
+        return false;
+    }
+    return true;
+}
+
+static bool
+GetFcitxBoolEnv(const char* aEnv)
+{
+    // See fcitx_utils_get_boolean_env in src/lib/fcitx-utils/utils.c
+    // https://github.com/fcitx/fcitx/blob/0c87840dc7d9460c2cb5feaeefec299d0d3d62ec/src/lib/fcitx-utils/utils.c#L721-L736
+    const char* env = PR_GetEnv(aEnv);
+    if (!env) {
+        return false;
+    }
+    nsDependentCString envStr(env);
+    if (envStr.IsEmpty() ||
+        envStr.EqualsLiteral("0") ||
+        envStr.EqualsLiteral("false")) {
+        return false;
+    }
+    return true;
+}
+
+static bool
+IsFcitxInSyncMode()
+{
+    // See fcitx_im_context_class_init() in src/frontend/gtk2/fcitximcontext.c
+    // https://github.com/fcitx/fcitx/blob/78b98d9230dc9630e99d52e3172bdf440ffd08c4/src/frontend/gtk2/fcitximcontext.c#L395-L398
+    return GetFcitxBoolEnv("IBUS_ENABLE_SYNC_MODE") ||
+           GetFcitxBoolEnv("FCITX_ENABLE_SYNC_MODE");
+}
+
 void
 IMContextWrapper::Init()
 {
-    MOZ_LOG(gGtkIMLog, LogLevel::Info,
-        ("0x%p Init(), mOwnerWindow=0x%p",
-         this, mOwnerWindow));
-
     MozContainer* container = mOwnerWindow->GetMozContainer();
     NS_PRECONDITION(container, "container is null");
     GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(container));
 
     // NOTE: gtk_im_*_new() abort (kill) the whole process when it fails.
     //       So, we don't need to check the result.
 
     // Normal context.
@@ -219,16 +334,41 @@ IMContextWrapper::Init()
     g_signal_connect(mContext, "delete_surrounding",
         G_CALLBACK(IMContextWrapper::OnDeleteSurroundingCallback), this);
     g_signal_connect(mContext, "commit",
         G_CALLBACK(IMContextWrapper::OnCommitCompositionCallback), this);
     g_signal_connect(mContext, "preedit_start",
         G_CALLBACK(IMContextWrapper::OnStartCompositionCallback), this);
     g_signal_connect(mContext, "preedit_end",
         G_CALLBACK(IMContextWrapper::OnEndCompositionCallback), this);
+    nsDependentCString contextID;
+    const char* contextIDChar =
+        gtk_im_multicontext_get_context_id(GTK_IM_MULTICONTEXT(mContext));
+    if (contextIDChar) {
+        contextID.Rebind(contextIDChar);
+    }
+    if (contextID.EqualsLiteral("ibus")) {
+        mIMContextID = IMContextID::eIBus;
+        mIsIMInAsyncKeyHandlingMode = !IsIBusInSyncMode();
+    } else if (contextID.EqualsLiteral("fcitx")) {
+        mIMContextID = IMContextID::eFcitx;
+        mIsIMInAsyncKeyHandlingMode = !IsFcitxInSyncMode();
+    } else if (contextID.EqualsLiteral("uim")) {
+        mIMContextID = IMContextID::eUim;
+        mIsIMInAsyncKeyHandlingMode = false;
+    } else if (contextID.EqualsLiteral("scim")) {
+        mIMContextID = IMContextID::eScim;
+        mIsIMInAsyncKeyHandlingMode = false;
+    } else if (contextID.EqualsLiteral("iiim")) {
+        mIMContextID = IMContextID::eIIIMF;
+        mIsIMInAsyncKeyHandlingMode = false;
+    } else {
+        mIMContextID = IMContextID::eUnknown;
+        mIsIMInAsyncKeyHandlingMode = 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),
             this);
@@ -247,16 +387,23 @@ IMContextWrapper::Init()
         g_signal_connect(mSimpleContext, "preedit_end",
             G_CALLBACK(IMContextWrapper::OnEndCompositionCallback),
             this);
     }
 
     // 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",
+         this, mOwnerWindow, mContext, contextID.get(),
+         ToChar(mIsIMInAsyncKeyHandlingMode), mSimpleContext, mDummyContext));
 }
 
 IMContextWrapper::~IMContextWrapper()
 {
     if (this == sLastFocusedContext) {
         sLastFocusedContext = nullptr;
     }
     MOZ_LOG(gGtkIMLog, LogLevel::Info,
@@ -493,26 +640,33 @@ IMContextWrapper::OnKeyEvent(nsWindow* a
 
     if (!mInputContext.mIMEState.MaybeEditable() ||
         MOZ_UNLIKELY(IsDestroyed())) {
         return false;
     }
 
     MOZ_LOG(gGtkIMLog, LogLevel::Info,
         ("0x%p OnKeyEvent(aCaller=0x%p, "
-         "aEvent(0x%p): { type=%s, keyval=%s, unicode=0x%X }, "
-         "aKeyboardEventWasDispatched=%s), "
-         "mMaybeInDeadKeySequence=%s, "
-         "mCompositionState=%s, current context=0x%p, active context=0x%p, ",
+         "aEvent(0x%p): { type=%s, keyval=%s, unicode=0x%X, state=%s, "
+         "time=%u, hardware_keycode=%u, group=%u }, "
+         "aKeyboardEventWasDispatched=%s)",
          this, aCaller, aEvent, GetEventType(aEvent),
          gdk_keyval_name(aEvent->keyval),
          gdk_keyval_to_unicode(aEvent->keyval),
-         ToChar(aKeyboardEventWasDispatched),
-         ToChar(mMaybeInDeadKeySequence),
-         GetCompositionStateName(), GetCurrentContext(), GetActiveContext()));
+         GetEventStateName(aEvent->state, mIMContextID).get(),
+         aEvent->time, aEvent->hardware_keycode, aEvent->group,
+         ToChar(aKeyboardEventWasDispatched)));
+    MOZ_LOG(gGtkIMLog, LogLevel::Info,
+        ("0x%p   OnKeyEvent(), mMaybeInDeadKeySequence=%s, "
+         "mCompositionState=%s, current context=%p, active context=%p, "
+         "mIMContextID=%s, mIsIMInAsyncKeyHandlingMode=%s",
+         this, ToChar(mMaybeInDeadKeySequence),
+         GetCompositionStateName(), GetCurrentContext(), GetActiveContext(),
+         GetIMContextIDName(mIMContextID),
+         ToChar(mIsIMInAsyncKeyHandlingMode)));
 
     if (aCaller != mLastFocusedWindow) {
         MOZ_LOG(gGtkIMLog, LogLevel::Error,
             ("0x%p   OnKeyEvent(), FAILED, the caller isn't focused "
              "window, mLastFocusedWindow=0x%p",
              this, mLastFocusedWindow));
         return false;
     }
@@ -533,16 +687,88 @@ IMContextWrapper::OnKeyEvent(nsWindow* a
     }
 
     // Let's support dead key event even if active keyboard layout also
     // supports complicated composition like CJK IME.
     bool isDeadKey =
         KeymapWrapper::ComputeDOMKeyNameIndex(aEvent) == KEY_NAME_INDEX_Dead;
     mMaybeInDeadKeySequence |= isDeadKey;
 
+    // If current context is mSimpleContext, both ibus and fcitx handles key
+    // events synchronously.  So, only when current context is mContext which
+    // is GtkIMMulticontext, the key event may be handled by IME asynchronously.
+    bool maybeHandledAsynchronously =
+        mIsIMInAsyncKeyHandlingMode && currentContext == mContext;
+
+    // If IM is ibus or fcitx and it handles key events asynchronously,
+    // they mark aEvent->state as "handled by me" when they post key event
+    // to another process.  Unfortunately, we need to check this hacky
+    // flag because it's difficult to store all pending key events by
+    // an array or a hashtable.
+    if (maybeHandledAsynchronously) {
+        switch (mIMContextID) {
+            case IMContextID::eIBus:
+                // ibus won't send back key press events in a dead key sequcne.
+                if (mMaybeInDeadKeySequence && aEvent->type == GDK_KEY_PRESS) {
+                    maybeHandledAsynchronously = false;
+                    break;
+                }
+                // ibus handles key events synchronously if focused editor is
+                // <input type="password"> or |ime-mode: disabled;|.
+                if (mInputContext.mIMEState.mEnabled == IMEState::PASSWORD) {
+                    maybeHandledAsynchronously = false;
+                    break;
+                }
+                // See src/ibustypes.h
+                static const guint IBUS_IGNORED_MASK = 1 << 25;
+                // If IBUS_IGNORED_MASK was set to aEvent->state, the event
+                // has already been handled by another process and it wasn't
+                // used by IME.
+                if (aEvent->state & IBUS_IGNORED_MASK) {
+                    MOZ_LOG(gGtkIMLog, LogLevel::Info,
+                        ("0x%p   OnKeyEvent(), aEvent->state has "
+                         "IBUS_IGNORED_MASK, so, it won't be handled "
+                         "asynchronously anymore",
+                         this));
+                    maybeHandledAsynchronously = false;
+                    break;
+                }
+                break;
+            case IMContextID::eFcitx:
+                // fcitx won't send back key press events in a dead key sequcne.
+                if (mMaybeInDeadKeySequence && aEvent->type == GDK_KEY_PRESS) {
+                    maybeHandledAsynchronously = false;
+                    break;
+                }
+
+                // fcitx handles key events asynchronously even if focused
+                // editor cannot use IME actually.
+
+                // See src/lib/fcitx-utils/keysym.h
+                static const guint FcitxKeyState_IgnoredMask = 1 << 25;
+                // If FcitxKeyState_IgnoredMask was set to aEvent->state,
+                // the event has already been handled by another process and
+                // it wasn't used by IME.
+                if (aEvent->state & FcitxKeyState_IgnoredMask) {
+                    MOZ_LOG(gGtkIMLog, LogLevel::Info,
+                        ("0x%p   OnKeyEvent(), aEvent->state has "
+                         "FcitxKeyState_IgnoredMask, so, it won't be handled "
+                         "asynchronously anymore",
+                         this));
+                    maybeHandledAsynchronously = false;
+                    break;
+                }
+                break;
+            default:
+                MOZ_ASSERT_UNREACHABLE("IME may handle key event "
+                    "asyncrhonously, but not yet confirmed if it comes agian "
+                    "actually");
+        }
+    }
+
     mKeyboardEventWasDispatched = aKeyboardEventWasDispatched;
     mFallbackToKeyEvent = false;
     mProcessingKeyEvent = aEvent;
     gboolean isFiltered =
         gtk_im_context_filter_keypress(currentContext, aEvent);
 
     // The caller of this shouldn't handle aEvent anymore if we've dispatched
     // composition events or modified content with other events.
@@ -564,19 +790,19 @@ IMContextWrapper::OnKeyEvent(nsWindow* a
         DispatchCompositionCommitEvent(currentContext, &EmptyString());
         mProcessingKeyEvent = aEvent;
         // In this case, even though we handle the keyboard event here,
         // but we should dispatch keydown event as
         filterThisEvent = false;
     }
 
     // If IME handled the key event but we've not dispatched eKeyDown nor
-    // eKeyUp event yet, we need to dispatch here because the caller won't
-    // do it.
-    if (filterThisEvent) {
+    // eKeyUp event yet, we need to dispatch here unless the key event is
+    // now being handled by other IME process.
+    if (filterThisEvent && !maybeHandledAsynchronously) {
         MaybeDispatchKeyEventAsProcessedByIME();
         // Be aware, the widget might have been gone here.
     }
 
     mProcessingKeyEvent = nullptr;
 
     if (aEvent->type == GDK_KEY_PRESS && !filterThisEvent) {
         // If the key event hasn't been handled by active IME nor keyboard
@@ -584,21 +810,22 @@ IMContextWrapper::OnKeyEvent(nsWindow* a
         // ended.  Note that we should not reset it when the key event is
         // GDK_KEY_RELEASE since it may not be filtered by active keyboard
         // layout even in composition.
         mMaybeInDeadKeySequence = false;
     }
 
     MOZ_LOG(gGtkIMLog, LogLevel::Debug,
         ("0x%p   OnKeyEvent(), succeeded, filterThisEvent=%s "
-         "(isFiltered=%s, mFallbackToKeyEvent=%s), mCompositionState=%s, "
+         "(isFiltered=%s, mFallbackToKeyEvent=%s, "
+         "maybeHandledAsynchronously=%s), mCompositionState=%s, "
          "mMaybeInDeadKeySequence=%s",
          this, ToChar(filterThisEvent), ToChar(isFiltered),
-         ToChar(mFallbackToKeyEvent), GetCompositionStateName(),
-         ToChar(mMaybeInDeadKeySequence)));
+         ToChar(mFallbackToKeyEvent), ToChar(maybeHandledAsynchronously),
+         GetCompositionStateName(), ToChar(mMaybeInDeadKeySequence)));
 
     return filterThisEvent;
 }
 
 void
 IMContextWrapper::OnFocusChangeInGecko(bool aFocus)
 {
     MOZ_LOG(gGtkIMLog, LogLevel::Info,
--- a/widget/gtk/IMContextWrapper.h
+++ b/widget/gtk/IMContextWrapper.h
@@ -92,16 +92,47 @@ public:
                          const InputContext* aContext,
                          const InputContextAction* aAction);
     InputContext GetInputContext();
     void OnUpdateComposition();
     void OnLayoutChange();
 
     TextEventDispatcher* GetTextEventDispatcher();
 
+    // TODO: Typically, new IM comes every several years.  And now, our code
+    //       becomes really IM behavior dependent.  So, perhaps, we need prefs
+    //       to control related flags for IM developers.
+    enum class IMContextID : uint8_t
+    {
+        eFcitx,
+        eIBus,
+        eIIIMF,
+        eScim,
+        eUim,
+        eUnknown,
+    };
+
+    static const char* GetIMContextIDName(IMContextID aIMContextID)
+    {
+        switch (aIMContextID) {
+            case IMContextID::eFcitx:
+                return "eFcitx";
+            case IMContextID::eIBus:
+                return "eIBus";
+            case IMContextID::eIIIMF:
+                return "eIIIMF";
+            case IMContextID::eScim:
+                return "eScim";
+            case IMContextID::eUim:
+                return "eUim";
+            default:
+                return "eUnknown";
+        }
+    }
+
 protected:
     ~IMContextWrapper();
 
     // Owner of an instance of this class. This should be top level window.
     // The owner window must release the contexts when it's destroyed because
     // the IME contexts need the native window.  If OnDestroyWindow() is called
     // with the owner window, it'll release IME contexts.  Otherwise, it'll
     // just clean up any existing composition if it's related to the destroying
@@ -170,17 +201,18 @@ protected:
             mLength = UINT32_MAX;
         }
     };
 
     // current target offset and length of IME composition
     Range mCompositionTargetRange;
 
     // mCompositionState indicates current status of composition.
-    enum eCompositionState {
+    enum eCompositionState : uint8_t
+    {
         eCompositionState_NotComposing,
         eCompositionState_CompositionStartDispatched,
         eCompositionState_CompositionChangeEventDispatched
     };
     eCompositionState mCompositionState;
 
     bool IsComposing() const
     {
@@ -222,16 +254,20 @@ protected:
                 return "CompositionStartDispatched";
             case eCompositionState_CompositionChangeEventDispatched:
                 return "CompositionChangeEventDispatched";
             default:
                 return "InvaildState";
         }
     }
 
+    // mIMContextID indicates the ID of mContext.  This is actually indicates
+    // IM which user selected.
+    IMContextID mIMContextID;
+
     struct Selection final
     {
         nsString mString;
         uint32_t mOffset;
         WritingMode mWritingMode;
 
         Selection()
             : mOffset(UINT32_MAX)
@@ -316,16 +352,20 @@ protected:
     // sequence.  For example, when you press dead key grave with ibus Spanish
     // keyboard layout, it just consumes the key event when we call
     // gtk_im_context_filter_keypress().  Then, pressing "Escape" key cancels
     // the dead key sequence but we don't receive any signal and it's consumed
     // 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;
 
     // 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.
--- a/widget/gtk/mozgtk/mozgtk.c
+++ b/widget/gtk/mozgtk/mozgtk.c
@@ -261,16 +261,17 @@ STUB(gtk_im_context_filter_keypress)
 STUB(gtk_im_context_focus_in)
 STUB(gtk_im_context_focus_out)
 STUB(gtk_im_context_get_preedit_string)
 STUB(gtk_im_context_reset)
 STUB(gtk_im_context_set_client_window)
 STUB(gtk_im_context_set_cursor_location)
 STUB(gtk_im_context_set_surrounding)
 STUB(gtk_im_context_simple_new)
+STUB(gtk_im_multicontext_get_context_id)
 STUB(gtk_im_multicontext_get_type)
 STUB(gtk_im_multicontext_new)
 STUB(gtk_info_bar_get_type)
 STUB(gtk_info_bar_get_content_area)
 STUB(gtk_info_bar_new)
 STUB(gtk_init)
 STUB(gtk_invisible_new)
 STUB(gtk_key_snooper_install)