Bug 1283299 - Implement client side decorations on GTK 3.20+ draft
authorMartin Stransky <stransky@redhat.com>
Wed, 13 Sep 2017 21:12:35 +0200
changeset 664217 3cdff239ebfb360d3d5637caba7db15bfe464ea4
parent 664064 e5f80a639bfe68b68693a5be610f9d36b6c5ad00
child 731385 7f6e0dda059dcf55b78734c5ec959431682cef17
push id79635
push userstransky@redhat.com
push dateWed, 13 Sep 2017 19:13:39 +0000
bugs1283299
milestone57.0a1
Bug 1283299 - Implement client side decorations on GTK 3.20+ Based on patches by Andrew Comminos [:acomminos] andrew@comminos.com This is a WIP and I'm looking for feedback here. It uses a bit different approach and emulates CSD on toolkit level. MozReview-Commit-ID: 6SgiSoOlskG
browser/app/profile/firefox.js
browser/base/content/browser-tabsintitlebar.js
browser/base/moz.build
browser/themes/linux/browser.css
dom/base/nsGkAtomList.h
gfx/src/nsThemeConstants.h
layout/style/nsCSSRuleProcessor.cpp
layout/style/nsMediaFeatures.cpp
toolkit/modules/moz.build
widget/LookAndFeel.h
widget/gtk/WidgetStyleCache.cpp
widget/gtk/gtk3drawing.cpp
widget/gtk/gtkdrawing.h
widget/gtk/mozgtk/mozgtk.c
widget/gtk/nsLookAndFeel.cpp
widget/gtk/nsLookAndFeel.h
widget/gtk/nsNativeThemeGTK.cpp
widget/gtk/nsWindow.cpp
widget/gtk/nsWindow.h
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -453,21 +453,17 @@ pref("browser.tabs.warnOnCloseOtherTabs"
 pref("browser.tabs.warnOnOpen", true);
 pref("browser.tabs.maxOpenBeforeWarn", 15);
 pref("browser.tabs.loadInBackground", true);
 pref("browser.tabs.opentabfor.middleclick", true);
 pref("browser.tabs.loadDivertedInBackground", false);
 pref("browser.tabs.loadBookmarksInBackground", false);
 pref("browser.tabs.loadBookmarksInTabs", false);
 pref("browser.tabs.tabClipWidth", 140);
-#ifdef UNIX_BUT_NOT_MAC
-pref("browser.tabs.drawInTitlebar", false);
-#else
 pref("browser.tabs.drawInTitlebar", true);
-#endif
 
 // 0 - Disable the tabbar session restore button.
 // 1 - Enable the tabbar session restore button.
 // 2 - Control group. The tabbar session restore button is disabled,
 // but we will record data on other session restore usage.
 // To be enabled with shield.
 pref("browser.tabs.restorebutton", 0);
 
--- a/browser/base/content/browser-tabsintitlebar.js
+++ b/browser/base/content/browser-tabsintitlebar.js
@@ -9,16 +9,21 @@
 var TabsInTitlebar = {
   init() {
     if (this._initialized) {
       return;
     }
     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
     // the inactive attribute after an event-loop spin.
     //
     // Because updating the appearance involves sampling the heights and margins
     // of various elements, it's important that the layout be more or less
     // settled before updating the titlebar. So instead of listening to
--- a/browser/base/moz.build
+++ b/browser/base/moz.build
@@ -51,15 +51,15 @@ BROWSER_CHROME_MANIFESTS += [
 DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
 DEFINES['MOZ_APP_VERSION_DISPLAY'] = CONFIG['MOZ_APP_VERSION_DISPLAY']
 
 DEFINES['APP_LICENSE_BLOCK'] = '%s/content/overrides/app-license.html' % SRCDIR
 
 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'):
     DEFINES['MENUBAR_CAN_AUTOHIDE'] = 1
 
 JAR_MANIFESTS += ['jar.mn']
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -615,18 +615,22 @@ html|span.ac-emphasize-text-url {
   margin-bottom: calc(-1 * var(--tab-toolbar-navbar-overlap));
 }
 
 #TabsToolbar:not(:-moz-lwtheme) {
   -moz-appearance: menubar;
   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");
 }
 
 .tabbrowser-tab:focus > .tab-stack > .tab-content {
   outline: 1px dotted;
   outline-offset: -6px;
 }
 
@@ -721,16 +725,17 @@ html|span.ac-emphasize-text-url {
 
 /* Customization mode */
 
 %include ../shared/customizableui/customizeMode.inc.css
 
 /* 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;
 }
 
 %include ../shared/UITour.inc.css
 
 #UITourHighlight {
@@ -781,8 +786,45 @@ html|span.ac-emphasize-text-url {
 .webextension-popup-stack {
   border-radius: inherit;
 }
 
 /* Prevent movement in the restore-tabs-button when it's clicked. */
 .restore-tabs-button:hover:active:not([disabled="true"]) {
   padding: 3px;
 }
+
+/* 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;
+  }
+}
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -2264,16 +2264,17 @@ GK_ATOM(windows_default_theme, "windows-
 GK_ATOM(mac_graphite_theme, "mac-graphite-theme")
 GK_ATOM(mac_yosemite_theme, "mac-yosemite-theme")
 GK_ATOM(windows_compositor, "windows-compositor")
 GK_ATOM(windows_glass, "windows-glass")
 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")
 GK_ATOM(windows_theme_aero, "windows-theme-aero")
 GK_ATOM(windows_theme_aero_lite, "windows-theme-aero-lite")
 GK_ATOM(windows_theme_luna_blue, "windows-theme-luna-blue")
 GK_ATOM(windows_theme_luna_olive, "windows-theme-luna-olive")
 GK_ATOM(windows_theme_luna_silver, "windows-theme-luna-silver")
@@ -2300,16 +2301,17 @@ GK_ATOM(_moz_windows_theme, "-moz-window
 GK_ATOM(_moz_os_version, "-moz-os-version")
 GK_ATOM(_moz_touch_enabled, "-moz-touch-enabled")
 GK_ATOM(_moz_menubar_drag, "-moz-menubar-drag")
 GK_ATOM(_moz_device_pixel_ratio, "-moz-device-pixel-ratio")
 GK_ATOM(_moz_device_orientation, "-moz-device-orientation")
 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")
 GK_ATOM(Forward, "Forward")
 GK_ATOM(Reload, "Reload")
 GK_ATOM(Stop, "Stop")
 GK_ATOM(Search, "Search")
 GK_ATOM(Bookmarks, "Bookmarks")
--- a/gfx/src/nsThemeConstants.h
+++ b/gfx/src/nsThemeConstants.h
@@ -294,13 +294,14 @@ enum ThemeWidgetType : uint8_t {
   NS_THEME_MAC_VIBRANCY_DARK,
   NS_THEME_MAC_DISCLOSURE_BUTTON_OPEN,
   NS_THEME_MAC_DISCLOSURE_BUTTON_CLOSED,
 
   NS_THEME_GTK_INFO_BAR,
   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
 };
 
 #endif // nsThemeConstants_h_
--- a/layout/style/nsCSSRuleProcessor.cpp
+++ b/layout/style/nsCSSRuleProcessor.cpp
@@ -1175,16 +1175,22 @@ nsCSSRuleProcessor::InitSystemMetrics()
   }
 
   rv = LookAndFeel::GetInt(LookAndFeel::eIntID_PhysicalHomeButton,
                            &metricResult);
   if (NS_SUCCEEDED(rv) && metricResult) {
     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,
                             &metricResult))) {
     nsCSSRuleProcessor::SetWindowsThemeIdentifier(static_cast<uint8_t>(metricResult));
     switch(metricResult) {
       case LookAndFeel::eWindowsTheme_Aero:
         sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_aero);
--- a/layout/style/nsMediaFeatures.cpp
+++ b/layout/style/nsMediaFeatures.cpp
@@ -783,16 +783,25 @@ nsMediaFeatures::features[] = {
     &nsGkAtoms::_moz_physical_home_button,
     nsMediaFeature::eMinMaxNotAllowed,
     nsMediaFeature::eBoolInteger,
     nsMediaFeature::eNoRequirements,
     { &nsGkAtoms::physical_home_button },
     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.
   {
     &nsGkAtoms::_moz_is_glyph,
     nsMediaFeature::eMinMaxNotAllowed,
     nsMediaFeature::eBoolInteger,
     nsMediaFeature::eNoRequirements,
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -254,17 +254,17 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'coco
 EXTRA_JS_MODULES.third_party.jsesc += ['third_party/jsesc/jsesc.js']
 EXTRA_JS_MODULES.sessionstore += [
     'sessionstore/PrivacyLevel.jsm',
     'sessionstore/SessionHistory.jsm',
     'sessionstore/Utils.jsm',
 ]
 
 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'):
     DEFINES['MENUBAR_CAN_AUTOHIDE'] = 1
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3', 'cocoa'):
     DEFINES['HAVE_SHELL_SERVICE'] = 1
 
--- a/widget/LookAndFeel.h
+++ b/widget/LookAndFeel.h
@@ -388,16 +388,22 @@ public:
     /*
      * A boolean value indicating whether or not the device has a hardware
      * home button. Used on gaia to determine whether a home button
      * is shown.
      */
      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.
       */
      eIntID_ScrollbarDisplayOnMouseMove,
 
      /*
       * Overlay scrollbar animation constants.
       */
--- a/widget/gtk/WidgetStyleCache.cpp
+++ b/widget/gtk/WidgetStyleCache.cpp
@@ -21,20 +21,24 @@ static GtkWidget* sWidgetStorage[MOZ_GTK
 static GtkStyleContext* sStyleStorage[MOZ_GTK_WIDGET_NODE_COUNT];
 
 static GtkStyleContext*
 GetWidgetRootStyle(WidgetNodeType aNodeType);
 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;
 }
 
 static GtkWidget*
 CreateWindowContainerWidget()
 {
   GtkWidget *widget = gtk_fixed_new();
   gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_WINDOW)), widget);
@@ -96,17 +100,17 @@ CreateProgressWidget()
   return widget;
 }
 
 static GtkWidget*
 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;
 }
 
 static GtkWidget*
 CreateExpanderWidget()
 {
@@ -524,21 +528,83 @@ static GtkWidget*
 CreateNotebookWidget()
 {
   GtkWidget* widget = gtk_notebook_new();
   AddToWindowContainer(widget);
   return widget;
 }
 
 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:
       return CreateCheckboxWidget();
     case MOZ_GTK_PROGRESSBAR:
       return CreateProgressWidget();
     case MOZ_GTK_RADIOBUTTON_CONTAINER:
       return CreateRadiobuttonWidget();
@@ -605,16 +671,23 @@ CreateWidget(WidgetNodeType aWidgetType)
     case MOZ_GTK_COMBOBOX_ENTRY:
       return CreateComboBoxEntryWidget();
     case MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA:
       return CreateComboBoxEntryTextareaWidget();
     case MOZ_GTK_COMBOBOX_ENTRY_BUTTON:
       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;
   }
 }
 
 GtkWidget*
 GetWidget(WidgetNodeType aWidgetType)
