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/. */
7 #include "mozilla/Logging.h"
12 #include "IMContextWrapper.h"
13 #include "nsGtkKeyUtils.h"
15 #include "mozilla/AutoRestore.h"
16 #include "mozilla/Likely.h"
17 #include "mozilla/LookAndFeel.h"
18 #include "mozilla/MiscEvents.h"
19 #include "mozilla/Preferences.h"
20 #include "mozilla/StaticPrefs_intl.h"
21 #include "mozilla/Telemetry.h"
22 #include "mozilla/TextEventDispatcher.h"
23 #include "mozilla/TextEvents.h"
24 #include "mozilla/ToString.h"
25 #include "mozilla/WritingModes.h"
27 // For collecting other people's log, tell `MOZ_LOG=IMEHandler:4,sync`
28 // rather than `MOZ_LOG=IMEHandler:5,sync` since using `5` may create too
30 // Therefore you shouldn't use `LogLevel::Verbose` for logging usual behavior.
31 mozilla::LazyLogModule
gIMELog("IMEHandler");
36 static inline const char* ToChar(bool aBool
) {
37 return aBool
? "true" : "false";
40 static const char* GetEventType(GdkEventKey
* aKeyEvent
) {
41 switch (aKeyEvent
->type
) {
43 return "GDK_KEY_PRESS";
45 return "GDK_KEY_RELEASE";
51 class GetEventStateName
: public nsAutoCString
{
53 explicit GetEventStateName(guint aState
,
54 IMContextWrapper::IMContextID aIMContextID
=
55 IMContextWrapper::IMContextID::Unknown
) {
56 if (aState
& GDK_SHIFT_MASK
) {
57 AppendModifier("shift");
59 if (aState
& GDK_CONTROL_MASK
) {
60 AppendModifier("control");
62 if (aState
& GDK_MOD1_MASK
) {
63 AppendModifier("mod1");
65 if (aState
& GDK_MOD2_MASK
) {
66 AppendModifier("mod2");
68 if (aState
& GDK_MOD3_MASK
) {
69 AppendModifier("mod3");
71 if (aState
& GDK_MOD4_MASK
) {
72 AppendModifier("mod4");
74 if (aState
& GDK_MOD4_MASK
) {
75 AppendModifier("mod5");
77 if (aState
& GDK_MOD4_MASK
) {
78 AppendModifier("mod5");
80 switch (aIMContextID
) {
81 case IMContextWrapper::IMContextID::IBus
:
82 static const guint IBUS_HANDLED_MASK
= 1 << 24;
83 static const guint IBUS_IGNORED_MASK
= 1 << 25;
84 if (aState
& IBUS_HANDLED_MASK
) {
85 AppendModifier("IBUS_HANDLED_MASK");
87 if (aState
& IBUS_IGNORED_MASK
) {
88 AppendModifier("IBUS_IGNORED_MASK");
91 case IMContextWrapper::IMContextID::Fcitx
:
92 case IMContextWrapper::IMContextID::Fcitx5
:
93 static const guint FcitxKeyState_HandledMask
= 1 << 24;
94 static const guint FcitxKeyState_IgnoredMask
= 1 << 25;
95 if (aState
& FcitxKeyState_HandledMask
) {
96 AppendModifier("FcitxKeyState_HandledMask");
98 if (aState
& FcitxKeyState_IgnoredMask
) {
99 AppendModifier("FcitxKeyState_IgnoredMask");
108 void AppendModifier(const char* aModifierName
) {
110 AppendLiteral(" + ");
112 Append(aModifierName
);
116 class GetTextRangeStyleText final
: public nsAutoCString
{
118 explicit GetTextRangeStyleText(const TextRangeStyle
& aStyle
) {
119 if (!aStyle
.IsDefined()) {
120 AssignLiteral("{ IsDefined()=false }");
124 if (aStyle
.IsLineStyleDefined()) {
125 AppendLiteral("{ mLineStyle=");
126 AppendLineStyle(aStyle
.mLineStyle
);
127 if (aStyle
.IsUnderlineColorDefined()) {
128 AppendLiteral(", mUnderlineColor=");
129 AppendColor(aStyle
.mUnderlineColor
);
131 AppendLiteral(", IsUnderlineColorDefined=false");
134 AppendLiteral("{ IsLineStyleDefined()=false");
137 if (aStyle
.IsForegroundColorDefined()) {
138 AppendLiteral(", mForegroundColor=");
139 AppendColor(aStyle
.mForegroundColor
);
141 AppendLiteral(", IsForegroundColorDefined()=false");
144 if (aStyle
.IsBackgroundColorDefined()) {
145 AppendLiteral(", mBackgroundColor=");
146 AppendColor(aStyle
.mBackgroundColor
);
148 AppendLiteral(", IsBackgroundColorDefined()=false");
153 void AppendLineStyle(TextRangeStyle::LineStyle aLineStyle
) {
154 switch (aLineStyle
) {
155 case TextRangeStyle::LineStyle::None
:
156 AppendLiteral("LineStyle::None");
158 case TextRangeStyle::LineStyle::Solid
:
159 AppendLiteral("LineStyle::Solid");
161 case TextRangeStyle::LineStyle::Dotted
:
162 AppendLiteral("LineStyle::Dotted");
164 case TextRangeStyle::LineStyle::Dashed
:
165 AppendLiteral("LineStyle::Dashed");
167 case TextRangeStyle::LineStyle::Double
:
168 AppendLiteral("LineStyle::Double");
170 case TextRangeStyle::LineStyle::Wavy
:
171 AppendLiteral("LineStyle::Wavy");
174 AppendPrintf("Invalid(0x%02X)",
175 static_cast<TextRangeStyle::LineStyleType
>(aLineStyle
));
179 void AppendColor(nscolor aColor
) {
180 AppendPrintf("{ R=0x%02X, G=0x%02X, B=0x%02X, A=0x%02X }", NS_GET_R(aColor
),
181 NS_GET_G(aColor
), NS_GET_B(aColor
), NS_GET_A(aColor
));
183 virtual ~GetTextRangeStyleText() = default;
186 const static bool kUseSimpleContextDefault
= false;
188 /******************************************************************************
189 * SelectionStyleProvider
191 * IME (e.g., fcitx, ~4.2.8.3) may look up selection colors of widget, which
192 * is related to the window associated with the IM context, to support any
193 * colored widgets. Our editor (like <input type="text">) is rendered as
194 * native GtkTextView as far as possible by default and if editor color is
195 * changed by web apps, nsTextFrame may swap background color of foreground
196 * color of composition string for making composition string is always
197 * visually distinct in normal text.
199 * So, we would like IME to set style of composition string to good colors
200 * in GtkTextView. Therefore, this class overwrites selection colors of
201 * our widget with selection colors of GtkTextView so that it's possible IME
202 * to refer selection colors of GtkTextView via our widget.
203 ******************************************************************************/
205 static Maybe
<nscolor
> GetSystemColor(LookAndFeel::ColorID aId
) {
206 return LookAndFeel::GetColor(aId
, LookAndFeel::ColorScheme::Light
,
207 LookAndFeel::UseStandins::No
);
210 class SelectionStyleProvider final
{
212 static SelectionStyleProvider
* GetExistingInstance() { return sInstance
; }
214 static SelectionStyleProvider
* GetInstance() {
219 sInstance
= new SelectionStyleProvider();
224 static void Shutdown() {
226 g_object_unref(sInstance
->mProvider
);
233 // aGDKWindow is a GTK window which will be associated with an IM context.
234 void AttachTo(GdkWindow
* aGDKWindow
) {
235 GtkWidget
* widget
= nullptr;
236 // gdk_window_get_user_data() typically returns pointer to widget that
237 // window belongs to. If it's widget, fcitx retrieves selection colors
238 // of them. So, we need to overwrite its style.
239 gdk_window_get_user_data(aGDKWindow
, (gpointer
*)&widget
);
240 if (GTK_IS_WIDGET(widget
)) {
241 gtk_style_context_add_provider(gtk_widget_get_style_context(widget
),
242 GTK_STYLE_PROVIDER(mProvider
),
243 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
247 void OnThemeChanged() {
248 // fcitx refers GtkStyle::text[GTK_STATE_SELECTED] and
249 // GtkStyle::bg[GTK_STATE_SELECTED] (although pair of text and *base*
250 // or *fg* and bg is correct). gtk_style_update_from_context() will
251 // set these colors using the widget's GtkStyleContext and so the
252 // colors can be controlled by a ":selected" CSS rule.
253 nsAutoCString
style(":selected{");
254 // FYI: LookAndFeel always returns selection colors of GtkTextView.
255 if (auto selectionForegroundColor
=
256 GetSystemColor(LookAndFeel::ColorID::Highlight
)) {
258 static_cast<double>(NS_GET_A(*selectionForegroundColor
)) / 0xFF;
259 style
.AppendPrintf("color:rgba(%u,%u,%u,",
260 NS_GET_R(*selectionForegroundColor
),
261 NS_GET_G(*selectionForegroundColor
),
262 NS_GET_B(*selectionForegroundColor
));
263 // We can't use AppendPrintf here, because it does locale-specific
264 // formatting of floating-point values.
265 style
.AppendFloat(alpha
);
266 style
.AppendPrintf(");");
268 if (auto selectionBackgroundColor
=
269 GetSystemColor(LookAndFeel::ColorID::Highlighttext
)) {
271 static_cast<double>(NS_GET_A(*selectionBackgroundColor
)) / 0xFF;
272 style
.AppendPrintf("background-color:rgba(%u,%u,%u,",
273 NS_GET_R(*selectionBackgroundColor
),
274 NS_GET_G(*selectionBackgroundColor
),
275 NS_GET_B(*selectionBackgroundColor
));
276 style
.AppendFloat(alpha
);
277 style
.AppendPrintf(");");
279 style
.AppendLiteral("}");
280 gtk_css_provider_load_from_data(mProvider
, style
.get(), -1, nullptr);
284 static SelectionStyleProvider
* sInstance
;
285 static bool sHasShutDown
;
286 GtkCssProvider
* const mProvider
;
288 SelectionStyleProvider() : mProvider(gtk_css_provider_new()) {
293 SelectionStyleProvider
* SelectionStyleProvider::sInstance
= nullptr;
294 bool SelectionStyleProvider::sHasShutDown
= false;
296 /******************************************************************************
298 ******************************************************************************/
300 IMContextWrapper
* IMContextWrapper::sLastFocusedContext
= nullptr;
301 guint16
IMContextWrapper::sWaitingSynthesizedKeyPressHardwareKeyCode
= 0;
302 bool IMContextWrapper::sUseSimpleContext
;
304 NS_IMPL_ISUPPORTS(IMContextWrapper
, TextEventDispatcherListener
,
305 nsISupportsWeakReference
)
307 IMContextWrapper::IMContextWrapper(nsWindow
* aOwnerWindow
)
308 : mOwnerWindow(aOwnerWindow
),
309 mLastFocusedWindow(nullptr),
311 mSimpleContext(nullptr),
312 mDummyContext(nullptr),
313 mComposingContext(nullptr),
314 mCompositionStart(UINT32_MAX
),
315 mProcessingKeyEvent(nullptr),
316 mCompositionState(eCompositionState_NotComposing
),
317 mIMContextID(IMContextID::Unknown
),
318 mFallbackToKeyEvent(false),
319 mKeyboardEventWasDispatched(false),
320 mKeyboardEventWasConsumed(false),
321 mIsDeletingSurrounding(false),
322 mLayoutChanged(false),
323 mSetCursorPositionOnKeyEvent(true),
324 mPendingResettingIMContext(false),
325 mRetrieveSurroundingSignalReceived(false),
326 mMaybeInDeadKeySequence(false),
327 mIsIMInAsyncKeyHandlingMode(false),
328 mSetInputPurposeAndInputHints(false) {
329 static bool sFirstInstance
= true;
330 if (sFirstInstance
) {
331 sFirstInstance
= false;
333 Preferences::GetBool("intl.ime.use_simple_context_on_password_field",
334 kUseSimpleContextDefault
);
339 static bool IsIBusInSyncMode() {
340 // See ibus_im_context_class_init() in client/gtk2/ibusimcontext.c
341 // https://github.com/ibus/ibus/blob/86963f2f94d1e4fc213b01c2bc2ba9dcf4b22219/client/gtk2/ibusimcontext.c#L610
342 const char* env
= PR_GetEnv("IBUS_ENABLE_SYNC_MODE");
344 // See _get_boolean_env() in client/gtk2/ibusimcontext.c
345 // https://github.com/ibus/ibus/blob/86963f2f94d1e4fc213b01c2bc2ba9dcf4b22219/client/gtk2/ibusimcontext.c#L520-L537
349 nsDependentCString
envStr(env
);
350 if (envStr
.IsEmpty() || envStr
.EqualsLiteral("0") ||
351 envStr
.EqualsLiteral("false") || envStr
.EqualsLiteral("False") ||
352 envStr
.EqualsLiteral("FALSE")) {
358 static bool GetFcitxBoolEnv(const char* aEnv
) {
359 // See fcitx_utils_get_boolean_env in src/lib/fcitx-utils/utils.c
360 // https://github.com/fcitx/fcitx/blob/0c87840dc7d9460c2cb5feaeefec299d0d3d62ec/src/lib/fcitx-utils/utils.c#L721-L736
361 const char* env
= PR_GetEnv(aEnv
);
365 nsDependentCString
envStr(env
);
366 if (envStr
.IsEmpty() || envStr
.EqualsLiteral("0") ||
367 envStr
.EqualsLiteral("false")) {
373 static bool IsFcitxInSyncMode() {
374 // See fcitx_im_context_class_init() in src/frontend/gtk2/fcitximcontext.c
375 // https://github.com/fcitx/fcitx/blob/78b98d9230dc9630e99d52e3172bdf440ffd08c4/src/frontend/gtk2/fcitximcontext.c#L395-L398
376 return GetFcitxBoolEnv("IBUS_ENABLE_SYNC_MODE") ||
377 GetFcitxBoolEnv("FCITX_ENABLE_SYNC_MODE");
380 nsDependentCSubstring
IMContextWrapper::GetIMName() const {
381 const char* contextIDChar
=
382 gtk_im_multicontext_get_context_id(GTK_IM_MULTICONTEXT(mContext
));
383 if (!contextIDChar
) {
384 return nsDependentCSubstring();
387 nsDependentCSubstring
im(contextIDChar
, strlen(contextIDChar
));
389 // If the context is XIM, actual engine must be specified with
390 // |XMODIFIERS=@im=foo|.
391 const char* xmodifiersChar
= PR_GetEnv("XMODIFIERS");
392 if (!xmodifiersChar
|| !im
.EqualsLiteral("xim")) {
396 nsDependentCString
xmodifiers(xmodifiersChar
);
397 int32_t atIMValueStart
= xmodifiers
.Find("@im=") + 4;
398 if (atIMValueStart
< 4 ||
399 xmodifiers
.Length() <= static_cast<size_t>(atIMValueStart
)) {
403 int32_t atIMValueEnd
= xmodifiers
.Find("@", atIMValueStart
);
404 if (atIMValueEnd
> atIMValueStart
) {
405 return nsDependentCSubstring(xmodifiersChar
+ atIMValueStart
,
406 atIMValueEnd
- atIMValueStart
);
409 if (atIMValueEnd
== kNotFound
) {
410 return nsDependentCSubstring(xmodifiersChar
+ atIMValueStart
,
411 strlen(xmodifiersChar
) - atIMValueStart
);
417 void IMContextWrapper::Init() {
418 MozContainer
* container
= mOwnerWindow
->GetMozContainer();
419 MOZ_ASSERT(container
, "container is null");
420 GdkWindow
* gdkWindow
= gtk_widget_get_window(GTK_WIDGET(container
));
422 // Overwrite selection colors of the window before associating the window
423 // with IM context since IME may look up selection colors via IM context
424 // to support any colored widgets.
425 SelectionStyleProvider::GetInstance()->AttachTo(gdkWindow
);
427 // NOTE: gtk_im_*_new() abort (kill) the whole process when it fails.
428 // So, we don't need to check the result.
431 mContext
= gtk_im_multicontext_new();
432 gtk_im_context_set_client_window(mContext
, gdkWindow
);
433 g_signal_connect(mContext
, "preedit_changed",
434 G_CALLBACK(IMContextWrapper::OnChangeCompositionCallback
),
436 g_signal_connect(mContext
, "retrieve_surrounding",
437 G_CALLBACK(IMContextWrapper::OnRetrieveSurroundingCallback
),
439 g_signal_connect(mContext
, "delete_surrounding",
440 G_CALLBACK(IMContextWrapper::OnDeleteSurroundingCallback
),
442 g_signal_connect(mContext
, "commit",
443 G_CALLBACK(IMContextWrapper::OnCommitCompositionCallback
),
445 g_signal_connect(mContext
, "preedit_start",
446 G_CALLBACK(IMContextWrapper::OnStartCompositionCallback
),
448 g_signal_connect(mContext
, "preedit_end",
449 G_CALLBACK(IMContextWrapper::OnEndCompositionCallback
),
451 nsDependentCSubstring im
= GetIMName();
452 if (im
.EqualsLiteral("ibus")) {
453 mIMContextID
= IMContextID::IBus
;
454 mIsIMInAsyncKeyHandlingMode
= !IsIBusInSyncMode();
455 // Although ibus has key snooper mode, it's forcibly disabled on Firefox
456 // in default settings by its whitelist since we always send key events
457 // to IME before handling shortcut keys. The whitelist can be
458 // customized with env, IBUS_NO_SNOOPER_APPS, but we don't need to
459 // support such rare cases for reducing maintenance cost.
460 mIsKeySnooped
= false;
461 } else if (im
.EqualsLiteral("fcitx")) {
462 mIMContextID
= IMContextID::Fcitx
;
463 mIsIMInAsyncKeyHandlingMode
= !IsFcitxInSyncMode();
464 // Although Fcitx has key snooper mode similar to ibus, it's also
465 // disabled on Firefox in default settings by its whitelist. The
466 // whitelist can be customized with env, IBUS_NO_SNOOPER_APPS or
467 // FCITX_NO_SNOOPER_APPS, but we don't need to support such rare cases
468 // for reducing maintenance cost.
469 mIsKeySnooped
= false;
470 } else if (im
.EqualsLiteral("fcitx5")) {
471 mIMContextID
= IMContextID::Fcitx5
;
472 mIsIMInAsyncKeyHandlingMode
= true; // does not have sync mode.
473 mIsKeySnooped
= false; // never use key snooper.
474 } else if (im
.EqualsLiteral("uim")) {
475 mIMContextID
= IMContextID::Uim
;
476 mIsIMInAsyncKeyHandlingMode
= false;
477 // We cannot know if uim uses key snooper since it's build option of
478 // uim. Therefore, we need to retrieve the consideration from the
479 // pref for making users and distributions allowed to choose their
482 Preferences::GetBool("intl.ime.hack.uim.using_key_snooper", true);
483 } else if (im
.EqualsLiteral("scim")) {
484 mIMContextID
= IMContextID::Scim
;
485 mIsIMInAsyncKeyHandlingMode
= false;
486 mIsKeySnooped
= false;
487 } else if (im
.EqualsLiteral("iiim")) {
488 mIMContextID
= IMContextID::IIIMF
;
489 mIsIMInAsyncKeyHandlingMode
= false;
490 mIsKeySnooped
= false;
491 } else if (im
.EqualsLiteral("wayland")) {
492 mIMContextID
= IMContextID::Wayland
;
493 mIsIMInAsyncKeyHandlingMode
= false;
494 mIsKeySnooped
= true;
496 mIMContextID
= IMContextID::Unknown
;
497 mIsIMInAsyncKeyHandlingMode
= false;
498 mIsKeySnooped
= false;
502 if (sUseSimpleContext
) {
503 mSimpleContext
= gtk_im_context_simple_new();
504 gtk_im_context_set_client_window(mSimpleContext
, gdkWindow
);
505 g_signal_connect(mSimpleContext
, "preedit_changed",
506 G_CALLBACK(&IMContextWrapper::OnChangeCompositionCallback
),
509 mSimpleContext
, "retrieve_surrounding",
510 G_CALLBACK(&IMContextWrapper::OnRetrieveSurroundingCallback
), this);
511 g_signal_connect(mSimpleContext
, "delete_surrounding",
512 G_CALLBACK(&IMContextWrapper::OnDeleteSurroundingCallback
),
514 g_signal_connect(mSimpleContext
, "commit",
515 G_CALLBACK(&IMContextWrapper::OnCommitCompositionCallback
),
517 g_signal_connect(mSimpleContext
, "preedit_start",
518 G_CALLBACK(IMContextWrapper::OnStartCompositionCallback
),
520 g_signal_connect(mSimpleContext
, "preedit_end",
521 G_CALLBACK(IMContextWrapper::OnEndCompositionCallback
),
526 mDummyContext
= gtk_im_multicontext_new();
527 gtk_im_context_set_client_window(mDummyContext
, gdkWindow
);
529 MOZ_LOG(gIMELog
, LogLevel::Info
,
530 ("0x%p Init(), mOwnerWindow=%p, mContext=%p (im=\"%s\"), "
531 "mIsIMInAsyncKeyHandlingMode=%s, mIsKeySnooped=%s, "
532 "mSimpleContext=%p, mDummyContext=%p, "
533 "gtk_im_multicontext_get_context_id()=\"%s\", "
534 "PR_GetEnv(\"XMODIFIERS\")=\"%s\"",
535 this, mOwnerWindow
, mContext
, nsAutoCString(im
).get(),
536 ToChar(mIsIMInAsyncKeyHandlingMode
), ToChar(mIsKeySnooped
),
537 mSimpleContext
, mDummyContext
,
538 gtk_im_multicontext_get_context_id(GTK_IM_MULTICONTEXT(mContext
)),
539 PR_GetEnv("XMODIFIERS")));
543 void IMContextWrapper::Shutdown() { SelectionStyleProvider::Shutdown(); }
545 IMContextWrapper::~IMContextWrapper() {
546 if (this == sLastFocusedContext
) {
547 sLastFocusedContext
= nullptr;
549 MOZ_LOG(gIMELog
, LogLevel::Info
, ("0x%p ~IMContextWrapper()", this));
553 IMContextWrapper::NotifyIME(TextEventDispatcher
* aTextEventDispatcher
,
554 const IMENotification
& aNotification
) {
555 switch (aNotification
.mMessage
) {
556 case REQUEST_TO_COMMIT_COMPOSITION
:
557 case REQUEST_TO_CANCEL_COMPOSITION
: {
559 static_cast<nsWindow
*>(aTextEventDispatcher
->GetWidget());
560 return IsComposing() ? EndIMEComposition(window
) : NS_OK
;
562 case NOTIFY_IME_OF_FOCUS
:
563 OnFocusChangeInGecko(true);
565 case NOTIFY_IME_OF_BLUR
:
566 OnFocusChangeInGecko(false);
568 case NOTIFY_IME_OF_POSITION_CHANGE
:
571 case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED
:
572 OnUpdateComposition();
574 case NOTIFY_IME_OF_SELECTION_CHANGE
: {
576 static_cast<nsWindow
*>(aTextEventDispatcher
->GetWidget());
577 OnSelectionChange(window
, aNotification
);
581 return NS_ERROR_NOT_IMPLEMENTED
;
586 IMContextWrapper::OnRemovedFrom(TextEventDispatcher
* aTextEventDispatcher
) {
587 // XXX When input transaction is being stolen by add-on, what should we do?
591 IMContextWrapper::WillDispatchKeyboardEvent(
592 TextEventDispatcher
* aTextEventDispatcher
,
593 WidgetKeyboardEvent
& aKeyboardEvent
, uint32_t aIndexOfKeypress
,
595 KeymapWrapper::WillDispatchKeyboardEvent(aKeyboardEvent
,
596 static_cast<GdkEventKey
*>(aData
));
599 TextEventDispatcher
* IMContextWrapper::GetTextEventDispatcher() {
600 if (NS_WARN_IF(!mLastFocusedWindow
)) {
603 TextEventDispatcher
* dispatcher
=
604 mLastFocusedWindow
->GetTextEventDispatcher();
605 // nsIWidget::GetTextEventDispatcher() shouldn't return nullptr.
606 MOZ_RELEASE_ASSERT(dispatcher
);
610 NS_IMETHODIMP_(IMENotificationRequests
)
611 IMContextWrapper::GetIMENotificationRequests() {
612 IMENotificationRequests::Notifications notifications
=
613 IMENotificationRequests::NOTIFY_NOTHING
;
614 // If it's not enabled, we don't need position change notification.
616 notifications
|= IMENotificationRequests::NOTIFY_POSITION_CHANGE
;
618 return IMENotificationRequests(notifications
);
621 void IMContextWrapper::OnDestroyWindow(nsWindow
* aWindow
) {
623 gIMELog
, LogLevel::Info
,
624 ("0x%p OnDestroyWindow(aWindow=0x%p), mLastFocusedWindow=0x%p, "
625 "mOwnerWindow=0x%p, mLastFocusedModule=0x%p",
626 this, aWindow
, mLastFocusedWindow
, mOwnerWindow
, sLastFocusedContext
));
628 MOZ_ASSERT(aWindow
, "aWindow must not be null");
630 if (mLastFocusedWindow
== aWindow
) {
632 EndIMEComposition(aWindow
);
634 NotifyIMEOfFocusChange(IMEFocusState::Blurred
);
635 mLastFocusedWindow
= nullptr;
638 if (mOwnerWindow
!= aWindow
) {
642 if (sLastFocusedContext
== this) {
643 sLastFocusedContext
= nullptr;
648 * The given window is the owner of this, so, we must release the
649 * contexts now. But that might be referred from other nsWindows
650 * (they are children of this. But we don't know why there are the
651 * cases). So, we need to clear the pointers that refers to contexts
652 * and this if the other referrers are still alive. See bug 349727.
655 PrepareToDestroyContext(mContext
);
656 gtk_im_context_set_client_window(mContext
, nullptr);
657 g_object_unref(mContext
);
661 if (mSimpleContext
) {
662 gtk_im_context_set_client_window(mSimpleContext
, nullptr);
663 g_object_unref(mSimpleContext
);
664 mSimpleContext
= nullptr;
668 // mContext and mDummyContext have the same slaveType and signal_data
669 // so no need for another workaround_gtk_im_display_closed.
670 gtk_im_context_set_client_window(mDummyContext
, nullptr);
671 g_object_unref(mDummyContext
);
672 mDummyContext
= nullptr;
675 if (NS_WARN_IF(mComposingContext
)) {
676 g_object_unref(mComposingContext
);
677 mComposingContext
= nullptr;
680 mOwnerWindow
= nullptr;
681 mLastFocusedWindow
= nullptr;
682 mInputContext
.mIMEState
.mEnabled
= IMEEnabled::Disabled
;
683 mPostingKeyEvents
.Clear();
685 MOZ_LOG(gIMELog
, LogLevel::Debug
,
686 ("0x%p OnDestroyWindow(), succeeded, Completely destroyed", this));
689 void IMContextWrapper::PrepareToDestroyContext(GtkIMContext
* aContext
) {
690 if (mIMContextID
== IMContextID::IIIMF
) {
691 // IIIM module registers handlers for the "closed" signal on the
692 // display, but the signal handler is not disconnected when the module
693 // is unloaded. To prevent the module from being unloaded, use static
694 // variable to hold reference of slave context class declared by IIIM.
695 // Note that this does not grab any instance, it grabs the "class".
696 static gpointer sGtkIIIMContextClass
= nullptr;
697 if (!sGtkIIIMContextClass
) {
698 // We retrieved slave context class with g_type_name() and actual
699 // slave context instance when our widget was GTK2. That must be
700 // _GtkIMContext::priv::slave in GTK3. However, _GtkIMContext::priv
701 // is an opacity struct named _GtkIMMulticontextPrivate, i.e., it's
702 // not exposed by GTK3. Therefore, we cannot access the instance
703 // safely. So, we need to retrieve the slave context class with
704 // g_type_from_name("GtkIMContextIIIM") directly (anyway, we needed
705 // to compare the class name with "GtkIMContextIIIM").
706 GType IIMContextType
= g_type_from_name("GtkIMContextIIIM");
707 if (IIMContextType
) {
708 sGtkIIIMContextClass
= g_type_class_ref(IIMContextType
);
709 MOZ_LOG(gIMELog
, LogLevel::Info
,
710 ("0x%p PrepareToDestroyContext(), added to reference to "
711 "GtkIMContextIIIM class to prevent it from being unloaded",
714 MOZ_LOG(gIMELog
, LogLevel::Error
,
715 ("0x%p PrepareToDestroyContext(), FAILED to prevent the "
716 "IIIM module from being uploaded",
723 void IMContextWrapper::OnFocusWindow(nsWindow
* aWindow
) {
724 if (MOZ_UNLIKELY(IsDestroyed())) {
728 MOZ_LOG(gIMELog
, LogLevel::Info
,
729 ("0x%p OnFocusWindow(aWindow=0x%p), mLastFocusedWindow=0x%p", this,
730 aWindow
, mLastFocusedWindow
));
731 mLastFocusedWindow
= aWindow
;
734 void IMContextWrapper::OnBlurWindow(nsWindow
* aWindow
) {
735 if (MOZ_UNLIKELY(IsDestroyed())) {
740 gIMELog
, LogLevel::Info
,
741 ("0x%p OnBlurWindow(aWindow=0x%p), mLastFocusedWindow=0x%p, "
743 this, aWindow
, mLastFocusedWindow
, ToString(mIMEFocusState
).c_str()));
745 if (mLastFocusedWindow
!= aWindow
) {
749 NotifyIMEOfFocusChange(IMEFocusState::Blurred
);
752 KeyHandlingState
IMContextWrapper::OnKeyEvent(
753 nsWindow
* aCaller
, GdkEventKey
* aEvent
,
754 bool aKeyboardEventWasDispatched
/* = false */) {
755 MOZ_ASSERT(aEvent
, "aEvent must be non-null");
757 if (!mInputContext
.mIMEState
.IsEditable() || MOZ_UNLIKELY(IsDestroyed())) {
758 return KeyHandlingState::eNotHandled
;
761 MOZ_LOG(gIMELog
, LogLevel::Info
, (">>>>>>>>>>>>>>>>"));
763 gIMELog
, LogLevel::Info
,
764 ("0x%p OnKeyEvent(aCaller=0x%p, "
765 "aEvent(0x%p): { type=%s, keyval=%s, unicode=0x%X, state=%s, "
766 "time=%u, hardware_keycode=%u, group=%u }, "
767 "aKeyboardEventWasDispatched=%s)",
768 this, aCaller
, aEvent
, GetEventType(aEvent
),
769 gdk_keyval_name(aEvent
->keyval
), gdk_keyval_to_unicode(aEvent
->keyval
),
770 GetEventStateName(aEvent
->state
, mIMContextID
).get(), aEvent
->time
,
771 aEvent
->hardware_keycode
, aEvent
->group
,
772 ToChar(aKeyboardEventWasDispatched
)));
774 gIMELog
, LogLevel::Info
,
775 ("0x%p OnKeyEvent(), mMaybeInDeadKeySequence=%s, "
776 "mCompositionState=%s, current context=%p, active context=%p, "
777 "mIMContextID=%s, mIsIMInAsyncKeyHandlingMode=%s",
778 this, ToChar(mMaybeInDeadKeySequence
), GetCompositionStateName(),
779 GetCurrentContext(), GetActiveContext(), ToString(mIMContextID
).c_str(),
780 ToChar(mIsIMInAsyncKeyHandlingMode
)));
782 if (aCaller
!= mLastFocusedWindow
) {
783 MOZ_LOG(gIMELog
, LogLevel::Error
,
784 ("0x%p OnKeyEvent(), FAILED, the caller isn't focused "
785 "window, mLastFocusedWindow=0x%p",
786 this, mLastFocusedWindow
));
787 return KeyHandlingState::eNotHandled
;
790 // Even if old IM context has composition, key event should be sent to
791 // current context since the user expects so.
792 GtkIMContext
* currentContext
= GetCurrentContext();
793 if (MOZ_UNLIKELY(!currentContext
)) {
794 MOZ_LOG(gIMELog
, LogLevel::Error
,
795 ("0x%p OnKeyEvent(), FAILED, there are no context", this));
796 return KeyHandlingState::eNotHandled
;
799 if (mSetCursorPositionOnKeyEvent
) {
800 SetCursorPosition(currentContext
);
801 mSetCursorPositionOnKeyEvent
= false;
804 // Let's support dead key event even if active keyboard layout also
805 // supports complicated composition like CJK IME.
807 KeymapWrapper::ComputeDOMKeyNameIndex(aEvent
) == KEY_NAME_INDEX_Dead
;
808 mMaybeInDeadKeySequence
|= isDeadKey
;
810 // If current context is mSimpleContext, both ibus and fcitx handles key
811 // events synchronously. So, only when current context is mContext which
812 // is GtkIMMulticontext, the key event may be handled by IME asynchronously.
813 bool probablyHandledAsynchronously
=
814 mIsIMInAsyncKeyHandlingMode
&& currentContext
== mContext
;
816 // If we're not sure whether the event is handled asynchronously, this is
818 bool maybeHandledAsynchronously
= false;
820 // If aEvent is a synthesized event for async handling, this will be set to
822 bool isHandlingAsyncEvent
= false;
824 // If we've decided that the event won't be synthesized asyncrhonously
825 // by IME, but actually IME did it, this is set to true.
826 bool isUnexpectedAsyncEvent
= false;
828 // If IM is ibus or fcitx and it handles key events asynchronously,
829 // they mark aEvent->state as "handled by me" when they post key event
830 // to another process. Unfortunately, we need to check this hacky
831 // flag because it's difficult to store all pending key events by
832 // an array or a hashtable.
833 if (probablyHandledAsynchronously
) {
834 switch (mIMContextID
) {
835 case IMContextID::IBus
: {
836 // See src/ibustypes.h
837 static const guint IBUS_IGNORED_MASK
= 1 << 25;
838 // If IBUS_IGNORED_MASK was set to aEvent->state, the event
839 // has already been handled by another process and it wasn't
841 isHandlingAsyncEvent
= !!(aEvent
->state
& IBUS_IGNORED_MASK
);
842 if (!isHandlingAsyncEvent
) {
843 // On some environments, IBUS_IGNORED_MASK flag is not set as
844 // expected. In such case, we keep pusing all events into the queue.
845 // I.e., that causes eating a lot of memory until it's blurred.
846 // Therefore, we need to check whether there is same timestamp event
847 // in the queue. This redundant cost should be low because in most
848 // causes, key events in the queue should be 2 or 4.
849 isHandlingAsyncEvent
=
850 mPostingKeyEvents
.IndexOf(aEvent
) != GdkEventKeyQueue::NoIndex();
851 if (isHandlingAsyncEvent
) {
852 MOZ_LOG(gIMELog
, LogLevel::Info
,
853 ("0x%p OnKeyEvent(), aEvent->state does not have "
854 "IBUS_IGNORED_MASK but "
855 "same event in the queue. So, assuming it's a "
861 // If it's a synthesized event, let's remove it from the posting
862 // event queue first. Otherwise the following blocks cannot use
864 if (isHandlingAsyncEvent
) {
865 MOZ_LOG(gIMELog
, LogLevel::Info
,
866 ("0x%p OnKeyEvent(), aEvent->state has IBUS_IGNORED_MASK "
867 "or aEvent is in the "
868 "posting event queue, so, it won't be handled "
869 "asynchronously anymore. Removing "
870 "the posted events from the queue",
872 probablyHandledAsynchronously
= false;
873 mPostingKeyEvents
.RemoveEvent(aEvent
);
876 // ibus won't send back key press events in a dead key sequcne.
877 if (mMaybeInDeadKeySequence
&& aEvent
->type
== GDK_KEY_PRESS
) {
878 probablyHandledAsynchronously
= false;
879 if (isHandlingAsyncEvent
) {
880 isUnexpectedAsyncEvent
= true;
883 // Some keyboard layouts which have dead keys may send
884 // "empty" key event to make us call
885 // gtk_im_context_filter_keypress() to commit composed
886 // character during a GDK_KEY_PRESS event dispatching.
887 if (!gdk_keyval_to_unicode(aEvent
->keyval
) &&
888 !aEvent
->hardware_keycode
) {
889 isUnexpectedAsyncEvent
= true;
894 // ibus may handle key events synchronously if focused editor is
895 // <input type="password"> or |ime-mode: disabled;|. However, in
896 // some environments, not so actually. Therefore, we need to check
897 // the result of gtk_im_context_filter_keypress() later.
898 if (mInputContext
.mIMEState
.mEnabled
== IMEEnabled::Password
) {
899 probablyHandledAsynchronously
= false;
900 maybeHandledAsynchronously
= !isHandlingAsyncEvent
;
905 case IMContextID::Fcitx
:
906 case IMContextID::Fcitx5
: {
907 // See src/lib/fcitx-utils/keysym.h
908 static const guint FcitxKeyState_IgnoredMask
= 1 << 25;
909 // If FcitxKeyState_IgnoredMask was set to aEvent->state,
910 // the event has already been handled by another process and
911 // it wasn't used by IME.
912 isHandlingAsyncEvent
= !!(aEvent
->state
& FcitxKeyState_IgnoredMask
);
913 if (!isHandlingAsyncEvent
) {
914 // On some environments, FcitxKeyState_IgnoredMask flag *might* be not
915 // set as expected. If there were such cases, we'd keep pusing all
916 // events into the queue. I.e., that would cause eating a lot of
917 // memory until it'd be blurred. Therefore, we should check whether
918 // there is same timestamp event in the queue. This redundant cost
919 // should be low because in most causes, key events in the queue
921 isHandlingAsyncEvent
=
922 mPostingKeyEvents
.IndexOf(aEvent
) != GdkEventKeyQueue::NoIndex();
923 if (isHandlingAsyncEvent
) {
924 MOZ_LOG(gIMELog
, LogLevel::Info
,
925 ("0x%p OnKeyEvent(), aEvent->state does not have "
926 "FcitxKeyState_IgnoredMask "
927 "but same event in the queue. So, assuming it's a "
933 // fcitx won't send back key press events in a dead key sequcne.
934 if (mMaybeInDeadKeySequence
&& aEvent
->type
== GDK_KEY_PRESS
) {
935 probablyHandledAsynchronously
= false;
936 if (isHandlingAsyncEvent
) {
937 isUnexpectedAsyncEvent
= true;
940 // Some keyboard layouts which have dead keys may send
941 // "empty" key event to make us call
942 // gtk_im_context_filter_keypress() to commit composed
943 // character during a GDK_KEY_PRESS event dispatching.
944 if (!gdk_keyval_to_unicode(aEvent
->keyval
) &&
945 !aEvent
->hardware_keycode
) {
946 isUnexpectedAsyncEvent
= true;
951 // fcitx handles key events asynchronously even if focused
952 // editor cannot use IME actually.
954 if (isHandlingAsyncEvent
) {
955 MOZ_LOG(gIMELog
, LogLevel::Info
,
956 ("0x%p OnKeyEvent(), aEvent->state has "
957 "FcitxKeyState_IgnoredMask or aEvent is in "
958 "the posting event queue, so, it won't be handled "
959 "asynchronously anymore. "
960 "Removing the posted events from the queue",
962 probablyHandledAsynchronously
= false;
963 mPostingKeyEvents
.RemoveEvent(aEvent
);
969 MOZ_ASSERT_UNREACHABLE(
970 "IME may handle key event "
971 "asyncrhonously, but not yet confirmed if it comes agian "
976 if (!isUnexpectedAsyncEvent
) {
977 mKeyboardEventWasDispatched
= aKeyboardEventWasDispatched
;
978 mKeyboardEventWasConsumed
= false;
980 // If we didn't expect this event, we've alreday dispatched eKeyDown
981 // event or eKeyUp event for that.
982 mKeyboardEventWasDispatched
= true;
983 // And in this case, we need to assume that another key event hasn't
984 // been receivied and mKeyboardEventWasConsumed keeps storing the
985 // dispatched eKeyDown or eKeyUp event's state.
987 mFallbackToKeyEvent
= false;
988 mProcessingKeyEvent
= aEvent
;
989 gboolean isFiltered
= gtk_im_context_filter_keypress(currentContext
, aEvent
);
991 // If we're not sure whether the event is handled by IME asynchronously or
992 // synchronously, we need to trust the result of
993 // gtk_im_context_filter_keypress(). If it consumed and but did nothing,
994 // we can assume that another event will be synthesized.
995 if (!isHandlingAsyncEvent
&& maybeHandledAsynchronously
) {
996 probablyHandledAsynchronously
|=
997 isFiltered
&& !mFallbackToKeyEvent
&& !mKeyboardEventWasDispatched
;
1000 if (aEvent
->type
== GDK_KEY_PRESS
) {
1001 if (isFiltered
&& probablyHandledAsynchronously
) {
1002 sWaitingSynthesizedKeyPressHardwareKeyCode
= aEvent
->hardware_keycode
;
1004 sWaitingSynthesizedKeyPressHardwareKeyCode
= 0;
1008 // The caller of this shouldn't handle aEvent anymore if we've dispatched
1009 // composition events or modified content with other events.
1010 bool filterThisEvent
= isFiltered
&& !mFallbackToKeyEvent
;
1012 if (IsComposingOnCurrentContext() && !isFiltered
&&
1013 aEvent
->type
== GDK_KEY_PRESS
&& mDispatchedCompositionString
.IsEmpty()) {
1014 // A Hangul input engine for SCIM doesn't emit preedit_end
1015 // signal even when composition string becomes empty. On the
1016 // other hand, we should allow to make composition with empty
1017 // string for other languages because there *might* be such
1018 // IM. For compromising this issue, we should dispatch
1019 // compositionend event, however, we don't need to reset IM
1021 // NOTE: Don't dispatch key events as "processed by IME" since
1022 // we need to dispatch keyboard events as IME wasn't handled it.
1023 mProcessingKeyEvent
= nullptr;
1024 DispatchCompositionCommitEvent(currentContext
, &EmptyString());
1025 mProcessingKeyEvent
= aEvent
;
1026 // In this case, even though we handle the keyboard event here,
1027 // but we should dispatch keydown event as
1028 filterThisEvent
= false;
1031 if (filterThisEvent
&& !mKeyboardEventWasDispatched
) {
1032 // If IME handled the key event but we've not dispatched eKeyDown nor
1033 // eKeyUp event yet, we need to dispatch here unless the key event is
1034 // now being handled by other IME process.
1035 if (!probablyHandledAsynchronously
) {
1036 MaybeDispatchKeyEventAsProcessedByIME(eVoidEvent
);
1037 // Be aware, the widget might have been gone here.
1039 // If we need to wait reply from IM, IM may send some signals to us
1040 // without sending the key event again. In such case, we need to
1041 // dispatch keyboard events with a copy of aEvent. Therefore, we
1042 // need to use information of this key event to dispatch an KeyDown
1043 // or eKeyUp event later.
1045 MOZ_LOG(gIMELog
, LogLevel::Info
,
1046 ("0x%p OnKeyEvent(), putting aEvent into the queue...", this));
1047 mPostingKeyEvents
.PutEvent(aEvent
);
1051 mProcessingKeyEvent
= nullptr;
1053 if (aEvent
->type
== GDK_KEY_PRESS
&& !filterThisEvent
) {
1054 // If the key event hasn't been handled by active IME nor keyboard
1055 // layout, we can assume that the dead key sequence has been or was
1056 // ended. Note that we should not reset it when the key event is
1057 // GDK_KEY_RELEASE since it may not be filtered by active keyboard
1058 // layout even in composition.
1059 mMaybeInDeadKeySequence
= false;
1062 if (aEvent
->type
== GDK_KEY_RELEASE
) {
1063 if (const GdkEventKey
* pendingKeyPressEvent
=
1064 mPostingKeyEvents
.GetCorrespondingKeyPressEvent(aEvent
)) {
1065 MOZ_LOG(gIMELog
, LogLevel::Warning
,
1066 ("0x%p OnKeyEvent(), forgetting a pending GDK_KEY_PRESS event "
1067 "because GDK_KEY_RELEASE for the event is handled",
1069 mPostingKeyEvents
.RemoveEvent(pendingKeyPressEvent
);
1074 gIMELog
, LogLevel::Debug
,
1075 ("0x%p OnKeyEvent(), succeeded, filterThisEvent=%s "
1076 "(isFiltered=%s, mFallbackToKeyEvent=%s, "
1077 "probablyHandledAsynchronously=%s, maybeHandledAsynchronously=%s), "
1078 "mPostingKeyEvents.Length()=%zu, mCompositionState=%s, "
1079 "mMaybeInDeadKeySequence=%s, mKeyboardEventWasDispatched=%s, "
1080 "mKeyboardEventWasConsumed=%s",
1081 this, ToChar(filterThisEvent
), ToChar(isFiltered
),
1082 ToChar(mFallbackToKeyEvent
), ToChar(probablyHandledAsynchronously
),
1083 ToChar(maybeHandledAsynchronously
), mPostingKeyEvents
.Length(),
1084 GetCompositionStateName(), ToChar(mMaybeInDeadKeySequence
),
1085 ToChar(mKeyboardEventWasDispatched
), ToChar(mKeyboardEventWasConsumed
)));
1086 MOZ_LOG(gIMELog
, LogLevel::Info
, ("<<<<<<<<<<<<<<<<\n\n"));
1088 if (filterThisEvent
) {
1089 return KeyHandlingState::eHandled
;
1091 // If another call of this method has already dispatched eKeyDown event,
1092 // we should return KeyHandlingState::eNotHandledButEventDispatched because
1093 // the caller should've stopped handling the event if preceding eKeyDown
1094 // event was consumed.
1095 if (aKeyboardEventWasDispatched
) {
1096 return KeyHandlingState::eNotHandledButEventDispatched
;
1098 if (!mKeyboardEventWasDispatched
) {
1099 return KeyHandlingState::eNotHandled
;
1101 return mKeyboardEventWasConsumed
1102 ? KeyHandlingState::eNotHandledButEventConsumed
1103 : KeyHandlingState::eNotHandledButEventDispatched
;
1106 void IMContextWrapper::OnFocusChangeInGecko(bool aFocus
) {
1107 MOZ_LOG(gIMELog
, LogLevel::Info
,
1108 ("0x%p OnFocusChangeInGecko(aFocus=%s),mCompositionState=%s, "
1109 "mIMEFocusState=%s, mSetInputPurposeAndInputHints=%s",
1110 this, ToChar(aFocus
), GetCompositionStateName(),
1111 ToString(mIMEFocusState
).c_str(),
1112 ToChar(mSetInputPurposeAndInputHints
)));
1114 // We shouldn't carry over the removed string to another editor.
1115 mSelectedStringRemovedByComposition
.Truncate();
1116 mContentSelection
.reset();
1119 if (mSetInputPurposeAndInputHints
) {
1120 mSetInputPurposeAndInputHints
= false;
1121 SetInputPurposeAndInputHints();
1123 NotifyIMEOfFocusChange(IMEFocusState::Focused
);
1125 NotifyIMEOfFocusChange(IMEFocusState::Blurred
);
1128 // When the focus changes, we need to inform IM about the new cursor
1129 // position. Chinese input methods generally rely on this because they
1130 // usually don't start composition until a character is picked.
1131 if (aFocus
&& EnsureToCacheContentSelection()) {
1132 SetCursorPosition(GetActiveContext());
1136 void IMContextWrapper::ResetIME() {
1137 MOZ_LOG(gIMELog
, LogLevel::Info
,
1138 ("0x%p ResetIME(), mCompositionState=%s, mIMEFocusState=%s", this,
1139 GetCompositionStateName(), ToString(mIMEFocusState
).c_str()));
1141 GtkIMContext
* activeContext
= GetActiveContext();
1142 if (MOZ_UNLIKELY(!activeContext
)) {
1143 MOZ_LOG(gIMELog
, LogLevel::Error
,
1144 ("0x%p ResetIME(), FAILED, there are no context", this));
1148 RefPtr
<IMContextWrapper
> kungFuDeathGrip(this);
1149 RefPtr
<nsWindow
> lastFocusedWindow(mLastFocusedWindow
);
1151 mPendingResettingIMContext
= false;
1152 gtk_im_context_reset(activeContext
);
1154 // The last focused window might have been destroyed by a DOM event handler
1155 // which was called by us during a call of gtk_im_context_reset().
1156 if (!lastFocusedWindow
||
1157 NS_WARN_IF(lastFocusedWindow
!= mLastFocusedWindow
) ||
1158 lastFocusedWindow
->Destroyed()) {
1162 nsAutoString compositionString
;
1163 GetCompositionString(activeContext
, compositionString
);
1165 MOZ_LOG(gIMELog
, LogLevel::Debug
,
1166 ("0x%p ResetIME() called gtk_im_context_reset(), "
1167 "activeContext=0x%p, mCompositionState=%s, compositionString=%s, "
1168 "mIMEFocusState=%s",
1169 this, activeContext
, GetCompositionStateName(),
1170 NS_ConvertUTF16toUTF8(compositionString
).get(),
1171 ToString(mIMEFocusState
).c_str()));
1173 // XXX IIIMF (ATOK X3 which is one of the Language Engine of it is still
1174 // used in Japan!) sends only "preedit_changed" signal with empty
1175 // composition string synchronously. Therefore, if composition string
1176 // is now empty string, we should assume that the IME won't send
1178 if (IsComposing() && compositionString
.IsEmpty()) {
1179 // WARNING: The widget might have been gone after this.
1180 DispatchCompositionCommitEvent(activeContext
, &EmptyString());
1184 nsresult
IMContextWrapper::EndIMEComposition(nsWindow
* aCaller
) {
1185 if (MOZ_UNLIKELY(IsDestroyed())) {
1189 MOZ_LOG(gIMELog
, LogLevel::Info
,
1190 ("0x%p EndIMEComposition(aCaller=0x%p), "
1191 "mCompositionState=%s",
1192 this, aCaller
, GetCompositionStateName()));
1194 if (aCaller
!= mLastFocusedWindow
) {
1195 MOZ_LOG(gIMELog
, LogLevel::Error
,
1196 ("0x%p EndIMEComposition(), FAILED, the caller isn't "
1197 "focused window, mLastFocusedWindow=0x%p",
1198 this, mLastFocusedWindow
));
1202 if (!IsComposing()) {
1206 // Currently, GTK has API neither to commit nor to cancel composition
1207 // forcibly. Therefore, TextComposition will recompute commit string for
1208 // the request even if native IME will cause unexpected commit string.
1209 // So, we don't need to emulate commit or cancel composition with
1210 // proper composition events.
1211 // XXX ResetIME() might not enough for finishing compositoin on some
1212 // environments. We should emulate focus change too because some IMEs
1213 // may commit or cancel composition at blur.
1219 void IMContextWrapper::OnLayoutChange() {
1220 if (MOZ_UNLIKELY(IsDestroyed())) {
1224 if (IsComposing()) {
1225 SetCursorPosition(GetActiveContext());
1227 // If not composing, candidate window position is updated before key
1229 mSetCursorPositionOnKeyEvent
= true;
1231 mLayoutChanged
= true;
1234 void IMContextWrapper::OnUpdateComposition() {
1235 if (MOZ_UNLIKELY(IsDestroyed())) {
1239 if (!IsComposing()) {
1240 // Composition has been committed. So we need update selection for
1242 mContentSelection
.reset();
1243 EnsureToCacheContentSelection();
1244 mSetCursorPositionOnKeyEvent
= true;
1247 // If we've already set candidate window position, we don't need to update
1248 // the position with update composition notification.
1249 if (!mLayoutChanged
) {
1250 SetCursorPosition(GetActiveContext());
1254 void IMContextWrapper::SetInputContext(nsWindow
* aCaller
,
1255 const InputContext
* aContext
,
1256 const InputContextAction
* aAction
) {
1257 if (MOZ_UNLIKELY(IsDestroyed())) {
1261 MOZ_LOG(gIMELog
, LogLevel::Info
,
1262 ("0x%p SetInputContext(aCaller=0x%p, aContext={ mIMEState={ "
1263 "mEnabled=%s }, mHTMLInputType=%s })",
1264 this, aCaller
, ToString(aContext
->mIMEState
.mEnabled
).c_str(),
1265 NS_ConvertUTF16toUTF8(aContext
->mHTMLInputType
).get()));
1267 if (aCaller
!= mLastFocusedWindow
) {
1268 MOZ_LOG(gIMELog
, LogLevel::Error
,
1269 ("0x%p SetInputContext(), FAILED, "
1270 "the caller isn't focused window, mLastFocusedWindow=0x%p",
1271 this, mLastFocusedWindow
));
1276 MOZ_LOG(gIMELog
, LogLevel::Error
,
1277 ("0x%p SetInputContext(), FAILED, "
1278 "there are no context",
1283 if (sLastFocusedContext
!= this) {
1284 mInputContext
= *aContext
;
1285 MOZ_LOG(gIMELog
, LogLevel::Debug
,
1286 ("0x%p SetInputContext(), succeeded, "
1287 "but we're not active",
1292 const bool changingEnabledState
=
1293 aContext
->IsInputAttributeChanged(mInputContext
);
1295 // Release current IME focus if IME is enabled.
1296 if (changingEnabledState
&& mInputContext
.mIMEState
.IsEditable()) {
1297 if (IsComposing()) {
1298 EndIMEComposition(mLastFocusedWindow
);
1300 if (mIMEFocusState
== IMEFocusState::Focused
) {
1301 NotifyIMEOfFocusChange(IMEFocusState::BlurredWithoutFocusChange
);
1305 mInputContext
= *aContext
;
1306 mSetInputPurposeAndInputHints
= false;
1308 if (!changingEnabledState
|| !mInputContext
.mIMEState
.IsEditable()) {
1312 // If the input context was temporarily disabled without a focus change,
1313 // it must be ready to query content even if the focused content is in
1314 // a remote process. In this case, we should set IME focus right now.
1315 if (mIMEFocusState
== IMEFocusState::BlurredWithoutFocusChange
) {
1316 SetInputPurposeAndInputHints();
1317 NotifyIMEOfFocusChange(IMEFocusState::Focused
);
1321 // Otherwise, we cannot set input-purpose and input-hints right now because
1322 // setting them may require to set focus immediately for IME own's UI.
1323 // However, at this moment, `ContentCacheInParent` does not have content
1324 // cache, it'll be available after `NOTIFY_IME_OF_FOCUS` notification.
1325 // Therefore, we set them at receiving the notification.
1326 mSetInputPurposeAndInputHints
= true;
1329 void IMContextWrapper::SetInputPurposeAndInputHints() {
1330 GtkIMContext
* currentContext
= GetCurrentContext();
1331 if (!currentContext
) {
1335 GtkInputPurpose purpose
= GTK_INPUT_PURPOSE_FREE_FORM
;
1336 const nsString
& inputType
= mInputContext
.mHTMLInputType
;
1337 // Password case has difficult issue. Desktop IMEs disable composition if
1338 // input-purpose is password. For disabling IME on |ime-mode: disabled;|, we
1339 // need to check mEnabled value instead of inputType value. This hack also
1340 // enables composition on <input type="password" style="ime-mode: enabled;">.
1341 // This is right behavior of ime-mode on desktop.
1343 // On the other hand, IME for tablet devices may provide a specific software
1344 // keyboard for password field. If so, the behavior might look strange on
1346 // <input type="text" style="ime-mode: disabled;">
1347 // <input type="password" style="ime-mode: enabled;">
1349 // Temporarily, we should focus on desktop environment for now. I.e., let's
1350 // ignore tablet devices for now. When somebody reports actual trouble on
1351 // tablet devices, we should try to look for a way to solve actual problem.
1352 if (mInputContext
.mIMEState
.mEnabled
== IMEEnabled::Password
) {
1353 purpose
= GTK_INPUT_PURPOSE_PASSWORD
;
1354 } else if (inputType
.EqualsLiteral("email")) {
1355 purpose
= GTK_INPUT_PURPOSE_EMAIL
;
1356 } else if (inputType
.EqualsLiteral("url")) {
1357 purpose
= GTK_INPUT_PURPOSE_URL
;
1358 } else if (inputType
.EqualsLiteral("tel")) {
1359 purpose
= GTK_INPUT_PURPOSE_PHONE
;
1360 } else if (inputType
.EqualsLiteral("number")) {
1361 purpose
= GTK_INPUT_PURPOSE_NUMBER
;
1362 } else if (mInputContext
.mHTMLInputMode
.EqualsLiteral("decimal")) {
1363 purpose
= GTK_INPUT_PURPOSE_NUMBER
;
1364 } else if (mInputContext
.mHTMLInputMode
.EqualsLiteral("email")) {
1365 purpose
= GTK_INPUT_PURPOSE_EMAIL
;
1366 } else if (mInputContext
.mHTMLInputMode
.EqualsLiteral("numeric")) {
1367 purpose
= GTK_INPUT_PURPOSE_DIGITS
;
1368 } else if (mInputContext
.mHTMLInputMode
.EqualsLiteral("tel")) {
1369 purpose
= GTK_INPUT_PURPOSE_PHONE
;
1370 } else if (mInputContext
.mHTMLInputMode
.EqualsLiteral("url")) {
1371 purpose
= GTK_INPUT_PURPOSE_URL
;
1373 // Search by type and inputmode isn't supported on GTK.
1375 g_object_set(currentContext
, "input-purpose", purpose
, nullptr);
1377 // Although GtkInputHints is enum type, value is bit field.
1378 gint hints
= GTK_INPUT_HINT_NONE
;
1379 if (mInputContext
.mHTMLInputMode
.EqualsLiteral("none")) {
1380 hints
|= GTK_INPUT_HINT_INHIBIT_OSK
;
1383 if (mInputContext
.mAutocapitalize
.EqualsLiteral("characters")) {
1384 hints
|= GTK_INPUT_HINT_UPPERCASE_CHARS
;
1385 } else if (mInputContext
.mAutocapitalize
.EqualsLiteral("sentences")) {
1386 hints
|= GTK_INPUT_HINT_UPPERCASE_SENTENCES
;
1387 } else if (mInputContext
.mAutocapitalize
.EqualsLiteral("words")) {
1388 hints
|= GTK_INPUT_HINT_UPPERCASE_WORDS
;
1391 g_object_set(currentContext
, "input-hints", hints
, nullptr);
1394 InputContext
IMContextWrapper::GetInputContext() {
1395 mInputContext
.mIMEState
.mOpen
= IMEState::OPEN_STATE_NOT_SUPPORTED
;
1396 return mInputContext
;
1399 GtkIMContext
* IMContextWrapper::GetCurrentContext() const {
1403 if (mInputContext
.mIMEState
.mEnabled
== IMEEnabled::Password
) {
1404 return mSimpleContext
;
1406 return mDummyContext
;
1409 bool IMContextWrapper::IsValidContext(GtkIMContext
* aContext
) const {
1413 return aContext
== mContext
|| aContext
== mSimpleContext
||
1414 aContext
== mDummyContext
;
1417 bool IMContextWrapper::IsEnabled() const {
1418 return mInputContext
.mIMEState
.mEnabled
== IMEEnabled::Enabled
||
1419 (!sUseSimpleContext
&&
1420 mInputContext
.mIMEState
.mEnabled
== IMEEnabled::Password
);
1423 void IMContextWrapper::NotifyIMEOfFocusChange(IMEFocusState aIMEFocusState
) {
1424 MOZ_ASSERT_IF(aIMEFocusState
== IMEFocusState::BlurredWithoutFocusChange
,
1425 mIMEFocusState
!= IMEFocusState::Blurred
);
1426 if (mIMEFocusState
== aIMEFocusState
) {
1430 MOZ_LOG(gIMELog
, LogLevel::Info
,
1431 ("0x%p NotifyIMEOfFocusChange(aIMEFocusState=%s), mIMEFocusState=%s, "
1432 "sLastFocusedContext=0x%p",
1433 this, ToString(aIMEFocusState
).c_str(),
1434 ToString(mIMEFocusState
).c_str(), sLastFocusedContext
));
1435 MOZ_ASSERT(!mSetInputPurposeAndInputHints
);
1437 // If we've already made IME blurred at setting the input context disabled
1438 // and it's now completely blurred by a focus move, we need only to update
1439 // mIMEFocusState and when the input context gets enabled, we cannot set
1440 // IME focus immediately.
1441 if (aIMEFocusState
== IMEFocusState::Blurred
&&
1442 mIMEFocusState
== IMEFocusState::BlurredWithoutFocusChange
) {
1443 mIMEFocusState
= IMEFocusState::Blurred
;
1447 auto Blur
= [&](IMEFocusState aInternalState
) {
1448 GtkIMContext
* currentContext
= GetCurrentContext();
1449 if (MOZ_UNLIKELY(!currentContext
)) {
1450 MOZ_LOG(gIMELog
, LogLevel::Error
,
1451 ("0x%p NotifyIMEOfFocusChange()::Blur(), FAILED, "
1452 "there is no context",
1456 gtk_im_context_focus_out(currentContext
);
1457 mIMEFocusState
= aInternalState
;
1460 if (aIMEFocusState
!= IMEFocusState::Focused
) {
1461 return Blur(aIMEFocusState
);
1464 GtkIMContext
* currentContext
= GetCurrentContext();
1465 if (MOZ_UNLIKELY(!currentContext
)) {
1466 MOZ_LOG(gIMELog
, LogLevel::Error
,
1467 ("0x%p NotifyIMEOfFocusChange(), FAILED, "
1468 "there is no context",
1473 if (sLastFocusedContext
&& sLastFocusedContext
!= this) {
1474 sLastFocusedContext
->NotifyIMEOfFocusChange(IMEFocusState::Blurred
);
1477 sLastFocusedContext
= this;
1479 // Forget all posted key events when focus is moved since they shouldn't
1480 // be fired in different editor.
1481 sWaitingSynthesizedKeyPressHardwareKeyCode
= 0;
1482 mPostingKeyEvents
.Clear();
1484 gtk_im_context_focus_in(currentContext
);
1485 mIMEFocusState
= aIMEFocusState
;
1486 mSetCursorPositionOnKeyEvent
= true;
1489 // We should release IME focus for uim and scim.
1490 // These IMs are using snooper that is released at losing focus.
1491 Blur(IMEFocusState::BlurredWithoutFocusChange
);
1495 void IMContextWrapper::OnSelectionChange(
1496 nsWindow
* aCaller
, const IMENotification
& aIMENotification
) {
1497 const bool isSelectionRangeChanged
=
1498 mContentSelection
.isNothing() ||
1499 !aIMENotification
.mSelectionChangeData
.EqualsRange(
1500 mContentSelection
.ref());
1502 Some(ContentSelection(aIMENotification
.mSelectionChangeData
));
1503 const bool retrievedSurroundingSignalReceived
=
1504 mRetrieveSurroundingSignalReceived
;
1505 mRetrieveSurroundingSignalReceived
= false;
1507 if (MOZ_UNLIKELY(IsDestroyed())) {
1511 const IMENotification::SelectionChangeDataBase
& selectionChangeData
=
1512 aIMENotification
.mSelectionChangeData
;
1514 MOZ_LOG(gIMELog
, LogLevel::Info
,
1515 ("0x%p OnSelectionChange(aCaller=0x%p, aIMENotification={ "
1516 "mSelectionChangeData=%s }), "
1517 "mCompositionState=%s, mIsDeletingSurrounding=%s, "
1518 "mRetrieveSurroundingSignalReceived=%s, isSelectionRangeChanged=%s",
1519 this, aCaller
, ToString(selectionChangeData
).c_str(),
1520 GetCompositionStateName(), ToChar(mIsDeletingSurrounding
),
1521 ToChar(retrievedSurroundingSignalReceived
),
1522 ToChar(isSelectionRangeChanged
)));
1524 if (aCaller
!= mLastFocusedWindow
) {
1525 MOZ_LOG(gIMELog
, LogLevel::Error
,
1526 ("0x%p OnSelectionChange(), FAILED, "
1527 "the caller isn't focused window, mLastFocusedWindow=0x%p",
1528 this, mLastFocusedWindow
));
1532 if (!IsComposing()) {
1533 // Now we have no composition (mostly situation on calling this method)
1534 // If we have it, it will set by
1535 // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED.
1536 mSetCursorPositionOnKeyEvent
= true;
1539 // The focused editor might have placeholder text with normal text node.
1540 // In such case, the text node must be removed from a compositionstart
1541 // event handler. So, we're dispatching eCompositionStart,
1542 // we should ignore selection change notification.
1543 if (mCompositionState
== eCompositionState_CompositionStartDispatched
) {
1544 if (NS_WARN_IF(mContentSelection
.isNothing())) {
1545 MOZ_LOG(gIMELog
, LogLevel::Error
,
1546 ("0x%p OnSelectionChange(), FAILED, "
1547 "new offset is too large, cannot keep composing",
1549 } else if (mContentSelection
->HasRange()) {
1550 // Modify the selection start offset with new offset.
1551 mCompositionStart
= mContentSelection
->OffsetAndDataRef().StartOffset();
1552 // XXX We should modify mSelectedStringRemovedByComposition?
1554 MOZ_LOG(gIMELog
, LogLevel::Debug
,
1555 ("0x%p OnSelectionChange(), ignored, mCompositionStart "
1556 "is updated to %u, the selection change doesn't cause "
1557 "resetting IM context",
1558 this, mCompositionStart
));
1559 // And don't reset the IM context.
1563 gIMELog
, LogLevel::Debug
,
1564 ("0x%p OnSelectionChange(), ignored, because of no selection range",
1568 // Otherwise, reset the IM context due to impossible to keep composing.
1571 // If the selection change is caused by deleting surrounding text,
1572 // we shouldn't need to notify IME of selection change.
1573 if (mIsDeletingSurrounding
) {
1577 bool occurredBeforeComposition
=
1578 IsComposing() && !selectionChangeData
.mOccurredDuringComposition
&&
1579 !selectionChangeData
.mCausedByComposition
;
1580 if (occurredBeforeComposition
) {
1581 mPendingResettingIMContext
= true;
1584 // When the selection change is caused by dispatching composition event,
1585 // selection set event and/or occurred before starting current composition,
1586 // we shouldn't notify IME of that and commit existing composition.
1587 // Don't do this even if selection is not changed actually. For example,
1588 // fcitx has direct input mode which does not insert composing string, but
1589 // inserts commited text for each key sequence (i.e., there is "invisible"
1590 // composition string). In the world after bug 1712269, we don't use a
1591 // set of composition events for this kind of IME. Therefore,
1592 // SelectionChangeData.mCausedByComposition is not expected value for here
1593 // if this call is caused by a preceding commit. And if the preceding commit
1594 // is triggered by a key type for next word, resetting IME state makes fcitx
1595 // discard the pending input for the next word. Thus, we need to check
1596 // whether the selection range is actually changed here.
1597 if (!selectionChangeData
.mCausedByComposition
&&
1598 !selectionChangeData
.mCausedBySelectionEvent
&& isSelectionRangeChanged
&&
1599 !occurredBeforeComposition
) {
1600 // Hack for ibus-pinyin. ibus-pinyin will synthesize a set of
1601 // composition which commits with empty string after calling
1602 // gtk_im_context_reset(). Therefore, selecting text causes
1603 // unexpectedly removing it. For preventing it but not breaking the
1604 // other IMEs which use surrounding text, we should call it only when
1605 // surrounding text has been retrieved after last selection range was
1606 // set. If it's not retrieved, that means that current IME doesn't
1607 // have any content cache, so, it must not need the notification of
1608 // selection change.
1609 if (IsComposing() || retrievedSurroundingSignalReceived
) {
1616 void IMContextWrapper::OnThemeChanged() {
1617 if (auto* provider
= SelectionStyleProvider::GetExistingInstance()) {
1618 provider
->OnThemeChanged();
1623 void IMContextWrapper::OnStartCompositionCallback(GtkIMContext
* aContext
,
1624 IMContextWrapper
* aModule
) {
1625 aModule
->OnStartCompositionNative(aContext
);
1628 void IMContextWrapper::OnStartCompositionNative(GtkIMContext
* aContext
) {
1629 // IME may synthesize composition asynchronously after filtering a
1630 // GDK_KEY_PRESS event. In that case, we should handle composition with
1631 // emulating the usual case, i.e., this is called in the stack of
1633 Maybe
<AutoRestore
<GdkEventKey
*>> maybeRestoreProcessingKeyEvent
;
1634 if (!mProcessingKeyEvent
&& !mPostingKeyEvents
.IsEmpty()) {
1635 GdkEventKey
* keyEvent
= mPostingKeyEvents
.GetFirstEvent();
1636 if (keyEvent
&& keyEvent
->type
== GDK_KEY_PRESS
&&
1637 KeymapWrapper::ComputeDOMKeyNameIndex(keyEvent
) ==
1638 KEY_NAME_INDEX_USE_STRING
) {
1639 maybeRestoreProcessingKeyEvent
.emplace(mProcessingKeyEvent
);
1640 mProcessingKeyEvent
= mPostingKeyEvents
.GetFirstEvent();
1644 MOZ_LOG(gIMELog
, LogLevel::Info
,
1645 ("0x%p OnStartCompositionNative(aContext=0x%p), "
1646 "current context=0x%p, mComposingContext=0x%p",
1647 this, aContext
, GetCurrentContext(), mComposingContext
));
1649 // See bug 472635, we should do nothing if IM context doesn't match.
1650 if (GetCurrentContext() != aContext
) {
1651 MOZ_LOG(gIMELog
, LogLevel::Error
,
1652 ("0x%p OnStartCompositionNative(), FAILED, "
1653 "given context doesn't match",
1658 if (mComposingContext
&& aContext
!= mComposingContext
) {
1659 // XXX For now, we should ignore this odd case, just logging.
1660 MOZ_LOG(gIMELog
, LogLevel::Warning
,
1661 ("0x%p OnStartCompositionNative(), Warning, "
1662 "there is already a composing context but starting new "
1663 "composition with different context",
1667 // IME may start composition without "preedit_start" signal. Therefore,
1668 // mComposingContext will be initialized in DispatchCompositionStart().
1670 if (!DispatchCompositionStart(aContext
)) {
1673 mCompositionTargetRange
.mOffset
= mCompositionStart
;
1674 mCompositionTargetRange
.mLength
= 0;
1678 void IMContextWrapper::OnEndCompositionCallback(GtkIMContext
* aContext
,
1679 IMContextWrapper
* aModule
) {
1680 aModule
->OnEndCompositionNative(aContext
);
1683 void IMContextWrapper::OnEndCompositionNative(GtkIMContext
* aContext
) {
1684 MOZ_LOG(gIMELog
, LogLevel::Info
,
1685 ("0x%p OnEndCompositionNative(aContext=0x%p), mComposingContext=0x%p",
1686 this, aContext
, mComposingContext
));
1688 // See bug 472635, we should do nothing if IM context doesn't match.
1689 // Note that if this is called after focus move, the context may different
1690 // from any our owning context.
1691 if (!IsValidContext(aContext
)) {
1692 MOZ_LOG(gIMELog
, LogLevel::Error
,
1693 ("0x%p OnEndCompositionNative(), FAILED, "
1694 "given context doesn't match with any context",
1699 // If we've not started composition with aContext, we should ignore it.
1700 if (aContext
!= mComposingContext
) {
1701 MOZ_LOG(gIMELog
, LogLevel::Warning
,
1702 ("0x%p OnEndCompositionNative(), Warning, "
1703 "given context doesn't match with mComposingContext",
1708 g_object_unref(mComposingContext
);
1709 mComposingContext
= nullptr;
1711 // If we already handled the commit event, we should do nothing here.
1712 if (IsComposing()) {
1713 if (!DispatchCompositionCommitEvent(aContext
)) {
1714 // If the widget is destroyed, we should do nothing anymore.
1719 if (mPendingResettingIMContext
) {
1725 void IMContextWrapper::OnChangeCompositionCallback(GtkIMContext
* aContext
,
1726 IMContextWrapper
* aModule
) {
1727 aModule
->OnChangeCompositionNative(aContext
);
1730 void IMContextWrapper::OnChangeCompositionNative(GtkIMContext
* aContext
) {
1731 // IME may synthesize composition asynchronously after filtering a
1732 // GDK_KEY_PRESS event. In that case, we should handle composition with
1733 // emulating the usual case, i.e., this is called in the stack of
1735 Maybe
<AutoRestore
<GdkEventKey
*>> maybeRestoreProcessingKeyEvent
;
1736 if (!mProcessingKeyEvent
&& !mPostingKeyEvents
.IsEmpty()) {
1737 GdkEventKey
* keyEvent
= mPostingKeyEvents
.GetFirstEvent();
1738 if (keyEvent
&& keyEvent
->type
== GDK_KEY_PRESS
&&
1739 KeymapWrapper::ComputeDOMKeyNameIndex(keyEvent
) ==
1740 KEY_NAME_INDEX_USE_STRING
) {
1741 maybeRestoreProcessingKeyEvent
.emplace(mProcessingKeyEvent
);
1742 mProcessingKeyEvent
= mPostingKeyEvents
.GetFirstEvent();
1746 MOZ_LOG(gIMELog
, LogLevel::Info
,
1747 ("0x%p OnChangeCompositionNative(aContext=0x%p), "
1748 "mComposingContext=0x%p",
1749 this, aContext
, mComposingContext
));
1751 // See bug 472635, we should do nothing if IM context doesn't match.
1752 // Note that if this is called after focus move, the context may different
1753 // from any our owning context.
1754 if (!IsValidContext(aContext
)) {
1755 MOZ_LOG(gIMELog
, LogLevel::Error
,
1756 ("0x%p OnChangeCompositionNative(), FAILED, "
1757 "given context doesn't match with any context",
1762 if (mComposingContext
&& aContext
!= mComposingContext
) {
1763 // XXX For now, we should ignore this odd case, just logging.
1764 MOZ_LOG(gIMELog
, LogLevel::Warning
,
1765 ("0x%p OnChangeCompositionNative(), Warning, "
1766 "given context doesn't match with composing context",
1770 nsAutoString compositionString
;
1771 GetCompositionString(aContext
, compositionString
);
1772 if (!IsComposing() && compositionString
.IsEmpty()) {
1773 MOZ_LOG(gIMELog
, LogLevel::Warning
,
1774 ("0x%p OnChangeCompositionNative(), Warning, does nothing "
1775 "because has not started composition and composing string is "
1778 mDispatchedCompositionString
.Truncate();
1779 return; // Don't start the composition with empty string.
1782 // Be aware, widget can be gone
1783 DispatchCompositionChangeEvent(aContext
, compositionString
);
1787 gboolean
IMContextWrapper::OnRetrieveSurroundingCallback(
1788 GtkIMContext
* aContext
, IMContextWrapper
* aModule
) {
1789 return aModule
->OnRetrieveSurroundingNative(aContext
);
1792 gboolean
IMContextWrapper::OnRetrieveSurroundingNative(GtkIMContext
* aContext
) {
1793 MOZ_LOG(gIMELog
, LogLevel::Info
,
1794 ("0x%p OnRetrieveSurroundingNative(aContext=0x%p), "
1795 "current context=0x%p",
1796 this, aContext
, GetCurrentContext()));
1798 // See bug 472635, we should do nothing if IM context doesn't match.
1799 if (GetCurrentContext() != aContext
) {
1800 MOZ_LOG(gIMELog
, LogLevel::Error
,
1801 ("0x%p OnRetrieveSurroundingNative(), FAILED, "
1802 "given context doesn't match",
1807 nsAutoString uniStr
;
1809 if (NS_FAILED(GetCurrentParagraph(uniStr
, cursorPos
))) {
1813 // Despite taking a pointer and a length, IBus wants the string to be
1814 // zero-terminated and doesn't like U+0000 within the string.
1815 uniStr
.ReplaceChar(char16_t(0), char16_t(0xFFFD));
1817 NS_ConvertUTF16toUTF8
utf8Str(nsDependentSubstring(uniStr
, 0, cursorPos
));
1818 uint32_t cursorPosInUTF8
= utf8Str
.Length();
1819 AppendUTF16toUTF8(nsDependentSubstring(uniStr
, cursorPos
), utf8Str
);
1820 gtk_im_context_set_surrounding(aContext
, utf8Str
.get(), utf8Str
.Length(),
1822 mRetrieveSurroundingSignalReceived
= true;
1827 gboolean
IMContextWrapper::OnDeleteSurroundingCallback(
1828 GtkIMContext
* aContext
, gint aOffset
, gint aNChars
,
1829 IMContextWrapper
* aModule
) {
1830 return aModule
->OnDeleteSurroundingNative(aContext
, aOffset
, aNChars
);
1833 gboolean
IMContextWrapper::OnDeleteSurroundingNative(GtkIMContext
* aContext
,
1836 MOZ_LOG(gIMELog
, LogLevel::Info
,
1837 ("0x%p OnDeleteSurroundingNative(aContext=0x%p, aOffset=%d, "
1838 "aNChar=%d), current context=0x%p",
1839 this, aContext
, aOffset
, aNChars
, GetCurrentContext()));
1841 // See bug 472635, we should do nothing if IM context doesn't match.
1842 if (GetCurrentContext() != aContext
) {
1843 MOZ_LOG(gIMELog
, LogLevel::Error
,
1844 ("0x%p OnDeleteSurroundingNative(), FAILED, "
1845 "given context doesn't match",
1850 AutoRestore
<bool> saveDeletingSurrounding(mIsDeletingSurrounding
);
1851 mIsDeletingSurrounding
= true;
1852 if (NS_SUCCEEDED(DeleteText(aContext
, aOffset
, (uint32_t)aNChars
))) {
1857 MOZ_LOG(gIMELog
, LogLevel::Error
,
1858 ("0x%p OnDeleteSurroundingNative(), FAILED, "
1859 "cannot delete text",
1865 void IMContextWrapper::OnCommitCompositionCallback(GtkIMContext
* aContext
,
1866 const gchar
* aString
,
1867 IMContextWrapper
* aModule
) {
1868 aModule
->OnCommitCompositionNative(aContext
, aString
);
1871 void IMContextWrapper::OnCommitCompositionNative(GtkIMContext
* aContext
,
1872 const gchar
* aUTF8Char
) {
1873 const gchar emptyStr
= 0;
1874 const gchar
* commitString
= aUTF8Char
? aUTF8Char
: &emptyStr
;
1875 NS_ConvertUTF8toUTF16
utf16CommitString(commitString
);
1877 // IME may synthesize composition asynchronously after filtering a
1878 // GDK_KEY_PRESS event. In that case, we should handle composition with
1879 // emulating the usual case, i.e., this is called in the stack of
1881 Maybe
<AutoRestore
<GdkEventKey
*>> maybeRestoreProcessingKeyEvent
;
1882 if (!mProcessingKeyEvent
&& !mPostingKeyEvents
.IsEmpty()) {
1883 GdkEventKey
* keyEvent
= mPostingKeyEvents
.GetFirstEvent();
1884 if (keyEvent
&& keyEvent
->type
== GDK_KEY_PRESS
&&
1885 KeymapWrapper::ComputeDOMKeyNameIndex(keyEvent
) ==
1886 KEY_NAME_INDEX_USE_STRING
) {
1887 maybeRestoreProcessingKeyEvent
.emplace(mProcessingKeyEvent
);
1888 mProcessingKeyEvent
= mPostingKeyEvents
.GetFirstEvent();
1892 MOZ_LOG(gIMELog
, LogLevel::Info
,
1893 ("0x%p OnCommitCompositionNative(aContext=0x%p), "
1894 "current context=0x%p, active context=0x%p, commitString=\"%s\", "
1895 "mProcessingKeyEvent=0x%p, IsComposingOn(aContext)=%s",
1896 this, aContext
, GetCurrentContext(), GetActiveContext(),
1897 commitString
, mProcessingKeyEvent
, ToChar(IsComposingOn(aContext
))));
1899 // See bug 472635, we should do nothing if IM context doesn't match.
1900 if (!IsValidContext(aContext
)) {
1901 MOZ_LOG(gIMELog
, LogLevel::Error
,
1902 ("0x%p OnCommitCompositionNative(), FAILED, "
1903 "given context doesn't match",
1908 // If we are not in composition and committing with empty string,
1909 // we need to do nothing because if we continued to handle this
1910 // signal, we would dispatch compositionstart, text, compositionend
1911 // events with empty string. Of course, they are unnecessary events
1912 // for Web applications and our editor.
1913 if (!IsComposingOn(aContext
) && utf16CommitString
.IsEmpty()) {
1914 MOZ_LOG(gIMELog
, LogLevel::Warning
,
1915 ("0x%p OnCommitCompositionNative(), Warning, does nothing "
1916 "because has not started composition and commit string is empty",
1921 // If IME doesn't change their keyevent that generated this commit,
1922 // we should treat that IME didn't handle the key event because
1923 // web applications want to receive "keydown" and "keypress" event
1925 // NOTE: While a key event is being handled, this might be caused on
1926 // current context. Otherwise, this may be caused on active context.
1927 if (!IsComposingOn(aContext
) && mProcessingKeyEvent
&&
1928 mProcessingKeyEvent
->type
== GDK_KEY_PRESS
&&
1929 aContext
== GetCurrentContext()) {
1930 char keyval_utf8
[8]; /* should have at least 6 bytes of space */
1931 gint keyval_utf8_len
;
1932 guint32 keyval_unicode
;
1934 keyval_unicode
= gdk_keyval_to_unicode(mProcessingKeyEvent
->keyval
);
1935 keyval_utf8_len
= g_unichar_to_utf8(keyval_unicode
, keyval_utf8
);
1936 keyval_utf8
[keyval_utf8_len
] = '\0';
1938 // If committing string is exactly same as a character which is
1939 // produced by the key, eKeyDown and eKeyPress event should be
1940 // dispatched by the caller of OnKeyEvent() normally. Note that
1941 // mMaybeInDeadKeySequence will be set to false by OnKeyEvent()
1942 // since we set mFallbackToKeyEvent to true here.
1943 if (!strcmp(commitString
, keyval_utf8
)) {
1944 MOZ_LOG(gIMELog
, LogLevel::Info
,
1945 ("0x%p OnCommitCompositionNative(), "
1946 "we'll send normal key event",
1948 mFallbackToKeyEvent
= true;
1952 // If we're in a dead key sequence, commit string is a character in
1953 // the BMP and mProcessingKeyEvent produces some characters but it's
1954 // not same as committing string, we should dispatch an eKeyPress
1956 WidgetKeyboardEvent
keyDownEvent(true, eKeyDown
, mLastFocusedWindow
);
1957 KeymapWrapper::InitKeyEvent(keyDownEvent
, mProcessingKeyEvent
, false);
1958 if (mMaybeInDeadKeySequence
&& utf16CommitString
.Length() == 1 &&
1959 keyDownEvent
.mKeyNameIndex
== KEY_NAME_INDEX_USE_STRING
) {
1960 mKeyboardEventWasDispatched
= true;
1961 // Anyway, we're not in dead key sequence anymore.
1962 mMaybeInDeadKeySequence
= false;
1964 RefPtr
<TextEventDispatcher
> dispatcher
= GetTextEventDispatcher();
1965 nsresult rv
= dispatcher
->BeginNativeInputTransaction();
1966 if (NS_WARN_IF(NS_FAILED(rv
))) {
1967 MOZ_LOG(gIMELog
, LogLevel::Error
,
1968 ("0x%p OnCommitCompositionNative(), FAILED, "
1969 "due to BeginNativeInputTransaction() failure",
1974 // First, dispatch eKeyDown event.
1975 keyDownEvent
.mKeyValue
= utf16CommitString
;
1976 nsEventStatus status
= nsEventStatus_eIgnore
;
1977 bool dispatched
= dispatcher
->DispatchKeyboardEvent(
1978 eKeyDown
, keyDownEvent
, status
, mProcessingKeyEvent
);
1979 if (!dispatched
|| status
== nsEventStatus_eConsumeNoDefault
) {
1980 mKeyboardEventWasConsumed
= true;
1981 MOZ_LOG(gIMELog
, LogLevel::Info
,
1982 ("0x%p OnCommitCompositionNative(), "
1983 "doesn't dispatch eKeyPress event because the preceding "
1984 "eKeyDown event was not dispatched or was consumed",
1988 if (mLastFocusedWindow
!= keyDownEvent
.mWidget
||
1989 mLastFocusedWindow
->Destroyed()) {
1990 MOZ_LOG(gIMELog
, LogLevel::Warning
,
1991 ("0x%p OnCommitCompositionNative(), Warning, "
1992 "stop dispatching eKeyPress event because the preceding "
1993 "eKeyDown event caused changing focused widget or "
1998 MOZ_LOG(gIMELog
, LogLevel::Info
,
1999 ("0x%p OnCommitCompositionNative(), "
2000 "dispatched eKeyDown event for the committed character",
2003 // Next, dispatch eKeyPress event.
2004 dispatcher
->MaybeDispatchKeypressEvents(keyDownEvent
, status
,
2005 mProcessingKeyEvent
);
2006 MOZ_LOG(gIMELog
, LogLevel::Info
,
2007 ("0x%p OnCommitCompositionNative(), "
2008 "dispatched eKeyPress event for the committed character",
2014 NS_ConvertUTF8toUTF16
str(commitString
);
2015 // Be aware, widget can be gone
2016 DispatchCompositionCommitEvent(aContext
, &str
);
2019 void IMContextWrapper::GetCompositionString(GtkIMContext
* aContext
,
2020 nsAString
& aCompositionString
) {
2021 gchar
* preedit_string
;
2023 PangoAttrList
* feedback_list
;
2024 gtk_im_context_get_preedit_string(aContext
, &preedit_string
, &feedback_list
,
2026 if (preedit_string
&& *preedit_string
) {
2027 CopyUTF8toUTF16(MakeStringSpan(preedit_string
), aCompositionString
);
2029 aCompositionString
.Truncate();
2032 MOZ_LOG(gIMELog
, LogLevel::Info
,
2033 ("0x%p GetCompositionString(aContext=0x%p), "
2034 "aCompositionString=\"%s\"",
2035 this, aContext
, preedit_string
));
2037 pango_attr_list_unref(feedback_list
);
2038 g_free(preedit_string
);
2041 bool IMContextWrapper::MaybeDispatchKeyEventAsProcessedByIME(
2042 EventMessage aFollowingEvent
) {
2043 if (!mLastFocusedWindow
) {
2047 if (!mIsKeySnooped
&&
2048 ((!mProcessingKeyEvent
&& mPostingKeyEvents
.IsEmpty()) ||
2049 (mProcessingKeyEvent
&& mKeyboardEventWasDispatched
))) {
2053 // A "keydown" or "keyup" event handler may change focus with the
2054 // following event. In such case, we need to cancel this composition.
2055 // So, we need to store IM context now because mComposingContext may be
2056 // overwritten with different context if calling this method recursively.
2057 // Note that we don't need to grab the context here because |context|
2058 // will be used only for checking if it's same as mComposingContext.
2059 GtkIMContext
* oldCurrentContext
= GetCurrentContext();
2060 GtkIMContext
* oldComposingContext
= mComposingContext
;
2062 RefPtr
<nsWindow
> lastFocusedWindow(mLastFocusedWindow
);
2064 if (mProcessingKeyEvent
|| !mPostingKeyEvents
.IsEmpty()) {
2065 if (mProcessingKeyEvent
) {
2066 mKeyboardEventWasDispatched
= true;
2068 // If we're not handling a key event synchronously, the signal may be
2069 // sent by IME without sending key event to us. In such case, we
2070 // should dispatch keyboard event for the last key event which was
2071 // posted to other IME process.
2072 GdkEventKey
* sourceEvent
= mProcessingKeyEvent
2073 ? mProcessingKeyEvent
2074 : mPostingKeyEvents
.GetFirstEvent();
2077 gIMELog
, LogLevel::Info
,
2078 ("0x%p MaybeDispatchKeyEventAsProcessedByIME("
2079 "aFollowingEvent=%s), dispatch %s %s "
2080 "event: { type=%s, keyval=%s, unicode=0x%X, state=%s, "
2081 "time=%u, hardware_keycode=%u, group=%u }",
2082 this, ToChar(aFollowingEvent
),
2083 ToChar(sourceEvent
->type
== GDK_KEY_PRESS
? eKeyDown
: eKeyUp
),
2084 mProcessingKeyEvent
? "processing" : "posted",
2085 GetEventType(sourceEvent
), gdk_keyval_name(sourceEvent
->keyval
),
2086 gdk_keyval_to_unicode(sourceEvent
->keyval
),
2087 GetEventStateName(sourceEvent
->state
, mIMContextID
).get(),
2088 sourceEvent
->time
, sourceEvent
->hardware_keycode
, sourceEvent
->group
));
2090 // Let's dispatch eKeyDown event or eKeyUp event now. Note that only
2091 // when we're not in a dead key composition, we should mark the
2092 // eKeyDown and eKeyUp event as "processed by IME" since we should
2093 // expose raw keyCode and key value to web apps the key event is a
2094 // part of a dead key sequence.
2095 // FYI: We should ignore if default of preceding keydown or keyup
2096 // event is prevented since even on the other browsers, web
2097 // applications cannot cancel the following composition event.
2098 // Spec bug: https://github.com/w3c/uievents/issues/180
2099 KeymapWrapper::DispatchKeyDownOrKeyUpEvent(lastFocusedWindow
, sourceEvent
,
2100 !mMaybeInDeadKeySequence
,
2101 &mKeyboardEventWasConsumed
);
2102 MOZ_LOG(gIMELog
, LogLevel::Info
,
2103 ("0x%p MaybeDispatchKeyEventAsProcessedByIME(), keydown or keyup "
2104 "event is dispatched",
2107 if (!mProcessingKeyEvent
) {
2108 MOZ_LOG(gIMELog
, LogLevel::Info
,
2109 ("0x%p MaybeDispatchKeyEventAsProcessedByIME(), removing first "
2110 "event from the queue",
2112 mPostingKeyEvents
.RemoveEvent(sourceEvent
);
2115 MOZ_ASSERT(mIsKeySnooped
);
2116 // Currently, we support key snooper mode of uim and wayland only.
2117 MOZ_ASSERT(mIMContextID
== IMContextID::Uim
||
2118 mIMContextID
== IMContextID::Wayland
);
2119 // uim sends "preedit_start" signal and "preedit_changed" separately
2120 // at starting composition, "commit" and "preedit_end" separately at
2121 // committing composition.
2123 // Currently, we should dispatch only fake eKeyDown event because
2124 // we cannot decide which is the last signal of each key operation
2125 // and Chromium also dispatches only "keydown" event in this case.
2126 bool dispatchFakeKeyDown
= false;
2127 switch (aFollowingEvent
) {
2128 case eCompositionStart
:
2129 case eCompositionCommit
:
2130 case eCompositionCommitAsIs
:
2131 case eContentCommandInsertText
:
2132 dispatchFakeKeyDown
= true;
2134 // XXX Unfortunately, I don't have a good idea to prevent to
2135 // dispatch redundant eKeyDown event for eCompositionStart
2136 // immediately after "delete_surrounding" signal. However,
2137 // not dispatching eKeyDown event is worse than dispatching
2138 // redundant eKeyDown events.
2139 case eContentCommandDelete
:
2140 dispatchFakeKeyDown
= true;
2142 // We need to prevent to dispatch redundant eKeyDown event for
2143 // eCompositionChange immediately after eCompositionStart. So,
2144 // We should not dispatch eKeyDown event if dispatched composition
2145 // string is still empty string.
2146 case eCompositionChange
:
2147 dispatchFakeKeyDown
= !mDispatchedCompositionString
.IsEmpty();
2150 MOZ_ASSERT_UNREACHABLE("Do you forget to handle the case?");
2154 if (dispatchFakeKeyDown
) {
2155 WidgetKeyboardEvent
fakeKeyDownEvent(true, eKeyDown
, lastFocusedWindow
);
2156 fakeKeyDownEvent
.mKeyCode
= NS_VK_PROCESSKEY
;
2157 fakeKeyDownEvent
.mKeyNameIndex
= KEY_NAME_INDEX_Process
;
2158 // It's impossible to get physical key information in this case but
2159 // this should be okay since web apps shouldn't do anything with
2160 // physical key information during composition.
2161 fakeKeyDownEvent
.mCodeNameIndex
= CODE_NAME_INDEX_UNKNOWN
;
2163 MOZ_LOG(gIMELog
, LogLevel::Info
,
2164 ("0x%p MaybeDispatchKeyEventAsProcessedByIME("
2165 "aFollowingEvent=%s), dispatch fake eKeyDown event",
2166 this, ToChar(aFollowingEvent
)));
2168 KeymapWrapper::DispatchKeyDownOrKeyUpEvent(
2169 lastFocusedWindow
, fakeKeyDownEvent
, &mKeyboardEventWasConsumed
);
2170 MOZ_LOG(gIMELog
, LogLevel::Info
,
2171 ("0x%p MaybeDispatchKeyEventAsProcessedByIME(), "
2172 "fake keydown event is dispatched",
2177 if (lastFocusedWindow
->IsDestroyed() ||
2178 lastFocusedWindow
!= mLastFocusedWindow
) {
2179 MOZ_LOG(gIMELog
, LogLevel::Warning
,
2180 ("0x%p MaybeDispatchKeyEventAsProcessedByIME(), Warning, the "
2181 "focused widget was destroyed/changed by a key event",
2186 // If the dispatched keydown event caused moving focus and that also
2187 // caused changing active context, we need to cancel composition here.
2188 if (GetCurrentContext() != oldCurrentContext
) {
2189 MOZ_LOG(gIMELog
, LogLevel::Warning
,
2190 ("0x%p MaybeDispatchKeyEventAsProcessedByIME(), Warning, the key "
2191 "event causes changing active IM context",
2193 if (mComposingContext
== oldComposingContext
) {
2194 // Only when the context is still composing, we should call
2195 // ResetIME() here. Otherwise, it should've already been
2205 bool IMContextWrapper::DispatchCompositionStart(GtkIMContext
* aContext
) {
2206 MOZ_LOG(gIMELog
, LogLevel::Info
,
2207 ("0x%p DispatchCompositionStart(aContext=0x%p)", this, aContext
));
2209 if (IsComposing()) {
2210 MOZ_LOG(gIMELog
, LogLevel::Error
,
2211 ("0x%p DispatchCompositionStart(), FAILED, "
2212 "we're already in composition",
2217 if (!mLastFocusedWindow
) {
2218 MOZ_LOG(gIMELog
, LogLevel::Error
,
2219 ("0x%p DispatchCompositionStart(), FAILED, "
2220 "there are no focused window in this module",
2225 if (NS_WARN_IF(!EnsureToCacheContentSelection())) {
2226 MOZ_LOG(gIMELog
, LogLevel::Error
,
2227 ("0x%p DispatchCompositionStart(), FAILED, "
2228 "cannot query the selection offset",
2233 if (NS_WARN_IF(!mContentSelection
->HasRange())) {
2234 MOZ_LOG(gIMELog
, LogLevel::Error
,
2235 ("0x%p DispatchCompositionStart(), FAILED, "
2236 "due to no selection",
2241 mComposingContext
= static_cast<GtkIMContext
*>(g_object_ref(aContext
));
2242 MOZ_ASSERT(mComposingContext
);
2244 // Keep the last focused window alive
2245 RefPtr
<nsWindow
> lastFocusedWindow(mLastFocusedWindow
);
2247 // XXX The composition start point might be changed by composition events
2248 // even though we strongly hope it doesn't happen.
2249 // Every composition event should have the start offset for the result
2250 // because it may high cost if we query the offset every time.
2251 mCompositionStart
= mContentSelection
->OffsetAndDataRef().StartOffset();
2252 mDispatchedCompositionString
.Truncate();
2254 // If this composition is started by a key press, we need to dispatch
2255 // eKeyDown or eKeyUp event before dispatching eCompositionStart event.
2256 // Note that dispatching a keyboard event which is marked as "processed
2257 // by IME" is okay since Chromium also dispatches keyboard event as so.
2258 if (!MaybeDispatchKeyEventAsProcessedByIME(eCompositionStart
)) {
2259 MOZ_LOG(gIMELog
, LogLevel::Warning
,
2260 ("0x%p DispatchCompositionStart(), Warning, "
2261 "MaybeDispatchKeyEventAsProcessedByIME() returned false",
2266 RefPtr
<TextEventDispatcher
> dispatcher
= GetTextEventDispatcher();
2267 nsresult rv
= dispatcher
->BeginNativeInputTransaction();
2268 if (NS_WARN_IF(NS_FAILED(rv
))) {
2269 MOZ_LOG(gIMELog
, LogLevel::Error
,
2270 ("0x%p DispatchCompositionStart(), FAILED, "
2271 "due to BeginNativeInputTransaction() failure",
2276 static bool sHasSetTelemetry
= false;
2277 if (!sHasSetTelemetry
) {
2278 sHasSetTelemetry
= true;
2279 NS_ConvertUTF8toUTF16
im(GetIMName());
2280 // 72 is kMaximumKeyStringLength in TelemetryScalar.cpp
2281 if (im
.Length() > 72) {
2282 if (NS_IS_SURROGATE_PAIR(im
[72 - 2], im
[72 - 1])) {
2283 im
.Truncate(72 - 2);
2285 im
.Truncate(72 - 1);
2288 im
.Append(char16_t(0x2026));
2290 Telemetry::ScalarSet(Telemetry::ScalarID::WIDGET_IME_NAME_ON_LINUX
, im
,
2294 MOZ_LOG(gIMELog
, LogLevel::Debug
,
2295 ("0x%p DispatchCompositionStart(), dispatching "
2296 "compositionstart... (mCompositionStart=%u)",
2297 this, mCompositionStart
));
2298 mCompositionState
= eCompositionState_CompositionStartDispatched
;
2299 nsEventStatus status
;
2300 dispatcher
->StartComposition(status
);
2301 if (lastFocusedWindow
->IsDestroyed() ||
2302 lastFocusedWindow
!= mLastFocusedWindow
) {
2303 MOZ_LOG(gIMELog
, LogLevel::Error
,
2304 ("0x%p DispatchCompositionStart(), FAILED, the focused "
2305 "widget was destroyed/changed by compositionstart event",
2313 bool IMContextWrapper::DispatchCompositionChangeEvent(
2314 GtkIMContext
* aContext
, const nsAString
& aCompositionString
) {
2316 gIMELog
, LogLevel::Info
,
2317 ("0x%p DispatchCompositionChangeEvent(aContext=0x%p)", this, aContext
));
2319 if (!mLastFocusedWindow
) {
2320 MOZ_LOG(gIMELog
, LogLevel::Error
,
2321 ("0x%p DispatchCompositionChangeEvent(), FAILED, "
2322 "there are no focused window in this module",
2327 if (!IsComposing()) {
2328 MOZ_LOG(gIMELog
, LogLevel::Debug
,
2329 ("0x%p DispatchCompositionChangeEvent(), the composition "
2330 "wasn't started, force starting...",
2332 if (!DispatchCompositionStart(aContext
)) {
2336 // If this composition string change caused by a key press, we need to
2337 // dispatch eKeyDown or eKeyUp before dispatching eCompositionChange event.
2338 else if (!MaybeDispatchKeyEventAsProcessedByIME(eCompositionChange
)) {
2339 MOZ_LOG(gIMELog
, LogLevel::Warning
,
2340 ("0x%p DispatchCompositionChangeEvent(), Warning, "
2341 "MaybeDispatchKeyEventAsProcessedByIME() returned false",
2346 RefPtr
<TextEventDispatcher
> dispatcher
= GetTextEventDispatcher();
2347 nsresult rv
= dispatcher
->BeginNativeInputTransaction();
2348 if (NS_WARN_IF(NS_FAILED(rv
))) {
2349 MOZ_LOG(gIMELog
, LogLevel::Error
,
2350 ("0x%p DispatchCompositionChangeEvent(), FAILED, "
2351 "due to BeginNativeInputTransaction() failure",
2356 // Store the selected string which will be removed by following
2357 // compositionchange event.
2358 if (mCompositionState
== eCompositionState_CompositionStartDispatched
) {
2359 if (NS_WARN_IF(!EnsureToCacheContentSelection(
2360 &mSelectedStringRemovedByComposition
))) {
2361 // XXX How should we behave in this case??
2362 } else if (mContentSelection
->HasRange()) {
2363 // XXX We should assume, for now, any web applications don't change
2364 // selection at handling this compositionchange event.
2365 mCompositionStart
= mContentSelection
->OffsetAndDataRef().StartOffset();
2367 // If there is no selection range, we should keep previously storing
2368 // mCompositionStart.
2372 RefPtr
<TextRangeArray
> rangeArray
=
2373 CreateTextRangeArray(aContext
, aCompositionString
);
2375 rv
= dispatcher
->SetPendingComposition(aCompositionString
, rangeArray
);
2376 if (NS_WARN_IF(NS_FAILED(rv
))) {
2377 MOZ_LOG(gIMELog
, LogLevel::Error
,
2378 ("0x%p DispatchCompositionChangeEvent(), FAILED, "
2379 "due to SetPendingComposition() failure",
2384 mCompositionState
= eCompositionState_CompositionChangeEventDispatched
;
2386 // We cannot call SetCursorPosition for e10s-aware.
2387 // DispatchEvent is async on e10s, so composition rect isn't updated now
2389 mDispatchedCompositionString
= aCompositionString
;
2390 mLayoutChanged
= false;
2391 mCompositionTargetRange
.mOffset
=
2392 mCompositionStart
+ rangeArray
->TargetClauseOffset();
2393 mCompositionTargetRange
.mLength
= rangeArray
->TargetClauseLength();
2395 RefPtr
<nsWindow
> lastFocusedWindow(mLastFocusedWindow
);
2396 nsEventStatus status
;
2397 rv
= dispatcher
->FlushPendingComposition(status
);
2398 if (NS_WARN_IF(NS_FAILED(rv
))) {
2399 MOZ_LOG(gIMELog
, LogLevel::Error
,
2400 ("0x%p DispatchCompositionChangeEvent(), FAILED, "
2401 "due to FlushPendingComposition() failure",
2406 if (lastFocusedWindow
->IsDestroyed() ||
2407 lastFocusedWindow
!= mLastFocusedWindow
) {
2408 MOZ_LOG(gIMELog
, LogLevel::Error
,
2409 ("0x%p DispatchCompositionChangeEvent(), FAILED, the "
2410 "focused widget was destroyed/changed by "
2411 "compositionchange event",
2418 bool IMContextWrapper::DispatchCompositionCommitEvent(
2419 GtkIMContext
* aContext
, const nsAString
* aCommitString
) {
2420 MOZ_LOG(gIMELog
, LogLevel::Info
,
2421 ("0x%p DispatchCompositionCommitEvent(aContext=0x%p, "
2422 "aCommitString=0x%p, (\"%s\"))",
2423 this, aContext
, aCommitString
,
2424 aCommitString
? NS_ConvertUTF16toUTF8(*aCommitString
).get() : ""));
2426 if (!mLastFocusedWindow
) {
2427 MOZ_LOG(gIMELog
, LogLevel::Error
,
2428 ("0x%p DispatchCompositionCommitEvent(), FAILED, "
2429 "there are no focused window in this module",
2434 // TODO: We need special care to handle request to commit composition
2435 // by content while we're committing composition because we have
2436 // commit string information now but IME may not have composition
2437 // anymore. Therefore, we may not be able to handle commit as
2438 // expected. However, this is rare case because this situation
2439 // never occurs with remote content. So, it's okay to fix this
2440 // issue later. (Perhaps, TextEventDisptcher should do it for
2441 // all platforms. E.g., creating WillCommitComposition()?)
2442 RefPtr
<nsWindow
> lastFocusedWindow(mLastFocusedWindow
);
2443 RefPtr
<TextEventDispatcher
> dispatcher
;
2444 if (!IsComposing() &&
2445 !StaticPrefs::intl_ime_use_composition_events_for_insert_text()) {
2446 if (!aCommitString
|| aCommitString
->IsEmpty()) {
2447 MOZ_LOG(gIMELog
, LogLevel::Error
,
2448 ("0x%p DispatchCompositionCommitEvent(), FAILED, "
2449 "did nothing due to inserting empty string without composition",
2453 if (MOZ_UNLIKELY(!EnsureToCacheContentSelection())) {
2454 MOZ_LOG(gIMELog
, LogLevel::Warning
,
2455 ("0x%p DispatchCompositionCommitEvent(), Warning, "
2456 "Failed to cache selection before dispatching "
2457 "eContentCommandInsertText event",
2460 if (!MaybeDispatchKeyEventAsProcessedByIME(eContentCommandInsertText
)) {
2461 MOZ_LOG(gIMELog
, LogLevel::Warning
,
2462 ("0x%p DispatchCompositionCommitEvent(), Warning, "
2463 "MaybeDispatchKeyEventAsProcessedByIME() returned false",
2467 // Emulate selection until receiving actual selection range. This is
2468 // important for OnSelectionChange. If selection is not changed by web
2469 // apps, i.e., selection range is same as what selection expects, we
2470 // shouldn't reset IME because the trigger of causing this commit may be an
2471 // input for next composition and we shouldn't cancel it.
2472 if (mContentSelection
.isSome()) {
2473 mContentSelection
->Collapse(
2474 (mContentSelection
->HasRange()
2475 ? mContentSelection
->OffsetAndDataRef().StartOffset()
2476 : mCompositionStart
) +
2477 aCommitString
->Length());
2478 MOZ_LOG(gIMELog
, LogLevel::Info
,
2479 ("0x%p DispatchCompositionCommitEvent(), mContentSelection=%s",
2480 this, ToString(mContentSelection
).c_str()));
2482 MOZ_ASSERT(!dispatcher
);
2484 if (!IsComposing()) {
2485 if (!aCommitString
|| aCommitString
->IsEmpty()) {
2486 MOZ_LOG(gIMELog
, LogLevel::Error
,
2487 ("0x%p DispatchCompositionCommitEvent(), FAILED, "
2488 "there is no composition and empty commit string",
2492 MOZ_LOG(gIMELog
, LogLevel::Debug
,
2493 ("0x%p DispatchCompositionCommitEvent(), "
2494 "the composition wasn't started, force starting...",
2496 if (!DispatchCompositionStart(aContext
)) {
2500 // If this commit caused by a key press, we need to dispatch eKeyDown or
2501 // eKeyUp before dispatching composition events.
2502 else if (!MaybeDispatchKeyEventAsProcessedByIME(
2503 aCommitString
? eCompositionCommit
: eCompositionCommitAsIs
)) {
2504 MOZ_LOG(gIMELog
, LogLevel::Warning
,
2505 ("0x%p DispatchCompositionCommitEvent(), Warning, "
2506 "MaybeDispatchKeyEventAsProcessedByIME() returned false",
2508 mCompositionState
= eCompositionState_NotComposing
;
2512 dispatcher
= GetTextEventDispatcher();
2513 MOZ_ASSERT(dispatcher
);
2514 nsresult rv
= dispatcher
->BeginNativeInputTransaction();
2515 if (NS_WARN_IF(NS_FAILED(rv
))) {
2516 MOZ_LOG(gIMELog
, LogLevel::Error
,
2517 ("0x%p DispatchCompositionCommitEvent(), FAILED, "
2518 "due to BeginNativeInputTransaction() failure",
2523 // Emulate selection until receiving actual selection range.
2524 const uint32_t offsetToPutCaret
=
2525 mCompositionStart
+ (aCommitString
2526 ? aCommitString
->Length()
2527 : mDispatchedCompositionString
.Length());
2528 if (mContentSelection
.isSome()) {
2529 mContentSelection
->Collapse(offsetToPutCaret
);
2531 // TODO: We should guarantee that there should be at least fake selection
2532 // for IME at here. Then, we can keep the last writing mode.
2533 mContentSelection
.emplace(offsetToPutCaret
, WritingMode());
2537 mCompositionState
= eCompositionState_NotComposing
;
2538 // Reset dead key sequence too because GTK doesn't support dead key chain
2539 // (i.e., a key press doesn't cause both producing some characters and
2540 // restarting new dead key sequence at one time). So, committing
2541 // composition means end of a dead key sequence.
2542 mMaybeInDeadKeySequence
= false;
2543 mCompositionStart
= UINT32_MAX
;
2544 mCompositionTargetRange
.Clear();
2545 mDispatchedCompositionString
.Truncate();
2546 mSelectedStringRemovedByComposition
.Truncate();
2549 MOZ_ASSERT(aCommitString
);
2550 MOZ_ASSERT(!aCommitString
->IsEmpty());
2551 nsEventStatus status
= nsEventStatus_eIgnore
;
2552 WidgetContentCommandEvent
insertTextEvent(true, eContentCommandInsertText
,
2554 insertTextEvent
.mString
.emplace(*aCommitString
);
2555 lastFocusedWindow
->DispatchEvent(&insertTextEvent
, status
);
2557 if (!insertTextEvent
.mSucceeded
) {
2558 MOZ_LOG(gIMELog
, LogLevel::Error
,
2559 ("0x%p DispatchCompositionChangeEvent(), FAILED, inserting "
2565 nsEventStatus status
= nsEventStatus_eIgnore
;
2566 nsresult rv
= dispatcher
->CommitComposition(status
, aCommitString
);
2567 if (NS_WARN_IF(NS_FAILED(rv
))) {
2568 MOZ_LOG(gIMELog
, LogLevel::Error
,
2569 ("0x%p DispatchCompositionChangeEvent(), FAILED, "
2570 "due to CommitComposition() failure",
2576 if (lastFocusedWindow
->IsDestroyed() ||
2577 lastFocusedWindow
!= mLastFocusedWindow
) {
2578 MOZ_LOG(gIMELog
, LogLevel::Error
,
2579 ("0x%p DispatchCompositionCommitEvent(), FAILED, "
2580 "the focused widget was destroyed/changed by "
2581 "compositioncommit event",
2589 already_AddRefed
<TextRangeArray
> IMContextWrapper::CreateTextRangeArray(
2590 GtkIMContext
* aContext
, const nsAString
& aCompositionString
) {
2591 MOZ_LOG(gIMELog
, LogLevel::Info
,
2592 ("0x%p CreateTextRangeArray(aContext=0x%p, "
2593 "aCompositionString=\"%s\" (Length()=%zu))",
2594 this, aContext
, NS_ConvertUTF16toUTF8(aCompositionString
).get(),
2595 aCompositionString
.Length()));
2597 RefPtr
<TextRangeArray
> textRangeArray
= new TextRangeArray();
2599 gchar
* preedit_string
;
2600 gint cursor_pos_in_chars
;
2601 PangoAttrList
* feedback_list
;
2602 gtk_im_context_get_preedit_string(aContext
, &preedit_string
, &feedback_list
,
2603 &cursor_pos_in_chars
);
2604 if (!preedit_string
|| !*preedit_string
) {
2605 if (!aCompositionString
.IsEmpty()) {
2606 MOZ_LOG(gIMELog
, LogLevel::Error
,
2607 ("0x%p CreateTextRangeArray(), FAILED, due to "
2608 "preedit_string is null",
2611 pango_attr_list_unref(feedback_list
);
2612 g_free(preedit_string
);
2613 return textRangeArray
.forget();
2616 // Convert caret offset from offset in characters to offset in UTF-16
2617 // string. If we couldn't proper offset in UTF-16 string, we should
2618 // assume that the caret is at the end of the composition string.
2619 uint32_t caretOffsetInUTF16
= aCompositionString
.Length();
2620 if (NS_WARN_IF(cursor_pos_in_chars
< 0)) {
2621 // Note that this case is undocumented. We should assume that the
2622 // caret is at the end of the composition string.
2623 } else if (cursor_pos_in_chars
== 0) {
2624 caretOffsetInUTF16
= 0;
2626 gchar
* charAfterCaret
=
2627 g_utf8_offset_to_pointer(preedit_string
, cursor_pos_in_chars
);
2628 if (NS_WARN_IF(!charAfterCaret
)) {
2629 MOZ_LOG(gIMELog
, LogLevel::Warning
,
2630 ("0x%p CreateTextRangeArray(), failed to get UTF-8 "
2631 "string before the caret (cursor_pos_in_chars=%d)",
2632 this, cursor_pos_in_chars
));
2634 glong caretOffset
= 0;
2635 gunichar2
* utf16StrBeforeCaret
=
2636 g_utf8_to_utf16(preedit_string
, charAfterCaret
- preedit_string
,
2637 nullptr, &caretOffset
, nullptr);
2638 if (NS_WARN_IF(!utf16StrBeforeCaret
) || NS_WARN_IF(caretOffset
< 0)) {
2639 MOZ_LOG(gIMELog
, LogLevel::Warning
,
2640 ("0x%p CreateTextRangeArray(), WARNING, failed to "
2641 "convert to UTF-16 string before the caret "
2642 "(cursor_pos_in_chars=%d, caretOffset=%ld)",
2643 this, cursor_pos_in_chars
, caretOffset
));
2645 caretOffsetInUTF16
= static_cast<uint32_t>(caretOffset
);
2646 uint32_t compositionStringLength
= aCompositionString
.Length();
2647 if (NS_WARN_IF(caretOffsetInUTF16
> compositionStringLength
)) {
2648 MOZ_LOG(gIMELog
, LogLevel::Warning
,
2649 ("0x%p CreateTextRangeArray(), WARNING, "
2650 "caretOffsetInUTF16=%u is larger than "
2651 "compositionStringLength=%u",
2652 this, caretOffsetInUTF16
, compositionStringLength
));
2653 caretOffsetInUTF16
= compositionStringLength
;
2656 if (utf16StrBeforeCaret
) {
2657 g_free(utf16StrBeforeCaret
);
2662 PangoAttrIterator
* iter
;
2663 iter
= pango_attr_list_get_iterator(feedback_list
);
2665 MOZ_LOG(gIMELog
, LogLevel::Error
,
2666 ("0x%p CreateTextRangeArray(), FAILED, iterator couldn't "
2669 pango_attr_list_unref(feedback_list
);
2670 g_free(preedit_string
);
2671 return textRangeArray
.forget();
2674 uint32_t minOffsetOfClauses
= aCompositionString
.Length();
2675 uint32_t maxOffsetOfClauses
= 0;
2678 if (!SetTextRange(iter
, preedit_string
, caretOffsetInUTF16
, range
)) {
2681 MOZ_ASSERT(range
.Length());
2682 minOffsetOfClauses
= std::min(minOffsetOfClauses
, range
.mStartOffset
);
2683 maxOffsetOfClauses
= std::max(maxOffsetOfClauses
, range
.mEndOffset
);
2684 textRangeArray
->AppendElement(range
);
2685 } while (pango_attr_iterator_next(iter
));
2687 // If the IME doesn't define clause from the start of the composition,
2688 // we should insert dummy clause information since TextRangeArray assumes
2689 // that there must be a clause whose start is 0 when there is one or
2691 if (minOffsetOfClauses
) {
2692 TextRange dummyClause
;
2693 dummyClause
.mStartOffset
= 0;
2694 dummyClause
.mEndOffset
= minOffsetOfClauses
;
2695 dummyClause
.mRangeType
= TextRangeType::eRawClause
;
2696 textRangeArray
->InsertElementAt(0, dummyClause
);
2697 maxOffsetOfClauses
= std::max(maxOffsetOfClauses
, dummyClause
.mEndOffset
);
2698 MOZ_LOG(gIMELog
, LogLevel::Warning
,
2699 ("0x%p CreateTextRangeArray(), inserting a dummy clause "
2700 "at the beginning of the composition string mStartOffset=%u, "
2701 "mEndOffset=%u, mRangeType=%s",
2702 this, dummyClause
.mStartOffset
, dummyClause
.mEndOffset
,
2703 ToChar(dummyClause
.mRangeType
)));
2706 // If the IME doesn't define clause at end of the composition, we should
2707 // insert dummy clause information since TextRangeArray assumes that there
2708 // must be a clase whose end is the length of the composition string when
2709 // there is one or more clauses.
2710 if (!textRangeArray
->IsEmpty() &&
2711 maxOffsetOfClauses
< aCompositionString
.Length()) {
2712 TextRange dummyClause
;
2713 dummyClause
.mStartOffset
= maxOffsetOfClauses
;
2714 dummyClause
.mEndOffset
= aCompositionString
.Length();
2715 dummyClause
.mRangeType
= TextRangeType::eRawClause
;
2716 textRangeArray
->AppendElement(dummyClause
);
2717 MOZ_LOG(gIMELog
, LogLevel::Warning
,
2718 ("0x%p CreateTextRangeArray(), inserting a dummy clause "
2719 "at the end of the composition string mStartOffset=%u, "
2720 "mEndOffset=%u, mRangeType=%s",
2721 this, dummyClause
.mStartOffset
, dummyClause
.mEndOffset
,
2722 ToChar(dummyClause
.mRangeType
)));
2726 range
.mStartOffset
= range
.mEndOffset
= caretOffsetInUTF16
;
2727 range
.mRangeType
= TextRangeType::eCaret
;
2728 textRangeArray
->AppendElement(range
);
2730 gIMELog
, LogLevel::Debug
,
2731 ("0x%p CreateTextRangeArray(), mStartOffset=%u, "
2732 "mEndOffset=%u, mRangeType=%s",
2733 this, range
.mStartOffset
, range
.mEndOffset
, ToChar(range
.mRangeType
)));
2735 pango_attr_iterator_destroy(iter
);
2736 pango_attr_list_unref(feedback_list
);
2737 g_free(preedit_string
);
2739 return textRangeArray
.forget();
2743 nscolor
IMContextWrapper::ToNscolor(PangoAttrColor
* aPangoAttrColor
) {
2744 PangoColor
& pangoColor
= aPangoAttrColor
->color
;
2745 uint8_t r
= pangoColor
.red
/ 0x100;
2746 uint8_t g
= pangoColor
.green
/ 0x100;
2747 uint8_t b
= pangoColor
.blue
/ 0x100;
2748 return NS_RGB(r
, g
, b
);
2751 bool IMContextWrapper::SetTextRange(PangoAttrIterator
* aPangoAttrIter
,
2752 const gchar
* aUTF8CompositionString
,
2753 uint32_t aUTF16CaretOffset
,
2754 TextRange
& aTextRange
) const {
2755 // Set the range offsets in UTF-16 string.
2756 gint utf8ClauseStart
, utf8ClauseEnd
;
2757 pango_attr_iterator_range(aPangoAttrIter
, &utf8ClauseStart
, &utf8ClauseEnd
);
2758 if (utf8ClauseStart
== utf8ClauseEnd
) {
2759 MOZ_LOG(gIMELog
, LogLevel::Error
,
2760 ("0x%p SetTextRange(), FAILED, due to collapsed range", this));
2764 if (!utf8ClauseStart
) {
2765 aTextRange
.mStartOffset
= 0;
2767 glong utf16PreviousClausesLength
;
2768 gunichar2
* utf16PreviousClausesString
=
2769 g_utf8_to_utf16(aUTF8CompositionString
, utf8ClauseStart
, nullptr,
2770 &utf16PreviousClausesLength
, nullptr);
2772 if (NS_WARN_IF(!utf16PreviousClausesString
)) {
2773 MOZ_LOG(gIMELog
, LogLevel::Error
,
2774 ("0x%p SetTextRange(), FAILED, due to g_utf8_to_utf16() "
2775 "failure (retrieving previous string of current clause)",
2780 aTextRange
.mStartOffset
= utf16PreviousClausesLength
;
2781 g_free(utf16PreviousClausesString
);
2784 glong utf16CurrentClauseLength
;
2785 gunichar2
* utf16CurrentClauseString
= g_utf8_to_utf16(
2786 aUTF8CompositionString
+ utf8ClauseStart
, utf8ClauseEnd
- utf8ClauseStart
,
2787 nullptr, &utf16CurrentClauseLength
, nullptr);
2789 if (NS_WARN_IF(!utf16CurrentClauseString
)) {
2790 MOZ_LOG(gIMELog
, LogLevel::Error
,
2791 ("0x%p SetTextRange(), FAILED, due to g_utf8_to_utf16() "
2792 "failure (retrieving current clause)",
2797 // iBus Chewing IME tells us that there is an empty clause at the end of
2798 // the composition string but we should ignore it since our code doesn't
2799 // assume that there is an empty clause.
2800 if (!utf16CurrentClauseLength
) {
2801 MOZ_LOG(gIMELog
, LogLevel::Warning
,
2802 ("0x%p SetTextRange(), FAILED, due to current clause length "
2808 aTextRange
.mEndOffset
= aTextRange
.mStartOffset
+ utf16CurrentClauseLength
;
2809 g_free(utf16CurrentClauseString
);
2810 utf16CurrentClauseString
= nullptr;
2813 TextRangeStyle
& style
= aTextRange
.mRangeStyle
;
2816 PangoAttrInt
* attrUnderline
= reinterpret_cast<PangoAttrInt
*>(
2817 pango_attr_iterator_get(aPangoAttrIter
, PANGO_ATTR_UNDERLINE
));
2818 if (attrUnderline
) {
2819 switch (attrUnderline
->value
) {
2820 case PANGO_UNDERLINE_NONE
:
2821 style
.mLineStyle
= TextRangeStyle::LineStyle::None
;
2823 case PANGO_UNDERLINE_DOUBLE
:
2824 style
.mLineStyle
= TextRangeStyle::LineStyle::Double
;
2826 case PANGO_UNDERLINE_ERROR
:
2827 style
.mLineStyle
= TextRangeStyle::LineStyle::Wavy
;
2829 case PANGO_UNDERLINE_SINGLE
:
2830 case PANGO_UNDERLINE_LOW
:
2831 style
.mLineStyle
= TextRangeStyle::LineStyle::Solid
;
2834 MOZ_LOG(gIMELog
, LogLevel::Warning
,
2835 ("0x%p SetTextRange(), retrieved unknown underline "
2837 this, attrUnderline
->value
));
2838 style
.mLineStyle
= TextRangeStyle::LineStyle::Solid
;
2841 style
.mDefinedStyles
|= TextRangeStyle::DEFINED_LINESTYLE
;
2844 PangoAttrColor
* attrUnderlineColor
= reinterpret_cast<PangoAttrColor
*>(
2845 pango_attr_iterator_get(aPangoAttrIter
, PANGO_ATTR_UNDERLINE_COLOR
));
2846 if (attrUnderlineColor
) {
2847 style
.mUnderlineColor
= ToNscolor(attrUnderlineColor
);
2848 style
.mDefinedStyles
|= TextRangeStyle::DEFINED_UNDERLINE_COLOR
;
2851 style
.mLineStyle
= TextRangeStyle::LineStyle::None
;
2852 style
.mDefinedStyles
|= TextRangeStyle::DEFINED_LINESTYLE
;
2855 // Don't set colors if they are not specified. They should be computed by
2856 // textframe if only one of the colors are specified.
2858 // Foreground color (text color)
2859 PangoAttrColor
* attrForeground
= reinterpret_cast<PangoAttrColor
*>(
2860 pango_attr_iterator_get(aPangoAttrIter
, PANGO_ATTR_FOREGROUND
));
2861 if (attrForeground
) {
2862 style
.mForegroundColor
= ToNscolor(attrForeground
);
2863 style
.mDefinedStyles
|= TextRangeStyle::DEFINED_FOREGROUND_COLOR
;
2867 PangoAttrColor
* attrBackground
= reinterpret_cast<PangoAttrColor
*>(
2868 pango_attr_iterator_get(aPangoAttrIter
, PANGO_ATTR_BACKGROUND
));
2869 if (attrBackground
) {
2870 style
.mBackgroundColor
= ToNscolor(attrBackground
);
2871 style
.mDefinedStyles
|= TextRangeStyle::DEFINED_BACKGROUND_COLOR
;
2875 * We need to judge the meaning of the clause for a11y. Before we support
2876 * IME specific composition string style, we used following rules:
2878 * 1: If attrUnderline and attrForground are specified, we assumed the
2879 * clause is TextRangeType::eSelectedClause.
2880 * 2: If only attrUnderline is specified, we assumed the clause is
2881 * TextRangeType::eConvertedClause.
2882 * 3: If only attrForground is specified, we assumed the clause is
2883 * TextRangeType::eSelectedRawClause.
2884 * 4: If neither attrUnderline nor attrForeground is specified, we assumed
2885 * the clause is TextRangeType::eRawClause.
2887 * However, this rules are odd since there can be two or more selected
2888 * clauses. Additionally, our old rules caused that IME developers/users
2889 * cannot specify composition string style as they want.
2891 * So, we shouldn't guess the meaning from its visual style.
2894 // If the range covers whole of composition string and the caret is at
2895 // the end of the composition string, the range is probably not converted.
2896 if (!utf8ClauseStart
&&
2897 utf8ClauseEnd
== static_cast<gint
>(strlen(aUTF8CompositionString
)) &&
2898 aTextRange
.mEndOffset
== aUTF16CaretOffset
) {
2899 aTextRange
.mRangeType
= TextRangeType::eRawClause
;
2901 // Typically, the caret is set at the start of the selected clause.
2902 // So, if the caret is in the clause, we can assume that the clause is
2904 else if (aTextRange
.mStartOffset
<= aUTF16CaretOffset
&&
2905 aTextRange
.mEndOffset
> aUTF16CaretOffset
) {
2906 aTextRange
.mRangeType
= TextRangeType::eSelectedClause
;
2908 // Otherwise, we should assume that the clause is converted but not
2911 aTextRange
.mRangeType
= TextRangeType::eConvertedClause
;
2914 MOZ_LOG(gIMELog
, LogLevel::Debug
,
2915 ("0x%p SetTextRange(), succeeded, aTextRange= { "
2916 "mStartOffset=%u, mEndOffset=%u, mRangeType=%s, mRangeStyle=%s }",
2917 this, aTextRange
.mStartOffset
, aTextRange
.mEndOffset
,
2918 ToChar(aTextRange
.mRangeType
),
2919 GetTextRangeStyleText(aTextRange
.mRangeStyle
).get()));
2924 void IMContextWrapper::SetCursorPosition(GtkIMContext
* aContext
) {
2926 gIMELog
, LogLevel::Info
,
2927 ("0x%p SetCursorPosition(aContext=0x%p), "
2928 "mCompositionTargetRange={ mOffset=%u, mLength=%u }, "
2929 "mContentSelection=%s",
2930 this, aContext
, mCompositionTargetRange
.mOffset
,
2931 mCompositionTargetRange
.mLength
, ToString(mContentSelection
).c_str()));
2933 bool useCaret
= false;
2934 if (!mCompositionTargetRange
.IsValid()) {
2935 if (mContentSelection
.isNothing()) {
2936 MOZ_LOG(gIMELog
, LogLevel::Error
,
2937 ("0x%p SetCursorPosition(), FAILED, "
2938 "mCompositionTargetRange and mContentSelection are invalid",
2942 if (!mContentSelection
->HasRange()) {
2943 MOZ_LOG(gIMELog
, LogLevel::Warning
,
2944 ("0x%p SetCursorPosition(), FAILED, "
2945 "mCompositionTargetRange is invalid and there is no selection",
2952 if (!mLastFocusedWindow
) {
2953 MOZ_LOG(gIMELog
, LogLevel::Error
,
2954 ("0x%p SetCursorPosition(), FAILED, due to no focused "
2960 if (MOZ_UNLIKELY(!aContext
)) {
2961 MOZ_LOG(gIMELog
, LogLevel::Error
,
2962 ("0x%p SetCursorPosition(), FAILED, due to no context", this));
2966 WidgetQueryContentEvent
queryCaretOrTextRectEvent(
2967 true, useCaret
? eQueryCaretRect
: eQueryTextRect
, mLastFocusedWindow
);
2969 queryCaretOrTextRectEvent
.InitForQueryCaretRect(
2970 mContentSelection
->OffsetAndDataRef().StartOffset());
2972 if (mContentSelection
->WritingModeRef().IsVertical()) {
2973 // For preventing the candidate window to overlap the target
2974 // clause, we should set fake (typically, very tall) caret rect.
2976 mCompositionTargetRange
.mLength
? mCompositionTargetRange
.mLength
: 1;
2977 queryCaretOrTextRectEvent
.InitForQueryTextRect(
2978 mCompositionTargetRange
.mOffset
, length
);
2980 queryCaretOrTextRectEvent
.InitForQueryTextRect(
2981 mCompositionTargetRange
.mOffset
, 1);
2984 nsEventStatus status
;
2985 mLastFocusedWindow
->DispatchEvent(&queryCaretOrTextRectEvent
, status
);
2986 if (queryCaretOrTextRectEvent
.Failed()) {
2987 MOZ_LOG(gIMELog
, LogLevel::Error
,
2988 ("0x%p SetCursorPosition(), FAILED, %s was failed", this,
2989 useCaret
? "eQueryCaretRect" : "eQueryTextRect"));
2993 nsWindow
* rootWindow
=
2994 static_cast<nsWindow
*>(mLastFocusedWindow
->GetTopLevelWidget());
2996 // Get the position of the rootWindow in screen.
2997 LayoutDeviceIntPoint root
= rootWindow
->WidgetToScreenOffset();
2999 // Get the position of IM context owner window in screen.
3000 LayoutDeviceIntPoint owner
= mOwnerWindow
->WidgetToScreenOffset();
3002 // Compute the caret position in the IM owner window.
3003 LayoutDeviceIntRect rect
=
3004 queryCaretOrTextRectEvent
.mReply
->mRect
+ root
- owner
;
3006 GdkRectangle area
= rootWindow
->DevicePixelsToGdkRectRoundOut(rect
);
3008 gtk_im_context_set_cursor_location(aContext
, &area
);
3011 nsresult
IMContextWrapper::GetCurrentParagraph(nsAString
& aText
,
3012 uint32_t& aCursorPos
) {
3013 MOZ_LOG(gIMELog
, LogLevel::Info
,
3014 ("0x%p GetCurrentParagraph(), mCompositionState=%s", this,
3015 GetCompositionStateName()));
3017 if (!mLastFocusedWindow
) {
3018 MOZ_LOG(gIMELog
, LogLevel::Error
,
3019 ("0x%p GetCurrentParagraph(), FAILED, there are no "
3020 "focused window in this module",
3022 return NS_ERROR_NULL_POINTER
;
3025 nsEventStatus status
;
3027 uint32_t selOffset
= mCompositionStart
;
3028 uint32_t selLength
= mSelectedStringRemovedByComposition
.Length();
3030 // If focused editor doesn't have composition string, we should use
3031 // current selection.
3032 if (!EditorHasCompositionString()) {
3033 // Query cursor position & selection
3034 if (NS_WARN_IF(!EnsureToCacheContentSelection())) {
3035 MOZ_LOG(gIMELog
, LogLevel::Error
,
3036 ("0x%p GetCurrentParagraph(), FAILED, due to no "
3037 "valid selection information",
3039 return NS_ERROR_FAILURE
;
3042 if (mContentSelection
.isSome() && mContentSelection
->HasRange()) {
3043 selOffset
= mContentSelection
->OffsetAndDataRef().StartOffset();
3044 selLength
= mContentSelection
->OffsetAndDataRef().Length();
3046 // If there is no range, let's get all text instead...
3048 selLength
= INT32_MAX
; // TODO: Change to UINT32_MAX, but see below
3052 MOZ_LOG(gIMELog
, LogLevel::Debug
,
3053 ("0x%p GetCurrentParagraph(), selOffset=%u, selLength=%u", this,
3054 selOffset
, selLength
));
3056 // XXX nsString::Find and nsString::RFind take int32_t for offset, so,
3057 // we cannot support this request when the current offset is larger
3059 if (selOffset
> INT32_MAX
|| selLength
> INT32_MAX
||
3060 selOffset
+ selLength
> INT32_MAX
) {
3061 MOZ_LOG(gIMELog
, LogLevel::Error
,
3062 ("0x%p GetCurrentParagraph(), FAILED, The selection is "
3065 return NS_ERROR_FAILURE
;
3068 // Get all text contents of the focused editor
3069 WidgetQueryContentEvent
queryTextContentEvent(true, eQueryTextContent
,
3070 mLastFocusedWindow
);
3071 queryTextContentEvent
.InitForQueryTextContent(0, UINT32_MAX
);
3072 mLastFocusedWindow
->DispatchEvent(&queryTextContentEvent
, status
);
3073 if (NS_WARN_IF(queryTextContentEvent
.Failed())) {
3074 return NS_ERROR_FAILURE
;
3077 if (selOffset
+ selLength
> queryTextContentEvent
.mReply
->DataLength()) {
3078 MOZ_LOG(gIMELog
, LogLevel::Error
,
3079 ("0x%p GetCurrentParagraph(), FAILED, The selection is "
3080 "invalid, queryTextContentEvent={ mReply=%s }",
3081 this, ToString(queryTextContentEvent
.mReply
).c_str()));
3082 return NS_ERROR_FAILURE
;
3085 // Remove composing string and restore the selected string because
3086 // GtkEntry doesn't remove selected string until committing, however,
3087 // our editor does it. We should emulate the behavior for IME.
3088 nsAutoString
textContent(queryTextContentEvent
.mReply
->DataRef());
3089 if (EditorHasCompositionString() &&
3090 mDispatchedCompositionString
!= mSelectedStringRemovedByComposition
) {
3091 textContent
.Replace(mCompositionStart
,
3092 mDispatchedCompositionString
.Length(),
3093 mSelectedStringRemovedByComposition
);
3096 // Get only the focused paragraph, by looking for newlines
3097 int32_t parStart
= 0;
3098 if (selOffset
> 0) {
3099 parStart
= Substring(textContent
, 0, selOffset
- 1).RFind(u
"\n") + 1;
3101 int32_t parEnd
= textContent
.Find(u
"\n", selOffset
+ selLength
);
3103 parEnd
= textContent
.Length();
3105 aText
= nsDependentSubstring(textContent
, parStart
, parEnd
- parStart
);
3106 aCursorPos
= selOffset
- uint32_t(parStart
);
3109 gIMELog
, LogLevel::Debug
,
3110 ("0x%p GetCurrentParagraph(), succeeded, aText=%s, "
3111 "aText.Length()=%zu, aCursorPos=%u",
3112 this, NS_ConvertUTF16toUTF8(aText
).get(), aText
.Length(), aCursorPos
));
3117 nsresult
IMContextWrapper::DeleteText(GtkIMContext
* aContext
, int32_t aOffset
,
3119 MOZ_LOG(gIMELog
, LogLevel::Info
,
3120 ("0x%p DeleteText(aContext=0x%p, aOffset=%d, aNChars=%u), "
3121 "mCompositionState=%s",
3122 this, aContext
, aOffset
, aNChars
, GetCompositionStateName()));
3124 if (!mLastFocusedWindow
) {
3125 MOZ_LOG(gIMELog
, LogLevel::Error
,
3126 ("0x%p DeleteText(), FAILED, there are no focused window "
3129 return NS_ERROR_NULL_POINTER
;
3133 MOZ_LOG(gIMELog
, LogLevel::Error
,
3134 ("0x%p DeleteText(), FAILED, aNChars must not be zero", this));
3135 return NS_ERROR_INVALID_ARG
;
3138 RefPtr
<nsWindow
> lastFocusedWindow(mLastFocusedWindow
);
3139 nsEventStatus status
;
3141 // First, we should cancel current composition because editor cannot
3142 // handle changing selection and deleting text.
3144 bool wasComposing
= IsComposing();
3145 bool editorHadCompositionString
= EditorHasCompositionString();
3147 selOffset
= mCompositionStart
;
3148 if (!DispatchCompositionCommitEvent(aContext
,
3149 &mSelectedStringRemovedByComposition
)) {
3150 MOZ_LOG(gIMELog
, LogLevel::Error
,
3151 ("0x%p DeleteText(), FAILED, quitting from DeletText", this));
3152 return NS_ERROR_FAILURE
;
3155 if (NS_WARN_IF(!EnsureToCacheContentSelection())) {
3156 MOZ_LOG(gIMELog
, LogLevel::Error
,
3157 ("0x%p DeleteText(), FAILED, due to no valid selection "
3160 return NS_ERROR_FAILURE
;
3162 if (!mContentSelection
->HasRange()) {
3163 MOZ_LOG(gIMELog
, LogLevel::Debug
,
3164 ("0x%p DeleteText(), does nothing, due to no selection range",
3168 selOffset
= mContentSelection
->OffsetAndDataRef().StartOffset();
3171 // Get all text contents of the focused editor
3172 WidgetQueryContentEvent
queryTextContentEvent(true, eQueryTextContent
,
3173 mLastFocusedWindow
);
3174 queryTextContentEvent
.InitForQueryTextContent(0, UINT32_MAX
);
3175 mLastFocusedWindow
->DispatchEvent(&queryTextContentEvent
, status
);
3176 if (NS_WARN_IF(queryTextContentEvent
.Failed())) {
3177 return NS_ERROR_FAILURE
;
3179 if (queryTextContentEvent
.mReply
->IsDataEmpty()) {
3180 MOZ_LOG(gIMELog
, LogLevel::Error
,
3181 ("0x%p DeleteText(), FAILED, there is no contents", this));
3182 return NS_ERROR_FAILURE
;
3185 NS_ConvertUTF16toUTF8
utf8Str(nsDependentSubstring(
3186 queryTextContentEvent
.mReply
->DataRef(), 0, selOffset
));
3187 glong offsetInUTF8Characters
=
3188 g_utf8_strlen(utf8Str
.get(), utf8Str
.Length()) + aOffset
;
3189 if (offsetInUTF8Characters
< 0) {
3190 MOZ_LOG(gIMELog
, LogLevel::Error
,
3191 ("0x%p DeleteText(), FAILED, aOffset is too small for "
3192 "current cursor pos (computed offset: %ld)",
3193 this, offsetInUTF8Characters
));
3194 return NS_ERROR_FAILURE
;
3198 nsDependentSubstring(queryTextContentEvent
.mReply
->DataRef(), selOffset
),
3200 glong countOfCharactersInUTF8
=
3201 g_utf8_strlen(utf8Str
.get(), utf8Str
.Length());
3202 glong endInUTF8Characters
= offsetInUTF8Characters
+ aNChars
;
3203 if (countOfCharactersInUTF8
< endInUTF8Characters
) {
3204 MOZ_LOG(gIMELog
, LogLevel::Error
,
3205 ("0x%p DeleteText(), FAILED, aNChars is too large for "
3206 "current contents (content length: %ld, computed end offset: %ld)",
3207 this, countOfCharactersInUTF8
, endInUTF8Characters
));
3208 return NS_ERROR_FAILURE
;
3211 gchar
* charAtOffset
=
3212 g_utf8_offset_to_pointer(utf8Str
.get(), offsetInUTF8Characters
);
3214 g_utf8_offset_to_pointer(utf8Str
.get(), endInUTF8Characters
);
3216 // Set selection to delete
3217 WidgetSelectionEvent
selectionEvent(true, eSetSelection
, mLastFocusedWindow
);
3219 nsDependentCSubstring
utf8StrBeforeOffset(utf8Str
, 0,
3220 charAtOffset
- utf8Str
.get());
3221 selectionEvent
.mOffset
= NS_ConvertUTF8toUTF16(utf8StrBeforeOffset
).Length();
3223 nsDependentCSubstring
utf8DeletingStr(utf8Str
, utf8StrBeforeOffset
.Length(),
3224 charAtEnd
- charAtOffset
);
3225 selectionEvent
.mLength
= NS_ConvertUTF8toUTF16(utf8DeletingStr
).Length();
3227 selectionEvent
.mReversed
= false;
3228 selectionEvent
.mExpandToClusterBoundary
= false;
3229 lastFocusedWindow
->DispatchEvent(&selectionEvent
, status
);
3231 if (!selectionEvent
.mSucceeded
|| lastFocusedWindow
!= mLastFocusedWindow
||
3232 lastFocusedWindow
->Destroyed()) {
3233 MOZ_LOG(gIMELog
, LogLevel::Error
,
3234 ("0x%p DeleteText(), FAILED, setting selection caused "
3235 "focus change or window destroyed",
3237 return NS_ERROR_FAILURE
;
3240 // If this deleting text caused by a key press, we need to dispatch
3241 // eKeyDown or eKeyUp before dispatching eContentCommandDelete event.
3242 if (!MaybeDispatchKeyEventAsProcessedByIME(eContentCommandDelete
)) {
3243 MOZ_LOG(gIMELog
, LogLevel::Warning
,
3244 ("0x%p DeleteText(), Warning, "
3245 "MaybeDispatchKeyEventAsProcessedByIME() returned false",
3247 return NS_ERROR_FAILURE
;
3250 // Delete the selection
3251 WidgetContentCommandEvent
contentCommandEvent(true, eContentCommandDelete
,
3252 mLastFocusedWindow
);
3253 mLastFocusedWindow
->DispatchEvent(&contentCommandEvent
, status
);
3255 if (!contentCommandEvent
.mSucceeded
||
3256 lastFocusedWindow
!= mLastFocusedWindow
||
3257 lastFocusedWindow
->Destroyed()) {
3258 MOZ_LOG(gIMELog
, LogLevel::Error
,
3259 ("0x%p DeleteText(), FAILED, deleting the selection caused "
3260 "focus change or window destroyed",
3262 return NS_ERROR_FAILURE
;
3265 if (!wasComposing
) {
3269 // Restore the composition at new caret position.
3270 if (!DispatchCompositionStart(aContext
)) {
3272 gIMELog
, LogLevel::Error
,
3273 ("0x%p DeleteText(), FAILED, resterting composition start", this));
3274 return NS_ERROR_FAILURE
;
3277 if (!editorHadCompositionString
) {
3281 nsAutoString compositionString
;
3282 GetCompositionString(aContext
, compositionString
);
3283 if (!DispatchCompositionChangeEvent(aContext
, compositionString
)) {
3285 gIMELog
, LogLevel::Error
,
3286 ("0x%p DeleteText(), FAILED, restoring composition string", this));
3287 return NS_ERROR_FAILURE
;
3293 bool IMContextWrapper::EnsureToCacheContentSelection(
3294 nsAString
* aSelectedString
) {
3295 if (aSelectedString
) {
3296 aSelectedString
->Truncate();
3299 if (mContentSelection
.isSome()) {
3300 if (mContentSelection
->HasRange() && aSelectedString
) {
3301 aSelectedString
->Assign(mContentSelection
->OffsetAndDataRef().DataRef());
3306 RefPtr
<nsWindow
> dispatcherWindow
=
3307 mLastFocusedWindow
? mLastFocusedWindow
: mOwnerWindow
;
3308 if (NS_WARN_IF(!dispatcherWindow
)) {
3309 MOZ_LOG(gIMELog
, LogLevel::Error
,
3310 ("0x%p EnsureToCacheContentSelection(), FAILED, due to "
3311 "no focused window",
3316 nsEventStatus status
;
3317 WidgetQueryContentEvent
querySelectedTextEvent(true, eQuerySelectedText
,
3319 dispatcherWindow
->DispatchEvent(&querySelectedTextEvent
, status
);
3320 if (NS_WARN_IF(querySelectedTextEvent
.Failed())) {
3321 MOZ_LOG(gIMELog
, LogLevel::Error
,
3322 ("0x%p EnsureToCacheContentSelection(), FAILED, due to "
3323 "failure of query selection event",
3328 mContentSelection
= Some(ContentSelection(querySelectedTextEvent
));
3329 if (mContentSelection
->HasRange()) {
3330 if (!mContentSelection
->OffsetAndDataRef().IsDataEmpty() &&
3332 aSelectedString
->Assign(querySelectedTextEvent
.mReply
->DataRef());
3337 gIMELog
, LogLevel::Debug
,
3338 ("0x%p EnsureToCacheContentSelection(), Succeeded, mContentSelection=%s",
3339 this, ToString(mContentSelection
).c_str()));
3343 } // namespace widget
3344 } // namespace mozilla