Bug 1461307 - Overwrite selection colors of widget which may be referred by IME via IM context with selection colors of GtkTextView r?karlt draft
authorMasayuki Nakano <masayuki@d-toybox.com>
Fri, 13 Jul 2018 18:12:53 +0900
changeset 823953 6c2cbd7edea516f2dd727f91fbefe1575d08a27a
parent 823872 0be4463d29159905dded07f1dbddc5bb7dfaa336
push id117833
push userbmo:masayuki@d-toybox.com
push dateMon, 30 Jul 2018 05:18:29 +0000
reviewerskarlt
bugs1461307
milestone63.0a1
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
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
@@ -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) {