Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/widget/gtk/IMContextWrapper.cpp
Line
Count
Source (jump to first uncovered line)
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/. */
6
7
#include "mozilla/Logging.h"
8
#include "prtime.h"
9
10
#include "IMContextWrapper.h"
11
#include "nsGtkKeyUtils.h"
12
#include "nsWindow.h"
13
#include "mozilla/AutoRestore.h"
14
#include "mozilla/Likely.h"
15
#include "mozilla/MiscEvents.h"
16
#include "mozilla/Preferences.h"
17
#include "mozilla/Telemetry.h"
18
#include "mozilla/TextEventDispatcher.h"
19
#include "mozilla/TextEvents.h"
20
#include "WritingModes.h"
21
22
namespace mozilla {
23
namespace widget {
24
25
LazyLogModule gGtkIMLog("nsGtkIMModuleWidgets");
26
27
static inline const char*
28
ToChar(bool aBool)
29
0
{
30
0
    return aBool ? "true" : "false";
31
0
}
32
33
static const char*
34
GetEnabledStateName(uint32_t aState)
35
{
36
    switch (aState) {
37
        case IMEState::DISABLED:
38
            return "DISABLED";
39
        case IMEState::ENABLED:
40
            return "ENABLED";
41
        case IMEState::PASSWORD:
42
            return "PASSWORD";
43
        case IMEState::PLUGIN:
44
            return "PLUG_IN";
45
        default:
46
            return "UNKNOWN ENABLED STATUS!!";
47
    }
48
}
49
50
static const char*
51
GetEventType(GdkEventKey* aKeyEvent)
52
{
53
    switch (aKeyEvent->type) {
54
        case GDK_KEY_PRESS:
55
            return "GDK_KEY_PRESS";
56
        case GDK_KEY_RELEASE:
57
            return "GDK_KEY_RELEASE";
58
        default:
59
            return "Unknown";
60
    }
61
}
62
63
class GetEventStateName : public nsAutoCString
64
{
65
public:
66
    explicit GetEventStateName(guint aState,
67
                               IMContextWrapper::IMContextID aIMContextID =
68
                                   IMContextWrapper::IMContextID::eUnknown)
69
0
    {
70
0
        if (aState & GDK_SHIFT_MASK) {
71
0
            AppendModifier("shift");
72
0
        }
73
0
        if (aState & GDK_CONTROL_MASK) {
74
0
            AppendModifier("control");
75
0
        }
76
0
        if (aState & GDK_MOD1_MASK) {
77
0
            AppendModifier("mod1");
78
0
        }
79
0
        if (aState & GDK_MOD2_MASK) {
80
0
            AppendModifier("mod2");
81
0
        }
82
0
        if (aState & GDK_MOD3_MASK) {
83
0
            AppendModifier("mod3");
84
0
        }
85
0
        if (aState & GDK_MOD4_MASK) {
86
0
            AppendModifier("mod4");
87
0
        }
88
0
        if (aState & GDK_MOD4_MASK) {
89
0
            AppendModifier("mod5");
90
0
        }
91
0
        if (aState & GDK_MOD4_MASK) {
92
0
            AppendModifier("mod5");
93
0
        }
94
0
        switch (aIMContextID) {
95
0
            case IMContextWrapper::IMContextID::eIBus:
96
0
                static const guint IBUS_HANDLED_MASK = 1 << 24;
97
0
                static const guint IBUS_IGNORED_MASK = 1 << 25;
98
0
                if (aState & IBUS_HANDLED_MASK) {
99
0
                    AppendModifier("IBUS_HANDLED_MASK");
100
0
                }
101
0
                if (aState & IBUS_IGNORED_MASK) {
102
0
                    AppendModifier("IBUS_IGNORED_MASK");
103
0
                }
104
0
                break;
105
0
            case IMContextWrapper::IMContextID::eFcitx:
106
0
                static const guint FcitxKeyState_HandledMask = 1 << 24;
107
0
                static const guint FcitxKeyState_IgnoredMask = 1 << 25;
108
0
                if (aState & FcitxKeyState_HandledMask) {
109
0
                    AppendModifier("FcitxKeyState_HandledMask");
110
0
                }
111
0
                if (aState & FcitxKeyState_IgnoredMask) {
112
0
                    AppendModifier("FcitxKeyState_IgnoredMask");
113
0
                }
114
0
                break;
115
0
            default:
116
0
                break;
117
0
        }
118
0
    }
119
120
private:
121
    void AppendModifier(const char* aModifierName)
122
0
    {
123
0
        if (!IsEmpty()) {
124
0
            AppendLiteral(" + ");
125
0
        }
126
0
        Append(aModifierName);
127
0
    }
128
};
129
130
class GetWritingModeName : public nsAutoCString
131
{
132
public:
133
  explicit GetWritingModeName(const WritingMode& aWritingMode)
134
0
  {
135
0
    if (!aWritingMode.IsVertical()) {
136
0
      AssignLiteral("Horizontal");
137
0
      return;
138
0
    }
139
0
    if (aWritingMode.IsVerticalLR()) {
140
0
      AssignLiteral("Vertical (LTR)");
141
0
      return;
142
0
    }
143
0
    AssignLiteral("Vertical (RTL)");
144
0
  }
145
0
  virtual ~GetWritingModeName() {}
146
};
147
148
class GetTextRangeStyleText final : public nsAutoCString
149
{
150
public:
151
    explicit GetTextRangeStyleText(const TextRangeStyle& aStyle)
152
0
    {
153
0
        if (!aStyle.IsDefined()) {
154
0
            AssignLiteral("{ IsDefined()=false }");
155
0
            return;
156
0
        }
157
0
158
0
        if (aStyle.IsLineStyleDefined()) {
159
0
            AppendLiteral("{ mLineStyle=");
160
0
            AppendLineStyle(aStyle.mLineStyle);
161
0
            if (aStyle.IsUnderlineColorDefined()) {
162
0
                AppendLiteral(", mUnderlineColor=");
163
0
                AppendColor(aStyle.mUnderlineColor);
164
0
            } else {
165
0
                AppendLiteral(", IsUnderlineColorDefined=false");
166
0
            }
167
0
        } else {
168
0
            AppendLiteral("{ IsLineStyleDefined()=false");
169
0
        }
170
0
171
0
        if (aStyle.IsForegroundColorDefined()) {
172
0
            AppendLiteral(", mForegroundColor=");
173
0
            AppendColor(aStyle.mForegroundColor);
174
0
        } else {
175
0
            AppendLiteral(", IsForegroundColorDefined()=false");
176
0
        }
177
0
178
0
        if (aStyle.IsBackgroundColorDefined()) {
179
0
            AppendLiteral(", mBackgroundColor=");
180
0
            AppendColor(aStyle.mBackgroundColor);
181
0
        } else {
182
0
            AppendLiteral(", IsBackgroundColorDefined()=false");
183
0
        }
184
0
185
0
        AppendLiteral(" }");
186
0
    }
187
    void AppendLineStyle(uint8_t aLineStyle)
188
    {
189
        switch (aLineStyle) {
190
            case TextRangeStyle::LINESTYLE_NONE:
191
                AppendLiteral("LINESTYLE_NONE");
192
                break;
193
            case TextRangeStyle::LINESTYLE_SOLID:
194
                AppendLiteral("LINESTYLE_SOLID");
195
                break;
196
            case TextRangeStyle::LINESTYLE_DOTTED:
197
                AppendLiteral("LINESTYLE_DOTTED");
198
                break;
199
            case TextRangeStyle::LINESTYLE_DASHED:
200
                AppendLiteral("LINESTYLE_DASHED");
201
                break;
202
            case TextRangeStyle::LINESTYLE_DOUBLE:
203
                AppendLiteral("LINESTYLE_DOUBLE");
204
                break;
205
            case TextRangeStyle::LINESTYLE_WAVY:
206
                AppendLiteral("LINESTYLE_WAVY");
207
                break;
208
            default:
209
                AppendPrintf("Invalid(0x%02X)", aLineStyle);
210
                break;
211
        }
212
    }
213
    void AppendColor(nscolor aColor)
214
0
    {
215
0
        AppendPrintf("{ R=0x%02X, G=0x%02X, B=0x%02X, A=0x%02X }",
216
0
                     NS_GET_R(aColor), NS_GET_G(aColor), NS_GET_B(aColor),
217
0
                     NS_GET_A(aColor));
218
0
    }
219
0
    virtual ~GetTextRangeStyleText() {};
220
};
221
222
const static bool kUseSimpleContextDefault = false;
223
224
/******************************************************************************
225
 * SelectionStyleProvider
226
 *
227
 * IME (e.g., fcitx, ~4.2.8.3) may look up selection colors of widget, which
228
 * is related to the window associated with the IM context, to support any
229
 * colored widgets.  Our editor (like <input type="text">) is rendered as
230
 * native GtkTextView as far as possible by default and if editor color is
231
 * changed by web apps, nsTextFrame may swap background color of foreground
232
 * color of composition string for making composition string is always
233
 * visually distinct in normal text.
234
 *
235
 * So, we would like IME to set style of composition string to good colors
236
 * in GtkTextView.  Therefore, this class overwrites selection colors of
237
 * our widget with selection colors of GtkTextView so that it's possible IME
238
 * to refer selection colors of GtkTextView via our widget.
239
 ******************************************************************************/
240
241
class SelectionStyleProvider final
242
{
243
public:
244
    static SelectionStyleProvider* GetInstance()
245
0
    {
246
0
        if (sHasShutDown) {
247
0
            return nullptr;
248
0
        }
249
0
        if (!sInstance) {
250
0
            sInstance = new SelectionStyleProvider();
251
0
        }
252
0
        return sInstance;
253
0
    }
254
255
    static void Shutdown()
256
0
    {
257
0
      if (sInstance) {
258
0
          g_object_unref(sInstance->mProvider);
259
0
      }
260
0
      delete sInstance;
261
0
      sInstance = nullptr;
262
0
      sHasShutDown = true;
263
0
    }
264
265
    // aGDKWindow is a GTK window which will be associated with an IM context.
266
    void AttachTo(GdkWindow* aGDKWindow)
267
0
    {
268
0
        GtkWidget* widget = nullptr;
269
0
        // gdk_window_get_user_data() typically returns pointer to widget that
270
0
        // window belongs to.  If it's widget, fcitx retrieves selection colors
271
0
        // of them.  So, we need to overwrite its style.
272
0
        gdk_window_get_user_data(aGDKWindow, (gpointer*)&widget);
273
0
        if (GTK_IS_WIDGET(widget)) {
274
0
            gtk_style_context_add_provider(
275
0
                gtk_widget_get_style_context(widget),
276
0
                GTK_STYLE_PROVIDER(mProvider),
277
0
                GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
278
0
        }
279
0
    }
280
281
    void OnThemeChanged()
282
0
    {
283
0
        // fcitx refers GtkStyle::text[GTK_STATE_SELECTED] and
284
0
        // GtkStyle::bg[GTK_STATE_SELECTED] (although pair of text and *base*
285
0
        // or *fg* and bg is correct).  gtk_style_update_from_context() will
286
0
        // set these colors using the widget's GtkStyleContext and so the
287
0
        // colors can be controlled by a ":selected" CSS rule.
288
0
        nsAutoCString style(":selected{");
289
0
        // FYI: LookAndFeel always returns selection colors of GtkTextView.
290
0
        nscolor selectionForegroundColor;
291
0
        if (NS_SUCCEEDED(LookAndFeel::GetColor(
292
0
                             LookAndFeel::eColorID_TextSelectForeground,
293
0
                             &selectionForegroundColor))) {
294
0
            double alpha =
295
0
              static_cast<double>(NS_GET_A(selectionForegroundColor)) / 0xFF;
296
0
            style.AppendPrintf("color:rgba(%u,%u,%u,%f);",
297
0
                               NS_GET_R(selectionForegroundColor),
298
0
                               NS_GET_G(selectionForegroundColor),
299
0
                               NS_GET_B(selectionForegroundColor), alpha);
300
0
        }
301
0
        nscolor selectionBackgroundColor;
302
0
        if (NS_SUCCEEDED(LookAndFeel::GetColor(
303
0
                             LookAndFeel::eColorID_TextSelectBackground,
304
0
                             &selectionBackgroundColor))) {
305
0
            double alpha =
306
0
              static_cast<double>(NS_GET_A(selectionBackgroundColor)) / 0xFF;
307
0
            style.AppendPrintf("background-color:rgba(%u,%u,%u,%f);",
308
0
                               NS_GET_R(selectionBackgroundColor),
309
0
                               NS_GET_G(selectionBackgroundColor),
310
0
                               NS_GET_B(selectionBackgroundColor), alpha);
311
0
        }
312
0
        style.AppendLiteral("}");
313
0
        gtk_css_provider_load_from_data(mProvider, style.get(), -1, nullptr);
314
0
    }
315
316
private:
317
    static SelectionStyleProvider* sInstance;
318
    static bool sHasShutDown;
319
    GtkCssProvider* const mProvider;
320
321
    SelectionStyleProvider()
322
      : mProvider(gtk_css_provider_new())
323
0
    {
324
0
        OnThemeChanged();
325
0
    }
326
};
327
328
SelectionStyleProvider* SelectionStyleProvider::sInstance = nullptr;
329
bool SelectionStyleProvider::sHasShutDown = false;
330
331
/******************************************************************************
332
 * IMContextWrapper
333
 ******************************************************************************/
334
335
IMContextWrapper* IMContextWrapper::sLastFocusedContext = nullptr;
336
bool IMContextWrapper::sUseSimpleContext;
337
338
NS_IMPL_ISUPPORTS(IMContextWrapper,
339
                  TextEventDispatcherListener,
340
                  nsISupportsWeakReference)
341
342
IMContextWrapper::IMContextWrapper(nsWindow* aOwnerWindow)
343
    : mOwnerWindow(aOwnerWindow)
344
    , mLastFocusedWindow(nullptr)
345
    , mContext(nullptr)
346
    , mSimpleContext(nullptr)
347
    , mDummyContext(nullptr)
348
    , mComposingContext(nullptr)
349
    , mCompositionStart(UINT32_MAX)
350
    , mProcessingKeyEvent(nullptr)
351
    , mCompositionState(eCompositionState_NotComposing)
352
    , mIMContextID(IMContextID::eUnknown)
353
    , mIsIMFocused(false)
354
    , mFallbackToKeyEvent(false)
355
    , mKeyboardEventWasDispatched(false)
356
    , mIsDeletingSurrounding(false)
357
    , mLayoutChanged(false)
358
    , mSetCursorPositionOnKeyEvent(true)
359
    , mPendingResettingIMContext(false)
360
    , mRetrieveSurroundingSignalReceived(false)
361
    , mMaybeInDeadKeySequence(false)
362
    , mIsIMInAsyncKeyHandlingMode(false)
363
0
{
364
0
    static bool sFirstInstance = true;
365
0
    if (sFirstInstance) {
366
0
        sFirstInstance = false;
367
0
        sUseSimpleContext =
368
0
            Preferences::GetBool(
369
0
                "intl.ime.use_simple_context_on_password_field",
370
0
                kUseSimpleContextDefault);
371
0
    }
372
0
    Init();
373
0
}
374
375
static bool
376
IsIBusInSyncMode()
377
0
{
378
0
    // See ibus_im_context_class_init() in client/gtk2/ibusimcontext.c
379
0
    // https://github.com/ibus/ibus/blob/86963f2f94d1e4fc213b01c2bc2ba9dcf4b22219/client/gtk2/ibusimcontext.c#L610
380
0
    const char* env = PR_GetEnv("IBUS_ENABLE_SYNC_MODE");
381
0
382
0
    // See _get_boolean_env() in client/gtk2/ibusimcontext.c
383
0
    // https://github.com/ibus/ibus/blob/86963f2f94d1e4fc213b01c2bc2ba9dcf4b22219/client/gtk2/ibusimcontext.c#L520-L537
384
0
    if (!env) {
385
0
        return false;
386
0
    }
387
0
    nsDependentCString envStr(env);
388
0
    if (envStr.IsEmpty() ||
389
0
        envStr.EqualsLiteral("0") ||
390
0
        envStr.EqualsLiteral("false") ||
391
0
        envStr.EqualsLiteral("False") ||
392
0
        envStr.EqualsLiteral("FALSE")) {
393
0
        return false;
394
0
    }
395
0
    return true;
396
0
}
397
398
static bool
399
GetFcitxBoolEnv(const char* aEnv)
400
0
{
401
0
    // See fcitx_utils_get_boolean_env in src/lib/fcitx-utils/utils.c
402
0
    // https://github.com/fcitx/fcitx/blob/0c87840dc7d9460c2cb5feaeefec299d0d3d62ec/src/lib/fcitx-utils/utils.c#L721-L736
403
0
    const char* env = PR_GetEnv(aEnv);
404
0
    if (!env) {
405
0
        return false;
406
0
    }
407
0
    nsDependentCString envStr(env);
408
0
    if (envStr.IsEmpty() ||
409
0
        envStr.EqualsLiteral("0") ||
410
0
        envStr.EqualsLiteral("false")) {
411
0
        return false;
412
0
    }
413
0
    return true;
414
0
}
415
416
static bool
417
IsFcitxInSyncMode()
418
0
{
419
0
    // See fcitx_im_context_class_init() in src/frontend/gtk2/fcitximcontext.c
420
0
    // https://github.com/fcitx/fcitx/blob/78b98d9230dc9630e99d52e3172bdf440ffd08c4/src/frontend/gtk2/fcitximcontext.c#L395-L398
421
0
    return GetFcitxBoolEnv("IBUS_ENABLE_SYNC_MODE") ||
422
0
           GetFcitxBoolEnv("FCITX_ENABLE_SYNC_MODE");
423
0
}
424
425
nsDependentCSubstring
426
IMContextWrapper::GetIMName() const
427
0
{
428
0
    const char* contextIDChar =
429
0
        gtk_im_multicontext_get_context_id(GTK_IM_MULTICONTEXT(mContext));
430
0
    if (!contextIDChar) {
431
0
        return nsDependentCSubstring();
432
0
    }
433
0
434
0
    nsDependentCSubstring im(contextIDChar, strlen(contextIDChar));
435
0
436
0
    // If the context is XIM, actual engine must be specified with
437
0
    // |XMODIFIERS=@im=foo|.
438
0
    const char* xmodifiersChar = PR_GetEnv("XMODIFIERS");
439
0
    if (!im.EqualsLiteral("xim") || !xmodifiersChar) {
440
0
        return im;
441
0
    }
442
0
443
0
    nsDependentCString xmodifiers(xmodifiersChar);
444
0
    int32_t atIMValueStart = xmodifiers.Find("@im=") + 4;
445
0
    if (atIMValueStart < 4 ||
446
0
        xmodifiers.Length() <= static_cast<size_t>(atIMValueStart)) {
447
0
        return im;
448
0
    }
449
0
450
0
    int32_t atIMValueEnd =
451
0
        xmodifiers.Find("@", false, atIMValueStart);
452
0
    if (atIMValueEnd > atIMValueStart) {
453
0
         return nsDependentCSubstring(xmodifiersChar + atIMValueStart,
454
0
                                      atIMValueEnd - atIMValueStart);
455
0
    }
456
0
457
0
    if (atIMValueEnd == kNotFound) {
458
0
        return nsDependentCSubstring(xmodifiersChar + atIMValueStart,
459
0
                                     strlen(xmodifiersChar) - atIMValueStart);
460
0
    }
461
0
462
0
    return im;
463
0
}
464
465
void
466
IMContextWrapper::Init()
467
0
{
468
0
    MozContainer* container = mOwnerWindow->GetMozContainer();
469
0
    MOZ_ASSERT(container, "container is null");
470
0
    GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(container));
471
0
472
0
    // Overwrite selection colors of the window before associating the window
473
0
    // with IM context since IME may look up selection colors via IM context
474
0
    // to support any colored widgets.
475
0
    SelectionStyleProvider::GetInstance()->AttachTo(gdkWindow);
476
0
477
0
    // NOTE: gtk_im_*_new() abort (kill) the whole process when it fails.
478
0
    //       So, we don't need to check the result.
479
0
480
0
    // Normal context.
481
0
    mContext = gtk_im_multicontext_new();
482
0
    gtk_im_context_set_client_window(mContext, gdkWindow);
483
0
    g_signal_connect(mContext, "preedit_changed",
484
0
        G_CALLBACK(IMContextWrapper::OnChangeCompositionCallback), this);
485
0
    g_signal_connect(mContext, "retrieve_surrounding",
486
0
        G_CALLBACK(IMContextWrapper::OnRetrieveSurroundingCallback), this);
487
0
    g_signal_connect(mContext, "delete_surrounding",
488
0
        G_CALLBACK(IMContextWrapper::OnDeleteSurroundingCallback), this);
489
0
    g_signal_connect(mContext, "commit",
490
0
        G_CALLBACK(IMContextWrapper::OnCommitCompositionCallback), this);
491
0
    g_signal_connect(mContext, "preedit_start",
492
0
        G_CALLBACK(IMContextWrapper::OnStartCompositionCallback), this);
493
0
    g_signal_connect(mContext, "preedit_end",
494
0
        G_CALLBACK(IMContextWrapper::OnEndCompositionCallback), this);
495
0
    nsDependentCSubstring im = GetIMName();
496
0
    if (im.EqualsLiteral("ibus")) {
497
0
        mIMContextID = IMContextID::eIBus;
498
0
        mIsIMInAsyncKeyHandlingMode = !IsIBusInSyncMode();
499
0
        // Although ibus has key snooper mode, it's forcibly disabled on Firefox
500
0
        // in default settings by its whitelist since we always send key events
501
0
        // to IME before handling shortcut keys.  The whitelist can be
502
0
        // customized with env, IBUS_NO_SNOOPER_APPS, but we don't need to
503
0
        // support such rare cases for reducing maintenance cost.
504
0
        mIsKeySnooped = false;
505
0
    } else if (im.EqualsLiteral("fcitx")) {
506
0
        mIMContextID = IMContextID::eFcitx;
507
0
        mIsIMInAsyncKeyHandlingMode = !IsFcitxInSyncMode();
508
0
        // Although Fcitx has key snooper mode similar to ibus, it's also
509
0
        // disabled on Firefox in default settings by its whitelist.  The
510
0
        // whitelist can be customized with env, IBUS_NO_SNOOPER_APPS or
511
0
        // FCITX_NO_SNOOPER_APPS, but we don't need to support such rare cases
512
0
        // for reducing maintenance cost.
513
0
        mIsKeySnooped = false;
514
0
    } else if (im.EqualsLiteral("uim")) {
515
0
        mIMContextID = IMContextID::eUim;
516
0
        mIsIMInAsyncKeyHandlingMode = false;
517
0
        // We cannot know if uim uses key snooper since it's build option of
518
0
        // uim.  Therefore, we need to retrieve the consideration from the
519
0
        // pref for making users and distributions allowed to choose their
520
0
        // preferred value.
521
0
        mIsKeySnooped =
522
0
            Preferences::GetBool("intl.ime.hack.uim.using_key_snooper", true);
523
0
    } else if (im.EqualsLiteral("scim")) {
524
0
        mIMContextID = IMContextID::eScim;
525
0
        mIsIMInAsyncKeyHandlingMode = false;
526
0
        mIsKeySnooped = false;
527
0
    } else if (im.EqualsLiteral("iiim")) {
528
0
        mIMContextID = IMContextID::eIIIMF;
529
0
        mIsIMInAsyncKeyHandlingMode = false;
530
0
        mIsKeySnooped = false;
531
0
    } else {
532
0
        mIMContextID = IMContextID::eUnknown;
533
0
        mIsIMInAsyncKeyHandlingMode = false;
534
0
        mIsKeySnooped = false;
535
0
    }
536
0
537
0
    // Simple context
538
0
    if (sUseSimpleContext) {
539
0
        mSimpleContext = gtk_im_context_simple_new();
540
0
        gtk_im_context_set_client_window(mSimpleContext, gdkWindow);
541
0
        g_signal_connect(mSimpleContext, "preedit_changed",
542
0
            G_CALLBACK(&IMContextWrapper::OnChangeCompositionCallback),
543
0
            this);
544
0
        g_signal_connect(mSimpleContext, "retrieve_surrounding",
545
0
            G_CALLBACK(&IMContextWrapper::OnRetrieveSurroundingCallback),
546
0
            this);
547
0
        g_signal_connect(mSimpleContext, "delete_surrounding",
548
0
            G_CALLBACK(&IMContextWrapper::OnDeleteSurroundingCallback),
549
0
            this);
550
0
        g_signal_connect(mSimpleContext, "commit",
551
0
            G_CALLBACK(&IMContextWrapper::OnCommitCompositionCallback),
552
0
            this);
553
0
        g_signal_connect(mSimpleContext, "preedit_start",
554
0
            G_CALLBACK(IMContextWrapper::OnStartCompositionCallback),
555
0
            this);
556
0
        g_signal_connect(mSimpleContext, "preedit_end",
557
0
            G_CALLBACK(IMContextWrapper::OnEndCompositionCallback),
558
0
            this);
559
0
    }
560
0
561
0
    // Dummy context
562
0
    mDummyContext = gtk_im_multicontext_new();
563
0
    gtk_im_context_set_client_window(mDummyContext, gdkWindow);
564
0
565
0
    MOZ_LOG(gGtkIMLog, LogLevel::Info,
566
0
        ("0x%p Init(), mOwnerWindow=%p, mContext=%p (im=\"%s\"), "
567
0
         "mIsIMInAsyncKeyHandlingMode=%s, mIsKeySnooped=%s, "
568
0
         "mSimpleContext=%p, mDummyContext=%p, "
569
0
         "gtk_im_multicontext_get_context_id()=\"%s\", "
570
0
         "PR_GetEnv(\"XMODIFIERS\")=\"%s\"",
571
0
         this, mOwnerWindow, mContext, nsAutoCString(im).get(),
572
0
         ToChar(mIsIMInAsyncKeyHandlingMode), ToChar(mIsKeySnooped),
573
0
         mSimpleContext, mDummyContext,
574
0
         gtk_im_multicontext_get_context_id(GTK_IM_MULTICONTEXT(mContext)),
575
0
         PR_GetEnv("XMODIFIERS")));
576
0
}
577
578
/* static */
579
void
580
IMContextWrapper::Shutdown()
581
0
{
582
0
    SelectionStyleProvider::Shutdown();
583
0
}
584
585
IMContextWrapper::~IMContextWrapper()
586
0
{
587
0
    if (this == sLastFocusedContext) {
588
0
        sLastFocusedContext = nullptr;
589
0
    }
590
0
    MOZ_LOG(gGtkIMLog, LogLevel::Info,
591
0
        ("0x%p ~IMContextWrapper()", this));
592
0
}
593
594
NS_IMETHODIMP
595
IMContextWrapper::NotifyIME(TextEventDispatcher* aTextEventDispatcher,
596
                            const IMENotification& aNotification)
597
{
598
    switch (aNotification.mMessage) {
599
        case REQUEST_TO_COMMIT_COMPOSITION:
600
        case REQUEST_TO_CANCEL_COMPOSITION: {
601
            nsWindow* window =
602
                static_cast<nsWindow*>(aTextEventDispatcher->GetWidget());
603
            return EndIMEComposition(window);
604
        }
605
        case NOTIFY_IME_OF_FOCUS:
606
            OnFocusChangeInGecko(true);
607
            return NS_OK;
608
        case NOTIFY_IME_OF_BLUR:
609
            OnFocusChangeInGecko(false);
610
            return NS_OK;
611
        case NOTIFY_IME_OF_POSITION_CHANGE:
612
            OnLayoutChange();
613
            return NS_OK;
614
        case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
615
            OnUpdateComposition();
616
            return NS_OK;
617
        case NOTIFY_IME_OF_SELECTION_CHANGE: {
618
            nsWindow* window =
619
                static_cast<nsWindow*>(aTextEventDispatcher->GetWidget());
620
            OnSelectionChange(window, aNotification);
621
            return NS_OK;
622
        }
623
        default:
624
            return NS_ERROR_NOT_IMPLEMENTED;
625
    }
626
}
627
628
NS_IMETHODIMP_(void)
629
IMContextWrapper::OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher)
630
0
{
631
0
    // XXX When input transaction is being stolen by add-on, what should we do?
632
0
}
633
634
NS_IMETHODIMP_(void)
635
IMContextWrapper::WillDispatchKeyboardEvent(
636
                      TextEventDispatcher* aTextEventDispatcher,
637
                      WidgetKeyboardEvent& aKeyboardEvent,
638
                      uint32_t aIndexOfKeypress,
639
                      void* aData)
640
0
{
641
0
    KeymapWrapper::WillDispatchKeyboardEvent(aKeyboardEvent,
642
0
                                             static_cast<GdkEventKey*>(aData));
643
0
}
644
645
TextEventDispatcher*
646
IMContextWrapper::GetTextEventDispatcher()
647
0
{
648
0
  if (NS_WARN_IF(!mLastFocusedWindow)) {
649
0
    return nullptr;
650
0
  }
651
0
  TextEventDispatcher* dispatcher =
652
0
    mLastFocusedWindow->GetTextEventDispatcher();
653
0
  // nsIWidget::GetTextEventDispatcher() shouldn't return nullptr.
654
0
  MOZ_RELEASE_ASSERT(dispatcher);
655
0
  return dispatcher;
656
0
}
657
658
NS_IMETHODIMP_(IMENotificationRequests)
659
IMContextWrapper::GetIMENotificationRequests()
660
0
{
661
0
    // While a plugin has focus, IMContextWrapper doesn't need any
662
0
    // notifications.
663
0
    if (mInputContext.mIMEState.mEnabled == IMEState::PLUGIN) {
664
0
      return IMENotificationRequests();
665
0
    }
666
0
667
0
    IMENotificationRequests::Notifications notifications =
668
0
        IMENotificationRequests::NOTIFY_NOTHING;
669
0
    // If it's not enabled, we don't need position change notification.
670
0
    if (IsEnabled()) {
671
0
        notifications |= IMENotificationRequests::NOTIFY_POSITION_CHANGE;
672
0
    }
673
0
    return IMENotificationRequests(notifications);
674
0
}
675
676
void
677
IMContextWrapper::OnDestroyWindow(nsWindow* aWindow)
678
0
{
679
0
    MOZ_LOG(gGtkIMLog, LogLevel::Info,
680
0
        ("0x%p OnDestroyWindow(aWindow=0x%p), mLastFocusedWindow=0x%p, "
681
0
         "mOwnerWindow=0x%p, mLastFocusedModule=0x%p",
682
0
         this, aWindow, mLastFocusedWindow, mOwnerWindow, sLastFocusedContext));
683
0
684
0
    MOZ_ASSERT(aWindow, "aWindow must not be null");
685
0
686
0
    if (mLastFocusedWindow == aWindow) {
687
0
        EndIMEComposition(aWindow);
688
0
        if (mIsIMFocused) {
689
0
            Blur();
690
0
        }
691
0
        mLastFocusedWindow = nullptr;
692
0
    }
693
0
694
0
    if (mOwnerWindow != aWindow) {
695
0
        return;
696
0
    }
697
0
698
0
    if (sLastFocusedContext == this) {
699
0
        sLastFocusedContext = nullptr;
700
0
    }
701
0
702
0
    /**
703
0
     * NOTE:
704
0
     *   The given window is the owner of this, so, we must release the
705
0
     *   contexts now.  But that might be referred from other nsWindows
706
0
     *   (they are children of this.  But we don't know why there are the
707
0
     *   cases).  So, we need to clear the pointers that refers to contexts
708
0
     *   and this if the other referrers are still alive. See bug 349727.
709
0
     */
710
0
    if (mContext) {
711
0
        PrepareToDestroyContext(mContext);
712
0
        gtk_im_context_set_client_window(mContext, nullptr);
713
0
        g_object_unref(mContext);
714
0
        mContext = nullptr;
715
0
    }
716
0
717
0
    if (mSimpleContext) {
718
0
        gtk_im_context_set_client_window(mSimpleContext, nullptr);
719
0
        g_object_unref(mSimpleContext);
720
0
        mSimpleContext = nullptr;
721
0
    }
722
0
723
0
    if (mDummyContext) {
724
0
        // mContext and mDummyContext have the same slaveType and signal_data
725
0
        // so no need for another workaround_gtk_im_display_closed.
726
0
        gtk_im_context_set_client_window(mDummyContext, nullptr);
727
0
        g_object_unref(mDummyContext);
728
0
        mDummyContext = nullptr;
729
0
    }
730
0
731
0
    if (NS_WARN_IF(mComposingContext)) {
732
0
        g_object_unref(mComposingContext);
733
0
        mComposingContext = nullptr;
734
0
    }
735
0
736
0
    mOwnerWindow = nullptr;
737
0
    mLastFocusedWindow = nullptr;
738
0
    mInputContext.mIMEState.mEnabled = IMEState::DISABLED;
739
0
    mPostingKeyEvents.Clear();
740
0
741
0
    MOZ_LOG(gGtkIMLog, LogLevel::Debug,
742
0
        ("0x%p   OnDestroyWindow(), succeeded, Completely destroyed",
743
0
         this));
744
0
}
745
746
void
747
IMContextWrapper::PrepareToDestroyContext(GtkIMContext* aContext)
748
0
{
749
0
    if (mIMContextID == IMContextID::eIIIMF) {
750
0
        // IIIM module registers handlers for the "closed" signal on the
751
0
        // display, but the signal handler is not disconnected when the module
752
0
        // is unloaded.  To prevent the module from being unloaded, use static
753
0
        // variable to hold reference of slave context class declared by IIIM.
754
0
        // Note that this does not grab any instance, it grabs the "class".
755
0
        static gpointer sGtkIIIMContextClass = nullptr;
756
0
        if (!sGtkIIIMContextClass) {
757
0
            // We retrieved slave context class with g_type_name() and actual
758
0
            // slave context instance when our widget was GTK2.  That must be
759
0
            // _GtkIMContext::priv::slave in GTK3.  However, _GtkIMContext::priv
760
0
            // is an opacity struct named _GtkIMMulticontextPrivate, i.e., it's
761
0
            // not exposed by GTK3.  Therefore, we cannot access the instance
762
0
            // safely.  So, we need to retrieve the slave context class with
763
0
            // g_type_from_name("GtkIMContextIIIM") directly (anyway, we needed
764
0
            // to compare the class name with "GtkIMContextIIIM").
765
0
            GType IIMContextType = g_type_from_name("GtkIMContextIIIM");
766
0
            if (IIMContextType) {
767
0
                sGtkIIIMContextClass = g_type_class_ref(IIMContextType);
768
0
                MOZ_LOG(gGtkIMLog, LogLevel::Info,
769
0
                    ("0x%p PrepareToDestroyContext(), added to reference to "
770
0
                     "GtkIMContextIIIM class to prevent it from being unloaded",
771
0
                     this));
772
0
            } else {
773
0
                MOZ_LOG(gGtkIMLog, LogLevel::Error,
774
0
                    ("0x%p PrepareToDestroyContext(), FAILED to prevent the "
775
0
                     "IIIM module from being uploaded",
776
0
                     this));
777
0
            }
778
0
        }
779
0
    }
780
0
}
781
782
void
783
IMContextWrapper::OnFocusWindow(nsWindow* aWindow)
784
0
{
785
0
    if (MOZ_UNLIKELY(IsDestroyed())) {
786
0
        return;
787
0
    }
788
0
789
0
    MOZ_LOG(gGtkIMLog, LogLevel::Info,
790
0
        ("0x%p OnFocusWindow(aWindow=0x%p), mLastFocusedWindow=0x%p",
791
0
         this, aWindow, mLastFocusedWindow));
792
0
    mLastFocusedWindow = aWindow;
793
0
    Focus();
794
0
}
795
796
void
797
IMContextWrapper::OnBlurWindow(nsWindow* aWindow)
798
0
{
799
0
    if (MOZ_UNLIKELY(IsDestroyed())) {
800
0
        return;
801
0
    }
802
0
803
0
    MOZ_LOG(gGtkIMLog, LogLevel::Info,
804
0
        ("0x%p OnBlurWindow(aWindow=0x%p), mLastFocusedWindow=0x%p, "
805
0
         "mIsIMFocused=%s",
806
0
         this, aWindow, mLastFocusedWindow, ToChar(mIsIMFocused)));
807
0
808
0
    if (!mIsIMFocused || mLastFocusedWindow != aWindow) {
809
0
        return;
810
0
    }
811
0
812
0
    Blur();
813
0
}
814
815
bool
816
IMContextWrapper::OnKeyEvent(nsWindow* aCaller,
817
                             GdkEventKey* aEvent,
818
                             bool aKeyboardEventWasDispatched /* = false */)
819
0
{
820
0
    MOZ_ASSERT(aEvent, "aEvent must be non-null");
821
0
822
0
    if (!mInputContext.mIMEState.MaybeEditable() ||
823
0
        MOZ_UNLIKELY(IsDestroyed())) {
824
0
        return false;
825
0
    }
826
0
827
0
    MOZ_LOG(gGtkIMLog, LogLevel::Info,
828
0
        ("0x%p OnKeyEvent(aCaller=0x%p, "
829
0
         "aEvent(0x%p): { type=%s, keyval=%s, unicode=0x%X, state=%s, "
830
0
         "time=%u, hardware_keycode=%u, group=%u }, "
831
0
         "aKeyboardEventWasDispatched=%s)",
832
0
         this, aCaller, aEvent, GetEventType(aEvent),
833
0
         gdk_keyval_name(aEvent->keyval),
834
0
         gdk_keyval_to_unicode(aEvent->keyval),
835
0
         GetEventStateName(aEvent->state, mIMContextID).get(),
836
0
         aEvent->time, aEvent->hardware_keycode, aEvent->group,
837
0
         ToChar(aKeyboardEventWasDispatched)));
838
0
    MOZ_LOG(gGtkIMLog, LogLevel::Info,
839
0
        ("0x%p   OnKeyEvent(), mMaybeInDeadKeySequence=%s, "
840
0
         "mCompositionState=%s, current context=%p, active context=%p, "
841
0
         "mIMContextID=%s, mIsIMInAsyncKeyHandlingMode=%s",
842
0
         this, ToChar(mMaybeInDeadKeySequence),
843
0
         GetCompositionStateName(), GetCurrentContext(), GetActiveContext(),
844
0
         GetIMContextIDName(mIMContextID),
845
0
         ToChar(mIsIMInAsyncKeyHandlingMode)));
846
0
847
0
    if (aCaller != mLastFocusedWindow) {
848
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
849
0
            ("0x%p   OnKeyEvent(), FAILED, the caller isn't focused "
850
0
             "window, mLastFocusedWindow=0x%p",
851
0
             this, mLastFocusedWindow));
852
0
        return false;
853
0
    }
854
0
855
0
    // Even if old IM context has composition, key event should be sent to
856
0
    // current context since the user expects so.
857
0
    GtkIMContext* currentContext = GetCurrentContext();
858
0
    if (MOZ_UNLIKELY(!currentContext)) {
859
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
860
0
            ("0x%p   OnKeyEvent(), FAILED, there are no context",
861
0
             this));
862
0
        return false;
863
0
    }
