widget/gtk/nsGtkIMModule.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
     2 /* vim: set ts=4 et sw=4 tw=80: */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 #ifdef MOZ_LOGGING
     8 #define FORCE_PR_LOG /* Allow logging in the release build */
     9 #endif // MOZ_LOGGING
    10 #include "prlog.h"
    11 #include "prtime.h"
    13 #include "nsGtkIMModule.h"
    14 #include "nsWindow.h"
    15 #include "mozilla/Likely.h"
    16 #include "mozilla/MiscEvents.h"
    17 #include "mozilla/Preferences.h"
    18 #include "mozilla/TextEvents.h"
    20 using namespace mozilla;
    21 using namespace mozilla::widget;
    23 #ifdef PR_LOGGING
    24 PRLogModuleInfo* gGtkIMLog = nullptr;
    26 static const char*
    27 GetRangeTypeName(uint32_t aRangeType)
    28 {
    29     switch (aRangeType) {
    30         case NS_TEXTRANGE_RAWINPUT:
    31             return "NS_TEXTRANGE_RAWINPUT";
    32         case NS_TEXTRANGE_CONVERTEDTEXT:
    33             return "NS_TEXTRANGE_CONVERTEDTEXT";
    34         case NS_TEXTRANGE_SELECTEDRAWTEXT:
    35             return "NS_TEXTRANGE_SELECTEDRAWTEXT";
    36         case NS_TEXTRANGE_SELECTEDCONVERTEDTEXT:
    37             return "NS_TEXTRANGE_SELECTEDCONVERTEDTEXT";
    38         case NS_TEXTRANGE_CARETPOSITION:
    39             return "NS_TEXTRANGE_CARETPOSITION";
    40         default:
    41             return "UNKNOWN SELECTION TYPE!!";
    42     }
    43 }
    45 static const char*
    46 GetEnabledStateName(uint32_t aState)
    47 {
    48     switch (aState) {
    49         case IMEState::DISABLED:
    50             return "DISABLED";
    51         case IMEState::ENABLED:
    52             return "ENABLED";
    53         case IMEState::PASSWORD:
    54             return "PASSWORD";
    55         case IMEState::PLUGIN:
    56             return "PLUG_IN";
    57         default:
    58             return "UNKNOWN ENABLED STATUS!!";
    59     }
    60 }
    61 #endif
    63 const static bool kUseSimpleContextDefault = MOZ_WIDGET_GTK == 2;
    65 nsGtkIMModule* nsGtkIMModule::sLastFocusedModule = nullptr;
    66 bool nsGtkIMModule::sUseSimpleContext;
    68 nsGtkIMModule::nsGtkIMModule(nsWindow* aOwnerWindow) :
    69     mOwnerWindow(aOwnerWindow), mLastFocusedWindow(nullptr),
    70     mContext(nullptr),
    71     mSimpleContext(nullptr),
    72     mDummyContext(nullptr),
    73     mCompositionStart(UINT32_MAX), mProcessingKeyEvent(nullptr),
    74     mCompositionTargetOffset(UINT32_MAX),
    75     mCompositionState(eCompositionState_NotComposing),
    76     mIsIMFocused(false), mIgnoreNativeCompositionEvent(false)
    77 {
    78 #ifdef PR_LOGGING
    79     if (!gGtkIMLog) {
    80         gGtkIMLog = PR_NewLogModule("nsGtkIMModuleWidgets");
    81     }
    82 #endif
    83     static bool sFirstInstance = true;
    84     if (sFirstInstance) {
    85         sFirstInstance = false;
    86         sUseSimpleContext =
    87             Preferences::GetBool(
    88                 "intl.ime.use_simple_context_on_password_field",
    89                 kUseSimpleContextDefault);
    90     }
    91     Init();
    92 }
    94 void
    95 nsGtkIMModule::Init()
    96 {
    97     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
    98         ("GtkIMModule(%p): Init, mOwnerWindow=%p",
    99          this, mOwnerWindow));
   101     MozContainer* container = mOwnerWindow->GetMozContainer();
   102     NS_PRECONDITION(container, "container is null");
   103     GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(container));
   105     // NOTE: gtk_im_*_new() abort (kill) the whole process when it fails.
   106     //       So, we don't need to check the result.
   108     // Normal context.
   109     mContext = gtk_im_multicontext_new();
   110     gtk_im_context_set_client_window(mContext, gdkWindow);
   111     g_signal_connect(mContext, "preedit_changed",
   112                      G_CALLBACK(nsGtkIMModule::OnChangeCompositionCallback),
   113                      this);
   114     g_signal_connect(mContext, "retrieve_surrounding",
   115                      G_CALLBACK(nsGtkIMModule::OnRetrieveSurroundingCallback),
   116                      this);
   117     g_signal_connect(mContext, "delete_surrounding",
   118                      G_CALLBACK(nsGtkIMModule::OnDeleteSurroundingCallback),
   119                      this);
   120     g_signal_connect(mContext, "commit",
   121                      G_CALLBACK(nsGtkIMModule::OnCommitCompositionCallback),
   122                      this);
   123     g_signal_connect(mContext, "preedit_start",
   124                      G_CALLBACK(nsGtkIMModule::OnStartCompositionCallback),
   125                      this);
   126     g_signal_connect(mContext, "preedit_end",
   127                      G_CALLBACK(nsGtkIMModule::OnEndCompositionCallback),
   128                      this);
   130     // Simple context
   131     if (sUseSimpleContext) {
   132         mSimpleContext = gtk_im_context_simple_new();
   133         gtk_im_context_set_client_window(mSimpleContext, gdkWindow);
   134         g_signal_connect(mSimpleContext, "preedit_changed",
   135             G_CALLBACK(&nsGtkIMModule::OnChangeCompositionCallback),
   136             this);
   137         g_signal_connect(mSimpleContext, "retrieve_surrounding",
   138             G_CALLBACK(&nsGtkIMModule::OnRetrieveSurroundingCallback),
   139             this);
   140         g_signal_connect(mSimpleContext, "delete_surrounding",
   141             G_CALLBACK(&nsGtkIMModule::OnDeleteSurroundingCallback),
   142             this);
   143         g_signal_connect(mSimpleContext, "commit",
   144             G_CALLBACK(&nsGtkIMModule::OnCommitCompositionCallback),
   145             this);
   146         g_signal_connect(mSimpleContext, "preedit_start",
   147             G_CALLBACK(nsGtkIMModule::OnStartCompositionCallback),
   148             this);
   149         g_signal_connect(mSimpleContext, "preedit_end",
   150             G_CALLBACK(nsGtkIMModule::OnEndCompositionCallback),
   151             this);
   152     }
   154     // Dummy context
   155     mDummyContext = gtk_im_multicontext_new();
   156     gtk_im_context_set_client_window(mDummyContext, gdkWindow);
   157 }
   159 nsGtkIMModule::~nsGtkIMModule()
   160 {
   161     if (this == sLastFocusedModule) {
   162         sLastFocusedModule = nullptr;
   163     }
   164     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   165         ("GtkIMModule(%p) was gone", this));
   166 }
   168 void
   169 nsGtkIMModule::OnDestroyWindow(nsWindow* aWindow)
   170 {
   171     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   172         ("GtkIMModule(%p): OnDestroyWindow, aWindow=%p, mLastFocusedWindow=%p, mOwnerWindow=%p, mLastFocusedModule=%p",
   173          this, aWindow, mLastFocusedWindow, mOwnerWindow, sLastFocusedModule));
   175     NS_PRECONDITION(aWindow, "aWindow must not be null");
   177     if (mLastFocusedWindow == aWindow) {
   178         CancelIMEComposition(aWindow);
   179         if (mIsIMFocused) {
   180             Blur();
   181         }
   182         mLastFocusedWindow = nullptr;
   183     }
   185     if (mOwnerWindow != aWindow) {
   186         return;
   187     }
   189     if (sLastFocusedModule == this) {
   190         sLastFocusedModule = nullptr;
   191     }
   193     /**
   194      * NOTE:
   195      *   The given window is the owner of this, so, we must release the
   196      *   contexts now.  But that might be referred from other nsWindows
   197      *   (they are children of this.  But we don't know why there are the
   198      *   cases).  So, we need to clear the pointers that refers to contexts
   199      *   and this if the other referrers are still alive. See bug 349727.
   200      */
   201     if (mContext) {
   202         PrepareToDestroyContext(mContext);
   203         gtk_im_context_set_client_window(mContext, nullptr);
   204         g_object_unref(mContext);
   205         mContext = nullptr;
   206     }
   208     if (mSimpleContext) {
   209         gtk_im_context_set_client_window(mSimpleContext, nullptr);
   210         g_object_unref(mSimpleContext);
   211         mSimpleContext = nullptr;
   212     }
   214     if (mDummyContext) {
   215         // mContext and mDummyContext have the same slaveType and signal_data
   216         // so no need for another workaround_gtk_im_display_closed.
   217         gtk_im_context_set_client_window(mDummyContext, nullptr);
   218         g_object_unref(mDummyContext);
   219         mDummyContext = nullptr;
   220     }
   222     mOwnerWindow = nullptr;
   223     mLastFocusedWindow = nullptr;
   224     mInputContext.mIMEState.mEnabled = IMEState::DISABLED;
   226     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   227         ("    SUCCEEDED, Completely destroyed"));
   228 }
   230 // Work around gtk bug http://bugzilla.gnome.org/show_bug.cgi?id=483223:
   231 // (and the similar issue of GTK+ IIIM)
   232 // The GTK+ XIM and IIIM modules register handlers for the "closed" signal
   233 // on the display, but:
   234 //  * The signal handlers are not disconnected when the module is unloaded.
   235 //
   236 // The GTK+ XIM module has another problem:
   237 //  * When the signal handler is run (with the module loaded) it tries
   238 //    XFree (and fails) on a pointer that did not come from Xmalloc.
   239 //
   240 // To prevent these modules from being unloaded, use static variables to
   241 // hold ref of GtkIMContext class.
   242 // For GTK+ XIM module, to prevent the signal handler from being run,
   243 // find the signal handlers and remove them.
   244 //
   245 // GtkIMContextXIMs share XOpenIM connections and display closed signal
   246 // handlers (where possible).
   248 void
   249 nsGtkIMModule::PrepareToDestroyContext(GtkIMContext *aContext)
   250 {
   251     MozContainer* container = mOwnerWindow->GetMozContainer();
   252     NS_PRECONDITION(container, "The container of the window is null");
   254     GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT(aContext);
   255 #if (MOZ_WIDGET_GTK == 2)
   256     GtkIMContext *slave = multicontext->slave;
   257 #else
   258     GtkIMContext *slave = nullptr; //TODO GTK3
   259 #endif
   260     if (!slave) {
   261         return;
   262     }
   264     GType slaveType = G_TYPE_FROM_INSTANCE(slave);
   265     const gchar *im_type_name = g_type_name(slaveType);
   266     if (strcmp(im_type_name, "GtkIMContextXIM") == 0) {
   267         if (gtk_check_version(2, 12, 1) == nullptr) {
   268             return; // gtk bug has been fixed
   269         }
   271         struct GtkIMContextXIM
   272         {
   273             GtkIMContext parent;
   274             gpointer private_data;
   275             // ... other fields
   276         };
   278         gpointer signal_data =
   279             reinterpret_cast<GtkIMContextXIM*>(slave)->private_data;
   280         if (!signal_data) {
   281             return;
   282         }
   284         g_signal_handlers_disconnect_matched(
   285             gtk_widget_get_display(GTK_WIDGET(container)),
   286             G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, signal_data);
   288         // Add a reference to prevent the XIM module from being unloaded
   289         // and reloaded: each time the module is loaded and used, it
   290         // opens (and doesn't close) new XOpenIM connections.
   291         static gpointer gtk_xim_context_class =
   292             g_type_class_ref(slaveType);
   293         // Mute unused variable warning:
   294         (void)gtk_xim_context_class;
   295     } else if (strcmp(im_type_name, "GtkIMContextIIIM") == 0) {
   296         // Add a reference to prevent the IIIM module from being unloaded
   297         static gpointer gtk_iiim_context_class =
   298             g_type_class_ref(slaveType);
   299         // Mute unused variable warning:
   300         (void)gtk_iiim_context_class;
   301     }
   302 }
   304 void
   305 nsGtkIMModule::OnFocusWindow(nsWindow* aWindow)
   306 {
   307     if (MOZ_UNLIKELY(IsDestroyed())) {
   308         return;
   309     }
   311     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   312         ("GtkIMModule(%p): OnFocusWindow, aWindow=%p, mLastFocusedWindow=%p",
   313          this, aWindow, mLastFocusedWindow));
   314     mLastFocusedWindow = aWindow;
   315     Focus();
   316 }
   318 void
   319 nsGtkIMModule::OnBlurWindow(nsWindow* aWindow)
   320 {
   321     if (MOZ_UNLIKELY(IsDestroyed())) {
   322         return;
   323     }
   325     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   326         ("GtkIMModule(%p): OnBlurWindow, aWindow=%p, mLastFocusedWindow=%p, mIsIMFocused=%s",
   327          this, aWindow, mLastFocusedWindow, mIsIMFocused ? "YES" : "NO"));
   329     if (!mIsIMFocused || mLastFocusedWindow != aWindow) {
   330         return;
   331     }
   333     Blur();
   334 }
   336 bool
   337 nsGtkIMModule::OnKeyEvent(nsWindow* aCaller, GdkEventKey* aEvent,
   338                           bool aKeyDownEventWasSent /* = false */)
   339 {
   340     NS_PRECONDITION(aEvent, "aEvent must be non-null");
   342     if (!IsEditable() || MOZ_UNLIKELY(IsDestroyed())) {
   343         return false;
   344     }
   346     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   347         ("GtkIMModule(%p): OnKeyEvent, aCaller=%p, aKeyDownEventWasSent=%s",
   348          this, aCaller, aKeyDownEventWasSent ? "TRUE" : "FALSE"));
   349     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   350         ("    aEvent: type=%s, keyval=%s, unicode=0x%X",
   351          aEvent->type == GDK_KEY_PRESS ? "GDK_KEY_PRESS" :
   352          aEvent->type == GDK_KEY_RELEASE ? "GDK_KEY_RELEASE" : "Unknown",
   353          gdk_keyval_name(aEvent->keyval),
   354          gdk_keyval_to_unicode(aEvent->keyval)));
   356     if (aCaller != mLastFocusedWindow) {
   357         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   358             ("    FAILED, the caller isn't focused window, mLastFocusedWindow=%p",
   359              mLastFocusedWindow));
   360         return false;
   361     }
   363     GtkIMContext* im = GetContext();
   364     if (MOZ_UNLIKELY(!im)) {
   365         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   366             ("    FAILED, there are no context"));
   367         return false;
   368     }
   370     mKeyDownEventWasSent = aKeyDownEventWasSent;
   371     mFilterKeyEvent = true;
   372     mProcessingKeyEvent = aEvent;
   373     gboolean isFiltered = gtk_im_context_filter_keypress(im, aEvent);
   374     mProcessingKeyEvent = nullptr;
   376     // We filter the key event if the event was not committed (because
   377     // it's probably part of a composition) or if the key event was
   378     // committed _and_ changed.  This way we still let key press
   379     // events go through as simple key press events instead of
   380     // composed characters.
   381     bool filterThisEvent = isFiltered && mFilterKeyEvent;
   383     if (IsComposing() && !isFiltered) {
   384         if (aEvent->type == GDK_KEY_PRESS) {
   385             if (!mDispatchedCompositionString.IsEmpty()) {
   386                 // If there is composition string, we shouldn't dispatch
   387                 // any keydown events during composition.
   388                 filterThisEvent = true;
   389             } else {
   390                 // A Hangul input engine for SCIM doesn't emit preedit_end
   391                 // signal even when composition string becomes empty.  On the
   392                 // other hand, we should allow to make composition with empty
   393                 // string for other languages because there *might* be such
   394                 // IM.  For compromising this issue, we should dispatch
   395                 // compositionend event, however, we don't need to reset IM
   396                 // actually.
   397                 CommitCompositionBy(EmptyString());
   398                 filterThisEvent = false;
   399             }
   400         } else {
   401             // Key release event may not be consumed by IM, however, we
   402             // shouldn't dispatch any keyup event during composition.
   403             filterThisEvent = true;
   404         }
   405     }
   407     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   408         ("    filterThisEvent=%s (isFiltered=%s, mFilterKeyEvent=%s)",
   409          filterThisEvent ? "TRUE" : "FALSE", isFiltered ? "YES" : "NO",
   410          mFilterKeyEvent ? "YES" : "NO"));
   412     return filterThisEvent;
   413 }
   415 void
   416 nsGtkIMModule::OnFocusChangeInGecko(bool aFocus)
   417 {
   418     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   419         ("GtkIMModule(%p): OnFocusChangeInGecko, aFocus=%s, "
   420          "mCompositionState=%s, mIsIMFocused=%s, "
   421          "mIgnoreNativeCompositionEvent=%s",
   422          this, aFocus ? "YES" : "NO", GetCompositionStateName(),
   423          mIsIMFocused ? "YES" : "NO",
   424          mIgnoreNativeCompositionEvent ? "YES" : "NO"));
   426     // We shouldn't carry over the removed string to another editor.
   427     mSelectedString.Truncate();
   429     if (aFocus) {
   430         // If we failed to commit forcedely in previous focused editor,
   431         // we should reopen the gate for native signals in new focused editor.
   432         mIgnoreNativeCompositionEvent = false;
   433     }
   434 }
   436 void
   437 nsGtkIMModule::ResetIME()
   438 {
   439     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   440         ("GtkIMModule(%p): ResetIME, mCompositionState=%s, mIsIMFocused=%s",
   441          this, GetCompositionStateName(), mIsIMFocused ? "YES" : "NO"));
   443     GtkIMContext *im = GetContext();
   444     if (MOZ_UNLIKELY(!im)) {
   445         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   446             ("    FAILED, there are no context"));
   447         return;
   448     }
   450     mIgnoreNativeCompositionEvent = true;
   451     gtk_im_context_reset(im);
   452 }
   454 nsresult
   455 nsGtkIMModule::CommitIMEComposition(nsWindow* aCaller)
   456 {
   457     if (MOZ_UNLIKELY(IsDestroyed())) {
   458         return NS_OK;
   459     }
   461     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   462         ("GtkIMModule(%p): CommitIMEComposition, aCaller=%p, "
   463          "mCompositionState=%s",
   464          this, aCaller, GetCompositionStateName()));
   466     if (aCaller != mLastFocusedWindow) {
   467         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   468             ("    WARNING: the caller isn't focused window, mLastFocusedWindow=%p",
   469              mLastFocusedWindow));
   470         return NS_OK;
   471     }
   473     if (!IsComposing()) {
   474         return NS_OK;
   475     }
   477     // XXX We should commit composition ourselves temporary...
   478     ResetIME();
   479     CommitCompositionBy(mDispatchedCompositionString);
   481     return NS_OK;
   482 }
   484 nsresult
   485 nsGtkIMModule::CancelIMEComposition(nsWindow* aCaller)
   486 {
   487     if (MOZ_UNLIKELY(IsDestroyed())) {
   488         return NS_OK;
   489     }
   491     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   492         ("GtkIMModule(%p): CancelIMEComposition, aCaller=%p",
   493          this, aCaller));
   495     if (aCaller != mLastFocusedWindow) {
   496         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   497             ("    FAILED, the caller isn't focused window, mLastFocusedWindow=%p",
   498              mLastFocusedWindow));
   499         return NS_OK;
   500     }
   502     if (!IsComposing()) {
   503         return NS_OK;
   504     }
   506     GtkIMContext *im = GetContext();
   507     if (MOZ_UNLIKELY(!im)) {
   508         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   509             ("    FAILED, there are no context"));
   510         return NS_OK;
   511     }
   513     ResetIME();
   514     CommitCompositionBy(EmptyString());
   516     return NS_OK;
   517 }
   519 void
   520 nsGtkIMModule::OnUpdateComposition(void)
   521 {
   522     if (MOZ_UNLIKELY(IsDestroyed())) {
   523         return;
   524     }
   526     SetCursorPosition(mCompositionTargetOffset);
   527 }
   529 void
   530 nsGtkIMModule::SetInputContext(nsWindow* aCaller,
   531                                const InputContext* aContext,
   532                                const InputContextAction* aAction)
   533 {
   534     if (MOZ_UNLIKELY(IsDestroyed())) {
   535         return;
   536     }
   538     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   539         ("GtkIMModule(%p): SetInputContext, aCaller=%p, aState=%s mHTMLInputType=%s",
   540          this, aCaller, GetEnabledStateName(aContext->mIMEState.mEnabled),
   541          NS_ConvertUTF16toUTF8(aContext->mHTMLInputType).get()));
   543     if (aCaller != mLastFocusedWindow) {
   544         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   545             ("    FAILED, the caller isn't focused window, mLastFocusedWindow=%p",
   546              mLastFocusedWindow));
   547         return;
   548     }
   550     if (!mContext) {
   551         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   552             ("    FAILED, there are no context"));
   553         return;
   554     }
   557     if (sLastFocusedModule != this) {
   558         mInputContext = *aContext;
   559         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   560             ("    SUCCEEDED, but we're not active"));
   561         return;
   562     }
   564     bool changingEnabledState =
   565         aContext->mIMEState.mEnabled != mInputContext.mIMEState.mEnabled ||
   566         aContext->mHTMLInputType != mInputContext.mHTMLInputType;
   568     // Release current IME focus if IME is enabled.
   569     if (changingEnabledState && IsEditable()) {
   570         CommitIMEComposition(mLastFocusedWindow);
   571         Blur();
   572     }
   574     mInputContext = *aContext;
   576     if (changingEnabledState) {
   577 #if (MOZ_WIDGET_GTK == 3)
   578         static bool sInputPurposeSupported = !gtk_check_version(3, 6, 0);
   579         if (sInputPurposeSupported && IsEditable()) {
   580             GtkIMContext* context = GetContext();
   581             if (context) {
   582                 GtkInputPurpose purpose = GTK_INPUT_PURPOSE_FREE_FORM;
   583                 const nsString& inputType = mInputContext.mHTMLInputType;
   584                 // Password case has difficult issue.  Desktop IMEs disable
   585                 // composition if input-purpose is password.
   586                 // For disabling IME on |ime-mode: disabled;|, we need to check
   587                 // mEnabled value instead of inputType value.  This hack also
   588                 // enables composition on
   589                 // <input type="password" style="ime-mode: enabled;">.
   590                 // This is right behavior of ime-mode on desktop.
   591                 //
   592                 // On the other hand, IME for tablet devices may provide a
   593                 // specific software keyboard for password field.  If so,
   594                 // the behavior might look strange on both:
   595                 //   <input type="text" style="ime-mode: disabled;">
   596                 //   <input type="password" style="ime-mode: enabled;">
   597                 //
   598                 // Temporarily, we should focus on desktop environment for now.
   599                 // I.e., let's ignore tablet devices for now.  When somebody
   600                 // reports actual trouble on tablet devices, we should try to
   601                 // look for a way to solve actual problem.
   602                 if (mInputContext.mIMEState.mEnabled == IMEState::PASSWORD) {
   603                     purpose = GTK_INPUT_PURPOSE_PASSWORD;
   604                 } else if (inputType.EqualsLiteral("email")) {
   605                     purpose = GTK_INPUT_PURPOSE_EMAIL;
   606                 } else if (inputType.EqualsLiteral("url")) {
   607                     purpose = GTK_INPUT_PURPOSE_URL;
   608                 } else if (inputType.EqualsLiteral("tel")) {
   609                     purpose = GTK_INPUT_PURPOSE_PHONE;
   610                 } else if (inputType.EqualsLiteral("number")) {
   611                     purpose = GTK_INPUT_PURPOSE_NUMBER;
   612                 }
   614                 g_object_set(context, "input-purpose", purpose, nullptr);
   615             }
   616         }
   617 #endif // #if (MOZ_WIDGET_GTK == 3)
   619         // Even when aState is not enabled state, we need to set IME focus.
   620         // Because some IMs are updating the status bar of them at this time.
   621         // Be aware, don't use aWindow here because this method shouldn't move
   622         // focus actually.
   623         Focus();
   625         // XXX Should we call Blur() when it's not editable?  E.g., it might be
   626         //     better to close VKB automatically.
   627     }
   628 }
   630 InputContext
   631 nsGtkIMModule::GetInputContext()
   632 {
   633     mInputContext.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
   634     return mInputContext;
   635 }
   637 /* static */
   638 bool
   639 nsGtkIMModule::IsVirtualKeyboardOpened()
   640 {
   641     return false;
   642 }
   644 GtkIMContext*
   645 nsGtkIMModule::GetContext()
   646 {
   647     if (IsEnabled()) {
   648         return mContext;
   649     }
   650     if (mInputContext.mIMEState.mEnabled == IMEState::PASSWORD) {
   651         return mSimpleContext;
   652     }
   653     return mDummyContext;
   654 }
   656 bool
   657 nsGtkIMModule::IsEnabled()
   658 {
   659     return mInputContext.mIMEState.mEnabled == IMEState::ENABLED ||
   660            mInputContext.mIMEState.mEnabled == IMEState::PLUGIN ||
   661            (!sUseSimpleContext &&
   662             mInputContext.mIMEState.mEnabled == IMEState::PASSWORD);
   663 }
   665 bool
   666 nsGtkIMModule::IsEditable()
   667 {
   668     return mInputContext.mIMEState.mEnabled == IMEState::ENABLED ||
   669            mInputContext.mIMEState.mEnabled == IMEState::PLUGIN ||
   670            mInputContext.mIMEState.mEnabled == IMEState::PASSWORD;
   671 }
   673 void
   674 nsGtkIMModule::Focus()
   675 {
   676     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   677         ("GtkIMModule(%p): Focus, sLastFocusedModule=%p",
   678          this, sLastFocusedModule));
   680     if (mIsIMFocused) {
   681         NS_ASSERTION(sLastFocusedModule == this,
   682                      "We're not active, but the IM was focused?");
   683         return;
   684     }
   686     GtkIMContext *im = GetContext();
   687     if (!im) {
   688         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   689             ("    FAILED, there are no context"));
   690         return;
   691     }
   693     if (sLastFocusedModule && sLastFocusedModule != this) {
   694         sLastFocusedModule->Blur();
   695     }
   697     sLastFocusedModule = this;
   699     gtk_im_context_focus_in(im);
   700     mIsIMFocused = true;
   702     if (!IsEnabled()) {
   703         // We should release IME focus for uim and scim.
   704         // These IMs are using snooper that is released at losing focus.
   705         Blur();
   706     }
   707 }
   709 void
   710 nsGtkIMModule::Blur()
   711 {
   712     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   713         ("GtkIMModule(%p): Blur, mIsIMFocused=%s",
   714          this, mIsIMFocused ? "YES" : "NO"));
   716     if (!mIsIMFocused) {
   717         return;
   718     }
   720     GtkIMContext *im = GetContext();
   721     if (!im) {
   722         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   723             ("    FAILED, there are no context"));
   724         return;
   725     }
   727     gtk_im_context_focus_out(im);
   728     mIsIMFocused = false;
   729 }
   731 /* static */
   732 void
   733 nsGtkIMModule::OnStartCompositionCallback(GtkIMContext *aContext,
   734                                           nsGtkIMModule* aModule)
   735 {
   736     aModule->OnStartCompositionNative(aContext);
   737 }
   739 void
   740 nsGtkIMModule::OnStartCompositionNative(GtkIMContext *aContext)
   741 {
   742     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   743         ("GtkIMModule(%p): OnStartCompositionNative, aContext=%p",
   744          this, aContext));
   746     // See bug 472635, we should do nothing if IM context doesn't match.
   747     if (GetContext() != aContext) {
   748         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   749             ("    FAILED, given context doesn't match, GetContext()=%p",
   750              GetContext()));
   751         return;
   752     }
   754     if (!DispatchCompositionStart()) {
   755         return;
   756     }
   757     mCompositionTargetOffset = mCompositionStart;
   758 }
   760 /* static */
   761 void
   762 nsGtkIMModule::OnEndCompositionCallback(GtkIMContext *aContext,
   763                                         nsGtkIMModule* aModule)
   764 {
   765     aModule->OnEndCompositionNative(aContext);
   766 }
   768 void
   769 nsGtkIMModule::OnEndCompositionNative(GtkIMContext *aContext)
   770 {
   771     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   772         ("GtkIMModule(%p): OnEndCompositionNative, aContext=%p",
   773          this, aContext));
   775     // See bug 472635, we should do nothing if IM context doesn't match.
   776     if (GetContext() != aContext) {
   777         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   778             ("    FAILED, given context doesn't match, GetContext()=%p",
   779              GetContext()));
   780         return;
   781     }
   783     bool shouldIgnoreThisEvent = ShouldIgnoreNativeCompositionEvent();
   785     // Finish the cancelling mode here rather than DispatchCompositionEnd()
   786     // because DispatchCompositionEnd() is called ourselves when we need to
   787     // commit the composition string *before* the focus moves completely.
   788     // Note that the native commit can be fired *after* ResetIME().
   789     mIgnoreNativeCompositionEvent = false;
   791     if (!IsComposing() || shouldIgnoreThisEvent) {
   792         // If we already handled the commit event, we should do nothing here.
   793         return;
   794     }
   796     // Be aware, widget can be gone
   797     DispatchCompositionEnd();
   798 }
   800 /* static */
   801 void
   802 nsGtkIMModule::OnChangeCompositionCallback(GtkIMContext *aContext,
   803                                            nsGtkIMModule* aModule)
   804 {
   805     aModule->OnChangeCompositionNative(aContext);
   806 }
   808 void
   809 nsGtkIMModule::OnChangeCompositionNative(GtkIMContext *aContext)
   810 {
   811     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   812         ("GtkIMModule(%p): OnChangeCompositionNative, aContext=%p",
   813          this, aContext));
   815     // See bug 472635, we should do nothing if IM context doesn't match.
   816     if (GetContext() != aContext) {
   817         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   818             ("    FAILED, given context doesn't match, GetContext()=%p",
   819              GetContext()));
   820         return;
   821     }
   823     if (ShouldIgnoreNativeCompositionEvent()) {
   824         return;
   825     }
   827     nsAutoString compositionString;
   828     GetCompositionString(compositionString);
   829     if (!IsComposing() && compositionString.IsEmpty()) {
   830         mDispatchedCompositionString.Truncate();
   831         return; // Don't start the composition with empty string.
   832     }
   834     // Be aware, widget can be gone
   835     DispatchTextEvent(compositionString, false);
   836 }
   838 /* static */
   839 gboolean
   840 nsGtkIMModule::OnRetrieveSurroundingCallback(GtkIMContext  *aContext,
   841                                              nsGtkIMModule *aModule)
   842 {
   843     return aModule->OnRetrieveSurroundingNative(aContext);
   844 }
   846 gboolean
   847 nsGtkIMModule::OnRetrieveSurroundingNative(GtkIMContext *aContext)
   848 {
   849     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   850         ("GtkIMModule(%p): OnRetrieveSurroundingNative, aContext=%p, current context=%p",
   851          this, aContext, GetContext()));
   853     if (GetContext() != aContext) {
   854         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   855             ("    FAILED, given context doesn't match, GetContext()=%p",
   856              GetContext()));
   857         return FALSE;
   858     }
   860     nsAutoString uniStr;
   861     uint32_t cursorPos;
   862     if (NS_FAILED(GetCurrentParagraph(uniStr, cursorPos))) {
   863         return FALSE;
   864     }
   866     NS_ConvertUTF16toUTF8 utf8Str(nsDependentSubstring(uniStr, 0, cursorPos));
   867     uint32_t cursorPosInUTF8 = utf8Str.Length();
   868     AppendUTF16toUTF8(nsDependentSubstring(uniStr, cursorPos), utf8Str);
   869     gtk_im_context_set_surrounding(aContext, utf8Str.get(), utf8Str.Length(),
   870                                    cursorPosInUTF8);
   871     return TRUE;
   872 }
   874 /* static */
   875 gboolean
   876 nsGtkIMModule::OnDeleteSurroundingCallback(GtkIMContext  *aContext,
   877                                            gint           aOffset,
   878                                            gint           aNChars,
   879                                            nsGtkIMModule *aModule)
   880 {
   881     return aModule->OnDeleteSurroundingNative(aContext, aOffset, aNChars);
   882 }
   884 gboolean
   885 nsGtkIMModule::OnDeleteSurroundingNative(GtkIMContext  *aContext,
   886                                          gint           aOffset,
   887                                          gint           aNChars)
   888 {
   889     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   890         ("GtkIMModule(%p): OnDeleteSurroundingNative, aContext=%p, current context=%p",
   891          this, aContext, GetContext()));
   893     if (GetContext() != aContext) {
   894         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   895             ("    FAILED, given context doesn't match, GetContext()=%p",
   896              GetContext()));
   897         return FALSE;
   898     }
   900     if (NS_SUCCEEDED(DeleteText(aOffset, (uint32_t)aNChars))) {
   901         return TRUE;
   902     }
   904     // failed
   905     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   906         ("    FAILED, cannot delete text"));
   907     return FALSE;
   908 }
   910 /* static */
   911 void
   912 nsGtkIMModule::OnCommitCompositionCallback(GtkIMContext *aContext,
   913                                            const gchar *aString,
   914                                            nsGtkIMModule* aModule)
   915 {
   916     aModule->OnCommitCompositionNative(aContext, aString);
   917 }
   919 void
   920 nsGtkIMModule::OnCommitCompositionNative(GtkIMContext *aContext,
   921                                          const gchar *aUTF8Char)
   922 {
   923     const gchar emptyStr = 0;
   924     const gchar *commitString = aUTF8Char ? aUTF8Char : &emptyStr;
   926     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   927         ("GtkIMModule(%p): OnCommitCompositionNative, aContext=%p, current context=%p, commitString=\"%s\"",
   928          this, aContext, GetContext(), commitString));
   930     // See bug 472635, we should do nothing if IM context doesn't match.
   931     if (GetContext() != aContext) {
   932         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   933             ("    FAILED, given context doesn't match, GetContext()=%p",
   934              GetContext()));
   935         return;
   936     }
   938     // If we are not in composition and committing with empty string,
   939     // we need to do nothing because if we continued to handle this
   940     // signal, we would dispatch compositionstart, text, compositionend
   941     // events with empty string.  Of course, they are unnecessary events
   942     // for Web applications and our editor.
   943     if (!IsComposing() && !commitString[0]) {
   944         return;
   945     }
   947     if (ShouldIgnoreNativeCompositionEvent()) {
   948         return;
   949     }
   951     // If IME doesn't change their keyevent that generated this commit,
   952     // don't send it through XIM - just send it as a normal key press
   953     // event.
   954     if (!IsComposing() && mProcessingKeyEvent) {
   955         char keyval_utf8[8]; /* should have at least 6 bytes of space */
   956         gint keyval_utf8_len;
   957         guint32 keyval_unicode;
   959         keyval_unicode = gdk_keyval_to_unicode(mProcessingKeyEvent->keyval);
   960         keyval_utf8_len = g_unichar_to_utf8(keyval_unicode, keyval_utf8);
   961         keyval_utf8[keyval_utf8_len] = '\0';
   963         if (!strcmp(commitString, keyval_utf8)) {
   964             PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   965                 ("GtkIMModule(%p): OnCommitCompositionNative, we'll send normal key event",
   966                  this));
   967             mFilterKeyEvent = false;
   968             return;
   969         }
   970     }
   972     NS_ConvertUTF8toUTF16 str(commitString);
   973     CommitCompositionBy(str); // Be aware, widget can be gone
   974 }
   976 bool
   977 nsGtkIMModule::CommitCompositionBy(const nsAString& aString)
   978 {
   979     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   980         ("GtkIMModule(%p): CommitCompositionBy, aString=\"%s\", "
   981          "mDispatchedCompositionString=\"%s\"",
   982          this, NS_ConvertUTF16toUTF8(aString).get(),
   983          NS_ConvertUTF16toUTF8(mDispatchedCompositionString).get()));
   985     if (!DispatchTextEvent(aString, true)) {
   986         return false;
   987     }
   988     // We should dispatch the compositionend event here because some IMEs
   989     // might not fire "preedit_end" native event.
   990     return DispatchCompositionEnd(); // Be aware, widget can be gone
   991 }
   993 void
   994 nsGtkIMModule::GetCompositionString(nsAString &aCompositionString)
   995 {
   996     gchar *preedit_string;
   997     gint cursor_pos;
   998     PangoAttrList *feedback_list;
   999     gtk_im_context_get_preedit_string(GetContext(), &preedit_string,
  1000                                       &feedback_list, &cursor_pos);
  1001     if (preedit_string && *preedit_string) {
  1002         CopyUTF8toUTF16(preedit_string, aCompositionString);
  1003     } else {
  1004         aCompositionString.Truncate();
  1007     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1008         ("GtkIMModule(%p): GetCompositionString, result=\"%s\"",
  1009          this, preedit_string));
  1011     pango_attr_list_unref(feedback_list);
  1012     g_free(preedit_string);
  1015 bool
  1016 nsGtkIMModule::DispatchCompositionStart()
  1018     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1019         ("GtkIMModule(%p): DispatchCompositionStart", this));
  1021     if (IsComposing()) {
  1022         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1023             ("    WARNING, we're already in composition"));
  1024         return true;
  1027     if (!mLastFocusedWindow) {
  1028         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1029             ("    FAILED, there are no focused window in this module"));
  1030         return false;
  1033     nsEventStatus status;
  1034     WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT,
  1035                                       mLastFocusedWindow);
  1036     InitEvent(selection);
  1037     mLastFocusedWindow->DispatchEvent(&selection, status);
  1039     if (!selection.mSucceeded || selection.mReply.mOffset == UINT32_MAX) {
  1040         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1041             ("    FAILED, cannot query the selection offset"));
  1042         return false;
  1045     // XXX The composition start point might be changed by composition events
  1046     //     even though we strongly hope it doesn't happen.
  1047     //     Every composition event should have the start offset for the result
  1048     //     because it may high cost if we query the offset every time.
  1049     mCompositionStart = selection.mReply.mOffset;
  1050     mDispatchedCompositionString.Truncate();
  1052     if (mProcessingKeyEvent && !mKeyDownEventWasSent &&
  1053         mProcessingKeyEvent->type == GDK_KEY_PRESS) {
  1054         // If this composition is started by a native keydown event, we need to
  1055         // dispatch our keydown event here (before composition start).
  1056         nsCOMPtr<nsIWidget> kungFuDeathGrip = mLastFocusedWindow;
  1057         bool isCancelled;
  1058         mLastFocusedWindow->DispatchKeyDownEvent(mProcessingKeyEvent,
  1059                                                  &isCancelled);
  1060         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1061             ("    keydown event is dispatched"));
  1062         if (static_cast<nsWindow*>(kungFuDeathGrip.get())->IsDestroyed() ||
  1063             kungFuDeathGrip != mLastFocusedWindow) {
  1064             PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1065                 ("    NOTE, the focused widget was destroyed/changed by keydown event"));
  1066             return false;
  1070     if (mIgnoreNativeCompositionEvent) {
  1071         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1072             ("    WARNING, mIgnoreNativeCompositionEvent is already TRUE, but we forcedly reset"));
  1073         mIgnoreNativeCompositionEvent = false;
  1076     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1077         ("    mCompositionStart=%u", mCompositionStart));
  1078     mCompositionState = eCompositionState_CompositionStartDispatched;
  1079     WidgetCompositionEvent compEvent(true, NS_COMPOSITION_START,
  1080                                      mLastFocusedWindow);
  1081     InitEvent(compEvent);
  1082     nsCOMPtr<nsIWidget> kungFuDeathGrip = mLastFocusedWindow;
  1083     mLastFocusedWindow->DispatchEvent(&compEvent, status);
  1084     if (static_cast<nsWindow*>(kungFuDeathGrip.get())->IsDestroyed() ||
  1085         kungFuDeathGrip != mLastFocusedWindow) {
  1086         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1087             ("    NOTE, the focused widget was destroyed/changed by compositionstart event"));
  1088         return false;
  1091     return true;
  1094 bool
  1095 nsGtkIMModule::DispatchCompositionEnd()
  1097     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1098         ("GtkIMModule(%p): DispatchCompositionEnd, "
  1099          "mDispatchedCompositionString=\"%s\"",
  1100          this, NS_ConvertUTF16toUTF8(mDispatchedCompositionString).get()));
  1102     if (!IsComposing()) {
  1103         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1104             ("    WARNING, we have alrady finished the composition"));
  1105         return false;
  1108     if (!mLastFocusedWindow) {
  1109         mDispatchedCompositionString.Truncate();
  1110         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1111             ("    FAILED, there are no focused window in this module"));
  1112         return false;
  1115     WidgetCompositionEvent compEvent(true, NS_COMPOSITION_END,
  1116                                      mLastFocusedWindow);
  1117     InitEvent(compEvent);
  1118     compEvent.data = mDispatchedCompositionString;
  1119     nsEventStatus status;
  1120     nsCOMPtr<nsIWidget> kungFuDeathGrip = mLastFocusedWindow;
  1121     mLastFocusedWindow->DispatchEvent(&compEvent, status);
  1122     mCompositionState = eCompositionState_NotComposing;
  1123     mCompositionStart = UINT32_MAX;
  1124     mCompositionTargetOffset = UINT32_MAX;
  1125     mDispatchedCompositionString.Truncate();
  1126     if (static_cast<nsWindow*>(kungFuDeathGrip.get())->IsDestroyed() ||
  1127         kungFuDeathGrip != mLastFocusedWindow) {
  1128         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1129             ("    NOTE, the focused widget was destroyed/changed by compositionend event"));
  1130         return false;
  1133     return true;
  1136 bool
  1137 nsGtkIMModule::DispatchTextEvent(const nsAString &aCompositionString,
  1138                                  bool aIsCommit)
  1140     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1141         ("GtkIMModule(%p): DispatchTextEvent, aIsCommit=%s",
  1142          this, aIsCommit ? "TRUE" : "FALSE"));
  1144     if (!mLastFocusedWindow) {
  1145         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1146             ("    FAILED, there are no focused window in this module"));
  1147         return false;
  1150     if (!IsComposing()) {
  1151         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1152             ("    The composition wasn't started, force starting..."));
  1153         nsCOMPtr<nsIWidget> kungFuDeathGrip = mLastFocusedWindow;
  1154         if (!DispatchCompositionStart()) {
  1155             return false;
  1159     nsEventStatus status;
  1160     nsRefPtr<nsWindow> lastFocusedWindow = mLastFocusedWindow;
  1162     if (aCompositionString != mDispatchedCompositionString) {
  1163       WidgetCompositionEvent compositionUpdate(true, NS_COMPOSITION_UPDATE,
  1164                                                mLastFocusedWindow);
  1165       InitEvent(compositionUpdate);
  1166       compositionUpdate.data = aCompositionString;
  1167       mDispatchedCompositionString = aCompositionString;
  1168       mLastFocusedWindow->DispatchEvent(&compositionUpdate, status);
  1169       if (lastFocusedWindow->IsDestroyed() ||
  1170           lastFocusedWindow != mLastFocusedWindow) {
  1171           PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1172               ("    NOTE, the focused widget was destroyed/changed by compositionupdate"));
  1173           return false;
  1177     // Store the selected string which will be removed by following text event.
  1178     if (mCompositionState == eCompositionState_CompositionStartDispatched) {
  1179         // XXX We should assume, for now, any web applications don't change
  1180         //     selection at handling this text event.
  1181         WidgetQueryContentEvent querySelectedTextEvent(true,
  1182                                                        NS_QUERY_SELECTED_TEXT,
  1183                                                        mLastFocusedWindow);
  1184         mLastFocusedWindow->DispatchEvent(&querySelectedTextEvent, status);
  1185         if (querySelectedTextEvent.mSucceeded) {
  1186             mSelectedString = querySelectedTextEvent.mReply.mString;
  1187             mCompositionStart = querySelectedTextEvent.mReply.mOffset;
  1191     WidgetTextEvent textEvent(true, NS_TEXT_TEXT, mLastFocusedWindow);
  1192     InitEvent(textEvent);
  1194     uint32_t targetOffset = mCompositionStart;
  1196     if (!aIsCommit) {
  1197         // NOTE: SetTextRangeList() assumes that mDispatchedCompositionString
  1198         //       has been updated already.
  1199         textEvent.mRanges = CreateTextRangeArray();
  1200         targetOffset += textEvent.mRanges->TargetClauseOffset();
  1203     textEvent.theText = mDispatchedCompositionString.get();
  1205     mCompositionState = aIsCommit ?
  1206         eCompositionState_CommitTextEventDispatched :
  1207         eCompositionState_TextEventDispatched;
  1209     mLastFocusedWindow->DispatchEvent(&textEvent, status);
  1210     if (lastFocusedWindow->IsDestroyed() ||
  1211         lastFocusedWindow != mLastFocusedWindow) {
  1212         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1213             ("    NOTE, the focused widget was destroyed/changed by text event"));
  1214         return false;
  1217     // We cannot call SetCursorPosition for e10s-aware.
  1218     // DispatchEvent is async on e10s, so composition rect isn't updated now
  1219     // on tab parent.
  1220     mCompositionTargetOffset = targetOffset;
  1222     return true;
  1225 already_AddRefed<TextRangeArray>
  1226 nsGtkIMModule::CreateTextRangeArray()
  1228     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1229         ("GtkIMModule(%p): CreateTextRangeArray", this));
  1231     nsRefPtr<TextRangeArray> textRangeArray = new TextRangeArray();
  1233     gchar *preedit_string;
  1234     gint cursor_pos;
  1235     PangoAttrList *feedback_list;
  1236     gtk_im_context_get_preedit_string(GetContext(), &preedit_string,
  1237                                       &feedback_list, &cursor_pos);
  1238     if (!preedit_string || !*preedit_string) {
  1239         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1240             ("    preedit_string is null"));
  1241         pango_attr_list_unref(feedback_list);
  1242         g_free(preedit_string);
  1243         return textRangeArray.forget();
  1246     PangoAttrIterator* iter;
  1247     iter = pango_attr_list_get_iterator(feedback_list);
  1248     if (!iter) {
  1249         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1250             ("    FAILED, iterator couldn't be allocated"));
  1251         pango_attr_list_unref(feedback_list);
  1252         g_free(preedit_string);
  1253         return textRangeArray.forget();
  1256     /*
  1257      * Depend on gtk2's implementation on XIM support.
  1258      * In aFeedback got from gtk2, there are only three types of data:
  1259      * PANGO_ATTR_UNDERLINE, PANGO_ATTR_FOREGROUND, PANGO_ATTR_BACKGROUND.
  1260      * Corresponding to XIMUnderline, XIMReverse.
  1261      * Don't take PANGO_ATTR_BACKGROUND into account, since
  1262      * PANGO_ATTR_BACKGROUND and PANGO_ATTR_FOREGROUND are always
  1263      * a couple.
  1264      */
  1265     do {
  1266         PangoAttribute* attrUnderline =
  1267             pango_attr_iterator_get(iter, PANGO_ATTR_UNDERLINE);
  1268         PangoAttribute* attrForeground =
  1269             pango_attr_iterator_get(iter, PANGO_ATTR_FOREGROUND);
  1270         if (!attrUnderline && !attrForeground) {
  1271             continue;
  1274         // Get the range of the current attribute(s)
  1275         gint start, end;
  1276         pango_attr_iterator_range(iter, &start, &end);
  1278         TextRange range;
  1279         // XIMReverse | XIMUnderline
  1280         if (attrUnderline && attrForeground) {
  1281             range.mRangeType = NS_TEXTRANGE_SELECTEDCONVERTEDTEXT;
  1283         // XIMUnderline
  1284         else if (attrUnderline) {
  1285             range.mRangeType = NS_TEXTRANGE_CONVERTEDTEXT;
  1287         // XIMReverse
  1288         else if (attrForeground) {
  1289             range.mRangeType = NS_TEXTRANGE_SELECTEDRAWTEXT;
  1290         } else {
  1291             range.mRangeType = NS_TEXTRANGE_RAWINPUT;
  1294         gunichar2* uniStr = nullptr;
  1295         if (start == 0) {
  1296             range.mStartOffset = 0;
  1297         } else {
  1298             glong uniStrLen;
  1299             uniStr = g_utf8_to_utf16(preedit_string, start,
  1300                                      nullptr, &uniStrLen, nullptr);
  1301             if (uniStr) {
  1302                 range.mStartOffset = uniStrLen;
  1303                 g_free(uniStr);
  1304                 uniStr = nullptr;
  1308         glong uniStrLen;
  1309         uniStr = g_utf8_to_utf16(preedit_string + start, end - start,
  1310                                  nullptr, &uniStrLen, nullptr);
  1311         if (!uniStr) {
  1312             range.mEndOffset = range.mStartOffset;
  1313         } else {
  1314             range.mEndOffset = range.mStartOffset + uniStrLen;
  1315             g_free(uniStr);
  1316             uniStr = nullptr;
  1319         textRangeArray->AppendElement(range);
  1321         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1322             ("    mStartOffset=%u, mEndOffset=%u, mRangeType=%s",
  1323              range.mStartOffset, range.mEndOffset,
  1324              GetRangeTypeName(range.mRangeType)));
  1325     } while (pango_attr_iterator_next(iter));
  1327     TextRange range;
  1328     if (cursor_pos < 0) {
  1329         range.mStartOffset = 0;
  1330     } else if (uint32_t(cursor_pos) > mDispatchedCompositionString.Length()) {
  1331         range.mStartOffset = mDispatchedCompositionString.Length();
  1332     } else {
  1333         range.mStartOffset = uint32_t(cursor_pos);
  1335     range.mEndOffset = range.mStartOffset;
  1336     range.mRangeType = NS_TEXTRANGE_CARETPOSITION;
  1337     textRangeArray->AppendElement(range);
  1339     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1340         ("    mStartOffset=%u, mEndOffset=%u, mRangeType=%s",
  1341          range.mStartOffset, range.mEndOffset,
  1342          GetRangeTypeName(range.mRangeType)));
  1344     pango_attr_iterator_destroy(iter);
  1345     pango_attr_list_unref(feedback_list);
  1346     g_free(preedit_string);
  1348     return textRangeArray.forget();
  1351 void
  1352 nsGtkIMModule::SetCursorPosition(uint32_t aTargetOffset)
  1354     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1355         ("GtkIMModule(%p): SetCursorPosition, aTargetOffset=%u",
  1356          this, aTargetOffset));
  1358     if (aTargetOffset == UINT32_MAX) {
  1359         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1360             ("    FAILED, aTargetOffset is wrong offset"));
  1361         return;
  1364     if (!mLastFocusedWindow) {
  1365         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1366             ("    FAILED, there are no focused window"));
  1367         return;
  1370     GtkIMContext *im = GetContext();
  1371     if (!im) {
  1372         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1373             ("    FAILED, there are no context"));
  1374         return;
  1377     WidgetQueryContentEvent charRect(true, NS_QUERY_TEXT_RECT,
  1378                                      mLastFocusedWindow);
  1379     charRect.InitForQueryTextRect(aTargetOffset, 1);
  1380     InitEvent(charRect);
  1381     nsEventStatus status;
  1382     mLastFocusedWindow->DispatchEvent(&charRect, status);
  1383     if (!charRect.mSucceeded) {
  1384         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1385             ("    FAILED, NS_QUERY_TEXT_RECT was failed"));
  1386         return;
  1388     nsWindow* rootWindow =
  1389         static_cast<nsWindow*>(mLastFocusedWindow->GetTopLevelWidget());
  1391     // Get the position of the rootWindow in screen.
  1392     gint rootX, rootY;
  1393     gdk_window_get_origin(rootWindow->GetGdkWindow(), &rootX, &rootY);
  1395     // Get the position of IM context owner window in screen.
  1396     gint ownerX, ownerY;
  1397     gdk_window_get_origin(mOwnerWindow->GetGdkWindow(), &ownerX, &ownerY);
  1399     // Compute the caret position in the IM owner window.
  1400     GdkRectangle area;
  1401     area.x = charRect.mReply.mRect.x + rootX - ownerX;
  1402     area.y = charRect.mReply.mRect.y + rootY - ownerY;
  1403     area.width  = 0;
  1404     area.height = charRect.mReply.mRect.height;
  1406     gtk_im_context_set_cursor_location(im, &area);
  1409 nsresult
  1410 nsGtkIMModule::GetCurrentParagraph(nsAString& aText, uint32_t& aCursorPos)
  1412     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1413         ("GtkIMModule(%p): GetCurrentParagraph, mCompositionState=%s",
  1414          this, GetCompositionStateName()));
  1416     if (!mLastFocusedWindow) {
  1417         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1418             ("    FAILED, there are no focused window in this module"));
  1419         return NS_ERROR_NULL_POINTER;
  1422     nsEventStatus status;
  1424     uint32_t selOffset = mCompositionStart;
  1425     uint32_t selLength = mSelectedString.Length();
  1427     // If focused editor doesn't have composition string, we should use
  1428     // current selection.
  1429     if (!EditorHasCompositionString()) {
  1430         // Query cursor position & selection
  1431         WidgetQueryContentEvent querySelectedTextEvent(true,
  1432                                                        NS_QUERY_SELECTED_TEXT,
  1433                                                        mLastFocusedWindow);
  1434         mLastFocusedWindow->DispatchEvent(&querySelectedTextEvent, status);
  1435         NS_ENSURE_TRUE(querySelectedTextEvent.mSucceeded, NS_ERROR_FAILURE);
  1437         selOffset = querySelectedTextEvent.mReply.mOffset;
  1438         selLength = querySelectedTextEvent.mReply.mString.Length();
  1441     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1442         ("        selOffset=%u, selLength=%u",
  1443          selOffset, selLength));
  1445     // XXX nsString::Find and nsString::RFind take int32_t for offset, so,
  1446     //     we cannot support this request when the current offset is larger
  1447     //     than INT32_MAX.
  1448     if (selOffset > INT32_MAX || selLength > INT32_MAX ||
  1449         selOffset + selLength > INT32_MAX) {
  1450         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1451             ("    FAILED, The selection is out of range"));
  1452         return NS_ERROR_FAILURE;
  1455     // Get all text contents of the focused editor
  1456     WidgetQueryContentEvent queryTextContentEvent(true,
  1457                                                   NS_QUERY_TEXT_CONTENT,
  1458                                                   mLastFocusedWindow);
  1459     queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
  1460     mLastFocusedWindow->DispatchEvent(&queryTextContentEvent, status);
  1461     NS_ENSURE_TRUE(queryTextContentEvent.mSucceeded, NS_ERROR_FAILURE);
  1463     nsAutoString textContent(queryTextContentEvent.mReply.mString);
  1464     if (selOffset + selLength > textContent.Length()) {
  1465         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1466             ("    FAILED, The selection is invalid, textContent.Length()=%u",
  1467              textContent.Length()));
  1468         return NS_ERROR_FAILURE;
  1471     // Remove composing string and restore the selected string because
  1472     // GtkEntry doesn't remove selected string until committing, however,
  1473     // our editor does it.  We should emulate the behavior for IME.
  1474     if (EditorHasCompositionString() &&
  1475         mDispatchedCompositionString != mSelectedString) {
  1476         textContent.Replace(mCompositionStart,
  1477             mDispatchedCompositionString.Length(), mSelectedString);
  1480     // Get only the focused paragraph, by looking for newlines
  1481     int32_t parStart = (selOffset == 0) ? 0 :
  1482         textContent.RFind("\n", false, selOffset - 1, -1) + 1;
  1483     int32_t parEnd = textContent.Find("\n", false, selOffset + selLength, -1);
  1484     if (parEnd < 0) {
  1485         parEnd = textContent.Length();
  1487     aText = nsDependentSubstring(textContent, parStart, parEnd - parStart);
  1488     aCursorPos = selOffset - uint32_t(parStart);
  1490     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1491         ("    aText=%s, aText.Length()=%u, aCursorPos=%u",
  1492          NS_ConvertUTF16toUTF8(aText).get(),
  1493          aText.Length(), aCursorPos));
  1495     return NS_OK;
  1498 nsresult
  1499 nsGtkIMModule::DeleteText(const int32_t aOffset, const uint32_t aNChars)
  1501     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1502         ("GtkIMModule(%p): DeleteText, aOffset=%d, aNChars=%d, "
  1503          "mCompositionState=%s",
  1504          this, aOffset, aNChars, GetCompositionStateName()));
  1506     if (!mLastFocusedWindow) {
  1507         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1508             ("    FAILED, there are no focused window in this module"));
  1509         return NS_ERROR_NULL_POINTER;
  1512     if (!aNChars) {
  1513         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1514             ("    FAILED, aNChars must not be zero"));
  1515         return NS_ERROR_INVALID_ARG;
  1518     nsRefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
  1519     nsEventStatus status;
  1521     // First, we should cancel current composition because editor cannot
  1522     // handle changing selection and deleting text.
  1523     uint32_t selOffset;
  1524     bool wasComposing = IsComposing();
  1525     bool editorHadCompositionString = EditorHasCompositionString();
  1526     if (wasComposing) {
  1527         selOffset = mCompositionStart;
  1528         if (editorHadCompositionString &&
  1529             !DispatchTextEvent(mSelectedString, false)) {
  1530             PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1531                 ("    FAILED, quitting from DeletText"));
  1532             return NS_ERROR_FAILURE;
  1534         if (!DispatchCompositionEnd()) {
  1535             PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1536                 ("    FAILED, quitting from DeletText"));
  1537             return NS_ERROR_FAILURE;
  1539     } else {
  1540         // Query cursor position & selection
  1541         WidgetQueryContentEvent querySelectedTextEvent(true,
  1542                                                        NS_QUERY_SELECTED_TEXT,
  1543                                                        mLastFocusedWindow);
  1544         lastFocusedWindow->DispatchEvent(&querySelectedTextEvent, status);
  1545         NS_ENSURE_TRUE(querySelectedTextEvent.mSucceeded, NS_ERROR_FAILURE);
  1547         selOffset = querySelectedTextEvent.mReply.mOffset;
  1550     // Get all text contents of the focused editor
  1551     WidgetQueryContentEvent queryTextContentEvent(true,
  1552                                                   NS_QUERY_TEXT_CONTENT,
  1553                                                   mLastFocusedWindow);
  1554     queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
  1555     mLastFocusedWindow->DispatchEvent(&queryTextContentEvent, status);
  1556     NS_ENSURE_TRUE(queryTextContentEvent.mSucceeded, NS_ERROR_FAILURE);
  1557     if (queryTextContentEvent.mReply.mString.IsEmpty()) {
  1558         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1559             ("    FAILED, there is no contents"));
  1560         return NS_ERROR_FAILURE;
  1563     NS_ConvertUTF16toUTF8 utf8Str(
  1564         nsDependentSubstring(queryTextContentEvent.mReply.mString,
  1565                              0, selOffset));
  1566     glong offsetInUTF8Characters =
  1567         g_utf8_strlen(utf8Str.get(), utf8Str.Length()) + aOffset;
  1568     if (offsetInUTF8Characters < 0) {
  1569         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1570             ("    FAILED, aOffset is too small for current cursor pos "
  1571              "(computed offset: %d)",
  1572              offsetInUTF8Characters));
  1573         return NS_ERROR_FAILURE;
  1576     AppendUTF16toUTF8(
  1577         nsDependentSubstring(queryTextContentEvent.mReply.mString, selOffset),
  1578         utf8Str);
  1579     glong countOfCharactersInUTF8 =
  1580         g_utf8_strlen(utf8Str.get(), utf8Str.Length());
  1581     glong endInUTF8Characters =
  1582         offsetInUTF8Characters + aNChars;
  1583     if (countOfCharactersInUTF8 < endInUTF8Characters) {
  1584         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1585             ("    FAILED, aNChars is too large for current contents "
  1586              "(content length: %d, computed end offset: %d)",
  1587              countOfCharactersInUTF8, endInUTF8Characters));
  1588         return NS_ERROR_FAILURE;
  1591     gchar* charAtOffset =
  1592         g_utf8_offset_to_pointer(utf8Str.get(), offsetInUTF8Characters);
  1593     gchar* charAtEnd =
  1594         g_utf8_offset_to_pointer(utf8Str.get(), endInUTF8Characters);
  1596     // Set selection to delete
  1597     WidgetSelectionEvent selectionEvent(true, NS_SELECTION_SET,
  1598                                         mLastFocusedWindow);
  1600     nsDependentCSubstring utf8StrBeforeOffset(utf8Str, 0,
  1601                                               charAtOffset - utf8Str.get());
  1602     selectionEvent.mOffset =
  1603         NS_ConvertUTF8toUTF16(utf8StrBeforeOffset).Length();
  1605     nsDependentCSubstring utf8DeletingStr(utf8Str,
  1606                                           utf8StrBeforeOffset.Length(),
  1607                                           charAtEnd - charAtOffset);
  1608     selectionEvent.mLength =
  1609         NS_ConvertUTF8toUTF16(utf8DeletingStr).Length();
  1611     selectionEvent.mReversed = false;
  1612     selectionEvent.mExpandToClusterBoundary = false;
  1613     lastFocusedWindow->DispatchEvent(&selectionEvent, status);
  1615     if (!selectionEvent.mSucceeded ||
  1616         lastFocusedWindow != mLastFocusedWindow ||
  1617         lastFocusedWindow->Destroyed()) {
  1618         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1619             ("    FAILED, setting selection caused focus change "
  1620              "or window destroyed"));
  1621         return NS_ERROR_FAILURE;
  1624     // Delete the selection
  1625     WidgetContentCommandEvent contentCommandEvent(true,
  1626                                                   NS_CONTENT_COMMAND_DELETE,
  1627                                                   mLastFocusedWindow);
  1628     mLastFocusedWindow->DispatchEvent(&contentCommandEvent, status);
  1630     if (!contentCommandEvent.mSucceeded ||
  1631         lastFocusedWindow != mLastFocusedWindow ||
  1632         lastFocusedWindow->Destroyed()) {
  1633         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1634             ("    FAILED, deleting the selection caused focus change "
  1635              "or window destroyed"));
  1636         return NS_ERROR_FAILURE;
  1639     if (!wasComposing) {
  1640         return NS_OK;
  1643     // Restore the composition at new caret position.
  1644     if (!DispatchCompositionStart()) {
  1645         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1646             ("    FAILED, resterting composition start"));
  1647         return NS_ERROR_FAILURE;
  1650     if (!editorHadCompositionString) {
  1651         return NS_OK;
  1654     nsAutoString compositionString;
  1655     GetCompositionString(compositionString);
  1656     if (!DispatchTextEvent(compositionString, true)) {
  1657         PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1658             ("    FAILED, restoring composition string"));
  1659         return NS_ERROR_FAILURE;
  1662     return NS_OK;
  1665 void
  1666 nsGtkIMModule::InitEvent(WidgetGUIEvent& aEvent)
  1668     aEvent.time = PR_Now() / 1000;
  1671 bool
  1672 nsGtkIMModule::ShouldIgnoreNativeCompositionEvent()
  1674     PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1675         ("GtkIMModule(%p): ShouldIgnoreNativeCompositionEvent, mLastFocusedWindow=%p, mIgnoreNativeCompositionEvent=%s",
  1676          this, mLastFocusedWindow,
  1677          mIgnoreNativeCompositionEvent ? "YES" : "NO"));
  1679     if (!mLastFocusedWindow) {
  1680         return true; // cannot continue
  1683     return mIgnoreNativeCompositionEvent;

mercurial