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