864
0
865
0
    if (mSetCursorPositionOnKeyEvent) {
866
0
        SetCursorPosition(currentContext);
867
0
        mSetCursorPositionOnKeyEvent = false;
868
0
    }
869
0
870
0
    // Let's support dead key event even if active keyboard layout also
871
0
    // supports complicated composition like CJK IME.
872
0
    bool isDeadKey =
873
0
        KeymapWrapper::ComputeDOMKeyNameIndex(aEvent) == KEY_NAME_INDEX_Dead;
874
0
    mMaybeInDeadKeySequence |= isDeadKey;
875
0
876
0
    // If current context is mSimpleContext, both ibus and fcitx handles key
877
0
    // events synchronously.  So, only when current context is mContext which
878
0
    // is GtkIMMulticontext, the key event may be handled by IME asynchronously.
879
0
    bool maybeHandledAsynchronously =
880
0
        mIsIMInAsyncKeyHandlingMode && currentContext == mContext;
881
0
882
0
    // If IM is ibus or fcitx and it handles key events asynchronously,
883
0
    // they mark aEvent->state as "handled by me" when they post key event
884
0
    // to another process.  Unfortunately, we need to check this hacky
885
0
    // flag because it's difficult to store all pending key events by
886
0
    // an array or a hashtable.