@@ -1044,16 +1117,20 @@ GetCssNodeStyleInternal(WidgetNodeType a
     case MOZ_GTK_NOTEBOOK_HEADER:
     case MOZ_GTK_TABPANELS:
     case MOZ_GTK_TAB_SCROLLARROW:
     { 
       // TODO - create from CSS node
       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);
   }
 
   MOZ_ASSERT(style, "missing style context for node type");
   sStyleStorage[aNodeType] = style;
   return style;
 }
@@ -1209,16 +1286,18 @@ ResetWidgetCache(void)
     if (sStyleStorage[i])
       g_object_unref(sStyleStorage[i]);
   }
   mozilla::PodArrayZero(sStyleStorage);
 
   /* 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);
 }
 
 GtkStyleContext*
 GetStyleContext(WidgetNodeType aNodeType, GtkTextDirection aDirection,
                 GtkStateFlags aStateFlags, StyleFlags aFlags)
--- a/widget/gtk/gtk3drawing.cpp
+++ b/widget/gtk/gtk3drawing.cpp
@@ -12,16 +12,17 @@
 #include <gdk/gdkprivate.h>
 #include <string.h>
 #include "gtkdrawing.h"
 #include "mozilla/Assertions.h"
 #include "prinrval.h"
 #include "WidgetStyleCache.h"
 
 #include <math.h>
+#include <dlfcn.h>
 
 static gboolean checkbox_check_state;
 static gboolean notebook_has_tab_gap;
 
 static ScrollbarGTKMetrics sScrollbarMetrics[2];
 
 #define ARROW_UP      0
 #define ARROW_DOWN    G_PI
@@ -34,19 +35,35 @@ static ScrollbarGTKMetrics sScrollbarMet
 
 static gint
 moz_gtk_get_tab_thickness(GtkStyleContext *style);
 
 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
 // adjusted to match GTK behavior.  Although GTK sets insensitive and focus
 // flags in the generic GtkWidget base class, GTK adds prelight and active
 // flags only to widgets that are expected to demonstrate prelight or active
 // states.  This contrasts with HTML where any element may have :active and
 // :hover states, and so Gecko's GtkStateFlags do not necessarily map to GTK
@@ -228,16 +245,53 @@ moz_gtk_splitter_get_metrics(gint orient
         style = GetStyleContext(MOZ_GTK_SPLITTER_HORIZONTAL);
     } else {
         style = GetStyleContext(MOZ_GTK_SPLITTER_VERTICAL);
     }
     gtk_style_context_get_style(style, "handle_size", size, NULL);
     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<gint16>(-shadowClip.x),                    // left
+      static_cast<gint16>(shadowClip.width + shadowClip.x),  // right
+      static_cast<gint16>(-shadowClip.y),                    // top
+      static_cast<gint16>(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)
 {
     GtkStyleContext* style = GetStyleContext(MOZ_GTK_WINDOW, direction);
 
     gtk_style_context_save(style);
     gtk_style_context_add_class(style, GTK_STYLE_CLASS_BACKGROUND);
@@ -297,16 +351,34 @@ moz_gtk_button_paint(cairo_t *cr, GdkRec
         height -= (border.top + border.bottom);
         gtk_render_focus(style, cr, x, y, width, height);
     }
     gtk_style_context_restore(style);
     return MOZ_GTK_SUCCESS;
 }
 
 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,
                      gboolean isradio, GtkTextDirection direction)
 {
     GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
     gint indicator_size, indicator_spacing;
     gint x, y, width, height;
@@ -1943,16 +2015,35 @@ moz_gtk_info_bar_paint(cairo_t *cr, GdkR
                         GetStateFlagsFromGtkWidgetState(state));
     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);
 
     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)
 {
     GtkBorder margin;
 
     gtk_style_context_get_margin(style, GTK_STATE_FLAG_NORMAL, &margin);
 
@@ -1994,16 +2085,24 @@ static void moz_gtk_add_margin_border_pa
                                               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, 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)
 {
     gint left = 0, top = 0, right = 0, bottom = 0;
     moz_gtk_add_margin_border_padding(aStyle, &left, &top, &right, &bottom);
     // narrowing conversions to gint16:
     GtkBorder result;
     result.left = left;
@@ -2049,18 +2148,17 @@ moz_gtk_get_widget_border(WidgetNodeType
         }
     case MOZ_GTK_ENTRY:
         {
             style = GetStyleContext(MOZ_GTK_ENTRY);
 
             // 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);
 
             return MOZ_GTK_SUCCESS;
         }
     case MOZ_GTK_TEXT_VIEW:
     case MOZ_GTK_TREEVIEW:
         {
             style = GetStyleContext(MOZ_GTK_SCROLLED_WINDOW);
             moz_gtk_add_style_border(style, left, top, right, bottom);
@@ -2071,20 +2169,18 @@ moz_gtk_get_widget_border(WidgetNodeType
             /* A Tree Header in GTK is just a different styled button
              * It must be placed in a TreeView for getting the correct style
              * assigned.
              * That is why the following code is the same as for MOZ_GTK_BUTTON.
              * */
             *left = *top = *right = *bottom =
                 gtk_container_get_border_width(GTK_CONTAINER(
                                                GetWidget(MOZ_GTK_TREE_HEADER_CELL)));
