# Global Metrics

path: .metrics.cyclomatic.average
old: 1.0
new: 5.790123456790123
path: .metrics.cyclomatic.sum
old: 2.0
new: 469.0
path: .metrics.loc.blank
old: 10.0
new: 336.0
path: .metrics.loc.ploc
old: 20.0
new: 2341.0
path: .metrics.loc.sloc
old: 114.0
new: 3169.0
path: .metrics.loc.lloc
old: 0.0
new: 1041.0
path: .metrics.loc.cloc
old: 84.0
new: 492.0
path: .metrics.nexits.average
old: null
new: 2.175675675675676
path: .metrics.nexits.sum
old: 0.0
new: 161.0
path: .metrics.cognitive.sum
old: 0.0
new: 456.0
path: .metrics.cognitive.average
old: null
new: 6.162162162162162
path: .metrics.mi.mi_sei
old: 68.42088502904346
new: -182.83461646231208
path: .metrics.mi.mi_visual_studio
old: 38.65524805196772
new: 0.0
path: .metrics.mi.mi_original
old: 66.1004741688648
new: -127.225614846108
path: .metrics.nargs.sum
old: 0.0
new: 82.0
path: .metrics.nargs.average
old: null
new: 1.108108108108108
path: .metrics.nom.total
old: 0.0
new: 74.0
path: .metrics.nom.functions
old: 0.0
new: 74.0
path: .metrics.halstead.length
old: 45.0
new: 9768.0
path: .metrics.halstead.time
old: 42.02882292327726
new: 424880.0630205198
path: .metrics.halstead.purity_ratio
old: 2.0126316673397455
new: 1.0548977933865114
path: .metrics.halstead.effort
old: 756.5188126189908
new: 7647841.134369357
path: .metrics.halstead.vocabulary
old: 24.0
new: 1051.0
path: .metrics.halstead.n2
old: 18.0
new: 1011.0
path: .metrics.halstead.volume
old: 206.32331253245204
new: 98046.75864630257
path: .metrics.halstead.level
old: 0.27272727272727276
new: 0.012820187674359624
path: .metrics.halstead.difficulty
old: 3.6666666666666665
new: 78.00197823936696
path: .metrics.halstead.estimated_program_length
old: 90.56842503028857
new: 10304.241645799442
path: .metrics.halstead.N1
old: 23.0
new: 5825.0
path: .metrics.halstead.N2
old: 22.0
new: 3943.0
path: .metrics.halstead.bugs
old: 0.027675272138737952
new: 12.939117317080182
path: .metrics.halstead.n1
old: 6.0
new: 40.0

# Spaces Data

## Minimal test - lines (24, 3169)

path: .spaces[0].metrics.halstead.time
old: 5.765719364395496
new: 428092.1038271773
path: .spaces[0].metrics.halstead.n2
old: 8.0
new: 996.0
path: .spaces[0].metrics.halstead.N2
old: 8.0
new: 3928.0
path: .spaces[0].metrics.halstead.purity_ratio
old: 1.4377443751081735
new: 1.0389663742576956
path: .spaces[0].metrics.halstead.vocabulary
old: 11.0
new: 1036.0
path: .spaces[0].metrics.halstead.effort
old: 103.78294855911892
new: 7705657.868889192
path: .spaces[0].metrics.halstead.N1
old: 12.0
new: 5825.0
path: .spaces[0].metrics.halstead.difficulty
old: 1.5
new: 78.87550200803213
path: .spaces[0].metrics.halstead.estimated_program_length
old: 28.75488750216347
new: 10133.039048135304
path: .spaces[0].metrics.halstead.length
old: 20.0
new: 9753.0
path: .spaces[0].metrics.halstead.volume
old: 69.18863237274594
new: 97693.93122980696
path: .spaces[0].metrics.halstead.level
old: 0.6666666666666666
new: 0.012678207739307536
path: .spaces[0].metrics.halstead.n1
old: 3.0
new: 40.0
path: .spaces[0].metrics.halstead.bugs
old: 0.007361439526411013
new: 13.004247512072029
path: .spaces[0].metrics.mi.mi_sei
old: 102.49357778835672
new: -182.46962644193667
path: .spaces[0].metrics.mi.mi_original
old: 90.68544299630246
new: -126.85886329717444
path: .spaces[0].metrics.mi.mi_visual_studio
old: 53.03242280485525
new: 0.0
path: .spaces[0].metrics.nargs.sum
old: 0.0
new: 82.0
path: .spaces[0].metrics.nargs.average
old: null
new: 1.108108108108108
path: .spaces[0].metrics.nexits.average
old: null
new: 2.175675675675676
path: .spaces[0].metrics.nexits.sum
old: 0.0
new: 161.0
path: .spaces[0].metrics.cyclomatic.average
old: 1.0
new: 5.85
path: .spaces[0].metrics.cyclomatic.sum
old: 1.0
new: 468.0
path: .spaces[0].metrics.loc.cloc
old: 23.0
new: 486.0
path: .spaces[0].metrics.loc.lloc
old: 0.0
new: 1041.0
path: .spaces[0].metrics.loc.sloc
old: 36.0
new: 3146.0
path: .spaces[0].metrics.loc.blank
old: 5.0
new: 334.0
path: .spaces[0].metrics.loc.ploc
old: 8.0
new: 2326.0
path: .spaces[0].metrics.nom.total
old: 0.0
new: 74.0
path: .spaces[0].metrics.nom.functions
old: 0.0
new: 74.0
path: .spaces[0].metrics.cognitive.average
old: null
new: 6.162162162162162
path: .spaces[0].metrics.cognitive.sum
old: 0.0
new: 456.0