887
0
    if (maybeHandledAsynchronously) {
888
0
        switch (mIMContextID) {
889
0
            case IMContextID::eIBus:
890
0
                // ibus won't send back key press events in a dead key sequcne.
891
0
                if (mMaybeInDeadKeySequence && aEvent->type == GDK_KEY_PRESS) {
892
0
                    maybeHandledAsynchronously = false;
893
0
                    break;
894
0
                }
895
0
                // ibus handles key events synchronously if focused editor is
896
0
                // <input type="password"> or |ime-mode: disabled;|.
897
0
                if (mInputContext.mIMEState.mEnabled == IMEState::PASSWORD) {
898
0
                    maybeHandledAsynchronously = false;
899
0
                    break;
900
0
                }
901
0
                // See src/ibustypes.h
902
0
                static const guint IBUS_IGNORED_MASK = 1 << 25;
903
0
                // If IBUS_IGNORED_MASK was set to aEvent->state, the event
904
0
                // has already been handled by another process and it wasn't
905
0
                // used by IME.
906
0
                if (aEvent->state & IBUS_IGNORED_MASK) {
907
0
                    MOZ_LOG(gGtkIMLog, LogLevel::Info,
908
0
                        ("0x%p   OnKeyEvent(), aEvent->state has "
909
0
                         "IBUS_IGNORED_MASK, so, it won't be handled "
910
0
                         "asynchronously anymore. Removing posted events from "
911
0
                         "the queue",
912
0
                         this));
913
0
                    maybeHandledAsynchronously = false;
914
0
                    mPostingKeyEvents.RemoveEvent(aEvent);
915
0
                    break;
916
0
                }
917
0
                break;
918
0
            case IMContextID::eFcitx:
919
0
                // fcitx won't send back key press events in a dead key sequcne.
920
0
                if (mMaybeInDeadKeySequence && aEvent->type == GDK_KEY_PRESS) {
921
0
                    maybeHandledAsynchronously = false;
922
0
                    break;
923
0
                }
924
0
925
0
                // fcitx handles key events asynchronously even if focused
926
0
                // editor cannot use IME actually.
927
0
928
0
                // See src/lib/fcitx-utils/keysym.h
929
0
                static const guint FcitxKeyState_IgnoredMask = 1 << 25;
930
0
                // If FcitxKeyState_IgnoredMask was set to aEvent->state,
931
0
                // the event has already been handled by another process and
932
0
                // it wasn't used by IME.
933
0
                if (aEvent->state & FcitxKeyState_IgnoredMask) {
934
0
                    MOZ_LOG(gGtkIMLog, LogLevel::Info,
935
0
                        ("0x%p   OnKeyEvent(), aEvent->state has "
936
0
                         "FcitxKeyState_IgnoredMask, so, it won't be handled "
937
0
                         "asynchronously anymore. Removing posted events from "
938
0
                         "the queue",
939
0
                         this));
940
0
                    maybeHandledAsynchronously = false;
941
0
                    mPostingKeyEvents.RemoveEvent(aEvent);
942
0
                    break;
943
0
                }
944
0
                break;
945
0
            default:
946
0
                MOZ_ASSERT_UNREACHABLE("IME may handle key event "
947
0
                    "asyncrhonously, but not yet confirmed if it comes agian "
948
0
                    "actually");
949
0
        }
950
0
    }
951
0
952
0
    mKeyboardEventWasDispatched = aKeyboardEventWasDispatched;
953
0
    mFallbackToKeyEvent = false;
954
0
    mProcessingKeyEvent = aEvent;
955
0
    gboolean isFiltered =
956
0
        gtk_im_context_filter_keypress(currentContext, aEvent);
957
0
958
0
    // The caller of this shouldn't handle aEvent anymore if we've dispatched
959
0
    // composition events or modified content with other events.
960
0
    bool filterThisEvent = isFiltered && !mFallbackToKeyEvent;
961
0
962
0
    if (IsComposingOnCurrentContext() && !isFiltered &&
963
0
        aEvent->type == GDK_KEY_PRESS &&
964
0
        mDispatchedCompositionString.IsEmpty()) {
965
0
        // A Hangul input engine for SCIM doesn't emit preedit_end
966
0
        // signal even when composition string becomes empty.  On the
967
0
        // other hand, we should allow to make composition with empty
968
0
        // string for other languages because there *might* be such
969
0
        // IM.  For compromising this issue, we should dispatch
970
0
        // compositionend event, however, we don't need to reset IM
971
0
        // actually.
972
0
        // NOTE: Don't dispatch key events as "processed by IME" since
973
0
        // we need to dispatch keyboard events as IME wasn't handled it.
974
0
        mProcessingKeyEvent = nullptr;
975
0
        DispatchCompositionCommitEvent(currentContext, &EmptyString());
976
0
        mProcessingKeyEvent = aEvent;
977
0
        // In this case, even though we handle the keyboard event here,
978
0
        // but we should dispatch keydown event as
979
0
        filterThisEvent = false;
980
0
    }
981
0
982
0
    if (filterThisEvent && !mKeyboardEventWasDispatched) {
983
0
        // If IME handled the key event but we've not dispatched eKeyDown nor
984
0
        // eKeyUp event yet, we need to dispatch here unless the key event is
985
0
        // now being handled by other IME process.
986
0
        if (!maybeHandledAsynchronously) {
987
0
            MaybeDispatchKeyEventAsProcessedByIME(eVoidEvent);
988
0
            // Be aware, the widget might have been gone here.
989
0
        }
990
0
        // If we need to wait reply from IM, IM may send some signals to us
991
0
        // without sending the key event again.  In such case, we need to
992
0
        // dispatch keyboard events with a copy of aEvent.  Therefore, we
993
0
        // need to use information of this key event to dispatch an KeyDown
994
0
        // or eKeyUp event later.
995
0
        else {
996
0
            MOZ_LOG(gGtkIMLog, LogLevel::Info,
997
0
                ("0x%p   OnKeyEvent(), putting aEvent into the queue...",
998
0
                 this));
999
0
            mPostingKeyEvents.PutEvent(aEvent);
1000
0
        }
1001
0
    }
1002
0
1003
0
    mProcessingKeyEvent = nullptr;
1004
0
1005
0
    if (aEvent->type == GDK_KEY_PRESS && !filterThisEvent) {
1006
0
        // If the key event hasn't been handled by active IME nor keyboard
1007
0
        // layout, we can assume that the dead key sequence has been or was
1008
0
        // ended.  Note that we should not reset it when the key event is
1009
0
        // GDK_KEY_RELEASE since it may not be filtered by active keyboard
1010
0
        // layout even in composition.
1011
0
        mMaybeInDeadKeySequence = false;
1012
0
    }
1013
0
1014
0
    MOZ_LOG(gGtkIMLog, LogLevel::Debug,
1015
0
        ("0x%p   OnKeyEvent(), succeeded, filterThisEvent=%s "
1016
0
         "(isFiltered=%s, mFallbackToKeyEvent=%s, "
1017
0
         "maybeHandledAsynchronously=%s), mCompositionState=%s, "
1018
0
         "mMaybeInDeadKeySequence=%s",
1019
0
         this, ToChar(filterThisEvent), ToChar(isFiltered),
1020
0
         ToChar(mFallbackToKeyEvent), ToChar(maybeHandledAsynchronously),
1021
0
         GetCompositionStateName(), ToChar(mMaybeInDeadKeySequence)));
1022
0
1023
0
    return filterThisEvent;
1024
0
}
1025
1026
void
1027
IMContextWrapper::OnFocusChangeInGecko(bool aFocus)
1028
0
{
1029
0
    MOZ_LOG(gGtkIMLog, LogLevel::Info,
1030
0
        ("0x%p OnFocusChangeInGecko(aFocus=%s), "
1031
0
         "mCompositionState=%s, mIsIMFocused=%s",
1032
0
         this, ToChar(aFocus), GetCompositionStateName(),
1033
0
         ToChar(mIsIMFocused)));
1034
0
1035
0
    // We shouldn't carry over the removed string to another editor.
1036
0
    mSelectedStringRemovedByComposition.Truncate();
1037
0
    mSelection.Clear();
1038
0
}
1039
1040
void
1041
IMContextWrapper::ResetIME()
1042
0
{
1043
0
    MOZ_LOG(gGtkIMLog, LogLevel::Info,
1044
0
        ("0x%p ResetIME(), mCompositionState=%s, mIsIMFocused=%s",
1045
0
         this, GetCompositionStateName(), ToChar(mIsIMFocused)));
1046
0
1047
0
    GtkIMContext* activeContext = GetActiveContext();
1048
0
    if (MOZ_UNLIKELY(!activeContext)) {
1049
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
1050
0
            ("0x%p   ResetIME(), FAILED, there are no context",
1051
0
             this));
1052
0
        return;
1053
0
    }
1054
0
1055
0
    RefPtr<IMContextWrapper> kungFuDeathGrip(this);
1056
0
    RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
1057
0
1058
0
    mPendingResettingIMContext = false;
1059
0
    gtk_im_context_reset(activeContext);
1060
0
1061
0
    // The last focused window might have been destroyed by a DOM event handler
1062
0
    // which was called by us during a call of gtk_im_context_reset().
1063
0
    if (!lastFocusedWindow ||
1064
0
        NS_WARN_IF(lastFocusedWindow != mLastFocusedWindow) ||
1065
0
        lastFocusedWindow->Destroyed()) {
1066
0
        return;
1067
0
    }
1068
0
1069
0
    nsAutoString compositionString;
1070
0
    GetCompositionString(activeContext, compositionString);
1071
0
1072
0
    MOZ_LOG(gGtkIMLog, LogLevel::Debug,
1073
0
        ("0x%p   ResetIME() called gtk_im_context_reset(), "
1074
0
         "activeContext=0x%p, mCompositionState=%s, compositionString=%s, "
1075
0
         "mIsIMFocused=%s",
1076
0
         this, activeContext, GetCompositionStateName(),
1077
0
         NS_ConvertUTF16toUTF8(compositionString).get(),
1078
0
         ToChar(mIsIMFocused)));
1079
0
1080
0
    // XXX IIIMF (ATOK X3 which is one of the Language Engine of it is still
1081
0
    //     used in Japan!) sends only "preedit_changed" signal with empty
1082
0
    //     composition string synchronously.  Therefore, if composition string
1083
0
    //     is now empty string, we should assume that the IME won't send
1084
0
    //     "commit" signal.
1085
0
    if (IsComposing() && compositionString.IsEmpty()) {
1086
0
        // WARNING: The widget might have been gone after this.
1087
0
        DispatchCompositionCommitEvent(activeContext, &EmptyString());
1088
0
    }
1089
0
}
1090
1091
nsresult
1092
IMContextWrapper::EndIMEComposition(nsWindow* aCaller)
1093
0
{
1094
0
    if (MOZ_UNLIKELY(IsDestroyed())) {
1095
0
        return NS_OK;
1096
0
    }
1097
0
1098
0
    MOZ_LOG(gGtkIMLog, LogLevel::Info,
1099
0
        ("0x%p EndIMEComposition(aCaller=0x%p), "
1100
0
         "mCompositionState=%s",
1101
0
         this, aCaller, GetCompositionStateName()));
1102
0
1103
0
    if (aCaller != mLastFocusedWindow) {
1104
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
1105
0
            ("0x%p   EndIMEComposition(), FAILED, the caller isn't "
1106
0
             "focused window, mLastFocusedWindow=0x%p",
1107
0
             this, mLastFocusedWindow));
1108
0
        return NS_OK;
1109
0
    }
1110
0
1111
0
    if (!IsComposing()) {
1112
0
        return NS_OK;
1113
0
    }
1114
0
1115
0
    // Currently, GTK has API neither to commit nor to cancel composition
1116
0
    // forcibly.  Therefore, TextComposition will recompute commit string for
1117
0
    // the request even if native IME will cause unexpected commit string.
1118
0
    // So, we don't need to emulate commit or cancel composition with
1119
0
    // proper composition events.
1120
0
    // XXX ResetIME() might not enough for finishing compositoin on some
1121
0
    //     environments.  We should emulate focus change too because some IMEs
1122
0
    //     may commit or cancel composition at blur.
1123
0
    ResetIME();
1124
0
1125
0
    return NS_OK;
1126
0
}
1127
1128
void
1129
IMContextWrapper::OnLayoutChange()
1130
0
{
1131
0
    if (MOZ_UNLIKELY(IsDestroyed())) {
1132
0
        return;
1133
0
    }
1134
0
1135
0
    if (IsComposing()) {
1136
0
        SetCursorPosition(GetActiveContext());
1137
0
    } else {
1138
0
        // If not composing, candidate window position is updated before key
1139
0
        // down
1140
0
        mSetCursorPositionOnKeyEvent = true;
1141
0
    }
1142
0
    mLayoutChanged = true;
1143
0
}
1144
1145
void
1146
IMContextWrapper::OnUpdateComposition()
1147
0
{
1148
0
    if (MOZ_UNLIKELY(IsDestroyed())) {
1149
0
        return;
1150
0
    }
1151
0
1152
0
    if (!IsComposing()) {
1153
0
        // Composition has been committed.  So we need update selection for
1154
0
        // caret later
1155
0
        mSelection.Clear();
1156
0
        EnsureToCacheSelection();
1157
0
        mSetCursorPositionOnKeyEvent = true;
1158
0
    }
1159
0
1160
0
    // If we've already set candidate window position, we don't need to update
1161
0
    // the position with update composition notification.
1162
0
    if (!mLayoutChanged) {
1163
0
        SetCursorPosition(GetActiveContext());
1164
0
    }
1165
0
}
1166
1167
void
1168
IMContextWrapper::SetInputContext(nsWindow* aCaller,
1169
                                  const InputContext* aContext,
1170
                                  const InputContextAction* aAction)
1171
0
{
1172
0
    if (MOZ_UNLIKELY(IsDestroyed())) {
1173
0
        return;
1174
0
    }
1175
0
1176
0
    MOZ_LOG(gGtkIMLog, LogLevel::Info,
1177
0
        ("0x%p SetInputContext(aCaller=0x%p, aContext={ mIMEState={ "
1178
0
         "mEnabled=%s }, mHTMLInputType=%s })",
1179
0
         this, aCaller, GetEnabledStateName(aContext->mIMEState.mEnabled),
1180
0
         NS_ConvertUTF16toUTF8(aContext->mHTMLInputType).get()));
1181
0
1182
0
    if (aCaller != mLastFocusedWindow) {
1183
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
1184
0
            ("0x%p   SetInputContext(), FAILED, "
1185
0
             "the caller isn't focused window, mLastFocusedWindow=0x%p",
1186
0
             this, mLastFocusedWindow));
1187
0
        return;
1188
0
    }
1189
0
1190
0
    if (!mContext) {
1191
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
1192
0
            ("0x%p   SetInputContext(), FAILED, "
1193
0
             "there are no context",
1194
0
             this));
1195
0
        return;
1196
0
    }
1197
0
1198
0
1199
0
    if (sLastFocusedContext != this) {
1200
0
        mInputContext = *aContext;
1201
0
        MOZ_LOG(gGtkIMLog, LogLevel::Debug,
1202
0
            ("0x%p   SetInputContext(), succeeded, "
1203
0
             "but we're not active",
1204
0
             this));
1205
0
        return;
1206
0
    }
1207
0
1208
0
    bool changingEnabledState =
1209
0
        aContext->mIMEState.mEnabled != mInputContext.mIMEState.mEnabled ||
1210
0
        aContext->mHTMLInputType != mInputContext.mHTMLInputType;
1211
0
1212
0
    // Release current IME focus if IME is enabled.
1213
0
    if (changingEnabledState && mInputContext.mIMEState.MaybeEditable()) {
1214
0
        EndIMEComposition(mLastFocusedWindow);
1215
0
        Blur();
1216
0
    }
1217
0
1218
0
    mInputContext = *aContext;