-
             style = GetStyleContext(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);
             return MOZ_GTK_SUCCESS;
         }
     case MOZ_GTK_TREE_HEADER_SORTARROW:
         w = GetWidget(MOZ_GTK_TREE_HEADER_SORTARROW);
         break;
     case MOZ_GTK_DROPDOWN_ENTRY:
         w = GetWidget(MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA);
         break;
@@ -2100,18 +2196,17 @@ moz_gtk_get_widget_border(WidgetNodeType
             gint separator_width;
             GtkRequisition arrow_req;
             GtkBorder border;
 
             *left = *top = *right = *bottom =
                 gtk_container_get_border_width(GTK_CONTAINER(
                                                GetWidget(MOZ_GTK_COMBOBOX_BUTTON)));
             style = GetStyleContext(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);
 
             /* If there is no separator, don't try to count its width. */
             separator_width = 0;
             GtkWidget* comboBoxSeparator = GetWidget(MOZ_GTK_COMBOBOX_SEPARATOR);
             if (comboBoxSeparator) {
                 style = gtk_widget_get_style_context(comboBoxSeparator);
                 gtk_style_context_get_style(style,
                                             "wide-separators", &wide_separators,
@@ -2155,20 +2250,18 @@ moz_gtk_get_widget_border(WidgetNodeType
         break;
     case MOZ_GTK_CHECKBUTTON_CONTAINER:
     case MOZ_GTK_RADIOBUTTON_CONTAINER:
         {
             w = GetWidget(widget);
             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:
         w = GetWidget(MOZ_GTK_MENUPOPUP);
         break;
     case MOZ_GTK_MENUBARITEM:
     case MOZ_GTK_MENUITEM:
     case MOZ_GTK_CHECKMENUITEM:
@@ -2205,16 +2298,33 @@ moz_gtk_get_widget_border(WidgetNodeType
                                               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;
         }
+    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:
     case MOZ_GTK_RADIOBUTTON_LABEL:
     case MOZ_GTK_SPLITTER_HORIZONTAL:
     case MOZ_GTK_SPLITTER_VERTICAL:
     case MOZ_GTK_CHECKBUTTON:
     case MOZ_GTK_RADIOBUTTON:
@@ -2641,16 +2751,46 @@ GetScrollbarMetrics(GtkOrientation aOrie
     GtkBorder contentsBorder = GetMarginBorderPadding(style);
 
     metrics->size.scrollbar =
         trackSizeForThumb + contentsBorder + metrics->border.scrollbar;
 
     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,
                      GdkRectangle* rect,
                      GtkWidgetState* state, gint flags,
                      GtkTextDirection direction)
 {
     /* A workaround for https://bugzilla.gnome.org/show_bug.cgi?id=694086
@@ -2666,16 +2806,30 @@ moz_gtk_widget_paint(WidgetNodeType widg
                                         GetWidget(MOZ_GTK_TOGGLE_BUTTON),
                                         direction);
         }
         return moz_gtk_button_paint(cr, rect, state,
                                     (GtkReliefStyle) flags,
                                     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,
                                     !!(flags & MOZ_GTK_WIDGET_CHECKED),
                                     !!(flags & MOZ_GTK_WIDGET_INCONSISTENT),
                                     (widget == MOZ_GTK_RADIOBUTTON),
                                     direction);
         break;
@@ -2872,16 +3026,20 @@ moz_gtk_widget_paint(WidgetNodeType widg
         return moz_gtk_hpaned_paint(cr, rect, state);
         break;
     case MOZ_GTK_WINDOW:
         return moz_gtk_window_paint(cr, rect, direction);
         break;
     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);
     }
 
     return MOZ_GTK_UNKNOWN_WIDGET;
 }
 
 gint
--- a/widget/gtk/gtkdrawing.h
+++ b/widget/gtk/gtkdrawing.h
@@ -263,18 +263,24 @@ typedef enum {
   /* GtkHPaned base class */
   MOZ_GTK_SPLITTER_VERTICAL,
   /* Paints a GtkVPaned separator */
   MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL,
   /* Paints a GtkHPaned separator */
   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. */
   MOZ_GTK_COMBOBOX,
   /* Paints a GtkComboBox button widget. */
   MOZ_GTK_COMBOBOX_BUTTON,
   /* Paints a GtkComboBox arrow widget. */
   MOZ_GTK_COMBOBOX_ARROW,
@@ -285,16 +291,27 @@ typedef enum {
   /* Paints a GtkComboBox entry widget. */
   MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA,
   /* Paints a GtkComboBox entry button widget. */
   MOZ_GTK_COMBOBOX_ENTRY_BUTTON,
   /* Paints a GtkComboBox entry arrow widget. */
   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;
 
 /*** General library functions ***/
 /**
  * Initializes the drawing library.  You must call this function
  * prior to using any other functionality.
@@ -537,16 +554,31 @@ gint moz_gtk_get_menu_separator_height(g
  * Get the desired size of a splitter
  * orientation:   [IN]  GTK_ORIENTATION_HORIZONTAL or GTK_ORIENTATION_VERTICAL
  * size:          [OUT] width or height of the splitter handle
  *
  * returns:    MOZ_GTK_SUCCESS if there was no error, an error code otherwise
  */
 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).
  */
 gint
 moz_gtk_get_tab_thickness(WidgetNodeType aNodeType);
 
 #if (MOZ_WIDGET_GTK == 2)
 #ifdef __cplusplus
--- a/widget/gtk/mozgtk/mozgtk.c
+++ b/widget/gtk/mozgtk/mozgtk.c
@@ -575,37 +575,44 @@ 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_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)
 STUB(gtk_widget_path_free)
 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)
 STUB(gtk_app_chooser_get_app_info)
 STUB(gtk_app_chooser_dialog_get_type)
 STUB(gtk_app_chooser_dialog_set_heading)
 STUB(gtk_color_chooser_dialog_new)
 STUB(gtk_color_chooser_dialog_get_type)
 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
 STUB(gdk_drawable_get_screen)
 STUB(gdk_rgb_get_colormap)
 STUB(gdk_rgb_get_visual)
 STUB(gdk_window_lookup)
 STUB(gdk_window_set_back_pixmap)
--- a/widget/gtk/nsLookAndFeel.cpp
+++ b/widget/gtk/nsLookAndFeel.cpp
@@ -661,16 +661,20 @@ nsLookAndFeel::GetIntImpl(IntID aID, int
         break;
     case eIntID_ColorPickerAvailable:
         aResult = 1;
         break;
     case eIntID_ContextMenuOffsetVertical:
     case eIntID_ContextMenuOffsetHorizontal:
         aResult = 2;
         break;
+    case eIntID_GTKCSDAvailable:
+        EnsureInit();
+        aResult = sCSDAvailable;
+        break;
     default:
         aResult = 0;
         res     = NS_ERROR_FAILURE;
     }
 
     return res;
 }
 
@@ -1053,16 +1057,26 @@ nsLookAndFeel::EnsureInit()
                          "cursor-aspect-ratio", &sCaretRatio,
                          nullptr);
 
     GetSystemFontInfo(gtk_widget_get_style_context(entry),
                       &mFieldFontName, &mFieldFontStyle);
 
     gtk_widget_destroy(window);
     g_object_unref(labelWidget);
+
+    // 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
 char16_t
 nsLookAndFeel::GetPasswordCharacterImpl()
 {
     EnsureInit();
     return sInvisibleCharacter;
--- a/widget/gtk/nsLookAndFeel.h
+++ b/widget/gtk/nsLookAndFeel.h
@@ -27,16 +27,18 @@ public:
     virtual bool GetFontImpl(FontID aID, nsString& aFontName,
                              gfxFontStyle& aFontStyle,
                              float aDevPixPerCSSPixel);
 
     virtual void RefreshImpl();
     virtual char16_t GetPasswordCharacterImpl();
     virtual bool GetEchoPasswordImpl();
 
+    bool IsCSDAvailable() const { return sCSDAvailable; }
+
 protected:
 
     // Cached fonts
     bool mDefaultFontCached;
     bool mButtonFontCached;
     bool mFieldFontCached;
     bool mMenuFontCached;
     nsString mDefaultFontName;
@@ -72,14 +74,15 @@ protected:
     nscolor sMozWindowBackground;
     nscolor sTextSelectedText;
     nscolor sTextSelectedBackground;
     nscolor sMozScrollbar;
     nscolor sInfoBarText;
     char16_t sInvisibleCharacter;
     float   sCaretRatio;
     bool    sMenuSupportsDrag;
+    bool    sCSDAvailable;
     bool    mInitialized;
 
     void EnsureInit();
 };
 
 #endif
--- a/widget/gtk/nsNativeThemeGTK.cpp
+++ b/widget/gtk/nsNativeThemeGTK.cpp
@@ -18,16 +18,17 @@
 #include "nsNameSpaceManager.h"
 #include "nsGfxCIID.h"
 #include "nsTransform2D.h"
 #include "nsMenuFrame.h"
 #include "prlink.h"
 #include "nsIDOMHTMLInputElement.h"
 #include "nsGkAtoms.h"
 #include "nsAttrValueInlines.h"
+#include "nsWindow.h"
 
 #include "mozilla/EventStates.h"
 #include "mozilla/Services.h"
 
 #include <gdk/gdkprivate.h>
 #include <gtk/gtk.h>
 
 #include "gfxContext.h"
@@ -698,16 +699,34 @@ nsNativeThemeGTK::GetGtkWidgetAndState(u
     break;
   case NS_THEME_WINDOW:
   case NS_THEME_DIALOG:
     aGtkWidgetType = MOZ_GTK_WINDOW;
     break;
   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;
   }
 
   return true;
 }
 
 #if (MOZ_WIDGET_GTK == 2)
@@ -1630,16 +1649,20 @@ nsNativeThemeGTK::GetMinimumWidgetSize(n
   case NS_THEME_CHECKBOX_CONTAINER:
   case NS_THEME_RADIO_CONTAINER:
   case NS_THEME_CHECKBOX_LABEL:
   case NS_THEME_RADIO_LABEL:
   case NS_THEME_BUTTON:
   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.
         moz_gtk_get_arrow_size(MOZ_GTK_DROPDOWN,
                                &aResult->width, &aResult->height);
       }
       // else the minimum size is missing consideration of container
       // descendants; the value returned here will not be helpful, but the
@@ -1895,19 +1918,31 @@ nsNativeThemeGTK::ThemeSupportsWidget(ns
   case NS_THEME_MENUSEPARATOR:
   case NS_THEME_CHECKMENUITEM:
   case NS_THEME_RADIOMENUITEM:
   case NS_THEME_SPLITTER:
   case NS_THEME_WINDOW:
   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;
     }
     // "Native" dropdown buttons cause padding and margin problems, but only
     // in HTML so allow them in XUL.
     return (!aFrame || IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) &&
            !IsWidgetStyled(aPresContext, aFrame, aWidgetType);
@@ -1981,12 +2016,19 @@ nsNativeThemeGTK::GetWidgetTransparency(
   // Tooltips use gtk_paint_flat_box() on Gtk2
   // but are shaped on Gtk3
   case NS_THEME_TOOLTIP:
 #if (MOZ_WIDGET_GTK == 2)
     return eOpaque;
 #else
     return eTransparent;
 #endif
+  case NS_THEME_GTK_WINDOW_DECORATION:
+  {
+    nsWindow* window = static_cast<nsWindow*>(aFrame->GetNearestWidget());
+    if (window)
+      return window->IsComposited() ? eTransparent : eOpaque;
+    return eOpaque;
+  }
   }
 
   return eUnknownTransparency;
 }
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -80,16 +80,17 @@
 #include "nsGfxCIID.h"
 #include "nsGtkUtils.h"
 #include "nsIObserverService.h"
 #include "mozilla/layers/LayersTypes.h"
 #include "nsIIdleServiceInternal.h"
 #include "nsIPropertyBag2.h"
 #include "GLContext.h"
 #include "gfx2DGlue.h"
