/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=4 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// For collecting other people's log, tell `MOZ_LOG=IMEHandler:4,sync` // rather than `MOZ_LOG=IMEHandler:5,sync` since using `5` may create too // big file. // Therefore you shouldn't use `LogLevel::Verbose` for logging usual behavior.
mozilla::LazyLogModule gIMELog("IMEHandler");
AppendLiteral(" }");
} void AppendLineStyle(TextRangeStyle::LineStyle aLineStyle) { switch (aLineStyle) { case TextRangeStyle::LineStyle::None:
AppendLiteral("LineStyle::None"); break; case TextRangeStyle::LineStyle::Solid:
AppendLiteral("LineStyle::Solid"); break; case TextRangeStyle::LineStyle::Dotted:
AppendLiteral("LineStyle::Dotted"); break; case TextRangeStyle::LineStyle::Dashed:
AppendLiteral("LineStyle::Dashed"); break; case TextRangeStyle::LineStyle::Double:
AppendLiteral("LineStyle::Double"); break; case TextRangeStyle::LineStyle::Wavy:
AppendLiteral("LineStyle::Wavy"); break; default:
AppendPrintf("Invalid(0x%02X)", static_cast<TextRangeStyle::LineStyleType>(aLineStyle)); break;
}
} void AppendColor(nscolor aColor) {
AppendPrintf("{ R=0x%02X, G=0x%02X, B=0x%02X, A=0x%02X }", NS_GET_R(aColor),
NS_GET_G(aColor), NS_GET_B(aColor), NS_GET_A(aColor));
} virtual ~GetTextRangeStyleText() = default;
};
conststaticbool kUseSimpleContextDefault = false;
/****************************************************************************** * SelectionStyleProvider * * IME (e.g., fcitx, ~4.2.8.3) may look up selection colors of widget, which * is related to the window associated with the IM context, to support any * colored widgets. Our editor (like <input type="text">) is rendered as * native GtkTextView as far as possible by default and if editor color is * changed by web apps, nsTextFrame may swap background color of foreground * color of composition string for making composition string is always * visually distinct in normal text. * * So, we would like IME to set style of composition string to good colors * in GtkTextView. Therefore, this class overwrites selection colors of * our widget with selection colors of GtkTextView so that it's possible IME * to refer selection colors of GtkTextView via our widget.
******************************************************************************/
// aGDKWindow is a GTK window which will be associated with an IM context. void AttachTo(GdkWindow* aGDKWindow) {
GtkWidget* widget = nullptr; // gdk_window_get_user_data() typically returns pointer to widget that // window belongs to. If it's widget, fcitx retrieves selection colors // of them. So, we need to overwrite its style.
gdk_window_get_user_data(aGDKWindow, (gpointer*)&widget); if (GTK_IS_WIDGET(widget)) {
gtk_style_context_add_provider(gtk_widget_get_style_context(widget),
GTK_STYLE_PROVIDER(mProvider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
}
}
void OnThemeChanged() { // fcitx refers GtkStyle::text[GTK_STATE_SELECTED] and // GtkStyle::bg[GTK_STATE_SELECTED] (although pair of text and *base* // or *fg* and bg is correct). gtk_style_update_from_context() will // set these colors using the widget's GtkStyleContext and so the // colors can be controlled by a ":selected" CSS rule.
nsAutoCString style(":selected{"); // FYI: LookAndFeel always returns selection colors of GtkTextView. if (auto selectionForegroundColor =
GetSystemColor(LookAndFeel::ColorID::Highlight)) { double alpha = static_cast<double>(NS_GET_A(*selectionForegroundColor)) / 0xFF;
style.AppendPrintf("color:rgba(%u,%u,%u,",
NS_GET_R(*selectionForegroundColor),
NS_GET_G(*selectionForegroundColor),
NS_GET_B(*selectionForegroundColor)); // We can't use AppendPrintf here, because it does locale-specific // formatting of floating-point values.
style.AppendFloat(alpha);
style.AppendPrintf(");");
} if (auto selectionBackgroundColor =
GetSystemColor(LookAndFeel::ColorID::Highlighttext)) { double alpha = static_cast<double>(NS_GET_A(*selectionBackgroundColor)) / 0xFF;
style.AppendPrintf("background-color:rgba(%u,%u,%u,",
NS_GET_R(*selectionBackgroundColor),
NS_GET_G(*selectionBackgroundColor),
NS_GET_B(*selectionBackgroundColor));
style.AppendFloat(alpha);
style.AppendPrintf(");");
}
style.AppendLiteral("}");
gtk_css_provider_load_from_data(mProvider, style.get(), -1, nullptr);
}
// If the context is XIM, actual engine must be specified with // |XMODIFIERS=@im=foo|. constchar* xmodifiersChar = PR_GetEnv("XMODIFIERS"); if (!xmodifiersChar || !im.EqualsLiteral("xim")) { return im;
}
// Overwrite selection colors of the window before associating the window // with IM context since IME may look up selection colors via IM context // to support any colored widgets.
SelectionStyleProvider::GetInstance()->AttachTo(gdkWindow);
// NOTE: gtk_im_*_new() abort (kill) the whole process when it fails. // So, we don't need to check the result.
// Normal context.
mContext = gtk_im_multicontext_new();
gtk_im_context_set_client_window(mContext, gdkWindow);
g_signal_connect(mContext, "preedit_changed",
G_CALLBACK(IMContextWrapper::OnChangeCompositionCallback), this);
g_signal_connect(mContext, "retrieve_surrounding",
G_CALLBACK(IMContextWrapper::OnRetrieveSurroundingCallback), this);
g_signal_connect(mContext, "delete_surrounding",
G_CALLBACK(IMContextWrapper::OnDeleteSurroundingCallback), this);
g_signal_connect(mContext, "commit",
G_CALLBACK(IMContextWrapper::OnCommitCompositionCallback), this);
g_signal_connect(mContext, "preedit_start",
G_CALLBACK(IMContextWrapper::OnStartCompositionCallback), this);
g_signal_connect(mContext, "preedit_end",
G_CALLBACK(IMContextWrapper::OnEndCompositionCallback), this);
nsDependentCSubstring im = GetIMName(); if (im.EqualsLiteral("ibus")) {
mIMContextID = IMContextID::IBus;
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;
} elseif (im.EqualsLiteral("fcitx")) {
mIMContextID = IMContextID::Fcitx;
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;
} elseif (im.EqualsLiteral("fcitx5")) {
mIMContextID = IMContextID::Fcitx5;
mIsIMInAsyncKeyHandlingMode = true; // does not have sync mode.
mIsKeySnooped = false; // never use key snooper.
} elseif (im.EqualsLiteral("uim")) {
mIMContextID = IMContextID::Uim;
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);
} elseif (im.EqualsLiteral("scim")) {
mIMContextID = IMContextID::Scim;
mIsIMInAsyncKeyHandlingMode = false;
mIsKeySnooped = false;
} elseif (im.EqualsLiteral("iiim")) {
mIMContextID = IMContextID::IIIMF;
mIsIMInAsyncKeyHandlingMode = false;
mIsKeySnooped = false;
} elseif (im.EqualsLiteral("wayland")) {
mIMContextID = IMContextID::Wayland;
mIsIMInAsyncKeyHandlingMode = false;
mIsKeySnooped = true;
} else {
mIMContextID = IMContextID::Unknown;
mIsIMInAsyncKeyHandlingMode = false;
mIsKeySnooped = false;
}
NS_IMETHODIMP
IMContextWrapper::NotifyIME(TextEventDispatcher* aTextEventDispatcher, const IMENotification& aNotification) { switch (aNotification.mMessage) { case REQUEST_TO_COMMIT_COMPOSITION: case REQUEST_TO_CANCEL_COMPOSITION: {
nsWindow* window = static_cast<nsWindow*>(aTextEventDispatcher->GetWidget()); return IsComposing() ? EndIMEComposition(window) : NS_OK;
} case NOTIFY_IME_OF_FOCUS:
OnFocusChangeInGecko(true); return NS_OK; case NOTIFY_IME_OF_BLUR:
OnFocusChangeInGecko(false); return NS_OK; case NOTIFY_IME_OF_POSITION_CHANGE:
OnLayoutChange(); return NS_OK; case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
OnUpdateComposition(); return NS_OK; case NOTIFY_IME_OF_SELECTION_CHANGE: {
nsWindow* window = static_cast<nsWindow*>(aTextEventDispatcher->GetWidget());
OnSelectionChange(window, aNotification); return NS_OK;
} default: return NS_ERROR_NOT_IMPLEMENTED;
}
}
NS_IMETHODIMP_(void)
IMContextWrapper::OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) { // XXX When input transaction is being stolen by add-on, what should we do?
}
NS_IMETHODIMP_(IMENotificationRequests)
IMContextWrapper::GetIMENotificationRequests() {
IMENotificationRequests::Notifications notifications =
IMENotificationRequests::NOTIFY_NOTHING; // If it's not enabled, we don't need position change notification. if (IsEnabled()) {
notifications |= IMENotificationRequests::NOTIFY_POSITION_CHANGE;
} return IMENotificationRequests(notifications);
}
if (mLastFocusedWindow == aWindow) { if (IsComposing()) {
EndIMEComposition(aWindow);
}
NotifyIMEOfFocusChange(IMEFocusState::Blurred);
mLastFocusedWindow = nullptr;
}
if (mOwnerWindow != aWindow) { return;
}
if (sLastFocusedContext == this) {
sLastFocusedContext = nullptr;
}
/** * NOTE: * The given window is the owner of this, so, we must disconnect from the * contexts now. But that might be referred from other nsWindows * (they are children of this. But we don't know why there are the * cases). So, we need to clear the pointers that refers to contexts * and this if the other referrers are still alive. See bug 349727.
*/ if (mContext) {
PrepareToDestroyContext(mContext);
gtk_im_context_set_client_window(mContext, nullptr);
g_signal_handlers_disconnect_by_data(mContext, this);
g_object_unref(mContext);
mContext = nullptr;
}
if (mDummyContext) { // mContext and mDummyContext have the same slaveType and signal_data // so no need for another workaround_gtk_im_display_closed.
gtk_im_context_set_client_window(mDummyContext, nullptr);
g_object_unref(mDummyContext);
mDummyContext = nullptr;
}
if (NS_WARN_IF(mComposingContext)) {
g_object_unref(mComposingContext);
mComposingContext = nullptr;
}
void IMContextWrapper::PrepareToDestroyContext(GtkIMContext* aContext) { if (mIMContextID == IMContextID::IIIMF) { // IIIM module registers handlers for the "closed" signal on the // display, but the signal handler is not disconnected when the module // is unloaded. To prevent the module from being unloaded, use static // variable to hold reference of slave context class declared by IIIM. // Note that this does not grab any instance, it grabs the "class". static gpointer sGtkIIIMContextClass = nullptr; if (!sGtkIIIMContextClass) { // We retrieved slave context class with g_type_name() and actual // slave context instance when our widget was GTK2. That must be // _GtkIMContext::priv::slave in GTK3. However, _GtkIMContext::priv // is an opacity struct named _GtkIMMulticontextPrivate, i.e., it's // not exposed by GTK3. Therefore, we cannot access the instance // safely. So, we need to retrieve the slave context class with // g_type_from_name("GtkIMContextIIIM") directly (anyway, we needed // to compare the class name with "GtkIMContextIIIM").
GType IIMContextType = g_type_from_name("GtkIMContextIIIM"); if (IIMContextType) {
sGtkIIIMContextClass = g_type_class_ref(IIMContextType);
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p PrepareToDestroyContext(), added to reference to " "GtkIMContextIIIM class to prevent it from being unloaded", this));
} else {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p PrepareToDestroyContext(), FAILED to prevent the " "IIIM module from being uploaded", this));
}
}
}
}
void IMContextWrapper::OnFocusWindow(nsWindow* aWindow) { if (MOZ_UNLIKELY(IsDestroyed())) { return;
}
// Even if old IM context has composition, key event should be sent to // current context since the user expects so.
GtkIMContext* currentContext = GetCurrentContext(); if (MOZ_UNLIKELY(!currentContext)) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p OnKeyEvent(), FAILED, there are no context", this)); return KeyHandlingState::eNotHandled;
}
if (mSetCursorPositionOnKeyEvent) {
SetCursorPosition(currentContext);
mSetCursorPositionOnKeyEvent = false;
}
// Let's support dead key event even if active keyboard layout also // supports complicated composition like CJK IME. bool isDeadKey =
KeymapWrapper::ComputeDOMKeyNameIndex(aEvent) == KEY_NAME_INDEX_Dead;
mMaybeInDeadKeySequence |= isDeadKey;
// If current context is mSimpleContext, both ibus and fcitx handles key // events synchronously. So, only when current context is mContext which // is GtkIMMulticontext, the key event may be handled by IME asynchronously. bool probablyHandledAsynchronously =
mIsIMInAsyncKeyHandlingMode && currentContext == mContext;
// If we're not sure whether the event is handled asynchronously, this is // set to true. bool maybeHandledAsynchronously = false;
// If aEvent is a synthesized event for async handling, this will be set to // true. bool isHandlingAsyncEvent = false;
// If we've decided that the event won't be synthesized asyncrhonously // by IME, but actually IME did it, this is set to true. bool isUnexpectedAsyncEvent = false;
// If IM is ibus or fcitx and it handles key events asynchronously, // they mark aEvent->state as "handled by me" when they post key event // to another process. Unfortunately, we need to check this hacky // flag because it's difficult to store all pending key events by // an array or a hashtable. if (probablyHandledAsynchronously) { switch (mIMContextID) { case IMContextID::IBus: { // See src/ibustypes.h staticconst guint IBUS_IGNORED_MASK = 1 << 25; // If IBUS_IGNORED_MASK was set to aEvent->state, the event // has already been handled by another process and it wasn't // used by IME.
isHandlingAsyncEvent = !!(aEvent->state & IBUS_IGNORED_MASK); if (!isHandlingAsyncEvent) { // On some environments, IBUS_IGNORED_MASK flag is not set as // expected. In such case, we keep pusing all events into the queue. // I.e., that causes eating a lot of memory until it's blurred. // Therefore, we need to check whether there is same timestamp event // in the queue. This redundant cost should be low because in most // causes, key events in the queue should be 2 or 4.
isHandlingAsyncEvent =
mPostingKeyEvents.IndexOf(aEvent) != GdkEventKeyQueue::NoIndex(); if (isHandlingAsyncEvent) {
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p OnKeyEvent(), aEvent->state does not have " "IBUS_IGNORED_MASK but " "same event in the queue. So, assuming it's a " "synthesized event", this));
}
}
// If it's a synthesized event, let's remove it from the posting // event queue first. Otherwise the following blocks cannot use // `break`. if (isHandlingAsyncEvent) {
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p OnKeyEvent(), aEvent->state has IBUS_IGNORED_MASK " "or aEvent is in the " "posting event queue, so, it won't be handled " "asynchronously anymore. Removing " "the posted events from the queue", this));
probablyHandledAsynchronously = false;
mPostingKeyEvents.RemoveEvent(aEvent);
}
// ibus won't send back key press events in a dead key sequcne. if (mMaybeInDeadKeySequence && aEvent->type == GDK_KEY_PRESS) {
probablyHandledAsynchronously = false; if (isHandlingAsyncEvent) {
isUnexpectedAsyncEvent = true; break;
} // Some keyboard layouts which have dead keys may send // "empty" key event to make us call // gtk_im_context_filter_keypress() to commit composed // character during a GDK_KEY_PRESS event dispatching. if (!gdk_keyval_to_unicode(aEvent->keyval) &&
!aEvent->hardware_keycode) {
isUnexpectedAsyncEvent = true; break;
} break;
} // ibus may handle key events synchronously if focused editor is // <input type="password"> or |ime-mode: disabled;|. However, in // some environments, not so actually. Therefore, we need to check // the result of gtk_im_context_filter_keypress() later. if (mInputContext.mIMEState.mEnabled == IMEEnabled::Password) {
probablyHandledAsynchronously = false;
maybeHandledAsynchronously = !isHandlingAsyncEvent; break;
} break;
} case IMContextID::Fcitx: case IMContextID::Fcitx5: { // See src/lib/fcitx-utils/keysym.h staticconst guint FcitxKeyState_IgnoredMask = 1 << 25; // If FcitxKeyState_IgnoredMask was set to aEvent->state, // the event has already been handled by another process and // it wasn't used by IME.
isHandlingAsyncEvent = !!(aEvent->state & FcitxKeyState_IgnoredMask); if (!isHandlingAsyncEvent) { // On some environments, FcitxKeyState_IgnoredMask flag *might* be not // set as expected. If there were such cases, we'd keep pusing all // events into the queue. I.e., that would cause eating a lot of // memory until it'd be blurred. Therefore, we should check whether // there is same timestamp event in the queue. This redundant cost // should be low because in most causes, key events in the queue // should be 2 or 4.
isHandlingAsyncEvent =
mPostingKeyEvents.IndexOf(aEvent) != GdkEventKeyQueue::NoIndex(); if (isHandlingAsyncEvent) {
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p OnKeyEvent(), aEvent->state does not have " "FcitxKeyState_IgnoredMask " "but same event in the queue. So, assuming it's a " "synthesized event", this));
}
}
// fcitx won't send back key press events in a dead key sequcne. if (mMaybeInDeadKeySequence && aEvent->type == GDK_KEY_PRESS) {
probablyHandledAsynchronously = false; if (isHandlingAsyncEvent) {
isUnexpectedAsyncEvent = true; break;
} // Some keyboard layouts which have dead keys may send // "empty" key event to make us call // gtk_im_context_filter_keypress() to commit composed // character during a GDK_KEY_PRESS event dispatching. if (!gdk_keyval_to_unicode(aEvent->keyval) &&
!aEvent->hardware_keycode) {
isUnexpectedAsyncEvent = true; break;
}
}
// fcitx handles key events asynchronously even if focused // editor cannot use IME actually.
if (isHandlingAsyncEvent) {
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p OnKeyEvent(), aEvent->state has " "FcitxKeyState_IgnoredMask or aEvent is in " "the posting event queue, so, it won't be handled " "asynchronously anymore. " "Removing the posted events from the queue", this));
probablyHandledAsynchronously = false;
mPostingKeyEvents.RemoveEvent(aEvent); break;
} break;
} default:
MOZ_ASSERT_UNREACHABLE( "IME may handle key event " "asyncrhonously, but not yet confirmed if it comes agian " "actually");
}
}
if (!isUnexpectedAsyncEvent) {
mKeyboardEventWasDispatched = aKeyboardEventWasDispatched;
mKeyboardEventWasConsumed = false;
} else { // If we didn't expect this event, we've alreday dispatched eKeyDown // event or eKeyUp event for that.
mKeyboardEventWasDispatched = true; // And in this case, we need to assume that another key event hasn't // been receivied and mKeyboardEventWasConsumed keeps storing the // dispatched eKeyDown or eKeyUp event's state.
}
mFallbackToKeyEvent = false;
mProcessingKeyEvent = aEvent;
gboolean isFiltered = gtk_im_context_filter_keypress(currentContext, aEvent);
// If we're not sure whether the event is handled by IME asynchronously or // synchronously, we need to trust the result of // gtk_im_context_filter_keypress(). If it consumed and but did nothing, // we can assume that another event will be synthesized. if (!isHandlingAsyncEvent && maybeHandledAsynchronously) {
probablyHandledAsynchronously |=
isFiltered && !mFallbackToKeyEvent && !mKeyboardEventWasDispatched;
}
// The caller of this shouldn't handle aEvent anymore if we've dispatched // composition events or modified content with other events. bool filterThisEvent = isFiltered && !mFallbackToKeyEvent;
if (IsComposingOnCurrentContext() && !isFiltered &&
aEvent->type == GDK_KEY_PRESS && mDispatchedCompositionString.IsEmpty()) { // A Hangul input engine for SCIM doesn't emit preedit_end // signal even when composition string becomes empty. On the // other hand, we should allow to make composition with empty // string for other languages because there *might* be such // IM. For compromising this issue, we should dispatch // compositionend event, however, we don't need to reset IM // actually. // NOTE: Don't dispatch key events as "processed by IME" since // we need to dispatch keyboard events as IME wasn't handled it.
mProcessingKeyEvent = nullptr;
DispatchCompositionCommitEvent(currentContext, &EmptyString());
mProcessingKeyEvent = aEvent; // In this case, even though we handle the keyboard event here, // but we should dispatch keydown event as
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 (!probablyHandledAsynchronously) {
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 {
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p OnKeyEvent(), putting aEvent into the queue...", this));
mPostingKeyEvents.PutEvent(aEvent);
}
}
mProcessingKeyEvent = nullptr;
if (aEvent->type == GDK_KEY_PRESS && !filterThisEvent) { // If the key event hasn't been handled by active IME nor keyboard // layout, we can assume that the dead key sequence has been or was // ended. Note that we should not reset it when the key event is // GDK_KEY_RELEASE since it may not be filtered by active keyboard // layout even in composition.
mMaybeInDeadKeySequence = false;
}
if (aEvent->type == GDK_KEY_RELEASE) { if (const GdkEventKey* pendingKeyPressEvent =
mPostingKeyEvents.GetCorrespondingKeyPressEvent(aEvent)) {
MOZ_LOG(gIMELog, LogLevel::Warning,
("0x%p OnKeyEvent(), forgetting a pending GDK_KEY_PRESS event " "because GDK_KEY_RELEASE for the event is handled", this));
mPostingKeyEvents.RemoveEvent(pendingKeyPressEvent);
}
}
if (filterThisEvent) { return KeyHandlingState::eHandled;
} // If another call of this method has already dispatched eKeyDown event, // we should return KeyHandlingState::eNotHandledButEventDispatched because // the caller should've stopped handling the event if preceding eKeyDown // event was consumed. if (aKeyboardEventWasDispatched) { return KeyHandlingState::eNotHandledButEventDispatched;
} if (!mKeyboardEventWasDispatched) { return KeyHandlingState::eNotHandled;
} return mKeyboardEventWasConsumed
? KeyHandlingState::eNotHandledButEventConsumed
: KeyHandlingState::eNotHandledButEventDispatched;
}
// We shouldn't carry over the removed string to another editor.
mSelectedStringRemovedByComposition.Truncate();
mContentSelection.reset();
if (aFocus) { if (mSetInputPurposeAndInputHints) {
mSetInputPurposeAndInputHints = false;
SetInputPurposeAndInputHints();
}
NotifyIMEOfFocusChange(IMEFocusState::Focused);
} else {
NotifyIMEOfFocusChange(IMEFocusState::Blurred);
}
// When the focus changes, we need to inform IM about the new cursor // position. Chinese input methods generally rely on this because they // usually don't start composition until a character is picked. if (aFocus && EnsureToCacheContentSelection()) {
SetCursorPosition(GetActiveContext());
}
}
GtkIMContext* activeContext = GetActiveContext(); if (MOZ_UNLIKELY(!activeContext)) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p ResetIME(), FAILED, there are no context", this)); return;
}
// The last focused window might have been destroyed by a DOM event handler // which was called by us during a call of gtk_im_context_reset(). if (!lastFocusedWindow ||
NS_WARN_IF(lastFocusedWindow != mLastFocusedWindow) ||
lastFocusedWindow->Destroyed()) { return;
}
// XXX IIIMF (ATOK X3 which is one of the Language Engine of it is still // used in Japan!) sends only "preedit_changed" signal with empty // composition string synchronously. Therefore, if composition string // is now empty string, we should assume that the IME won't send // "commit" signal. if (IsComposing() && compositionString.IsEmpty()) { // WARNING: The widget might have been gone after this.
DispatchCompositionCommitEvent(activeContext, &EmptyString());
}
}
nsresult IMContextWrapper::EndIMEComposition(nsWindow* aCaller) { if (MOZ_UNLIKELY(IsDestroyed())) { return NS_OK;
}
// Currently, GTK has API neither to commit nor to cancel composition // forcibly. Therefore, TextComposition will recompute commit string for // the request even if native IME will cause unexpected commit string. // So, we don't need to emulate commit or cancel composition with // proper composition events. // XXX ResetIME() might not enough for finishing compositoin on some // environments. We should emulate focus change too because some IMEs // may commit or cancel composition at blur.
ResetIME();
return NS_OK;
}
void IMContextWrapper::OnLayoutChange() { if (MOZ_UNLIKELY(IsDestroyed())) { return;
}
if (IsComposing()) {
SetCursorPosition(GetActiveContext());
} else { // If not composing, candidate window position is updated before key // down
mSetCursorPositionOnKeyEvent = true;
}
mLayoutChanged = true;
}
void IMContextWrapper::OnUpdateComposition() { if (MOZ_UNLIKELY(IsDestroyed())) { return;
}
if (!IsComposing()) { // Composition has been committed. So we need update selection for // caret later
mContentSelection.reset();
EnsureToCacheContentSelection();
mSetCursorPositionOnKeyEvent = true;
}
// If we've already set candidate window position, we don't need to update // the position with update composition notification. if (!mLayoutChanged) {
SetCursorPosition(GetActiveContext());
}
}
// Release current IME focus if IME is enabled. if (changingEnabledState && mInputContext.mIMEState.IsEditable()) { if (IsComposing()) {
EndIMEComposition(mLastFocusedWindow);
} if (mIMEFocusState == IMEFocusState::Focused) {
NotifyIMEOfFocusChange(IMEFocusState::BlurredWithoutFocusChange);
}
}
if (!changingEnabledState || !mInputContext.mIMEState.IsEditable()) { return;
}
// If the input context was temporarily disabled without a focus change, // it must be ready to query content even if the focused content is in // a remote process. In this case, we should set IME focus right now. if (mIMEFocusState == IMEFocusState::BlurredWithoutFocusChange) {
SetInputPurposeAndInputHints();
NotifyIMEOfFocusChange(IMEFocusState::Focused); return;
}
// Otherwise, we cannot set input-purpose and input-hints right now because // setting them may require to set focus immediately for IME own's UI. // However, at this moment, `ContentCacheInParent` does not have content // cache, it'll be available after `NOTIFY_IME_OF_FOCUS` notification. // Therefore, we set them at receiving the notification.
mSetInputPurposeAndInputHints = true;
}
GtkInputPurpose purpose = GTK_INPUT_PURPOSE_FREE_FORM; const nsString& inputType = mInputContext.mHTMLInputType; // Password case has difficult issue. Desktop IMEs disable composition if // input-purpose is password. For disabling IME on |ime-mode: disabled;|, we // need to check mEnabled value instead of inputType value. This hack also // enables composition on <input type="password" style="ime-mode: enabled;">. // This is right behavior of ime-mode on desktop. // // On the other hand, IME for tablet devices may provide a specific software // keyboard for password field. If so, the behavior might look strange on // both: // <input type="text" style="ime-mode: disabled;"> // <input type="password" style="ime-mode: enabled;"> // // Temporarily, we should focus on desktop environment for now. I.e., let's // ignore tablet devices for now. When somebody reports actual trouble on // tablet devices, we should try to look for a way to solve actual problem. if (mInputContext.mIMEState.mEnabled == IMEEnabled::Password) {
purpose = GTK_INPUT_PURPOSE_PASSWORD;
} elseif (inputType.EqualsLiteral("email")) {
purpose = GTK_INPUT_PURPOSE_EMAIL;
} elseif (inputType.EqualsLiteral("url")) {
purpose = GTK_INPUT_PURPOSE_URL;
} elseif (inputType.EqualsLiteral("tel")) {
purpose = GTK_INPUT_PURPOSE_PHONE;
} elseif (inputType.EqualsLiteral("number")) {
purpose = GTK_INPUT_PURPOSE_NUMBER;
} elseif (mInputContext.mHTMLInputMode.EqualsLiteral("decimal")) {
purpose = GTK_INPUT_PURPOSE_NUMBER;
} elseif (mInputContext.mHTMLInputMode.EqualsLiteral("email")) {
purpose = GTK_INPUT_PURPOSE_EMAIL;
} elseif (mInputContext.mHTMLInputMode.EqualsLiteral("numeric")) {
purpose = GTK_INPUT_PURPOSE_DIGITS;
} elseif (mInputContext.mHTMLInputMode.EqualsLiteral("tel")) {
purpose = GTK_INPUT_PURPOSE_PHONE;
} elseif (mInputContext.mHTMLInputMode.EqualsLiteral("url")) {
purpose = GTK_INPUT_PURPOSE_URL;
} // Search by type and inputmode isn't supported on GTK.
// Although GtkInputHints is enum type, value is bit field.
gint hints = GTK_INPUT_HINT_NONE; if (mInputContext.mHTMLInputMode.EqualsLiteral("none")) {
hints |= GTK_INPUT_HINT_INHIBIT_OSK;
}
// If we've already made IME blurred at setting the input context disabled // and it's now completely blurred by a focus move, we need only to update // mIMEFocusState and when the input context gets enabled, we cannot set // IME focus immediately. if (aIMEFocusState == IMEFocusState::Blurred &&
mIMEFocusState == IMEFocusState::BlurredWithoutFocusChange) {
mIMEFocusState = IMEFocusState::Blurred; return;
}
auto Blur = [&](IMEFocusState aInternalState) {
GtkIMContext* currentContext = GetCurrentContext(); if (MOZ_UNLIKELY(!currentContext)) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p NotifyIMEOfFocusChange()::Blur(), FAILED, " "there is no context", this)); return;
}
gtk_im_context_focus_out(currentContext);
mIMEFocusState = aInternalState;
};
if (aIMEFocusState != IMEFocusState::Focused) { return Blur(aIMEFocusState);
}
GtkIMContext* currentContext = GetCurrentContext(); if (MOZ_UNLIKELY(!currentContext)) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p NotifyIMEOfFocusChange(), FAILED, " "there is no context", this)); return;
}
if (sLastFocusedContext && sLastFocusedContext != this) {
sLastFocusedContext->NotifyIMEOfFocusChange(IMEFocusState::Blurred);
}
sLastFocusedContext = this;
// Forget all posted key events when focus is moved since they shouldn't // be fired in different editor.
sWaitingSynthesizedKeyPressHardwareKeyCode = 0;
mPostingKeyEvents.Clear();
if (!IsEnabled()) { // We should release IME focus for uim and scim. // These IMs are using snooper that is released at losing focus.
Blur(IMEFocusState::BlurredWithoutFocusChange);
}
}
if (!IsComposing()) { // Now we have no composition (mostly situation on calling this method) // If we have it, it will set by // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED.
mSetCursorPositionOnKeyEvent = true;
}
// The focused editor might have placeholder text with normal text node. // In such case, the text node must be removed from a compositionstart // event handler. So, we're dispatching eCompositionStart, // we should ignore selection change notification. if (mCompositionState == eCompositionState_CompositionStartDispatched) { if (NS_WARN_IF(mContentSelection.isNothing())) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p OnSelectionChange(), FAILED, " "new offset is too large, cannot keep composing", this));
} elseif (mContentSelection->HasRange()) { // Modify the selection start offset with new offset.
mCompositionStart = mContentSelection->OffsetAndDataRef().StartOffset(); // XXX We should modify mSelectedStringRemovedByComposition? // But how?
MOZ_LOG(gIMELog, LogLevel::Debug,
("0x%p OnSelectionChange(), ignored, mCompositionStart " "is updated to %u, the selection change doesn't cause " "resetting IM context", this, mCompositionStart)); // And don't reset the IM context. return;
} else {
MOZ_LOG(
gIMELog, LogLevel::Debug,
("0x%p OnSelectionChange(), ignored, because of no selection range", this)); return;
} // Otherwise, reset the IM context due to impossible to keep composing.
}
// If the selection change is caused by deleting surrounding text, // we shouldn't need to notify IME of selection change. if (mIsDeletingSurrounding) { return;
}
// When the selection change is caused by dispatching composition event, // selection set event and/or occurred before starting current composition, // we shouldn't notify IME of that and commit existing composition. // Don't do this even if selection is not changed actually. For example, // fcitx has direct input mode which does not insert composing string, but // inserts commited text for each key sequence (i.e., there is "invisible" // composition string). In the world after bug 1712269, we don't use a // set of composition events for this kind of IME. Therefore, // SelectionChangeData.mCausedByComposition is not expected value for here // if this call is caused by a preceding commit. And if the preceding commit // is triggered by a key type for next word, resetting IME state makes fcitx // discard the pending input for the next word. Thus, we need to check // whether the selection range is actually changed here. if (!selectionChangeData.mCausedByComposition &&
!selectionChangeData.mCausedBySelectionEvent && isSelectionRangeChanged &&
!occurredBeforeComposition) { // Hack for ibus-pinyin. ibus-pinyin will synthesize a set of // composition which commits with empty string after calling // gtk_im_context_reset(). Therefore, selecting text causes // unexpectedly removing it. For preventing it but not breaking the // other IMEs which use surrounding text, we should call it only when // surrounding text has been retrieved after last selection range was // set. If it's not retrieved, that means that current IME doesn't // have any content cache, so, it must not need the notification of // selection change. if (IsComposing() || retrievedSurroundingSignalReceived) {
ResetIME();
}
}
}
void IMContextWrapper::OnStartCompositionNative(GtkIMContext* aContext) { // IME may synthesize composition asynchronously after filtering a // GDK_KEY_PRESS event. In that case, we should handle composition with // emulating the usual case, i.e., this is called in the stack of // OnKeyEvent().
Maybe<AutoRestore<GdkEventKey*>> maybeRestoreProcessingKeyEvent; if (!mProcessingKeyEvent && !mPostingKeyEvents.IsEmpty()) {
GdkEventKey* keyEvent = mPostingKeyEvents.GetFirstEvent(); if (keyEvent && keyEvent->type == GDK_KEY_PRESS &&
KeymapWrapper::ComputeDOMKeyNameIndex(keyEvent) ==
KEY_NAME_INDEX_USE_STRING) {
maybeRestoreProcessingKeyEvent.emplace(mProcessingKeyEvent);
mProcessingKeyEvent = mPostingKeyEvents.GetFirstEvent();
}
}
// See bug 472635, we should do nothing if IM context doesn't match. if (GetCurrentContext() != aContext) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p OnStartCompositionNative(), FAILED, " "given context doesn't match", this)); return;
}
if (mComposingContext && aContext != mComposingContext) { // XXX For now, we should ignore this odd case, just logging.
MOZ_LOG(gIMELog, LogLevel::Warning,
("0x%p OnStartCompositionNative(), Warning, " "there is already a composing context but starting new " "composition with different context", this));
}
// IME may start composition without "preedit_start" signal. Therefore, // mComposingContext will be initialized in DispatchCompositionStart().
// See bug 472635, we should do nothing if IM context doesn't match. // Note that if this is called after focus move, the context may different // from any our owning context. if (!IsValidContext(aContext)) {
MOZ_LOG(gIMELog, LogLevel::Error,
("0x%p OnEndCompositionNative(), FAILED, "
--> --------------------
--> maximum size reached
--> --------------------
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.24Angebot
Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.