1219
0
1220
0
    if (changingEnabledState) {
1221
0
#ifdef MOZ_WIDGET_GTK
1222
0
        static bool sInputPurposeSupported = !gtk_check_version(3, 6, 0);
1223
0
        if (sInputPurposeSupported && mInputContext.mIMEState.MaybeEditable()) {
1224
0
            GtkIMContext* currentContext = GetCurrentContext();
1225
0
            if (currentContext) {
1226
0
                GtkInputPurpose purpose = GTK_INPUT_PURPOSE_FREE_FORM;
1227
0
                const nsString& inputType = mInputContext.mHTMLInputType;
1228
0
                // Password case has difficult issue.  Desktop IMEs disable
1229
0
                // composition if input-purpose is password.
1230
0
                // For disabling IME on |ime-mode: disabled;|, we need to check
1231
0
                // mEnabled value instead of inputType value.  This hack also
1232
0
                // enables composition on
1233
0
                // <input type="password" style="ime-mode: enabled;">.
1234
0
                // This is right behavior of ime-mode on desktop.
1235
0
                //
1236
0
                // On the other hand, IME for tablet devices may provide a
1237
0
                // specific software keyboard for password field.  If so,
1238
0
                // the behavior might look strange on both:
1239
0
                //   <input type="text" style="ime-mode: disabled;">
1240
0
                //   <input type="password" style="ime-mode: enabled;">
1241
0
                //
1242
0
                // Temporarily, we should focus on desktop environment for now.
1243
0
                // I.e., let's ignore tablet devices for now.  When somebody
1244
0
                // reports actual trouble on tablet devices, we should try to
1245
0
                // look for a way to solve actual problem.
1246
0
                if (mInputContext.mIMEState.mEnabled == IMEState::PASSWORD) {
1247
0
                    purpose = GTK_INPUT_PURPOSE_PASSWORD;
1248
0
                } else if (inputType.EqualsLiteral("email")) {
1249
0
                    purpose = GTK_INPUT_PURPOSE_EMAIL;
1250
0
                } else if (inputType.EqualsLiteral("url")) {
1251
0
                    purpose = GTK_INPUT_PURPOSE_URL;
1252
0
                } else if (inputType.EqualsLiteral("tel")) {
1253
0
                    purpose = GTK_INPUT_PURPOSE_PHONE;
1254
0
                } else if (inputType.EqualsLiteral("number")) {
1255
0
                    purpose = GTK_INPUT_PURPOSE_NUMBER;
1256
0
                }
1257
0
1258
0
                g_object_set(currentContext, "input-purpose", purpose, nullptr);
1259
0
            }
1260
0
        }
1261
0
#endif // #ifdef MOZ_WIDGET_GTK
1262
0
1263
0
        // Even when aState is not enabled state, we need to set IME focus.
1264
0
        // Because some IMs are updating the status bar of them at this time.
1265
0
        // Be aware, don't use aWindow here because this method shouldn't move
1266
0
        // focus actually.
1267
0
        Focus();
1268
0
1269
0
        // XXX Should we call Blur() when it's not editable?  E.g., it might be
1270
0
        //     better to close VKB automatically.
1271
0
    }
1272
0
}
1273
1274
InputContext
1275
IMContextWrapper::GetInputContext()
1276
0
{
1277
0
    mInputContext.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
1278
0
    return mInputContext;
1279
0
}
1280
1281
GtkIMContext*
1282
IMContextWrapper::GetCurrentContext() const
1283
0
{
1284
0
    if (IsEnabled()) {
1285
0
        return mContext;
1286
0
    }
1287
0
    if (mInputContext.mIMEState.mEnabled == IMEState::PASSWORD) {
1288
0
        return mSimpleContext;
1289
0
    }
1290
0
    return mDummyContext;
1291
0
}
1292
1293
bool
1294
IMContextWrapper::IsValidContext(GtkIMContext* aContext) const
1295
0
{
1296
0
    if (!aContext) {
1297
0
        return false;
1298
0
    }
1299
0
    return aContext == mContext ||
1300
0
           aContext == mSimpleContext ||
1301
0
           aContext == mDummyContext;
1302
0
}
1303
1304
bool
1305
IMContextWrapper::IsEnabled() const
1306
0
{
1307
0
    return mInputContext.mIMEState.mEnabled == IMEState::ENABLED ||
1308
0
           mInputContext.mIMEState.mEnabled == IMEState::PLUGIN ||
1309
0
           (!sUseSimpleContext &&
1310
0
            mInputContext.mIMEState.mEnabled == IMEState::PASSWORD);
1311
0
}
1312
1313
void
1314
IMContextWrapper::Focus()
1315
0
{
1316
0
    MOZ_LOG(gGtkIMLog, LogLevel::Info,
1317
0
        ("0x%p Focus(), sLastFocusedContext=0x%p",
1318
0
         this, sLastFocusedContext));
1319
0
1320
0
    if (mIsIMFocused) {
1321
0
        NS_ASSERTION(sLastFocusedContext == this,
1322
0
                     "We're not active, but the IM was focused?");
1323
0
        return;
1324
0
    }
1325
0
1326
0
    GtkIMContext* currentContext = GetCurrentContext();
1327
0
    if (!currentContext) {
1328
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
1329
0
            ("0x%p   Focus(), FAILED, there are no context",
1330
0
             this));
1331
0
        return;
1332
0
    }
1333
0
1334
0
    if (sLastFocusedContext && sLastFocusedContext != this) {
1335
0
        sLastFocusedContext->Blur();
1336
0
    }
1337
0
1338
0
    sLastFocusedContext = this;
1339
0
1340
0
    // Forget all posted key events when focus is moved since they shouldn't
1341
0
    // be fired in different editor.
1342
0
    mPostingKeyEvents.Clear();
1343
0
1344
0
    gtk_im_context_focus_in(currentContext);
1345
0
    mIsIMFocused = true;
1346
0
    mSetCursorPositionOnKeyEvent = true;
1347
0
1348
0
    if (!IsEnabled()) {
1349
0
        // We should release IME focus for uim and scim.
1350
0
        // These IMs are using snooper that is released at losing focus.
1351
0
        Blur();
1352
0
    }
1353
0
}
1354
1355
void
1356
IMContextWrapper::Blur()
1357
0
{
1358
0
    MOZ_LOG(gGtkIMLog, LogLevel::Info,
1359
0
        ("0x%p Blur(), mIsIMFocused=%s",
1360
0
         this, ToChar(mIsIMFocused)));
1361
0
1362
0
    if (!mIsIMFocused) {
1363
0
        return;
1364
0
    }
1365
0
1366
0
    GtkIMContext* currentContext = GetCurrentContext();
1367
0
    if (!currentContext) {
1368
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
1369
0
            ("0x%p   Blur(), FAILED, there are no context",
1370
0
             this));
1371
0
        return;
1372
0
    }
1373
0
1374
0
    gtk_im_context_focus_out(currentContext);
1375
0
    mIsIMFocused = false;
1376
0
}
1377
1378
void
1379
IMContextWrapper::OnSelectionChange(nsWindow* aCaller,
1380
                                    const IMENotification& aIMENotification)
1381
0
{
1382
0
    mSelection.Assign(aIMENotification);
1383
0
    bool retrievedSurroundingSignalReceived =
1384
0
      mRetrieveSurroundingSignalReceived;
1385
0
    mRetrieveSurroundingSignalReceived = false;
1386
0
1387
0
    if (MOZ_UNLIKELY(IsDestroyed())) {
1388
0
        return;
1389
0
    }
1390
0
1391
0
    const IMENotification::SelectionChangeDataBase& selectionChangeData =
1392
0
        aIMENotification.mSelectionChangeData;
1393
0
1394
0
    MOZ_LOG(gGtkIMLog, LogLevel::Info,
1395
0
        ("0x%p OnSelectionChange(aCaller=0x%p, aIMENotification={ "
1396
0
         "mSelectionChangeData={ mOffset=%u, Length()=%u, mReversed=%s, "
1397
0
         "mWritingMode=%s, mCausedByComposition=%s, "
1398
0
         "mCausedBySelectionEvent=%s, mOccurredDuringComposition=%s "
1399
0
         "} }), mCompositionState=%s, mIsDeletingSurrounding=%s, "
1400
0
         "mRetrieveSurroundingSignalReceived=%s",
1401
0
         this, aCaller, selectionChangeData.mOffset,
1402
0
         selectionChangeData.Length(),
1403
0
         ToChar(selectionChangeData.mReversed),
1404
0
         GetWritingModeName(selectionChangeData.GetWritingMode()).get(),
1405
0
         ToChar(selectionChangeData.mCausedByComposition),
1406
0
         ToChar(selectionChangeData.mCausedBySelectionEvent),
1407
0
         ToChar(selectionChangeData.mOccurredDuringComposition),
1408
0
         GetCompositionStateName(), ToChar(mIsDeletingSurrounding),
1409
0
         ToChar(retrievedSurroundingSignalReceived)));
1410
0
1411
0
    if (aCaller != mLastFocusedWindow) {
1412
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
1413
0
            ("0x%p   OnSelectionChange(), FAILED, "
1414
0
             "the caller isn't focused window, mLastFocusedWindow=0x%p",
1415
0
             this, mLastFocusedWindow));
1416
0
        return;
1417
0
    }
1418
0
1419
0
    if (!IsComposing()) {
1420
0
        // Now we have no composition (mostly situation on calling this method)
1421
0
        // If we have it, it will set by
1422
0
        // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED.
1423
0
        mSetCursorPositionOnKeyEvent = true;
1424
0
    }
1425
0
1426
0
    // The focused editor might have placeholder text with normal text node.
1427
0
    // In such case, the text node must be removed from a compositionstart
1428
0
    // event handler.  So, we're dispatching eCompositionStart,
1429
0
    // we should ignore selection change notification.
1430
0
    if (mCompositionState == eCompositionState_CompositionStartDispatched) {
1431
0
        if (NS_WARN_IF(!mSelection.IsValid())) {
1432
0
            MOZ_LOG(gGtkIMLog, LogLevel::Error,
1433
0
                ("0x%p   OnSelectionChange(), FAILED, "
1434
0
                 "new offset is too large, cannot keep composing",
1435
0
                 this));
1436
0
        } else {
1437
0
            // Modify the selection start offset with new offset.
1438
0
            mCompositionStart = mSelection.mOffset;
1439
0
            // XXX We should modify mSelectedStringRemovedByComposition?
1440
0
            // But how?
1441
0
            MOZ_LOG(gGtkIMLog, LogLevel::Debug,
1442
0
                ("0x%p   OnSelectionChange(), ignored, mCompositionStart "
1443
0
                 "is updated to %u, the selection change doesn't cause "
1444
0
                 "resetting IM context",
1445
0
                 this, mCompositionStart));
1446
0
            // And don't reset the IM context.
1447
0
            return;
1448
0
        }
1449
0
        // Otherwise, reset the IM context due to impossible to keep composing.
1450
0
    }
1451
0
1452
0
    // If the selection change is caused by deleting surrounding text,
1453
0
    // we shouldn't need to notify IME of selection change.
1454
0
    if (mIsDeletingSurrounding) {
1455
0
        return;
1456
0
    }
1457
0
1458
0
    bool occurredBeforeComposition =
1459
0
      IsComposing() && !selectionChangeData.mOccurredDuringComposition &&
1460
0
      !selectionChangeData.mCausedByComposition;
1461
0
    if (occurredBeforeComposition) {
1462
0
        mPendingResettingIMContext = true;
1463
0
    }
1464
0
1465
0
    // When the selection change is caused by dispatching composition event,
1466
0
    // selection set event and/or occurred before starting current composition,
1467
0
    // we shouldn't notify IME of that and commit existing composition.
1468
0
    if (!selectionChangeData.mCausedByComposition &&
1469
0
        !selectionChangeData.mCausedBySelectionEvent &&
1470
0
        !occurredBeforeComposition) {
1471
0
        // Hack for ibus-pinyin.  ibus-pinyin will synthesize a set of
1472
0
        // composition which commits with empty string after calling
1473
0
        // gtk_im_context_reset().  Therefore, selecting text causes
1474
0
        // unexpectedly removing it.  For preventing it but not breaking the
1475
0
        // other IMEs which use surrounding text, we should call it only when
1476
0
        // surrounding text has been retrieved after last selection range was
1477
0
        // set.  If it's not retrieved, that means that current IME doesn't
1478
0
        // have any content cache, so, it must not need the notification of
1479
0
        // selection change.
1480
0
        if (IsComposing() || retrievedSurroundingSignalReceived) {
1481
0
            ResetIME();
1482
0
        }
1483
0
    }
1484
0
}
1485
1486
/* static */
1487
void
1488
IMContextWrapper::OnThemeChanged()
1489
0
{
1490
0
    if (!SelectionStyleProvider::GetInstance()) {
1491
0
        return;
1492
0
    }
1493
0
    SelectionStyleProvider::GetInstance()->OnThemeChanged();
1494
0
}
1495
1496
/* static */
1497
void
1498
IMContextWrapper::OnStartCompositionCallback(GtkIMContext* aContext,
1499
                                             IMContextWrapper* aModule)
1500
0
{
1501
0
    aModule->OnStartCompositionNative(aContext);
1502
0
}
1503
1504
void
1505
IMContextWrapper::OnStartCompositionNative(GtkIMContext* aContext)
1506
0
{
1507
0
    MOZ_LOG(gGtkIMLog, LogLevel::Info,
1508
0
        ("0x%p OnStartCompositionNative(aContext=0x%p), "
1509
0
         "current context=0x%p, mComposingContext=0x%p",
1510
0
         this, aContext, GetCurrentContext(), mComposingContext));
1511
0
1512
0
    // See bug 472635, we should do nothing if IM context doesn't match.
1513
0
    if (GetCurrentContext() != aContext) {
1514
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
1515
0
            ("0x%p   OnStartCompositionNative(), FAILED, "
1516
0
             "given context doesn't match",
1517
0
             this));
1518
0
        return;
1519
0
    }
1520
0
1521
0
    if (mComposingContext && aContext != mComposingContext) {
1522
0
        // XXX For now, we should ignore this odd case, just logging.
1523
0
        MOZ_LOG(gGtkIMLog, LogLevel::Warning,
1524
0
            ("0x%p   OnStartCompositionNative(), Warning, "
1525
0
             "there is already a composing context but starting new "
1526
0
             "composition with different context",
1527
0
             this));
1528
0
    }
1529
0
1530
0
    // IME may start composition without "preedit_start" signal.  Therefore,
1531
0
    // mComposingContext will be initialized in DispatchCompositionStart().
1532
0
1533
0
    if (!DispatchCompositionStart(aContext)) {
1534
0
        return;
1535
0
    }
1536
0
    mCompositionTargetRange.mOffset = mCompositionStart;
1537
0
    mCompositionTargetRange.mLength = 0;
1538
0
}
1539
1540
/* static */
1541
void
1542
IMContextWrapper::OnEndCompositionCallback(GtkIMContext* aContext,
1543
                                           IMContextWrapper* aModule)
1544
0
{
1545
0
    aModule->OnEndCompositionNative(aContext);
1546
0
}
1547
1548
void
1549
IMContextWrapper::OnEndCompositionNative(GtkIMContext* aContext)
1550
0
{
1551
0
    MOZ_LOG(gGtkIMLog, LogLevel::Info,
1552
0
        ("0x%p OnEndCompositionNative(aContext=0x%p), mComposingContext=0x%p",
1553
0
         this, aContext, mComposingContext));
1554
0
1555
0
    // See bug 472635, we should do nothing if IM context doesn't match.
1556
0
    // Note that if this is called after focus move, the context may different
1557
0
    // from any our owning context.
1558
0
    if (!IsValidContext(aContext)) {
1559
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
1560
0
            ("0x%p    OnEndCompositionNative(), FAILED, "
1561
0
             "given context doesn't match with any context",
1562
0
             this));
1563
0
        return;
1564
0
    }
1565
0
1566
0
    // If we've not started composition with aContext, we should ignore it.
1567
0
    if (aContext != mComposingContext) {
1568
0
        MOZ_LOG(gGtkIMLog, LogLevel::Warning,
1569
0
            ("0x%p    OnEndCompositionNative(), Warning, "
1570
0
             "given context doesn't match with mComposingContext",
1571
0
             this));
1572
0
        return;
1573
0
    }
1574
0
1575
0
    g_object_unref(mComposingContext);
1576
0
    mComposingContext = nullptr;
1577
0
1578
0
    // If we already handled the commit event, we should do nothing here.
1579
0
    if (IsComposing()) {
1580
0
        if (!DispatchCompositionCommitEvent(aContext)) {
1581
0
            // If the widget is destroyed, we should do nothing anymore.
1582
0
            return;
1583
0
        }
1584
0
    }
1585
0
1586
0
    if (mPendingResettingIMContext) {
1587
0
        ResetIME();
1588
0
    }
1589
0
}
1590
1591
/* static */
1592
void
1593
IMContextWrapper::OnChangeCompositionCallback(GtkIMContext* aContext,
1594
                                              IMContextWrapper* aModule)
1595
0
{
1596
0
    aModule->OnChangeCompositionNative(aContext);
1597
0
}
1598
1599
void
1600
IMContextWrapper::OnChangeCompositionNative(GtkIMContext* aContext)
1601
0
{
1602
0
    MOZ_LOG(gGtkIMLog, LogLevel::Info,
1603
0
        ("0x%p OnChangeCompositionNative(aContext=0x%p), "
1604
0
         "mComposingContext=0x%p",
1605
0
         this, aContext, mComposingContext));
1606
0
1607
0
    // See bug 472635, we should do nothing if IM context doesn't match.
1608
0
    // Note that if this is called after focus move, the context may different
1609
0
    // from any our owning context.
1610
0
    if (!IsValidContext(aContext)) {
1611
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
1612
0
            ("0x%p   OnChangeCompositionNative(), FAILED, "
1613
0
             "given context doesn't match with any context",
1614
0
             this));
1615
0
        return;
1616
0
    }
1617
0
1618
0
    if (mComposingContext && aContext != mComposingContext) {
1619
0
        // XXX For now, we should ignore this odd case, just logging.
1620
0
        MOZ_LOG(gGtkIMLog, LogLevel::Warning,
1621
0
            ("0x%p   OnChangeCompositionNative(), Warning, "
1622
0
             "given context doesn't match with composing context",
1623
0
             this));
1624
0
    }
1625
0
1626
0
    nsAutoString compositionString;
1627
0
    GetCompositionString(aContext, compositionString);
1628
0
    if (!IsComposing() && compositionString.IsEmpty()) {
1629
0
        MOZ_LOG(gGtkIMLog, LogLevel::Warning,
1630
0
            ("0x%p   OnChangeCompositionNative(), Warning, does nothing "
1631
0
             "because has not started composition and composing string is "
1632
0
             "empty", this));
1633
0
        mDispatchedCompositionString.Truncate();
1634
0
        return; // Don't start the composition with empty string.
1635
0
    }
1636
0
1637
0
    // Be aware, widget can be gone
1638
0
    DispatchCompositionChangeEvent(aContext, compositionString);
1639
0
}
1640
1641
/* static */
1642
gboolean
1643
IMContextWrapper::OnRetrieveSurroundingCallback(GtkIMContext* aContext,
1644
                                                IMContextWrapper* aModule)
