widget/gtk/nsGtkIMModule.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/widget/gtk/nsGtkIMModule.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1684 @@
     1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
     1.5 +/* vim: set ts=4 et sw=4 tw=80: */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#ifdef MOZ_LOGGING
    1.11 +#define FORCE_PR_LOG /* Allow logging in the release build */
    1.12 +#endif // MOZ_LOGGING
    1.13 +#include "prlog.h"
    1.14 +#include "prtime.h"
    1.15 +
    1.16 +#include "nsGtkIMModule.h"
    1.17 +#include "nsWindow.h"
    1.18 +#include "mozilla/Likely.h"
    1.19 +#include "mozilla/MiscEvents.h"
    1.20 +#include "mozilla/Preferences.h"
    1.21 +#include "mozilla/TextEvents.h"
    1.22 +
    1.23 +using namespace mozilla;
    1.24 +using namespace mozilla::widget;
    1.25 +
    1.26 +#ifdef PR_LOGGING
    1.27 +PRLogModuleInfo* gGtkIMLog = nullptr;
    1.28 +
    1.29 +static const char*
    1.30 +GetRangeTypeName(uint32_t aRangeType)
    1.31 +{
    1.32 +    switch (aRangeType) {
    1.33 +        case NS_TEXTRANGE_RAWINPUT:
    1.34 +            return "NS_TEXTRANGE_RAWINPUT";
    1.35 +        case NS_TEXTRANGE_CONVERTEDTEXT:
    1.36 +            return "NS_TEXTRANGE_CONVERTEDTEXT";
    1.37 +        case NS_TEXTRANGE_SELECTEDRAWTEXT:
    1.38 +            return "NS_TEXTRANGE_SELECTEDRAWTEXT";
    1.39 +        case NS_TEXTRANGE_SELECTEDCONVERTEDTEXT:
    1.40 +            return "NS_TEXTRANGE_SELECTEDCONVERTEDTEXT";
    1.41 +        case NS_TEXTRANGE_CARETPOSITION:
    1.42 +            return "NS_TEXTRANGE_CARETPOSITION";
    1.43 +        default:
    1.44 +            return "UNKNOWN SELECTION TYPE!!";
    1.45 +    }
    1.46 +}
    1.47 +
    1.48 +static const char*
    1.49 +GetEnabledStateName(uint32_t aState)
    1.50 +{
    1.51 +    switch (aState) {
    1.52 +        case IMEState::DISABLED:
    1.53 +            return "DISABLED";
    1.54 +        case IMEState::ENABLED:
    1.55 +            return "ENABLED";
    1.56 +        case IMEState::PASSWORD:
    1.57 +            return "PASSWORD";
    1.58 +        case IMEState::PLUGIN:
    1.59 +            return "PLUG_IN";
    1.60 +        default:
    1.61 +            return "UNKNOWN ENABLED STATUS!!";
    1.62 +    }
    1.63 +}
    1.64 +#endif
    1.65 +
    1.66 +const static bool kUseSimpleContextDefault = MOZ_WIDGET_GTK == 2;
    1.67 +
    1.68 +nsGtkIMModule* nsGtkIMModule::sLastFocusedModule = nullptr;
    1.69 +bool nsGtkIMModule::sUseSimpleContext;
    1.70 +
    1.71 +nsGtkIMModule::nsGtkIMModule(nsWindow* aOwnerWindow) :
    1.72 +    mOwnerWindow(aOwnerWindow), mLastFocusedWindow(nullptr),
    1.73 +    mContext(nullptr),
    1.74 +    mSimpleContext(nullptr),
    1.75 +    mDummyContext(nullptr),
    1.76 +    mCompositionStart(UINT32_MAX), mProcessingKeyEvent(nullptr),
    1.77 +    mCompositionTargetOffset(UINT32_MAX),
    1.78 +    mCompositionState(eCompositionState_NotComposing),
    1.79 +    mIsIMFocused(false), mIgnoreNativeCompositionEvent(false)
    1.80 +{
    1.81 +#ifdef PR_LOGGING
    1.82 +    if (!gGtkIMLog) {
    1.83 +        gGtkIMLog = PR_NewLogModule("nsGtkIMModuleWidgets");
    1.84 +    }
    1.85 +#endif
    1.86 +    static bool sFirstInstance = true;
    1.87 +    if (sFirstInstance) {
    1.88 +        sFirstInstance = false;
    1.89 +        sUseSimpleContext =
    1.90 +            Preferences::GetBool(
    1.91 +                "intl.ime.use_simple_context_on_password_field",
    1.92 +                kUseSimpleContextDefault);
    1.93 +    }
    1.94 +    Init();
    1.95 +}
    1.96 +
    1.97 +void
    1.98 +nsGtkIMModule::Init()
    1.99 +{
   1.100 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.101 +        ("GtkIMModule(%p): Init, mOwnerWindow=%p",
   1.102 +         this, mOwnerWindow));
   1.103 +
   1.104 +    MozContainer* container = mOwnerWindow->GetMozContainer();
   1.105 +    NS_PRECONDITION(container, "container is null");
   1.106 +    GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(container));
   1.107 +
   1.108 +    // NOTE: gtk_im_*_new() abort (kill) the whole process when it fails.
   1.109 +    //       So, we don't need to check the result.
   1.110 +
   1.111 +    // Normal context.
   1.112 +    mContext = gtk_im_multicontext_new();
   1.113 +    gtk_im_context_set_client_window(mContext, gdkWindow);
   1.114 +    g_signal_connect(mContext, "preedit_changed",
   1.115 +                     G_CALLBACK(nsGtkIMModule::OnChangeCompositionCallback),
   1.116 +                     this);
   1.117 +    g_signal_connect(mContext, "retrieve_surrounding",
   1.118 +                     G_CALLBACK(nsGtkIMModule::OnRetrieveSurroundingCallback),
   1.119 +                     this);
   1.120 +    g_signal_connect(mContext, "delete_surrounding",
   1.121 +                     G_CALLBACK(nsGtkIMModule::OnDeleteSurroundingCallback),
   1.122 +                     this);
   1.123 +    g_signal_connect(mContext, "commit",
   1.124 +                     G_CALLBACK(nsGtkIMModule::OnCommitCompositionCallback),
   1.125 +                     this);
   1.126 +    g_signal_connect(mContext, "preedit_start",
   1.127 +                     G_CALLBACK(nsGtkIMModule::OnStartCompositionCallback),
   1.128 +                     this);
   1.129 +    g_signal_connect(mContext, "preedit_end",
   1.130 +                     G_CALLBACK(nsGtkIMModule::OnEndCompositionCallback),
   1.131 +                     this);
   1.132 +
   1.133 +    // Simple context
   1.134 +    if (sUseSimpleContext) {
   1.135 +        mSimpleContext = gtk_im_context_simple_new();
   1.136 +        gtk_im_context_set_client_window(mSimpleContext, gdkWindow);
   1.137 +        g_signal_connect(mSimpleContext, "preedit_changed",
   1.138 +            G_CALLBACK(&nsGtkIMModule::OnChangeCompositionCallback),
   1.139 +            this);
   1.140 +        g_signal_connect(mSimpleContext, "retrieve_surrounding",
   1.141 +            G_CALLBACK(&nsGtkIMModule::OnRetrieveSurroundingCallback),
   1.142 +            this);
   1.143 +        g_signal_connect(mSimpleContext, "delete_surrounding",
   1.144 +            G_CALLBACK(&nsGtkIMModule::OnDeleteSurroundingCallback),
   1.145 +            this);
   1.146 +        g_signal_connect(mSimpleContext, "commit",
   1.147 +            G_CALLBACK(&nsGtkIMModule::OnCommitCompositionCallback),
   1.148 +            this);
   1.149 +        g_signal_connect(mSimpleContext, "preedit_start",
   1.150 +            G_CALLBACK(nsGtkIMModule::OnStartCompositionCallback),
   1.151 +            this);
   1.152 +        g_signal_connect(mSimpleContext, "preedit_end",
   1.153 +            G_CALLBACK(nsGtkIMModule::OnEndCompositionCallback),
   1.154 +            this);
   1.155 +    }
   1.156 +
   1.157 +    // Dummy context
   1.158 +    mDummyContext = gtk_im_multicontext_new();
   1.159 +    gtk_im_context_set_client_window(mDummyContext, gdkWindow);
   1.160 +}
   1.161 +
   1.162 +nsGtkIMModule::~nsGtkIMModule()
   1.163 +{
   1.164 +    if (this == sLastFocusedModule) {
   1.165 +        sLastFocusedModule = nullptr;
   1.166 +    }
   1.167 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.168 +        ("GtkIMModule(%p) was gone", this));
   1.169 +}
   1.170 +
   1.171 +void
   1.172 +nsGtkIMModule::OnDestroyWindow(nsWindow* aWindow)
   1.173 +{
   1.174 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.175 +        ("GtkIMModule(%p): OnDestroyWindow, aWindow=%p, mLastFocusedWindow=%p, mOwnerWindow=%p, mLastFocusedModule=%p",
   1.176 +         this, aWindow, mLastFocusedWindow, mOwnerWindow, sLastFocusedModule));
   1.177 +
   1.178 +    NS_PRECONDITION(aWindow, "aWindow must not be null");
   1.179 +
   1.180 +    if (mLastFocusedWindow == aWindow) {
   1.181 +        CancelIMEComposition(aWindow);
   1.182 +        if (mIsIMFocused) {
   1.183 +            Blur();
   1.184 +        }
   1.185 +        mLastFocusedWindow = nullptr;
   1.186 +    }
   1.187 +
   1.188 +    if (mOwnerWindow != aWindow) {
   1.189 +        return;
   1.190 +    }
   1.191 +
   1.192 +    if (sLastFocusedModule == this) {
   1.193 +        sLastFocusedModule = nullptr;
   1.194 +    }
   1.195 +
   1.196 +    /**
   1.197 +     * NOTE:
   1.198 +     *   The given window is the owner of this, so, we must release the
   1.199 +     *   contexts now.  But that might be referred from other nsWindows
   1.200 +     *   (they are children of this.  But we don't know why there are the
   1.201 +     *   cases).  So, we need to clear the pointers that refers to contexts
   1.202 +     *   and this if the other referrers are still alive. See bug 349727.
   1.203 +     */
   1.204 +    if (mContext) {
   1.205 +        PrepareToDestroyContext(mContext);
   1.206 +        gtk_im_context_set_client_window(mContext, nullptr);
   1.207 +        g_object_unref(mContext);
   1.208 +        mContext = nullptr;
   1.209 +    }
   1.210 +
   1.211 +    if (mSimpleContext) {
   1.212 +        gtk_im_context_set_client_window(mSimpleContext, nullptr);
   1.213 +        g_object_unref(mSimpleContext);
   1.214 +        mSimpleContext = nullptr;
   1.215 +    }
   1.216 +
   1.217 +    if (mDummyContext) {
   1.218 +        // mContext and mDummyContext have the same slaveType and signal_data
   1.219 +        // so no need for another workaround_gtk_im_display_closed.
   1.220 +        gtk_im_context_set_client_window(mDummyContext, nullptr);
   1.221 +        g_object_unref(mDummyContext);
   1.222 +        mDummyContext = nullptr;
   1.223 +    }
   1.224 +
   1.225 +    mOwnerWindow = nullptr;
   1.226 +    mLastFocusedWindow = nullptr;
   1.227 +    mInputContext.mIMEState.mEnabled = IMEState::DISABLED;
   1.228 +
   1.229 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.230 +        ("    SUCCEEDED, Completely destroyed"));
   1.231 +}
   1.232 +
   1.233 +// Work around gtk bug http://bugzilla.gnome.org/show_bug.cgi?id=483223:
   1.234 +// (and the similar issue of GTK+ IIIM)
   1.235 +// The GTK+ XIM and IIIM modules register handlers for the "closed" signal
   1.236 +// on the display, but:
   1.237 +//  * The signal handlers are not disconnected when the module is unloaded.
   1.238 +//
   1.239 +// The GTK+ XIM module has another problem:
   1.240 +//  * When the signal handler is run (with the module loaded) it tries
   1.241 +//    XFree (and fails) on a pointer that did not come from Xmalloc.
   1.242 +//
   1.243 +// To prevent these modules from being unloaded, use static variables to
   1.244 +// hold ref of GtkIMContext class.
   1.245 +// For GTK+ XIM module, to prevent the signal handler from being run,
   1.246 +// find the signal handlers and remove them.
   1.247 +//
   1.248 +// GtkIMContextXIMs share XOpenIM connections and display closed signal
   1.249 +// handlers (where possible).
   1.250 +
   1.251 +void
   1.252 +nsGtkIMModule::PrepareToDestroyContext(GtkIMContext *aContext)
   1.253 +{
   1.254 +    MozContainer* container = mOwnerWindow->GetMozContainer();
   1.255 +    NS_PRECONDITION(container, "The container of the window is null");
   1.256 +
   1.257 +    GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT(aContext);
   1.258 +#if (MOZ_WIDGET_GTK == 2)
   1.259 +    GtkIMContext *slave = multicontext->slave;
   1.260 +#else
   1.261 +    GtkIMContext *slave = nullptr; //TODO GTK3
   1.262 +#endif
   1.263 +    if (!slave) {
   1.264 +        return;
   1.265 +    }
   1.266 +
   1.267 +    GType slaveType = G_TYPE_FROM_INSTANCE(slave);
   1.268 +    const gchar *im_type_name = g_type_name(slaveType);
   1.269 +    if (strcmp(im_type_name, "GtkIMContextXIM") == 0) {
   1.270 +        if (gtk_check_version(2, 12, 1) == nullptr) {
   1.271 +            return; // gtk bug has been fixed
   1.272 +        }
   1.273 +
   1.274 +        struct GtkIMContextXIM
   1.275 +        {
   1.276 +            GtkIMContext parent;
   1.277 +            gpointer private_data;
   1.278 +            // ... other fields
   1.279 +        };
   1.280 +
   1.281 +        gpointer signal_data =
   1.282 +            reinterpret_cast<GtkIMContextXIM*>(slave)->private_data;
   1.283 +        if (!signal_data) {
   1.284 +            return;
   1.285 +        }
   1.286 +
   1.287 +        g_signal_handlers_disconnect_matched(
   1.288 +            gtk_widget_get_display(GTK_WIDGET(container)),
   1.289 +            G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, signal_data);
   1.290 +
   1.291 +        // Add a reference to prevent the XIM module from being unloaded
   1.292 +        // and reloaded: each time the module is loaded and used, it
   1.293 +        // opens (and doesn't close) new XOpenIM connections.
   1.294 +        static gpointer gtk_xim_context_class =
   1.295 +            g_type_class_ref(slaveType);
   1.296 +        // Mute unused variable warning:
   1.297 +        (void)gtk_xim_context_class;
   1.298 +    } else if (strcmp(im_type_name, "GtkIMContextIIIM") == 0) {
   1.299 +        // Add a reference to prevent the IIIM module from being unloaded
   1.300 +        static gpointer gtk_iiim_context_class =
   1.301 +            g_type_class_ref(slaveType);
   1.302 +        // Mute unused variable warning:
   1.303 +        (void)gtk_iiim_context_class;
   1.304 +    }
   1.305 +}
   1.306 +
   1.307 +void
   1.308 +nsGtkIMModule::OnFocusWindow(nsWindow* aWindow)
   1.309 +{
   1.310 +    if (MOZ_UNLIKELY(IsDestroyed())) {
   1.311 +        return;
   1.312 +    }
   1.313 +
   1.314 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.315 +        ("GtkIMModule(%p): OnFocusWindow, aWindow=%p, mLastFocusedWindow=%p",
   1.316 +         this, aWindow, mLastFocusedWindow));
   1.317 +    mLastFocusedWindow = aWindow;
   1.318 +    Focus();
   1.319 +}
   1.320 +
   1.321 +void
   1.322 +nsGtkIMModule::OnBlurWindow(nsWindow* aWindow)
   1.323 +{
   1.324 +    if (MOZ_UNLIKELY(IsDestroyed())) {
   1.325 +        return;
   1.326 +    }
   1.327 +
   1.328 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.329 +        ("GtkIMModule(%p): OnBlurWindow, aWindow=%p, mLastFocusedWindow=%p, mIsIMFocused=%s",
   1.330 +         this, aWindow, mLastFocusedWindow, mIsIMFocused ? "YES" : "NO"));
   1.331 +
   1.332 +    if (!mIsIMFocused || mLastFocusedWindow != aWindow) {
   1.333 +        return;
   1.334 +    }
   1.335 +
   1.336 +    Blur();
   1.337 +}
   1.338 +
   1.339 +bool
   1.340 +nsGtkIMModule::OnKeyEvent(nsWindow* aCaller, GdkEventKey* aEvent,
   1.341 +                          bool aKeyDownEventWasSent /* = false */)
   1.342 +{
   1.343 +    NS_PRECONDITION(aEvent, "aEvent must be non-null");
   1.344 +
   1.345 +    if (!IsEditable() || MOZ_UNLIKELY(IsDestroyed())) {
   1.346 +        return false;
   1.347 +    }
   1.348 +
   1.349 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.350 +        ("GtkIMModule(%p): OnKeyEvent, aCaller=%p, aKeyDownEventWasSent=%s",
   1.351 +         this, aCaller, aKeyDownEventWasSent ? "TRUE" : "FALSE"));
   1.352 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.353 +        ("    aEvent: type=%s, keyval=%s, unicode=0x%X",
   1.354 +         aEvent->type == GDK_KEY_PRESS ? "GDK_KEY_PRESS" :
   1.355 +         aEvent->type == GDK_KEY_RELEASE ? "GDK_KEY_RELEASE" : "Unknown",
   1.356 +         gdk_keyval_name(aEvent->keyval),
   1.357 +         gdk_keyval_to_unicode(aEvent->keyval)));
   1.358 +
   1.359 +    if (aCaller != mLastFocusedWindow) {
   1.360 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.361 +            ("    FAILED, the caller isn't focused window, mLastFocusedWindow=%p",
   1.362 +             mLastFocusedWindow));
   1.363 +        return false;
   1.364 +    }
   1.365 +
   1.366 +    GtkIMContext* im = GetContext();
   1.367 +    if (MOZ_UNLIKELY(!im)) {
   1.368 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.369 +            ("    FAILED, there are no context"));
   1.370 +        return false;
   1.371 +    }
   1.372 +
   1.373 +    mKeyDownEventWasSent = aKeyDownEventWasSent;
   1.374 +    mFilterKeyEvent = true;
   1.375 +    mProcessingKeyEvent = aEvent;
   1.376 +    gboolean isFiltered = gtk_im_context_filter_keypress(im, aEvent);
   1.377 +    mProcessingKeyEvent = nullptr;
   1.378 +
   1.379 +    // We filter the key event if the event was not committed (because
   1.380 +    // it's probably part of a composition) or if the key event was
   1.381 +    // committed _and_ changed.  This way we still let key press
   1.382 +    // events go through as simple key press events instead of
   1.383 +    // composed characters.
   1.384 +    bool filterThisEvent = isFiltered && mFilterKeyEvent;
   1.385 +
   1.386 +    if (IsComposing() && !isFiltered) {
   1.387 +        if (aEvent->type == GDK_KEY_PRESS) {
   1.388 +            if (!mDispatchedCompositionString.IsEmpty()) {
   1.389 +                // If there is composition string, we shouldn't dispatch
   1.390 +                // any keydown events during composition.
   1.391 +                filterThisEvent = true;
   1.392 +            } else {
   1.393 +                // A Hangul input engine for SCIM doesn't emit preedit_end
   1.394 +                // signal even when composition string becomes empty.  On the
   1.395 +                // other hand, we should allow to make composition with empty
   1.396 +                // string for other languages because there *might* be such
   1.397 +                // IM.  For compromising this issue, we should dispatch
   1.398 +                // compositionend event, however, we don't need to reset IM
   1.399 +                // actually.
   1.400 +                CommitCompositionBy(EmptyString());
   1.401 +                filterThisEvent = false;
   1.402 +            }
   1.403 +        } else {
   1.404 +            // Key release event may not be consumed by IM, however, we
   1.405 +            // shouldn't dispatch any keyup event during composition.
   1.406 +            filterThisEvent = true;
   1.407 +        }
   1.408 +    }
   1.409 +
   1.410 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.411 +        ("    filterThisEvent=%s (isFiltered=%s, mFilterKeyEvent=%s)",
   1.412 +         filterThisEvent ? "TRUE" : "FALSE", isFiltered ? "YES" : "NO",
   1.413 +         mFilterKeyEvent ? "YES" : "NO"));
   1.414 +
   1.415 +    return filterThisEvent;
   1.416 +}
   1.417 +
   1.418 +void
   1.419 +nsGtkIMModule::OnFocusChangeInGecko(bool aFocus)
   1.420 +{
   1.421 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.422 +        ("GtkIMModule(%p): OnFocusChangeInGecko, aFocus=%s, "
   1.423 +         "mCompositionState=%s, mIsIMFocused=%s, "
   1.424 +         "mIgnoreNativeCompositionEvent=%s",
   1.425 +         this, aFocus ? "YES" : "NO", GetCompositionStateName(),
   1.426 +         mIsIMFocused ? "YES" : "NO",
   1.427 +         mIgnoreNativeCompositionEvent ? "YES" : "NO"));
   1.428 +
   1.429 +    // We shouldn't carry over the removed string to another editor.
   1.430 +    mSelectedString.Truncate();
   1.431 +
   1.432 +    if (aFocus) {
   1.433 +        // If we failed to commit forcedely in previous focused editor,
   1.434 +        // we should reopen the gate for native signals in new focused editor.
   1.435 +        mIgnoreNativeCompositionEvent = false;
   1.436 +    }
   1.437 +}
   1.438 +
   1.439 +void
   1.440 +nsGtkIMModule::ResetIME()
   1.441 +{
   1.442 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.443 +        ("GtkIMModule(%p): ResetIME, mCompositionState=%s, mIsIMFocused=%s",
   1.444 +         this, GetCompositionStateName(), mIsIMFocused ? "YES" : "NO"));
   1.445 +
   1.446 +    GtkIMContext *im = GetContext();
   1.447 +    if (MOZ_UNLIKELY(!im)) {
   1.448 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.449 +            ("    FAILED, there are no context"));
   1.450 +        return;
   1.451 +    }
   1.452 +
   1.453 +    mIgnoreNativeCompositionEvent = true;
   1.454 +    gtk_im_context_reset(im);
   1.455 +}
   1.456 +
   1.457 +nsresult
   1.458 +nsGtkIMModule::CommitIMEComposition(nsWindow* aCaller)
   1.459 +{
   1.460 +    if (MOZ_UNLIKELY(IsDestroyed())) {
   1.461 +        return NS_OK;
   1.462 +    }
   1.463 +
   1.464 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.465 +        ("GtkIMModule(%p): CommitIMEComposition, aCaller=%p, "
   1.466 +         "mCompositionState=%s",
   1.467 +         this, aCaller, GetCompositionStateName()));
   1.468 +
   1.469 +    if (aCaller != mLastFocusedWindow) {
   1.470 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.471 +            ("    WARNING: the caller isn't focused window, mLastFocusedWindow=%p",
   1.472 +             mLastFocusedWindow));
   1.473 +        return NS_OK;
   1.474 +    }
   1.475 +
   1.476 +    if (!IsComposing()) {
   1.477 +        return NS_OK;
   1.478 +    }
   1.479 +
   1.480 +    // XXX We should commit composition ourselves temporary...
   1.481 +    ResetIME();
   1.482 +    CommitCompositionBy(mDispatchedCompositionString);
   1.483 +
   1.484 +    return NS_OK;
   1.485 +}
   1.486 +
   1.487 +nsresult
   1.488 +nsGtkIMModule::CancelIMEComposition(nsWindow* aCaller)
   1.489 +{
   1.490 +    if (MOZ_UNLIKELY(IsDestroyed())) {
   1.491 +        return NS_OK;
   1.492 +    }
   1.493 +
   1.494 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.495 +        ("GtkIMModule(%p): CancelIMEComposition, aCaller=%p",
   1.496 +         this, aCaller));
   1.497 +
   1.498 +    if (aCaller != mLastFocusedWindow) {
   1.499 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.500 +            ("    FAILED, the caller isn't focused window, mLastFocusedWindow=%p",
   1.501 +             mLastFocusedWindow));
   1.502 +        return NS_OK;
   1.503 +    }
   1.504 +
   1.505 +    if (!IsComposing()) {
   1.506 +        return NS_OK;
   1.507 +    }
   1.508 +
   1.509 +    GtkIMContext *im = GetContext();
   1.510 +    if (MOZ_UNLIKELY(!im)) {
   1.511 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.512 +            ("    FAILED, there are no context"));
   1.513 +        return NS_OK;
   1.514 +    }
   1.515 +
   1.516 +    ResetIME();
   1.517 +    CommitCompositionBy(EmptyString());
   1.518 +
   1.519 +    return NS_OK;
   1.520 +}
   1.521 +
   1.522 +void
   1.523 +nsGtkIMModule::OnUpdateComposition(void)
   1.524 +{
   1.525 +    if (MOZ_UNLIKELY(IsDestroyed())) {
   1.526 +        return;
   1.527 +    }
   1.528 +
   1.529 +    SetCursorPosition(mCompositionTargetOffset);
   1.530 +}
   1.531 +
   1.532 +void
   1.533 +nsGtkIMModule::SetInputContext(nsWindow* aCaller,
   1.534 +                               const InputContext* aContext,
   1.535 +                               const InputContextAction* aAction)
   1.536 +{
   1.537 +    if (MOZ_UNLIKELY(IsDestroyed())) {
   1.538 +        return;
   1.539 +    }
   1.540 +
   1.541 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.542 +        ("GtkIMModule(%p): SetInputContext, aCaller=%p, aState=%s mHTMLInputType=%s",
   1.543 +         this, aCaller, GetEnabledStateName(aContext->mIMEState.mEnabled),
   1.544 +         NS_ConvertUTF16toUTF8(aContext->mHTMLInputType).get()));
   1.545 +
   1.546 +    if (aCaller != mLastFocusedWindow) {
   1.547 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.548 +            ("    FAILED, the caller isn't focused window, mLastFocusedWindow=%p",
   1.549 +             mLastFocusedWindow));
   1.550 +        return;
   1.551 +    }
   1.552 +
   1.553 +    if (!mContext) {
   1.554 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.555 +            ("    FAILED, there are no context"));
   1.556 +        return;
   1.557 +    }
   1.558 +
   1.559 +
   1.560 +    if (sLastFocusedModule != this) {
   1.561 +        mInputContext = *aContext;
   1.562 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.563 +            ("    SUCCEEDED, but we're not active"));
   1.564 +        return;
   1.565 +    }
   1.566 +
   1.567 +    bool changingEnabledState =
   1.568 +        aContext->mIMEState.mEnabled != mInputContext.mIMEState.mEnabled ||
   1.569 +        aContext->mHTMLInputType != mInputContext.mHTMLInputType;
   1.570 +
   1.571 +    // Release current IME focus if IME is enabled.
   1.572 +    if (changingEnabledState && IsEditable()) {
   1.573 +        CommitIMEComposition(mLastFocusedWindow);
   1.574 +        Blur();
   1.575 +    }
   1.576 +
   1.577 +    mInputContext = *aContext;
   1.578 +
   1.579 +    if (changingEnabledState) {
   1.580 +#if (MOZ_WIDGET_GTK == 3)
   1.581 +        static bool sInputPurposeSupported = !gtk_check_version(3, 6, 0);
   1.582 +        if (sInputPurposeSupported && IsEditable()) {
   1.583 +            GtkIMContext* context = GetContext();
   1.584 +            if (context) {
   1.585 +                GtkInputPurpose purpose = GTK_INPUT_PURPOSE_FREE_FORM;
   1.586 +                const nsString& inputType = mInputContext.mHTMLInputType;
   1.587 +                // Password case has difficult issue.  Desktop IMEs disable
   1.588 +                // composition if input-purpose is password.
   1.589 +                // For disabling IME on |ime-mode: disabled;|, we need to check
   1.590 +                // mEnabled value instead of inputType value.  This hack also
   1.591 +                // enables composition on
   1.592 +                // <input type="password" style="ime-mode: enabled;">.
   1.593 +                // This is right behavior of ime-mode on desktop.
   1.594 +                //
   1.595 +                // On the other hand, IME for tablet devices may provide a
   1.596 +                // specific software keyboard for password field.  If so,
   1.597 +                // the behavior might look strange on both:
   1.598 +                //   <input type="text" style="ime-mode: disabled;">
   1.599 +                //   <input type="password" style="ime-mode: enabled;">
   1.600 +                //
   1.601 +                // Temporarily, we should focus on desktop environment for now.
   1.602 +                // I.e., let's ignore tablet devices for now.  When somebody
   1.603 +                // reports actual trouble on tablet devices, we should try to
   1.604 +                // look for a way to solve actual problem.
   1.605 +                if (mInputContext.mIMEState.mEnabled == IMEState::PASSWORD) {
   1.606 +                    purpose = GTK_INPUT_PURPOSE_PASSWORD;
   1.607 +                } else if (inputType.EqualsLiteral("email")) {
   1.608 +                    purpose = GTK_INPUT_PURPOSE_EMAIL;
   1.609 +                } else if (inputType.EqualsLiteral("url")) {
   1.610 +                    purpose = GTK_INPUT_PURPOSE_URL;
   1.611 +                } else if (inputType.EqualsLiteral("tel")) {
   1.612 +                    purpose = GTK_INPUT_PURPOSE_PHONE;
   1.613 +                } else if (inputType.EqualsLiteral("number")) {
   1.614 +                    purpose = GTK_INPUT_PURPOSE_NUMBER;
   1.615 +                }
   1.616 +
   1.617 +                g_object_set(context, "input-purpose", purpose, nullptr);
   1.618 +            }
   1.619 +        }
   1.620 +#endif // #if (MOZ_WIDGET_GTK == 3)
   1.621 +
   1.622 +        // Even when aState is not enabled state, we need to set IME focus.
   1.623 +        // Because some IMs are updating the status bar of them at this time.
   1.624 +        // Be aware, don't use aWindow here because this method shouldn't move
   1.625 +        // focus actually.
   1.626 +        Focus();
   1.627 +
   1.628 +        // XXX Should we call Blur() when it's not editable?  E.g., it might be
   1.629 +        //     better to close VKB automatically.
   1.630 +    }
   1.631 +}
   1.632 +
   1.633 +InputContext
   1.634 +nsGtkIMModule::GetInputContext()
   1.635 +{
   1.636 +    mInputContext.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
   1.637 +    return mInputContext;
   1.638 +}
   1.639 +
   1.640 +/* static */
   1.641 +bool
   1.642 +nsGtkIMModule::IsVirtualKeyboardOpened()
   1.643 +{
   1.644 +    return false;
   1.645 +}
   1.646 +
   1.647 +GtkIMContext*
   1.648 +nsGtkIMModule::GetContext()
   1.649 +{
   1.650 +    if (IsEnabled()) {
   1.651 +        return mContext;
   1.652 +    }
   1.653 +    if (mInputContext.mIMEState.mEnabled == IMEState::PASSWORD) {
   1.654 +        return mSimpleContext;
   1.655 +    }
   1.656 +    return mDummyContext;
   1.657 +}
   1.658 +
   1.659 +bool
   1.660 +nsGtkIMModule::IsEnabled()
   1.661 +{
   1.662 +    return mInputContext.mIMEState.mEnabled == IMEState::ENABLED ||
   1.663 +           mInputContext.mIMEState.mEnabled == IMEState::PLUGIN ||
   1.664 +           (!sUseSimpleContext &&
   1.665 +            mInputContext.mIMEState.mEnabled == IMEState::PASSWORD);
   1.666 +}
   1.667 +
   1.668 +bool
   1.669 +nsGtkIMModule::IsEditable()
   1.670 +{
   1.671 +    return mInputContext.mIMEState.mEnabled == IMEState::ENABLED ||
   1.672 +           mInputContext.mIMEState.mEnabled == IMEState::PLUGIN ||
   1.673 +           mInputContext.mIMEState.mEnabled == IMEState::PASSWORD;
   1.674 +}
   1.675 +
   1.676 +void
   1.677 +nsGtkIMModule::Focus()
   1.678 +{
   1.679 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.680 +        ("GtkIMModule(%p): Focus, sLastFocusedModule=%p",
   1.681 +         this, sLastFocusedModule));
   1.682 +
   1.683 +    if (mIsIMFocused) {
   1.684 +        NS_ASSERTION(sLastFocusedModule == this,
   1.685 +                     "We're not active, but the IM was focused?");
   1.686 +        return;
   1.687 +    }
   1.688 +
   1.689 +    GtkIMContext *im = GetContext();
   1.690 +    if (!im) {
   1.691 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.692 +            ("    FAILED, there are no context"));
   1.693 +        return;
   1.694 +    }
   1.695 +
   1.696 +    if (sLastFocusedModule && sLastFocusedModule != this) {
   1.697 +        sLastFocusedModule->Blur();
   1.698 +    }
   1.699 +
   1.700 +    sLastFocusedModule = this;
   1.701 +
   1.702 +    gtk_im_context_focus_in(im);
   1.703 +    mIsIMFocused = true;
   1.704 +
   1.705 +    if (!IsEnabled()) {
   1.706 +        // We should release IME focus for uim and scim.
   1.707 +        // These IMs are using snooper that is released at losing focus.
   1.708 +        Blur();
   1.709 +    }
   1.710 +}
   1.711 +
   1.712 +void
   1.713 +nsGtkIMModule::Blur()
   1.714 +{
   1.715 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.716 +        ("GtkIMModule(%p): Blur, mIsIMFocused=%s",
   1.717 +         this, mIsIMFocused ? "YES" : "NO"));
   1.718 +
   1.719 +    if (!mIsIMFocused) {
   1.720 +        return;
   1.721 +    }
   1.722 +
   1.723 +    GtkIMContext *im = GetContext();
   1.724 +    if (!im) {
   1.725 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.726 +            ("    FAILED, there are no context"));
   1.727 +        return;
   1.728 +    }
   1.729 +
   1.730 +    gtk_im_context_focus_out(im);
   1.731 +    mIsIMFocused = false;
   1.732 +}
   1.733 +
   1.734 +/* static */
   1.735 +void
   1.736 +nsGtkIMModule::OnStartCompositionCallback(GtkIMContext *aContext,
   1.737 +                                          nsGtkIMModule* aModule)
   1.738 +{
   1.739 +    aModule->OnStartCompositionNative(aContext);
   1.740 +}
   1.741 +
   1.742 +void
   1.743 +nsGtkIMModule::OnStartCompositionNative(GtkIMContext *aContext)
   1.744 +{
   1.745 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.746 +        ("GtkIMModule(%p): OnStartCompositionNative, aContext=%p",
   1.747 +         this, aContext));
   1.748 +
   1.749 +    // See bug 472635, we should do nothing if IM context doesn't match.
   1.750 +    if (GetContext() != aContext) {
   1.751 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.752 +            ("    FAILED, given context doesn't match, GetContext()=%p",
   1.753 +             GetContext()));
   1.754 +        return;
   1.755 +    }
   1.756 +
   1.757 +    if (!DispatchCompositionStart()) {
   1.758 +        return;
   1.759 +    }
   1.760 +    mCompositionTargetOffset = mCompositionStart;
   1.761 +}
   1.762 +
   1.763 +/* static */
   1.764 +void
   1.765 +nsGtkIMModule::OnEndCompositionCallback(GtkIMContext *aContext,
   1.766 +                                        nsGtkIMModule* aModule)
   1.767 +{
   1.768 +    aModule->OnEndCompositionNative(aContext);
   1.769 +}
   1.770 +
   1.771 +void
   1.772 +nsGtkIMModule::OnEndCompositionNative(GtkIMContext *aContext)
   1.773 +{
   1.774 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.775 +        ("GtkIMModule(%p): OnEndCompositionNative, aContext=%p",
   1.776 +         this, aContext));
   1.777 +
   1.778 +    // See bug 472635, we should do nothing if IM context doesn't match.
   1.779 +    if (GetContext() != aContext) {
   1.780 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.781 +            ("    FAILED, given context doesn't match, GetContext()=%p",
   1.782 +             GetContext()));
   1.783 +        return;
   1.784 +    }
   1.785 +
   1.786 +    bool shouldIgnoreThisEvent = ShouldIgnoreNativeCompositionEvent();
   1.787 +
   1.788 +    // Finish the cancelling mode here rather than DispatchCompositionEnd()
   1.789 +    // because DispatchCompositionEnd() is called ourselves when we need to
   1.790 +    // commit the composition string *before* the focus moves completely.
   1.791 +    // Note that the native commit can be fired *after* ResetIME().
   1.792 +    mIgnoreNativeCompositionEvent = false;
   1.793 +
   1.794 +    if (!IsComposing() || shouldIgnoreThisEvent) {
   1.795 +        // If we already handled the commit event, we should do nothing here.
   1.796 +        return;
   1.797 +    }
   1.798 +
   1.799 +    // Be aware, widget can be gone
   1.800 +    DispatchCompositionEnd();
   1.801 +}
   1.802 +
   1.803 +/* static */
   1.804 +void
   1.805 +nsGtkIMModule::OnChangeCompositionCallback(GtkIMContext *aContext,
   1.806 +                                           nsGtkIMModule* aModule)
   1.807 +{
   1.808 +    aModule->OnChangeCompositionNative(aContext);
   1.809 +}
   1.810 +
   1.811 +void
   1.812 +nsGtkIMModule::OnChangeCompositionNative(GtkIMContext *aContext)
   1.813 +{
   1.814 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.815 +        ("GtkIMModule(%p): OnChangeCompositionNative, aContext=%p",
   1.816 +         this, aContext));
   1.817 +
   1.818 +    // See bug 472635, we should do nothing if IM context doesn't match.
   1.819 +    if (GetContext() != aContext) {
   1.820 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.821 +            ("    FAILED, given context doesn't match, GetContext()=%p",
   1.822 +             GetContext()));
   1.823 +        return;
   1.824 +    }
   1.825 +
   1.826 +    if (ShouldIgnoreNativeCompositionEvent()) {
   1.827 +        return;
   1.828 +    }
   1.829 +
   1.830 +    nsAutoString compositionString;
   1.831 +    GetCompositionString(compositionString);
   1.832 +    if (!IsComposing() && compositionString.IsEmpty()) {
   1.833 +        mDispatchedCompositionString.Truncate();
   1.834 +        return; // Don't start the composition with empty string.
   1.835 +    }
   1.836 +
   1.837 +    // Be aware, widget can be gone
   1.838 +    DispatchTextEvent(compositionString, false);
   1.839 +}
   1.840 +
   1.841 +/* static */
   1.842 +gboolean
   1.843 +nsGtkIMModule::OnRetrieveSurroundingCallback(GtkIMContext  *aContext,
   1.844 +                                             nsGtkIMModule *aModule)
   1.845 +{
   1.846 +    return aModule->OnRetrieveSurroundingNative(aContext);
   1.847 +}
   1.848 +
   1.849 +gboolean
   1.850 +nsGtkIMModule::OnRetrieveSurroundingNative(GtkIMContext *aContext)
   1.851 +{
   1.852 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.853 +        ("GtkIMModule(%p): OnRetrieveSurroundingNative, aContext=%p, current context=%p",
   1.854 +         this, aContext, GetContext()));
   1.855 +
   1.856 +    if (GetContext() != aContext) {
   1.857 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.858 +            ("    FAILED, given context doesn't match, GetContext()=%p",
   1.859 +             GetContext()));
   1.860 +        return FALSE;
   1.861 +    }
   1.862 +
   1.863 +    nsAutoString uniStr;
   1.864 +    uint32_t cursorPos;
   1.865 +    if (NS_FAILED(GetCurrentParagraph(uniStr, cursorPos))) {
   1.866 +        return FALSE;
   1.867 +    }
   1.868 +
   1.869 +    NS_ConvertUTF16toUTF8 utf8Str(nsDependentSubstring(uniStr, 0, cursorPos));
   1.870 +    uint32_t cursorPosInUTF8 = utf8Str.Length();
   1.871 +    AppendUTF16toUTF8(nsDependentSubstring(uniStr, cursorPos), utf8Str);
   1.872 +    gtk_im_context_set_surrounding(aContext, utf8Str.get(), utf8Str.Length(),
   1.873 +                                   cursorPosInUTF8);
   1.874 +    return TRUE;
   1.875 +}
   1.876 +
   1.877 +/* static */
   1.878 +gboolean
   1.879 +nsGtkIMModule::OnDeleteSurroundingCallback(GtkIMContext  *aContext,
   1.880 +                                           gint           aOffset,
   1.881 +                                           gint           aNChars,
   1.882 +                                           nsGtkIMModule *aModule)
   1.883 +{
   1.884 +    return aModule->OnDeleteSurroundingNative(aContext, aOffset, aNChars);
   1.885 +}
   1.886 +
   1.887 +gboolean
   1.888 +nsGtkIMModule::OnDeleteSurroundingNative(GtkIMContext  *aContext,
   1.889 +                                         gint           aOffset,
   1.890 +                                         gint           aNChars)
   1.891 +{
   1.892 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.893 +        ("GtkIMModule(%p): OnDeleteSurroundingNative, aContext=%p, current context=%p",
   1.894 +         this, aContext, GetContext()));
   1.895 +
   1.896 +    if (GetContext() != aContext) {
   1.897 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.898 +            ("    FAILED, given context doesn't match, GetContext()=%p",
   1.899 +             GetContext()));
   1.900 +        return FALSE;
   1.901 +    }
   1.902 +
   1.903 +    if (NS_SUCCEEDED(DeleteText(aOffset, (uint32_t)aNChars))) {
   1.904 +        return TRUE;
   1.905 +    }
   1.906 +
   1.907 +    // failed
   1.908 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.909 +        ("    FAILED, cannot delete text"));
   1.910 +    return FALSE;
   1.911 +}
   1.912 +                         
   1.913 +/* static */
   1.914 +void
   1.915 +nsGtkIMModule::OnCommitCompositionCallback(GtkIMContext *aContext,
   1.916 +                                           const gchar *aString,
   1.917 +                                           nsGtkIMModule* aModule)
   1.918 +{
   1.919 +    aModule->OnCommitCompositionNative(aContext, aString);
   1.920 +}
   1.921 +
   1.922 +void
   1.923 +nsGtkIMModule::OnCommitCompositionNative(GtkIMContext *aContext,
   1.924 +                                         const gchar *aUTF8Char)
   1.925 +{
   1.926 +    const gchar emptyStr = 0;
   1.927 +    const gchar *commitString = aUTF8Char ? aUTF8Char : &emptyStr;
   1.928 +
   1.929 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.930 +        ("GtkIMModule(%p): OnCommitCompositionNative, aContext=%p, current context=%p, commitString=\"%s\"",
   1.931 +         this, aContext, GetContext(), commitString));
   1.932 +
   1.933 +    // See bug 472635, we should do nothing if IM context doesn't match.
   1.934 +    if (GetContext() != aContext) {
   1.935 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.936 +            ("    FAILED, given context doesn't match, GetContext()=%p",
   1.937 +             GetContext()));
   1.938 +        return;
   1.939 +    }
   1.940 +
   1.941 +    // If we are not in composition and committing with empty string,
   1.942 +    // we need to do nothing because if we continued to handle this
   1.943 +    // signal, we would dispatch compositionstart, text, compositionend
   1.944 +    // events with empty string.  Of course, they are unnecessary events
   1.945 +    // for Web applications and our editor.
   1.946 +    if (!IsComposing() && !commitString[0]) {
   1.947 +        return;
   1.948 +    }
   1.949 +
   1.950 +    if (ShouldIgnoreNativeCompositionEvent()) {
   1.951 +        return;
   1.952 +    }
   1.953 +
   1.954 +    // If IME doesn't change their keyevent that generated this commit,
   1.955 +    // don't send it through XIM - just send it as a normal key press
   1.956 +    // event.
   1.957 +    if (!IsComposing() && mProcessingKeyEvent) {
   1.958 +        char keyval_utf8[8]; /* should have at least 6 bytes of space */
   1.959 +        gint keyval_utf8_len;
   1.960 +        guint32 keyval_unicode;
   1.961 +
   1.962 +        keyval_unicode = gdk_keyval_to_unicode(mProcessingKeyEvent->keyval);
   1.963 +        keyval_utf8_len = g_unichar_to_utf8(keyval_unicode, keyval_utf8);
   1.964 +        keyval_utf8[keyval_utf8_len] = '\0';
   1.965 +
   1.966 +        if (!strcmp(commitString, keyval_utf8)) {
   1.967 +            PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.968 +                ("GtkIMModule(%p): OnCommitCompositionNative, we'll send normal key event",
   1.969 +                 this));
   1.970 +            mFilterKeyEvent = false;
   1.971 +            return;
   1.972 +        }
   1.973 +    }
   1.974 +
   1.975 +    NS_ConvertUTF8toUTF16 str(commitString);
   1.976 +    CommitCompositionBy(str); // Be aware, widget can be gone
   1.977 +}
   1.978 +
   1.979 +bool
   1.980 +nsGtkIMModule::CommitCompositionBy(const nsAString& aString)
   1.981 +{
   1.982 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
   1.983 +        ("GtkIMModule(%p): CommitCompositionBy, aString=\"%s\", "
   1.984 +         "mDispatchedCompositionString=\"%s\"",
   1.985 +         this, NS_ConvertUTF16toUTF8(aString).get(),
   1.986 +         NS_ConvertUTF16toUTF8(mDispatchedCompositionString).get()));
   1.987 +
   1.988 +    if (!DispatchTextEvent(aString, true)) {
   1.989 +        return false;
   1.990 +    }
   1.991 +    // We should dispatch the compositionend event here because some IMEs
   1.992 +    // might not fire "preedit_end" native event.
   1.993 +    return DispatchCompositionEnd(); // Be aware, widget can be gone
   1.994 +}
   1.995 +
   1.996 +void
   1.997 +nsGtkIMModule::GetCompositionString(nsAString &aCompositionString)
   1.998 +{
   1.999 +    gchar *preedit_string;
  1.1000 +    gint cursor_pos;
  1.1001 +    PangoAttrList *feedback_list;
  1.1002 +    gtk_im_context_get_preedit_string(GetContext(), &preedit_string,
  1.1003 +                                      &feedback_list, &cursor_pos);
  1.1004 +    if (preedit_string && *preedit_string) {
  1.1005 +        CopyUTF8toUTF16(preedit_string, aCompositionString);
  1.1006 +    } else {
  1.1007 +        aCompositionString.Truncate();
  1.1008 +    }
  1.1009 +
  1.1010 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1011 +        ("GtkIMModule(%p): GetCompositionString, result=\"%s\"",
  1.1012 +         this, preedit_string));
  1.1013 +
  1.1014 +    pango_attr_list_unref(feedback_list);
  1.1015 +    g_free(preedit_string);
  1.1016 +}
  1.1017 +
  1.1018 +bool
  1.1019 +nsGtkIMModule::DispatchCompositionStart()
  1.1020 +{
  1.1021 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1022 +        ("GtkIMModule(%p): DispatchCompositionStart", this));
  1.1023 +
  1.1024 +    if (IsComposing()) {
  1.1025 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1026 +            ("    WARNING, we're already in composition"));
  1.1027 +        return true;
  1.1028 +    }
  1.1029 +
  1.1030 +    if (!mLastFocusedWindow) {
  1.1031 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1032 +            ("    FAILED, there are no focused window in this module"));
  1.1033 +        return false;
  1.1034 +    }
  1.1035 +
  1.1036 +    nsEventStatus status;
  1.1037 +    WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT,
  1.1038 +                                      mLastFocusedWindow);
  1.1039 +    InitEvent(selection);
  1.1040 +    mLastFocusedWindow->DispatchEvent(&selection, status);
  1.1041 +
  1.1042 +    if (!selection.mSucceeded || selection.mReply.mOffset == UINT32_MAX) {
  1.1043 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1044 +            ("    FAILED, cannot query the selection offset"));
  1.1045 +        return false;
  1.1046 +    }
  1.1047 +
  1.1048 +    // XXX The composition start point might be changed by composition events
  1.1049 +    //     even though we strongly hope it doesn't happen.
  1.1050 +    //     Every composition event should have the start offset for the result
  1.1051 +    //     because it may high cost if we query the offset every time.
  1.1052 +    mCompositionStart = selection.mReply.mOffset;
  1.1053 +    mDispatchedCompositionString.Truncate();
  1.1054 +
  1.1055 +    if (mProcessingKeyEvent && !mKeyDownEventWasSent &&
  1.1056 +        mProcessingKeyEvent->type == GDK_KEY_PRESS) {
  1.1057 +        // If this composition is started by a native keydown event, we need to
  1.1058 +        // dispatch our keydown event here (before composition start).
  1.1059 +        nsCOMPtr<nsIWidget> kungFuDeathGrip = mLastFocusedWindow;
  1.1060 +        bool isCancelled;
  1.1061 +        mLastFocusedWindow->DispatchKeyDownEvent(mProcessingKeyEvent,
  1.1062 +                                                 &isCancelled);
  1.1063 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1064 +            ("    keydown event is dispatched"));
  1.1065 +        if (static_cast<nsWindow*>(kungFuDeathGrip.get())->IsDestroyed() ||
  1.1066 +            kungFuDeathGrip != mLastFocusedWindow) {
  1.1067 +            PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1068 +                ("    NOTE, the focused widget was destroyed/changed by keydown event"));
  1.1069 +            return false;
  1.1070 +        }
  1.1071 +    }
  1.1072 +
  1.1073 +    if (mIgnoreNativeCompositionEvent) {
  1.1074 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1075 +            ("    WARNING, mIgnoreNativeCompositionEvent is already TRUE, but we forcedly reset"));
  1.1076 +        mIgnoreNativeCompositionEvent = false;
  1.1077 +    }
  1.1078 +
  1.1079 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1080 +        ("    mCompositionStart=%u", mCompositionStart));
  1.1081 +    mCompositionState = eCompositionState_CompositionStartDispatched;
  1.1082 +    WidgetCompositionEvent compEvent(true, NS_COMPOSITION_START,
  1.1083 +                                     mLastFocusedWindow);
  1.1084 +    InitEvent(compEvent);
  1.1085 +    nsCOMPtr<nsIWidget> kungFuDeathGrip = mLastFocusedWindow;
  1.1086 +    mLastFocusedWindow->DispatchEvent(&compEvent, status);
  1.1087 +    if (static_cast<nsWindow*>(kungFuDeathGrip.get())->IsDestroyed() ||
  1.1088 +        kungFuDeathGrip != mLastFocusedWindow) {
  1.1089 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1090 +            ("    NOTE, the focused widget was destroyed/changed by compositionstart event"));
  1.1091 +        return false;
  1.1092 +    }
  1.1093 +
  1.1094 +    return true;
  1.1095 +}
  1.1096 +
  1.1097 +bool
  1.1098 +nsGtkIMModule::DispatchCompositionEnd()
  1.1099 +{
  1.1100 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1101 +        ("GtkIMModule(%p): DispatchCompositionEnd, "
  1.1102 +         "mDispatchedCompositionString=\"%s\"",
  1.1103 +         this, NS_ConvertUTF16toUTF8(mDispatchedCompositionString).get()));
  1.1104 +
  1.1105 +    if (!IsComposing()) {
  1.1106 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1107 +            ("    WARNING, we have alrady finished the composition"));
  1.1108 +        return false;
  1.1109 +    }
  1.1110 +
  1.1111 +    if (!mLastFocusedWindow) {
  1.1112 +        mDispatchedCompositionString.Truncate();
  1.1113 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1114 +            ("    FAILED, there are no focused window in this module"));
  1.1115 +        return false;
  1.1116 +    }
  1.1117 +
  1.1118 +    WidgetCompositionEvent compEvent(true, NS_COMPOSITION_END,
  1.1119 +                                     mLastFocusedWindow);
  1.1120 +    InitEvent(compEvent);
  1.1121 +    compEvent.data = mDispatchedCompositionString;
  1.1122 +    nsEventStatus status;
  1.1123 +    nsCOMPtr<nsIWidget> kungFuDeathGrip = mLastFocusedWindow;
  1.1124 +    mLastFocusedWindow->DispatchEvent(&compEvent, status);
  1.1125 +    mCompositionState = eCompositionState_NotComposing;
  1.1126 +    mCompositionStart = UINT32_MAX;
  1.1127 +    mCompositionTargetOffset = UINT32_MAX;
  1.1128 +    mDispatchedCompositionString.Truncate();
  1.1129 +    if (static_cast<nsWindow*>(kungFuDeathGrip.get())->IsDestroyed() ||
  1.1130 +        kungFuDeathGrip != mLastFocusedWindow) {
  1.1131 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1132 +            ("    NOTE, the focused widget was destroyed/changed by compositionend event"));
  1.1133 +        return false;
  1.1134 +    }
  1.1135 +
  1.1136 +    return true;
  1.1137 +}
  1.1138 +
  1.1139 +bool
  1.1140 +nsGtkIMModule::DispatchTextEvent(const nsAString &aCompositionString,
  1.1141 +                                 bool aIsCommit)
  1.1142 +{
  1.1143 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1144 +        ("GtkIMModule(%p): DispatchTextEvent, aIsCommit=%s",
  1.1145 +         this, aIsCommit ? "TRUE" : "FALSE"));
  1.1146 +
  1.1147 +    if (!mLastFocusedWindow) {
  1.1148 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1149 +            ("    FAILED, there are no focused window in this module"));
  1.1150 +        return false;
  1.1151 +    }
  1.1152 +
  1.1153 +    if (!IsComposing()) {
  1.1154 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1155 +            ("    The composition wasn't started, force starting..."));
  1.1156 +        nsCOMPtr<nsIWidget> kungFuDeathGrip = mLastFocusedWindow;
  1.1157 +        if (!DispatchCompositionStart()) {
  1.1158 +            return false;
  1.1159 +        }
  1.1160 +    }
  1.1161 +
  1.1162 +    nsEventStatus status;
  1.1163 +    nsRefPtr<nsWindow> lastFocusedWindow = mLastFocusedWindow;
  1.1164 +
  1.1165 +    if (aCompositionString != mDispatchedCompositionString) {
  1.1166 +      WidgetCompositionEvent compositionUpdate(true, NS_COMPOSITION_UPDATE,
  1.1167 +                                               mLastFocusedWindow);
  1.1168 +      InitEvent(compositionUpdate);
  1.1169 +      compositionUpdate.data = aCompositionString;
  1.1170 +      mDispatchedCompositionString = aCompositionString;
  1.1171 +      mLastFocusedWindow->DispatchEvent(&compositionUpdate, status);
  1.1172 +      if (lastFocusedWindow->IsDestroyed() ||
  1.1173 +          lastFocusedWindow != mLastFocusedWindow) {
  1.1174 +          PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1175 +              ("    NOTE, the focused widget was destroyed/changed by compositionupdate"));
  1.1176 +          return false;
  1.1177 +      }
  1.1178 +    }
  1.1179 +
  1.1180 +    // Store the selected string which will be removed by following text event.
  1.1181 +    if (mCompositionState == eCompositionState_CompositionStartDispatched) {
  1.1182 +        // XXX We should assume, for now, any web applications don't change
  1.1183 +        //     selection at handling this text event.
  1.1184 +        WidgetQueryContentEvent querySelectedTextEvent(true,
  1.1185 +                                                       NS_QUERY_SELECTED_TEXT,
  1.1186 +                                                       mLastFocusedWindow);
  1.1187 +        mLastFocusedWindow->DispatchEvent(&querySelectedTextEvent, status);
  1.1188 +        if (querySelectedTextEvent.mSucceeded) {
  1.1189 +            mSelectedString = querySelectedTextEvent.mReply.mString;
  1.1190 +            mCompositionStart = querySelectedTextEvent.mReply.mOffset;
  1.1191 +        }
  1.1192 +    }
  1.1193 +
  1.1194 +    WidgetTextEvent textEvent(true, NS_TEXT_TEXT, mLastFocusedWindow);
  1.1195 +    InitEvent(textEvent);
  1.1196 +
  1.1197 +    uint32_t targetOffset = mCompositionStart;
  1.1198 +
  1.1199 +    if (!aIsCommit) {
  1.1200 +        // NOTE: SetTextRangeList() assumes that mDispatchedCompositionString
  1.1201 +        //       has been updated already.
  1.1202 +        textEvent.mRanges = CreateTextRangeArray();
  1.1203 +        targetOffset += textEvent.mRanges->TargetClauseOffset();
  1.1204 +    }
  1.1205 +
  1.1206 +    textEvent.theText = mDispatchedCompositionString.get();
  1.1207 +
  1.1208 +    mCompositionState = aIsCommit ?
  1.1209 +        eCompositionState_CommitTextEventDispatched :
  1.1210 +        eCompositionState_TextEventDispatched;
  1.1211 +
  1.1212 +    mLastFocusedWindow->DispatchEvent(&textEvent, status);
  1.1213 +    if (lastFocusedWindow->IsDestroyed() ||
  1.1214 +        lastFocusedWindow != mLastFocusedWindow) {
  1.1215 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1216 +            ("    NOTE, the focused widget was destroyed/changed by text event"));
  1.1217 +        return false;
  1.1218 +    }
  1.1219 +
  1.1220 +    // We cannot call SetCursorPosition for e10s-aware.
  1.1221 +    // DispatchEvent is async on e10s, so composition rect isn't updated now
  1.1222 +    // on tab parent.
  1.1223 +    mCompositionTargetOffset = targetOffset;
  1.1224 +
  1.1225 +    return true;
  1.1226 +}
  1.1227 +
  1.1228 +already_AddRefed<TextRangeArray>
  1.1229 +nsGtkIMModule::CreateTextRangeArray()
  1.1230 +{
  1.1231 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1232 +        ("GtkIMModule(%p): CreateTextRangeArray", this));
  1.1233 +
  1.1234 +    nsRefPtr<TextRangeArray> textRangeArray = new TextRangeArray();
  1.1235 +
  1.1236 +    gchar *preedit_string;
  1.1237 +    gint cursor_pos;
  1.1238 +    PangoAttrList *feedback_list;
  1.1239 +    gtk_im_context_get_preedit_string(GetContext(), &preedit_string,
  1.1240 +                                      &feedback_list, &cursor_pos);
  1.1241 +    if (!preedit_string || !*preedit_string) {
  1.1242 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1243 +            ("    preedit_string is null"));
  1.1244 +        pango_attr_list_unref(feedback_list);
  1.1245 +        g_free(preedit_string);
  1.1246 +        return textRangeArray.forget();
  1.1247 +    }
  1.1248 +
  1.1249 +    PangoAttrIterator* iter;
  1.1250 +    iter = pango_attr_list_get_iterator(feedback_list);
  1.1251 +    if (!iter) {
  1.1252 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1253 +            ("    FAILED, iterator couldn't be allocated"));
  1.1254 +        pango_attr_list_unref(feedback_list);
  1.1255 +        g_free(preedit_string);
  1.1256 +        return textRangeArray.forget();
  1.1257 +    }
  1.1258 +
  1.1259 +    /*
  1.1260 +     * Depend on gtk2's implementation on XIM support.
  1.1261 +     * In aFeedback got from gtk2, there are only three types of data:
  1.1262 +     * PANGO_ATTR_UNDERLINE, PANGO_ATTR_FOREGROUND, PANGO_ATTR_BACKGROUND.
  1.1263 +     * Corresponding to XIMUnderline, XIMReverse.
  1.1264 +     * Don't take PANGO_ATTR_BACKGROUND into account, since
  1.1265 +     * PANGO_ATTR_BACKGROUND and PANGO_ATTR_FOREGROUND are always
  1.1266 +     * a couple.
  1.1267 +     */
  1.1268 +    do {
  1.1269 +        PangoAttribute* attrUnderline =
  1.1270 +            pango_attr_iterator_get(iter, PANGO_ATTR_UNDERLINE);
  1.1271 +        PangoAttribute* attrForeground =
  1.1272 +            pango_attr_iterator_get(iter, PANGO_ATTR_FOREGROUND);
  1.1273 +        if (!attrUnderline && !attrForeground) {
  1.1274 +            continue;
  1.1275 +        }
  1.1276 +
  1.1277 +        // Get the range of the current attribute(s)
  1.1278 +        gint start, end;
  1.1279 +        pango_attr_iterator_range(iter, &start, &end);
  1.1280 +
  1.1281 +        TextRange range;
  1.1282 +        // XIMReverse | XIMUnderline
  1.1283 +        if (attrUnderline && attrForeground) {
  1.1284 +            range.mRangeType = NS_TEXTRANGE_SELECTEDCONVERTEDTEXT;
  1.1285 +        }
  1.1286 +        // XIMUnderline
  1.1287 +        else if (attrUnderline) {
  1.1288 +            range.mRangeType = NS_TEXTRANGE_CONVERTEDTEXT;
  1.1289 +        }
  1.1290 +        // XIMReverse
  1.1291 +        else if (attrForeground) {
  1.1292 +            range.mRangeType = NS_TEXTRANGE_SELECTEDRAWTEXT;
  1.1293 +        } else {
  1.1294 +            range.mRangeType = NS_TEXTRANGE_RAWINPUT;
  1.1295 +        }
  1.1296 +
  1.1297 +        gunichar2* uniStr = nullptr;
  1.1298 +        if (start == 0) {
  1.1299 +            range.mStartOffset = 0;
  1.1300 +        } else {
  1.1301 +            glong uniStrLen;
  1.1302 +            uniStr = g_utf8_to_utf16(preedit_string, start,
  1.1303 +                                     nullptr, &uniStrLen, nullptr);
  1.1304 +            if (uniStr) {
  1.1305 +                range.mStartOffset = uniStrLen;
  1.1306 +                g_free(uniStr);
  1.1307 +                uniStr = nullptr;
  1.1308 +            }
  1.1309 +        }
  1.1310 +
  1.1311 +        glong uniStrLen;
  1.1312 +        uniStr = g_utf8_to_utf16(preedit_string + start, end - start,
  1.1313 +                                 nullptr, &uniStrLen, nullptr);
  1.1314 +        if (!uniStr) {
  1.1315 +            range.mEndOffset = range.mStartOffset;
  1.1316 +        } else {
  1.1317 +            range.mEndOffset = range.mStartOffset + uniStrLen;
  1.1318 +            g_free(uniStr);
  1.1319 +            uniStr = nullptr;
  1.1320 +        }
  1.1321 +
  1.1322 +        textRangeArray->AppendElement(range);
  1.1323 +
  1.1324 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1325 +            ("    mStartOffset=%u, mEndOffset=%u, mRangeType=%s",
  1.1326 +             range.mStartOffset, range.mEndOffset,
  1.1327 +             GetRangeTypeName(range.mRangeType)));
  1.1328 +    } while (pango_attr_iterator_next(iter));
  1.1329 +
  1.1330 +    TextRange range;
  1.1331 +    if (cursor_pos < 0) {
  1.1332 +        range.mStartOffset = 0;
  1.1333 +    } else if (uint32_t(cursor_pos) > mDispatchedCompositionString.Length()) {
  1.1334 +        range.mStartOffset = mDispatchedCompositionString.Length();
  1.1335 +    } else {
  1.1336 +        range.mStartOffset = uint32_t(cursor_pos);
  1.1337 +    }
  1.1338 +    range.mEndOffset = range.mStartOffset;
  1.1339 +    range.mRangeType = NS_TEXTRANGE_CARETPOSITION;
  1.1340 +    textRangeArray->AppendElement(range);
  1.1341 +
  1.1342 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1343 +        ("    mStartOffset=%u, mEndOffset=%u, mRangeType=%s",
  1.1344 +         range.mStartOffset, range.mEndOffset,
  1.1345 +         GetRangeTypeName(range.mRangeType)));
  1.1346 +
  1.1347 +    pango_attr_iterator_destroy(iter);
  1.1348 +    pango_attr_list_unref(feedback_list);
  1.1349 +    g_free(preedit_string);
  1.1350 +
  1.1351 +    return textRangeArray.forget();
  1.1352 +}
  1.1353 +
  1.1354 +void
  1.1355 +nsGtkIMModule::SetCursorPosition(uint32_t aTargetOffset)
  1.1356 +{
  1.1357 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1358 +        ("GtkIMModule(%p): SetCursorPosition, aTargetOffset=%u",
  1.1359 +         this, aTargetOffset));
  1.1360 +
  1.1361 +    if (aTargetOffset == UINT32_MAX) {
  1.1362 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1363 +            ("    FAILED, aTargetOffset is wrong offset"));
  1.1364 +        return;
  1.1365 +    }
  1.1366 +
  1.1367 +    if (!mLastFocusedWindow) {
  1.1368 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1369 +            ("    FAILED, there are no focused window"));
  1.1370 +        return;
  1.1371 +    }
  1.1372 +
  1.1373 +    GtkIMContext *im = GetContext();
  1.1374 +    if (!im) {
  1.1375 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1376 +            ("    FAILED, there are no context"));
  1.1377 +        return;
  1.1378 +    }
  1.1379 +
  1.1380 +    WidgetQueryContentEvent charRect(true, NS_QUERY_TEXT_RECT,
  1.1381 +                                     mLastFocusedWindow);
  1.1382 +    charRect.InitForQueryTextRect(aTargetOffset, 1);
  1.1383 +    InitEvent(charRect);
  1.1384 +    nsEventStatus status;
  1.1385 +    mLastFocusedWindow->DispatchEvent(&charRect, status);
  1.1386 +    if (!charRect.mSucceeded) {
  1.1387 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1388 +            ("    FAILED, NS_QUERY_TEXT_RECT was failed"));
  1.1389 +        return;
  1.1390 +    }
  1.1391 +    nsWindow* rootWindow =
  1.1392 +        static_cast<nsWindow*>(mLastFocusedWindow->GetTopLevelWidget());
  1.1393 +
  1.1394 +    // Get the position of the rootWindow in screen.
  1.1395 +    gint rootX, rootY;
  1.1396 +    gdk_window_get_origin(rootWindow->GetGdkWindow(), &rootX, &rootY);
  1.1397 +
  1.1398 +    // Get the position of IM context owner window in screen.
  1.1399 +    gint ownerX, ownerY;
  1.1400 +    gdk_window_get_origin(mOwnerWindow->GetGdkWindow(), &ownerX, &ownerY);
  1.1401 +
  1.1402 +    // Compute the caret position in the IM owner window.
  1.1403 +    GdkRectangle area;
  1.1404 +    area.x = charRect.mReply.mRect.x + rootX - ownerX;
  1.1405 +    area.y = charRect.mReply.mRect.y + rootY - ownerY;
  1.1406 +    area.width  = 0;
  1.1407 +    area.height = charRect.mReply.mRect.height;
  1.1408 +
  1.1409 +    gtk_im_context_set_cursor_location(im, &area);
  1.1410 +}
  1.1411 +
  1.1412 +nsresult
  1.1413 +nsGtkIMModule::GetCurrentParagraph(nsAString& aText, uint32_t& aCursorPos)
  1.1414 +{
  1.1415 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1416 +        ("GtkIMModule(%p): GetCurrentParagraph, mCompositionState=%s",
  1.1417 +         this, GetCompositionStateName()));
  1.1418 +
  1.1419 +    if (!mLastFocusedWindow) {
  1.1420 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1421 +            ("    FAILED, there are no focused window in this module"));
  1.1422 +        return NS_ERROR_NULL_POINTER;
  1.1423 +    }
  1.1424 +
  1.1425 +    nsEventStatus status;
  1.1426 +
  1.1427 +    uint32_t selOffset = mCompositionStart;
  1.1428 +    uint32_t selLength = mSelectedString.Length();
  1.1429 +
  1.1430 +    // If focused editor doesn't have composition string, we should use
  1.1431 +    // current selection.
  1.1432 +    if (!EditorHasCompositionString()) {
  1.1433 +        // Query cursor position & selection
  1.1434 +        WidgetQueryContentEvent querySelectedTextEvent(true,
  1.1435 +                                                       NS_QUERY_SELECTED_TEXT,
  1.1436 +                                                       mLastFocusedWindow);
  1.1437 +        mLastFocusedWindow->DispatchEvent(&querySelectedTextEvent, status);
  1.1438 +        NS_ENSURE_TRUE(querySelectedTextEvent.mSucceeded, NS_ERROR_FAILURE);
  1.1439 +
  1.1440 +        selOffset = querySelectedTextEvent.mReply.mOffset;
  1.1441 +        selLength = querySelectedTextEvent.mReply.mString.Length();
  1.1442 +    }
  1.1443 +
  1.1444 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1445 +        ("        selOffset=%u, selLength=%u",
  1.1446 +         selOffset, selLength));
  1.1447 +
  1.1448 +    // XXX nsString::Find and nsString::RFind take int32_t for offset, so,
  1.1449 +    //     we cannot support this request when the current offset is larger
  1.1450 +    //     than INT32_MAX.
  1.1451 +    if (selOffset > INT32_MAX || selLength > INT32_MAX ||
  1.1452 +        selOffset + selLength > INT32_MAX) {
  1.1453 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1454 +            ("    FAILED, The selection is out of range"));
  1.1455 +        return NS_ERROR_FAILURE;
  1.1456 +    }
  1.1457 +
  1.1458 +    // Get all text contents of the focused editor
  1.1459 +    WidgetQueryContentEvent queryTextContentEvent(true,
  1.1460 +                                                  NS_QUERY_TEXT_CONTENT,
  1.1461 +                                                  mLastFocusedWindow);
  1.1462 +    queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
  1.1463 +    mLastFocusedWindow->DispatchEvent(&queryTextContentEvent, status);
  1.1464 +    NS_ENSURE_TRUE(queryTextContentEvent.mSucceeded, NS_ERROR_FAILURE);
  1.1465 +
  1.1466 +    nsAutoString textContent(queryTextContentEvent.mReply.mString);
  1.1467 +    if (selOffset + selLength > textContent.Length()) {
  1.1468 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1469 +            ("    FAILED, The selection is invalid, textContent.Length()=%u",
  1.1470 +             textContent.Length()));
  1.1471 +        return NS_ERROR_FAILURE;
  1.1472 +    }
  1.1473 +
  1.1474 +    // Remove composing string and restore the selected string because
  1.1475 +    // GtkEntry doesn't remove selected string until committing, however,
  1.1476 +    // our editor does it.  We should emulate the behavior for IME.
  1.1477 +    if (EditorHasCompositionString() &&
  1.1478 +        mDispatchedCompositionString != mSelectedString) {
  1.1479 +        textContent.Replace(mCompositionStart,
  1.1480 +            mDispatchedCompositionString.Length(), mSelectedString);
  1.1481 +    }
  1.1482 +
  1.1483 +    // Get only the focused paragraph, by looking for newlines
  1.1484 +    int32_t parStart = (selOffset == 0) ? 0 :
  1.1485 +        textContent.RFind("\n", false, selOffset - 1, -1) + 1;
  1.1486 +    int32_t parEnd = textContent.Find("\n", false, selOffset + selLength, -1);
  1.1487 +    if (parEnd < 0) {
  1.1488 +        parEnd = textContent.Length();
  1.1489 +    }
  1.1490 +    aText = nsDependentSubstring(textContent, parStart, parEnd - parStart);
  1.1491 +    aCursorPos = selOffset - uint32_t(parStart);
  1.1492 +
  1.1493 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1494 +        ("    aText=%s, aText.Length()=%u, aCursorPos=%u",
  1.1495 +         NS_ConvertUTF16toUTF8(aText).get(),
  1.1496 +         aText.Length(), aCursorPos));
  1.1497 +
  1.1498 +    return NS_OK;
  1.1499 +}
  1.1500 +
  1.1501 +nsresult
  1.1502 +nsGtkIMModule::DeleteText(const int32_t aOffset, const uint32_t aNChars)
  1.1503 +{
  1.1504 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1505 +        ("GtkIMModule(%p): DeleteText, aOffset=%d, aNChars=%d, "
  1.1506 +         "mCompositionState=%s",
  1.1507 +         this, aOffset, aNChars, GetCompositionStateName()));
  1.1508 +
  1.1509 +    if (!mLastFocusedWindow) {
  1.1510 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1511 +            ("    FAILED, there are no focused window in this module"));
  1.1512 +        return NS_ERROR_NULL_POINTER;
  1.1513 +    }
  1.1514 +
  1.1515 +    if (!aNChars) {
  1.1516 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1517 +            ("    FAILED, aNChars must not be zero"));
  1.1518 +        return NS_ERROR_INVALID_ARG;
  1.1519 +    }
  1.1520 +
  1.1521 +    nsRefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
  1.1522 +    nsEventStatus status;
  1.1523 +
  1.1524 +    // First, we should cancel current composition because editor cannot
  1.1525 +    // handle changing selection and deleting text.
  1.1526 +    uint32_t selOffset;
  1.1527 +    bool wasComposing = IsComposing();
  1.1528 +    bool editorHadCompositionString = EditorHasCompositionString();
  1.1529 +    if (wasComposing) {
  1.1530 +        selOffset = mCompositionStart;
  1.1531 +        if (editorHadCompositionString &&
  1.1532 +            !DispatchTextEvent(mSelectedString, false)) {
  1.1533 +            PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1534 +                ("    FAILED, quitting from DeletText"));
  1.1535 +            return NS_ERROR_FAILURE;
  1.1536 +        }
  1.1537 +        if (!DispatchCompositionEnd()) {
  1.1538 +            PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1539 +                ("    FAILED, quitting from DeletText"));
  1.1540 +            return NS_ERROR_FAILURE;
  1.1541 +        }
  1.1542 +    } else {
  1.1543 +        // Query cursor position & selection
  1.1544 +        WidgetQueryContentEvent querySelectedTextEvent(true,
  1.1545 +                                                       NS_QUERY_SELECTED_TEXT,
  1.1546 +                                                       mLastFocusedWindow);
  1.1547 +        lastFocusedWindow->DispatchEvent(&querySelectedTextEvent, status);
  1.1548 +        NS_ENSURE_TRUE(querySelectedTextEvent.mSucceeded, NS_ERROR_FAILURE);
  1.1549 +
  1.1550 +        selOffset = querySelectedTextEvent.mReply.mOffset;
  1.1551 +    }
  1.1552 +
  1.1553 +    // Get all text contents of the focused editor
  1.1554 +    WidgetQueryContentEvent queryTextContentEvent(true,
  1.1555 +                                                  NS_QUERY_TEXT_CONTENT,
  1.1556 +                                                  mLastFocusedWindow);
  1.1557 +    queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
  1.1558 +    mLastFocusedWindow->DispatchEvent(&queryTextContentEvent, status);
  1.1559 +    NS_ENSURE_TRUE(queryTextContentEvent.mSucceeded, NS_ERROR_FAILURE);
  1.1560 +    if (queryTextContentEvent.mReply.mString.IsEmpty()) {
  1.1561 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1562 +            ("    FAILED, there is no contents"));
  1.1563 +        return NS_ERROR_FAILURE;
  1.1564 +    }
  1.1565 +
  1.1566 +    NS_ConvertUTF16toUTF8 utf8Str(
  1.1567 +        nsDependentSubstring(queryTextContentEvent.mReply.mString,
  1.1568 +                             0, selOffset));
  1.1569 +    glong offsetInUTF8Characters =
  1.1570 +        g_utf8_strlen(utf8Str.get(), utf8Str.Length()) + aOffset;
  1.1571 +    if (offsetInUTF8Characters < 0) {
  1.1572 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1573 +            ("    FAILED, aOffset is too small for current cursor pos "
  1.1574 +             "(computed offset: %d)",
  1.1575 +             offsetInUTF8Characters));
  1.1576 +        return NS_ERROR_FAILURE;
  1.1577 +    }
  1.1578 +
  1.1579 +    AppendUTF16toUTF8(
  1.1580 +        nsDependentSubstring(queryTextContentEvent.mReply.mString, selOffset),
  1.1581 +        utf8Str);
  1.1582 +    glong countOfCharactersInUTF8 =
  1.1583 +        g_utf8_strlen(utf8Str.get(), utf8Str.Length());
  1.1584 +    glong endInUTF8Characters =
  1.1585 +        offsetInUTF8Characters + aNChars;
  1.1586 +    if (countOfCharactersInUTF8 < endInUTF8Characters) {
  1.1587 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1588 +            ("    FAILED, aNChars is too large for current contents "
  1.1589 +             "(content length: %d, computed end offset: %d)",
  1.1590 +             countOfCharactersInUTF8, endInUTF8Characters));
  1.1591 +        return NS_ERROR_FAILURE;
  1.1592 +    }
  1.1593 +
  1.1594 +    gchar* charAtOffset =
  1.1595 +        g_utf8_offset_to_pointer(utf8Str.get(), offsetInUTF8Characters);
  1.1596 +    gchar* charAtEnd =
  1.1597 +        g_utf8_offset_to_pointer(utf8Str.get(), endInUTF8Characters);
  1.1598 +
  1.1599 +    // Set selection to delete
  1.1600 +    WidgetSelectionEvent selectionEvent(true, NS_SELECTION_SET,
  1.1601 +                                        mLastFocusedWindow);
  1.1602 +
  1.1603 +    nsDependentCSubstring utf8StrBeforeOffset(utf8Str, 0,
  1.1604 +                                              charAtOffset - utf8Str.get());
  1.1605 +    selectionEvent.mOffset =
  1.1606 +        NS_ConvertUTF8toUTF16(utf8StrBeforeOffset).Length();
  1.1607 +
  1.1608 +    nsDependentCSubstring utf8DeletingStr(utf8Str,
  1.1609 +                                          utf8StrBeforeOffset.Length(),
  1.1610 +                                          charAtEnd - charAtOffset);
  1.1611 +    selectionEvent.mLength =
  1.1612 +        NS_ConvertUTF8toUTF16(utf8DeletingStr).Length();
  1.1613 +
  1.1614 +    selectionEvent.mReversed = false;
  1.1615 +    selectionEvent.mExpandToClusterBoundary = false;
  1.1616 +    lastFocusedWindow->DispatchEvent(&selectionEvent, status);
  1.1617 +
  1.1618 +    if (!selectionEvent.mSucceeded ||
  1.1619 +        lastFocusedWindow != mLastFocusedWindow ||
  1.1620 +        lastFocusedWindow->Destroyed()) {
  1.1621 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1622 +            ("    FAILED, setting selection caused focus change "
  1.1623 +             "or window destroyed"));
  1.1624 +        return NS_ERROR_FAILURE;
  1.1625 +    }
  1.1626 +
  1.1627 +    // Delete the selection
  1.1628 +    WidgetContentCommandEvent contentCommandEvent(true,
  1.1629 +                                                  NS_CONTENT_COMMAND_DELETE,
  1.1630 +                                                  mLastFocusedWindow);
  1.1631 +    mLastFocusedWindow->DispatchEvent(&contentCommandEvent, status);
  1.1632 +
  1.1633 +    if (!contentCommandEvent.mSucceeded ||
  1.1634 +        lastFocusedWindow != mLastFocusedWindow ||
  1.1635 +        lastFocusedWindow->Destroyed()) {
  1.1636 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1637 +            ("    FAILED, deleting the selection caused focus change "
  1.1638 +             "or window destroyed"));
  1.1639 +        return NS_ERROR_FAILURE;
  1.1640 +    }
  1.1641 +
  1.1642 +    if (!wasComposing) {
  1.1643 +        return NS_OK;
  1.1644 +    }
  1.1645 +
  1.1646 +    // Restore the composition at new caret position.
  1.1647 +    if (!DispatchCompositionStart()) {
  1.1648 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1649 +            ("    FAILED, resterting composition start"));
  1.1650 +        return NS_ERROR_FAILURE;
  1.1651 +    }
  1.1652 +
  1.1653 +    if (!editorHadCompositionString) {
  1.1654 +        return NS_OK;
  1.1655 +    }
  1.1656 +
  1.1657 +    nsAutoString compositionString;
  1.1658 +    GetCompositionString(compositionString);
  1.1659 +    if (!DispatchTextEvent(compositionString, true)) {
  1.1660 +        PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1661 +            ("    FAILED, restoring composition string"));
  1.1662 +        return NS_ERROR_FAILURE;
  1.1663 +    }
  1.1664 +
  1.1665 +    return NS_OK;
  1.1666 +}
  1.1667 +
  1.1668 +void
  1.1669 +nsGtkIMModule::InitEvent(WidgetGUIEvent& aEvent)
  1.1670 +{
  1.1671 +    aEvent.time = PR_Now() / 1000;
  1.1672 +}
  1.1673 +
  1.1674 +bool
  1.1675 +nsGtkIMModule::ShouldIgnoreNativeCompositionEvent()
  1.1676 +{
  1.1677 +    PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
  1.1678 +        ("GtkIMModule(%p): ShouldIgnoreNativeCompositionEvent, mLastFocusedWindow=%p, mIgnoreNativeCompositionEvent=%s",
  1.1679 +         this, mLastFocusedWindow,
  1.1680 +         mIgnoreNativeCompositionEvent ? "YES" : "NO"));
  1.1681 +
  1.1682 +    if (!mLastFocusedWindow) {
  1.1683 +        return true; // cannot continue
  1.1684 +    }
  1.1685 +
  1.1686 +    return mIgnoreNativeCompositionEvent;
  1.1687 +}

mercurial