### Code

    namespace mozilla {
    namespace widget {

    LazyLogModule gGtkIMLog("nsGtkIMModuleWidgets");

    static inline const char* ToChar(bool aBool) {
      return aBool ? "true" : "false";
    }

    static const char* GetEventType(GdkEventKey* aKeyEvent) {
      switch (aKeyEvent->type) {
        case GDK_KEY_PRESS:
          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::Unknown) {
        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::IBus:
            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::Fcitx:
          case IMContextWrapper::IMContextID::Fcitx5:
            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;
        }
        if (aWritingMode.IsVerticalLR()) {
          AssignLiteral("Vertical (LTR)");
          return;
        }
        AssignLiteral("Vertical (RTL)");
      }
      virtual ~GetWritingModeName() = default;
    };

    class GetTextRangeStyleText final : public nsAutoCString {
     public:
      explicit GetTextRangeStyleText(const TextRangeStyle& aStyle) {
        if (!aStyle.IsDefined()) {
          AssignLiteral("{ IsDefined()=false }");
          return;
        }

        if (aStyle.IsLineStyleDefined()) {
          AppendLiteral("{ mLineStyle=");
          AppendLineStyle(aStyle.mLineStyle);
          if (aStyle.IsUnderlineColorDefined()) {
            AppendLiteral(", mUnderlineColor=");
            AppendColor(aStyle.mUnderlineColor);
          } else {
            AppendLiteral(", IsUnderlineColorDefined=false");
          }
        } else {
          AppendLiteral("{ IsLineStyleDefined()=false");
        }

        if (aStyle.IsForegroundColorDefined()) {
          AppendLiteral(", mForegroundColor=");
          AppendColor(aStyle.mForegroundColor);
        } else {
          AppendLiteral(", IsForegroundColorDefined()=false");
        }

        if (aStyle.IsBackgroundColorDefined()) {
          AppendLiteral(", mBackgroundColor=");
          AppendColor(aStyle.mBackgroundColor);
        } else {
          AppendLiteral(", IsBackgroundColorDefined()=false");
        }

        AppendLiteral(" }");
      }
      void AppendLineStyle(TextRangeStyle::LineStyle aLineStyle) {
        switch (aLineStyle) {
          case TextRangeStyle::LineStyle::None:
            AppendLiteral("LineStyle::None");
            break;
          case TextRangeStyle::LineStyle::Solid:
            AppendLiteral("LineStyle::Solid");
            break;
          case TextRangeStyle::LineStyle::Dotted:
            AppendLiteral("LineStyle::Dotted");
            break;
          case TextRangeStyle::LineStyle::Dashed:
            AppendLiteral("LineStyle::Dashed");
            break;
          case TextRangeStyle::LineStyle::Double:
            AppendLiteral("LineStyle::Double");
            break;
          case TextRangeStyle::LineStyle::Wavy:
            AppendLiteral("LineStyle::Wavy");
            break;
          default:
            AppendPrintf("Invalid(0x%02X)",
                         static_cast(aLineStyle));
            break;
        }
      }
      void AppendColor(nscolor aColor) {
        AppendPrintf("{ R=0x%02X, G=0x%02X, B=0x%02X, A=0x%02X }", NS_GET_R(aColor),
                     NS_GET_G(aColor), NS_GET_B(aColor), NS_GET_A(aColor));
      }
      virtual ~GetTextRangeStyleText() = default;
    };

    const static bool kUseSimpleContextDefault = false;

    /******************************************************************************
     * SelectionStyleProvider
     *
     * IME (e.g., fcitx, ~4.2.8.3) may look up selection colors of widget, which
     * is related to the window associated with the IM context, to support any
     * colored widgets.  Our editor (like ) is rendered as
     * native GtkTextView as far as possible by default and if editor color is
     * changed by web apps, nsTextFrame may swap background color of foreground
     * color of composition string for making composition string is always
     * visually distinct in normal text.
     *
     * So, we would like IME to set style of composition string to good colors
     * in GtkTextView.  Therefore, this class overwrites selection colors of
     * our widget with selection colors of GtkTextView so that it's possible IME
     * to refer selection colors of GtkTextView via our widget.
     ******************************************************************************/

    class SelectionStyleProvider final {
     public:
      static SelectionStyleProvider* GetInstance() {
        if (sHasShutDown) {
          return nullptr;
        }
        if (!sInstance) {
          sInstance = new SelectionStyleProvider();
        }
        return sInstance;
      }

      static void Shutdown() {
        if (sInstance) {
          g_object_unref(sInstance->mProvider);
        }
        delete sInstance;
        sInstance = nullptr;
        sHasShutDown = true;
      }

      // aGDKWindow is a GTK window which will be associated with an IM context.
      void AttachTo(GdkWindow* aGDKWindow) {
        GtkWidget* widget = nullptr;
        // gdk_window_get_user_data() typically returns pointer to widget that
        // window belongs to.  If it's widget, fcitx retrieves selection colors
        // of them.  So, we need to overwrite its style.
        gdk_window_get_user_data(aGDKWindow, (gpointer*)&widget);
        if (GTK_IS_WIDGET(widget)) {
          gtk_style_context_add_provider(gtk_widget_get_style_context(widget),
                                         GTK_STYLE_PROVIDER(mProvider),
                                         GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
        }
      }

      void OnThemeChanged() {
        // fcitx refers GtkStyle::text[GTK_STATE_SELECTED] and
        // GtkStyle::bg[GTK_STATE_SELECTED] (although pair of text and *base*
        // or *fg* and bg is correct).  gtk_style_update_from_context() will
        // set these colors using the widget's GtkStyleContext and so the
        // colors can be controlled by a ":selected" CSS rule.
        nsAutoCString style(":selected{");
        // FYI: LookAndFeel always returns selection colors of GtkTextView.
        nscolor selectionForegroundColor;
        if (NS_SUCCEEDED(
                LookAndFeel::GetColor(LookAndFeel::ColorID::TextSelectForeground,
                                      &selectionForegroundColor))) {
          double alpha =
              static_cast(NS_GET_A(selectionForegroundColor)) / 0xFF;
          style.AppendPrintf("color:rgba(%u,%u,%u,",
                             NS_GET_R(selectionForegroundColor),
                             NS_GET_G(selectionForegroundColor),
                             NS_GET_B(selectionForegroundColor));
          // We can't use AppendPrintf here, because it does locale-specific
          // formatting of floating-point values.
          style.AppendFloat(alpha);
          style.AppendPrintf(");");
        }
        nscolor selectionBackgroundColor;
        if (NS_SUCCEEDED(
                LookAndFeel::GetColor(LookAndFeel::ColorID::TextSelectBackground,
                                      &selectionBackgroundColor))) {
          double alpha =
              static_cast(NS_GET_A(selectionBackgroundColor)) / 0xFF;
          style.AppendPrintf("background-color:rgba(%u,%u,%u,",
                             NS_GET_R(selectionBackgroundColor),
                             NS_GET_G(selectionBackgroundColor),
                             NS_GET_B(selectionBackgroundColor));
          style.AppendFloat(alpha);
          style.AppendPrintf(");");
        }
        style.AppendLiteral("}");
        gtk_css_provider_load_from_data(mProvider, style.get(), -1, nullptr);
      }

     private:
      static SelectionStyleProvider* sInstance;
      static bool sHasShutDown;
      GtkCssProvider* const mProvider;

      SelectionStyleProvider() : mProvider(gtk_css_provider_new()) {
        OnThemeChanged();
      }
    };

    SelectionStyleProvider* SelectionStyleProvider::sInstance = nullptr;
    bool SelectionStyleProvider::sHasShutDown = false;

    /******************************************************************************
     * IMContextWrapper
     ******************************************************************************/

    IMContextWrapper* IMContextWrapper::sLastFocusedContext = nullptr;
    guint16 IMContextWrapper::sWaitingSynthesizedKeyPressHardwareKeyCode = 0;
    bool IMContextWrapper::sUseSimpleContext;

    NS_IMPL_ISUPPORTS(IMContextWrapper, TextEventDispatcherListener,
                      nsISupportsWeakReference)

    IMContextWrapper::IMContextWrapper(nsWindow* aOwnerWindow)
        : mOwnerWindow(aOwnerWindow),
          mLastFocusedWindow(nullptr),
          mContext(nullptr),
          mSimpleContext(nullptr),
          mDummyContext(nullptr),
          mComposingContext(nullptr),
          mCompositionStart(UINT32_MAX),
          mProcessingKeyEvent(nullptr),
          mCompositionState(eCompositionState_NotComposing),
          mIMContextID(IMContextID::Unknown),
          mIsIMFocused(false),
          mFallbackToKeyEvent(false),
          mKeyboardEventWasDispatched(false),
          mKeyboardEventWasConsumed(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");
    }

    nsDependentCSubstring IMContextWrapper::GetIMName() const {
      const char* contextIDChar =
          gtk_im_multicontext_get_context_id(GTK_IM_MULTICONTEXT(mContext));
      if (!contextIDChar) {
        return nsDependentCSubstring();
      }

      nsDependentCSubstring im(contextIDChar, strlen(contextIDChar));

      // If the context is XIM, actual engine must be specified with
      // |XMODIFIERS=@im=foo|.
      const char* xmodifiersChar = PR_GetEnv("XMODIFIERS");
      if (!xmodifiersChar || !im.EqualsLiteral("xim")) {
        return im;
      }

      nsDependentCString xmodifiers(xmodifiersChar);
      int32_t atIMValueStart = xmodifiers.Find("@im=") + 4;
      if (atIMValueStart < 4 ||
          xmodifiers.Length() <= static_cast(atIMValueStart)) {
        return im;
      }

      int32_t atIMValueEnd = xmodifiers.Find("@", false, atIMValueStart);
      if (atIMValueEnd > atIMValueStart) {
        return nsDependentCSubstring(xmodifiersChar + atIMValueStart,
                                     atIMValueEnd - atIMValueStart);
      }

      if (atIMValueEnd == kNotFound) {
        return nsDependentCSubstring(xmodifiersChar + atIMValueStart,
                                     strlen(xmodifiersChar) - atIMValueStart);
      }

      return im;
    }

    void IMContextWrapper::Init() {
      MozContainer* container = mOwnerWindow->GetMozContainer();
      MOZ_ASSERT(container, "container is null");
      GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(container));

      // Overwrite selection colors of the window before associating the window
      // with IM context since IME may look up selection colors via IM context
      // to support any colored widgets.
      SelectionStyleProvider::GetInstance()->AttachTo(gdkWindow);

      // NOTE: gtk_im_*_new() abort (kill) the whole process when it fails.
      //       So, we don't need to check the result.

      // Normal context.
      mContext = gtk_im_multicontext_new();
      gtk_im_context_set_client_window(mContext, gdkWindow);
      g_signal_connect(mContext, "preedit_changed",
                       G_CALLBACK(IMContextWrapper::OnChangeCompositionCallback),
                       this);
      g_signal_connect(mContext, "retrieve_surrounding",
                       G_CALLBACK(IMContextWrapper::OnRetrieveSurroundingCallback),
                       this);
      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);
      nsDependentCSubstring im = GetIMName();
      if (im.EqualsLiteral("ibus")) {
        mIMContextID = IMContextID::IBus;
        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 (im.EqualsLiteral("fcitx")) {
        mIMContextID = IMContextID::Fcitx;
        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 (im.EqualsLiteral("fcitx5")) {
        mIMContextID = IMContextID::Fcitx5;
        mIsIMInAsyncKeyHandlingMode = true;  // does not have sync mode.
        mIsKeySnooped = false;               // never use key snooper.
      } else if (im.EqualsLiteral("uim")) {
        mIMContextID = IMContextID::Uim;
        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 (im.EqualsLiteral("scim")) {
        mIMContextID = IMContextID::Scim;
        mIsIMInAsyncKeyHandlingMode = false;
        mIsKeySnooped = false;
      } else if (im.EqualsLiteral("iiim")) {
        mIMContextID = IMContextID::IIIMF;
        mIsIMInAsyncKeyHandlingMode = false;
        mIsKeySnooped = false;
      } else if (im.EqualsLiteral("wayland")) {
        mIMContextID = IMContextID::Wayland;
        mIsIMInAsyncKeyHandlingMode = false;
        mIsKeySnooped = true;
      } else {
        mIMContextID = IMContextID::Unknown;
        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),
                         this);
        g_signal_connect(
            mSimpleContext, "retrieve_surrounding",
            G_CALLBACK(&IMContextWrapper::OnRetrieveSurroundingCallback), this);
        g_signal_connect(mSimpleContext, "delete_surrounding",
                         G_CALLBACK(&IMContextWrapper::OnDeleteSurroundingCallback),
                         this);
        g_signal_connect(mSimpleContext, "commit",
                         G_CALLBACK(&IMContextWrapper::OnCommitCompositionCallback),
                         this);
        g_signal_connect(mSimpleContext, "preedit_start",
                         G_CALLBACK(IMContextWrapper::OnStartCompositionCallback),
                         this);
        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 (im=\"%s\"), "
               "mIsIMInAsyncKeyHandlingMode=%s, mIsKeySnooped=%s, "
               "mSimpleContext=%p, mDummyContext=%p, "
               "gtk_im_multicontext_get_context_id()=\"%s\", "
               "PR_GetEnv(\"XMODIFIERS\")=\"%s\"",
               this, mOwnerWindow, mContext, nsAutoCString(im).get(),
               ToChar(mIsIMInAsyncKeyHandlingMode), ToChar(mIsKeySnooped),
               mSimpleContext, mDummyContext,
               gtk_im_multicontext_get_context_id(GTK_IM_MULTICONTEXT(mContext)),
               PR_GetEnv("XMODIFIERS")));
    }

    /* static */
    void IMContextWrapper::Shutdown() { SelectionStyleProvider::Shutdown(); }

    IMContextWrapper::~IMContextWrapper() {
      if (this == sLastFocusedContext) {
        sLastFocusedContext = nullptr;
      }
      MOZ_LOG(gGtkIMLog, LogLevel::Info, ("0x%p ~IMContextWrapper()", this));
    }

    NS_IMETHODIMP
    IMContextWrapper::NotifyIME(TextEventDispatcher* aTextEventDispatcher,
                                const IMENotification& aNotification) {
      switch (aNotification.mMessage) {
        case REQUEST_TO_COMMIT_COMPOSITION:
        case REQUEST_TO_CANCEL_COMPOSITION: {
          nsWindow* window =
              static_cast(aTextEventDispatcher->GetWidget());
          return EndIMEComposition(window);
        }
        case NOTIFY_IME_OF_FOCUS:
          OnFocusChangeInGecko(true);
          return NS_OK;
        case NOTIFY_IME_OF_BLUR:
          OnFocusChangeInGecko(false);
          return NS_OK;
        case NOTIFY_IME_OF_POSITION_CHANGE:
          OnLayoutChange();
          return NS_OK;
        case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
          OnUpdateComposition();
          return NS_OK;
        case NOTIFY_IME_OF_SELECTION_CHANGE: {
          nsWindow* window =
              static_cast(aTextEventDispatcher->GetWidget());
          OnSelectionChange(window, aNotification);
          return NS_OK;
        }
        default:
          return NS_ERROR_NOT_IMPLEMENTED;
      }
    }

    NS_IMETHODIMP_(void)
    IMContextWrapper::OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) {
      // XXX When input transaction is being stolen by add-on, what should we do?
    }

    NS_IMETHODIMP_(void)
    IMContextWrapper::WillDispatchKeyboardEvent(
        TextEventDispatcher* aTextEventDispatcher,
        WidgetKeyboardEvent& aKeyboardEvent, uint32_t aIndexOfKeypress,
        void* aData) {
      KeymapWrapper::WillDispatchKeyboardEvent(aKeyboardEvent,
                                               static_cast(aData));
    }

    TextEventDispatcher* IMContextWrapper::GetTextEventDispatcher() {
      if (NS_WARN_IF(!mLastFocusedWindow)) {
        return nullptr;
      }
      TextEventDispatcher* dispatcher =
          mLastFocusedWindow->GetTextEventDispatcher();
      // nsIWidget::GetTextEventDispatcher() shouldn't return nullptr.
      MOZ_RELEASE_ASSERT(dispatcher);
      return dispatcher;
    }

    NS_IMETHODIMP_(IMENotificationRequests)
    IMContextWrapper::GetIMENotificationRequests() {
      IMENotificationRequests::Notifications notifications =
          IMENotificationRequests::NOTIFY_NOTHING;
      // If it's not enabled, we don't need position change notification.
      if (IsEnabled()) {
        notifications |= IMENotificationRequests::NOTIFY_POSITION_CHANGE;
      }
      return IMENotificationRequests(notifications);
    }

    void IMContextWrapper::OnDestroyWindow(nsWindow* aWindow) {
      MOZ_LOG(
          gGtkIMLog, LogLevel::Info,
          ("0x%p OnDestroyWindow(aWindow=0x%p), mLastFocusedWindow=0x%p, "
           "mOwnerWindow=0x%p, mLastFocusedModule=0x%p",
           this, aWindow, mLastFocusedWindow, mOwnerWindow, sLastFocusedContext));

      MOZ_ASSERT(aWindow, "aWindow must not be null");

      if (mLastFocusedWindow == aWindow) {
        EndIMEComposition(aWindow);
        if (mIsIMFocused) {
          Blur();
        }
        mLastFocusedWindow = nullptr;
      }

      if (mOwnerWindow != aWindow) {
        return;
      }

      if (sLastFocusedContext == this) {
        sLastFocusedContext = nullptr;
      }

      /**
       * NOTE:
       *   The given window is the owner of this, so, we must release the
       *   contexts now.  But that might be referred from other nsWindows
       *   (they are children of this.  But we don't know why there are the
       *   cases).  So, we need to clear the pointers that refers to contexts
       *   and this if the other referrers are still alive. See bug 349727.
       */
      if (mContext) {
        PrepareToDestroyContext(mContext);
        gtk_im_context_set_client_window(mContext, nullptr);
        g_object_unref(mContext);
        mContext = nullptr;
      }

      if (mSimpleContext) {
        gtk_im_context_set_client_window(mSimpleContext, nullptr);
        g_object_unref(mSimpleContext);
        mSimpleContext = nullptr;
      }

      if (mDummyContext) {
        // mContext and mDummyContext have the same slaveType and signal_data
        // so no need for another workaround_gtk_im_display_closed.
        gtk_im_context_set_client_window(mDummyContext, nullptr);
        g_object_unref(mDummyContext);
        mDummyContext = nullptr;
      }

      if (NS_WARN_IF(mComposingContext)) {
        g_object_unref(mComposingContext);
        mComposingContext = nullptr;
      }

      mOwnerWindow = nullptr;
      mLastFocusedWindow = nullptr;
      mInputContext.mIMEState.mEnabled = IMEEnabled::Disabled;
      mPostingKeyEvents.Clear();

      MOZ_LOG(gGtkIMLog, LogLevel::Debug,
              ("0x%p   OnDestroyWindow(), succeeded, Completely destroyed", this));
    }

    void IMContextWrapper::PrepareToDestroyContext(GtkIMContext* aContext) {
      if (mIMContextID == IMContextID::IIIMF) {
        // IIIM module registers handlers for the "closed" signal on the
        // display, but the signal handler is not disconnected when the module
        // is unloaded.  To prevent the module from being unloaded, use static
        // variable to hold reference of slave context class declared by IIIM.
        // Note that this does not grab any instance, it grabs the "class".
        static gpointer sGtkIIIMContextClass = nullptr;
        if (!sGtkIIIMContextClass) {
          // We retrieved slave context class with g_type_name() and actual
          // slave context instance when our widget was GTK2.  That must be
          // _GtkIMContext::priv::slave in GTK3.  However, _GtkIMContext::priv
          // is an opacity struct named _GtkIMMulticontextPrivate, i.e., it's
          // not exposed by GTK3.  Therefore, we cannot access the instance
          // safely.  So, we need to retrieve the slave context class with
          // g_type_from_name("GtkIMContextIIIM") directly (anyway, we needed
          // to compare the class name with "GtkIMContextIIIM").
          GType IIMContextType = g_type_from_name("GtkIMContextIIIM");
          if (IIMContextType) {
            sGtkIIIMContextClass = g_type_class_ref(IIMContextType);
            MOZ_LOG(gGtkIMLog, LogLevel::Info,
                    ("0x%p PrepareToDestroyContext(), added to reference to "
                     "GtkIMContextIIIM class to prevent it from being unloaded",
                     this));
          } else {
            MOZ_LOG(gGtkIMLog, LogLevel::Error,
                    ("0x%p PrepareToDestroyContext(), FAILED to prevent the "
                     "IIIM module from being uploaded",
                     this));
          }
        }
      }
    }

    void IMContextWrapper::OnFocusWindow(nsWindow* aWindow) {
      if (MOZ_UNLIKELY(IsDestroyed())) {
        return;
      }

      MOZ_LOG(gGtkIMLog, LogLevel::Info,
              ("0x%p OnFocusWindow(aWindow=0x%p), mLastFocusedWindow=0x%p", this,
               aWindow, mLastFocusedWindow));
      mLastFocusedWindow = aWindow;
      Focus();
    }

    void IMContextWrapper::OnBlurWindow(nsWindow* aWindow) {
      if (MOZ_UNLIKELY(IsDestroyed())) {
        return;
      }

      MOZ_LOG(gGtkIMLog, LogLevel::Info,
              ("0x%p OnBlurWindow(aWindow=0x%p), mLastFocusedWindow=0x%p, "
               "mIsIMFocused=%s",
               this, aWindow, mLastFocusedWindow, ToChar(mIsIMFocused)));

      if (!mIsIMFocused || mLastFocusedWindow != aWindow) {
        return;
      }

      Blur();
    }

    KeyHandlingState IMContextWrapper::OnKeyEvent(
        nsWindow* aCaller, GdkEventKey* aEvent,
        bool aKeyboardEventWasDispatched /* = false */) {
      MOZ_ASSERT(aEvent, "aEvent must be non-null");

      if (!mInputContext.mIMEState.IsEditable() || MOZ_UNLIKELY(IsDestroyed())) {
        return KeyHandlingState::eNotHandled;
      }

      MOZ_LOG(gGtkIMLog, LogLevel::Info, (">>>>>>>>>>>>>>>>"));
      MOZ_LOG(
          gGtkIMLog, LogLevel::Info,
          ("0x%p OnKeyEvent(aCaller=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),
           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(), ToString(mIMContextID).c_str(),
           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 KeyHandlingState::eNotHandled;
      }

      // Even if old IM context has composition, key event should be sent to
      // current context since the user expects so.
      GtkIMContext* currentContext = GetCurrentContext();
      if (MOZ_UNLIKELY(!currentContext)) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   OnKeyEvent(), FAILED, there are no context", this));
        return KeyHandlingState::eNotHandled;
      }

      if (mSetCursorPositionOnKeyEvent) {
        SetCursorPosition(currentContext);
        mSetCursorPositionOnKeyEvent = false;
      }

      // 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 probablyHandledAsynchronously =
          mIsIMInAsyncKeyHandlingMode && currentContext == mContext;

      // If we're not sure whether the event is handled asynchronously, this is
      // set to true.
      bool maybeHandledAsynchronously = false;

      // If aEvent is a synthesized event for async handling, this will be set to
      // true.
      bool isHandlingAsyncEvent = false;

      // If we've decided that the event won't be synthesized asyncrhonously
      // by IME, but actually IME did it, this is set to true.
      bool isUnexpectedAsyncEvent = false;

      // 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 (probablyHandledAsynchronously) {
        switch (mIMContextID) {
          case IMContextID::IBus: {
            // 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.
            isHandlingAsyncEvent = !!(aEvent->state & IBUS_IGNORED_MASK);
            if (!isHandlingAsyncEvent) {
              // On some environments, IBUS_IGNORED_MASK flag is not set as
              // expected.  In such case, we keep pusing all events into the queue.
              // I.e., that causes eating a lot of memory until it's blurred.
              // Therefore, we need to check whether there is same timestamp event
              // in the queue.  This redundant cost should be low because in most
              // causes, key events in the queue should be 2 or 4.
              isHandlingAsyncEvent =
                  mPostingKeyEvents.IndexOf(aEvent) != GdkEventKeyQueue::NoIndex();
              if (isHandlingAsyncEvent) {
                MOZ_LOG(gGtkIMLog, LogLevel::Info,
                        ("0x%p   OnKeyEvent(), aEvent->state does not have "
                         "IBUS_IGNORED_MASK but "
                         "same event in the queue.  So, assuming it's a "
                         "synthesized event",
                         this));
              }
            }

            // If it's a synthesized event, let's remove it from the posting
            // event queue first.  Otherwise the following blocks cannot use
            // `break`.
            if (isHandlingAsyncEvent) {
              MOZ_LOG(gGtkIMLog, LogLevel::Info,
                      ("0x%p   OnKeyEvent(), aEvent->state has IBUS_IGNORED_MASK "
                       "or aEvent is in the "
                       "posting event queue, so, it won't be handled "
                       "asynchronously anymore. Removing "
                       "the posted events from the queue",
                       this));
              probablyHandledAsynchronously = false;
              mPostingKeyEvents.RemoveEvent(aEvent);
            }

            // ibus won't send back key press events in a dead key sequcne.
            if (mMaybeInDeadKeySequence && aEvent->type == GDK_KEY_PRESS) {
              probablyHandledAsynchronously = false;
              if (isHandlingAsyncEvent) {
                isUnexpectedAsyncEvent = true;
                break;
              }
              // Some keyboard layouts which have dead keys may send
              // "empty" key event to make us call
              // gtk_im_context_filter_keypress() to commit composed
              // character during a GDK_KEY_PRESS event dispatching.
              if (!gdk_keyval_to_unicode(aEvent->keyval) &&
                  !aEvent->hardware_keycode) {
                isUnexpectedAsyncEvent = true;
                break;
              }
              break;
            }
            // ibus may handle key events synchronously if focused editor is
            //  or |ime-mode: disabled;|.  However, in
            // some environments, not so actually.  Therefore, we need to check
            // the result of gtk_im_context_filter_keypress() later.
            if (mInputContext.mIMEState.mEnabled == IMEEnabled::Password) {
              probablyHandledAsynchronously = false;
              maybeHandledAsynchronously = !isHandlingAsyncEvent;
              break;
            }
            break;
          }
          case IMContextID::Fcitx:
          case IMContextID::Fcitx5: {
            // 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.
            isHandlingAsyncEvent = !!(aEvent->state & FcitxKeyState_IgnoredMask);
            if (!isHandlingAsyncEvent) {
              // On some environments, FcitxKeyState_IgnoredMask flag *might* be not
              // set as expected. If there were such cases, we'd keep pusing all
              // events into the queue.  I.e., that would cause eating a lot of
              // memory until it'd be blurred.  Therefore, we should check whether
              // there is same timestamp event in the queue.  This redundant cost
              // should be low because in most causes, key events in the queue
              // should be 2 or 4.
              isHandlingAsyncEvent =
                  mPostingKeyEvents.IndexOf(aEvent) != GdkEventKeyQueue::NoIndex();
              if (isHandlingAsyncEvent) {
                MOZ_LOG(gGtkIMLog, LogLevel::Info,
                        ("0x%p   OnKeyEvent(), aEvent->state does not have "
                         "FcitxKeyState_IgnoredMask "
                         "but same event in the queue.  So, assuming it's a "
                         "synthesized event",
                         this));
              }
            }

            // fcitx won't send back key press events in a dead key sequcne.
            if (mMaybeInDeadKeySequence && aEvent->type == GDK_KEY_PRESS) {
              probablyHandledAsynchronously = false;
              if (isHandlingAsyncEvent) {
                isUnexpectedAsyncEvent = true;
                break;
              }
              // Some keyboard layouts which have dead keys may send
              // "empty" key event to make us call
              // gtk_im_context_filter_keypress() to commit composed
              // character during a GDK_KEY_PRESS event dispatching.
              if (!gdk_keyval_to_unicode(aEvent->keyval) &&
                  !aEvent->hardware_keycode) {
                isUnexpectedAsyncEvent = true;
                break;
              }
            }

            // fcitx handles key events asynchronously even if focused
            // editor cannot use IME actually.

            if (isHandlingAsyncEvent) {
              MOZ_LOG(gGtkIMLog, LogLevel::Info,
                      ("0x%p   OnKeyEvent(), aEvent->state has "
                       "FcitxKeyState_IgnoredMask or aEvent is in "
                       "the posting event queue, so, it won't be handled "
                       "asynchronously anymore. "
                       "Removing the posted events from the queue",
                       this));
              probablyHandledAsynchronously = false;
              mPostingKeyEvents.RemoveEvent(aEvent);
              break;
            }
            break;
          }
          default:
            MOZ_ASSERT_UNREACHABLE(
                "IME may handle key event "
                "asyncrhonously, but not yet confirmed if it comes agian "
                "actually");
        }
      }

      if (!isUnexpectedAsyncEvent) {
        mKeyboardEventWasDispatched = aKeyboardEventWasDispatched;
        mKeyboardEventWasConsumed = false;
      } else {
        // If we didn't expect this event, we've alreday dispatched eKeyDown
        // event or eKeyUp event for that.
        mKeyboardEventWasDispatched = true;
        // And in this case, we need to assume that another key event hasn't
        // been receivied and mKeyboardEventWasConsumed keeps storing the
        // dispatched eKeyDown or eKeyUp event's state.
      }
      mFallbackToKeyEvent = false;
      mProcessingKeyEvent = aEvent;
      gboolean isFiltered = gtk_im_context_filter_keypress(currentContext, aEvent);

      // If we're not sure whether the event is handled by IME asynchronously or
      // synchronously, we need to trust the result of
      // gtk_im_context_filter_keypress().  If it consumed and but did nothing,
      // we can assume that another event will be synthesized.
      if (!isHandlingAsyncEvent && maybeHandledAsynchronously) {
        probablyHandledAsynchronously |=
            isFiltered && !mFallbackToKeyEvent && !mKeyboardEventWasDispatched;
      }

      if (aEvent->type == GDK_KEY_PRESS) {
        if (isFiltered && probablyHandledAsynchronously) {
          sWaitingSynthesizedKeyPressHardwareKeyCode = aEvent->hardware_keycode;
        } else {
          sWaitingSynthesizedKeyPressHardwareKeyCode = 0;
        }
      }

      // The caller of this shouldn't handle aEvent anymore if we've dispatched
      // composition events or modified content with other events.
      bool filterThisEvent = isFiltered && !mFallbackToKeyEvent;

      if (IsComposingOnCurrentContext() && !isFiltered &&
          aEvent->type == GDK_KEY_PRESS && mDispatchedCompositionString.IsEmpty()) {
        // A Hangul input engine for SCIM doesn't emit preedit_end
        // signal even when composition string becomes empty.  On the
        // other hand, we should allow to make composition with empty
        // string for other languages because there *might* be such
        // IM.  For compromising this issue, we should dispatch
        // compositionend event, however, we don't need to reset IM
        // actually.
        // NOTE: Don't dispatch key events as "processed by IME" since
        // we need to dispatch keyboard events as IME wasn't handled it.
        mProcessingKeyEvent = nullptr;
        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 (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 (!probablyHandledAsynchronously) {
          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 {
          MOZ_LOG(gGtkIMLog, LogLevel::Info,
                  ("0x%p   OnKeyEvent(), putting aEvent into the queue...", this));
          mPostingKeyEvents.PutEvent(aEvent);
        }
      }

      mProcessingKeyEvent = nullptr;

      if (aEvent->type == GDK_KEY_PRESS && !filterThisEvent) {
        // If the key event hasn't been handled by active IME nor keyboard
        // layout, we can assume that the dead key sequence has been or was
        // 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, "
           "probablyHandledAsynchronously=%s, maybeHandledAsynchronously=%s), "
           "mPostingKeyEvents.Length()=%zu, mCompositionState=%s, "
           "mMaybeInDeadKeySequence=%s, mKeyboardEventWasDispatched=%s, "
           "mKeyboardEventWasConsumed=%s",
           this, ToChar(filterThisEvent), ToChar(isFiltered),
           ToChar(mFallbackToKeyEvent), ToChar(probablyHandledAsynchronously),
           ToChar(maybeHandledAsynchronously), mPostingKeyEvents.Length(),
           GetCompositionStateName(), ToChar(mMaybeInDeadKeySequence),
           ToChar(mKeyboardEventWasDispatched), ToChar(mKeyboardEventWasConsumed)));
      MOZ_LOG(gGtkIMLog, LogLevel::Info, ("<<<<<<<<<<<<<<<<\n\n"));

      if (filterThisEvent) {
        return KeyHandlingState::eHandled;
      }
      // If another call of this method has already dispatched eKeyDown event,
      // we should return KeyHandlingState::eNotHandledButEventDispatched because
      // the caller should've stopped handling the event if preceding eKeyDown
      // event was consumed.
      if (aKeyboardEventWasDispatched) {
        return KeyHandlingState::eNotHandledButEventDispatched;
      }
      if (!mKeyboardEventWasDispatched) {
        return KeyHandlingState::eNotHandled;
      }
      return mKeyboardEventWasConsumed
                 ? KeyHandlingState::eNotHandledButEventConsumed
                 : KeyHandlingState::eNotHandledButEventDispatched;
    }

    void IMContextWrapper::OnFocusChangeInGecko(bool aFocus) {
      MOZ_LOG(
          gGtkIMLog, LogLevel::Info,
          ("0x%p OnFocusChangeInGecko(aFocus=%s), "
           "mCompositionState=%s, mIsIMFocused=%s",
           this, ToChar(aFocus), GetCompositionStateName(), ToChar(mIsIMFocused)));

      // We shouldn't carry over the removed string to another editor.
      mSelectedStringRemovedByComposition.Truncate();
      mSelection.Clear();

      // When the focus changes, we need to inform IM about the new cursor
      // position. Chinese input methods generally rely on this because they
      // usually don't start composition until a character is picked.
      if (aFocus && EnsureToCacheSelection()) {
        SetCursorPosition(GetActiveContext());
      }
    }

    void IMContextWrapper::ResetIME() {
      MOZ_LOG(gGtkIMLog, LogLevel::Info,
              ("0x%p ResetIME(), mCompositionState=%s, mIsIMFocused=%s", this,
               GetCompositionStateName(), ToChar(mIsIMFocused)));

      GtkIMContext* activeContext = GetActiveContext();
      if (MOZ_UNLIKELY(!activeContext)) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   ResetIME(), FAILED, there are no context", this));
        return;
      }

      RefPtr kungFuDeathGrip(this);
      RefPtr lastFocusedWindow(mLastFocusedWindow);

      mPendingResettingIMContext = false;
      gtk_im_context_reset(activeContext);

      // The last focused window might have been destroyed by a DOM event handler
      // which was called by us during a call of gtk_im_context_reset().
      if (!lastFocusedWindow ||
          NS_WARN_IF(lastFocusedWindow != mLastFocusedWindow) ||
          lastFocusedWindow->Destroyed()) {
        return;
      }

      nsAutoString compositionString;
      GetCompositionString(activeContext, compositionString);

      MOZ_LOG(
          gGtkIMLog, LogLevel::Debug,
          ("0x%p   ResetIME() called gtk_im_context_reset(), "
           "activeContext=0x%p, mCompositionState=%s, compositionString=%s, "
           "mIsIMFocused=%s",
           this, activeContext, GetCompositionStateName(),
           NS_ConvertUTF16toUTF8(compositionString).get(), ToChar(mIsIMFocused)));

      // XXX IIIMF (ATOK X3 which is one of the Language Engine of it is still
      //     used in Japan!) sends only "preedit_changed" signal with empty
      //     composition string synchronously.  Therefore, if composition string
      //     is now empty string, we should assume that the IME won't send
      //     "commit" signal.
      if (IsComposing() && compositionString.IsEmpty()) {
        // WARNING: The widget might have been gone after this.
        DispatchCompositionCommitEvent(activeContext, &EmptyString());
      }
    }

    nsresult IMContextWrapper::EndIMEComposition(nsWindow* aCaller) {
      if (MOZ_UNLIKELY(IsDestroyed())) {
        return NS_OK;
      }

      MOZ_LOG(gGtkIMLog, LogLevel::Info,
              ("0x%p EndIMEComposition(aCaller=0x%p), "
               "mCompositionState=%s",
               this, aCaller, GetCompositionStateName()));

      if (aCaller != mLastFocusedWindow) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   EndIMEComposition(), FAILED, the caller isn't "
                 "focused window, mLastFocusedWindow=0x%p",
                 this, mLastFocusedWindow));
        return NS_OK;
      }

      if (!IsComposing()) {
        return NS_OK;
      }

      // Currently, GTK has API neither to commit nor to cancel composition
      // forcibly.  Therefore, TextComposition will recompute commit string for
      // the request even if native IME will cause unexpected commit string.
      // So, we don't need to emulate commit or cancel composition with
      // proper composition events.
      // XXX ResetIME() might not enough for finishing compositoin on some
      //     environments.  We should emulate focus change too because some IMEs
      //     may commit or cancel composition at blur.
      ResetIME();

      return NS_OK;
    }

    void IMContextWrapper::OnLayoutChange() {
      if (MOZ_UNLIKELY(IsDestroyed())) {
        return;
      }

      if (IsComposing()) {
        SetCursorPosition(GetActiveContext());
      } else {
        // If not composing, candidate window position is updated before key
        // down
        mSetCursorPositionOnKeyEvent = true;
      }
      mLayoutChanged = true;
    }

    void IMContextWrapper::OnUpdateComposition() {
      if (MOZ_UNLIKELY(IsDestroyed())) {
        return;
      }

      if (!IsComposing()) {
        // Composition has been committed.  So we need update selection for
        // caret later
        mSelection.Clear();
        EnsureToCacheSelection();
        mSetCursorPositionOnKeyEvent = true;
      }

      // If we've already set candidate window position, we don't need to update
      // the position with update composition notification.
      if (!mLayoutChanged) {
        SetCursorPosition(GetActiveContext());
      }
    }

    void IMContextWrapper::SetInputContext(nsWindow* aCaller,
                                           const InputContext* aContext,
                                           const InputContextAction* aAction) {
      if (MOZ_UNLIKELY(IsDestroyed())) {
        return;
      }

      MOZ_LOG(gGtkIMLog, LogLevel::Info,
              ("0x%p SetInputContext(aCaller=0x%p, aContext={ mIMEState={ "
               "mEnabled=%s }, mHTMLInputType=%s })",
               this, aCaller, ToString(aContext->mIMEState.mEnabled).c_str(),
               NS_ConvertUTF16toUTF8(aContext->mHTMLInputType).get()));

      if (aCaller != mLastFocusedWindow) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   SetInputContext(), FAILED, "
                 "the caller isn't focused window, mLastFocusedWindow=0x%p",
                 this, mLastFocusedWindow));
        return;
      }

      if (!mContext) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   SetInputContext(), FAILED, "
                 "there are no context",
                 this));
        return;
      }

      if (sLastFocusedContext != this) {
        mInputContext = *aContext;
        MOZ_LOG(gGtkIMLog, LogLevel::Debug,
                ("0x%p   SetInputContext(), succeeded, "
                 "but we're not active",
                 this));
        return;
      }

      bool changingEnabledState =
          aContext->mIMEState.mEnabled != mInputContext.mIMEState.mEnabled ||
          aContext->mHTMLInputType != mInputContext.mHTMLInputType;

      // Release current IME focus if IME is enabled.
      if (changingEnabledState && mInputContext.mIMEState.IsEditable()) {
        EndIMEComposition(mLastFocusedWindow);
        Blur();
      }

      mInputContext = *aContext;

      if (changingEnabledState) {
        if (mInputContext.mIMEState.IsEditable()) {
          GtkIMContext* currentContext = GetCurrentContext();
          if (currentContext) {
            GtkInputPurpose purpose = GTK_INPUT_PURPOSE_FREE_FORM;
            const nsString& inputType = mInputContext.mHTMLInputType;
            // Password case has difficult issue.  Desktop IMEs disable
            // composition if input-purpose is password.
            // For disabling IME on |ime-mode: disabled;|, we need to check
            // mEnabled value instead of inputType value.  This hack also
            // enables composition on
            // .
            // This is right behavior of ime-mode on desktop.
            //
            // On the other hand, IME for tablet devices may provide a
            // specific software keyboard for password field.  If so,
            // the behavior might look strange on both:
            //
            //
            //
            // Temporarily, we should focus on desktop environment for now.
            // I.e., let's ignore tablet devices for now.  When somebody
            // reports actual trouble on tablet devices, we should try to
            // look for a way to solve actual problem.
            if (mInputContext.mIMEState.mEnabled == IMEEnabled::Password) {
              purpose = GTK_INPUT_PURPOSE_PASSWORD;
            } else if (inputType.EqualsLiteral("email")) {
              purpose = GTK_INPUT_PURPOSE_EMAIL;
            } else if (inputType.EqualsLiteral("url")) {
              purpose = GTK_INPUT_PURPOSE_URL;
            } else if (inputType.EqualsLiteral("tel")) {
              purpose = GTK_INPUT_PURPOSE_PHONE;
            } else if (inputType.EqualsLiteral("number")) {
              purpose = GTK_INPUT_PURPOSE_NUMBER;
            } else if (mInputContext.mHTMLInputInputmode.EqualsLiteral("decimal")) {
              purpose = GTK_INPUT_PURPOSE_NUMBER;
            } else if (mInputContext.mHTMLInputInputmode.EqualsLiteral("email")) {
              purpose = GTK_INPUT_PURPOSE_EMAIL;
            } else if (mInputContext.mHTMLInputInputmode.EqualsLiteral("numeric")) {
              purpose = GTK_INPUT_PURPOSE_DIGITS;
            } else if (mInputContext.mHTMLInputInputmode.EqualsLiteral("tel")) {
              purpose = GTK_INPUT_PURPOSE_PHONE;
            } else if (mInputContext.mHTMLInputInputmode.EqualsLiteral("url")) {
              purpose = GTK_INPUT_PURPOSE_URL;
            }
            // Search by type and inputmode isn't supported on GTK.

            g_object_set(currentContext, "input-purpose", purpose, nullptr);

            // Although GtkInputHints is enum type, value is bit field.
            gint hints = GTK_INPUT_HINT_NONE;
            if (mInputContext.mHTMLInputInputmode.EqualsLiteral("none")) {
              hints |= GTK_INPUT_HINT_INHIBIT_OSK;
            }

            if (mInputContext.mAutocapitalize.EqualsLiteral("characters")) {
              hints |= GTK_INPUT_HINT_UPPERCASE_CHARS;
            } else if (mInputContext.mAutocapitalize.EqualsLiteral("sentences")) {
              hints |= GTK_INPUT_HINT_UPPERCASE_SENTENCES;
            } else if (mInputContext.mAutocapitalize.EqualsLiteral("words")) {
              hints |= GTK_INPUT_HINT_UPPERCASE_WORDS;
            }

            g_object_set(currentContext, "input-hints", hints, nullptr);
          }
        }

        // Even when aState is not enabled state, we need to set IME focus.
        // Because some IMs are updating the status bar of them at this time.
        // Be aware, don't use aWindow here because this method shouldn't move
        // focus actually.
        Focus();

        // XXX Should we call Blur() when it's not editable?  E.g., it might be
        //     better to close VKB automatically.
      }
    }

    InputContext IMContextWrapper::GetInputContext() {
      mInputContext.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
      return mInputContext;
    }

    GtkIMContext* IMContextWrapper::GetCurrentContext() const {
      if (IsEnabled()) {
        return mContext;
      }
      if (mInputContext.mIMEState.mEnabled == IMEEnabled::Password) {
        return mSimpleContext;
      }
      return mDummyContext;
    }

    bool IMContextWrapper::IsValidContext(GtkIMContext* aContext) const {
      if (!aContext) {
        return false;
      }
      return aContext == mContext || aContext == mSimpleContext ||
             aContext == mDummyContext;
    }

    bool IMContextWrapper::IsEnabled() const {
      return mInputContext.mIMEState.mEnabled == IMEEnabled::Enabled ||
             (!sUseSimpleContext &&
              mInputContext.mIMEState.mEnabled == IMEEnabled::Password);
    }

    void IMContextWrapper::Focus() {
      MOZ_LOG(
          gGtkIMLog, LogLevel::Info,
          ("0x%p Focus(), sLastFocusedContext=0x%p", this, sLastFocusedContext));

      if (mIsIMFocused) {
        NS_ASSERTION(sLastFocusedContext == this,
                     "We're not active, but the IM was focused?");
        return;
      }

      GtkIMContext* currentContext = GetCurrentContext();
      if (!currentContext) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   Focus(), FAILED, there are no context", this));
        return;
      }

      if (sLastFocusedContext && sLastFocusedContext != this) {
        sLastFocusedContext->Blur();
      }

      sLastFocusedContext = this;

      // Forget all posted key events when focus is moved since they shouldn't
      // be fired in different editor.
      sWaitingSynthesizedKeyPressHardwareKeyCode = 0;
      mPostingKeyEvents.Clear();

      gtk_im_context_focus_in(currentContext);
      mIsIMFocused = true;
      mSetCursorPositionOnKeyEvent = true;

      if (!IsEnabled()) {
        // We should release IME focus for uim and scim.
        // These IMs are using snooper that is released at losing focus.
        Blur();
      }
    }

    void IMContextWrapper::Blur() {
      MOZ_LOG(gGtkIMLog, LogLevel::Info,
              ("0x%p Blur(), mIsIMFocused=%s", this, ToChar(mIsIMFocused)));

      if (!mIsIMFocused) {
        return;
      }

      GtkIMContext* currentContext = GetCurrentContext();
      if (!currentContext) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   Blur(), FAILED, there are no context", this));
        return;
      }

      gtk_im_context_focus_out(currentContext);
      mIsIMFocused = false;
    }

    void IMContextWrapper::OnSelectionChange(
        nsWindow* aCaller, const IMENotification& aIMENotification) {
      mSelection.Assign(aIMENotification);
      bool retrievedSurroundingSignalReceived = mRetrieveSurroundingSignalReceived;
      mRetrieveSurroundingSignalReceived = false;

      if (MOZ_UNLIKELY(IsDestroyed())) {
        return;
      }

      const IMENotification::SelectionChangeDataBase& selectionChangeData =
          aIMENotification.mSelectionChangeData;

      MOZ_LOG(gGtkIMLog, LogLevel::Info,
              ("0x%p OnSelectionChange(aCaller=0x%p, aIMENotification={ "
               "mSelectionChangeData=%s }), "
               "mCompositionState=%s, mIsDeletingSurrounding=%s, "
               "mRetrieveSurroundingSignalReceived=%s",
               this, aCaller, ToString(selectionChangeData).c_str(),
               GetCompositionStateName(), ToChar(mIsDeletingSurrounding),
               ToChar(retrievedSurroundingSignalReceived)));

      if (aCaller != mLastFocusedWindow) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   OnSelectionChange(), FAILED, "
                 "the caller isn't focused window, mLastFocusedWindow=0x%p",
                 this, mLastFocusedWindow));
        return;
      }

      if (!IsComposing()) {
        // Now we have no composition (mostly situation on calling this method)
        // If we have it, it will set by
        // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED.
        mSetCursorPositionOnKeyEvent = true;
      }

      // The focused editor might have placeholder text with normal text node.
      // In such case, the text node must be removed from a compositionstart
      // event handler.  So, we're dispatching eCompositionStart,
      // we should ignore selection change notification.
      if (mCompositionState == eCompositionState_CompositionStartDispatched) {
        if (NS_WARN_IF(!mSelection.IsValid())) {
          MOZ_LOG(gGtkIMLog, LogLevel::Error,
                  ("0x%p   OnSelectionChange(), FAILED, "
                   "new offset is too large, cannot keep composing",
                   this));
        } else {
          // Modify the selection start offset with new offset.
          mCompositionStart = mSelection.mOffset;
          // XXX We should modify mSelectedStringRemovedByComposition?
          // But how?
          MOZ_LOG(gGtkIMLog, LogLevel::Debug,
                  ("0x%p   OnSelectionChange(), ignored, mCompositionStart "
                   "is updated to %u, the selection change doesn't cause "
                   "resetting IM context",
                   this, mCompositionStart));
          // And don't reset the IM context.
          return;
        }
        // Otherwise, reset the IM context due to impossible to keep composing.
      }

      // If the selection change is caused by deleting surrounding text,
      // we shouldn't need to notify IME of selection change.
      if (mIsDeletingSurrounding) {
        return;
      }

      bool occurredBeforeComposition =
          IsComposing() && !selectionChangeData.mOccurredDuringComposition &&
          !selectionChangeData.mCausedByComposition;
      if (occurredBeforeComposition) {
        mPendingResettingIMContext = true;
      }

      // When the selection change is caused by dispatching composition event,
      // selection set event and/or occurred before starting current composition,
      // we shouldn't notify IME of that and commit existing composition.
      if (!selectionChangeData.mCausedByComposition &&
          !selectionChangeData.mCausedBySelectionEvent &&
          !occurredBeforeComposition) {
        // Hack for ibus-pinyin.  ibus-pinyin will synthesize a set of
        // composition which commits with empty string after calling
        // gtk_im_context_reset().  Therefore, selecting text causes
        // unexpectedly removing it.  For preventing it but not breaking the
        // other IMEs which use surrounding text, we should call it only when
        // surrounding text has been retrieved after last selection range was
        // set.  If it's not retrieved, that means that current IME doesn't
        // have any content cache, so, it must not need the notification of
        // selection change.
        if (IsComposing() || retrievedSurroundingSignalReceived) {
          ResetIME();
        }
      }
    }

    /* static */
    void IMContextWrapper::OnThemeChanged() {
      if (!SelectionStyleProvider::GetInstance()) {
        return;
      }
      SelectionStyleProvider::GetInstance()->OnThemeChanged();
    }

    /* static */
    void IMContextWrapper::OnStartCompositionCallback(GtkIMContext* aContext,
                                                      IMContextWrapper* aModule) {
      aModule->OnStartCompositionNative(aContext);
    }

    void IMContextWrapper::OnStartCompositionNative(GtkIMContext* aContext) {
      MOZ_LOG(gGtkIMLog, LogLevel::Info,
              ("0x%p OnStartCompositionNative(aContext=0x%p), "
               "current context=0x%p, mComposingContext=0x%p",
               this, aContext, GetCurrentContext(), mComposingContext));

      // See bug 472635, we should do nothing if IM context doesn't match.
      if (GetCurrentContext() != aContext) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   OnStartCompositionNative(), FAILED, "
                 "given context doesn't match",
                 this));
        return;
      }

      if (mComposingContext && aContext != mComposingContext) {
        // XXX For now, we should ignore this odd case, just logging.
        MOZ_LOG(gGtkIMLog, LogLevel::Warning,
                ("0x%p   OnStartCompositionNative(), Warning, "
                 "there is already a composing context but starting new "
                 "composition with different context",
                 this));
      }

      // IME may start composition without "preedit_start" signal.  Therefore,
      // mComposingContext will be initialized in DispatchCompositionStart().

      if (!DispatchCompositionStart(aContext)) {
        return;
      }
      mCompositionTargetRange.mOffset = mCompositionStart;
      mCompositionTargetRange.mLength = 0;
    }

    /* static */
    void IMContextWrapper::OnEndCompositionCallback(GtkIMContext* aContext,
                                                    IMContextWrapper* aModule) {
      aModule->OnEndCompositionNative(aContext);
    }

    void IMContextWrapper::OnEndCompositionNative(GtkIMContext* aContext) {
      MOZ_LOG(gGtkIMLog, LogLevel::Info,
              ("0x%p OnEndCompositionNative(aContext=0x%p), mComposingContext=0x%p",
               this, aContext, mComposingContext));

      // See bug 472635, we should do nothing if IM context doesn't match.
      // Note that if this is called after focus move, the context may different
      // from any our owning context.
      if (!IsValidContext(aContext)) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p    OnEndCompositionNative(), FAILED, "
                 "given context doesn't match with any context",
                 this));
        return;
      }

      // If we've not started composition with aContext, we should ignore it.
      if (aContext != mComposingContext) {
        MOZ_LOG(gGtkIMLog, LogLevel::Warning,
                ("0x%p    OnEndCompositionNative(), Warning, "
                 "given context doesn't match with mComposingContext",
                 this));
        return;
      }

      g_object_unref(mComposingContext);
      mComposingContext = nullptr;

      // If we already handled the commit event, we should do nothing here.
      if (IsComposing()) {
        if (!DispatchCompositionCommitEvent(aContext)) {
          // If the widget is destroyed, we should do nothing anymore.
          return;
        }
      }

      if (mPendingResettingIMContext) {
        ResetIME();
      }
    }

    /* static */
    void IMContextWrapper::OnChangeCompositionCallback(GtkIMContext* aContext,
                                                       IMContextWrapper* aModule) {
      aModule->OnChangeCompositionNative(aContext);
    }

    void IMContextWrapper::OnChangeCompositionNative(GtkIMContext* aContext) {
      MOZ_LOG(gGtkIMLog, LogLevel::Info,
              ("0x%p OnChangeCompositionNative(aContext=0x%p), "
               "mComposingContext=0x%p",
               this, aContext, mComposingContext));

      // See bug 472635, we should do nothing if IM context doesn't match.
      // Note that if this is called after focus move, the context may different
      // from any our owning context.
      if (!IsValidContext(aContext)) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   OnChangeCompositionNative(), FAILED, "
                 "given context doesn't match with any context",
                 this));
        return;
      }

      if (mComposingContext && aContext != mComposingContext) {
        // XXX For now, we should ignore this odd case, just logging.
        MOZ_LOG(gGtkIMLog, LogLevel::Warning,
                ("0x%p   OnChangeCompositionNative(), Warning, "
                 "given context doesn't match with composing context",
                 this));
      }

      nsAutoString compositionString;
      GetCompositionString(aContext, compositionString);
      if (!IsComposing() && compositionString.IsEmpty()) {
        MOZ_LOG(gGtkIMLog, LogLevel::Warning,
                ("0x%p   OnChangeCompositionNative(), Warning, does nothing "
                 "because has not started composition and composing string is "
                 "empty",
                 this));
        mDispatchedCompositionString.Truncate();
        return;  // Don't start the composition with empty string.
      }

      // Be aware, widget can be gone
      DispatchCompositionChangeEvent(aContext, compositionString);
    }

    /* static */
    gboolean IMContextWrapper::OnRetrieveSurroundingCallback(
        GtkIMContext* aContext, IMContextWrapper* aModule) {
      return aModule->OnRetrieveSurroundingNative(aContext);
    }

    gboolean IMContextWrapper::OnRetrieveSurroundingNative(GtkIMContext* aContext) {
      MOZ_LOG(gGtkIMLog, LogLevel::Info,
              ("0x%p OnRetrieveSurroundingNative(aContext=0x%p), "
               "current context=0x%p",
               this, aContext, GetCurrentContext()));

      // See bug 472635, we should do nothing if IM context doesn't match.
      if (GetCurrentContext() != aContext) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   OnRetrieveSurroundingNative(), FAILED, "
                 "given context doesn't match",
                 this));
        return FALSE;
      }

      nsAutoString uniStr;
      uint32_t cursorPos;
      if (NS_FAILED(GetCurrentParagraph(uniStr, cursorPos))) {
        return FALSE;
      }

      // Despite taking a pointer and a length, IBus wants the string to be
      // zero-terminated and doesn't like U+0000 within the string.
      uniStr.ReplaceChar(char16_t(0), char16_t(0xFFFD));

      NS_ConvertUTF16toUTF8 utf8Str(nsDependentSubstring(uniStr, 0, cursorPos));
      uint32_t cursorPosInUTF8 = utf8Str.Length();
      AppendUTF16toUTF8(nsDependentSubstring(uniStr, cursorPos), utf8Str);
      gtk_im_context_set_surrounding(aContext, utf8Str.get(), utf8Str.Length(),
                                     cursorPosInUTF8);
      mRetrieveSurroundingSignalReceived = true;
      return TRUE;
    }

    /* static */
    gboolean IMContextWrapper::OnDeleteSurroundingCallback(
        GtkIMContext* aContext, gint aOffset, gint aNChars,
        IMContextWrapper* aModule) {
      return aModule->OnDeleteSurroundingNative(aContext, aOffset, aNChars);
    }

    gboolean IMContextWrapper::OnDeleteSurroundingNative(GtkIMContext* aContext,
                                                         gint aOffset,
                                                         gint aNChars) {
      MOZ_LOG(gGtkIMLog, LogLevel::Info,
              ("0x%p OnDeleteSurroundingNative(aContext=0x%p, aOffset=%d, "
               "aNChar=%d), current context=0x%p",
               this, aContext, aOffset, aNChars, GetCurrentContext()));

      // See bug 472635, we should do nothing if IM context doesn't match.
      if (GetCurrentContext() != aContext) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   OnDeleteSurroundingNative(), FAILED, "
                 "given context doesn't match",
                 this));
        return FALSE;
      }

      AutoRestore saveDeletingSurrounding(mIsDeletingSurrounding);
      mIsDeletingSurrounding = true;
      if (NS_SUCCEEDED(DeleteText(aContext, aOffset, (uint32_t)aNChars))) {
        return TRUE;
      }

      // failed
      MOZ_LOG(gGtkIMLog, LogLevel::Error,
              ("0x%p   OnDeleteSurroundingNative(), FAILED, "
               "cannot delete text",
               this));
      return FALSE;
    }

    /* static */
    void IMContextWrapper::OnCommitCompositionCallback(GtkIMContext* aContext,
                                                       const gchar* aString,
                                                       IMContextWrapper* aModule) {
      aModule->OnCommitCompositionNative(aContext, aString);
    }

    void IMContextWrapper::OnCommitCompositionNative(GtkIMContext* aContext,
                                                     const gchar* aUTF8Char) {
      const gchar emptyStr = 0;
      const gchar* commitString = aUTF8Char ? aUTF8Char : &emptyStr;
      NS_ConvertUTF8toUTF16 utf16CommitString(commitString);

      MOZ_LOG(gGtkIMLog, LogLevel::Info,
              ("0x%p OnCommitCompositionNative(aContext=0x%p), "
               "current context=0x%p, active context=0x%p, commitString=\"%s\", "
               "mProcessingKeyEvent=0x%p, IsComposingOn(aContext)=%s",
               this, aContext, GetCurrentContext(), GetActiveContext(),
               commitString, mProcessingKeyEvent, ToChar(IsComposingOn(aContext))));

      // See bug 472635, we should do nothing if IM context doesn't match.
      if (!IsValidContext(aContext)) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   OnCommitCompositionNative(), FAILED, "
                 "given context doesn't match",
                 this));
        return;
      }

      // If we are not in composition and committing with empty string,
      // we need to do nothing because if we continued to handle this
      // signal, we would dispatch compositionstart, text, compositionend
      // events with empty string.  Of course, they are unnecessary events
      // for Web applications and our editor.
      if (!IsComposingOn(aContext) && utf16CommitString.IsEmpty()) {
        MOZ_LOG(gGtkIMLog, LogLevel::Warning,
                ("0x%p   OnCommitCompositionNative(), Warning, does nothing "
                 "because has not started composition and commit string is empty",
                 this));
        return;
      }

      // If IME doesn't change their keyevent that generated this commit,
      // we should treat that IME didn't handle the key event because
      // web applications want to receive "keydown" and "keypress" event
      // in such case.
      // NOTE: While a key event is being handled, this might be caused on
      // current context.  Otherwise, this may be caused on active context.
      if (!IsComposingOn(aContext) && mProcessingKeyEvent &&
          mProcessingKeyEvent->type == GDK_KEY_PRESS &&
          aContext == GetCurrentContext()) {
        char keyval_utf8[8]; /* should have at least 6 bytes of space */
        gint keyval_utf8_len;
        guint32 keyval_unicode;

        keyval_unicode = gdk_keyval_to_unicode(mProcessingKeyEvent->keyval);
        keyval_utf8_len = g_unichar_to_utf8(keyval_unicode, keyval_utf8);
        keyval_utf8[keyval_utf8_len] = '\0';

        // If committing string is exactly same as a character which is
        // produced by the key, eKeyDown and eKeyPress event should be
        // dispatched by the caller of OnKeyEvent() normally.  Note that
        // mMaybeInDeadKeySequence will be set to false by OnKeyEvent()
        // since we set mFallbackToKeyEvent to true here.
        if (!strcmp(commitString, keyval_utf8)) {
          MOZ_LOG(gGtkIMLog, LogLevel::Info,
                  ("0x%p   OnCommitCompositionNative(), "
                   "we'll send normal key event",
                   this));
          mFallbackToKeyEvent = true;
          return;
        }

        // If we're in a dead key sequence, commit string is a character in
        // the BMP and mProcessingKeyEvent produces some characters but it's
        // not same as committing string, we should dispatch an eKeyPress
        // event from here.
        WidgetKeyboardEvent keyDownEvent(true, eKeyDown, mLastFocusedWindow);
        KeymapWrapper::InitKeyEvent(keyDownEvent, mProcessingKeyEvent, false);
        if (mMaybeInDeadKeySequence && utf16CommitString.Length() == 1 &&
            keyDownEvent.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING) {
          mKeyboardEventWasDispatched = true;
          // Anyway, we're not in dead key sequence anymore.
          mMaybeInDeadKeySequence = false;

          RefPtr dispatcher = GetTextEventDispatcher();
          nsresult rv = dispatcher->BeginNativeInputTransaction();
          if (NS_WARN_IF(NS_FAILED(rv))) {
            MOZ_LOG(gGtkIMLog, LogLevel::Error,
                    ("0x%p   OnCommitCompositionNative(), FAILED, "
                     "due to BeginNativeInputTransaction() failure",
                     this));
            return;
          }

          // First, dispatch eKeyDown event.
          keyDownEvent.mKeyValue = utf16CommitString;
          nsEventStatus status = nsEventStatus_eIgnore;
          bool dispatched = dispatcher->DispatchKeyboardEvent(
              eKeyDown, keyDownEvent, status, mProcessingKeyEvent);
          if (!dispatched || status == nsEventStatus_eConsumeNoDefault) {
            mKeyboardEventWasConsumed = true;
            MOZ_LOG(gGtkIMLog, LogLevel::Info,
                    ("0x%p   OnCommitCompositionNative(), "
                     "doesn't dispatch eKeyPress event because the preceding "
                     "eKeyDown event was not dispatched or was consumed",
                     this));
            return;
          }
          if (mLastFocusedWindow != keyDownEvent.mWidget ||
              mLastFocusedWindow->Destroyed()) {
            MOZ_LOG(gGtkIMLog, LogLevel::Warning,
                    ("0x%p   OnCommitCompositionNative(), Warning, "
                     "stop dispatching eKeyPress event because the preceding "
                     "eKeyDown event caused changing focused widget or "
                     "destroyed",
                     this));
            return;
          }
          MOZ_LOG(gGtkIMLog, LogLevel::Info,
                  ("0x%p   OnCommitCompositionNative(), "
                   "dispatched eKeyDown event for the committed character",
                   this));

          // Next, dispatch eKeyPress event.
          dispatcher->MaybeDispatchKeypressEvents(keyDownEvent, status,
                                                  mProcessingKeyEvent);
          MOZ_LOG(gGtkIMLog, LogLevel::Info,
                  ("0x%p   OnCommitCompositionNative(), "
                   "dispatched eKeyPress event for the committed character",
                   this));
          return;
        }
      }

      NS_ConvertUTF8toUTF16 str(commitString);
      // Be aware, widget can be gone
      DispatchCompositionCommitEvent(aContext, &str);
    }

    void IMContextWrapper::GetCompositionString(GtkIMContext* aContext,
                                                nsAString& aCompositionString) {
      gchar* preedit_string;
      gint cursor_pos;
      PangoAttrList* feedback_list;
      gtk_im_context_get_preedit_string(aContext, &preedit_string, &feedback_list,
                                        &cursor_pos);
      if (preedit_string && *preedit_string) {
        CopyUTF8toUTF16(MakeStringSpan(preedit_string), aCompositionString);
      } else {
        aCompositionString.Truncate();
      }

      MOZ_LOG(gGtkIMLog, LogLevel::Info,
              ("0x%p GetCompositionString(aContext=0x%p), "
               "aCompositionString=\"%s\"",
               this, aContext, preedit_string));

      pango_attr_list_unref(feedback_list);
      g_free(preedit_string);
    }

    bool IMContextWrapper::MaybeDispatchKeyEventAsProcessedByIME(
        EventMessage aFollowingEvent) {
      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 lastFocusedWindow(mLastFocusedWindow);

      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();

        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));

        // 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
        KeymapWrapper::DispatchKeyDownOrKeyUpEvent(lastFocusedWindow, sourceEvent,
                                                   !mMaybeInDeadKeySequence,
                                                   &mKeyboardEventWasConsumed);
        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 and wayland only.
        MOZ_ASSERT(mIMContextID == IMContextID::Uim ||
                   mIMContextID == IMContextID::Wayland);
        // uim sends "preedit_start" signal and "preedit_changed" separately
        // at starting composition, "commit" and "preedit_end" separately at
        // committing composition.

        // 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)));

          KeymapWrapper::DispatchKeyDownOrKeyUpEvent(
              lastFocusedWindow, fakeKeyDownEvent, &mKeyboardEventWasConsumed);
          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));
        return false;
      }

      // If the dispatched keydown event caused moving focus and that also
      // caused changing active context, we need to cancel composition here.
      if (GetCurrentContext() != oldCurrentContext) {
        MOZ_LOG(gGtkIMLog, LogLevel::Warning,
                ("0x%p   MaybeDispatchKeyEventAsProcessedByIME(), Warning, the key "
                 "event causes changing active IM context",
                 this));
        if (mComposingContext == oldComposingContext) {
          // Only when the context is still composing, we should call
          // ResetIME() here.  Otherwise, it should've already been
          // cleaned up.
          ResetIME();
        }
        return false;
      }

      return true;
    }

    bool IMContextWrapper::DispatchCompositionStart(GtkIMContext* aContext) {
      MOZ_LOG(gGtkIMLog, LogLevel::Info,
              ("0x%p DispatchCompositionStart(aContext=0x%p)", this, aContext));

      if (IsComposing()) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   DispatchCompositionStart(), FAILED, "
                 "we're already in composition",
                 this));
        return true;
      }

      if (!mLastFocusedWindow) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   DispatchCompositionStart(), FAILED, "
                 "there are no focused window in this module",
                 this));
        return false;
      }

      if (NS_WARN_IF(!EnsureToCacheSelection())) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   DispatchCompositionStart(), FAILED, "
                 "cannot query the selection offset",
                 this));
        return false;
      }

      mComposingContext = static_cast(g_object_ref(aContext));
      MOZ_ASSERT(mComposingContext);

      // Keep the last focused window alive
      RefPtr lastFocusedWindow(mLastFocusedWindow);

      // XXX The composition start point might be changed by composition events
      //     even though we strongly hope it doesn't happen.
      //     Every composition event should have the start offset for the result
      //     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(eCompositionStart)) {
        MOZ_LOG(gGtkIMLog, LogLevel::Warning,
                ("0x%p   DispatchCompositionStart(), Warning, "
                 "MaybeDispatchKeyEventAsProcessedByIME() returned false",
                 this));
        return false;
      }

      RefPtr dispatcher = GetTextEventDispatcher();
      nsresult rv = dispatcher->BeginNativeInputTransaction();
      if (NS_WARN_IF(NS_FAILED(rv))) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   DispatchCompositionStart(), FAILED, "
                 "due to BeginNativeInputTransaction() failure",
                 this));
        return false;
      }

      static bool sHasSetTelemetry = false;
      if (!sHasSetTelemetry) {
        sHasSetTelemetry = true;
        NS_ConvertUTF8toUTF16 im(GetIMName());
        // 72 is kMaximumKeyStringLength in TelemetryScalar.cpp
        if (im.Length() > 72) {
          if (NS_IS_SURROGATE_PAIR(im[72 - 2], im[72 - 1])) {
            im.Truncate(72 - 2);
          } else {
            im.Truncate(72 - 1);
          }
          // U+2026 is "..."
          im.Append(char16_t(0x2026));
        }
        Telemetry::ScalarSet(Telemetry::ScalarID::WIDGET_IME_NAME_ON_LINUX, im,
                             true);
      }

      MOZ_LOG(gGtkIMLog, LogLevel::Debug,
              ("0x%p   DispatchCompositionStart(), dispatching "
               "compositionstart... (mCompositionStart=%u)",
               this, mCompositionStart));
      mCompositionState = eCompositionState_CompositionStartDispatched;
      nsEventStatus status;
      dispatcher->StartComposition(status);
      if (lastFocusedWindow->IsDestroyed() ||
          lastFocusedWindow != mLastFocusedWindow) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   DispatchCompositionStart(), FAILED, the focused "
                 "widget was destroyed/changed by compositionstart event",
                 this));
        return false;
      }

      return true;
    }

    bool IMContextWrapper::DispatchCompositionChangeEvent(
        GtkIMContext* aContext, const nsAString& aCompositionString) {
      MOZ_LOG(
          gGtkIMLog, LogLevel::Info,
          ("0x%p DispatchCompositionChangeEvent(aContext=0x%p)", this, aContext));

      if (!mLastFocusedWindow) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   DispatchCompositionChangeEvent(), FAILED, "
                 "there are no focused window in this module",
                 this));
        return false;
      }

      if (!IsComposing()) {
        MOZ_LOG(gGtkIMLog, LogLevel::Debug,
                ("0x%p   DispatchCompositionChangeEvent(), the composition "
                 "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(eCompositionChange)) {
        MOZ_LOG(gGtkIMLog, LogLevel::Warning,
                ("0x%p   DispatchCompositionChangeEvent(), Warning, "
                 "MaybeDispatchKeyEventAsProcessedByIME() returned false",
                 this));
        return false;
      }

      RefPtr dispatcher = GetTextEventDispatcher();
      nsresult rv = dispatcher->BeginNativeInputTransaction();
      if (NS_WARN_IF(NS_FAILED(rv))) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   DispatchCompositionChangeEvent(), FAILED, "
                 "due to BeginNativeInputTransaction() failure",
                 this));
        return false;
      }

      // Store the selected string which will be removed by following
      // compositionchange event.
      if (mCompositionState == eCompositionState_CompositionStartDispatched) {
        if (NS_WARN_IF(
                !EnsureToCacheSelection(&mSelectedStringRemovedByComposition))) {
          // XXX How should we behave in this case??
        } else {
          // XXX We should assume, for now, any web applications don't change
          //     selection at handling this compositionchange event.
          mCompositionStart = mSelection.mOffset;
        }
      }

      RefPtr rangeArray =
          CreateTextRangeArray(aContext, aCompositionString);

      rv = dispatcher->SetPendingComposition(aCompositionString, rangeArray);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   DispatchCompositionChangeEvent(), FAILED, "
                 "due to SetPendingComposition() failure",
                 this));
        return false;
      }

      mCompositionState = eCompositionState_CompositionChangeEventDispatched;

      // We cannot call SetCursorPosition for e10s-aware.
      // DispatchEvent is async on e10s, so composition rect isn't updated now
      // on tab parent.
      mDispatchedCompositionString = aCompositionString;
      mLayoutChanged = false;
      mCompositionTargetRange.mOffset =
          mCompositionStart + rangeArray->TargetClauseOffset();
      mCompositionTargetRange.mLength = rangeArray->TargetClauseLength();

      RefPtr lastFocusedWindow(mLastFocusedWindow);
      nsEventStatus status;
      rv = dispatcher->FlushPendingComposition(status);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   DispatchCompositionChangeEvent(), FAILED, "
                 "due to FlushPendingComposition() failure",
                 this));
        return false;
      }

      if (lastFocusedWindow->IsDestroyed() ||
          lastFocusedWindow != mLastFocusedWindow) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   DispatchCompositionChangeEvent(), FAILED, the "
                 "focused widget was destroyed/changed by "
                 "compositionchange event",
                 this));
        return false;
      }
      return true;
    }

    bool IMContextWrapper::DispatchCompositionCommitEvent(
        GtkIMContext* aContext, const nsAString* aCommitString) {
      MOZ_LOG(gGtkIMLog, LogLevel::Info,
              ("0x%p DispatchCompositionCommitEvent(aContext=0x%p, "
               "aCommitString=0x%p, (\"%s\"))",
               this, aContext, aCommitString,
               aCommitString ? NS_ConvertUTF16toUTF8(*aCommitString).get() : ""));

      if (!mLastFocusedWindow) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   DispatchCompositionCommitEvent(), FAILED, "
                 "there are no focused window in this module",
                 this));
        return false;
      }

      // TODO: We need special care to handle request to commit composition
      //       by content while we're committing composition because we have
      //       commit string information now but IME may not have composition
      //       anymore.  Therefore, we may not be able to handle commit as
      //       expected.  However, this is rare case because this situation
      //       never occurs with remote content.  So, it's okay to fix this
      //       issue later.  (Perhaps, TextEventDisptcher should do it for
      //       all platforms.  E.g., creating WillCommitComposition()?)
      if (!IsComposing()) {
        if (!aCommitString || aCommitString->IsEmpty()) {
          MOZ_LOG(gGtkIMLog, LogLevel::Error,
                  ("0x%p   DispatchCompositionCommitEvent(), FAILED, "
                   "there is no composition and empty commit string",
                   this));
          return true;
        }
        MOZ_LOG(gGtkIMLog, LogLevel::Debug,
                ("0x%p   DispatchCompositionCommitEvent(), "
                 "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(
                   aCommitString ? eCompositionCommit : eCompositionCommitAsIs)) {
        MOZ_LOG(gGtkIMLog, LogLevel::Warning,
                ("0x%p   DispatchCompositionCommitEvent(), Warning, "
                 "MaybeDispatchKeyEventAsProcessedByIME() returned false",
                 this));
        mCompositionState = eCompositionState_NotComposing;
        return false;
      }

      RefPtr dispatcher = GetTextEventDispatcher();
      nsresult rv = dispatcher->BeginNativeInputTransaction();
      if (NS_WARN_IF(NS_FAILED(rv))) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   DispatchCompositionCommitEvent(), FAILED, "
                 "due to BeginNativeInputTransaction() failure",
                 this));
        return false;
      }

      RefPtr lastFocusedWindow(mLastFocusedWindow);

      // Emulate selection until receiving actual selection range.
      mSelection.CollapseTo(
          mCompositionStart + (aCommitString
                                   ? aCommitString->Length()
                                   : mDispatchedCompositionString.Length()),
          mSelection.mWritingMode);

      mCompositionState = eCompositionState_NotComposing;
      // Reset dead key sequence too because GTK doesn't support dead key chain
      // (i.e., a key press doesn't cause both producing some characters and
      // restarting new dead key sequence at one time).  So, committing
      // composition means end of a dead key sequence.
      mMaybeInDeadKeySequence = false;
      mCompositionStart = UINT32_MAX;
      mCompositionTargetRange.Clear();
      mDispatchedCompositionString.Truncate();
      mSelectedStringRemovedByComposition.Truncate();

      nsEventStatus status;
      rv = dispatcher->CommitComposition(status, aCommitString);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   DispatchCompositionChangeEvent(), FAILED, "
                 "due to CommitComposition() failure",
                 this));
        return false;
      }

      if (lastFocusedWindow->IsDestroyed() ||
          lastFocusedWindow != mLastFocusedWindow) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   DispatchCompositionCommitEvent(), FAILED, "
                 "the focused widget was destroyed/changed by "
                 "compositioncommit event",
                 this));
        return false;
      }

      return true;
    }

    already_AddRefed IMContextWrapper::CreateTextRangeArray(
        GtkIMContext* aContext, const nsAString& aCompositionString) {
      MOZ_LOG(gGtkIMLog, LogLevel::Info,
              ("0x%p CreateTextRangeArray(aContext=0x%p, "
               "aCompositionString=\"%s\" (Length()=%u))",
               this, aContext, NS_ConvertUTF16toUTF8(aCompositionString).get(),
               aCompositionString.Length()));

      RefPtr textRangeArray = new TextRangeArray();

      gchar* preedit_string;
      gint cursor_pos_in_chars;
      PangoAttrList* feedback_list;
      gtk_im_context_get_preedit_string(aContext, &preedit_string, &feedback_list,
                                        &cursor_pos_in_chars);
      if (!preedit_string || !*preedit_string) {
        if (!aCompositionString.IsEmpty()) {
          MOZ_LOG(gGtkIMLog, LogLevel::Error,
                  ("0x%p   CreateTextRangeArray(), FAILED, due to "
                   "preedit_string is null",
                   this));
        }
        pango_attr_list_unref(feedback_list);
        g_free(preedit_string);
        return textRangeArray.forget();
      }

      // Convert caret offset from offset in characters to offset in UTF-16
      // string.  If we couldn't proper offset in UTF-16 string, we should
      // assume that the caret is at the end of the composition string.
      uint32_t caretOffsetInUTF16 = aCompositionString.Length();
      if (NS_WARN_IF(cursor_pos_in_chars < 0)) {
        // Note that this case is undocumented.  We should assume that the
        // caret is at the end of the composition string.
      } else if (cursor_pos_in_chars == 0) {
        caretOffsetInUTF16 = 0;
      } else {
        gchar* charAfterCaret =
            g_utf8_offset_to_pointer(preedit_string, cursor_pos_in_chars);
        if (NS_WARN_IF(!charAfterCaret)) {
          MOZ_LOG(gGtkIMLog, LogLevel::Warning,
                  ("0x%p   CreateTextRangeArray(), failed to get UTF-8 "
                   "string before the caret (cursor_pos_in_chars=%d)",
                   this, cursor_pos_in_chars));
        } else {
          glong caretOffset = 0;
          gunichar2* utf16StrBeforeCaret =
              g_utf8_to_utf16(preedit_string, charAfterCaret - preedit_string,
                              nullptr, &caretOffset, nullptr);
          if (NS_WARN_IF(!utf16StrBeforeCaret) || NS_WARN_IF(caretOffset < 0)) {
            MOZ_LOG(gGtkIMLog, LogLevel::Warning,
                    ("0x%p   CreateTextRangeArray(), WARNING, failed to "
                     "convert to UTF-16 string before the caret "
                     "(cursor_pos_in_chars=%d, caretOffset=%ld)",
                     this, cursor_pos_in_chars, caretOffset));
          } else {
            caretOffsetInUTF16 = static_cast(caretOffset);
            uint32_t compositionStringLength = aCompositionString.Length();
            if (NS_WARN_IF(caretOffsetInUTF16 > compositionStringLength)) {
              MOZ_LOG(gGtkIMLog, LogLevel::Warning,
                      ("0x%p   CreateTextRangeArray(), WARNING, "
                       "caretOffsetInUTF16=%u is larger than "
                       "compositionStringLength=%u",
                       this, caretOffsetInUTF16, compositionStringLength));
              caretOffsetInUTF16 = compositionStringLength;
            }
          }
          if (utf16StrBeforeCaret) {
            g_free(utf16StrBeforeCaret);
          }
        }
      }

      PangoAttrIterator* iter;
      iter = pango_attr_list_get_iterator(feedback_list);
      if (!iter) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   CreateTextRangeArray(), FAILED, iterator couldn't "
                 "be allocated",
                 this));
        pango_attr_list_unref(feedback_list);
        g_free(preedit_string);
        return textRangeArray.forget();
      }

      uint32_t minOffsetOfClauses = aCompositionString.Length();
      uint32_t maxOffsetOfClauses = 0;
      do {
        TextRange range;
        if (!SetTextRange(iter, preedit_string, caretOffsetInUTF16, range)) {
          continue;
        }
        MOZ_ASSERT(range.Length());
        minOffsetOfClauses = std::min(minOffsetOfClauses, range.mStartOffset);
        maxOffsetOfClauses = std::max(maxOffsetOfClauses, range.mEndOffset);
        textRangeArray->AppendElement(range);
      } while (pango_attr_iterator_next(iter));

      // If the IME doesn't define clause from the start of the composition,
      // we should insert dummy clause information since TextRangeArray assumes
      // that there must be a clause whose start is 0 when there is one or
      // more clauses.
      if (minOffsetOfClauses) {
        TextRange dummyClause;
        dummyClause.mStartOffset = 0;
        dummyClause.mEndOffset = minOffsetOfClauses;
        dummyClause.mRangeType = TextRangeType::eRawClause;
        textRangeArray->InsertElementAt(0, dummyClause);
        maxOffsetOfClauses = std::max(maxOffsetOfClauses, dummyClause.mEndOffset);
        MOZ_LOG(gGtkIMLog, LogLevel::Warning,
                ("0x%p   CreateTextRangeArray(), inserting a dummy clause "
                 "at the beginning of the composition string mStartOffset=%u, "
                 "mEndOffset=%u, mRangeType=%s",
                 this, dummyClause.mStartOffset, dummyClause.mEndOffset,
                 ToChar(dummyClause.mRangeType)));
      }

      // If the IME doesn't define clause at end of the composition, we should
      // insert dummy clause information since TextRangeArray assumes that there
      // must be a clase whose end is the length of the composition string when
      // there is one or more clauses.
      if (!textRangeArray->IsEmpty() &&
          maxOffsetOfClauses < aCompositionString.Length()) {
        TextRange dummyClause;
        dummyClause.mStartOffset = maxOffsetOfClauses;
        dummyClause.mEndOffset = aCompositionString.Length();
        dummyClause.mRangeType = TextRangeType::eRawClause;
        textRangeArray->AppendElement(dummyClause);
        MOZ_LOG(gGtkIMLog, LogLevel::Warning,
                ("0x%p   CreateTextRangeArray(), inserting a dummy clause "
                 "at the end of the composition string mStartOffset=%u, "
                 "mEndOffset=%u, mRangeType=%s",
                 this, dummyClause.mStartOffset, dummyClause.mEndOffset,
                 ToChar(dummyClause.mRangeType)));
      }

      TextRange range;
      range.mStartOffset = range.mEndOffset = caretOffsetInUTF16;
      range.mRangeType = TextRangeType::eCaret;
      textRangeArray->AppendElement(range);
      MOZ_LOG(
          gGtkIMLog, LogLevel::Debug,
          ("0x%p   CreateTextRangeArray(), mStartOffset=%u, "
           "mEndOffset=%u, mRangeType=%s",
           this, range.mStartOffset, range.mEndOffset, ToChar(range.mRangeType)));

      pango_attr_iterator_destroy(iter);
      pango_attr_list_unref(feedback_list);
      g_free(preedit_string);

      return textRangeArray.forget();
    }

    /* static */
    nscolor IMContextWrapper::ToNscolor(PangoAttrColor* aPangoAttrColor) {
      PangoColor& pangoColor = aPangoAttrColor->color;
      uint8_t r = pangoColor.red / 0x100;
      uint8_t g = pangoColor.green / 0x100;
      uint8_t b = pangoColor.blue / 0x100;
      return NS_RGB(r, g, b);
    }

    bool IMContextWrapper::SetTextRange(PangoAttrIterator* aPangoAttrIter,
                                        const gchar* aUTF8CompositionString,
                                        uint32_t aUTF16CaretOffset,
                                        TextRange& aTextRange) const {
      // Set the range offsets in UTF-16 string.
      gint utf8ClauseStart, utf8ClauseEnd;
      pango_attr_iterator_range(aPangoAttrIter, &utf8ClauseStart, &utf8ClauseEnd);
      if (utf8ClauseStart == utf8ClauseEnd) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   SetTextRange(), FAILED, due to collapsed range", this));
        return false;
      }

      if (!utf8ClauseStart) {
        aTextRange.mStartOffset = 0;
      } else {
        glong utf16PreviousClausesLength;
        gunichar2* utf16PreviousClausesString =
            g_utf8_to_utf16(aUTF8CompositionString, utf8ClauseStart, nullptr,
                            &utf16PreviousClausesLength, nullptr);

        if (NS_WARN_IF(!utf16PreviousClausesString)) {
          MOZ_LOG(gGtkIMLog, LogLevel::Error,
                  ("0x%p   SetTextRange(), FAILED, due to g_utf8_to_utf16() "
                   "failure (retrieving previous string of current clause)",
                   this));
          return false;
        }

        aTextRange.mStartOffset = utf16PreviousClausesLength;
        g_free(utf16PreviousClausesString);
      }

      glong utf16CurrentClauseLength;
      gunichar2* utf16CurrentClauseString = g_utf8_to_utf16(
          aUTF8CompositionString + utf8ClauseStart, utf8ClauseEnd - utf8ClauseStart,
          nullptr, &utf16CurrentClauseLength, nullptr);

      if (NS_WARN_IF(!utf16CurrentClauseString)) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   SetTextRange(), FAILED, due to g_utf8_to_utf16() "
                 "failure (retrieving current clause)",
                 this));
        return false;
      }

      // iBus Chewing IME tells us that there is an empty clause at the end of
      // the composition string but we should ignore it since our code doesn't
      // assume that there is an empty clause.
      if (!utf16CurrentClauseLength) {
        MOZ_LOG(gGtkIMLog, LogLevel::Warning,
                ("0x%p   SetTextRange(), FAILED, due to current clause length "
                 "is 0",
                 this));
        return false;
      }

      aTextRange.mEndOffset = aTextRange.mStartOffset + utf16CurrentClauseLength;
      g_free(utf16CurrentClauseString);
      utf16CurrentClauseString = nullptr;

      // Set styles
      TextRangeStyle& style = aTextRange.mRangeStyle;

      // Underline
      PangoAttrInt* attrUnderline = reinterpret_cast(
          pango_attr_iterator_get(aPangoAttrIter, PANGO_ATTR_UNDERLINE));
      if (attrUnderline) {
        switch (attrUnderline->value) {
          case PANGO_UNDERLINE_NONE:
            style.mLineStyle = TextRangeStyle::LineStyle::None;
            break;
          case PANGO_UNDERLINE_DOUBLE:
            style.mLineStyle = TextRangeStyle::LineStyle::Double;
            break;
          case PANGO_UNDERLINE_ERROR:
            style.mLineStyle = TextRangeStyle::LineStyle::Wavy;
            break;
          case PANGO_UNDERLINE_SINGLE:
          case PANGO_UNDERLINE_LOW:
            style.mLineStyle = TextRangeStyle::LineStyle::Solid;
            break;
          default:
            MOZ_LOG(gGtkIMLog, LogLevel::Warning,
                    ("0x%p   SetTextRange(), retrieved unknown underline "
                     "style: %d",
                     this, attrUnderline->value));
            style.mLineStyle = TextRangeStyle::LineStyle::Solid;
            break;
        }
        style.mDefinedStyles |= TextRangeStyle::DEFINED_LINESTYLE;

        // Underline color
        PangoAttrColor* attrUnderlineColor = reinterpret_cast(
            pango_attr_iterator_get(aPangoAttrIter, PANGO_ATTR_UNDERLINE_COLOR));
        if (attrUnderlineColor) {
          style.mUnderlineColor = ToNscolor(attrUnderlineColor);
          style.mDefinedStyles |= TextRangeStyle::DEFINED_UNDERLINE_COLOR;
        }
      } else {
        style.mLineStyle = TextRangeStyle::LineStyle::None;
        style.mDefinedStyles |= TextRangeStyle::DEFINED_LINESTYLE;
      }

      // Don't set colors if they are not specified.  They should be computed by
      // textframe if only one of the colors are specified.

      // Foreground color (text color)
      PangoAttrColor* attrForeground = reinterpret_cast(
          pango_attr_iterator_get(aPangoAttrIter, PANGO_ATTR_FOREGROUND));
      if (attrForeground) {
        style.mForegroundColor = ToNscolor(attrForeground);
        style.mDefinedStyles |= TextRangeStyle::DEFINED_FOREGROUND_COLOR;
      }

      // Background color
      PangoAttrColor* attrBackground = reinterpret_cast(
          pango_attr_iterator_get(aPangoAttrIter, PANGO_ATTR_BACKGROUND));
      if (attrBackground) {
        style.mBackgroundColor = ToNscolor(attrBackground);
        style.mDefinedStyles |= TextRangeStyle::DEFINED_BACKGROUND_COLOR;
      }

      /**
       * We need to judge the meaning of the clause for a11y.  Before we support
       * IME specific composition string style, we used following rules:
       *
       *   1: If attrUnderline and attrForground are specified, we assumed the
       *      clause is TextRangeType::eSelectedClause.
       *   2: If only attrUnderline is specified, we assumed the clause is
       *      TextRangeType::eConvertedClause.
       *   3: If only attrForground is specified, we assumed the clause is
       *      TextRangeType::eSelectedRawClause.
       *   4: If neither attrUnderline nor attrForeground is specified, we assumed
       *      the clause is TextRangeType::eRawClause.
       *
       * However, this rules are odd since there can be two or more selected
       * clauses.  Additionally, our old rules caused that IME developers/users
       * cannot specify composition string style as they want.
       *
       * So, we shouldn't guess the meaning from its visual style.
       */

      // If the range covers whole of composition string and the caret is at
      // the end of the composition string, the range is probably not converted.
      if (!utf8ClauseStart &&
          utf8ClauseEnd == static_cast(strlen(aUTF8CompositionString)) &&
          aTextRange.mEndOffset == aUTF16CaretOffset) {
        aTextRange.mRangeType = TextRangeType::eRawClause;
      }
      // Typically, the caret is set at the start of the selected clause.
      // So, if the caret is in the clause, we can assume that the clause is
      // selected.
      else if (aTextRange.mStartOffset <= aUTF16CaretOffset &&
               aTextRange.mEndOffset > aUTF16CaretOffset) {
        aTextRange.mRangeType = TextRangeType::eSelectedClause;
      }
      // Otherwise, we should assume that the clause is converted but not
      // selected.
      else {
        aTextRange.mRangeType = TextRangeType::eConvertedClause;
      }

      MOZ_LOG(gGtkIMLog, LogLevel::Debug,
              ("0x%p   SetTextRange(), succeeded, aTextRange= { "
               "mStartOffset=%u, mEndOffset=%u, mRangeType=%s, mRangeStyle=%s }",
               this, aTextRange.mStartOffset, aTextRange.mEndOffset,
               ToChar(aTextRange.mRangeType),
               GetTextRangeStyleText(aTextRange.mRangeStyle).get()));

      return true;
    }

    void IMContextWrapper::SetCursorPosition(GtkIMContext* aContext) {
      MOZ_LOG(
          gGtkIMLog, LogLevel::Info,
          ("0x%p SetCursorPosition(aContext=0x%p), "
           "mCompositionTargetRange={ mOffset=%u, mLength=%u }"
           "mSelection={ mOffset=%u, Length()=%u, mWritingMode=%s }",
           this, aContext, mCompositionTargetRange.mOffset,
           mCompositionTargetRange.mLength, mSelection.mOffset, mSelection.Length(),
           GetWritingModeName(mSelection.mWritingMode).get()));

      bool useCaret = false;
      if (!mCompositionTargetRange.IsValid()) {
        if (!mSelection.IsValid()) {
          MOZ_LOG(gGtkIMLog, LogLevel::Error,
                  ("0x%p   SetCursorPosition(), FAILED, "
                   "mCompositionTargetRange and mSelection are invalid",
                   this));
          return;
        }
        useCaret = true;
      }

      if (!mLastFocusedWindow) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   SetCursorPosition(), FAILED, due to no focused "
                 "window",
                 this));
        return;
      }

      if (MOZ_UNLIKELY(!aContext)) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   SetCursorPosition(), FAILED, due to no context", this));
        return;
      }

      WidgetQueryContentEvent queryCaretOrTextRectEvent(
          true, useCaret ? eQueryCaretRect : eQueryTextRect, mLastFocusedWindow);
      if (useCaret) {
        queryCaretOrTextRectEvent.InitForQueryCaretRect(mSelection.mOffset);
      } else {
        if (mSelection.mWritingMode.IsVertical()) {
          // For preventing the candidate window to overlap the target
          // clause, we should set fake (typically, very tall) caret rect.
          uint32_t length =
              mCompositionTargetRange.mLength ? mCompositionTargetRange.mLength : 1;
          queryCaretOrTextRectEvent.InitForQueryTextRect(
              mCompositionTargetRange.mOffset, length);
        } else {
          queryCaretOrTextRectEvent.InitForQueryTextRect(
              mCompositionTargetRange.mOffset, 1);
        }
      }
      InitEvent(queryCaretOrTextRectEvent);
      nsEventStatus status;
      mLastFocusedWindow->DispatchEvent(&queryCaretOrTextRectEvent, status);
      if (queryCaretOrTextRectEvent.Failed()) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   SetCursorPosition(), FAILED, %s was failed", this,
                 useCaret ? "eQueryCaretRect" : "eQueryTextRect"));
        return;
      }

      nsWindow* rootWindow =
          static_cast(mLastFocusedWindow->GetTopLevelWidget());

      // Get the position of the rootWindow in screen.
      LayoutDeviceIntPoint root = rootWindow->WidgetToScreenOffset();

      // Get the position of IM context owner window in screen.
      LayoutDeviceIntPoint owner = mOwnerWindow->WidgetToScreenOffset();

      // Compute the caret position in the IM owner window.
      LayoutDeviceIntRect rect =
          queryCaretOrTextRectEvent.mReply->mRect + root - owner;
      rect.width = 0;
      GdkRectangle area = rootWindow->DevicePixelsToGdkRectRoundOut(rect);

      gtk_im_context_set_cursor_location(aContext, &area);
    }

    nsresult IMContextWrapper::GetCurrentParagraph(nsAString& aText,
                                                   uint32_t& aCursorPos) {
      MOZ_LOG(gGtkIMLog, LogLevel::Info,
              ("0x%p GetCurrentParagraph(), mCompositionState=%s", this,
               GetCompositionStateName()));

      if (!mLastFocusedWindow) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   GetCurrentParagraph(), FAILED, there are no "
                 "focused window in this module",
                 this));
        return NS_ERROR_NULL_POINTER;
      }

      nsEventStatus status;

      uint32_t selOffset = mCompositionStart;
      uint32_t selLength = mSelectedStringRemovedByComposition.Length();

      // If focused editor doesn't have composition string, we should use
      // current selection.
      if (!EditorHasCompositionString()) {
        // Query cursor position & selection
        if (NS_WARN_IF(!EnsureToCacheSelection())) {
          MOZ_LOG(gGtkIMLog, LogLevel::Error,
                  ("0x%p   GetCurrentParagraph(), FAILED, due to no "
                   "valid selection information",
                   this));
          return NS_ERROR_FAILURE;
        }

        selOffset = mSelection.mOffset;
        selLength = mSelection.Length();
      }

      MOZ_LOG(gGtkIMLog, LogLevel::Debug,
              ("0x%p   GetCurrentParagraph(), selOffset=%u, selLength=%u", this,
               selOffset, selLength));

      // XXX nsString::Find and nsString::RFind take int32_t for offset, so,
      //     we cannot support this request when the current offset is larger
      //     than INT32_MAX.
      if (selOffset > INT32_MAX || selLength > INT32_MAX ||
          selOffset + selLength > INT32_MAX) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   GetCurrentParagraph(), FAILED, The selection is "
                 "out of range",
                 this));
        return NS_ERROR_FAILURE;
      }

      // Get all text contents of the focused editor
      WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
                                                    mLastFocusedWindow);
      queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
      mLastFocusedWindow->DispatchEvent(&queryTextContentEvent, status);
      if (NS_WARN_IF(queryTextContentEvent.Failed())) {
        return NS_ERROR_FAILURE;
      }

      if (selOffset + selLength > queryTextContentEvent.mReply->DataLength()) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   GetCurrentParagraph(), FAILED, The selection is "
                 "invalid, queryTextContentEvent={ mReply=%s }",
                 this, ToString(queryTextContentEvent.mReply).c_str()));
        return NS_ERROR_FAILURE;
      }

      // Remove composing string and restore the selected string because
      // GtkEntry doesn't remove selected string until committing, however,
      // our editor does it.  We should emulate the behavior for IME.
      nsAutoString textContent(queryTextContentEvent.mReply->DataRef());
      if (EditorHasCompositionString() &&
          mDispatchedCompositionString != mSelectedStringRemovedByComposition) {
        textContent.Replace(mCompositionStart,
                            mDispatchedCompositionString.Length(),
                            mSelectedStringRemovedByComposition);
      }

      // Get only the focused paragraph, by looking for newlines
      int32_t parStart =
          (selOffset == 0) ? 0
                           : textContent.RFind("\n", false, selOffset - 1, -1) + 1;
      int32_t parEnd = textContent.Find("\n", false, selOffset + selLength, -1);
      if (parEnd < 0) {
        parEnd = textContent.Length();
      }
      aText = nsDependentSubstring(textContent, parStart, parEnd - parStart);
      aCursorPos = selOffset - uint32_t(parStart);

      MOZ_LOG(
          gGtkIMLog, LogLevel::Debug,
          ("0x%p   GetCurrentParagraph(), succeeded, aText=%s, "
           "aText.Length()=%u, aCursorPos=%u",
           this, NS_ConvertUTF16toUTF8(aText).get(), aText.Length(), aCursorPos));

      return NS_OK;
    }

    nsresult IMContextWrapper::DeleteText(GtkIMContext* aContext, int32_t aOffset,
                                          uint32_t aNChars) {
      MOZ_LOG(gGtkIMLog, LogLevel::Info,
              ("0x%p DeleteText(aContext=0x%p, aOffset=%d, aNChars=%u), "
               "mCompositionState=%s",
               this, aContext, aOffset, aNChars, GetCompositionStateName()));

      if (!mLastFocusedWindow) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   DeleteText(), FAILED, there are no focused window "
                 "in this module",
                 this));
        return NS_ERROR_NULL_POINTER;
      }

      if (!aNChars) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   DeleteText(), FAILED, aNChars must not be zero", this));
        return NS_ERROR_INVALID_ARG;
      }

      RefPtr lastFocusedWindow(mLastFocusedWindow);
      nsEventStatus status;

      // First, we should cancel current composition because editor cannot
      // handle changing selection and deleting text.
      uint32_t selOffset;
      bool wasComposing = IsComposing();
      bool editorHadCompositionString = EditorHasCompositionString();
      if (wasComposing) {
        selOffset = mCompositionStart;
        if (!DispatchCompositionCommitEvent(aContext,
                                            &mSelectedStringRemovedByComposition)) {
          MOZ_LOG(gGtkIMLog, LogLevel::Error,
                  ("0x%p   DeleteText(), FAILED, quitting from DeletText", this));
          return NS_ERROR_FAILURE;
        }
      } else {
        if (NS_WARN_IF(!EnsureToCacheSelection())) {
          MOZ_LOG(gGtkIMLog, LogLevel::Error,
                  ("0x%p   DeleteText(), FAILED, due to no valid selection "
                   "information",
                   this));
          return NS_ERROR_FAILURE;
        }
        selOffset = mSelection.mOffset;
      }

      // Get all text contents of the focused editor
      WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
                                                    mLastFocusedWindow);
      queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
      mLastFocusedWindow->DispatchEvent(&queryTextContentEvent, status);
      if (NS_WARN_IF(queryTextContentEvent.Failed())) {
        return NS_ERROR_FAILURE;
      }
      if (queryTextContentEvent.mReply->IsDataEmpty()) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   DeleteText(), FAILED, there is no contents", this));
        return NS_ERROR_FAILURE;
      }

      NS_ConvertUTF16toUTF8 utf8Str(nsDependentSubstring(
          queryTextContentEvent.mReply->DataRef(), 0, selOffset));
      glong offsetInUTF8Characters =
          g_utf8_strlen(utf8Str.get(), utf8Str.Length()) + aOffset;
      if (offsetInUTF8Characters < 0) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   DeleteText(), FAILED, aOffset is too small for "
                 "current cursor pos (computed offset: %ld)",
                 this, offsetInUTF8Characters));
        return NS_ERROR_FAILURE;
      }

      AppendUTF16toUTF8(
          nsDependentSubstring(queryTextContentEvent.mReply->DataRef(), selOffset),
          utf8Str);
      glong countOfCharactersInUTF8 =
          g_utf8_strlen(utf8Str.get(), utf8Str.Length());
      glong endInUTF8Characters = offsetInUTF8Characters + aNChars;
      if (countOfCharactersInUTF8 < endInUTF8Characters) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   DeleteText(), FAILED, aNChars is too large for "
                 "current contents (content length: %ld, computed end offset: %ld)",
                 this, countOfCharactersInUTF8, endInUTF8Characters));
        return NS_ERROR_FAILURE;
      }

      gchar* charAtOffset =
          g_utf8_offset_to_pointer(utf8Str.get(), offsetInUTF8Characters);
      gchar* charAtEnd =
          g_utf8_offset_to_pointer(utf8Str.get(), endInUTF8Characters);

      // Set selection to delete
      WidgetSelectionEvent selectionEvent(true, eSetSelection, mLastFocusedWindow);

      nsDependentCSubstring utf8StrBeforeOffset(utf8Str, 0,
                                                charAtOffset - utf8Str.get());
      selectionEvent.mOffset = NS_ConvertUTF8toUTF16(utf8StrBeforeOffset).Length();

      nsDependentCSubstring utf8DeletingStr(utf8Str, utf8StrBeforeOffset.Length(),
                                            charAtEnd - charAtOffset);
      selectionEvent.mLength = NS_ConvertUTF8toUTF16(utf8DeletingStr).Length();

      selectionEvent.mReversed = false;
      selectionEvent.mExpandToClusterBoundary = false;
      lastFocusedWindow->DispatchEvent(&selectionEvent, status);

      if (!selectionEvent.mSucceeded || lastFocusedWindow != mLastFocusedWindow ||
          lastFocusedWindow->Destroyed()) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("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(eContentCommandDelete)) {
        MOZ_LOG(gGtkIMLog, LogLevel::Warning,
                ("0x%p   DeleteText(), Warning, "
                 "MaybeDispatchKeyEventAsProcessedByIME() returned false",
                 this));
        return NS_ERROR_FAILURE;
      }

      // Delete the selection
      WidgetContentCommandEvent contentCommandEvent(true, eContentCommandDelete,
                                                    mLastFocusedWindow);
      mLastFocusedWindow->DispatchEvent(&contentCommandEvent, status);

      if (!contentCommandEvent.mSucceeded ||
          lastFocusedWindow != mLastFocusedWindow ||
          lastFocusedWindow->Destroyed()) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p   DeleteText(), FAILED, deleting the selection caused "
                 "focus change or window destroyed",
                 this));
        return NS_ERROR_FAILURE;
      }

      if (!wasComposing) {
        return NS_OK;
      }

      // Restore the composition at new caret position.
      if (!DispatchCompositionStart(aContext)) {
        MOZ_LOG(
            gGtkIMLog, LogLevel::Error,
            ("0x%p   DeleteText(), FAILED, resterting composition start", this));
        return NS_ERROR_FAILURE;
      }

      if (!editorHadCompositionString) {
        return NS_OK;
      }

      nsAutoString compositionString;
      GetCompositionString(aContext, compositionString);
      if (!DispatchCompositionChangeEvent(aContext, compositionString)) {
        MOZ_LOG(
            gGtkIMLog, LogLevel::Error,
            ("0x%p   DeleteText(), FAILED, restoring composition string", this));
        return NS_ERROR_FAILURE;
      }

      return NS_OK;
    }

    void IMContextWrapper::InitEvent(WidgetGUIEvent& aEvent) {
      aEvent.mTime = PR_Now() / 1000;
    }

    bool IMContextWrapper::EnsureToCacheSelection(nsAString* aSelectedString) {
      if (aSelectedString) {
        aSelectedString->Truncate();
      }

      if (mSelection.IsValid()) {
        if (aSelectedString) {
          *aSelectedString = mSelection.mString;
        }
        return true;
      }

      if (NS_WARN_IF(!mLastFocusedWindow)) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p EnsureToCacheSelection(), FAILED, due to "
                 "no focused window",
                 this));
        return false;
      }

      nsEventStatus status;
      WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
                                                     mLastFocusedWindow);
      InitEvent(querySelectedTextEvent);
      mLastFocusedWindow->DispatchEvent(&querySelectedTextEvent, status);
      if (NS_WARN_IF(querySelectedTextEvent.Failed())) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p EnsureToCacheSelection(), FAILED, due to "
                 "failure of query selection event",
                 this));
        return false;
      }

      mSelection.Assign(querySelectedTextEvent);
      if (!mSelection.IsValid()) {
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
                ("0x%p EnsureToCacheSelection(), FAILED, due to "
                 "failure of query selection event (invalid result)",
                 this));
        return false;
      }

      if (!mSelection.Collapsed() && aSelectedString) {
        aSelectedString->Assign(querySelectedTextEvent.mReply->DataRef());
      }

      MOZ_LOG(gGtkIMLog, LogLevel::Debug,
              ("0x%p EnsureToCacheSelection(), Succeeded, mSelection="
               "{ mOffset=%u, Length()=%u, mWritingMode=%s }",
               this, mSelection.mOffset, mSelection.Length(),
               GetWritingModeName(mSelection.mWritingMode).get()));
      return true;
    }

    /******************************************************************************
     * IMContextWrapper::Selection
     ******************************************************************************/

    void IMContextWrapper::Selection::Assign(
        const IMENotification& aIMENotification) {
      MOZ_ASSERT(aIMENotification.mMessage == NOTIFY_IME_OF_SELECTION_CHANGE);
      mString = aIMENotification.mSelectionChangeData.String();
      mOffset = aIMENotification.mSelectionChangeData.mOffset;
      mWritingMode = aIMENotification.mSelectionChangeData.GetWritingMode();
    }

    void IMContextWrapper::Selection::Assign(
        const WidgetQueryContentEvent& aEvent) {
      MOZ_ASSERT(aEvent.mMessage == eQuerySelectedText);
      MOZ_ASSERT(aEvent.Succeeded());
      MOZ_ASSERT(aEvent.mReply->mOffsetAndData.isSome());
      mString = aEvent.mReply->DataRef();
      mOffset = aEvent.mReply->StartOffset();
      mWritingMode = aEvent.mReply->WritingModeRef();
    }

    }  // namespace widget
    }  // namespace mozilla