1645
0
{
1646
0
    return aModule->OnRetrieveSurroundingNative(aContext);
1647
0
}
1648
1649
gboolean
1650
IMContextWrapper::OnRetrieveSurroundingNative(GtkIMContext* aContext)
1651
0
{
1652
0
    MOZ_LOG(gGtkIMLog, LogLevel::Info,
1653
0
        ("0x%p OnRetrieveSurroundingNative(aContext=0x%p), "
1654
0
         "current context=0x%p",
1655
0
         this, aContext, GetCurrentContext()));
1656
0
1657
0
    // See bug 472635, we should do nothing if IM context doesn't match.
1658
0
    if (GetCurrentContext() != aContext) {
1659
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
1660
0
            ("0x%p   OnRetrieveSurroundingNative(), FAILED, "
1661
0
             "given context doesn't match",
1662
0
             this));
1663
0
        return FALSE;
1664
0
    }
1665
0
1666
0
    nsAutoString uniStr;
1667
0
    uint32_t cursorPos;
1668
0
    if (NS_FAILED(GetCurrentParagraph(uniStr, cursorPos))) {
1669
0
        return FALSE;
1670
0
    }
1671
0
1672
0
    NS_ConvertUTF16toUTF8 utf8Str(nsDependentSubstring(uniStr, 0, cursorPos));
1673
0
    uint32_t cursorPosInUTF8 = utf8Str.Length();
1674
0
    AppendUTF16toUTF8(nsDependentSubstring(uniStr, cursorPos), utf8Str);
1675
0
    gtk_im_context_set_surrounding(aContext, utf8Str.get(), utf8Str.Length(),
1676
0
                                   cursorPosInUTF8);
1677
0
    mRetrieveSurroundingSignalReceived = true;
1678
0
    return TRUE;
1679
0
}
1680
1681
/* static */
1682
gboolean
1683
IMContextWrapper::OnDeleteSurroundingCallback(GtkIMContext* aContext,
1684
                                              gint aOffset,
1685
                                              gint aNChars,
1686
                                              IMContextWrapper* aModule)
1687
0
{
1688
0
    return aModule->OnDeleteSurroundingNative(aContext, aOffset, aNChars);
1689
0
}
1690
1691
gboolean
1692
IMContextWrapper::OnDeleteSurroundingNative(GtkIMContext* aContext,
1693
                                            gint aOffset,
1694
                                            gint aNChars)
1695
0
{
1696
0
    MOZ_LOG(gGtkIMLog, LogLevel::Info,
1697
0
        ("0x%p OnDeleteSurroundingNative(aContext=0x%p, aOffset=%d, "
1698
0
         "aNChar=%d), current context=0x%p",
1699
0
         this, aContext, aOffset, aNChars, GetCurrentContext()));
1700
0
1701
0
    // See bug 472635, we should do nothing if IM context doesn't match.
1702
0
    if (GetCurrentContext() != aContext) {
1703
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
1704
0
            ("0x%p   OnDeleteSurroundingNative(), FAILED, "
1705
0
             "given context doesn't match",
1706
0
             this));
1707
0
        return FALSE;
1708
0
    }
1709
0
1710
0
    AutoRestore<bool> saveDeletingSurrounding(mIsDeletingSurrounding);
1711
0
    mIsDeletingSurrounding = true;
1712
0
    if (NS_SUCCEEDED(DeleteText(aContext, aOffset, (uint32_t)aNChars))) {
1713
0
        return TRUE;
1714
0
    }
1715
0
1716
0
    // failed
1717
0
    MOZ_LOG(gGtkIMLog, LogLevel::Error,
1718
0
        ("0x%p   OnDeleteSurroundingNative(), FAILED, "
1719
0
         "cannot delete text",
1720
0
         this));
1721
0
    return FALSE;
1722
0
}
1723
1724
/* static */
1725
void
1726
IMContextWrapper::OnCommitCompositionCallback(GtkIMContext* aContext,
1727
                                              const gchar* aString,
1728
                                              IMContextWrapper* aModule)
1729
0
{
1730
0
    aModule->OnCommitCompositionNative(aContext, aString);
1731
0
}
1732
1733
void
1734
IMContextWrapper::OnCommitCompositionNative(GtkIMContext* aContext,
1735
                                            const gchar* aUTF8Char)
1736
0
{
1737
0
    const gchar emptyStr = 0;
1738
0
    const gchar *commitString = aUTF8Char ? aUTF8Char : &emptyStr;
1739
0
    NS_ConvertUTF8toUTF16 utf16CommitString(commitString);
1740
0
1741
0
    MOZ_LOG(gGtkIMLog, LogLevel::Info,
1742
0
        ("0x%p OnCommitCompositionNative(aContext=0x%p), "
1743
0
         "current context=0x%p, active context=0x%p, commitString=\"%s\", "
1744
0
         "mProcessingKeyEvent=0x%p, IsComposingOn(aContext)=%s",
1745
0
         this, aContext, GetCurrentContext(), GetActiveContext(), commitString,
1746
0
         mProcessingKeyEvent, ToChar(IsComposingOn(aContext))));
1747
0
1748
0
    // See bug 472635, we should do nothing if IM context doesn't match.
1749
0
    if (!IsValidContext(aContext)) {
1750
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
1751
0
            ("0x%p   OnCommitCompositionNative(), FAILED, "
1752
0
             "given context doesn't match",
1753
0
             this));
1754
0
        return;
1755
0
    }
1756
0
1757
0
    // If we are not in composition and committing with empty string,
1758
0
    // we need to do nothing because if we continued to handle this
1759
0
    // signal, we would dispatch compositionstart, text, compositionend
1760
0
    // events with empty string.  Of course, they are unnecessary events
1761
0
    // for Web applications and our editor.
1762
0
    if (!IsComposingOn(aContext) && utf16CommitString.IsEmpty()) {
1763
0
        MOZ_LOG(gGtkIMLog, LogLevel::Warning,
1764
0
            ("0x%p   OnCommitCompositionNative(), Warning, does nothing "
1765
0
             "because has not started composition and commit string is empty",
1766
0
             this));
1767
0
        return;
1768
0
    }
1769
0
1770
0
    // If IME doesn't change their keyevent that generated this commit,
1771
0
    // we should treat that IME didn't handle the key event because
1772
0
    // web applications want to receive "keydown" and "keypress" event
1773
0
    // in such case.
1774
0
    // NOTE: While a key event is being handled, this might be caused on
1775
0
    // current context.  Otherwise, this may be caused on active context.
1776
0
    if (!IsComposingOn(aContext) &&
1777
0
        mProcessingKeyEvent &&
1778
0
        mProcessingKeyEvent->type == GDK_KEY_PRESS &&
1779
0
        aContext == GetCurrentContext()) {
1780
0
        char keyval_utf8[8]; /* should have at least 6 bytes of space */
1781
0
        gint keyval_utf8_len;
1782
0
        guint32 keyval_unicode;
1783
0
1784
0
        keyval_unicode = gdk_keyval_to_unicode(mProcessingKeyEvent->keyval);
1785
0
        keyval_utf8_len = g_unichar_to_utf8(keyval_unicode, keyval_utf8);
1786
0
        keyval_utf8[keyval_utf8_len] = '\0';
1787
0
1788
0
        // If committing string is exactly same as a character which is
1789
0
        // produced by the key, eKeyDown and eKeyPress event should be
1790
0
        // dispatched by the caller of OnKeyEvent() normally.  Note that
1791
0
        // mMaybeInDeadKeySequence will be set to false by OnKeyEvent()
1792
0
        // since we set mFallbackToKeyEvent to true here.
1793
0
        if (!strcmp(commitString, keyval_utf8)) {
1794
0
            MOZ_LOG(gGtkIMLog, LogLevel::Info,
1795
0
                ("0x%p   OnCommitCompositionNative(), "
1796
0
                 "we'll send normal key event",
1797
0
                 this));
1798
0
            mFallbackToKeyEvent = true;
1799
0
            return;
1800
0
        }
1801
0
1802
0
        // If we're in a dead key sequence, commit string is a character in
1803
0
        // the BMP and mProcessingKeyEvent produces some characters but it's
1804
0
        // not same as committing string, we should dispatch an eKeyPress
1805
0
        // event from here.
1806
0
        WidgetKeyboardEvent keyDownEvent(true, eKeyDown,
1807
0
                                         mLastFocusedWindow);
1808
0
        KeymapWrapper::InitKeyEvent(keyDownEvent, mProcessingKeyEvent, false);
1809
0
        if (mMaybeInDeadKeySequence &&
1810
0
            utf16CommitString.Length() == 1 &&
1811
0
            keyDownEvent.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING) {
1812
0
            mKeyboardEventWasDispatched = true;
1813
0
            // Anyway, we're not in dead key sequence anymore.
1814
0
            mMaybeInDeadKeySequence = false;
1815
0
1816
0
            RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
1817
0
            nsresult rv = dispatcher->BeginNativeInputTransaction();
1818
0
            if (NS_WARN_IF(NS_FAILED(rv))) {
1819
0
                MOZ_LOG(gGtkIMLog, LogLevel::Error,
1820
0
                    ("0x%p   OnCommitCompositionNative(), FAILED, "
1821
0
                     "due to BeginNativeInputTransaction() failure",
1822
0
                     this));
1823
0
                return;
1824
0
            }
1825
0
1826
0
            // First, dispatch eKeyDown event.
1827
0
            keyDownEvent.mKeyValue = utf16CommitString;
1828
0
            nsEventStatus status = nsEventStatus_eIgnore;
1829
0
            bool dispatched =
1830
0
                dispatcher->DispatchKeyboardEvent(eKeyDown, keyDownEvent,
1831
0
                                                  status, mProcessingKeyEvent);
1832
0
            if (!dispatched || status == nsEventStatus_eConsumeNoDefault) {
1833
0
                MOZ_LOG(gGtkIMLog, LogLevel::Info,
1834
0
                    ("0x%p   OnCommitCompositionNative(), "
1835
0
                     "doesn't dispatch eKeyPress event because the preceding "
1836
0
                     "eKeyDown event was not dispatched or was consumed",
1837
0
                     this));
1838
0
                return;
1839
0
            }
1840
0
            if (mLastFocusedWindow != keyDownEvent.mWidget ||
1841
0
                mLastFocusedWindow->Destroyed()) {
1842
0
                MOZ_LOG(gGtkIMLog, LogLevel::Warning,
1843
0
                    ("0x%p   OnCommitCompositionNative(), Warning, "
1844
0
                     "stop dispatching eKeyPress event because the preceding "
1845
0
                     "eKeyDown event caused changing focused widget or "
1846
0
                     "destroyed",
1847
0
                     this));
1848
0
                return;
1849
0
            }
1850
0
            MOZ_LOG(gGtkIMLog, LogLevel::Info,
1851
0
                ("0x%p   OnCommitCompositionNative(), "
1852
0
                 "dispatched eKeyDown event for the committed character",
1853
0
                 this));
1854
0
1855
0
            // Next, dispatch eKeyPress event.
1856
0
            dispatcher->MaybeDispatchKeypressEvents(keyDownEvent, status,
1857
0
                                                    mProcessingKeyEvent);
1858
0
            MOZ_LOG(gGtkIMLog, LogLevel::Info,
1859
0
                ("0x%p   OnCommitCompositionNative(), "
1860
0
                 "dispatched eKeyPress event for the committed character",
1861
0
                 this));
1862
0
            return;
1863
0
        }
1864
0
    }
1865
0
1866
0
    NS_ConvertUTF8toUTF16 str(commitString);
1867
0
    // Be aware, widget can be gone
1868
0
    DispatchCompositionCommitEvent(aContext, &str);
1869
0
}
1870
1871
void
1872
IMContextWrapper::GetCompositionString(GtkIMContext* aContext,
1873
                                       nsAString& aCompositionString)
1874
0
{
1875
0
    gchar *preedit_string;
1876
0
    gint cursor_pos;
1877
0
    PangoAttrList *feedback_list;
1878
0
    gtk_im_context_get_preedit_string(aContext, &preedit_string,
1879
0
                                      &feedback_list, &cursor_pos);
1880
0
    if (preedit_string && *preedit_string) {
1881
0
      CopyUTF8toUTF16(MakeStringSpan(preedit_string), aCompositionString);
1882
0
    } else {
1883
0
        aCompositionString.Truncate();
1884
0
    }
1885
0
1886
0
    MOZ_LOG(gGtkIMLog, LogLevel::Info,
1887
0
        ("0x%p GetCompositionString(aContext=0x%p), "
1888
0
         "aCompositionString=\"%s\"",
1889
0
         this, aContext, preedit_string));
1890
0
1891
0
    pango_attr_list_unref(feedback_list);
1892
0
    g_free(preedit_string);
1893
0
}
1894
1895
bool
1896
IMContextWrapper::MaybeDispatchKeyEventAsProcessedByIME(
1897
                      EventMessage aFollowingEvent)
1898
0
{
1899
0
    if (!mLastFocusedWindow) {
1900
0
        return false;
1901
0
    }
1902
0
1903
0
    if (!mIsKeySnooped &&
1904
0
        ((!mProcessingKeyEvent && mPostingKeyEvents.IsEmpty()) ||
1905
0
         (mProcessingKeyEvent && mKeyboardEventWasDispatched))) {
1906
0
        return true;
1907
0
    }
1908
0
1909
0
    // A "keydown" or "keyup" event handler may change focus with the
1910
0
    // following event.  In such case, we need to cancel this composition.
1911
0
    // So, we need to store IM context now because mComposingContext may be
1912
0
    // overwritten with different context if calling this method recursively.
1913
0
    // Note that we don't need to grab the context here because |context|
1914
0
    // will be used only for checking if it's same as mComposingContext.
1915
0
    GtkIMContext* oldCurrentContext = GetCurrentContext();
1916
0
    GtkIMContext* oldComposingContext = mComposingContext;
1917
0
1918
0
    RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
1919
0
1920
0
    if (mProcessingKeyEvent || !mPostingKeyEvents.IsEmpty()) {
1921
0
        if (mProcessingKeyEvent) {
1922
0
            mKeyboardEventWasDispatched = true;
1923
0
        }
1924
0
        // If we're not handling a key event synchronously, the signal may be
1925
0
        // sent by IME without sending key event to us.  In such case, we
1926
0
        // should dispatch keyboard event for the last key event which was
1927
0
        // posted to other IME process.
1928
0
        GdkEventKey* sourceEvent =
1929
0
            mProcessingKeyEvent ? mProcessingKeyEvent :
1930
0
                                  mPostingKeyEvents.GetFirstEvent();
1931
0
1932
0
        MOZ_LOG(gGtkIMLog, LogLevel::Info,
1933
0
            ("0x%p MaybeDispatchKeyEventAsProcessedByIME("
1934
0
             "aFollowingEvent=%s), dispatch %s %s "
1935
0
             "event: { type=%s, keyval=%s, unicode=0x%X, state=%s, "
1936
0
             "time=%u, hardware_keycode=%u, group=%u }",
1937
0
             this, ToChar(aFollowingEvent),
1938
0
             ToChar(sourceEvent->type == GDK_KEY_PRESS ? eKeyDown : eKeyUp),
1939
0
             mProcessingKeyEvent ? "processing" : "posted",
1940
0
             GetEventType(sourceEvent), gdk_keyval_name(sourceEvent->keyval),
1941
0
             gdk_keyval_to_unicode(sourceEvent->keyval),
1942
0
             GetEventStateName(sourceEvent->state, mIMContextID).get(),
1943
0
             sourceEvent->time, sourceEvent->hardware_keycode,
1944
0
             sourceEvent->group));
1945
0
1946
0
        // Let's dispatch eKeyDown event or eKeyUp event now.  Note that only
1947
0
        // when we're not in a dead key composition, we should mark the
1948
0
        // eKeyDown and eKeyUp event as "processed by IME" since we should
1949
0
        // expose raw keyCode and key value to web apps the key event is a
1950
0
        // part of a dead key sequence.
1951
0
        // FYI: We should ignore if default of preceding keydown or keyup
1952
0
        //      event is prevented since even on the other browsers, web
1953
0
        //      applications cannot cancel the following composition event.
1954
0
        //      Spec bug: https://github.com/w3c/uievents/issues/180
1955
0
        bool isCancelled;
1956
0
        lastFocusedWindow->DispatchKeyDownOrKeyUpEvent(sourceEvent,
1957
0
                                                       !mMaybeInDeadKeySequence,
1958
0
                                                       &isCancelled);
1959
0
        MOZ_LOG(gGtkIMLog, LogLevel::Info,
1960
0
            ("0x%p   MaybeDispatchKeyEventAsProcessedByIME(), keydown or keyup "
1961
0
             "event is dispatched",
1962
0
             this));
1963
0
1964
0
        if (!mProcessingKeyEvent) {
1965
0
            MOZ_LOG(gGtkIMLog, LogLevel::Info,
1966
0
                ("0x%p   MaybeDispatchKeyEventAsProcessedByIME(), removing first "
1967
0
                 "event from the queue",
1968
0
                 this));
1969
0
            mPostingKeyEvents.RemoveEvent(sourceEvent);
1970
0
        }
1971
0
    } else {
1972
0
        MOZ_ASSERT(mIsKeySnooped);
1973
0
        // Currently, we support key snooper mode of uim only.
1974
0
        MOZ_ASSERT(mIMContextID == IMContextID::eUim);
1975
0
        // uim sends "preedit_start" signal and "preedit_changed" separately
1976
0
        // at starting composition, "commit" and "preedit_end" separately at
1977
0
        // committing composition.
1978
0
1979
0
        // Currently, we should dispatch only fake eKeyDown event because
1980
0
        // we cannot decide which is the last signal of each key operation
1981
0
        // and Chromium also dispatches only "keydown" event in this case.
1982
0
        bool dispatchFakeKeyDown = false;
1983
0
        switch (aFollowingEvent) {
1984
0
            case eCompositionStart:
1985
0
            case eCompositionCommit:
1986
0
            case eCompositionCommitAsIs:
1987
0
                dispatchFakeKeyDown = true;
1988
0
                break;
1989
0
            // XXX Unfortunately, I don't have a good idea to prevent to
1990
0
            //     dispatch redundant eKeyDown event for eCompositionStart
1991
0
            //     immediately after "delete_surrounding" signal.  However,
1992
0
            //     not dispatching eKeyDown event is worse than dispatching
1993
0
            //     redundant eKeyDown events.
1994
0
            case eContentCommandDelete:
1995
0
                dispatchFakeKeyDown = true;
1996
0
                break;
1997
0
            // We need to prevent to dispatch redundant eKeyDown event for
1998
0
            // eCompositionChange immediately after eCompositionStart.  So,
1999
0
            // We should not dispatch eKeyDown event if dispatched composition
2000
0
            // string is still empty string.
2001
0
            case eCompositionChange:
2002
0
                dispatchFakeKeyDown = !mDispatchedCompositionString.IsEmpty();
2003
0
                break;
2004
0
            default:
2005
0
                MOZ_ASSERT_UNREACHABLE("Do you forget to handle the case?");
2006
0
                break;
2007
0
        }
2008
0
2009
0
        if (dispatchFakeKeyDown) {
2010
0
            WidgetKeyboardEvent fakeKeyDownEvent(true, eKeyDown,
2011
0
                                                 lastFocusedWindow);
2012
0
            fakeKeyDownEvent.mKeyCode = NS_VK_PROCESSKEY;
2013
0
            fakeKeyDownEvent.mKeyNameIndex = KEY_NAME_INDEX_Process;
2014
0
            // It's impossible to get physical key information in this case but
2015
0
            // this should be okay since web apps shouldn't do anything with
2016
0
            // physical key information during composition.
2017
0
            fakeKeyDownEvent.mCodeNameIndex = CODE_NAME_INDEX_UNKNOWN;
2018
0
2019
0
            MOZ_LOG(gGtkIMLog, LogLevel::Info,
2020
0
                ("0x%p MaybeDispatchKeyEventAsProcessedByIME("
2021
0
                 "aFollowingEvent=%s), dispatch fake eKeyDown event",
2022
0
                 this, ToChar(aFollowingEvent)));
2023
0
2024
0
            bool isCancelled;
2025
0
            lastFocusedWindow->DispatchKeyDownOrKeyUpEvent(fakeKeyDownEvent,
2026
0
                                                           &isCancelled);
2027
0
            MOZ_LOG(gGtkIMLog, LogLevel::Info,
2028
0
                ("0x%p   MaybeDispatchKeyEventAsProcessedByIME(), "
2029
0
                 "fake keydown event is dispatched",
2030
0
                 this));
2031
0
        }
2032
0
    }
2033
0
2034
0
    if (lastFocusedWindow->IsDestroyed() ||
2035
0
        lastFocusedWindow != mLastFocusedWindow) {
2036
0
        MOZ_LOG(gGtkIMLog, LogLevel::Warning,
2037
0
            ("0x%p   MaybeDispatchKeyEventAsProcessedByIME(), Warning, the "
2038
0
             "focused widget was destroyed/changed by a key event",
2039
0
             this));
2040
0
        return false;
2041
0
    }
2042
0
2043
0
    // If the dispatched keydown event caused moving focus and that also
2044
0
    // caused changing active context, we need to cancel composition here.
2045
0
    if (GetCurrentContext() != oldCurrentContext) {
2046
0
        MOZ_LOG(gGtkIMLog, LogLevel::Warning,
2047
0
            ("0x%p   MaybeDispatchKeyEventAsProcessedByIME(), Warning, the key "
2048
0
             "event causes changing active IM context",
2049
0
             this));
2050
0
        if (mComposingContext == oldComposingContext) {
2051
0
            // Only when the context is still composing, we should call
2052
0
            // ResetIME() here.  Otherwise, it should've already been
2053
0
            // cleaned up.
2054
0
            ResetIME();
2055
0
        }
2056
0
        return false;
2057
0
    }
2058
0
2059
0
    return true;
2060
0
}
2061
2062
bool
2063
IMContextWrapper::DispatchCompositionStart(GtkIMContext* aContext)
2064
0
{
2065
0
    MOZ_LOG(gGtkIMLog, LogLevel::Info,
2066
0
        ("0x%p DispatchCompositionStart(aContext=0x%p)",
2067
0
         this, aContext));
2068
0
2069
0
    if (IsComposing()) {
2070
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
2071
0
            ("0x%p   DispatchCompositionStart(), FAILED, "
2072
0
             "we're already in composition",
2073
0
             this));
2074
0
        return true;
2075
0
    }
