Bug 1461307 - Overwrite selection colors of widget which may be referred by IME via IM context with selection colors of GtkTextView r?karlt
IME (e.g., fcitx) may refer selection colors of widget under window which
is associated with IM context to support any colored widget. So, IME
expects good selection colors which have sufficient contrast between
foreground and background, and also selection background color and
widget background color like GtkTextView. However, some desktop themes
set our widget to different selection colors from GtkTextView which may
be unreadable.
nsTextFrame (which paints composition string) expects that composition
string colors coming from IME are sufficiently readable and background
color of composition string and background color of our editor's default
style (coming from LookAndFeel) have sufficient contrast because
nsTextFrame assmes that composition string colors coming from IME are
decided for the default style.
Therefore, this patch creates SelectionStyleProvider which overwrites
selection style of our widget with selection style of GtkTextView so
that IME can refer selection colors of GtkTextView via our widget.
MozReview-Commit-ID: 5vdcSgoEYv0
--- a/widget/gtk/IMContextWrapper.cpp
+++ b/widget/gtk/IMContextWrapper.cpp
@@ -217,16 +217,123 @@ public:
NS_GET_A(aColor));
}
virtual ~GetTextRangeStyleText() {};
};
const static bool 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.
+ ******************************************************************************/
+
+class SelectionStyleProvider final
+{
+public:
+ static SelectionStyleProvider* GetInstance()
+ {
+ if (sHasShutDown) {
+ return nullptr;
+ }
+ if (!sInstance) {
+ sInstance = new SelectionStyleProvider();
+ }
+ return sInstance;
+ }
+
+ static void Shutdown()
+ {
+ if (sInstance) {
+ g_object_unref(sInstance->mProvider);
+ }
+ delete sInstance;
+ sInstance = nullptr;
+ sHasShutDown = true;
+ }
+
+ // 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.
+ nscolor selectionForegroundColor;
+ if (NS_SUCCEEDED(LookAndFeel::GetColor(
+ LookAndFeel::eColorID_TextSelectForeground,
+ &selectionForegroundColor))) {
+ double alpha =
+ static_cast<double>(NS_GET_A(selectionForegroundColor)) / 0xFF;
+ style.AppendPrintf("color:rgba(%u,%u,%u,%f);",
+ NS_GET_R(selectionForegroundColor),
+ NS_GET_G(selectionForegroundColor),
+ NS_GET_B(selectionForegroundColor), alpha);
+ }
+ nscolor selectionBackgroundColor;
+ if (NS_SUCCEEDED(LookAndFeel::GetColor(
+ LookAndFeel::eColorID_TextSelectBackground,
+ &selectionBackgroundColor))) {
+ double alpha =
+ static_cast<double>(NS_GET_A(selectionBackgroundColor)) / 0xFF;
+ style.AppendPrintf("background-color:rgba(%u,%u,%u,%f);",
+ NS_GET_R(selectionBackgroundColor),
+ NS_GET_G(selectionBackgroundColor),
+ NS_GET_B(selectionBackgroundColor), alpha);
+ }
+ style.AppendLiteral("}");
+ gtk_css_provider_load_from_data(mProvider, style.get(), -1, nullptr);
+ }
+
+private:
+ static SelectionStyleProvider* sInstance;
+ static bool sHasShutDown;
+ GtkCssProvider* const mProvider;
+
+ SelectionStyleProvider()
+ : mProvider(gtk_css_provider_new())
+ {
+ OnThemeChanged();
+ }
+};
+
+SelectionStyleProvider* SelectionStyleProvider::sInstance = nullptr;
+bool SelectionStyleProvider::sHasShutDown = false;
+
+/******************************************************************************
* IMContextWrapper
******************************************************************************/
IMContextWrapper* IMContextWrapper::sLastFocusedContext = nullptr;
bool IMContextWrapper::sUseSimpleContext;
NS_IMPL_ISUPPORTS(IMContextWrapper,
TextEventDispatcherListener,
@@ -357,16 +464,21 @@ IMContextWrapper::GetIMName() const
void
IMContextWrapper::Init()
{
MozContainer* container = mOwnerWindow->GetMozContainer();
MOZ_ASSERT(container, "container is null");
GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(container));
+ // 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);
@@ -458,16 +570,23 @@ IMContextWrapper::Init()
"PR_GetEnv(\"XMODIFIERS\")=\"%s\"",
this, mOwnerWindow, mContext, nsAutoCString(im).get(),
ToChar(mIsIMInAsyncKeyHandlingMode), ToChar(mIsKeySnooped),
mSimpleContext, mDummyContext,
gtk_im_multicontext_get_context_id(GTK_IM_MULTICONTEXT(mContext)),
PR_GetEnv("XMODIFIERS")));
}
+/* static */
+void
+IMContextWrapper::Shutdown()
+{
+ SelectionStyleProvider::Shutdown();
+}
+
IMContextWrapper::~IMContextWrapper()
{
if (this == sLastFocusedContext) {
sLastFocusedContext = nullptr;
}
MOZ_LOG(gGtkIMLog, LogLevel::Info,
("0x%p ~IMContextWrapper()", this));
}
@@ -1361,16 +1480,26 @@ IMContextWrapper::OnSelectionChange(nsWi
if (IsComposing() || retrievedSurroundingSignalReceived) {
ResetIME();
}
}
}
/* static */
void
+IMContextWrapper::OnThemeChanged()
+{
+ if (!SelectionStyleProvider::GetInstance()) {
+ return;
+ }
+ SelectionStyleProvider::GetInstance()->OnThemeChanged();
+}
+
+/* static */
+void
IMContextWrapper::OnStartCompositionCallback(GtkIMContext* aContext,
IMContextWrapper* aModule)
{
aModule->OnStartCompositionNative(aContext);
}
void
IMContextWrapper::OnStartCompositionNative(GtkIMContext* aContext)
--- a/widget/gtk/IMContextWrapper.h
+++ b/widget/gtk/IMContextWrapper.h
@@ -43,32 +43,37 @@ public:
void* aData) override;
public:
// aOwnerWindow is a pointer of the owner window. When aOwnerWindow is
// destroyed, the related IME contexts are released (i.e., IME cannot be
// used with the instance after that).
explicit IMContextWrapper(nsWindow* aOwnerWindow);
+ // Called when the process is being shut down.
+ static void Shutdown();
+
// "Enabled" means the users can use all IMEs.
// I.e., the focus is in the normal editors.
bool IsEnabled() const;
// OnFocusWindow is a notification that aWindow is going to be focused.
void OnFocusWindow(nsWindow* aWindow);
// OnBlurWindow is a notification that aWindow is going to be unfocused.
void OnBlurWindow(nsWindow* aWindow);
// OnDestroyWindow is a notification that aWindow is going to be destroyed.
void OnDestroyWindow(nsWindow* aWindow);
// OnFocusChangeInGecko is a notification that an editor gets focus.
void OnFocusChangeInGecko(bool aFocus);
// OnSelectionChange is a notification that selection (caret) is changed
// in the focused editor.
void OnSelectionChange(nsWindow* aCaller,
const IMENotification& aIMENotification);
+ // OnThemeChanged is called when desktop theme is changed.
+ static void OnThemeChanged();
/**
* OnKeyEvent() is called when aWindow gets a native key press event or a
* native key release event. If this returns true, the key event was
* filtered by IME. Otherwise, this returns false.
* NOTE: When the native key press event starts composition, this returns
* true but dispatches an eKeyDown event or eKeyUp event before
* dispatching composition events or content command event.
--- a/widget/gtk/mozgtk/mozgtk.c
+++ b/widget/gtk/mozgtk/mozgtk.c
@@ -519,16 +519,18 @@ STUB(gtk_window_set_title)
STUB(gtk_window_set_transient_for)
STUB(gtk_window_set_type_hint)
STUB(gtk_window_set_wmclass)
STUB(gtk_window_unfullscreen)
STUB(gtk_window_unmaximize)
#endif
#ifdef GTK3_SYMBOLS
+STUB(gtk_css_provider_load_from_data)
+STUB(gtk_css_provider_new)
STUB(gdk_device_get_source)
STUB(gdk_device_manager_get_client_pointer)
STUB(gdk_disable_multidevice)
STUB(gdk_device_manager_list_devices)
STUB(gdk_display_get_device_manager)
STUB(gdk_display_manager_open_display)
STUB(gdk_error_trap_pop_ignored)
STUB(gdk_event_get_source_device)
@@ -562,16 +564,17 @@ STUB(gtk_render_frame_gap)
STUB(gtk_render_handle)
STUB(gtk_render_icon)
STUB(gtk_render_line)
STUB(gtk_render_option)
STUB(gtk_render_slider)
STUB(gtk_scale_new)
STUB(gtk_scrollbar_new)
STUB(gtk_style_context_add_class)
+STUB(gtk_style_context_add_provider)
STUB(gtk_style_context_add_region)
STUB(gtk_style_context_get)
STUB(gtk_style_context_get_background_color)
STUB(gtk_style_context_get_border)
STUB(gtk_style_context_get_border_color)
STUB(gtk_style_context_get_color)
STUB(gtk_style_context_get_direction)
STUB(gtk_style_context_get_margin)
@@ -588,16 +591,17 @@ STUB(gtk_style_context_remove_class)
STUB(gtk_style_context_remove_region)
STUB(gtk_style_context_restore)
STUB(gtk_style_context_save)
STUB(gtk_style_context_set_direction)
STUB(gtk_style_context_set_path)
STUB(gtk_style_context_set_parent)
STUB(gtk_style_context_set_state)
STUB(gtk_style_properties_lookup_property)
+STUB(gtk_style_provider_get_type)
STUB(gtk_tree_view_column_get_button)
STUB(gtk_widget_get_preferred_size)
STUB(gtk_widget_get_preferred_width)
STUB(gtk_widget_get_preferred_height)
STUB(gtk_widget_get_state_flags)
STUB(gtk_widget_get_style_context)
STUB(gtk_widget_path_append_type)
STUB(gtk_widget_path_copy)
--- a/widget/gtk/nsWidgetFactory.cpp
+++ b/widget/gtk/nsWidgetFactory.cpp
@@ -13,16 +13,17 @@
#include "nsAppShellSingleton.h"
#include "nsBaseWidget.h"
#include "nsGtkKeyUtils.h"
#include "nsLookAndFeel.h"
#include "nsWindow.h"
#include "nsTransferable.h"
#include "nsHTMLFormatConverter.h"
#include "HeadlessClipboard.h"
+#include "IMContextWrapper.h"
#ifdef MOZ_X11
#include "nsClipboardHelper.h"
#include "nsClipboard.h"
#include "nsDragService.h"
#endif
#ifdef MOZ_WIDGET_GTK
#include "nsApplicationChooser.h"
#endif
@@ -312,16 +313,17 @@ nsWidgetGtk2ModuleDtor()
// Shutdown all XP level widget classes.
WidgetUtils::Shutdown();
NativeKeyBindings::Shutdown();
nsLookAndFeel::Shutdown();
nsFilePicker::Shutdown();
nsSound::Shutdown();
nsWindow::ReleaseGlobals();
+ IMContextWrapper::Shutdown();
KeymapWrapper::Shutdown();
nsGTKToolkit::Shutdown();
nsAppShellShutdown();
#ifdef MOZ_ENABLE_DBUS
WakeLockListener::Shutdown();
#endif
}
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -3438,16 +3438,18 @@ nsWindow::ThemeChanged()
if (win && win != this) { // guard against infinite recursion
RefPtr<nsWindow> kungFuDeathGrip = win;
win->ThemeChanged();
}
children = children->next;
}
+
+ IMContextWrapper::OnThemeChanged();
}
void
nsWindow::OnDPIChanged()
{
if (mWidgetListener) {
nsIPresShell* presShell = mWidgetListener->GetPresShell();
if (presShell) {