+#include "nsLookAndFeel.h"
 
 #ifdef ACCESSIBILITY
 #include "mozilla/a11y/Accessible.h"
 #include "mozilla/a11y/Platform.h"
 #include "nsAccessibilityService.h"
 
 using namespace mozilla;
 using namespace mozilla::widget;
@@ -134,16 +135,18 @@ using namespace mozilla::widget;
 #include "nsIDOMWheelEvent.h"
 
 #include "NativeKeyBindings.h"
 
 #include <dlfcn.h>
 
 #include "mozilla/layers/APZCTreeManager.h"
 
+#include "gtkdrawing.h"
+
 using namespace mozilla;
 using namespace mozilla::gfx;
 using namespace mozilla::widget;
 using namespace mozilla::layers;
 using mozilla::gl::GLContext;
 
 // Don't put more than this many rects in the dirty region, just fluff
 // out to the bounding-box if there are more
@@ -180,16 +183,18 @@ static void GetBrandName(nsAString& bran
 
 /* callbacks from widgets */
 #if (MOZ_WIDGET_GTK == 2)
 static gboolean expose_event_cb           (GtkWidget *widget,
                                            GdkEventExpose *event);
 #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);
 static void     container_unrealize_cb    (GtkWidget *widget);
 static void     size_allocate_cb          (GtkWidget *widget,
                                            GtkAllocation *allocation);
 static gboolean delete_event_cb           (GtkWidget *widget,
                                            GdkEventAny *event);
@@ -225,17 +230,16 @@ static void     theme_changed_cb        
                                            GParamSpec *pspec,
                                            nsWindow *data);
 static void     check_resize_cb           (GtkContainer* container,
                                            gpointer user_data);
 static void     screen_composited_changed_cb     (GdkScreen* screen,
                                                   gpointer user_data);
 static void     widget_composited_changed_cb     (GtkWidget* widget,
                                                   gpointer user_data);
