Bug 1527048 - [Linux/GTK] Fallback to Adwaita light GTK theme for content when system theme has no light variant, r=karlt a=jcristau
authorMartin Stransky <stransky@redhat.com>
Tue, 28 May 2019 06:51:36 +0000
changeset 537007 ca5607bbcad3ed768cd576cebcc6b1f69b3e624c
parent 537006 2edcae33cd6a9755aee477a868ccd4dff299eb35
child 537008 8dd6c8c8a610719913e9738daae139e003b45d1d
push id2082
push userffxbld-merge
push dateMon, 01 Jul 2019 08:34:18 +0000
treeherdermozilla-release@2fb19d0466d2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskarlt, jcristau
bugs1527048
milestone68.0
Bug 1527048 - [Linux/GTK] Fallback to Adwaita light GTK theme for content when system theme has no light variant, r=karlt a=jcristau We previously disabled system dark themes for Firefox chrome and content by gtk-application-prefer-dark-theme settings. That option is no longer preferred by gnome project and was removed from tweaks tools. Theme makers are encouraged to use a different name for the dark theme variants, like Adwaita-dark, Yaru-dark and so on. This option also does not work when the GTK theme is missing the light variant completely. To address that, this patch implements heuristics based on https://www.w3.org/TR/AERT/#color-contrast to check if the system GTK theme has good contrast/visibility with default HTML colors (white background and black text). If widget.content.gtk-theme-override is empty and the system theme fails the test with gtk-application-prefer-dark-theme set to false, then Adwaita:light theme is used. This patch was tested with some distro default light themes (Ambiance, Radiance, Yaru - Ubuntu, Arc - KDE, Menta - MATE) and dark/light themes are recognized correctly. Differential Revision: https://phabricator.services.mozilla.com/D29823
widget/gtk/nsLookAndFeel.cpp
--- a/widget/gtk/nsLookAndFeel.cpp
+++ b/widget/gtk/nsLookAndFeel.cpp
@@ -819,16 +819,93 @@ bool nsLookAndFeel::GetFontImpl(FontID a
   } else {
     // Convert gdk pixels to CSS pixels.
     aFontStyle.size /= gfxPlatformGtk::GetFontScaleFactor();
   }
 
   return true;
 }
 
+const gchar* dark_theme_setting = "gtk-application-prefer-dark-theme";
+static bool SystemPrefersDarkVariant(GtkSettings* aSettings) {
+  gboolean darkThemeDefault;
+  g_object_get(aSettings, dark_theme_setting, &darkThemeDefault, nullptr);
+  return darkThemeDefault;
+}
+
+// Check color contrast according to
+// https://www.w3.org/TR/AERT/#color-contrast
+static bool HasGoodContrastVisibility(GdkRGBA& aColor1, GdkRGBA& aColor2) {
+  int32_t luminosityDifference = NS_LUMINOSITY_DIFFERENCE(
+      GDK_RGBA_TO_NS_RGBA(aColor1), GDK_RGBA_TO_NS_RGBA(aColor2));
+  if (luminosityDifference < NS_SUFFICIENT_LUMINOSITY_DIFFERENCE) {
+    return false;
+  }
+
+  double colorDifference = std::abs(aColor1.red - aColor2.red) +
+                           std::abs(aColor1.green - aColor2.green) +
+                           std::abs(aColor1.blue - aColor2.blue);
+  return (colorDifference * 255.0 > 500.0);
+}
+
+// Check if the foreground/background colors match with default white/black
+// html page colors.
+static bool IsGtkThemeCompatibleWithHTMLColors() {
+  GdkRGBA white = {1.0, 1.0, 1.0};
+  GdkRGBA black = {0.0, 0.0, 0.0};
+
+  GtkStyleContext* style = GetStyleContext(MOZ_GTK_WINDOW);
+
+  GdkRGBA textColor;
+  gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &textColor);
+
+  // Theme text color and default white html page background
+  if (!HasGoodContrastVisibility(textColor, white)) {
+    return false;
+  }
+
+  GdkRGBA backgroundColor;
+  gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL,
+                                         &backgroundColor);
+
+  // Theme background color and default white html page background
+  if (HasGoodContrastVisibility(backgroundColor, white)) {
+    return false;
+  }
+
+  // Theme background color and default black text color
+  return HasGoodContrastVisibility(backgroundColor, black);
+}
+
+static void ConfigureContentGtkTheme() {
+  GtkSettings* settings = gtk_settings_get_for_screen(gdk_screen_get_default());
+  nsAutoCString contentThemeName;
+  mozilla::Preferences::GetCString("widget.content.gtk-theme-override",
+                                   contentThemeName);
+  if (!contentThemeName.IsEmpty()) {
+    g_object_set(settings, "gtk-theme-name", contentThemeName.get(), nullptr);
+  }
+
+  // Dark theme is active but user explicitly enables it so we're done now.
+  if (mozilla::Preferences::GetBool("widget.content.allow-gtk-dark-theme",
+                                    false)) {
+    return;
+  }
+
+  // Try to disable 'gtk-application-prefer-dark-theme' first...
+  if (SystemPrefersDarkVariant(settings)) {
+    g_object_set(settings, dark_theme_setting, FALSE, nullptr);
+  }
+
+  // ...and use a default Gtk theme as a fallback.
+  if (contentThemeName.IsEmpty() && !IsGtkThemeCompatibleWithHTMLColors()) {
+    g_object_set(settings, "gtk-theme-name", "Adwaita", nullptr);
+  }
+}
+
 void nsLookAndFeel::EnsureInit() {
   GdkColor colorValue;
   GdkColor* colorValuePtr;
 
   if (mInitialized) {
     return;
   }
 
@@ -860,48 +937,34 @@ void nsLookAndFeel::EnsureInit() {
   GdkRGBA bg, fg;
   style = GetStyleContext(MOZ_GTK_WINDOW);
   gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &bg);
   gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &fg);
   mSystemUsesDarkTheme =
       (RelativeLuminanceUtils::Compute(GDK_RGBA_TO_NS_RGBA(bg)) <
        RelativeLuminanceUtils::Compute(GDK_RGBA_TO_NS_RGBA(fg)));
 