2076
0
2077
0
    if (!mLastFocusedWindow) {
2078
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
2079
0
            ("0x%p   DispatchCompositionStart(), FAILED, "
2080
0
             "there are no focused window in this module",
2081
0
             this));
2082
0
        return false;
2083
0
    }
2084
0
2085
0
    if (NS_WARN_IF(!EnsureToCacheSelection())) {
2086
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
2087
0
            ("0x%p   DispatchCompositionStart(), FAILED, "
2088
0
             "cannot query the selection offset",
2089
0
             this));
2090
0
        return false;
2091
0
    }
2092
0
2093
0
    mComposingContext = static_cast<GtkIMContext*>(g_object_ref(aContext));
2094
0
    MOZ_ASSERT(mComposingContext);
2095
0
2096
0
    // Keep the last focused window alive
2097
0
    RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
2098
0
2099
0
    // XXX The composition start point might be changed by composition events
2100
0
    //     even though we strongly hope it doesn't happen.
2101
0
    //     Every composition event should have the start offset for the result
2102
0
    //     because it may high cost if we query the offset every time.
2103
0
    mCompositionStart = mSelection.mOffset;
2104
0
    mDispatchedCompositionString.Truncate();
2105
0
2106
0
    // If this composition is started by a key press, we need to dispatch
2107
0
    // eKeyDown or eKeyUp event before dispatching eCompositionStart event.
2108
0
    // Note that dispatching a keyboard event which is marked as "processed
2109
0
    // by IME" is okay since Chromium also dispatches keyboard event as so.
2110
0
    if (!MaybeDispatchKeyEventAsProcessedByIME(eCompositionStart)) {
2111
0
        MOZ_LOG(gGtkIMLog, LogLevel::Warning,
2112
0
            ("0x%p   DispatchCompositionStart(), Warning, "
2113
0
             "MaybeDispatchKeyEventAsProcessedByIME() returned false",
2114
0
             this));
2115
0
        return false;
2116
0
    }
2117
0
2118
0
    RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
2119
0
    nsresult rv = dispatcher->BeginNativeInputTransaction();
2120
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
2121
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
2122
0
            ("0x%p   DispatchCompositionStart(), FAILED, "
2123
0
             "due to BeginNativeInputTransaction() failure",
2124
0
             this));
2125
0
        return false;
2126
0
    }
2127
0
2128
0
    static bool sHasSetTelemetry = false;
2129
0
    if (!sHasSetTelemetry) {
2130
0
        sHasSetTelemetry = true;
2131
0
        NS_ConvertUTF8toUTF16 im(GetIMName());
2132
0
        // 72 is kMaximumKeyStringLength in TelemetryScalar.cpp
2133
0
        if (im.Length() > 72) {
2134
0
            if (NS_IS_LOW_SURROGATE(im[72 - 1]) &&
2135
0
                NS_IS_HIGH_SURROGATE(im[72 - 2])) {
2136
0
                im.Truncate(72 - 2);
2137
0
            } else {
2138
0
                im.Truncate(72 - 1);
2139
0
            }
2140
0
            // U+2026 is "..."
2141
0
            im.Append(char16_t(0x2026));
2142
0
        }
2143
0
        Telemetry::ScalarSet(Telemetry::ScalarID::WIDGET_IME_NAME_ON_LINUX,
2144
0
                             im, true);
2145
0
    }
2146
0
2147
0
    MOZ_LOG(gGtkIMLog, LogLevel::Debug,
2148
0
        ("0x%p   DispatchCompositionStart(), dispatching "
2149
0
         "compositionstart... (mCompositionStart=%u)",
2150
0
         this, mCompositionStart));
2151
0
    mCompositionState = eCompositionState_CompositionStartDispatched;
2152
0
    nsEventStatus status;
2153
0
    dispatcher->StartComposition(status);
2154
0
    if (lastFocusedWindow->IsDestroyed() ||
2155
0
        lastFocusedWindow != mLastFocusedWindow) {
2156
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
2157
0
            ("0x%p   DispatchCompositionStart(), FAILED, the focused "
2158
0
             "widget was destroyed/changed by compositionstart event",
2159
0
             this));
2160
0
        return false;
2161
0
    }
2162
0
2163
0
    return true;
2164
0
}
2165
2166
bool
2167
IMContextWrapper::DispatchCompositionChangeEvent(
2168
                      GtkIMContext* aContext,
2169
                      const nsAString& aCompositionString)
2170
0
{
2171
0
    MOZ_LOG(gGtkIMLog, LogLevel::Info,
2172
0
        ("0x%p DispatchCompositionChangeEvent(aContext=0x%p)",
2173
0
         this, aContext));
2174
0
2175
0
    if (!mLastFocusedWindow) {
2176
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
2177
0
            ("0x%p   DispatchCompositionChangeEvent(), FAILED, "
2178
0
             "there are no focused window in this module",
2179
0
             this));
2180
0
        return false;
2181
0
    }
2182
0
2183
0
    if (!IsComposing()) {
2184
0
        MOZ_LOG(gGtkIMLog, LogLevel::Debug,
2185
0
            ("0x%p   DispatchCompositionChangeEvent(), the composition "
2186
0
             "wasn't started, force starting...",
2187
0
             this));
2188
0
        if (!DispatchCompositionStart(aContext)) {
2189
0
            return false;
2190
0
        }
2191
0
    }
2192
0
    // If this composition string change caused by a key press, we need to
2193
0
    // dispatch eKeyDown or eKeyUp before dispatching eCompositionChange event.
2194
0
    else if (!MaybeDispatchKeyEventAsProcessedByIME(eCompositionChange)) {
2195
0
        MOZ_LOG(gGtkIMLog, LogLevel::Warning,
2196
0
            ("0x%p   DispatchCompositionChangeEvent(), Warning, "
2197
0
             "MaybeDispatchKeyEventAsProcessedByIME() returned false",
2198
0
             this));
2199
0
        return false;
2200
0
    }
2201
0
2202
0
    RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
2203
0
    nsresult rv = dispatcher->BeginNativeInputTransaction();
2204
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
2205
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
2206
0
            ("0x%p   DispatchCompositionChangeEvent(), FAILED, "
2207
0
             "due to BeginNativeInputTransaction() failure",
2208
0
             this));
2209
0
        return false;
2210
0
    }
2211
0
2212
0
    // Store the selected string which will be removed by following
2213
0
    // compositionchange event.
2214
0
    if (mCompositionState == eCompositionState_CompositionStartDispatched) {
2215
0
        if (NS_WARN_IF(!EnsureToCacheSelection(
2216
0
                            &mSelectedStringRemovedByComposition))) {
2217
0
            // XXX How should we behave in this case??
2218
0
        } else {
2219
0
            // XXX We should assume, for now, any web applications don't change
2220
0
            //     selection at handling this compositionchange event.
2221
0
            mCompositionStart = mSelection.mOffset;
2222
0
        }
2223
0
    }
2224
0
2225
0
    RefPtr<TextRangeArray> rangeArray =
2226
0
      CreateTextRangeArray(aContext, aCompositionString);
2227
0
2228
0
    rv = dispatcher->SetPendingComposition(aCompositionString, rangeArray);
2229
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
2230
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
2231
0
            ("0x%p   DispatchCompositionChangeEvent(), FAILED, "
2232
0
             "due to SetPendingComposition() failure",
2233
0
             this));
2234
0
        return false;
2235
0
    }
2236
0
2237
0
    mCompositionState = eCompositionState_CompositionChangeEventDispatched;
2238
0
2239
0
    // We cannot call SetCursorPosition for e10s-aware.
2240
0
    // DispatchEvent is async on e10s, so composition rect isn't updated now
2241
0
    // on tab parent.
2242
0
    mDispatchedCompositionString = aCompositionString;
2243
0
    mLayoutChanged = false;
2244
0
    mCompositionTargetRange.mOffset =
2245
0
        mCompositionStart + rangeArray->TargetClauseOffset();
2246
0
    mCompositionTargetRange.mLength = rangeArray->TargetClauseLength();
2247
0
2248
0
    RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
2249
0
    nsEventStatus status;
2250
0
    rv = dispatcher->FlushPendingComposition(status);
2251
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
2252
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
2253
0
            ("0x%p   DispatchCompositionChangeEvent(), FAILED, "
2254
0
             "due to FlushPendingComposition() failure",
2255
0
             this));
2256
0
        return false;
2257
0
    }
2258
0
2259
0
    if (lastFocusedWindow->IsDestroyed() ||
2260
0
        lastFocusedWindow != mLastFocusedWindow) {
2261
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
2262
0
            ("0x%p   DispatchCompositionChangeEvent(), FAILED, the "
2263
0
             "focused widget was destroyed/changed by "
2264
0
             "compositionchange event",
2265
0
             this));
2266
0
        return false;
2267
0
    }
2268
0
    return true;
2269
0
}
2270
2271
bool
2272
IMContextWrapper::DispatchCompositionCommitEvent(
2273
                      GtkIMContext* aContext,
2274
                      const nsAString* aCommitString)
2275
0
{
2276
0
    MOZ_LOG(gGtkIMLog, LogLevel::Info,
2277
0
        ("0x%p DispatchCompositionCommitEvent(aContext=0x%p, "
2278
0
         "aCommitString=0x%p, (\"%s\"))",
2279
0
         this, aContext, aCommitString,
2280
0
         aCommitString ? NS_ConvertUTF16toUTF8(*aCommitString).get() : ""));
2281
0
2282
0
    if (!mLastFocusedWindow) {
2283
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
2284
0
            ("0x%p   DispatchCompositionCommitEvent(), FAILED, "
2285
0
             "there are no focused window in this module",
2286
0
             this));
2287
0
        return false;
2288
0
    }
2289
0
2290
0
    // TODO: We need special care to handle request to commit composition
2291
0
    //       by content while we're committing composition because we have
2292
0
    //       commit string information now but IME may not have composition
2293
0
    //       anymore.  Therefore, we may not be able to handle commit as
2294
0
    //       expected.  However, this is rare case because this situation
2295
0
    //       never occurs with remote content.  So, it's okay to fix this
2296
0
    //       issue later.  (Perhaps, TextEventDisptcher should do it for
2297
0
    //       all platforms.  E.g., creating WillCommitComposition()?)
2298
0
    if (!IsComposing()) {
2299
0
        if (!aCommitString || aCommitString->IsEmpty()) {
2300
0
            MOZ_LOG(gGtkIMLog, LogLevel::Error,
2301
0
                ("0x%p   DispatchCompositionCommitEvent(), FAILED, "
2302
0
                 "there is no composition and empty commit string",
2303
0
                 this));
2304
0
            return true;
2305
0
        }
2306
0
        MOZ_LOG(gGtkIMLog, LogLevel::Debug,
2307
0
            ("0x%p   DispatchCompositionCommitEvent(), "
2308
0
             "the composition wasn't started, force starting...",
2309
0
             this));
2310
0
        if (!DispatchCompositionStart(aContext)) {
2311
0
            return false;
2312
0
        }
2313
0
    }
2314
0
    // If this commit caused by a key press, we need to dispatch eKeyDown or
2315
0
    // eKeyUp before dispatching composition events.
2316
0
    else if (!MaybeDispatchKeyEventAsProcessedByIME(
2317
0
                 aCommitString ? eCompositionCommit : eCompositionCommitAsIs)) {
2318
0
        MOZ_LOG(gGtkIMLog, LogLevel::Warning,
2319
0
            ("0x%p   DispatchCompositionCommitEvent(), Warning, "
2320
0
             "MaybeDispatchKeyEventAsProcessedByIME() returned false",
2321
0
             this));
2322
0
        mCompositionState = eCompositionState_NotComposing;
2323
0
        return false;
2324
0
    }
2325
0
2326
0
    RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
2327
0
    nsresult rv = dispatcher->BeginNativeInputTransaction();
2328
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
2329
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
2330
0
            ("0x%p   DispatchCompositionCommitEvent(), FAILED, "
2331
0
             "due to BeginNativeInputTransaction() failure",
2332
0
             this));
2333
0
        return false;
2334
0
    }
2335
0
2336
0
    RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
2337
0
2338
0
    // Emulate selection until receiving actual selection range.
2339
0
    mSelection.CollapseTo(
2340
0
        mCompositionStart +
2341
0
            (aCommitString ? aCommitString->Length() :
2342
0
                             mDispatchedCompositionString.Length()),
2343
0
        mSelection.mWritingMode);
2344
0
2345
0
    mCompositionState = eCompositionState_NotComposing;
2346
0
    // Reset dead key sequence too because GTK doesn't support dead key chain
2347
0
    // (i.e., a key press doesn't cause both producing some characters and
2348
0
    // restarting new dead key sequence at one time).  So, committing
2349
0
    // composition means end of a dead key sequence.
2350
0
    mMaybeInDeadKeySequence = false;
2351
0
    mCompositionStart = UINT32_MAX;
2352
0
    mCompositionTargetRange.Clear();
2353
0
    mDispatchedCompositionString.Truncate();
2354
0
    mSelectedStringRemovedByComposition.Truncate();
2355
0
2356
0
    nsEventStatus status;
2357
0
    rv = dispatcher->CommitComposition(status, aCommitString);
2358
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
2359
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
2360
0
            ("0x%p   DispatchCompositionChangeEvent(), FAILED, "
2361
0
             "due to CommitComposition() failure",
2362
0
             this));
2363
0
        return false;
2364
0
    }
2365
0
2366
0
    if (lastFocusedWindow->IsDestroyed() ||
2367
0
        lastFocusedWindow != mLastFocusedWindow) {
2368
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
2369
0
            ("0x%p   DispatchCompositionCommitEvent(), FAILED, "
2370
0
             "the focused widget was destroyed/changed by "
2371
0
             "compositioncommit event",
2372
0
             this));
2373
0
        return false;
2374
0
    }
2375
0
2376
0
    return true;
2377
0
}
2378
2379
already_AddRefed<TextRangeArray>
2380
IMContextWrapper::CreateTextRangeArray(GtkIMContext* aContext,
2381
                                       const nsAString& aCompositionString)
