Bug 1461307 - Overwrite selection colors of widget which may be referred by IME via IM context with selection colors of GtkTextView r=karlt
authorMasayuki Nakano <masayuki@d-toybox.com>
Fri, 13 Jul 2018 18:12:53 +0900
changeset 486657 6cdfa85af851cd124cd535dacd0ce8e4131aeec5
parent 486656 0626f30fbcd3523f649c7b2d8f1f4b0b108929ee
child 486658 44fd3a35a89ac4a19e5c7cd4e09f1b3d154af7c6
push id1815
push userffxbld-merge
push dateMon, 15 Oct 2018 10:40:45 +0000
treeherdermozilla-release@18d4c09e9378 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskarlt
bugs1461307
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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) {