--- a/widget/gtk/IMContextWrapper.cpp
+++ b/widget/gtk/IMContextWrapper.cpp
@@ -339,31 +339,52 @@ IMContextWrapper::Init()
G_CALLBACK(IMContextWrapper::OnStartCompositionCallback), this);
g_signal_connect(mContext, "preedit_end",
G_CALLBACK(IMContextWrapper::OnEndCompositionCallback), this);
nsDependentCString contextID(
gtk_im_multicontext_get_context_id(GTK_IM_MULTICONTEXT(mContext)));
if (contextID.EqualsLiteral("ibus")) {
mIMContextID = IMContextID::eIBus;
mIsIMInAsyncKeyHandlingMode = !IsIBusInSyncMode();
+ // Although ibus has key snooper mode, it's forcibly disabled on Firefox
+ // in default settings by its whitelist since we always send key events
+ // to IME before handling shortcut keys. The whitelist can be
+ // customized with env, IBUS_NO_SNOOPER_APPS, but we don't need to
+ // support such rare cases for reducing maintenance cost.
+ mIsKeySnooped = false;
} else if (contextID.EqualsLiteral("fcitx")) {
mIMContextID = IMContextID::eFcitx;
mIsIMInAsyncKeyHandlingMode = !IsFcitxInSyncMode();
+ // Although Fcitx has key snooper mode similar to ibus, it's also
+ // disabled on Firefox in default settings by its whitelist. The
+ // whitelist can be customized with env, IBUS_NO_SNOOPER_APPS or
+ // FCITX_NO_SNOOPER_APPS, but we don't need to support such rare cases
+ // for reducing maintenance cost.
+ mIsKeySnooped = false;
} else if (contextID.EqualsLiteral("uim")) {
mIMContextID = IMContextID::eUim;
mIsIMInAsyncKeyHandlingMode = false;
+ // We cannot know if uim uses key snooper since it's build option of
+ // uim. Therefore, we need to retrieve the consideration from the
+ // pref for making users and distributions allowed to choose their
+ // preferred value.
+ mIsKeySnooped =
+ Preferences::GetBool("intl.ime.hack.uim.using_key_snooper", true);
} else if (contextID.EqualsLiteral("scim")) {
mIMContextID = IMContextID::eScim;
mIsIMInAsyncKeyHandlingMode = false;
+ mIsKeySnooped = false;
} else if (contextID.EqualsLiteral("iiim")) {
mIMContextID = IMContextID::eIIIMF;
mIsIMInAsyncKeyHandlingMode = false;
+ mIsKeySnooped = false;
} else {
mIMContextID = IMContextID::eUnknown;
mIsIMInAsyncKeyHandlingMode = false;
+ mIsKeySnooped = false;
}
// Simple context
if (sUseSimpleContext) {
mSimpleContext = gtk_im_context_simple_new();
gtk_im_context_set_client_window(mSimpleContext, gdkWindow);
g_signal_connect(mSimpleContext, "preedit_changed",
G_CALLBACK(&IMContextWrapper::OnChangeCompositionCallback),
@@ -386,20 +407,21 @@ IMContextWrapper::Init()
}
// Dummy context
mDummyContext = gtk_im_multicontext_new();
gtk_im_context_set_client_window(mDummyContext, gdkWindow);
MOZ_LOG(gGtkIMLog, LogLevel::Info,
("0x%p Init(), mOwnerWindow=%p, mContext=%p (%s), "
- "mIsIMInAsyncKeyHandlingMode=%s, mSimpleContext=%p, "
- "mDummyContext=%p",
+ "mIsIMInAsyncKeyHandlingMode=%s, mIsKeySnooped=%s, "
+ "mSimpleContext=%p, mDummyContext=%p",
this, mOwnerWindow, mContext, contextID.get(),
- ToChar(mIsIMInAsyncKeyHandlingMode), mSimpleContext, mDummyContext));
+ ToChar(mIsIMInAsyncKeyHandlingMode), ToChar(mIsKeySnooped),
+ mSimpleContext, mDummyContext));
}
IMContextWrapper::~IMContextWrapper()
{
if (this == sLastFocusedContext) {
sLastFocusedContext = nullptr;
}
MOZ_LOG(gGtkIMLog, LogLevel::Info,
@@ -795,17 +817,17 @@ IMContextWrapper::OnKeyEvent(nsWindow* a
filterThisEvent = false;
}
if (filterThisEvent && !mKeyboardEventWasDispatched) {
// If IME handled the key event but we've not dispatched eKeyDown nor
// eKeyUp event yet, we need to dispatch here unless the key event is
// now being handled by other IME process.
if (!maybeHandledAsynchronously) {
- MaybeDispatchKeyEventAsProcessedByIME();
+ MaybeDispatchKeyEventAsProcessedByIME(eVoidEvent);
// Be aware, the widget might have been gone here.
}
// If we need to wait reply from IM, IM may send some signals to us
// without sending the key event again. In such case, we need to
// dispatch keyboard events with a copy of aEvent. Therefore, we
// need to use information of this key event to dispatch an KeyDown
// or eKeyUp event later.
else {
@@ -1694,81 +1716,152 @@ IMContextWrapper::GetCompositionString(G
"aCompositionString=\"%s\"",
this, aContext, preedit_string));
pango_attr_list_unref(feedback_list);
g_free(preedit_string);
}
bool
-IMContextWrapper::MaybeDispatchKeyEventAsProcessedByIME()
+IMContextWrapper::MaybeDispatchKeyEventAsProcessedByIME(
+ EventMessage aFollowingEvent)
{
- if ((!mProcessingKeyEvent && mPostingKeyEvents.IsEmpty()) ||
- (mProcessingKeyEvent && mKeyboardEventWasDispatched) ||
- !mLastFocusedWindow) {
+ if (!mLastFocusedWindow) {
+ return false;
+ }
+
+ if (!mIsKeySnooped &&
+ ((!mProcessingKeyEvent && mPostingKeyEvents.IsEmpty()) ||
+ (mProcessingKeyEvent && mKeyboardEventWasDispatched))) {
return true;
}
// A "keydown" or "keyup" event handler may change focus with the
// following event. In such case, we need to cancel this composition.
// So, we need to store IM context now because mComposingContext may be
// overwritten with different context if calling this method recursively.
// Note that we don't need to grab the context here because |context|
// will be used only for checking if it's same as mComposingContext.
GtkIMContext* oldCurrentContext = GetCurrentContext();
GtkIMContext* oldComposingContext = mComposingContext;
RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
- if (mProcessingKeyEvent) {
- mKeyboardEventWasDispatched = true;
- }
+
+ if (mProcessingKeyEvent || !mPostingKeyEvents.IsEmpty()) {
+ if (mProcessingKeyEvent) {
+ mKeyboardEventWasDispatched = true;
+ }
+ // If we're not handling a key event synchronously, the signal may be
+ // sent by IME without sending key event to us. In such case, we
+ // should dispatch keyboard event for the last key event which was
+ // posted to other IME process.
+ GdkEventKey* sourceEvent =
+ mProcessingKeyEvent ? mProcessingKeyEvent :
+ mPostingKeyEvents.GetFirstEvent();
- // If we're not handling a key event synchronously, the signal may be
- // sent by IME without sending key event to us. In such case, we should
- // dispatch keyboard event for the last key event which was posted to
- // other IME process.
- GdkEventKey* sourceEvent =
- mProcessingKeyEvent ? mProcessingKeyEvent :
- mPostingKeyEvents.GetFirstEvent();
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p MaybeDispatchKeyEventAsProcessedByIME("
+ "aFollowingEvent=%s), dispatch %s %s "
+ "event: { type=%s, keyval=%s, unicode=0x%X, state=%s, "
+ "time=%u, hardware_keycode=%u, group=%u }",
+ this, ToChar(aFollowingEvent),
+ ToChar(sourceEvent->type == GDK_KEY_PRESS ? eKeyDown : eKeyUp),
+ mProcessingKeyEvent ? "processing" : "posted",
+ GetEventType(sourceEvent), gdk_keyval_name(sourceEvent->keyval),
+ gdk_keyval_to_unicode(sourceEvent->keyval),
+ GetEventStateName(sourceEvent->state, mIMContextID).get(),
+ sourceEvent->time, sourceEvent->hardware_keycode,
+ sourceEvent->group));
- MOZ_LOG(gGtkIMLog, LogLevel::Info,
- ("0x%p MaybeDispatchKeyEventAsProcessedByIME(), dispatch %s %s "
- "event: { type=%s, keyval=%s, unicode=0x%X, state=%s, "
- "time=%u, hardware_keycode=%u, group=%u }",
- this, ToChar(sourceEvent->type == GDK_KEY_PRESS ? eKeyDown : eKeyUp),
- mProcessingKeyEvent ? "processing" : "posted",
- GetEventType(sourceEvent), gdk_keyval_name(sourceEvent->keyval),
- gdk_keyval_to_unicode(sourceEvent->keyval),
- GetEventStateName(sourceEvent->state, mIMContextID).get(),
- sourceEvent->time, sourceEvent->hardware_keycode, sourceEvent->group));
+ // Let's dispatch eKeyDown event or eKeyUp event now. Note that only
+ // when we're not in a dead key composition, we should mark the
+ // eKeyDown and eKeyUp event as "processed by IME" since we should
+ // expose raw keyCode and key value to web apps the key event is a
+ // part of a dead key sequence.
+ // FYI: We should ignore if default of preceding keydown or keyup
+ // event is prevented since even on the other browsers, web
+ // applications cannot cancel the following composition event.
+ // Spec bug: https://github.com/w3c/uievents/issues/180
+ bool isCancelled;
+ lastFocusedWindow->DispatchKeyDownOrKeyUpEvent(sourceEvent,
+ !mMaybeInDeadKeySequence,
+ &isCancelled);
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p MaybeDispatchKeyEventAsProcessedByIME(), keydown or keyup "
+ "event is dispatched",
+ this));
- // Let's dispatch eKeyDown event or eKeyUp event now. Note that only when
- // we're not in a dead key composition, we should mark the eKeyDown and
- // eKeyUp event as "processed by IME" since we should expose raw keyCode
- // and key value to web apps the key event is a part of a dead key
- // sequence.
- // FYI: We should ignore if default of preceding keydown or keyup event is
- // prevented since even on the other browsers, web applications
- // cannot cancel the following composition event.
- // Spec bug: https://github.com/w3c/uievents/issues/180
- bool isCancelled;
- lastFocusedWindow->DispatchKeyDownOrKeyUpEvent(sourceEvent,
- !mMaybeInDeadKeySequence,
- &isCancelled);
- MOZ_LOG(gGtkIMLog, LogLevel::Info,
- ("0x%p MaybeDispatchKeyEventAsProcessedByIME(), keydown or keyup "
- "event is dispatched",
- this));
+ if (!mProcessingKeyEvent) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p MaybeDispatchKeyEventAsProcessedByIME(), removing first "
+ "event from the queue",
+ this));
+ mPostingKeyEvents.RemoveEvent(sourceEvent);
+ }
+ } else {
+ MOZ_ASSERT(mIsKeySnooped);
+ // Currently, we support key snooper mode of uim only.
+ MOZ_ASSERT(mIMContextID == IMContextID::eUim);
+ // uim sends "preedit_start" signal and "preedit_changed" separately
+ // at starting composition, "commit" and "preedit_end" separately at
+ // committing composition.
- if (!mProcessingKeyEvent) {
- MOZ_LOG(gGtkIMLog, LogLevel::Info,
- ("0x%p MaybeDispatchKeyEventAsProcessedByIME(), removing first "
- "event from the queue",
- this));
- mPostingKeyEvents.RemoveEvent(sourceEvent);
+ // Currently, we should dispatch only fake eKeyDown event because
+ // we cannot decide which is the last signal of each key operation
+ // and Chromium also dispatches only "keydown" event in this case.
+ bool dispatchFakeKeyDown = false;
+ switch (aFollowingEvent) {
+ case eCompositionStart:
+ case eCompositionCommit:
+ case eCompositionCommitAsIs:
+ dispatchFakeKeyDown = true;
+ break;
+ // XXX Unfortunately, I don't have a good idea to prevent to
+ // dispatch redundant eKeyDown event for eCompositionStart
+ // immediately after "delete_surrounding" signal. However,
+ // not dispatching eKeyDown event is worse than dispatching
+ // redundant eKeyDown events.
+ case eContentCommandDelete:
+ dispatchFakeKeyDown = true;
+ break;
+ // We need to prevent to dispatch redundant eKeyDown event for
+ // eCompositionChange immediately after eCompositionStart. So,
+ // We should not dispatch eKeyDown event if dispatched composition
+ // string is still empty string.
+ case eCompositionChange:
+ dispatchFakeKeyDown = !mDispatchedCompositionString.IsEmpty();
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Do you forget to handle the case?");
+ break;
+ }
+
+ if (dispatchFakeKeyDown) {
+ WidgetKeyboardEvent fakeKeyDownEvent(true, eKeyDown,
+ lastFocusedWindow);
+ fakeKeyDownEvent.mKeyCode = NS_VK_PROCESSKEY;
+ fakeKeyDownEvent.mKeyNameIndex = KEY_NAME_INDEX_Process;
+ // It's impossible to get physical key information in this case but
+ // this should be okay since web apps shouldn't do anything with
+ // physical key information during composition.
+ fakeKeyDownEvent.mCodeNameIndex = CODE_NAME_INDEX_UNKNOWN;
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p MaybeDispatchKeyEventAsProcessedByIME("
+ "aFollowingEvent=%s), dispatch fake eKeyDown event",
+ this, ToChar(aFollowingEvent)));
+
+ bool isCancelled;
+ lastFocusedWindow->DispatchKeyDownOrKeyUpEvent(fakeKeyDownEvent,
+ &isCancelled);
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p MaybeDispatchKeyEventAsProcessedByIME(), "
+ "fake keydown event is dispatched",
+ this));
+ }
}
if (lastFocusedWindow->IsDestroyed() ||
lastFocusedWindow != mLastFocusedWindow) {
MOZ_LOG(gGtkIMLog, LogLevel::Warning,
("0x%p MaybeDispatchKeyEventAsProcessedByIME(), Warning, the "
"focused widget was destroyed/changed by a key event",
this));
@@ -1837,17 +1930,17 @@ IMContextWrapper::DispatchCompositionSta
// because it may high cost if we query the offset every time.
mCompositionStart = mSelection.mOffset;
mDispatchedCompositionString.Truncate();
// If this composition is started by a key press, we need to dispatch
// eKeyDown or eKeyUp event before dispatching eCompositionStart event.
// Note that dispatching a keyboard event which is marked as "processed
// by IME" is okay since Chromium also dispatches keyboard event as so.
- if (!MaybeDispatchKeyEventAsProcessedByIME()) {
+ if (!MaybeDispatchKeyEventAsProcessedByIME(eCompositionStart)) {
MOZ_LOG(gGtkIMLog, LogLevel::Warning,
("0x%p DispatchCompositionStart(), Warning, "
"MaybeDispatchKeyEventAsProcessedByIME() returned false",
this));
return false;
}
RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
@@ -1902,17 +1995,17 @@ IMContextWrapper::DispatchCompositionCha
"wasn't started, force starting...",
this));
if (!DispatchCompositionStart(aContext)) {
return false;
}
}
// If this composition string change caused by a key press, we need to
// dispatch eKeyDown or eKeyUp before dispatching eCompositionChange event.
- else if (!MaybeDispatchKeyEventAsProcessedByIME()) {
+ else if (!MaybeDispatchKeyEventAsProcessedByIME(eCompositionChange)) {
MOZ_LOG(gGtkIMLog, LogLevel::Warning,
("0x%p DispatchCompositionChangeEvent(), Warning, "
"MaybeDispatchKeyEventAsProcessedByIME() returned false",
this));
return false;
}
RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
@@ -2024,17 +2117,18 @@ IMContextWrapper::DispatchCompositionCom
"the composition wasn't started, force starting...",
this));
if (!DispatchCompositionStart(aContext)) {
return false;
}
}
// If this commit caused by a key press, we need to dispatch eKeyDown or
// eKeyUp before dispatching composition events.
- else if (!MaybeDispatchKeyEventAsProcessedByIME()) {
+ else if (!MaybeDispatchKeyEventAsProcessedByIME(
+ aCommitString ? eCompositionCommit : eCompositionCommitAsIs)) {
MOZ_LOG(gGtkIMLog, LogLevel::Warning,
("0x%p DispatchCompositionCommitEvent(), Warning, "
"MaybeDispatchKeyEventAsProcessedByIME() returned false",
this));
mCompositionState = eCompositionState_NotComposing;
return false;
}
@@ -2733,17 +2827,17 @@ IMContextWrapper::DeleteText(GtkIMContex
("0x%p DeleteText(), FAILED, setting selection caused "
"focus change or window destroyed",
this));
return NS_ERROR_FAILURE;
}
// If this deleting text caused by a key press, we need to dispatch
// eKeyDown or eKeyUp before dispatching eContentCommandDelete event.
- if (!MaybeDispatchKeyEventAsProcessedByIME()) {
+ if (!MaybeDispatchKeyEventAsProcessedByIME(eContentCommandDelete)) {
MOZ_LOG(gGtkIMLog, LogLevel::Warning,
("0x%p DeleteText(), Warning, "
"MaybeDispatchKeyEventAsProcessedByIME() returned false",
this));
return NS_ERROR_FAILURE;
}
// Delete the selection