Bug 1461307 - Overwrite selection colors of widget which may be referred by IME via IM context with selection colors of GtkTextView. r=karlt, a=IanN CLOSED TREE DONTBUILD SEAMONKEY_2_49_ESR_RELBRANCH
authorMasayuki Nakano <masayuki@d-toybox.com>
Fri, 13 Jul 2018 18:12:53 +0900
branchSEAMONKEY_2_49_ESR_RELBRANCH
changeset 357540 ad03f53f55099a25dcc19a59844623b3adb95932
parent 357539 30a49fef33b4374b5e19998cdab9e9e0f4988eb9
child 357541 14da0f87d12c12f6ef9f8e82128d6ce500a2bba2
push id7834
push userfrgrahl@gmx.net
push dateSun, 13 Jan 2019 12:17:02 +0000
treeherdermozilla-esr52@6e4ad8a8f2e8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskarlt, IanN
bugs1461307
milestone52.9.1
Bug 1461307 - Overwrite selection colors of widget which may be referred by IME via IM context with selection colors of GtkTextView. r=karlt, a=IanN CLOSED TREE DONTBUILD mozilla-esr52 SEAMONKEY_2_49_ESR_RELBRANCH 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.
widget/gtk/IMContextWrapper.cpp
widget/gtk/IMContextWrapper.h
widget/gtk/mozgtk/mozgtk.c
widget/gtk/nsWidgetFactory.cpp
widget/gtk/nsWindow.cpp
--- a/widget/gtk/IMContextWrapper.cpp
+++ b/widget/gtk/IMContextWrapper.cpp
@@ -149,16 +149,123 @@ public:
                      NS_GET_A(aColor));
     }
     virtual ~GetTextRangeStyleText() {};
 };
 
 const static bool kUseSimpleContextDefault = MOZ_WIDGET_GTK == 2;
 
 /******************************************************************************
+ * 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,
@@ -198,16 +305,21 @@ IMContextWrapper::Init()
     MOZ_LOG(gGtkIMLog, LogLevel::Info,
         ("0x%p Init(), mOwnerWindow=0x%p",
          this, mOwnerWindow));
 
     MozContainer* container = mOwnerWindow->GetMozContainer();
     NS_PRECONDITION(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);
@@ -246,16 +358,23 @@ IMContextWrapper::Init()
             this);
     }
 
     // Dummy context
     mDummyContext = gtk_im_multicontext_new();
     gtk_im_context_set_client_window(mDummyContext, gdkWindow);
 }
 
+/* static */
+void
+IMContextWrapper::Shutdown()
+{
+    SelectionStyleProvider::Shutdown();
+}
+
 IMContextWrapper::~IMContextWrapper()
 {
     if (this == sLastFocusedContext) {
         sLastFocusedContext = nullptr;
     }
     MOZ_LOG(gGtkIMLog, LogLevel::Info,
         ("0x%p ~IMContextWrapper()", this));
 }
@@ -1030,16 +1149,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
@@ -42,16 +42,19 @@ 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;
 
     nsIMEUpdatePreference GetIMEUpdatePreference() const;
 
     // OnFocusWindow is a notification that aWindow is going to be focused.
     void OnFocusWindow(nsWindow* aWindow);
@@ -60,16 +63,18 @@ public:
     // 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 keypress event starts composition, this returns TRUE but
     //       this dispatches keydown event before compositionstart event.
     bool OnKeyEvent(nsWindow* aWindow, GdkEventKey* aEvent,
                       bool aKeyDownEventWasSent = false);
--- a/widget/gtk/mozgtk/mozgtk.c
+++ b/widget/gtk/mozgtk/mozgtk.c
@@ -505,16 +505,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_error_trap_pop_ignored)
 STUB(gdk_event_get_source_device)
 STUB(gdk_window_get_type)
@@ -543,16 +545,17 @@ STUB(gtk_render_frame)
 STUB(gtk_render_frame_gap)
 STUB(gtk_render_handle)
 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)
@@ -569,16 +572,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_state_flags)
 STUB(gtk_widget_get_style_context)
 STUB(gtk_widget_path_append_for_widget)
 STUB(gtk_widget_path_append_type)
 STUB(gtk_widget_path_copy)
 STUB(gtk_widget_path_free)
--- a/widget/gtk/nsWidgetFactory.cpp
+++ b/widget/gtk/nsWidgetFactory.cpp
@@ -12,16 +12,17 @@
 #include "nsAppShell.h"
 #include "nsAppShellSingleton.h"
 #include "nsBaseWidget.h"
 #include "nsGtkKeyUtils.h"
 #include "nsLookAndFeel.h"
 #include "nsWindow.h"
 #include "nsTransferable.h"
 #include "nsHTMLFormatConverter.h"
+#include "IMContextWrapper.h"
 #ifdef MOZ_X11
 #include "nsClipboardHelper.h"
 #include "nsClipboard.h"
 #include "nsDragService.h"
 #endif
 #if (MOZ_WIDGET_GTK == 3)
 #include "nsApplicationChooser.h"
 #endif
@@ -304,16 +305,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
@@ -3396,16 +3396,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) {