-  // Dark themes interacts poorly with widget styling (see bug 1216658).
-  // We disable dark themes by default for all processes (chrome, web content)
-  // but allow user to overide it by prefs.
-  const gchar* dark_setting = "gtk-application-prefer-dark-theme";
-  gboolean darkThemeDefault;
-  g_object_get(settings, dark_setting, &darkThemeDefault, nullptr);
-
-  // To avoid triggering reload of theme settings unnecessarily, only set the
-  // setting when necessary.
-  if (darkThemeDefault) {
-    bool allowDarkTheme;
-    if (XRE_IsContentProcess()) {
-      allowDarkTheme = mozilla::Preferences::GetBool(
-          "widget.content.allow-gtk-dark-theme", false);
-    } else {
-      allowDarkTheme = (PR_GetEnv("MOZ_ALLOW_GTK_DARK_THEME") != nullptr) ||
-                       mozilla::Preferences::GetBool(
-                           "widget.chrome.allow-gtk-dark-theme", false);
-    }
-    if (!allowDarkTheme) {
-      g_object_set(settings, dark_setting, FALSE, nullptr);
-    }
-  }
-
-  // Allow content Gtk theme override by pref, it's useful when styled Gtk+
-  // widgets break web content.
   if (XRE_IsContentProcess()) {
-    nsAutoCString contentThemeName;
-    mozilla::Preferences::GetCString("widget.content.gtk-theme-override",
-                                     contentThemeName);
-    if (!contentThemeName.IsEmpty()) {
-      g_object_set(settings, "gtk-theme-name", contentThemeName.get(), nullptr);
+    // Dark themes interacts poorly with widget styling (see bug 1216658).
+    // We disable dark themes by default for web content
+    // but allow user to overide it by prefs.
+    ConfigureContentGtkTheme();
+  } else {
+    // To avoid triggering reload of theme settings unnecessarily, only set the
+    // setting when necessary.
+    GtkSettings* settings =
+        gtk_settings_get_for_screen(gdk_screen_get_default());
+    if (SystemPrefersDarkVariant(settings)) {
+      bool allowDarkTheme =
+          (PR_GetEnv("MOZ_ALLOW_GTK_DARK_THEME") != nullptr) ||
+          mozilla::Preferences::GetBool("widget.chrome.allow-gtk-dark-theme",
+                                        false);
+      if (!allowDarkTheme) {
+        g_object_set(settings, dark_theme_setting, FALSE, nullptr);
+      }
     }
   }
 
   // The label is not added to a parent widget, but shared for constructing
   // different style contexts.  The node hierarchy is constructed only on
   // the label style context.
   GtkWidget* labelWidget = gtk_label_new("M");
   g_object_ref_sink(labelWidget);