-
 #if (MOZ_WIDGET_GTK == 3)
 static void     scale_changed_cb          (GtkWidget* widget,
                                            GParamSpec* aPSpec,
                                            gpointer aPointer);
 #endif
 #if GTK_CHECK_VERSION(3,4,0)
 static gboolean touch_event_cb            (GtkWidget* aWidget,
                                            GdkEventTouch* aEvent);
@@ -434,16 +438,17 @@ nsWindow::nsWindow()
 #if GTK_CHECK_VERSION(3,4,0)
     mHandleTouchEvent    = false;
 #endif
     mIsDragPopup         = false;
     mIsX11Display        = GDK_IS_X11_DISPLAY(gdk_display_get_default());
 
     mContainer           = nullptr;
     mGdkWindow           = nullptr;
+    mIsCSDEnabled        = false;
     mShell               = nullptr;
     mCompositorWidgetDelegate = nullptr;
     mHasMappedToplevel   = false;
     mIsFullyObscured     = false;
     mRetryPointerGrab    = false;
     mWindowType          = eWindowType_child;
     mSizeState           = nsSizeMode_Normal;
     mLastSizeMode        = nsSizeMode_Normal;
@@ -475,16 +480,19 @@ nsWindow::nsWindow()
 
     mTransparencyBitmapWidth  = 0;
     mTransparencyBitmapHeight = 0;
 
 #if GTK_CHECK_VERSION(3,4,0)
     mLastScrollEventTime = GDK_CURRENT_TIME;
 #endif
     mPendingConfigures = 0;
