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