Bug 1958733 - Use non-native GTK tooltip drawing. r=stransky

This looks pretty good, is simpler, and should avoid issues where the
CSS colors and the widget colors are out of sync like bug 1958596.

Differential Revision: https://phabricator.services.mozilla.com/D244537
This commit is contained in:
Emilio Cobos Álvarez
2025-04-07 09:28:54 +00:00
parent b7f1e63735
commit 171a2eef5c
10 changed files with 37 additions and 172 deletions

View File

@@ -6,6 +6,7 @@
<script> <script>
const NON_CONTENT_ACCESSIBLE_ENV_VARS = [ const NON_CONTENT_ACCESSIBLE_ENV_VARS = [
"-moz-gtk-csd-titlebar-radius", "-moz-gtk-csd-titlebar-radius",
"-moz-gtk-csd-tooltip-radius",
"-moz-gtk-csd-minimize-button-position", "-moz-gtk-csd-minimize-button-position",
"-moz-gtk-csd-maximize-button-position", "-moz-gtk-csd-maximize-button-position",
"-moz-gtk-csd-close-button-position", "-moz-gtk-csd-close-button-position",

View File

@@ -142,7 +142,7 @@ macro_rules! lnf_int_variable {
}}; }};
} }
static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 9] = [ static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 10] = [
lnf_int_variable!( lnf_int_variable!(
atom!("-moz-mac-titlebar-height"), atom!("-moz-mac-titlebar-height"),
MacTitlebarHeight, MacTitlebarHeight,
@@ -158,6 +158,11 @@ static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 9] = [
TitlebarRadius, TitlebarRadius,
int_pixels int_pixels
), ),
lnf_int_variable!(
atom!("-moz-gtk-csd-tooltip-radius"),
TooltipRadius,
int_pixels
),
lnf_int_variable!( lnf_int_variable!(
atom!("-moz-gtk-csd-close-button-position"), atom!("-moz-gtk-csd-close-button-position"),
GTKCSDCloseButtonPosition, GTKCSDCloseButtonPosition,

View File

@@ -369,48 +369,34 @@ tooltip {
max-width: 40em; max-width: 40em;
overflow: clip; overflow: clip;
pointer-events: none; pointer-events: none;
}
/** /**
* It's important that these styles are in a UA sheet, because the default * It's important that these styles are in a UA sheet, because the default
* tooltip is native anonymous content * tooltip is native anonymous content
*/ */
@media (-moz-platform: linux) { @media (-moz-platform: linux) {
tooltip {
padding: 6px 10px; /* Matches Adwaita. */ padding: 6px 10px; /* Matches Adwaita. */
line-height: 1.4; /* For Noto Sans; note that 1.2 may clip descenders. */ line-height: 1.4; /* For Noto Sans; note that 1.2 may clip descenders. */
border: .5px solid color-mix(in srgb, currentColor 60%, transparent);
@media (-moz-gtk-csd-transparency-available) {
border-radius: env(-moz-gtk-csd-tooltip-radius);
}
} }
} @media (-moz-platform: macos) {
@media (-moz-platform: macos) {
tooltip {
padding: 2px 6px; /* Matches native metrics. */ padding: 2px 6px; /* Matches native metrics. */
} }
} @media (-moz-platform: windows) {
@media (-moz-platform: windows) {
tooltip {
appearance: none; appearance: none;
border: 1px solid; border: 1px solid;
}
/* TODO(emilio): Probably make InfoText/InfoBackground do the right thing and /* TODO(emilio): Probably make InfoText/InfoBackground do the right thing and
* remove this? */ * remove this? */
@media not (prefers-contrast) { @media not (prefers-contrast) {
tooltip { background-color: light-dark(#f9f9fb, #2b2a33);
background-color: #f9f9fb; color: light-dark(black, white);
color: black; border-color: light-dark(#67676c, #f9f9fb);
border-color: #67676c;
border-radius: 4px; border-radius: 4px;
} }
@media (prefers-color-scheme: dark) {
tooltip {
background-color: #2b2a33;
color: white;
border-color: #f9f9fb;
}
}
} }
} }

View File

@@ -305,6 +305,9 @@ class LookAndFeel {
/** GTK button-to-button spacing in the inline axis */ /** GTK button-to-button spacing in the inline axis */
TitlebarButtonSpacing, TitlebarButtonSpacing,
/** GTK tooltip radius */
TooltipRadius,
/** /**
* Corresponding to dynamic-range. * Corresponding to dynamic-range.
* https://drafts.csswg.org/mediaqueries-5/#dynamic-range * https://drafts.csswg.org/mediaqueries-5/#dynamic-range

View File

@@ -56,20 +56,6 @@ static gint moz_gtk_get_tab_thickness(GtkStyleContext* style);
static void Inset(GdkRectangle*, const GtkBorder&); static void Inset(GdkRectangle*, const GtkBorder&);
static void InsetByMargin(GdkRectangle*, GtkStyleContext* style);
static void moz_gtk_add_style_margin(GtkStyleContext* style, gint* left,
gint* top, gint* right, gint* bottom) {
GtkBorder margin;
gtk_style_context_get_margin(style, gtk_style_context_get_state(style),
&margin);
*left += margin.left;
*right += margin.right;
*top += margin.top;
*bottom += margin.bottom;
}
static void moz_gtk_add_style_border(GtkStyleContext* style, gint* left, static void moz_gtk_add_style_border(GtkStyleContext* style, gint* left,
gint* top, gint* right, gint* bottom) { gint* top, gint* right, gint* bottom) {
GtkBorder border; GtkBorder border;
@@ -96,14 +82,6 @@ static void moz_gtk_add_style_padding(GtkStyleContext* style, gint* left,
*bottom += padding.bottom; *bottom += padding.bottom;
} }
static void moz_gtk_add_margin_border_padding(GtkStyleContext* style,
gint* left, gint* top,
gint* right, gint* bottom) {
moz_gtk_add_style_margin(style, left, top, right, bottom);
moz_gtk_add_style_border(style, left, top, right, bottom);
moz_gtk_add_style_padding(style, left, top, right, bottom);
}
static void moz_gtk_add_border_padding(GtkStyleContext* style, gint* left, static void moz_gtk_add_border_padding(GtkStyleContext* style, gint* left,
gint* top, gint* right, gint* bottom) { gint* top, gint* right, gint* bottom) {
moz_gtk_add_style_border(style, left, top, right, bottom); moz_gtk_add_style_border(style, left, top, right, bottom);
@@ -598,14 +576,6 @@ static void Inset(GdkRectangle* rect, const GtkBorder& aBorder) {
rect->height -= aBorder.top + aBorder.bottom; rect->height -= aBorder.top + aBorder.bottom;
} }
// Inset a rectangle by the margins specified in a style context.
static void InsetByMargin(GdkRectangle* rect, GtkStyleContext* style) {
GtkBorder margin;
gtk_style_context_get_margin(style, gtk_style_context_get_state(style),
&margin);
Inset(rect, margin);
}
// Inset a rectangle by the border and padding specified in a style context. // Inset a rectangle by the border and padding specified in a style context.
static void InsetByBorderPadding(GdkRectangle* rect, GtkStyleContext* style) { static void InsetByBorderPadding(GdkRectangle* rect, GtkStyleContext* style) {
GtkStateFlags state = gtk_style_context_get_state(style); GtkStateFlags state = gtk_style_context_get_state(style);
@@ -617,20 +587,6 @@ static void InsetByBorderPadding(GdkRectangle* rect, GtkStyleContext* style) {
Inset(rect, border); Inset(rect, border);
} }
static void moz_gtk_draw_styled_frame(GtkStyleContext* style, cairo_t* cr,
const GdkRectangle* aRect,
bool drawFocus) {
GdkRectangle rect = *aRect;
InsetByMargin(&rect, style);
gtk_render_background(style, cr, rect.x, rect.y, rect.width, rect.height);
gtk_render_frame(style, cr, rect.x, rect.y, rect.width, rect.height);
if (drawFocus) {
gtk_render_focus(style, cr, rect.x, rect.y, rect.width, rect.height);
}
}
/* See gtk_range_draw() for reference. /* See gtk_range_draw() for reference.
*/ */
static gint moz_gtk_scale_paint(cairo_t* cr, GdkRectangle* rect, static gint moz_gtk_scale_paint(cairo_t* cr, GdkRectangle* rect,
@@ -916,54 +872,6 @@ static gint moz_gtk_arrow_paint(cairo_t* cr, GdkRectangle* rect,
return MOZ_GTK_SUCCESS; return MOZ_GTK_SUCCESS;
} }
static gint moz_gtk_tooltip_paint(cairo_t* cr, const GdkRectangle* aRect,
GtkWidgetState* state,
GtkTextDirection direction) {
// Tooltip widget is made in GTK3 as following tree:
// Tooltip window
// Horizontal Box
// Icon (not supported by Firefox)
// Label
// Each element can be fully styled by CSS of GTK theme.
// We have to draw all elements with appropriate offset and right dimensions.
// Tooltip drawing
GtkStyleContext* style =
GetStyleContext(MOZ_GTK_TOOLTIP, state->image_scale, direction);
GdkRectangle rect = *aRect;
gtk_render_background(style, cr, rect.x, rect.y, rect.width, rect.height);
gtk_render_frame(style, cr, rect.x, rect.y, rect.width, rect.height);
// Horizontal Box drawing
//
// The box element has hard-coded 6px margin-* GtkWidget properties, which
// are added between the window dimensions and the CSS margin box of the
// horizontal box. The frame of the tooltip window is drawn in the
// 6px margin.
// For drawing Horizontal Box we have to inset drawing area by that 6px
// plus its CSS margin.
GtkStyleContext* boxStyle =
GetStyleContext(MOZ_GTK_TOOLTIP_BOX, state->image_scale, direction);
rect.x += 6;
rect.y += 6;
rect.width -= 12;
rect.height -= 12;
InsetByMargin(&rect, boxStyle);
gtk_render_background(boxStyle, cr, rect.x, rect.y, rect.width, rect.height);
gtk_render_frame(boxStyle, cr, rect.x, rect.y, rect.width, rect.height);
// Label drawing
InsetByBorderPadding(&rect, boxStyle);
GtkStyleContext* labelStyle =
GetStyleContext(MOZ_GTK_TOOLTIP_BOX_LABEL, state->image_scale, direction);
moz_gtk_draw_styled_frame(labelStyle, cr, &rect, false);
return MOZ_GTK_SUCCESS;
}
static gint moz_gtk_resizer_paint(cairo_t* cr, GdkRectangle* rect, static gint moz_gtk_resizer_paint(cairo_t* cr, GdkRectangle* rect,
GtkWidgetState* state, GtkWidgetState* state,
GtkTextDirection direction) { GtkTextDirection direction) {
@@ -1409,23 +1317,6 @@ gint moz_gtk_get_widget_border(WidgetNodeType widget, gint* left, gint* top,
case MOZ_GTK_FRAME: case MOZ_GTK_FRAME:
w = GetWidget(MOZ_GTK_FRAME); w = GetWidget(MOZ_GTK_FRAME);
break; break;
case MOZ_GTK_TOOLTIP: {
// In GTK 3 there are 6 pixels of additional margin around the box.
// See details there:
// https://github.com/GNOME/gtk/blob/5ea69a136bd7e4970b3a800390e20314665aaed2/gtk/ui/gtktooltipwindow.ui#L11
*left = *right = *top = *bottom = 6;
// We also need to add margin/padding/borders from Tooltip content.
// Tooltip contains horizontal box, where icon and label is put.
// We ignore icon as long as we don't have support for it.
GtkStyleContext* boxStyle = GetStyleContext(MOZ_GTK_TOOLTIP_BOX);
moz_gtk_add_margin_border_padding(boxStyle, left, top, right, bottom);
GtkStyleContext* labelStyle = GetStyleContext(MOZ_GTK_TOOLTIP_BOX_LABEL);
moz_gtk_add_margin_border_padding(labelStyle, left, top, right, bottom);
return MOZ_GTK_SUCCESS;
}
/* These widgets have no borders, since they are not containers. */ /* These widgets have no borders, since they are not containers. */
case MOZ_GTK_SPLITTER_HORIZONTAL: case MOZ_GTK_SPLITTER_HORIZONTAL:
case MOZ_GTK_SPLITTER_VERTICAL: case MOZ_GTK_SPLITTER_VERTICAL:
@@ -1670,8 +1561,6 @@ gint moz_gtk_widget_paint(WidgetNodeType widget, cairo_t* cr,
return moz_gtk_text_view_paint(cr, rect, state, direction); return moz_gtk_text_view_paint(cr, rect, state, direction);
case MOZ_GTK_DROPDOWN: case MOZ_GTK_DROPDOWN:
return moz_gtk_combo_box_paint(cr, rect, state, direction); return moz_gtk_combo_box_paint(cr, rect, state, direction);
case MOZ_GTK_TOOLTIP:
return moz_gtk_tooltip_paint(cr, rect, state, direction);
case MOZ_GTK_FRAME: case MOZ_GTK_FRAME:
return moz_gtk_frame_paint(cr, rect, state, direction); return moz_gtk_frame_paint(cr, rect, state, direction);
case MOZ_GTK_RESIZER: case MOZ_GTK_RESIZER:

View File

@@ -1176,6 +1176,11 @@ nsresult nsLookAndFeel::NativeGetInt(IntID aID, int32_t& aResult) {
// No GTK API for checking if inverted colors is enabled // No GTK API for checking if inverted colors is enabled
aResult = 0; aResult = 0;
break; break;
case IntID::TooltipRadius: {
EnsureInit();
aResult = EffectiveTheme().mTooltipRadius;
break;
}
case IntID::TitlebarRadius: { case IntID::TitlebarRadius: {
EnsureInit(); EnsureInit();
aResult = EffectiveTheme().mTitlebarRadius; aResult = EffectiveTheme().mTitlebarRadius;
@@ -2100,6 +2105,7 @@ void nsLookAndFeel::PerThemeData::Init() {
mInfo.mFg = GetTextColor(style); mInfo.mFg = GetTextColor(style);
style = GetStyleContext(MOZ_GTK_TOOLTIP); style = GetStyleContext(MOZ_GTK_TOOLTIP);
mInfo.mBg = GetBackgroundColor(style, mInfo.mFg); mInfo.mBg = GetBackgroundColor(style, mInfo.mFg);
mTooltipRadius = GetBorderRadius(style);
style = GetStyleContext(MOZ_GTK_MENUITEM); style = GetStyleContext(MOZ_GTK_MENUITEM);
{ {

View File

@@ -167,6 +167,7 @@ class nsLookAndFeel final : public nsXPLookAndFeel {
float mCaretRatio = 0.0f; float mCaretRatio = 0.0f;
int32_t mTitlebarRadius = 0; int32_t mTitlebarRadius = 0;
int32_t mTooltipRadius = 0;
int32_t mTitlebarButtonSpacing = 0; int32_t mTitlebarButtonSpacing = 0;
char16_t mInvisibleCharacter = 0; char16_t mInvisibleCharacter = 0;
bool mMenuSupportsDrag = false; bool mMenuSupportsDrag = false;

View File

@@ -291,9 +291,6 @@ bool nsNativeThemeGTK::GetGtkWidgetAndState(StyleAppearance aAppearance,
*aWidgetFlags = GTK_ARROW_LEFT; *aWidgetFlags = GTK_ARROW_LEFT;
} }
break; break;
case StyleAppearance::Tooltip:
aGtkWidgetType = MOZ_GTK_TOOLTIP;
break;
case StyleAppearance::ProgressBar: case StyleAppearance::ProgressBar:
aGtkWidgetType = MOZ_GTK_PROGRESSBAR; aGtkWidgetType = MOZ_GTK_PROGRESSBAR;
break; break;
@@ -868,7 +865,6 @@ bool nsNativeThemeGTK::GetWidgetPadding(nsDeviceContext* aContext,
} }
switch (aAppearance) { switch (aAppearance) {
case StyleAppearance::Toolbarbutton: case StyleAppearance::Toolbarbutton:
case StyleAppearance::Tooltip:
case StyleAppearance::MozWindowButtonClose: case StyleAppearance::MozWindowButtonClose:
case StyleAppearance::MozWindowButtonMinimize: case StyleAppearance::MozWindowButtonMinimize:
case StyleAppearance::MozWindowButtonMaximize: case StyleAppearance::MozWindowButtonMaximize:
@@ -919,21 +915,6 @@ auto nsNativeThemeGTK::IsWidgetNonNative(nsIFrame* aFrame,
return NonNative::No; return NonNative::No;
} }
// As an special-case, for tooltips, we check if the tooltip color is the
// same between the light and dark themes. If so we can get away with drawing
// the native widget, see bug 1817396.
if (aAppearance == StyleAppearance::Tooltip) {
auto darkColor =
LookAndFeel::Color(StyleSystemColor::Infotext, ColorScheme::Dark,
LookAndFeel::UseStandins::No);
auto lightColor =
LookAndFeel::Color(StyleSystemColor::Infotext, ColorScheme::Light,
LookAndFeel::UseStandins::No);
if (darkColor == lightColor) {
return NonNative::No;
}
}
// If the non-native theme doesn't support the widget then oh well... // If the non-native theme doesn't support the widget then oh well...
if (!Theme::ThemeSupportsWidget(aFrame->PresContext(), aFrame, aAppearance)) { if (!Theme::ThemeSupportsWidget(aFrame->PresContext(), aFrame, aAppearance)) {
return NonNative::No; return NonNative::No;
@@ -1068,7 +1049,6 @@ bool nsNativeThemeGTK::WidgetAttributeChangeRequiresRepaint(
// Some widget types just never change state. // Some widget types just never change state.
if (aAppearance == StyleAppearance::Progresschunk || if (aAppearance == StyleAppearance::Progresschunk ||
aAppearance == StyleAppearance::ProgressBar || aAppearance == StyleAppearance::ProgressBar ||
aAppearance == StyleAppearance::Tooltip ||
aAppearance == StyleAppearance::MozWindowDecorations) { aAppearance == StyleAppearance::MozWindowDecorations) {
return false; return false;
} }
@@ -1117,7 +1097,6 @@ nsNativeThemeGTK::ThemeSupportsWidget(nsPresContext* aPresContext,
case StyleAppearance::Tab: case StyleAppearance::Tab:
// case StyleAppearance::Tabpanel: // case StyleAppearance::Tabpanel:
case StyleAppearance::Tabpanels: case StyleAppearance::Tabpanels:
case StyleAppearance::Tooltip:
case StyleAppearance::NumberInput: case StyleAppearance::NumberInput:
case StyleAppearance::PasswordInput: case StyleAppearance::PasswordInput:
case StyleAppearance::Textfield: case StyleAppearance::Textfield:
@@ -1178,14 +1157,7 @@ nsITheme::Transparency nsNativeThemeGTK::GetWidgetTransparency(
return Theme::GetWidgetTransparency(aFrame, aAppearance); return Theme::GetWidgetTransparency(aFrame, aAppearance);
} }
switch (aAppearance) { return eUnknownTransparency;
// Tooltips use gtk_paint_flat_box() on Gtk2
// but are shaped on Gtk3
case StyleAppearance::Tooltip:
return eTransparent;
default:
return eUnknownTransparency;
}
} }
already_AddRefed<Theme> do_CreateNativeThemeDoNotUseDirectly() { already_AddRefed<Theme> do_CreateNativeThemeDoNotUseDirectly() {

View File

@@ -158,6 +158,7 @@ static const char sIntPrefs[][45] = {
"ui.touchDeviceSupportPresent", "ui.touchDeviceSupportPresent",
"ui.titlebarRadius", "ui.titlebarRadius",
"ui.titlebarButtonSpacing", "ui.titlebarButtonSpacing",
"ui.tooltipRadius",
"ui.dynamicRange", "ui.dynamicRange",
"ui.panelAnimations", "ui.panelAnimations",
"ui.hideCursorWhileTyping", "ui.hideCursorWhileTyping",

View File

@@ -2293,6 +2293,7 @@ STATIC_ATOMS = [
Atom("_moz_gtk_csd_available", "-moz-gtk-csd-available"), Atom("_moz_gtk_csd_available", "-moz-gtk-csd-available"),
Atom("_moz_gtk_csd_transparency_available", "-moz-gtk-csd-transparency-available"), Atom("_moz_gtk_csd_transparency_available", "-moz-gtk-csd-transparency-available"),
Atom("_moz_gtk_csd_titlebar_radius", "-moz-gtk-csd-titlebar-radius"), Atom("_moz_gtk_csd_titlebar_radius", "-moz-gtk-csd-titlebar-radius"),
Atom("_moz_gtk_csd_tooltip_radius", "-moz-gtk-csd-tooltip-radius"),
Atom("_moz_gtk_csd_titlebar_button_spacing", "-moz-gtk-csd-titlebar-button-spacing"), Atom("_moz_gtk_csd_titlebar_button_spacing", "-moz-gtk-csd-titlebar-button-spacing"),
Atom("_moz_gtk_csd_minimize_button", "-moz-gtk-csd-minimize-button"), Atom("_moz_gtk_csd_minimize_button", "-moz-gtk-csd-minimize-button"),
Atom("_moz_gtk_csd_minimize_button_position", "-moz-gtk-csd-minimize-button-position"), Atom("_moz_gtk_csd_minimize_button_position", "-moz-gtk-csd-minimize-button-position"),