+    mDrawWindowDecoration = false;
+    mDecorationSize = {0,0,0,0};
+    mDecorationSizeChanged = false;
 }
 
 nsWindow::~nsWindow()
 {
     LOG(("nsWindow::~nsWindow() [%p]\n", (void *)this));
 
     delete[] mTransparencyBitmap;
     mTransparencyBitmap = nullptr;
@@ -1121,16 +1129,22 @@ nsWindow::Resize(double aWidth, double a
     int32_t width = NSToIntRound(scale * aWidth);
     int32_t height = NSToIntRound(scale * aHeight);
     ConstrainSize(&width, &height);
 
     // For top-level windows, aWidth and aHeight should possibly be
     // 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)
         return;
 
     NativeResize();
 
     NotifyRollupGeometryChange();
@@ -1600,16 +1614,20 @@ nsWindow::SetCursor(nsCursor aCursor)
 
         if (nullptr != newCursor) {
             mCursor = aCursor;
 
             if (!mContainer)
                 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);
+            }
         }
     }
 }
 
 nsresult
 nsWindow::SetCursor(imgIContainer* aCursor,
                     uint32_t aHotspotX, uint32_t aHotspotY)
 {
@@ -1656,16 +1674,20 @@ nsWindow::SetCursor(imgIContainer* aCurs
     GdkCursor* cursor = gdk_cursor_new_from_pixbuf(gdk_display_get_default(),
                                                    pixbuf,
                                                    aHotspotX, aHotspotY);
     g_object_unref(pixbuf);
     nsresult rv = NS_ERROR_OUT_OF_MEMORY;
     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)
         g_object_unref(cursor);
 #else
         gdk_cursor_unref(cursor);
 #endif
     }