2382
0
{
2383
0
    MOZ_LOG(gGtkIMLog, LogLevel::Info,
2384
0
        ("0x%p CreateTextRangeArray(aContext=0x%p, "
2385
0
         "aCompositionString=\"%s\" (Length()=%u))",
2386
0
         this, aContext, NS_ConvertUTF16toUTF8(aCompositionString).get(),
2387
0
         aCompositionString.Length()));
2388
0
2389
0
    RefPtr<TextRangeArray> textRangeArray = new TextRangeArray();
2390
0
2391
0
    gchar *preedit_string;
2392
0
    gint cursor_pos_in_chars;
2393
0
    PangoAttrList *feedback_list;
2394
0
    gtk_im_context_get_preedit_string(aContext, &preedit_string,
2395
0
                                      &feedback_list, &cursor_pos_in_chars);
2396
0
    if (!preedit_string || !*preedit_string) {
2397
0
        if (!aCompositionString.IsEmpty()) {
2398
0
            MOZ_LOG(gGtkIMLog, LogLevel::Error,
2399
0
                ("0x%p   CreateTextRangeArray(), FAILED, due to "
2400
0
                 "preedit_string is null",
2401
0
                 this));
2402
0
        }
2403
0
        pango_attr_list_unref(feedback_list);
2404
0
        g_free(preedit_string);
2405
0
        return textRangeArray.forget();
2406
0
    }
2407
0
2408
0
    // Convert caret offset from offset in characters to offset in UTF-16
2409
0
    // string.  If we couldn't proper offset in UTF-16 string, we should
2410
0
    // assume that the caret is at the end of the composition string.
2411
0
    uint32_t caretOffsetInUTF16 = aCompositionString.Length();
2412
0
    if (NS_WARN_IF(cursor_pos_in_chars < 0)) {
2413
0
        // Note that this case is undocumented.  We should assume that the
2414
0
        // caret is at the end of the composition string.
2415
0
    } else if (cursor_pos_in_chars == 0) {
2416
0
        caretOffsetInUTF16 = 0;
2417
0
    } else {
2418
0
        gchar* charAfterCaret =
2419
0
            g_utf8_offset_to_pointer(preedit_string, cursor_pos_in_chars);
2420
0
        if (NS_WARN_IF(!charAfterCaret)) {
2421
0
            MOZ_LOG(gGtkIMLog, LogLevel::Warning,
2422
0
                ("0x%p   CreateTextRangeArray(), failed to get UTF-8 "
2423
0
                 "string before the caret (cursor_pos_in_chars=%d)",
2424
0
                 this, cursor_pos_in_chars));
2425
0
        } else {
2426
0
            glong caretOffset = 0;
2427
0
            gunichar2* utf16StrBeforeCaret =
2428
0
                g_utf8_to_utf16(preedit_string, charAfterCaret - preedit_string,
2429
0
                                nullptr, &caretOffset, nullptr);
2430
0
            if (NS_WARN_IF(!utf16StrBeforeCaret) ||
2431
0
                NS_WARN_IF(caretOffset < 0)) {
2432
0
                MOZ_LOG(gGtkIMLog, LogLevel::Warning,
2433
0
                    ("0x%p   CreateTextRangeArray(), WARNING, failed to "
2434
0
                     "convert to UTF-16 string before the caret "
2435
0
                     "(cursor_pos_in_chars=%d, caretOffset=%ld)",
2436
0
                     this, cursor_pos_in_chars, caretOffset));
2437
0
            } else {
2438
0
                caretOffsetInUTF16 = static_cast<uint32_t>(caretOffset);
2439
0
                uint32_t compositionStringLength = aCompositionString.Length();
2440
0
                if (NS_WARN_IF(caretOffsetInUTF16 > compositionStringLength)) {
2441
0
                    MOZ_LOG(gGtkIMLog, LogLevel::Warning,
2442
0
                        ("0x%p   CreateTextRangeArray(), WARNING, "
2443
0
                         "caretOffsetInUTF16=%u is larger than "
2444
0
                         "compositionStringLength=%u",
2445
0
                         this, caretOffsetInUTF16, compositionStringLength));
2446
0
                    caretOffsetInUTF16 = compositionStringLength;
2447
0
                }
2448
0
            }
2449
0
            if (utf16StrBeforeCaret) {
2450
0
                g_free(utf16StrBeforeCaret);
2451
0
            }
2452
0
        }
2453
0
    }
2454
0
2455
0
    PangoAttrIterator* iter;
2456
0
    iter = pango_attr_list_get_iterator(feedback_list);
2457
0
    if (!iter) {
2458
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
2459
0
            ("0x%p   CreateTextRangeArray(), FAILED, iterator couldn't "
2460
0
             "be allocated",
2461
0
             this));
2462
0
        pango_attr_list_unref(feedback_list);
2463
0
        g_free(preedit_string);
2464
0
        return textRangeArray.forget();
2465
0
    }
2466
0
2467
0
    uint32_t minOffsetOfClauses = aCompositionString.Length();
2468
0
    do {
2469
0
        TextRange range;
2470
0
        if (!SetTextRange(iter, preedit_string, caretOffsetInUTF16, range)) {
2471
0
            continue;
2472
0
        }
2473
0
        MOZ_ASSERT(range.Length());
2474
0
        minOffsetOfClauses = std::min(minOffsetOfClauses, range.mStartOffset);
2475
0
        textRangeArray->AppendElement(range);
2476
0
    } while (pango_attr_iterator_next(iter));
2477
0
2478
0
    // If the IME doesn't define clause from the start of the composition,
2479
0
    // we should insert dummy clause information since TextRangeArray assumes
2480
0
    // that there must be a clause whose start is 0 when there is one or
2481
0
    // more clauses.
2482
0
    if (minOffsetOfClauses) {
2483
0
        TextRange dummyClause;
2484
0
        dummyClause.mStartOffset = 0;
2485
0
        dummyClause.mEndOffset = minOffsetOfClauses;
2486
0
        dummyClause.mRangeType = TextRangeType::eRawClause;
2487
0
        textRangeArray->InsertElementAt(0, dummyClause);
2488
0
        MOZ_LOG(gGtkIMLog, LogLevel::Warning,
2489
0
             ("0x%p   CreateTextRangeArray(), inserting a dummy clause "
2490
0
              "at the beginning of the composition string mStartOffset=%u, "
2491
0
              "mEndOffset=%u, mRangeType=%s",
2492
0
              this, dummyClause.mStartOffset, dummyClause.mEndOffset,
2493
0
              ToChar(dummyClause.mRangeType)));
2494
0
    }
2495
0
2496
0
    TextRange range;
2497
0
    range.mStartOffset = range.mEndOffset = caretOffsetInUTF16;
2498
0
    range.mRangeType = TextRangeType::eCaret;
2499
0
    textRangeArray->AppendElement(range);
2500
0
    MOZ_LOG(gGtkIMLog, LogLevel::Debug,
2501
0
        ("0x%p   CreateTextRangeArray(), mStartOffset=%u, "
2502
0
         "mEndOffset=%u, mRangeType=%s",
2503
0
         this, range.mStartOffset, range.mEndOffset,
2504
0
         ToChar(range.mRangeType)));
2505
0
2506
0
    pango_attr_iterator_destroy(iter);
2507
0
    pango_attr_list_unref(feedback_list);
2508
0
    g_free(preedit_string);
2509
0
2510
0
    return textRangeArray.forget();
2511
0
}
2512
2513
/* static */
2514
nscolor
2515
IMContextWrapper::ToNscolor(PangoAttrColor* aPangoAttrColor)
2516
0
{
2517
0
    PangoColor& pangoColor = aPangoAttrColor->color;
2518
0
    uint8_t r = pangoColor.red / 0x100;
2519
0
    uint8_t g = pangoColor.green / 0x100;
2520
0
    uint8_t b = pangoColor.blue / 0x100;
2521
0
    return NS_RGB(r, g, b);
2522
0
}
2523
2524
bool
2525
IMContextWrapper::SetTextRange(PangoAttrIterator* aPangoAttrIter,
2526
                               const gchar* aUTF8CompositionString,
2527
                               uint32_t aUTF16CaretOffset,
2528
                               TextRange& aTextRange) const
2529
0
{
2530
0
    // Set the range offsets in UTF-16 string.
2531
0
    gint utf8ClauseStart, utf8ClauseEnd;
2532
0
    pango_attr_iterator_range(aPangoAttrIter, &utf8ClauseStart, &utf8ClauseEnd);
2533
0
    if (utf8ClauseStart == utf8ClauseEnd) {
2534
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
2535
0
            ("0x%p   SetTextRange(), FAILED, due to collapsed range",
2536
0
             this));
2537
0
        return false;
2538
0
    }
2539
0
2540
0
    if (!utf8ClauseStart) {
2541
0
        aTextRange.mStartOffset = 0;
2542
0
    } else {
2543
0
        glong utf16PreviousClausesLength;
2544
0
        gunichar2* utf16PreviousClausesString =
2545
0
            g_utf8_to_utf16(aUTF8CompositionString, utf8ClauseStart, nullptr,
2546
0
                            &utf16PreviousClausesLength, nullptr);
2547
0
2548
0
        if (NS_WARN_IF(!utf16PreviousClausesString)) {
2549
0
            MOZ_LOG(gGtkIMLog, LogLevel::Error,
2550
0
                ("0x%p   SetTextRange(), FAILED, due to g_utf8_to_utf16() "
2551
0
                 "failure (retrieving previous string of current clause)",
2552
0
                 this));
2553
0
            return false;
2554
0
        }
2555
0
2556
0
        aTextRange.mStartOffset = utf16PreviousClausesLength;
2557
0
        g_free(utf16PreviousClausesString);
2558
0
    }
2559
0
2560
0
    glong utf16CurrentClauseLength;
2561
0
    gunichar2* utf16CurrentClauseString =
2562
0
        g_utf8_to_utf16(aUTF8CompositionString + utf8ClauseStart,
2563
0
                        utf8ClauseEnd - utf8ClauseStart,
2564
0
                        nullptr, &utf16CurrentClauseLength, nullptr);
2565
0
2566
0
    if (NS_WARN_IF(!utf16CurrentClauseString)) {
2567
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
2568
0
            ("0x%p   SetTextRange(), FAILED, due to g_utf8_to_utf16() "
2569
0
             "failure (retrieving current clause)",
2570
0
             this));
2571
0
        return false;
2572
0
    }
2573
0
2574
0
    // iBus Chewing IME tells us that there is an empty clause at the end of
2575
0
    // the composition string but we should ignore it since our code doesn't
2576
0
    // assume that there is an empty clause.
2577
0
    if (!utf16CurrentClauseLength) {
2578
0
        MOZ_LOG(gGtkIMLog, LogLevel::Warning,
2579
0
            ("0x%p   SetTextRange(), FAILED, due to current clause length "
2580
0
             "is 0",
2581
0
             this));
2582
0
        return false;
2583
0
    }
2584
0
2585
0
    aTextRange.mEndOffset = aTextRange.mStartOffset + utf16CurrentClauseLength;
2586
0
    g_free(utf16CurrentClauseString);
2587
0
    utf16CurrentClauseString = nullptr;
2588
0
2589
0
    // Set styles
2590
0
    TextRangeStyle& style = aTextRange.mRangeStyle;
2591
0
2592
0
    // Underline
2593
0
    PangoAttrInt* attrUnderline =
2594
0
        reinterpret_cast<PangoAttrInt*>(
2595
0
            pango_attr_iterator_get(aPangoAttrIter, PANGO_ATTR_UNDERLINE));
2596
0
    if (attrUnderline) {
2597
0
        switch (attrUnderline->value) {
2598
0
            case PANGO_UNDERLINE_NONE:
2599
0
                style.mLineStyle = TextRangeStyle::LINESTYLE_NONE;
2600
0
                break;
2601
0
            case PANGO_UNDERLINE_DOUBLE:
2602
0
                style.mLineStyle = TextRangeStyle::LINESTYLE_DOUBLE;
2603
0
                break;
2604
0
            case PANGO_UNDERLINE_ERROR:
2605
0
                style.mLineStyle = TextRangeStyle::LINESTYLE_WAVY;
2606
0
                break;
2607
0
            case PANGO_UNDERLINE_SINGLE:
2608
0
            case PANGO_UNDERLINE_LOW:
2609
0
                style.mLineStyle = TextRangeStyle::LINESTYLE_SOLID;
2610
0
                break;
2611
0
            default:
2612
0
                MOZ_LOG(gGtkIMLog, LogLevel::Warning,
2613
0
                    ("0x%p   SetTextRange(), retrieved unknown underline "
2614
0
                     "style: %d",
2615
0
                     this, attrUnderline->value));
2616
0
                style.mLineStyle = TextRangeStyle::LINESTYLE_SOLID;
2617
0
                break;
2618
0
        }
2619
0
        style.mDefinedStyles |= TextRangeStyle::DEFINED_LINESTYLE;
2620
0
2621
0
        // Underline color
2622
0
        PangoAttrColor* attrUnderlineColor =
2623
0
            reinterpret_cast<PangoAttrColor*>(
2624
0
                pango_attr_iterator_get(aPangoAttrIter,
2625
0
                                        PANGO_ATTR_UNDERLINE_COLOR));
2626
0
        if (attrUnderlineColor) {
2627
0
            style.mUnderlineColor = ToNscolor(attrUnderlineColor);
2628
0
            style.mDefinedStyles |= TextRangeStyle::DEFINED_UNDERLINE_COLOR;
2629
0
        }
2630
0
    } else {
2631
0
        style.mLineStyle = TextRangeStyle::LINESTYLE_NONE;
2632
0
        style.mDefinedStyles |= TextRangeStyle::DEFINED_LINESTYLE;
2633
0
    }
2634
0
2635
0
    // Don't set colors if they are not specified.  They should be computed by
2636
0
    // textframe if only one of the colors are specified.
2637
0
2638
0
    // Foreground color (text color)
2639
0
    PangoAttrColor* attrForeground =
2640
0
        reinterpret_cast<PangoAttrColor*>(
2641
0
            pango_attr_iterator_get(aPangoAttrIter, PANGO_ATTR_FOREGROUND));
2642
0
    if (attrForeground) {
2643
0
        style.mForegroundColor = ToNscolor(attrForeground);
2644
0
        style.mDefinedStyles |= TextRangeStyle::DEFINED_FOREGROUND_COLOR;
2645
0
    }
2646
0
2647
0
    // Background color
2648
0
    PangoAttrColor* attrBackground =
2649
0
        reinterpret_cast<PangoAttrColor*>(
2650
0
            pango_attr_iterator_get(aPangoAttrIter, PANGO_ATTR_BACKGROUND));
2651
0
    if (attrBackground) {
2652
0
        style.mBackgroundColor = ToNscolor(attrBackground);
2653
0
        style.mDefinedStyles |= TextRangeStyle::DEFINED_BACKGROUND_COLOR;
2654
0
    }
2655
0
2656
0
    /**
2657
0
     * We need to judge the meaning of the clause for a11y.  Before we support
2658
0
     * IME specific composition string style, we used following rules:
2659
0
     *
2660
0
     *   1: If attrUnderline and attrForground are specified, we assumed the
2661
0
     *      clause is TextRangeType::eSelectedClause.
2662
0
     *   2: If only attrUnderline is specified, we assumed the clause is
2663
0
     *      TextRangeType::eConvertedClause.
2664
0
     *   3: If only attrForground is specified, we assumed the clause is
2665
0
     *      TextRangeType::eSelectedRawClause.
2666
0
     *   4: If neither attrUnderline nor attrForeground is specified, we assumed
2667
0
     *      the clause is TextRangeType::eRawClause.
2668
0
     *
2669
0
     * However, this rules are odd since there can be two or more selected
2670
0
     * clauses.  Additionally, our old rules caused that IME developers/users
2671
0
     * cannot specify composition string style as they want.
2672
0
     *
2673
0
     * So, we shouldn't guess the meaning from its visual style.
2674
0
     */
2675
0
2676
0
    if (!attrUnderline && !attrForeground && !attrBackground) {
2677
0
        MOZ_LOG(gGtkIMLog, LogLevel::Warning,
2678
0
            ("0x%p   SetTextRange(), FAILED, due to no attr, "
2679
0
             "aTextRange= { mStartOffset=%u, mEndOffset=%u }",
2680
0
             this, aTextRange.mStartOffset, aTextRange.mEndOffset));
2681
0
        return false;
2682
0
    }
2683
0
2684
0
    // If the range covers whole of composition string and the caret is at
2685
0
    // the end of the composition string, the range is probably not converted.
2686
0
    if (!utf8ClauseStart &&
2687
0
        utf8ClauseEnd == static_cast<gint>(strlen(aUTF8CompositionString)) &&
2688
0
        aTextRange.mEndOffset == aUTF16CaretOffset) {
2689
0
        aTextRange.mRangeType = TextRangeType::eRawClause;
2690
0
    }
2691
0
    // Typically, the caret is set at the start of the selected clause.
2692
0
    // So, if the caret is in the clause, we can assume that the clause is
2693
0
    // selected.
2694
0
    else if (aTextRange.mStartOffset <= aUTF16CaretOffset &&
2695
0
             aTextRange.mEndOffset > aUTF16CaretOffset) {
2696
0
        aTextRange.mRangeType = TextRangeType::eSelectedClause;
2697
0
    }
2698
0
    // Otherwise, we should assume that the clause is converted but not
2699
0
    // selected.
2700
0
    else {
2701
0
        aTextRange.mRangeType = TextRangeType::eConvertedClause;
2702
0
    }
2703
0
2704
0
    MOZ_LOG(gGtkIMLog, LogLevel::Debug,
2705
0
        ("0x%p   SetTextRange(), succeeded, aTextRange= { "
2706
0
         "mStartOffset=%u, mEndOffset=%u, mRangeType=%s, mRangeStyle=%s }",
2707
0
         this, aTextRange.mStartOffset, aTextRange.mEndOffset,
2708
0
         ToChar(aTextRange.mRangeType),
2709
0
         GetTextRangeStyleText(aTextRange.mRangeStyle).get()));
2710
0
2711
0
    return true;
2712
0
}
2713
2714
void
2715
IMContextWrapper::SetCursorPosition(GtkIMContext* aContext)
2716
0
{
2717
0
    MOZ_LOG(gGtkIMLog, LogLevel::Info,
2718
0
        ("0x%p SetCursorPosition(aContext=0x%p), "
2719
0
         "mCompositionTargetRange={ mOffset=%u, mLength=%u }"
2720
0
         "mSelection={ mOffset=%u, Length()=%u, mWritingMode=%s }",
2721
0
         this, aContext, mCompositionTargetRange.mOffset,
2722
0
         mCompositionTargetRange.mLength,
2723
0
         mSelection.mOffset, mSelection.Length(),
2724
0
         GetWritingModeName(mSelection.mWritingMode).get()));
2725
0
2726
0
    bool useCaret = false;
2727
0
    if (!mCompositionTargetRange.IsValid()) {
2728
0
        if (!mSelection.IsValid()) {
2729
0
            MOZ_LOG(gGtkIMLog, LogLevel::Error,
2730
0
                ("0x%p   SetCursorPosition(), FAILED, "
2731
0
                 "mCompositionTargetRange and mSelection are invalid",
2732
0
                 this));
2733
0
            return;
2734
0
        }
2735
0
        useCaret = true;
2736
0
    }
2737
0
2738
0
    if (!mLastFocusedWindow) {
2739
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
2740
0
            ("0x%p   SetCursorPosition(), FAILED, due to no focused "
2741
0
             "window",
2742
0
             this));
2743
0
        return;
2744
0
    }
2745
0
2746
0
    if (MOZ_UNLIKELY(!aContext)) {
2747
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
2748
0
            ("0x%p   SetCursorPosition(), FAILED, due to no context",
2749
0
             this));
2750
0
        return;
2751
0
    }
2752
0
2753
0
    WidgetQueryContentEvent charRect(true,
2754
0
                                     useCaret ? eQueryCaretRect :
2755
0
                                                eQueryTextRect,
2756
0
                                     mLastFocusedWindow);
2757
0
    if (useCaret) {
2758
0
        charRect.InitForQueryCaretRect(mSelection.mOffset);
2759
0
    } else {
2760
0
        if (mSelection.mWritingMode.IsVertical()) {
2761
0
            // For preventing the candidate window to overlap the target
2762
0
            // clause, we should set fake (typically, very tall) caret rect.
2763
0
            uint32_t length = mCompositionTargetRange.mLength ?
2764
0
                mCompositionTargetRange.mLength : 1;
2765
0
            charRect.InitForQueryTextRect(mCompositionTargetRange.mOffset,
2766
0
                                          length);
2767
0
        } else {
2768
0
            charRect.InitForQueryTextRect(mCompositionTargetRange.mOffset, 1);
2769
0
        }
2770
0
    }
