From 7c282132d5cfcb99bd4738c0032d63fe57e4c1fc Mon Sep 17 00:00:00 2001 From: Martin Stransky Date: Thu, 14 Sep 2017 12:37:37 +0200 Subject: Added experimental patch for mozbz#1399611 --- firefox.spec | 7 +- mozilla-1399611.patch | 1467 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1473 insertions(+), 1 deletion(-) create mode 100644 mozilla-1399611.patch diff --git a/firefox.spec b/firefox.spec index 27adf05..1451e36 100644 --- a/firefox.spec +++ b/firefox.spec @@ -99,7 +99,7 @@ Summary: Mozilla Firefox Web browser Name: firefox Version: 55.0.3 -Release: 2%{?pre_tag}%{?dist} +Release: 3%{?pre_tag}%{?dist} URL: https://www.mozilla.org/firefox/ License: MPLv1.1 or GPLv2+ or LGPLv2+ Group: Applications/Internet @@ -164,6 +164,7 @@ Patch410: mozilla-1321521.patch Patch411: mozilla-1321521-2.patch Patch412: mozilla-1337988.patch Patch413: mozilla-1353817.patch +Patch414: mozilla-1399611.patch # Debian patches Patch500: mozilla-440908.patch @@ -350,6 +351,7 @@ This package contains results of tests executed during build. %endif %endif %patch413 -p1 -b .1353817 +%patch414 -p1 -b .1399611 # Debian extension patch %patch500 -p1 -b .440908 @@ -872,6 +874,9 @@ gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || : #--------------------------------------------------------------------- %changelog +* Thu Sep 14 2017 Martin Stransky - 55.0.3-3 +- Added experimental patch for mozbz#1399611 + * Thu Sep 14 2017 Ville Skyttä - 55.0.3-2 - Own the %%{_sysconfdir}/%%{name} dir diff --git a/mozilla-1399611.patch b/mozilla-1399611.patch new file mode 100644 index 0000000..5d04664 --- /dev/null +++ b/mozilla-1399611.patch @@ -0,0 +1,1467 @@ +diff -up firefox-55.0.3/browser/app/profile/firefox.js.csd firefox-55.0.3/browser/app/profile/firefox.js +--- firefox-55.0.3/browser/app/profile/firefox.js.csd 2017-09-14 11:29:45.346643502 +0200 ++++ firefox-55.0.3/browser/app/profile/firefox.js 2017-09-14 11:36:31.074589389 +0200 +@@ -463,11 +463,7 @@ pref("browser.tabs.opentabfor.middleclic + pref("browser.tabs.loadDivertedInBackground", false); + pref("browser.tabs.loadBookmarksInBackground", false); + pref("browser.tabs.tabClipWidth", 140); +-#ifdef UNIX_BUT_NOT_MAC +-pref("browser.tabs.drawInTitlebar", false); +-#else + pref("browser.tabs.drawInTitlebar", true); +-#endif + + // When tabs opened by links in other tabs via a combination of + // browser.link.open_newwindow being set to 3 and target="_blank" etc are +diff -up firefox-55.0.3/browser/base/content/browser-tabsintitlebar.js.csd firefox-55.0.3/browser/base/content/browser-tabsintitlebar.js +--- firefox-55.0.3/browser/base/content/browser-tabsintitlebar.js.csd 2017-07-31 18:20:46.000000000 +0200 ++++ firefox-55.0.3/browser/base/content/browser-tabsintitlebar.js 2017-09-14 11:29:45.347643499 +0200 +@@ -14,6 +14,11 @@ var TabsInTitlebar = { + this._readPref(); + Services.prefs.addObserver(this._prefName, this); + ++ // Always disable on unsupported GTK versions. ++ if (AppConstants.MOZ_WIDGET_TOOLKIT == "gtk3") { ++ this.allowedBy("gtk", window.matchMedia("(-moz-gtk-csd-available)")); ++ } ++ + // We need to update the appearance of the titlebar when the menu changes + // from the active to the inactive state. We can't, however, rely on + // DOMMenuBarInactive, because the menu fires this event and then removes +diff -up firefox-55.0.3/browser/base/moz.build.csd firefox-55.0.3/browser/base/moz.build +--- firefox-55.0.3/browser/base/moz.build.csd 2017-07-31 18:20:47.000000000 +0200 ++++ firefox-55.0.3/browser/base/moz.build 2017-09-14 11:29:45.347643499 +0200 +@@ -49,7 +49,7 @@ DEFINES['APP_LICENSE_BLOCK'] = '%s/conte + if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3', 'cocoa'): + DEFINES['CONTEXT_COPY_IMAGE_CONTENTS'] = 1 + +-if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'cocoa'): ++if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'cocoa', 'gtk3'): + DEFINES['CAN_DRAW_IN_TITLEBAR'] = 1 + + if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3'): +diff -up firefox-55.0.3/browser/themes/linux/browser.css.csd firefox-55.0.3/browser/themes/linux/browser.css +--- firefox-55.0.3/browser/themes/linux/browser.css.csd 2017-07-31 18:20:49.000000000 +0200 ++++ firefox-55.0.3/browser/themes/linux/browser.css 2017-09-14 11:37:47.545390712 +0200 +@@ -980,8 +980,12 @@ html|span.ac-emphasize-text-url { + color: -moz-menubartext; + } + ++/* Support dragging the window using the toolbar when drawing our own ++ * decorations, or where the GTK theme allows. */ + #toolbar-menubar:not([autohide="true"]):not(:-moz-lwtheme):-moz-system-metric(menubar-drag), +-#TabsToolbar:not(:-moz-lwtheme):-moz-system-metric(menubar-drag) { ++#TabsToolbar:not(:-moz-lwtheme):-moz-system-metric(menubar-drag), ++#main-window[tabsintitlebar] #toolbar-menubar:not([autohide="true"]), ++#main-window[tabsintitlebar] #TabsToolbar { + -moz-binding: url("chrome://browser/content/customizableui/toolbar.xml#toolbar-drag"); + } + +@@ -1217,6 +1221,7 @@ notification.pluginVulnerable > .notific + /* End customization mode */ + + ++#main-window[tabsintitlebar][privatebrowsingmode=temporary] > #titlebar, + #main-window[privatebrowsingmode=temporary] #private-browsing-indicator { + background: url("chrome://browser/skin/privatebrowsing-mask.png") center no-repeat; + width: 40px; +@@ -1272,3 +1277,41 @@ notification.pluginVulnerable > .notific + .webextension-popup-browser { + border-radius: inherit; + } ++ ++/* Titlebar/CSD */ ++@media (-moz-gtk-csd-available) { ++ #main-window[tabsintitlebar][sizemode="normal"]:not([customizing]):not(:-moz-lwtheme) > #titlebar { ++ -moz-appearance: -moz-window-titlebar; ++ } ++ ++ #main-window[tabsintitlebar][sizemode="maximized"]:not([customizing]):not(:-moz-lwtheme) > #titlebar { ++ -moz-appearance: -moz-window-titlebar-maximized; ++ } ++ ++ #main-window[tabsintitlebar]:not([sizemode="fullscreen"]) #TabsToolbar { ++ -moz-appearance: none; ++ } ++ ++ /* titlebar command buttons */ ++ ++ #titlebar-min { ++ list-style-image: url("moz-icon://stock/window-minimize-symbolic?size=menu"); ++ -moz-appearance: -moz-window-button-minimize; ++ } ++ ++ #titlebar-max { ++ list-style-image: url("moz-icon://stock/window-maximize-symbolic?size=menu"); ++ -moz-appearance: -moz-window-button-maximize; ++ } ++ ++ #main-window[sizemode="maximized"] #titlebar-max { ++ list-style-image: url("moz-icon://stock/window-restore-symbolic?size=menu"); ++ -moz-appearance: -moz-window-button-restore; ++ } ++ ++ #titlebar-close { ++ list-style-image: url("moz-icon://stock/window-close-symbolic?size=menu"); ++ -moz-appearance: -moz-window-button-close; ++ } ++} ++ +diff -up firefox-55.0.3/dom/base/nsGkAtomList.h.csd firefox-55.0.3/dom/base/nsGkAtomList.h +--- firefox-55.0.3/dom/base/nsGkAtomList.h.csd 2017-07-31 18:20:51.000000000 +0200 ++++ firefox-55.0.3/dom/base/nsGkAtomList.h 2017-09-14 11:29:45.347643499 +0200 +@@ -2203,6 +2203,7 @@ GK_ATOM(touch_enabled, "touch-enabled") + GK_ATOM(menubar_drag, "menubar-drag") + GK_ATOM(swipe_animation_enabled, "swipe-animation-enabled") + GK_ATOM(physical_home_button, "physical-home-button") ++GK_ATOM(gtk_csd_available, "gtk-csd-available") + + // windows theme selector metrics + GK_ATOM(windows_classic, "windows-classic") +@@ -2238,6 +2239,7 @@ GK_ATOM(_moz_device_orientation, "-moz-d + GK_ATOM(_moz_is_resource_document, "-moz-is-resource-document") + GK_ATOM(_moz_swipe_animation_enabled, "-moz-swipe-animation-enabled") + GK_ATOM(_moz_physical_home_button, "-moz-physical-home-button") ++GK_ATOM(_moz_gtk_csd_available, "-moz-gtk-csd-available") + + // application commands + GK_ATOM(Back, "Back") +diff -up firefox-55.0.3/gfx/src/nsThemeConstants.h.csd firefox-55.0.3/gfx/src/nsThemeConstants.h +--- firefox-55.0.3/gfx/src/nsThemeConstants.h.csd 2017-07-31 18:20:46.000000000 +0200 ++++ firefox-55.0.3/gfx/src/nsThemeConstants.h 2017-09-14 11:29:45.348643497 +0200 +@@ -299,6 +299,7 @@ enum ThemeWidgetType : uint8_t { + NS_THEME_MAC_SOURCE_LIST, + NS_THEME_MAC_SOURCE_LIST_SELECTION, + NS_THEME_MAC_ACTIVE_SOURCE_LIST_SELECTION, ++ NS_THEME_GTK_WINDOW_DECORATION, + + ThemeWidgetType_COUNT + }; +diff -up firefox-55.0.3/layout/style/nsCSSRuleProcessor.cpp.csd firefox-55.0.3/layout/style/nsCSSRuleProcessor.cpp +--- firefox-55.0.3/layout/style/nsCSSRuleProcessor.cpp.csd 2017-07-31 18:20:48.000000000 +0200 ++++ firefox-55.0.3/layout/style/nsCSSRuleProcessor.cpp 2017-09-14 11:29:45.348643497 +0200 +@@ -1168,6 +1168,12 @@ nsCSSRuleProcessor::InitSystemMetrics() + sSystemMetrics->AppendElement(nsGkAtoms::physical_home_button); + } + ++ rv = LookAndFeel::GetInt(LookAndFeel::eIntID_GTKCSDAvailable, ++ &metricResult); ++ if (NS_SUCCEEDED(rv) && metricResult) { ++ sSystemMetrics->AppendElement(nsGkAtoms::gtk_csd_available); ++ } ++ + #ifdef XP_WIN + if (NS_SUCCEEDED( + LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsThemeIdentifier, +diff -up firefox-55.0.3/layout/style/nsMediaFeatures.cpp.csd firefox-55.0.3/layout/style/nsMediaFeatures.cpp +--- firefox-55.0.3/layout/style/nsMediaFeatures.cpp.csd 2017-06-15 22:52:11.000000000 +0200 ++++ firefox-55.0.3/layout/style/nsMediaFeatures.cpp 2017-09-14 11:29:45.348643497 +0200 +@@ -768,6 +768,15 @@ nsMediaFeatures::features[] = { + GetSystemMetric + }, + ++ { ++ &nsGkAtoms::_moz_gtk_csd_available, ++ nsMediaFeature::eMinMaxNotAllowed, ++ nsMediaFeature::eBoolInteger, ++ nsMediaFeature::eNoRequirements, ++ { &nsGkAtoms::gtk_csd_available }, ++ GetSystemMetric ++ }, ++ + // Internal -moz-is-glyph media feature: applies only inside SVG glyphs. + // Internal because it is really only useful in the user agent anyway + // and therefore not worth standardizing. +diff -up firefox-55.0.3/modules/libpref/init/all.js.csd firefox-55.0.3/modules/libpref/init/all.js +--- firefox-55.0.3/modules/libpref/init/all.js.csd 2017-09-14 11:39:55.152059181 +0200 ++++ firefox-55.0.3/modules/libpref/init/all.js 2017-09-14 11:40:08.494024516 +0200 +@@ -4796,6 +4796,7 @@ pref("gfx.apitrace.enabled",false); + pref("gfx.xrender.enabled",false); + pref("widget.chrome.allow-gtk-dark-theme", false); + pref("widget.content.allow-gtk-dark-theme", false); ++pref("widget.allow-client-side-decoration", false); + #endif + #endif + +diff -up firefox-55.0.3/toolkit/modules/moz.build.csd firefox-55.0.3/toolkit/modules/moz.build +--- firefox-55.0.3/toolkit/modules/moz.build.csd 2017-07-31 18:20:52.000000000 +0200 ++++ firefox-55.0.3/toolkit/modules/moz.build 2017-09-14 11:29:45.348643497 +0200 +@@ -263,7 +263,7 @@ EXTRA_JS_MODULES.sessionstore += [ + ] + + DEFINES['INSTALL_COMPACT_THEMES'] = 1 +-if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'cocoa'): ++if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'cocoa', 'gtk3'): + DEFINES['CAN_DRAW_IN_TITLEBAR'] = 1 + + if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3'): +diff -up firefox-55.0.3/widget/gtk/gtk3drawing.cpp.csd firefox-55.0.3/widget/gtk/gtk3drawing.cpp +--- firefox-55.0.3/widget/gtk/gtk3drawing.cpp.csd 2017-07-31 18:20:53.000000000 +0200 ++++ firefox-55.0.3/widget/gtk/gtk3drawing.cpp 2017-09-14 11:29:45.349643494 +0200 +@@ -17,6 +17,7 @@ + #include "WidgetStyleCache.h" + + #include ++#include + + static gboolean checkbox_check_state; + static gboolean notebook_has_tab_gap; +@@ -39,9 +40,25 @@ static gint + moz_gtk_menu_item_paint(WidgetNodeType widget, cairo_t *cr, GdkRectangle* rect, + GtkWidgetState* state, GtkTextDirection direction); + ++static void ++moz_gtk_add_style_margin(GtkStyleContext* style, ++ gint* left, gint* top, gint* right, gint* bottom); ++static void ++moz_gtk_add_style_border(GtkStyleContext* style, ++ gint* left, gint* top, gint* right, gint* bottom); ++static void ++moz_gtk_add_style_padding(GtkStyleContext* style, ++ gint* left, gint* top, gint* right, gint* bottom); ++static void moz_gtk_add_margin_border_padding(GtkStyleContext *style, ++ gint* left, gint* top, ++ gint* right, gint* bottom); ++static void moz_gtk_add_border_padding(GtkStyleContext *style, ++ gint* left, gint* top, ++ gint* right, gint* bottom); + static GtkBorder + GetMarginBorderPadding(GtkStyleContext* aStyle); + ++ + // GetStateFlagsFromGtkWidgetState() can be safely used for the specific + // GtkWidgets that set both prelight and active flags. For other widgets, + // either the GtkStateFlags or Gecko's GtkWidgetState need to be carefully +@@ -240,6 +257,43 @@ moz_gtk_splitter_get_metrics(gint orient + return MOZ_GTK_SUCCESS; + } + ++void ++moz_gtk_get_window_border(gint* top, gint* right, gint* bottom, gint* left) ++{ ++ MOZ_ASSERT(gtk_check_version(3, 20, 0) == nullptr, ++ "Window decorations are only supported on GTK 3.20+."); ++ ++ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_WINDOW); ++ ++ *top = *right = *bottom = *left = 0; ++ moz_gtk_add_border_padding(style, left, top, right, bottom); ++ GtkBorder windowMargin; ++ gtk_style_context_get_margin(style, GTK_STATE_FLAG_NORMAL, &windowMargin); ++ ++ style = ClaimStyleContext(MOZ_GTK_WINDOW_DECORATION); ++ ++ // Available on GTK 3.20+. ++ static auto sGtkRenderBackgroundGetClip = ++ (void (*)(GtkStyleContext*, gdouble, gdouble, gdouble, gdouble, GdkRectangle*)) ++ dlsym(RTLD_DEFAULT, "gtk_render_background_get_clip"); ++ ++ GdkRectangle shadowClip; ++ sGtkRenderBackgroundGetClip(style, 0, 0, 0, 0, &shadowClip); ++ ++ // Transfer returned inset rectangle to GtkBorder ++ GtkBorder shadowBorder = { ++ static_cast(-shadowClip.x), // left ++ static_cast(shadowClip.width + shadowClip.x), // right ++ static_cast(-shadowClip.y), // top ++ static_cast(shadowClip.height + shadowClip.y), // bottom ++ }; ++ ++ *left += MAX(windowMargin.left, shadowBorder.left); ++ *right += MAX(windowMargin.right, shadowBorder.right); ++ *top += MAX(windowMargin.top, shadowBorder.top); ++ *bottom += MAX(windowMargin.bottom, shadowBorder.bottom); ++} ++ + static gint + moz_gtk_window_paint(cairo_t *cr, GdkRectangle* rect, + GtkTextDirection direction) +@@ -311,6 +365,24 @@ moz_gtk_button_paint(cairo_t *cr, GdkRec + } + + static gint ++moz_gtk_header_bar_button_paint(cairo_t *cr, GdkRectangle* rect, ++ GtkWidgetState* state, ++ GtkReliefStyle relief, GtkWidget* widget, ++ GtkTextDirection direction) ++{ ++ GtkBorder margin; ++ GtkStyleContext* style = gtk_widget_get_style_context(widget); ++ gtk_style_context_get_margin(style, GTK_STATE_FLAG_NORMAL, &margin); ++ ++ rect->x += margin.left; ++ rect->y += margin.top; ++ rect->width -= margin.left + margin.right; ++ rect->height -= margin.top + margin.bottom; ++ ++ return moz_gtk_button_paint(cr, rect, state, relief, widget, direction); ++} ++ ++static gint + moz_gtk_toggle_paint(cairo_t *cr, GdkRectangle* rect, + GtkWidgetState* state, + gboolean selected, gboolean inconsistent, +@@ -2007,6 +2079,25 @@ moz_gtk_info_bar_paint(cairo_t *cr, GdkR + return MOZ_GTK_SUCCESS; + } + ++static gint ++moz_gtk_header_bar_paint(WidgetNodeType widgetType, ++ cairo_t *cr, GdkRectangle* rect, GtkWidgetState* state) ++{ ++ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state); ++ GtkStyleContext *style; ++ ++ style = ClaimStyleContext(widgetType, GTK_TEXT_DIR_LTR, ++ state_flags); ++ 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); ++ ++ ReleaseStyleContext(style); ++ ++ return MOZ_GTK_SUCCESS; ++} ++ + static void + moz_gtk_add_style_margin(GtkStyleContext* style, + gint* left, gint* top, gint* right, gint* bottom) +@@ -2058,6 +2149,14 @@ static void moz_gtk_add_margin_border_pa + moz_gtk_add_style_padding(style, left, top, right, bottom); + } + ++static void moz_gtk_add_border_padding(GtkStyleContext *style, ++ gint* left, gint* top, ++ gint* right, gint* bottom) ++{ ++ moz_gtk_add_style_border(style, left, top, right, bottom); ++ moz_gtk_add_style_padding(style, left, top, right, bottom); ++} ++ + static GtkBorder + GetMarginBorderPadding(GtkStyleContext* aStyle) + { +@@ -2114,8 +2213,7 @@ moz_gtk_get_widget_border(WidgetNodeType + // XXX: Subtract 1 pixel from the padding to account for the default + // padding in forms.css. See bug 1187385. + *left = *top = *right = *bottom = -1; +- moz_gtk_add_style_padding(style, left, top, right, bottom); +- moz_gtk_add_style_border(style, left, top, right, bottom); ++ moz_gtk_add_border_padding(style, left, top, right, bottom); + + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; +@@ -2140,8 +2238,7 @@ moz_gtk_get_widget_border(WidgetNodeType + GetWidget(MOZ_GTK_TREE_HEADER_CELL))); + + style = ClaimStyleContext(MOZ_GTK_TREE_HEADER_CELL); +- moz_gtk_add_style_border(style, left, top, right, bottom); +- moz_gtk_add_style_padding(style, left, top, right, bottom); ++ moz_gtk_add_border_padding(style, left, top, right, bottom); + ReleaseStyleContext(style); + return MOZ_GTK_SUCCESS; + } +@@ -2168,8 +2265,7 @@ moz_gtk_get_widget_border(WidgetNodeType + gtk_container_get_border_width(GTK_CONTAINER( + GetWidget(MOZ_GTK_COMBOBOX_BUTTON))); + style = ClaimStyleContext(MOZ_GTK_COMBOBOX_BUTTON); +- moz_gtk_add_style_padding(style, left, top, right, bottom); +- moz_gtk_add_style_border(style, left, top, right, bottom); ++ moz_gtk_add_border_padding(style, left, top, right, bottom); + ReleaseStyleContext(style); + + /* If there is no separator, don't try to count its width. */ +@@ -2224,10 +2320,8 @@ moz_gtk_get_widget_border(WidgetNodeType + style = gtk_widget_get_style_context(w); + + *left = *top = *right = *bottom = gtk_container_get_border_width(GTK_CONTAINER(w)); +- moz_gtk_add_style_border(style, +- left, top, right, bottom); +- moz_gtk_add_style_padding(style, +- left, top, right, bottom); ++ moz_gtk_add_border_padding(style, ++ left, top, right, bottom); + return MOZ_GTK_SUCCESS; + } + case MOZ_GTK_MENUPOPUP: +@@ -2277,6 +2371,23 @@ moz_gtk_get_widget_border(WidgetNodeType + + return MOZ_GTK_SUCCESS; + } ++ case MOZ_GTK_HEADER_BAR: ++ case MOZ_GTK_HEADER_BAR_MAXIMIZED: ++ { ++ style = ClaimStyleContext(widget); ++ moz_gtk_add_border_padding(style, left, top, right, bottom); ++ ReleaseStyleContext(style); ++ return MOZ_GTK_SUCCESS; ++ } ++ case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE: ++ case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE: ++ case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE: ++ { ++ style = ClaimStyleContext(widget); ++ moz_gtk_add_margin_border_padding(style, left, top, right, bottom); ++ ReleaseStyleContext(style); ++ return MOZ_GTK_SUCCESS; ++ } + + /* These widgets have no borders, since they are not containers. */ + case MOZ_GTK_CHECKBUTTON_LABEL: +@@ -2729,6 +2840,36 @@ GetScrollbarMetrics(GtkOrientation aOrie + return metrics; + } + ++void ++moz_gtk_window_decoration_paint(cairo_t *cr, GdkRectangle* rect) ++{ ++ gint top, right, bottom, left; ++ moz_gtk_get_window_border(&top, &right, &bottom, &left); ++ ++ cairo_save(cr); ++ cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); ++ cairo_rectangle(cr, rect->x, rect->y, left, rect->height); ++ cairo_fill(cr); ++ cairo_rectangle(cr, rect->x+rect->width-right, rect->y, right, rect->height); ++ cairo_fill(cr); ++ cairo_rectangle(cr, rect->x, rect->y, rect->width, top); ++ cairo_fill(cr); ++ cairo_rectangle(cr, rect->x, rect->height-bottom, rect->width, bottom); ++ cairo_fill(cr); ++ cairo_restore(cr); ++ ++ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_WINDOW_DECORATION, ++ GTK_TEXT_DIR_NONE); ++ rect->x += left; ++ rect->y += top; ++ rect->width -= left + right; ++ rect->height -= top + bottom; ++ ++ 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); ++} ++ ++ + /* cairo_t *cr argument has to be a system-cairo. */ + gint + moz_gtk_widget_paint(WidgetNodeType widget, cairo_t *cr, +@@ -2754,6 +2895,20 @@ moz_gtk_widget_paint(WidgetNodeType widg + GetWidget(MOZ_GTK_BUTTON), + direction); + break; ++ case MOZ_GTK_HEADER_BAR_BUTTON: ++ return moz_gtk_header_bar_button_paint(cr, rect, state, ++ (GtkReliefStyle) flags, ++ GetWidget(MOZ_GTK_HEADER_BAR_BUTTON), ++ direction); ++ break; ++ case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE: ++ case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE: ++ case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE: ++ return moz_gtk_header_bar_button_paint(cr, rect, state, ++ (GtkReliefStyle) flags, ++ GetWidget(widget), ++ direction); ++ break; + case MOZ_GTK_CHECKBUTTON: + case MOZ_GTK_RADIOBUTTON: + return moz_gtk_toggle_paint(cr, rect, state, +@@ -2961,6 +3116,10 @@ moz_gtk_widget_paint(WidgetNodeType widg + case MOZ_GTK_INFO_BAR: + return moz_gtk_info_bar_paint(cr, rect, state); + break; ++ case MOZ_GTK_HEADER_BAR: ++ case MOZ_GTK_HEADER_BAR_MAXIMIZED: ++ return moz_gtk_header_bar_paint(widget, cr, rect, state); ++ break; + default: + g_warning("Unknown widget type: %d", widget); + } +diff -up firefox-55.0.3/widget/gtk/gtkdrawing.h.csd firefox-55.0.3/widget/gtk/gtkdrawing.h +--- firefox-55.0.3/widget/gtk/gtkdrawing.h.csd 2017-07-31 18:20:53.000000000 +0200 ++++ firefox-55.0.3/widget/gtk/gtkdrawing.h 2017-09-14 11:29:45.349643494 +0200 +@@ -268,8 +268,14 @@ typedef enum { + MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL, + /* Paints the background of a window, dialog or page. */ + MOZ_GTK_WINDOW, ++ /* Used only as a container for MOZ_GTK_HEADER_BAR_MAXIMIZED. */ ++ MOZ_GTK_WINDOW_MAXIMIZED, + /* Window container for all widgets */ + MOZ_GTK_WINDOW_CONTAINER, ++ /* Window with the 'csd' style class. */ ++ MOZ_GTK_WINDOW_CSD, ++ /* Client-side window decoration node. Available on GTK 3.20+. */ ++ MOZ_GTK_WINDOW_DECORATION, + /* Paints a GtkInfoBar, for notifications. */ + MOZ_GTK_INFO_BAR, + /* Used for widget tree construction. */ +@@ -290,6 +296,17 @@ typedef enum { + MOZ_GTK_COMBOBOX_ENTRY_ARROW, + /* Used for scrolled window shell. */ + MOZ_GTK_SCROLLED_WINDOW, ++ /* Paints a GtkHeaderBar */ ++ MOZ_GTK_HEADER_BAR, ++ /* Paints a GtkHeaderBar in maximized state */ ++ MOZ_GTK_HEADER_BAR_MAXIMIZED, ++ /* Paints a GtkHeaderBar title buttons */ ++ MOZ_GTK_HEADER_BAR_BUTTON_CLOSE, ++ MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE, ++ MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE, ++ ++ /* Paints a GtkHeaderBar title button */ ++ MOZ_GTK_HEADER_BAR_BUTTON, + + MOZ_GTK_WIDGET_NODE_COUNT + } WidgetNodeType; +@@ -542,6 +559,21 @@ gint moz_gtk_get_menu_separator_height(g + */ + gint moz_gtk_splitter_get_metrics(gint orientation, gint* size); + ++#if (MOZ_WIDGET_GTK == 3) ++/** ++ * Gets the margins to be used for window decorations, typically the extra space ++ * required to draw a drop shadow (obtained from gtk_render_background_get_clip). ++ * Only available on GTK 3.20+. ++ */ ++void moz_gtk_get_window_border(gint* top, gint* right, gint* bottom, gint* left); ++ ++/** ++ * Draw window decorations, typically a shadow. ++ * Only available on GTK 3.20+. ++ */ ++void moz_gtk_window_decoration_paint(cairo_t *cr, GdkRectangle* rect); ++#endif ++ + /** + * Get the YTHICKNESS of a tab (notebook extension). + */ +diff -up firefox-55.0.3/widget/gtk/mozgtk/mozgtk.c.csd firefox-55.0.3/widget/gtk/mozgtk/mozgtk.c +--- firefox-55.0.3/widget/gtk/mozgtk/mozgtk.c.csd 2017-07-31 18:20:53.000000000 +0200 ++++ firefox-55.0.3/widget/gtk/mozgtk/mozgtk.c 2017-09-14 11:29:45.350643492 +0200 +@@ -576,6 +576,8 @@ STUB(gtk_style_context_set_state) + STUB(gtk_style_properties_lookup_property) + 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) +@@ -585,6 +587,10 @@ STUB(gtk_widget_path_iter_add_class) + STUB(gtk_widget_path_get_object_type) + STUB(gtk_widget_path_new) + STUB(gtk_widget_path_unref) ++STUB(gtk_widget_set_margin_left) ++STUB(gtk_widget_set_margin_right) ++STUB(gtk_widget_set_margin_top) ++STUB(gtk_widget_set_margin_bottom) + STUB(gtk_widget_set_visual) + STUB(gtk_app_chooser_dialog_new_for_content_type) + STUB(gtk_app_chooser_get_type) +@@ -597,6 +603,7 @@ STUB(gtk_color_chooser_get_type) + STUB(gtk_color_chooser_set_rgba) + STUB(gtk_color_chooser_get_rgba) + STUB(gtk_color_chooser_set_use_alpha) ++STUB(gtk_window_get_size) + #endif + + #ifdef GTK2_SYMBOLS +diff -up firefox-55.0.3/widget/gtk/nsLookAndFeel.cpp.csd firefox-55.0.3/widget/gtk/nsLookAndFeel.cpp +--- firefox-55.0.3/widget/gtk/nsLookAndFeel.cpp.csd 2017-07-31 18:20:53.000000000 +0200 ++++ firefox-55.0.3/widget/gtk/nsLookAndFeel.cpp 2017-09-14 11:29:45.350643492 +0200 +@@ -859,6 +859,10 @@ nsLookAndFeel::GetIntImpl(IntID aID, int + case eIntID_ContextMenuOffsetHorizontal: + aResult = 2; + break; ++ case eIntID_GTKCSDAvailable: ++ EnsureInit(); ++ aResult = sCSDAvailable; ++ break; + default: + aResult = 0; + res = NS_ERROR_FAILURE; +@@ -1468,6 +1472,16 @@ nsLookAndFeel::EnsureInit() + nullptr); + + gtk_widget_destroy(window); ++ ++ // Require GTK 3.20 for client-side decoration support. ++ // 3.20 exposes gtk_render_background_get_clip, which is required for ++ // calculating shadow metrics for decorated windows. ++ sCSDAvailable = gtk_check_version(3, 20, 0) == nullptr; ++ if (sCSDAvailable) { ++ sCSDAvailable = ++ mozilla::Preferences::GetBool("widget.allow-client-side-decoration", ++ false); ++ } + } + + // virtual +diff -up firefox-55.0.3/widget/gtk/nsLookAndFeel.h.csd firefox-55.0.3/widget/gtk/nsLookAndFeel.h +--- firefox-55.0.3/widget/gtk/nsLookAndFeel.h.csd 2017-07-31 18:20:53.000000000 +0200 ++++ firefox-55.0.3/widget/gtk/nsLookAndFeel.h 2017-09-14 11:29:45.350643492 +0200 +@@ -30,6 +30,8 @@ public: + virtual char16_t GetPasswordCharacterImpl(); + virtual bool GetEchoPasswordImpl(); + ++ bool IsCSDAvailable() const { return sCSDAvailable; } ++ + protected: + #if (MOZ_WIDGET_GTK == 2) + struct _GtkStyle *mStyle; +@@ -81,6 +83,7 @@ protected: + char16_t sInvisibleCharacter; + float sCaretRatio; + bool sMenuSupportsDrag; ++ bool sCSDAvailable; + bool mInitialized; + + void EnsureInit(); +diff -up firefox-55.0.3/widget/gtk/nsNativeThemeGTK.cpp.csd firefox-55.0.3/widget/gtk/nsNativeThemeGTK.cpp +--- firefox-55.0.3/widget/gtk/nsNativeThemeGTK.cpp.csd 2017-07-31 18:20:53.000000000 +0200 ++++ firefox-55.0.3/widget/gtk/nsNativeThemeGTK.cpp 2017-09-14 11:35:18.230778645 +0200 +@@ -24,9 +24,7 @@ + #include "nsRenderingContext.h" + #include "nsGkAtoms.h" + #include "nsAttrValueInlines.h" +- +-#include "mozilla/EventStates.h" +-#include "mozilla/Services.h" ++#include "nsWindow.h" + + #include + #include +@@ -704,6 +702,24 @@ nsNativeThemeGTK::GetGtkWidgetAndState(u + case NS_THEME_GTK_INFO_BAR: + aGtkWidgetType = MOZ_GTK_INFO_BAR; + break; ++ case NS_THEME_WINDOW_TITLEBAR: ++ aGtkWidgetType = MOZ_GTK_HEADER_BAR; ++ break; ++ case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED: ++ aGtkWidgetType = MOZ_GTK_HEADER_BAR_MAXIMIZED; ++ break; ++ case NS_THEME_WINDOW_BUTTON_CLOSE: ++ aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_CLOSE; ++ break; ++ case NS_THEME_WINDOW_BUTTON_MINIMIZE: ++ aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE; ++ break; ++ case NS_THEME_WINDOW_BUTTON_MAXIMIZE: ++ aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE; ++ break; ++ case NS_THEME_WINDOW_BUTTON_RESTORE: ++ aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE; ++ break; + default: + return false; + } +@@ -1636,6 +1652,10 @@ nsNativeThemeGTK::GetMinimumWidgetSize(n + case NS_THEME_MENULIST: + case NS_THEME_TOOLBARBUTTON: + case NS_THEME_TREEHEADERCELL: ++ case NS_THEME_WINDOW_BUTTON_CLOSE: ++ case NS_THEME_WINDOW_BUTTON_MINIMIZE: ++ case NS_THEME_WINDOW_BUTTON_MAXIMIZE: ++ case NS_THEME_WINDOW_BUTTON_RESTORE: + { + if (aWidgetType == NS_THEME_MENULIST) { + // Include the arrow size. +@@ -1901,9 +1921,21 @@ nsNativeThemeGTK::ThemeSupportsWidget(ns + case NS_THEME_DIALOG: + #if (MOZ_WIDGET_GTK == 3) + case NS_THEME_GTK_INFO_BAR: ++ case NS_THEME_GTK_WINDOW_DECORATION: + #endif + return !IsWidgetStyled(aPresContext, aFrame, aWidgetType); + ++ case NS_THEME_WINDOW_BUTTON_CLOSE: ++ case NS_THEME_WINDOW_BUTTON_MINIMIZE: ++ case NS_THEME_WINDOW_BUTTON_MAXIMIZE: ++ case NS_THEME_WINDOW_BUTTON_RESTORE: ++ case NS_THEME_WINDOW_TITLEBAR: ++ case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED: ++ // GtkHeaderBar is available on GTK 3.10+, which is used for styling ++ // title bars and title buttons. ++ return gtk_check_version(3, 10, 0) == nullptr && ++ !IsWidgetStyled(aPresContext, aFrame, aWidgetType); ++ + case NS_THEME_MENULIST_BUTTON: + if (aFrame && aFrame->GetWritingMode().IsVertical()) { + return false; +@@ -1987,6 +2019,13 @@ nsNativeThemeGTK::GetWidgetTransparency( + #else + return eTransparent; + #endif ++ case NS_THEME_GTK_WINDOW_DECORATION: ++ { ++ nsWindow* window = static_cast(aFrame->GetNearestWidget()); ++ if (window) ++ return window->IsComposited() ? eTransparent : eOpaque; ++ return eOpaque; ++ } + } + + return eUnknownTransparency; +diff -up firefox-55.0.3/widget/gtk/nsWindow.cpp.csd firefox-55.0.3/widget/gtk/nsWindow.cpp +--- firefox-55.0.3/widget/gtk/nsWindow.cpp.csd 2017-09-14 11:29:45.342643512 +0200 ++++ firefox-55.0.3/widget/gtk/nsWindow.cpp 2017-09-14 11:34:23.671920392 +0200 +@@ -80,6 +80,7 @@ + #include "nsIPropertyBag2.h" + #include "GLContext.h" + #include "gfx2DGlue.h" ++#include "nsLookAndFeel.h" + + #ifdef ACCESSIBILITY + #include "mozilla/a11y/Accessible.h" +@@ -133,6 +134,8 @@ using namespace mozilla::widget; + + #include "mozilla/layers/APZCTreeManager.h" + ++#include "gtkdrawing.h" ++ + using namespace mozilla; + using namespace mozilla::gfx; + using namespace mozilla::widget; +@@ -180,6 +183,8 @@ static gboolean expose_event_cb + #else + static gboolean expose_event_cb (GtkWidget *widget, + cairo_t *rect); ++static gboolean expose_event_decoration_draw_cb (GtkWidget *widget, ++ cairo_t *cr); + #endif + static gboolean configure_event_cb (GtkWidget *widget, + GdkEventConfigure *event); +@@ -432,6 +437,7 @@ nsWindow::nsWindow() + + mContainer = nullptr; + mGdkWindow = nullptr; ++ mIsCSDEnabled = false; + mShell = nullptr; + mHasMappedToplevel = false; + mIsFullyObscured = false; +@@ -472,6 +478,9 @@ nsWindow::nsWindow() + mLastScrollEventTime = GDK_CURRENT_TIME; + #endif + mPendingConfigures = 0; ++ mDrawWindowDecoration = false; ++ mDecorationSize = {0,0,0,0}; ++ mDecorationSizeChanged = false; + } + + nsWindow::~nsWindow() +@@ -1105,6 +1114,12 @@ nsWindow::Resize(double aWidth, double a + // interpreted as frame bounds, but NativeResize treats these as window + // bounds (Bug 581866). + ++ // When we draw decorations add extra space to draw shadows around the main window. ++ if (mDrawWindowDecoration) { ++ width += mDecorationSize.left + mDecorationSize.right; ++ height += mDecorationSize.top + mDecorationSize.bottom; ++ } ++ + mBounds.SizeTo(width, height); + + if (!mCreated) +@@ -1584,6 +1599,10 @@ nsWindow::SetCursor(nsCursor aCursor) + return; + + gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(mContainer)), newCursor); ++ if (IsClientDecorated()) { ++ gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(mShell)), ++ newCursor); ++ } + } + } + } +@@ -1640,6 +1659,10 @@ nsWindow::SetCursor(imgIContainer* aCurs + if (cursor) { + if (mContainer) { + gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(mContainer)), cursor); ++ if (IsClientDecorated()) { ++ gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(mShell)), ++ cursor); ++ } + rv = NS_OK; + } + #if (MOZ_WIDGET_GTK == 3) +@@ -2563,6 +2586,53 @@ nsWindow::OnMotionNotifyEvent(GdkEventMo + } + } + #endif /* MOZ_X11 */ ++ // Client is decorated and we're getting the motion event for mShell ++ // window which draws the CSD decorations around mContainer. ++ if (IsClientDecorated()) { ++ if (aEvent->window == gtk_widget_get_window(mShell)) { ++ GdkWindowEdge edge; ++ LayoutDeviceIntPoint refPoint = ++ GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y); ++ if (CheckResizerEdge(refPoint, edge)) { ++ nsCursor cursor = eCursor_none; ++ switch (edge) { ++ case GDK_WINDOW_EDGE_NORTH: ++ cursor = eCursor_n_resize; ++ break; ++ case GDK_WINDOW_EDGE_NORTH_WEST: ++ cursor = eCursor_nw_resize; ++ break; ++ case GDK_WINDOW_EDGE_NORTH_EAST: ++ cursor = eCursor_ne_resize; ++ break; ++ case GDK_WINDOW_EDGE_WEST: ++ cursor = eCursor_w_resize; ++ break; ++ case GDK_WINDOW_EDGE_EAST: ++ cursor = eCursor_e_resize; ++ break; ++ case GDK_WINDOW_EDGE_SOUTH: ++ cursor = eCursor_s_resize; ++ break; ++ case GDK_WINDOW_EDGE_SOUTH_WEST: ++ cursor = eCursor_sw_resize; ++ break; ++ case GDK_WINDOW_EDGE_SOUTH_EAST: ++ cursor = eCursor_se_resize; ++ break; ++ } ++ SetCursor(cursor); ++ return; ++ } ++ } ++ // We're not on resize handle - check if we need to reset cursor back. ++ if (mCursor == eCursor_n_resize || mCursor == eCursor_nw_resize || ++ mCursor == eCursor_ne_resize || mCursor == eCursor_w_resize || ++ mCursor == eCursor_e_resize || mCursor == eCursor_s_resize || ++ mCursor == eCursor_sw_resize || mCursor == eCursor_se_resize) { ++ SetCursor(eCursor_standard); ++ } ++ } + + WidgetMouseEvent event(true, eMouseMove, this, WidgetMouseEvent::eReal); + +@@ -2733,6 +2803,20 @@ nsWindow::OnButtonPressEvent(GdkEventBut + if (CheckForRollup(aEvent->x_root, aEvent->y_root, false, false)) + return; + ++ if (IsClientDecorated() && aEvent->window == gtk_widget_get_window(mShell)) { ++ // Check to see if the event is within our window's resize region ++ GdkWindowEdge edge; ++ LayoutDeviceIntPoint refPoint = ++ GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y); ++ if (CheckResizerEdge(refPoint, edge)) { ++ gdk_window_begin_resize_drag(gtk_widget_get_window(mShell), ++ edge, aEvent->button, ++ aEvent->x_root, aEvent->y_root, ++ aEvent->time); ++ return; ++ } ++ } ++ + gdouble pressure = 0; + gdk_event_get_axis ((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure); + mLastMotionPressure = pressure; +@@ -3318,6 +3402,8 @@ nsWindow::OnWindowStateEvent(GtkWidget * + #endif //ACCESSIBILITY + } + ++ UpdateClientDecorations(); ++ + if (mWidgetListener) { + mWidgetListener->SizeModeChanged(mSizeState); + if (aEvent->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) { +@@ -3364,6 +3450,7 @@ nsWindow::OnDPIChanged() + presShell->ThemeChanged(); + } + } ++ UpdateClientDecorations(); + } + + void +@@ -3573,7 +3660,8 @@ nsWindow::Create(nsIWidget* aParent, + GtkWindow *topLevelParent = nullptr; + nsWindow *parentnsWindow = nullptr; + GtkWidget *eventWidget = nullptr; +- bool shellHasCSD = false; ++ GtkWidget *drawWidget = nullptr; ++ bool drawToContainer = false; + + if (aParent) { + parentnsWindow = static_cast(aParent); +@@ -3620,29 +3708,46 @@ nsWindow::Create(nsIWidget* aParent, + GTK_WINDOW_TOPLEVEL : GTK_WINDOW_POPUP; + mShell = gtk_window_new(type); + +- bool useAlphaVisual = (mWindowType == eWindowType_popup && +- aInitData->mSupportTranslucency); ++ bool useAlphaVisual = false; ++#if (MOZ_WIDGET_GTK == 3) ++ // When CSD is available we can emulate it for toplevel windows. ++ // Content is rendered to mContainer and transparent decorations to mShell. ++ if (mWindowType == eWindowType_toplevel) { ++ int32_t isCSDAvailable = false; ++ nsresult rv = LookAndFeel::GetInt(LookAndFeel::eIntID_GTKCSDAvailable, ++ &isCSDAvailable); ++ if (NS_SUCCEEDED(rv)) { ++ mIsCSDEnabled = useAlphaVisual = isCSDAvailable; ++ } ++ } else ++#endif ++ if (mWindowType == eWindowType_popup) { ++ useAlphaVisual = aInitData->mSupportTranslucency; ++ } + + // mozilla.widget.use-argb-visuals is a hidden pref defaulting to false + // to allow experimentation + if (Preferences::GetBool("mozilla.widget.use-argb-visuals", false)) + useAlphaVisual = true; + ++ // An ARGB visual is only useful if we are on a compositing ++ // window manager. ++ GdkScreen *screen = gtk_widget_get_screen(mShell); ++ if (useAlphaVisual && !gdk_screen_is_composited(screen)) { ++ useAlphaVisual = false; ++ } ++ + // We need to select an ARGB visual here instead of in + // SetTransparencyMode() because it has to be done before the +- // widget is realized. An ARGB visual is only useful if we +- // are on a compositing window manager. ++ // widget is realized. + if (useAlphaVisual) { +- GdkScreen *screen = gtk_widget_get_screen(mShell); +- if (gdk_screen_is_composited(screen)) { + #if (MOZ_WIDGET_GTK == 2) +- GdkColormap *colormap = gdk_screen_get_rgba_colormap(screen); +- gtk_widget_set_colormap(mShell, colormap); ++ GdkColormap *colormap = gdk_screen_get_rgba_colormap(screen); ++ gtk_widget_set_colormap(mShell, colormap); + #else +- GdkVisual *visual = gdk_screen_get_rgba_visual(screen); +- gtk_widget_set_visual(mShell, visual); ++ GdkVisual *visual = gdk_screen_get_rgba_visual(screen); ++ gtk_widget_set_visual(mShell, visual); + #endif +- } + } + + // We only move a general managed toplevel window if someone has +@@ -3736,24 +3841,56 @@ nsWindow::Create(nsIWidget* aParent, + mContainer = MOZ_CONTAINER(container); + + #if (MOZ_WIDGET_GTK == 3) +- // "csd" style is set when widget is realized so we need to call +- // it explicitly now. +- gtk_widget_realize(mShell); +- +- // We can't draw directly to top-level window when client side +- // decorations are enabled. We use container with GdkWindow instead. +- GtkStyleContext* style = gtk_widget_get_style_context(mShell); +- shellHasCSD = gtk_style_context_has_class(style, "csd"); +-#endif +- if (!shellHasCSD) { +- // Use mShell's window for drawing and events. +- gtk_widget_set_has_window(container, FALSE); +- // Prevent GtkWindow from painting a background to flicker. +- gtk_widget_set_app_paintable(mShell, TRUE); +- } +- // Set up event widget +- eventWidget = shellHasCSD ? container : mShell; ++ /* There are tree possible situations here: ++ * ++ * 1) We're running on Gtk+ < 3.20 without any decorations. Content ++ * is rendered to mShell window and we listen Gtk+ events on mShell. ++ * 2) We're running on Gtk+ > 3.20 and window decorations are drawn ++ * by default by Gtk+. Content is rendered to mContainer, ++ * we listen events on mContainer. mShell contains default Gtk+ ++ * window decorations rendered by Gtk+. ++ * 3) We're running on Gtk+ > 3.20 and both window decorations and ++ * content is rendered by gecko. We emulate Gtk+ decoration rendering ++ * to mShell and we need to listen Gtk events on both mShell ++ * and mContainer. ++ */ ++ ++ // When client side decorations are enabled (rendered by us or by Gtk+) ++ // the decoration is rendered to mShell (toplevel) window and ++ // we draw our content to mContainer. ++ if (mIsCSDEnabled) { ++ drawToContainer = true; ++ } else { ++ // mIsCSDEnabled can be disabled by preference so look at actual ++ // toplevel window style to to detect active "csd" style. ++ // The "csd" style is set when widget is realized so we need to call ++ // it explicitly now. ++ gtk_widget_realize(mShell); ++ ++ GtkStyleContext* style = gtk_widget_get_style_context(mShell); ++ drawToContainer = gtk_style_context_has_class(style, "csd"); ++ } ++#endif ++ drawWidget = (drawToContainer) ? container : mShell; ++ // When we draw decorations on our own we need to handle resize events ++ // because Gtk+ does not provide resizers for undecorated windows. ++ // The CSD on mShell borders act as resize handlers ++ // so we need to listen there. ++ eventWidget = (drawToContainer && !mIsCSDEnabled) ? container : mShell; ++ + gtk_widget_add_events(eventWidget, kEvents); ++ if (eventWidget != drawWidget) { ++ // CSD is rendered by us (not by Gtk+) so we also need to listen ++ // at mShell window for events. ++ gtk_widget_add_events(drawWidget, kEvents); ++ } ++ ++ // Prevent GtkWindow from painting a background to flicker. ++ gtk_widget_set_app_paintable(drawWidget, TRUE); ++ ++ // gtk_container_add() realizes the child widget so we need to ++ // set it now. ++ gtk_widget_set_has_window(container, drawToContainer); + + gtk_container_add(GTK_CONTAINER(mShell), container); + gtk_widget_realize(container); +@@ -3763,7 +3900,7 @@ nsWindow::Create(nsIWidget* aParent, + gtk_widget_grab_focus(container); + + // the drawing window +- mGdkWindow = gtk_widget_get_window(eventWidget); ++ mGdkWindow = gtk_widget_get_window(drawWidget); + + if (mWindowType == eWindowType_popup) { + // gdk does not automatically set the cursor for "temporary" +@@ -3836,6 +3973,11 @@ nsWindow::Create(nsIWidget* aParent, + + // label the drawing window with this object so we can find our way home + g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", this); ++ if (mIsCSDEnabled) { ++ // label the CSD window with this object so we can find our way home ++ g_object_set_data(G_OBJECT(gtk_widget_get_window(mShell)), ++ "nsWindow", this); ++ } + + if (mContainer) + g_object_set_data(G_OBJECT(mContainer), "nsWindow", this); +@@ -3863,6 +4005,10 @@ nsWindow::Create(nsIWidget* aParent, + g_signal_connect_after(default_settings, + "notify::gtk-font-name", + G_CALLBACK(theme_changed_cb), this); ++ if (mIsCSDEnabled) { ++ g_signal_connect(G_OBJECT(mShell), "draw", ++ G_CALLBACK(expose_event_decoration_draw_cb), nullptr); ++ } + } + + if (mContainer) { +@@ -3913,7 +4059,7 @@ nsWindow::Create(nsIWidget* aParent, + G_CALLBACK(drag_data_received_event_cb), nullptr); + + GtkWidget *widgets[] = { GTK_WIDGET(mContainer), +- !shellHasCSD ? mShell : nullptr }; ++ !drawToContainer ? mShell : nullptr }; + for (size_t i = 0; i < ArrayLength(widgets) && widgets[i]; ++i) { + // Visibility events are sent to the owning widget of the relevant + // window but do not propagate to parent widgets so connect on +@@ -3943,7 +4089,6 @@ nsWindow::Create(nsIWidget* aParent, + // Don't let GTK mess with the shapes of our GdkWindows + GTK_PRIVATE_SET_FLAG(eventWidget, GTK_HAS_SHAPE_MASK); + #endif +- + // These events are sent to the owning widget of the relevant window + // and propagate up to the first widget that handles the events, so we + // need only connect on mShell, if it exists, to catch events on its +@@ -5492,6 +5637,31 @@ expose_event_cb(GtkWidget *widget, cairo + + return FALSE; + } ++ ++/* static */ ++gboolean ++expose_event_decoration_draw_cb(GtkWidget *widget, cairo_t *cr) ++{ ++ GdkWindow* gdkWindow = gtk_widget_get_window(widget); ++ if (gtk_cairo_should_draw_window(cr, gdkWindow)) { ++ RefPtr window = get_window_for_gtk_widget(widget); ++ if (!window) { ++ NS_WARNING("Cannot get nsWindow from GtkWidget"); ++ } ++ else if (window->IsClientDecorated()) { ++ cairo_save(cr); ++ gtk_cairo_transform_to_window(cr, widget, gdkWindow); ++ ++ GdkRectangle rect = {0,0,0,0}; ++ gtk_window_get_size(GTK_WINDOW(widget), &rect.width, &rect.height); ++ moz_gtk_window_decoration_paint(cr, &rect); ++ ++ cairo_restore(cr); ++ } ++ } ++ return TRUE; ++} ++ + #endif //MOZ_WIDGET_GTK == 2 + + static gboolean +@@ -6526,6 +6696,28 @@ nsWindow::ClearCachedResources() + } + } + ++NS_IMETHODIMP ++nsWindow::SetNonClientMargins(LayoutDeviceIntMargin &aMargins) ++{ ++ SetDrawsInTitlebar(aMargins.top == 0); ++ return NS_OK; ++} ++ ++void ++nsWindow::SetDrawsInTitlebar(bool aState) ++{ ++ if (!mIsCSDEnabled || aState == mDrawWindowDecoration) ++ return; ++ ++ if (mShell) { ++ gtk_window_set_decorated(GTK_WINDOW(mShell), !aState); ++ gtk_widget_set_app_paintable(mShell, aState); ++ } ++ ++ mDrawWindowDecoration = aState; ++ UpdateClientDecorations(); ++} ++ + gint + nsWindow::GdkScaleFactor() + { +@@ -6796,6 +6988,107 @@ nsWindow::SynthesizeNativeTouchPoint(uin + } + #endif + ++bool ++nsWindow::IsClientDecorated() const ++{ ++ return mDrawWindowDecoration && mSizeState == nsSizeMode_Normal; ++} ++ ++int ++nsWindow::GetClientResizerSize() ++{ ++ if (!mShell) ++ return 0; ++ ++ // GTK uses a default size of 20px as of 3.20. ++ gint size = 20; ++ gtk_widget_style_get(mShell, "decoration-resize-handle", &size, nullptr); ++ ++ return GdkCoordToDevicePixels(size); ++} ++ ++void ++nsWindow::UpdateClientDecorations() ++{ ++ if (!mDrawWindowDecoration) ++ return; ++ ++ gint top = 0, right = 0, bottom = 0, left = 0; ++ if (mSizeState == nsSizeMode_Normal) { ++ moz_gtk_get_window_border(&top, &right, &bottom, &left); ++ } ++ ++ static auto sGdkWindowSetShadowWidth = ++ (void (*)(GdkWindow*, gint, gint, gint, gint)) ++ dlsym(RTLD_DEFAULT, "gdk_window_set_shadow_width"); ++ sGdkWindowSetShadowWidth(gtk_widget_get_window(mShell), ++ left, right, top, bottom); ++ ++ mDecorationSize.left = left; ++ mDecorationSize.right = right; ++ mDecorationSize.top = top; ++ mDecorationSize.bottom = bottom; ++ ++ // Gtk+ doesn't like when we set mContainer margin bigger than actual ++ // mContainer window size. That happens when we're called early and the ++ // mShell/mContainer is not allocated/resized yet and has default 1x1 size. ++ // Just resize to some minimal value which will be changed ++ // by Gecko soon. ++ GtkAllocation allocation; ++ gtk_widget_get_allocation(GTK_WIDGET(mContainer), &allocation); ++ if (allocation.width < left + right || allocation.height < top + bottom) { ++ gtk_widget_get_preferred_width(GTK_WIDGET(mContainer), nullptr, ++ &allocation.width); ++ gtk_widget_get_preferred_height(GTK_WIDGET(mContainer), nullptr, ++ &allocation.height); ++ allocation.width += left + right + 1; ++ allocation.height += top + bottom + 1; ++ gtk_widget_size_allocate(GTK_WIDGET(mContainer), &allocation); ++ } ++ ++ gtk_widget_set_margin_left(GTK_WIDGET(mContainer), mDecorationSize.left); ++ gtk_widget_set_margin_right(GTK_WIDGET(mContainer), mDecorationSize.right); ++ gtk_widget_set_margin_top(GTK_WIDGET(mContainer), mDecorationSize.top); ++ gtk_widget_set_margin_bottom(GTK_WIDGET(mContainer), mDecorationSize.bottom); ++} ++ ++bool ++nsWindow::CheckResizerEdge(LayoutDeviceIntPoint aPoint, GdkWindowEdge& aOutEdge) ++{ ++ gint scale = GdkScaleFactor(); ++ gint left = scale * mDecorationSize.left; ++ gint top = scale * mDecorationSize.top; ++ gint right = scale * mDecorationSize.right; ++ gint bottom = scale * mDecorationSize.bottom; ++ ++ int resizerSize = GetClientResizerSize(); ++ int topDist = aPoint.y; ++ int leftDist = aPoint.x; ++ int rightDist = mBounds.width - aPoint.x; ++ int bottomDist = mBounds.height - aPoint.y; ++ ++ if (leftDist <= resizerSize && topDist <= resizerSize) { ++ aOutEdge = GDK_WINDOW_EDGE_NORTH_WEST; ++ } else if (rightDist <= resizerSize && topDist <= resizerSize) { ++ aOutEdge = GDK_WINDOW_EDGE_NORTH_EAST; ++ } else if (leftDist <= resizerSize && bottomDist <= resizerSize) { ++ aOutEdge = GDK_WINDOW_EDGE_SOUTH_WEST; ++ } else if (rightDist <= resizerSize && bottomDist <= resizerSize) { ++ aOutEdge = GDK_WINDOW_EDGE_SOUTH_EAST; ++ } else if (topDist <= top) { ++ aOutEdge = GDK_WINDOW_EDGE_NORTH; ++ } else if (leftDist <= left) { ++ aOutEdge = GDK_WINDOW_EDGE_WEST; ++ } else if (rightDist <= right) { ++ aOutEdge = GDK_WINDOW_EDGE_EAST; ++ } else if (bottomDist <= bottom) { ++ aOutEdge = GDK_WINDOW_EDGE_SOUTH; ++ } else { ++ return false; ++ } ++ return true; ++} ++ + int32_t + nsWindow::RoundsWidgetCoordinatesTo() + { +diff -up firefox-55.0.3/widget/gtk/nsWindow.h.csd firefox-55.0.3/widget/gtk/nsWindow.h +--- firefox-55.0.3/widget/gtk/nsWindow.h.csd 2017-07-31 18:20:53.000000000 +0200 ++++ firefox-55.0.3/widget/gtk/nsWindow.h 2017-09-14 11:33:50.009007850 +0200 +@@ -125,6 +125,7 @@ public: + double aHeight, + bool aRepaint) override; + virtual bool IsEnabled() const override; ++ bool IsComposited() const; + + void SetZIndex(int32_t aZIndex) override; + virtual void SetSizeMode(nsSizeMode aMode) override; +@@ -353,6 +354,9 @@ public: + #endif + virtual void GetCompositorWidgetInitData(mozilla::widget::CompositorWidgetInitData* aInitData) override; + ++ NS_IMETHOD SetNonClientMargins(LayoutDeviceIntMargin& aMargins) override; ++ void SetDrawsInTitlebar(bool aState) override; ++ + // HiDPI scale conversion + gint GdkScaleFactor(); + +@@ -369,6 +373,9 @@ public: + LayoutDeviceIntRect GdkRectToDevicePixels(GdkRectangle rect); + + virtual bool WidgetTypeSupportsAcceleration() override; ++ ++ // Decorations ++ bool IsClientDecorated() const; + protected: + virtual ~nsWindow(); + +@@ -386,6 +393,16 @@ protected: + + virtual void RegisterTouchWindow() override; + ++ int GetClientResizerSize(); ++ ++ // Informs the window manager about the size of the shadows surrounding ++ // a client-side decorated window. ++ void UpdateClientDecorations(); ++ ++ // Returns true if the given point (in device pixels) is within a resizer ++ // region of the window. Only used when drawing decorations client side. ++ bool CheckResizerEdge(LayoutDeviceIntPoint aPoint, GdkWindowEdge& aOutEdge); ++ + nsCOMPtr mParent; + // Is this a toplevel window? + bool mIsTopLevel; +@@ -434,12 +451,12 @@ private: + gint* aRootX, gint* aRootY); + void ClearCachedResources(); + nsIWidgetListener* GetListener(); +- bool IsComposited() const; + + + GtkWidget *mShell; + MozContainer *mContainer; + GdkWindow *mGdkWindow; ++ bool mIsCSDEnabled; + + uint32_t mHasMappedToplevel : 1, + mIsFullyObscured : 1, +@@ -536,6 +553,11 @@ private: + // leaving fullscreen + nsSizeMode mLastSizeMode; + ++ // If true, draw our own window decorations (where supported). ++ bool mDrawWindowDecoration; ++ GtkBorder mDecorationSize; ++ bool mDecorationSizeChanged; ++ + static bool DragInProgress(void); + + void DispatchMissedButtonReleases(GdkEventCrossing *aGdkEvent); +diff -up firefox-55.0.3/widget/gtk/WidgetStyleCache.cpp.csd firefox-55.0.3/widget/gtk/WidgetStyleCache.cpp +--- firefox-55.0.3/widget/gtk/WidgetStyleCache.cpp.csd 2017-07-31 18:20:53.000000000 +0200 ++++ firefox-55.0.3/widget/gtk/WidgetStyleCache.cpp 2017-09-14 11:29:45.349643494 +0200 +@@ -26,10 +26,14 @@ static GtkStyleContext* + GetCssNodeStyleInternal(WidgetNodeType aNodeType); + + static GtkWidget* +-CreateWindowWidget() ++CreateWindowWidget(WidgetNodeType type) + { + GtkWidget *widget = gtk_window_new(GTK_WINDOW_POPUP); + gtk_widget_set_name(widget, "MozillaGtkWidget"); ++ if (type == MOZ_GTK_WINDOW_CSD) { ++ GtkStyleContext* style = gtk_widget_get_style_context(widget); ++ gtk_style_context_add_class(style, "csd"); ++ } + return widget; + } + +@@ -101,7 +105,7 @@ CreateTooltipWidget() + { + MOZ_ASSERT(gtk_check_version(3, 20, 0) != nullptr, + "CreateTooltipWidget should be used for Gtk < 3.20 only."); +- GtkWidget* widget = CreateWindowWidget(); ++ GtkWidget* widget = CreateWindowWidget(MOZ_GTK_WINDOW); + GtkStyleContext* style = gtk_widget_get_style_context(widget); + gtk_style_context_add_class(style, GTK_STYLE_CLASS_TOOLTIP); + return widget; +@@ -529,11 +533,73 @@ CreateNotebookWidget() + } + + static GtkWidget* ++CreateHeaderBar(bool aMaximized) ++{ ++ MOZ_ASSERT(gtk_check_version(3, 10, 0) == nullptr, ++ "GtkHeaderBar is only available on GTK 3.10+."); ++ ++ static auto sGtkHeaderBarNewPtr = (GtkWidget* (*)()) ++ dlsym(RTLD_DEFAULT, "gtk_header_bar_new"); ++ static const char* MOZ_GTK_STYLE_CLASS_TITLEBAR = "titlebar"; ++ ++ GtkWidget* headerbar = sGtkHeaderBarNewPtr(); ++ if (aMaximized) { ++ GtkWidget *window = gtk_window_new(GTK_WINDOW_POPUP); ++ gtk_widget_set_name(window, "MozillaMaximizedGtkWidget"); ++ GtkStyleContext* style = gtk_widget_get_style_context(window); ++ gtk_style_context_add_class(style, "maximized"); ++ GtkWidget *fixed = gtk_fixed_new(); ++ gtk_container_add(GTK_CONTAINER(window), fixed); ++ gtk_container_add(GTK_CONTAINER(fixed), headerbar); ++ // Save the window container so we don't leak it. ++ sWidgetStorage[MOZ_GTK_WINDOW_MAXIMIZED] = window; ++ } else { ++ AddToWindowContainer(headerbar); ++ } ++ ++ // Emulate what create_titlebar() at gtkwindow.c does. ++ GtkStyleContext* style = gtk_widget_get_style_context(headerbar); ++ gtk_style_context_add_class(style, MOZ_GTK_STYLE_CLASS_TITLEBAR); ++ gtk_style_context_add_class(style, "default-decoration"); ++ ++ return headerbar; ++} ++ ++static GtkWidget* ++CreateHeaderBarButton(WidgetNodeType aWidgetType) ++{ ++ static const char* MOZ_GTK_STYLE_CLASS_TITLEBUTTON = "titlebutton"; ++ ++ GtkWidget* widget = gtk_button_new(); ++ gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_HEADER_BAR)), widget); ++ ++ GtkStyleContext* style = gtk_widget_get_style_context(widget); ++ gtk_style_context_add_class(style, MOZ_GTK_STYLE_CLASS_TITLEBUTTON); ++ ++ switch (aWidgetType) { ++ case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE: ++ gtk_style_context_add_class(style, "close"); ++ break; ++ case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE: ++ gtk_style_context_add_class(style, "minimize"); ++ break; ++ case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE: ++ gtk_style_context_add_class(style, "maximize"); ++ break; ++ default: ++ break; ++ } ++ ++ return widget; ++} ++ ++static GtkWidget* + CreateWidget(WidgetNodeType aWidgetType) + { + switch (aWidgetType) { + case MOZ_GTK_WINDOW: +- return CreateWindowWidget(); ++ case MOZ_GTK_WINDOW_CSD: ++ return CreateWindowWidget(aWidgetType); + case MOZ_GTK_WINDOW_CONTAINER: + return CreateWindowContainerWidget(); + case MOZ_GTK_CHECKBUTTON_CONTAINER: +@@ -610,6 +676,13 @@ CreateWidget(WidgetNodeType aWidgetType) + return CreateComboBoxEntryButtonWidget(); + case MOZ_GTK_COMBOBOX_ENTRY_ARROW: + return CreateComboBoxEntryArrowWidget(); ++ case MOZ_GTK_HEADER_BAR: ++ case MOZ_GTK_HEADER_BAR_MAXIMIZED: ++ return CreateHeaderBar(aWidgetType == MOZ_GTK_HEADER_BAR_MAXIMIZED); ++ case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE: ++ case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE: ++ case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE: ++ return CreateHeaderBarButton(aWidgetType); + default: + /* Not implemented */ + return nullptr; +@@ -1031,6 +1104,10 @@ GetCssNodeStyleInternal(WidgetNodeType a + GtkWidget* widget = GetWidget(MOZ_GTK_NOTEBOOK); + return gtk_widget_get_style_context(widget); + } ++ case MOZ_GTK_WINDOW_DECORATION: ++ style = CreateChildCSSNode("decoration", ++ MOZ_GTK_WINDOW_CSD); ++ break; + default: + return GetWidgetRootStyle(aNodeType); + } +@@ -1196,6 +1273,8 @@ ResetWidgetCache(void) + /* This will destroy all of our widgets */ + if (sWidgetStorage[MOZ_GTK_WINDOW]) + gtk_widget_destroy(sWidgetStorage[MOZ_GTK_WINDOW]); ++ if (sWidgetStorage[MOZ_GTK_WINDOW_MAXIMIZED]) ++ gtk_widget_destroy(sWidgetStorage[MOZ_GTK_WINDOW_MAXIMIZED]); + + /* Clear already freed arrays */ + mozilla::PodArrayZero(sWidgetStorage); +diff -up firefox-55.0.3/widget/LookAndFeel.h.csd firefox-55.0.3/widget/LookAndFeel.h +--- firefox-55.0.3/widget/LookAndFeel.h.csd 2017-07-31 18:20:53.000000000 +0200 ++++ firefox-55.0.3/widget/LookAndFeel.h 2017-09-14 11:29:45.348643497 +0200 +@@ -380,6 +380,12 @@ public: + eIntID_PhysicalHomeButton, + + /* ++ * A boolean value indicating whether client-side decorations are ++ * supported by the user's GTK version. ++ */ ++ eIntID_GTKCSDAvailable, ++ ++ /* + * Controls whether overlay scrollbars display when the user moves + * the mouse in a scrollable frame. + */ -- cgit