@@ -2580,16 +2602,63 @@ nsWindow::OnMotionNotifyEvent(GdkEventMo
                 || peeked.type != MotionNotify)
                 break;
 
             synthEvent = true;
             XNextEvent (GDK_WINDOW_XDISPLAY(aEvent->window), &xevent);
         }
     }
 #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);
 
     gdouble pressure = 0;
     gdk_event_get_axis ((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure);
     // Sometime gdk generate 0 pressure value between normal values
     // We have to ignore that and use last valid value
     if (pressure)
@@ -2750,16 +2819,30 @@ nsWindow::OnButtonPressEvent(GdkEventBut
     if (!gFocusWindow && containerWindow) {
         containerWindow->DispatchActivateEvent();
     }
 
     // check to see if we should rollup
     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;
 
     uint16_t domButton;
     switch (aEvent->button) {
     case 1:
         domButton = WidgetMouseEvent::eLeftButton;
@@ -3335,16 +3418,18 @@ nsWindow::OnWindowStateEvent(GtkWidget *
     else {
         LOG(("\tNormal\n"));
         mSizeState = nsSizeMode_Normal;
 #ifdef ACCESSIBILITY
         DispatchRestoreEventAccessible();
 #endif //ACCESSIBILITY
     }
 
+    UpdateClientDecorations();
+
     if (mWidgetListener) {
       mWidgetListener->SizeModeChanged(mSizeState);
       if (aEvent->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) {
         mWidgetListener->FullscreenChanged(
           aEvent->new_window_state & GDK_WINDOW_STATE_FULLSCREEN);
       }
     }
 }
@@ -3399,16 +3484,17 @@ nsWindow::OnCompositedChanged()
 {
   if (mWidgetListener) {
     nsIPresShell* presShell = mWidgetListener->GetPresShell();
     if (presShell) {
       // Update CSD after the change in alpha visibility
       presShell->ThemeChanged();
     }
   }
+  UpdateClientDecorations();
 }
 
 void
 nsWindow::DispatchDragEvent(EventMessage aMsg, const LayoutDeviceIntPoint& aRefPoint,
                             guint aTime)
 {
     WidgetDragEvent event(true, aMsg, this);
 
@@ -3587,17 +3673,18 @@ nsWindow::Create(nsIWidget* aParent,
 
     // figure out our parent window
     GtkWidget      *parentMozContainer = nullptr;
     GtkContainer   *parentGtkContainer = nullptr;
     GdkWindow      *parentGdkWindow = nullptr;
     GtkWindow      *topLevelParent = nullptr;
     nsWindow       *parentnsWindow = nullptr;
     GtkWidget      *eventWidget = nullptr;
-    bool            shellHasCSD = false;
+    GtkWidget      *drawWidget = nullptr;
+    bool            drawToContainer = false;
 
     if (aParent) {
         parentnsWindow = static_cast<nsWindow*>(aParent);
         parentGdkWindow = parentnsWindow->mGdkWindow;
     } else if (aNativeParent && GDK_IS_WINDOW(aNativeParent)) {
         parentGdkWindow = GDK_WINDOW(aNativeParent);
         parentnsWindow = get_window_for_gdk_window(parentGdkWindow);
         if (!parentnsWindow)
@@ -3634,39 +3721,56 @@ nsWindow::Create(nsIWidget* aParent,
         // (for temporary windows).
         // For long-lived windows, their stacking order is managed by the
         // window manager, as indicated by GTK_WINDOW_TOPLEVEL ...
         GtkWindowType type =
             mWindowType != eWindowType_popup || aInitData->mNoAutoHide ?
               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
         // actually placed the window somewhere.  If no placement has taken
         // place, we just let the window manager Do The Right Thing.
         NativeResize();
 
         if (mWindowType == eWindowType_dialog) {
@@ -3750,44 +3854,76 @@ nsWindow::Create(nsIWidget* aParent,
             g_object_unref(group);
         }
 
         // Create a container to hold child windows and child GtkWidgets.
         GtkWidget *container = moz_container_new();
         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");
+        /* 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
-        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);
+        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);
         }
-        // Set up event widget
-        eventWidget = shellHasCSD ? container : mShell;
-        gtk_widget_add_events(eventWidget, 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);
 
         // make sure this is the focus widget in the container
         gtk_widget_show(container);
         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"
             // windows, which are what gtk uses for popups.
 
             mCursor = eCursor_wait; // force SetCursor to actually set the
                                     // cursor, even though our internal state
                                     // indicates that we already have the
@@ -3850,16 +3986,21 @@ nsWindow::Create(nsIWidget* aParent,
     }
         break;
     default:
         break;
     }
 
     // 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);
 
     if (mShell)
         g_object_set_data(G_OBJECT(mShell), "nsWindow", this);
 
     // attach listeners for events
@@ -3887,16 +4028,20 @@ nsWindow::Create(nsIWidget* aParent,
 
         GtkSettings* default_settings = gtk_settings_get_default();
         g_signal_connect_after(default_settings,
                                "notify::gtk-theme-name",
                                G_CALLBACK(theme_changed_cb), this);
         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) {
         // Widget signals
         g_signal_connect(mContainer, "unrealize",
                          G_CALLBACK(container_unrealize_cb), nullptr);
         g_signal_connect_after(mContainer, "size_allocate",
                                G_CALLBACK(size_allocate_cb), nullptr);
@@ -3937,17 +4082,17 @@ nsWindow::Create(nsIWidget* aParent,
         g_signal_connect(mContainer, "drag_leave",
                          G_CALLBACK(drag_leave_event_cb), nullptr);
         g_signal_connect(mContainer, "drag_drop",
                          G_CALLBACK(drag_drop_event_cb), nullptr);
         g_signal_connect(mContainer, "drag_data_received",
                          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
             // mShell (if it exists) as well as mContainer.
             g_signal_connect(widgets[i], "visibility-notify-event",
                              G_CALLBACK(visibility_notify_event_cb), nullptr);
             // Similarly double buffering is controlled by the window's owning
             // widget.  Disable double buffering for painting directly to the
@@ -3967,17 +4112,16 @@ nsWindow::Create(nsIWidget* aParent,
         }
     }
 
     if (eventWidget) {
 #if (MOZ_WIDGET_GTK == 2)
         // 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
         // window and windows of mContainer.
         g_signal_connect(eventWidget, "enter-notify-event",
                          G_CALLBACK(enter_notify_event_cb), nullptr);
         g_signal_connect(eventWidget, "leave-notify-event",
                          G_CALLBACK(leave_notify_event_cb), nullptr);
@@ -5518,16 +5662,41 @@ expose_event_cb(GtkWidget *widget, cairo
         [](gpointer data) -> gboolean {
             g_object_unref(data);
             return G_SOURCE_REMOVE;
         },
         widget);
 
     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<nsWindow> 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
 configure_event_cb(GtkWidget *widget,
                    GdkEventConfigure *event)
 {
     RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
     if (!window)
@@ -6570,16 +6739,38 @@ nsWindow::ClearCachedResources()
     for (GList* list = children; list; list = list->next) {
         nsWindow* window = get_window_for_gdk_window(GDK_WINDOW(list->data));
         if (window) {
             window->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()
 {
 #if (MOZ_WIDGET_GTK >= 3)
     // Available as of GTK 3.10+
     static auto sGdkWindowGetScaleFactorPtr = (gint (*)(GdkWindow*))
         dlsym(RTLD_DEFAULT, "gdk_window_get_scale_factor");
     if (sGdkWindowGetScaleFactorPtr && mGdkWindow)
@@ -6840,16 +7031,117 @@ nsWindow::SynthesizeNativeTouchPoint(uin
   event.touch.y = DevicePixelsToGdkCoordRoundDown(pointInWindow.y);
 
   gdk_event_put(&event);
 
   return NS_OK;
 }
 #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()
 {
     return GdkScaleFactor();
 }
 
 void nsWindow::GetCompositorWidgetInitData(mozilla::widget::CompositorWidgetInitData* aInitData)
 {
--- a/widget/gtk/nsWindow.h
+++ b/widget/gtk/nsWindow.h
@@ -118,16 +118,17 @@ public:
                                          double aHeight,
                                          bool   aRepaint) override;
     virtual void       Resize           (double aX,
                                          double aY,
                                          double aWidth,
                                          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;
     virtual void       Enable(bool aState) override;
     virtual nsresult   SetFocus(bool aRaise = false) override;
     virtual LayoutDeviceIntRect GetScreenBounds() override;
     virtual LayoutDeviceIntRect GetClientBounds() override;
     virtual LayoutDeviceIntSize GetClientSize() override;
@@ -346,32 +347,38 @@ public:
                                                 nsIObserver* aObserver) override;
 #endif
 
 #ifdef MOZ_X11
     Display* XDisplay() { return mXDisplay; }
 #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();
 
     // To GDK
     gint DevicePixelsToGdkCoordRoundUp(int pixels);
     gint DevicePixelsToGdkCoordRoundDown(int pixels);
     GdkPoint DevicePixelsToGdkPointRoundDown(LayoutDeviceIntPoint point);
     GdkRectangle DevicePixelsToGdkSizeRoundUp(LayoutDeviceIntSize pixelSize);
 
     // From GDK
     int GdkCoordToDevicePixels(gint coord);
     LayoutDeviceIntPoint GdkPointToDevicePixels(GdkPoint point);
     LayoutDeviceIntPoint GdkEventCoordsToDevicePixels(gdouble x, gdouble y);
     LayoutDeviceIntRect GdkRectToDevicePixels(GdkRectangle rect);
 
     virtual bool WidgetTypeSupportsAcceleration() override;
+
+    // Decorations
+    bool IsClientDecorated() const;
 protected:
     virtual ~nsWindow();
 
     // event handling code
     void DispatchActivateEvent(void);
     void DispatchDeactivateEvent(void);
     void DispatchResized();
     void MaybeDispatchResized();
@@ -379,16 +386,26 @@ protected:
     // Helper for SetParent and ReparentNativeWidget.
     void ReparentNativeWidgetInternal(nsIWidget* aNewParent,
                                       GtkWidget* aNewContainer,
                                       GdkWindow* aNewParentWindow,
                                       GtkWidget* aOldContainer);
 
     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<nsIWidget> mParent;
     // Is this a toplevel window?
     bool                mIsTopLevel;
     // Has this widget been destroyed yet?
     bool                mIsDestroyed;
 
     // Should we send resize events on all resizes?
     bool                mListenForResizes;
@@ -427,22 +444,22 @@ private:
       CheckForRollup(0, 0, false, true);
     }
 
     bool               GetDragInfo(mozilla::WidgetMouseEvent* aMouseEvent,
                                    GdkWindow** aWindow, gint* aButton,
                                    gint* aRootX, gint* aRootY);
     void               ClearCachedResources();
     nsIWidgetListener* GetListener();
-    bool               IsComposited() const;
 
 
     GtkWidget          *mShell;
     MozContainer       *mContainer;
     GdkWindow          *mGdkWindow;
+    bool                mIsCSDEnabled;
     PlatformCompositorWidgetDelegate* mCompositorWidgetDelegate;
 
 
     uint32_t            mHasMappedToplevel : 1,
                         mIsFullyObscured : 1,
                         mRetryPointerGrab : 1;
     nsSizeMode          mSizeState;
 
@@ -531,16 +548,21 @@ private:
     void   InitDragEvent(mozilla::WidgetDragEvent& aEvent);
 
     float              mLastMotionPressure;
 
     // Remember the last sizemode so that we can restore it when
     // 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);
 
     // nsBaseWidget
     virtual LayerManager* GetLayerManager(PLayerTransactionChild* aShadowManager = nullptr,
                                           LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE,
                                           LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT) override;