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