Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/widget/gtk/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 127 kB image not shown  

Quelle  IMContextWrapper.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=4 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "mozilla/Logging.h"
#include "nsString.h"
#include "prtime.h"
#include "prenv.h"

#include "IMContextWrapper.h"

#include "GRefPtr.h"
#include "nsGtkKeyUtils.h"
#include "nsWindow.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/Likely.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/MiscEvents.h"
#include "mozilla/Preferences.h"
#include "mozilla/StaticPrefs_intl.h"
#include "mozilla/glean/WidgetGtkMetrics.h"
#include "mozilla/TextEventDispatcher.h"
#include "mozilla/TextEvents.h"
#include "mozilla/ToString.h"
#include "mozilla/WritingModes.h"

// For collecting other people's log, tell `MOZ_LOG=IMEHandler:4,sync`
// rather than `MOZ_LOG=IMEHandler:5,sync` since using `5` may create too
// big file.
// Therefore you shouldn't use `LogLevel::Verbose` for logging usual behavior.
mozilla::LazyLogModule gIMELog("IMEHandler");

namespace mozilla {
namespace widget {

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 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<TextRangeStyle::LineStyleType>(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 <input type="text">) 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.
 ******************************************************************************/


static Maybe<nscolor> GetSystemColor(LookAndFeel::ColorID aId) {
  return LookAndFeel::GetColor(aId, LookAndFeel::ColorScheme::Light,
                               LookAndFeel::UseStandins::No);
}

class SelectionStyleProvider final {
 public:
  static SelectionStyleProvider* GetExistingInstance() { return sInstance; }

  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.
    if (auto selectionForegroundColor =
            GetSystemColor(LookAndFeel::ColorID::Highlight)) {
      double alpha =
          static_cast<double>(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(");");
    }
    if (auto selectionBackgroundColor =
            GetSystemColor(LookAndFeel::ColorID::Highlighttext)) {
      double alpha =
          static_cast<double>(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),
      mFallbackToKeyEvent(false),
      mKeyboardEventWasDispatched(false),
      mKeyboardEventWasConsumed(false),
      mIsDeletingSurrounding(false),
      mLayoutChanged(false),
      mSetCursorPositionOnKeyEvent(true),
      mPendingResettingIMContext(false),
      mRetrieveSurroundingSignalReceived(false),
      mMaybeInDeadKeySequence(false),
      mIsIMInAsyncKeyHandlingMode(false),
      mSetInputPurposeAndInputHints(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<size_t>(atIMValueStart)) {
    return im;
  }

  int32_t atIMValueEnd = xmodifiers.Find("@", 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(gIMELog, 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() {
  MOZ_ASSERT(!mContext);
  MOZ_ASSERT(!mComposingContext);
  if (this == sLastFocusedContext) {
    sLastFocusedContext = nullptr;
  }
  MOZ_LOG(gIMELog, 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<nsWindow*>(aTextEventDispatcher->GetWidget());
      return IsComposing() ? EndIMEComposition(window) : NS_OK;
    }
    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<nsWindow*>(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<GdkEventKey*>(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(
      gIMELog, 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) {
    if (IsComposing()) {
      EndIMEComposition(aWindow);
    }
    NotifyIMEOfFocusChange(IMEFocusState::Blurred);
    mLastFocusedWindow = nullptr;
  }

  if (mOwnerWindow != aWindow) {
    return;
  }

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

  /**
   * NOTE:
   *   The given window is the owner of this, so, we must disconnect from 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_signal_handlers_disconnect_by_data(mContext, this);
    g_object_unref(mContext);
    mContext = nullptr;
  }

  if (mSimpleContext) {
    gtk_im_context_set_client_window(mSimpleContext, nullptr);
    g_signal_handlers_disconnect_by_data(mSimpleContext, this);
    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(gIMELog, 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(gIMELog, LogLevel::Info,
                ("0x%p PrepareToDestroyContext(), added to reference to "
                 "GtkIMContextIIIM class to prevent it from being unloaded",
                 this));
      } else {
        MOZ_LOG(gIMELog, 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(gIMELog, LogLevel::Info,
          ("0x%p OnFocusWindow(aWindow=0x%p), mLastFocusedWindow=0x%p"this,
           aWindow, mLastFocusedWindow));
  mLastFocusedWindow = aWindow;
}

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

  MOZ_LOG(
      gIMELog, LogLevel::Info,
      ("0x%p OnBlurWindow(aWindow=0x%p), mLastFocusedWindow=0x%p, "
       "mIMEFocusState=%s",
       this, aWindow, mLastFocusedWindow, ToString(mIMEFocusState).c_str()));

  if (mLastFocusedWindow != aWindow) {
    return;
  }

  NotifyIMEOfFocusChange(IMEFocusState::Blurred);
}

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(gIMELog, LogLevel::Info, (">>>>>>>>>>>>>>>>"));
  MOZ_LOG(
      gIMELog, 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(
      gIMELog, 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(gIMELog, 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(gIMELog, 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(gIMELog, 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(gIMELog, 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
        // <input type="password"> 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(gIMELog, 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(gIMELog, 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(gIMELog, 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;
  }

  if (aEvent->type == GDK_KEY_RELEASE) {
    if (const GdkEventKey* pendingKeyPressEvent =
            mPostingKeyEvents.GetCorrespondingKeyPressEvent(aEvent)) {
      MOZ_LOG(gIMELog, LogLevel::Warning,
              ("0x%p OnKeyEvent(), forgetting a pending GDK_KEY_PRESS event "
               "because GDK_KEY_RELEASE for the event is handled",
               this));
      mPostingKeyEvents.RemoveEvent(pendingKeyPressEvent);
    }
  }

  MOZ_LOG(
      gIMELog, 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(gIMELog, 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(gIMELog, LogLevel::Info,
          ("0x%p OnFocusChangeInGecko(aFocus=%s),mCompositionState=%s, "
           "mIMEFocusState=%s, mSetInputPurposeAndInputHints=%s",
           this, ToChar(aFocus), GetCompositionStateName(),
           ToString(mIMEFocusState).c_str(),
           ToChar(mSetInputPurposeAndInputHints)));

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

  if (aFocus) {
    if (mSetInputPurposeAndInputHints) {
      mSetInputPurposeAndInputHints = false;
      SetInputPurposeAndInputHints();
    }
    NotifyIMEOfFocusChange(IMEFocusState::Focused);
  } else {
    NotifyIMEOfFocusChange(IMEFocusState::Blurred);
  }

  // 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 && EnsureToCacheContentSelection()) {
    SetCursorPosition(GetActiveContext());
  }
}

void IMContextWrapper::ResetIME() {
  MOZ_LOG(gIMELog, LogLevel::Info,
          ("0x%p ResetIME(), mCompositionState=%s, mIMEFocusState=%s"this,
           GetCompositionStateName(), ToString(mIMEFocusState).c_str()));

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

  RefPtr<IMContextWrapper> kungFuDeathGrip(this);
  RefPtr<nsWindow> 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(gIMELog, LogLevel::Debug,
          ("0x%p ResetIME() called gtk_im_context_reset(), "
           "activeContext=0x%p, mCompositionState=%s, compositionString=%s, "
           "mIMEFocusState=%s",
           this, activeContext, GetCompositionStateName(),
           NS_ConvertUTF16toUTF8(compositionString).get(),
           ToString(mIMEFocusState).c_str()));

  // 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(gIMELog, LogLevel::Info,
          ("0x%p EndIMEComposition(aCaller=0x%p), "
           "mCompositionState=%s",
           this, aCaller, GetCompositionStateName()));

  if (aCaller != mLastFocusedWindow) {
    MOZ_LOG(gIMELog, 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
    mContentSelection.reset();
    EnsureToCacheContentSelection();
    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(gIMELog, 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(gIMELog, LogLevel::Error,
            ("0x%p SetInputContext(), FAILED, "
             "the caller isn't focused window, mLastFocusedWindow=0x%p",
             this, mLastFocusedWindow));
    return;
  }

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

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

  const bool changingEnabledState =
      aContext->IsInputAttributeChanged(mInputContext);

  // Release current IME focus if IME is enabled.
  if (changingEnabledState && mInputContext.mIMEState.IsEditable()) {
    if (IsComposing()) {
      EndIMEComposition(mLastFocusedWindow);
    }
    if (mIMEFocusState == IMEFocusState::Focused) {
      NotifyIMEOfFocusChange(IMEFocusState::BlurredWithoutFocusChange);
    }
  }

  mInputContext = *aContext;
  mSetInputPurposeAndInputHints = false;

  if (!changingEnabledState || !mInputContext.mIMEState.IsEditable()) {
    return;
  }

  // If the input context was temporarily disabled without a focus change,
  // it must be ready to query content even if the focused content is in
  // a remote process.  In this case, we should set IME focus right now.
  if (mIMEFocusState == IMEFocusState::BlurredWithoutFocusChange) {
    SetInputPurposeAndInputHints();
    NotifyIMEOfFocusChange(IMEFocusState::Focused);
    return;
  }

  // Otherwise, we cannot set input-purpose and input-hints right now because
  // setting them may require to set focus immediately for IME own's UI.
  // However, at this moment, `ContentCacheInParent` does not have content
  // cache, it'll be available after `NOTIFY_IME_OF_FOCUS` notification.
  // Therefore, we set them at receiving the notification.
  mSetInputPurposeAndInputHints = true;
}

void IMContextWrapper::SetInputPurposeAndInputHints() {
  GtkIMContext* currentContext = GetCurrentContext();
  if (!currentContext) {
    return;
  }

  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 <input type="password" style="ime-mode: enabled;">.
  // 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:
  //   <input type="text" style="ime-mode: disabled;">
  //   <input type="password" style="ime-mode: enabled;">
  //
  // 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.mHTMLInputMode.EqualsLiteral("decimal")) {
    purpose = GTK_INPUT_PURPOSE_NUMBER;
  } else if (mInputContext.mHTMLInputMode.EqualsLiteral("email")) {
    purpose = GTK_INPUT_PURPOSE_EMAIL;
  } else if (mInputContext.mHTMLInputMode.EqualsLiteral("numeric")) {
    purpose = GTK_INPUT_PURPOSE_DIGITS;
  } else if (mInputContext.mHTMLInputMode.EqualsLiteral("tel")) {
    purpose = GTK_INPUT_PURPOSE_PHONE;
  } else if (mInputContext.mHTMLInputMode.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.mHTMLInputMode.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);
}

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::NotifyIMEOfFocusChange(IMEFocusState aIMEFocusState) {
  MOZ_ASSERT_IF(aIMEFocusState == IMEFocusState::BlurredWithoutFocusChange,
                mIMEFocusState != IMEFocusState::Blurred);
  if (mIMEFocusState == aIMEFocusState) {
    return;
  }

  MOZ_LOG(gIMELog, LogLevel::Info,
          ("0x%p NotifyIMEOfFocusChange(aIMEFocusState=%s), mIMEFocusState=%s, "
           "sLastFocusedContext=0x%p",
           this, ToString(aIMEFocusState).c_str(),
           ToString(mIMEFocusState).c_str(), sLastFocusedContext));
  MOZ_ASSERT(!mSetInputPurposeAndInputHints);

  // If we've already made IME blurred at setting the input context disabled
  // and it's now completely blurred by a focus move, we need only to update
  // mIMEFocusState and when the input context gets enabled, we cannot set
  // IME focus immediately.
  if (aIMEFocusState == IMEFocusState::Blurred &&
      mIMEFocusState == IMEFocusState::BlurredWithoutFocusChange) {
    mIMEFocusState = IMEFocusState::Blurred;
    return;
  }

  auto Blur = [&](IMEFocusState aInternalState) {
    GtkIMContext* currentContext = GetCurrentContext();
    if (MOZ_UNLIKELY(!currentContext)) {
      MOZ_LOG(gIMELog, LogLevel::Error,
              ("0x%p NotifyIMEOfFocusChange()::Blur(), FAILED, "
               "there is no context",
               this));
      return;
    }
    gtk_im_context_focus_out(currentContext);
    mIMEFocusState = aInternalState;
  };

  if (aIMEFocusState != IMEFocusState::Focused) {
    return Blur(aIMEFocusState);
  }

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

  if (sLastFocusedContext && sLastFocusedContext != this) {
    sLastFocusedContext->NotifyIMEOfFocusChange(IMEFocusState::Blurred);
  }

  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);
  mIMEFocusState = aIMEFocusState;
  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(IMEFocusState::BlurredWithoutFocusChange);
  }
}

void IMContextWrapper::OnSelectionChange(
    nsWindow* aCaller, const IMENotification& aIMENotification) {
  const bool isSelectionRangeChanged =
      mContentSelection.isNothing() ||
      !aIMENotification.mSelectionChangeData.EqualsRange(
          mContentSelection.ref());
  mContentSelection =
      Some(ContentSelection(aIMENotification.mSelectionChangeData));
  const bool retrievedSurroundingSignalReceived =
      mRetrieveSurroundingSignalReceived;
  mRetrieveSurroundingSignalReceived = false;

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

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

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

  if (aCaller != mLastFocusedWindow) {
    MOZ_LOG(gIMELog, 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(mContentSelection.isNothing())) {
      MOZ_LOG(gIMELog, LogLevel::Error,
              ("0x%p OnSelectionChange(), FAILED, "
               "new offset is too large, cannot keep composing",
               this));
    } else if (mContentSelection->HasRange()) {
      // Modify the selection start offset with new offset.
      mCompositionStart = mContentSelection->OffsetAndDataRef().StartOffset();
      // XXX We should modify mSelectedStringRemovedByComposition?
      // But how?
      MOZ_LOG(gIMELog, 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;
    } else {
      MOZ_LOG(
          gIMELog, LogLevel::Debug,
          ("0x%p OnSelectionChange(), ignored, because of no selection range",
           this));
      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.
  // Don't do this even if selection is not changed actually.  For example,
  // fcitx has direct input mode which does not insert composing string, but
  // inserts commited text for each key sequence (i.e., there is "invisible"
  // composition string).  In the world after bug 1712269, we don't use a
  // set of composition events for this kind of IME.  Therefore,
  // SelectionChangeData.mCausedByComposition is not expected value for here
  // if this call is caused by a preceding commit.  And if the preceding commit
  // is triggered by a key type for next word, resetting IME state makes fcitx
  // discard the pending input for the next word.  Thus, we need to check
  // whether the selection range is actually changed here.
  if (!selectionChangeData.mCausedByComposition &&
      !selectionChangeData.mCausedBySelectionEvent && isSelectionRangeChanged &&
      !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 (auto* provider = SelectionStyleProvider::GetExistingInstance()) {
    provider->OnThemeChanged();
  }
}

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

void IMContextWrapper::OnStartCompositionNative(GtkIMContext* aContext) {
  // IME may synthesize composition asynchronously after filtering a
  // GDK_KEY_PRESS event.  In that case, we should handle composition with
  // emulating the usual case, i.e., this is called in the stack of
  // OnKeyEvent().
  Maybe<AutoRestore<GdkEventKey*>> maybeRestoreProcessingKeyEvent;
  if (!mProcessingKeyEvent && !mPostingKeyEvents.IsEmpty()) {
    GdkEventKey* keyEvent = mPostingKeyEvents.GetFirstEvent();
    if (keyEvent && keyEvent->type == GDK_KEY_PRESS &&
        KeymapWrapper::ComputeDOMKeyNameIndex(keyEvent) ==
            KEY_NAME_INDEX_USE_STRING) {
      maybeRestoreProcessingKeyEvent.emplace(mProcessingKeyEvent);
      mProcessingKeyEvent = mPostingKeyEvents.GetFirstEvent();
    }
  }

  MOZ_LOG(gIMELog, 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(gIMELog, 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(gIMELog, 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(gIMELog, 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(gIMELog, LogLevel::Error,
            ("0x%p OnEndCompositionNative(), FAILED, "
--> --------------------

--> maximum size reached

--> --------------------

100%


¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.24Angebot  Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können  ¤

*Eine klare Vorstellung vom Zielzustand






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.