2771
0
    InitEvent(charRect);
2772
0
    nsEventStatus status;
2773
0
    mLastFocusedWindow->DispatchEvent(&charRect, status);
2774
0
    if (!charRect.mSucceeded) {
2775
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
2776
0
            ("0x%p   SetCursorPosition(), FAILED, %s was failed",
2777
0
             this, useCaret ? "eQueryCaretRect" : "eQueryTextRect"));
2778
0
        return;
2779
0
    }
2780
0
2781
0
    nsWindow* rootWindow =
2782
0
        static_cast<nsWindow*>(mLastFocusedWindow->GetTopLevelWidget());
2783
0
2784
0
    // Get the position of the rootWindow in screen.
2785
0
    LayoutDeviceIntPoint root = rootWindow->WidgetToScreenOffset();
2786
0
2787
0
    // Get the position of IM context owner window in screen.
2788
0
    LayoutDeviceIntPoint owner = mOwnerWindow->WidgetToScreenOffset();
2789
0
2790
0
    // Compute the caret position in the IM owner window.
2791
0
    LayoutDeviceIntRect rect = charRect.mReply.mRect + root - owner;
2792
0
    rect.width = 0;
2793
0
    GdkRectangle area = rootWindow->DevicePixelsToGdkRectRoundOut(rect);
2794
0
2795
0
    gtk_im_context_set_cursor_location(aContext, &area);
2796
0
}
2797
2798
nsresult
2799
IMContextWrapper::GetCurrentParagraph(nsAString& aText,
2800
                                      uint32_t& aCursorPos)
2801
0
{
2802
0
    MOZ_LOG(gGtkIMLog, LogLevel::Info,
2803
0
        ("0x%p GetCurrentParagraph(), mCompositionState=%s",
2804
0
         this, GetCompositionStateName()));
2805
0
2806
0
    if (!mLastFocusedWindow) {
2807
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
2808
0
            ("0x%p   GetCurrentParagraph(), FAILED, there are no "
2809
0
             "focused window in this module",
2810
0
             this));
2811
0
        return NS_ERROR_NULL_POINTER;
2812
0
    }
2813
0
2814
0
    nsEventStatus status;
2815
0
2816
0
    uint32_t selOffset = mCompositionStart;
2817
0
    uint32_t selLength = mSelectedStringRemovedByComposition.Length();
2818
0
2819
0
    // If focused editor doesn't have composition string, we should use
2820
0
    // current selection.
2821
0
    if (!EditorHasCompositionString()) {
2822
0
        // Query cursor position & selection
2823
0
        if (NS_WARN_IF(!EnsureToCacheSelection())) {
2824
0
            MOZ_LOG(gGtkIMLog, LogLevel::Error,
2825
0
                ("0x%p   GetCurrentParagraph(), FAILED, due to no "
2826
0
                 "valid selection information",
2827
0
                 this));
2828
0
            return NS_ERROR_FAILURE;
2829
0
        }
2830
0
2831
0
        selOffset = mSelection.mOffset;
2832
0
        selLength = mSelection.Length();
2833
0
    }
2834
0
2835
0
    MOZ_LOG(gGtkIMLog, LogLevel::Debug,
2836
0
        ("0x%p   GetCurrentParagraph(), selOffset=%u, selLength=%u",
2837
0
         this, selOffset, selLength));
2838
0
2839
0
    // XXX nsString::Find and nsString::RFind take int32_t for offset, so,
2840
0
    //     we cannot support this request when the current offset is larger
2841
0
    //     than INT32_MAX.
2842
0
    if (selOffset > INT32_MAX || selLength > INT32_MAX ||
2843
0
        selOffset + selLength > INT32_MAX) {
2844
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
2845
0
            ("0x%p   GetCurrentParagraph(), FAILED, The selection is "
2846
0
             "out of range",
2847
0
             this));
2848
0
        return NS_ERROR_FAILURE;
2849
0
    }
2850
0
2851
0
    // Get all text contents of the focused editor
2852
0
    WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
2853
0
                                                  mLastFocusedWindow);
2854
0
    queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
2855
0
    mLastFocusedWindow->DispatchEvent(&queryTextContentEvent, status);
2856
0
    NS_ENSURE_TRUE(queryTextContentEvent.mSucceeded, NS_ERROR_FAILURE);
2857
0
2858
0
    nsAutoString textContent(queryTextContentEvent.mReply.mString);
2859
0
    if (selOffset + selLength > textContent.Length()) {
2860
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
2861
0
            ("0x%p   GetCurrentParagraph(), FAILED, The selection is "
2862
0
             "invalid, textContent.Length()=%u",
2863
0
             this, textContent.Length()));
2864
0
        return NS_ERROR_FAILURE;
2865
0
    }
2866
0
2867
0
    // Remove composing string and restore the selected string because
2868
0
    // GtkEntry doesn't remove selected string until committing, however,
2869
0
    // our editor does it.  We should emulate the behavior for IME.
2870
0
    if (EditorHasCompositionString() &&
2871
0
        mDispatchedCompositionString != mSelectedStringRemovedByComposition) {
2872
0
        textContent.Replace(mCompositionStart,
2873
0
            mDispatchedCompositionString.Length(),
2874
0
            mSelectedStringRemovedByComposition);
2875
0
    }
2876
0
2877
0
    // Get only the focused paragraph, by looking for newlines
2878
0
    int32_t parStart = (selOffset == 0) ? 0 :
2879
0
        textContent.RFind("\n", false, selOffset - 1, -1) + 1;
2880
0
    int32_t parEnd = textContent.Find("\n", false, selOffset + selLength, -1);
2881
0
    if (parEnd < 0) {
2882
0
        parEnd = textContent.Length();
2883
0
    }
2884
0
    aText = nsDependentSubstring(textContent, parStart, parEnd - parStart);
2885
0
    aCursorPos = selOffset - uint32_t(parStart);
2886
0
2887
0
    MOZ_LOG(gGtkIMLog, LogLevel::Debug,
2888
0
        ("0x%p   GetCurrentParagraph(), succeeded, aText=%s, "
2889
0
         "aText.Length()=%u, aCursorPos=%u",
2890
0
         this, NS_ConvertUTF16toUTF8(aText).get(),
2891
0
         aText.Length(), aCursorPos));
2892
0
2893
0
    return NS_OK;
2894
0
}
2895
2896
nsresult
2897
IMContextWrapper::DeleteText(GtkIMContext* aContext,
2898
                             int32_t aOffset,
2899
                             uint32_t aNChars)
2900
0
{
2901
0
    MOZ_LOG(gGtkIMLog, LogLevel::Info,
2902
0
        ("0x%p DeleteText(aContext=0x%p, aOffset=%d, aNChars=%u), "
2903
0
         "mCompositionState=%s",
2904
0
         this, aContext, aOffset, aNChars, GetCompositionStateName()));
2905
0
2906
0
    if (!mLastFocusedWindow) {
2907
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
2908
0
            ("0x%p   DeleteText(), FAILED, there are no focused window "
2909
0
             "in this module",
2910
0
             this));
2911
0
        return NS_ERROR_NULL_POINTER;
2912
0
    }
2913
0
2914
0
    if (!aNChars) {
2915
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
2916
0
            ("0x%p   DeleteText(), FAILED, aNChars must not be zero",
2917
0
             this));
2918
0
        return NS_ERROR_INVALID_ARG;
2919
0
    }
2920
0
2921
0
    RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
2922
0
    nsEventStatus status;
2923
0
2924
0
    // First, we should cancel current composition because editor cannot
2925
0
    // handle changing selection and deleting text.
2926
0
    uint32_t selOffset;
2927
0
    bool wasComposing = IsComposing();
2928
0
    bool editorHadCompositionString = EditorHasCompositionString();
2929
0
    if (wasComposing) {
2930
0
        selOffset = mCompositionStart;
2931
0
        if (!DispatchCompositionCommitEvent(aContext,
2932
0
                 &mSelectedStringRemovedByComposition)) {
2933
0
            MOZ_LOG(gGtkIMLog, LogLevel::Error,
2934
0
                ("0x%p   DeleteText(), FAILED, quitting from DeletText",
2935
0
                 this));
2936
0
            return NS_ERROR_FAILURE;
2937
0
        }
2938
0
    } else {
2939
0
        if (NS_WARN_IF(!EnsureToCacheSelection())) {
2940
0
            MOZ_LOG(gGtkIMLog, LogLevel::Error,
2941
0
                ("0x%p   DeleteText(), FAILED, due to no valid selection "
2942
0
                 "information",
2943
0
                 this));
2944
0
            return NS_ERROR_FAILURE;
2945
0
        }
2946
0
        selOffset = mSelection.mOffset;
2947
0
    }
2948
0
2949
0
    // Get all text contents of the focused editor
2950
0
    WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
2951
0
                                                  mLastFocusedWindow);
2952
0
    queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
2953
0
    mLastFocusedWindow->DispatchEvent(&queryTextContentEvent, status);
2954
0
    NS_ENSURE_TRUE(queryTextContentEvent.mSucceeded, NS_ERROR_FAILURE);
2955
0
    if (queryTextContentEvent.mReply.mString.IsEmpty()) {
2956
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
2957
0
            ("0x%p   DeleteText(), FAILED, there is no contents",
2958
0
             this));
2959
0
        return NS_ERROR_FAILURE;
2960
0
    }
2961
0
2962
0
    NS_ConvertUTF16toUTF8 utf8Str(
2963
0
        nsDependentSubstring(queryTextContentEvent.mReply.mString,
2964
0
                             0, selOffset));
2965
0
    glong offsetInUTF8Characters =
2966
0
        g_utf8_strlen(utf8Str.get(), utf8Str.Length()) + aOffset;
2967
0
    if (offsetInUTF8Characters < 0) {
2968
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
2969
0
            ("0x%p   DeleteText(), FAILED, aOffset is too small for "
2970
0
             "current cursor pos (computed offset: %ld)",
2971
0
             this, offsetInUTF8Characters));
2972
0
        return NS_ERROR_FAILURE;
2973
0
    }
2974
0
2975
0
    AppendUTF16toUTF8(
2976
0
        nsDependentSubstring(queryTextContentEvent.mReply.mString, selOffset),
2977
0
        utf8Str);
2978
0
    glong countOfCharactersInUTF8 =
2979
0
        g_utf8_strlen(utf8Str.get(), utf8Str.Length());
2980
0
    glong endInUTF8Characters =
2981
0
        offsetInUTF8Characters + aNChars;
2982
0
    if (countOfCharactersInUTF8 < endInUTF8Characters) {
2983
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
2984
0
            ("0x%p   DeleteText(), FAILED, aNChars is too large for "
2985
0
             "current contents (content length: %ld, computed end offset: %ld)",
2986
0
             this, countOfCharactersInUTF8, endInUTF8Characters));
2987
0
        return NS_ERROR_FAILURE;
2988
0
    }
2989
0
2990
0
    gchar* charAtOffset =
2991
0
        g_utf8_offset_to_pointer(utf8Str.get(), offsetInUTF8Characters);
2992
0
    gchar* charAtEnd =
2993
0
        g_utf8_offset_to_pointer(utf8Str.get(), endInUTF8Characters);
2994
0
2995
0
    // Set selection to delete
2996
0
    WidgetSelectionEvent selectionEvent(true, eSetSelection,
2997
0
                                        mLastFocusedWindow);
2998
0
2999
0
    nsDependentCSubstring utf8StrBeforeOffset(utf8Str, 0,
3000
0
                                              charAtOffset - utf8Str.get());
3001
0
    selectionEvent.mOffset =
3002
0
        NS_ConvertUTF8toUTF16(utf8StrBeforeOffset).Length();
3003
0
3004
0
    nsDependentCSubstring utf8DeletingStr(utf8Str,
3005
0
                                          utf8StrBeforeOffset.Length(),
3006
0
                                          charAtEnd - charAtOffset);
3007
0
    selectionEvent.mLength =
3008
0
        NS_ConvertUTF8toUTF16(utf8DeletingStr).Length();
3009
0
3010
0
    selectionEvent.mReversed = false;
3011
0
    selectionEvent.mExpandToClusterBoundary = false;
3012
0
    lastFocusedWindow->DispatchEvent(&selectionEvent, status);
3013
0
3014
0
    if (!selectionEvent.mSucceeded ||
3015
0
        lastFocusedWindow != mLastFocusedWindow ||
3016
0
        lastFocusedWindow->Destroyed()) {
3017
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
3018
0
            ("0x%p   DeleteText(), FAILED, setting selection caused "
3019
0
             "focus change or window destroyed",
3020
0
             this));
3021
0
        return NS_ERROR_FAILURE;
3022
0
    }
3023
0
3024
0
    // If this deleting text caused by a key press, we need to dispatch
3025
0
    // eKeyDown or eKeyUp before dispatching eContentCommandDelete event.
3026
0
    if (!MaybeDispatchKeyEventAsProcessedByIME(eContentCommandDelete)) {
3027
0
        MOZ_LOG(gGtkIMLog, LogLevel::Warning,
3028
0
            ("0x%p   DeleteText(), Warning, "
3029
0
             "MaybeDispatchKeyEventAsProcessedByIME() returned false",
3030
0
             this));
3031
0
        return NS_ERROR_FAILURE;
3032
0
    }
3033
0
3034
0
    // Delete the selection
3035
0
    WidgetContentCommandEvent contentCommandEvent(true, eContentCommandDelete,
3036
0
                                                  mLastFocusedWindow);
3037
0
    mLastFocusedWindow->DispatchEvent(&contentCommandEvent, status);
3038
0
3039
0
    if (!contentCommandEvent.mSucceeded ||
3040
0
        lastFocusedWindow != mLastFocusedWindow ||
3041
0
        lastFocusedWindow->Destroyed()) {
3042
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
3043
0
            ("0x%p   DeleteText(), FAILED, deleting the selection caused "
3044
0
             "focus change or window destroyed",
3045
0
             this));
3046
0
        return NS_ERROR_FAILURE;
3047
0
    }
3048
0
3049
0
    if (!wasComposing) {
3050
0
        return NS_OK;
3051
0
    }
3052
0
3053
0
    // Restore the composition at new caret position.
3054
0
    if (!DispatchCompositionStart(aContext)) {
3055
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
3056
0
            ("0x%p   DeleteText(), FAILED, resterting composition start",
3057
0
             this));
3058
0
        return NS_ERROR_FAILURE;
3059
0
    }
3060
0
3061
0
    if (!editorHadCompositionString) {
3062
0
        return NS_OK;
3063
0
    }
3064
0
3065
0
    nsAutoString compositionString;
3066
0
    GetCompositionString(aContext, compositionString);
3067
0
    if (!DispatchCompositionChangeEvent(aContext, compositionString)) {
3068
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
3069
0
            ("0x%p   DeleteText(), FAILED, restoring composition string",
3070
0
             this));
3071
0
        return NS_ERROR_FAILURE;
3072
0
    }
3073
0
3074
0
    return NS_OK;
3075
0
}
3076
3077
void
3078
IMContextWrapper::InitEvent(WidgetGUIEvent& aEvent)
3079
0
{
3080
0
    aEvent.mTime = PR_Now() / 1000;
3081
0
}
3082
3083
bool
3084
IMContextWrapper::EnsureToCacheSelection(nsAString* aSelectedString)
3085
0
{
3086
0
    if (aSelectedString) {
3087
0
        aSelectedString->Truncate();
3088
0
    }
3089
0
3090
0
    if (mSelection.IsValid()) {
3091
0
       if (aSelectedString) {
3092
0
           *aSelectedString = mSelection.mString;
3093
0
       }
3094
0
       return true;
3095
0
    }
3096
0
3097
0
    if (NS_WARN_IF(!mLastFocusedWindow)) {
3098
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
3099
0
            ("0x%p EnsureToCacheSelection(), FAILED, due to "
3100
0
             "no focused window",
3101
0
             this));
3102
0
        return false;
3103
0
    }
3104
0
3105
0
    nsEventStatus status;
3106
0
    WidgetQueryContentEvent selection(true, eQuerySelectedText,
3107
0
                                      mLastFocusedWindow);
3108
0
    InitEvent(selection);
3109
0
    mLastFocusedWindow->DispatchEvent(&selection, status);
3110
0
    if (NS_WARN_IF(!selection.mSucceeded)) {
3111
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
3112
0
            ("0x%p EnsureToCacheSelection(), FAILED, due to "
3113
0
             "failure of query selection event",
3114
0
             this));
3115
0
        return false;
3116
0
    }
3117
0
3118
0
    mSelection.Assign(selection);
3119
0
    if (!mSelection.IsValid()) {
3120
0
        MOZ_LOG(gGtkIMLog, LogLevel::Error,
3121
0
            ("0x%p EnsureToCacheSelection(), FAILED, due to "
3122
0
             "failure of query selection event (invalid result)",
3123
0
             this));
3124
0
        return false;
3125
0
    }
3126
0
3127
0
    if (!mSelection.Collapsed() && aSelectedString) {
3128
0
        aSelectedString->Assign(selection.mReply.mString);
3129
0
    }
3130
0
3131
0
    MOZ_LOG(gGtkIMLog, LogLevel::Debug,
3132
0
        ("0x%p EnsureToCacheSelection(), Succeeded, mSelection="
3133
0
         "{ mOffset=%u, Length()=%u, mWritingMode=%s }",
3134
0
         this, mSelection.mOffset, mSelection.Length(),
3135
0
         GetWritingModeName(mSelection.mWritingMode).get()));
3136
0
    return true;
3137
0
}
3138
3139
/******************************************************************************
3140
 * IMContextWrapper::Selection
3141
 ******************************************************************************/
3142
3143
void
3144
IMContextWrapper::Selection::Assign(const IMENotification& aIMENotification)
3145
0
{
3146
0
    MOZ_ASSERT(aIMENotification.mMessage == NOTIFY_IME_OF_SELECTION_CHANGE);
3147
0
    mString = aIMENotification.mSelectionChangeData.String();
3148
0
    mOffset = aIMENotification.mSelectionChangeData.mOffset;
3149
0
    mWritingMode = aIMENotification.mSelectionChangeData.GetWritingMode();
3150
0
}
3151
3152
void
3153
IMContextWrapper::Selection::Assign(const WidgetQueryContentEvent& aEvent)
3154
0
{
3155
0
    MOZ_ASSERT(aEvent.mMessage == eQuerySelectedText);
3156
0
    MOZ_ASSERT(aEvent.mSucceeded);
3157
0
    mString = aEvent.mReply.mString.Length();
3158
0
    mOffset = aEvent.mReply.mOffset;
3159
0
    mWritingMode = aEvent.GetWritingMode();
3160
0
}
3161
3162
} // namespace widget
3163
} // namespace mozilla