Bug 1433667 - Honour system titlebar button left/right placement, r=dao
authorMartin Stransky <stransky@redhat.com>
Fri, 18 Jan 2019 13:52:29 +0000
changeset 514425 7d151a27de64cce8b0ed96443c1888656341e420
parent 514424 529a105310435244ef67c078a810ad92b22d9327
child 514426 e902aece9ed7f640a7be789ff2cb5ccdfa8a5719
push id1953
push userffxbld-merge
push dateMon, 11 Mar 2019 12:10:20 +0000
treeherdermozilla-release@9c35dcbaa899 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdao
bugs1433667
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1433667 - Honour system titlebar button left/right placement, r=dao Differential Revision: https://phabricator.services.mozilla.com/D16466
browser/base/content/browser.css
browser/themes/linux/browser.css
layout/style/nsMediaFeatures.cpp
layout/style/test/chrome/bug418986-2.js
layout/style/test/test_media_queries.html
servo/components/style/gecko/media_features.rs
widget/LookAndFeel.h
widget/gtk/WidgetStyleCache.cpp
widget/gtk/gtk3drawing.cpp
widget/gtk/gtkdrawing.h
widget/gtk/nsLookAndFeel.cpp
widget/gtk/nsLookAndFeel.h
widget/headless/HeadlessLookAndFeelGTK.cpp
widget/nsXPLookAndFeel.cpp
xpcom/ds/StaticAtoms.py
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -280,24 +280,38 @@ window:not([chromehidden~="toolbar"]) #n
 }
 
 
 %ifdef MENUBAR_CAN_AUTOHIDE
 #toolbar-menubar:not([autohide=true]) + #TabsToolbar > .titlebar-buttonbox-container,
 #toolbar-menubar:not([autohide=true]) + #TabsToolbar .titlebar-spacer,
 %endif
 %ifndef MOZ_WIDGET_COCOA
+%ifndef MOZ_WIDGET_GTK
 :root:not([sizemode=normal]) .titlebar-spacer[type="pre-tabs"],
 %endif
+%endif
 :root:not([chromemargin]) .titlebar-buttonbox-container,
 :root[inFullscreen] .titlebar-buttonbox-container,
 :root[inFullscreen] .titlebar-spacer,
 :root:not([tabsintitlebar]) .titlebar-spacer {
   display: none;
 }
+%ifdef MOZ_WIDGET_GTK
+@media (-moz-gtk-csd-reversed-placement: 0) {
+  :root:not([sizemode=normal]) .titlebar-spacer[type="pre-tabs"] {
+    display: none;
+  }
+}
+@media (-moz-gtk-csd-reversed-placement) {
+  :root:not([sizemode=normal]) .titlebar-spacer[type="post-tabs"] {
+    display: none;
+  }
+}
+%endif
 
 %ifdef MENUBAR_CAN_AUTOHIDE
 #toolbar-menubar[autohide=true]:not([inactive]) + #TabsToolbar > .titlebar-buttonbox-container {
   visibility: hidden;
 }
 %endif
 
 #titlebar {
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -711,9 +711,15 @@ notification[value="translation"] menuli
       -moz-appearance: -moz-window-button-close;
     }
   }
   @media (-moz-gtk-csd-close-button: 0) {
     .titlebar-close {
       display: none;
     }
   }
+  @media (-moz-gtk-csd-reversed-placement) {
+    .titlebar-buttonbox-container,
+    .titlebar-close {
+      -moz-box-ordinal-group: 0;
+    }
+  }
 }
--- a/layout/style/nsMediaFeatures.cpp
+++ b/layout/style/nsMediaFeatures.cpp
@@ -420,16 +420,23 @@ PointerCapabilities Gecko_MediaFeatures_
 
   rv =
       LookAndFeel::GetInt(LookAndFeel::eIntID_GTKCSDCloseButton, &metricResult);
   if (NS_SUCCEEDED(rv) && metricResult) {
     sSystemMetrics->AppendElement(
         (nsStaticAtom*)nsGkAtoms::_moz_gtk_csd_close_button);
   }
 
+  rv = LookAndFeel::GetInt(LookAndFeel::eIntID_GTKCSDReversedPlacement,
+                           &metricResult);
+  if (NS_SUCCEEDED(rv) && metricResult) {
+    sSystemMetrics->AppendElement(
+        (nsStaticAtom*)nsGkAtoms::_moz_gtk_csd_reversed_placement);
+  }
+
   metricResult = LookAndFeel::GetInt(LookAndFeel::eIntID_SystemUsesDarkTheme);
   if (metricResult) {
     sSystemMetrics->AppendElement(
         (nsStaticAtom*)nsGkAtoms::_moz_system_dark_theme);
   }
 }
 
 /* static */ void nsMediaFeatures::FreeSystemMetrics() {
--- a/layout/style/test/chrome/bug418986-2.js
+++ b/layout/style/test/chrome/bug418986-2.js
@@ -54,16 +54,17 @@ var suppressed_toggles = [
   "-moz-windows-default-theme",
   "-moz-windows-glass",
   "-moz-gtk-csd-available",
   "-moz-gtk-csd-hide-titlebar-by-default",
   "-moz-gtk-csd-transparent-background",
   "-moz-gtk-csd-minimize-button",
   "-moz-gtk-csd-maximize-button",
   "-moz-gtk-csd-close-button",
+  "-moz-gtk-csd-reversed-placement",
 ];
 
 var toggles_enabled_in_content = [
   "-moz-touch-enabled",
 ];
 
 // Possible values for '-moz-os-version'
 var windows_versions = [
--- a/layout/style/test/test_media_queries.html
+++ b/layout/style/test/test_media_queries.html
@@ -694,16 +694,17 @@ function run() {
   expression_should_not_be_parseable("-moz-windows-glass");
   expression_should_not_be_parseable("-moz-swipe-animation-enabled");
   expression_should_not_be_parseable("-moz-gtk-csd-available");
   expression_should_not_be_parseable("-moz-gtk-csd-hide-titlebar-by-default");
   expression_should_not_be_parseable("-moz-gtk-csd-transparent-background");
   expression_should_not_be_parseable("-moz-gtk-csd-minimize-button");
   expression_should_not_be_parseable("-moz-gtk-csd-maximize-button");
   expression_should_not_be_parseable("-moz-gtk-csd-close-button");
+  expression_should_not_be_parseable("-moz-gtk-csd-reversed-placement");
   expression_should_be_parseable("-moz-touch-enabled");
 
   expression_should_not_be_parseable("-moz-scrollbar-start-backward: 0");
   expression_should_not_be_parseable("-moz-scrollbar-start-forward: 0");
   expression_should_not_be_parseable("-moz-scrollbar-end-backward: 0");
   expression_should_not_be_parseable("-moz-scrollbar-end-forward: 0");
   expression_should_not_be_parseable("-moz-scrollbar-thumb-proportional: 0");
   expression_should_not_be_parseable("-moz-overlay-scrollbars: 0");
@@ -716,16 +717,17 @@ function run() {
   expression_should_not_be_parseable("-moz-windows-glass: 0");
   expression_should_not_be_parseable("-moz-swipe-animation-enabled: 0");
   expression_should_not_be_parseable("-moz-gtk-csd-available: 0");
   expression_should_not_be_parseable("-moz-gtk-csd-hide-titlebar-by-default: 0");
   expression_should_not_be_parseable("-moz-gtk-csd-transparent-background: 0");
   expression_should_not_be_parseable("-moz-gtk-csd-minimize-button: 0");
   expression_should_not_be_parseable("-moz-gtk-csd-maximize-button: 0");
   expression_should_not_be_parseable("-moz-gtk-csd-close-button: 0");
+  expression_should_not_be_parseable("-moz-gtk-csd-reversed-placement: 0");
   expression_should_be_parseable("-moz-touch-enabled: 0");
 
   expression_should_not_be_parseable("-moz-scrollbar-start-backward: 1");
   expression_should_not_be_parseable("-moz-scrollbar-start-forward: 1");
   expression_should_not_be_parseable("-moz-scrollbar-end-backward: 1");
   expression_should_not_be_parseable("-moz-scrollbar-end-forward: 1");
   expression_should_not_be_parseable("-moz-scrollbar-thumb-proportional: 1");
   expression_should_not_be_parseable("-moz-overlay-scrollbars: 1");
@@ -738,16 +740,17 @@ function run() {
   expression_should_not_be_parseable("-moz-windows-glass: 1");
   expression_should_not_be_parseable("-moz-swipe-animation-enabled: 1");
   expression_should_not_be_parseable("-moz-gtk-csd-available: 1");
   expression_should_not_be_parseable("-moz-gtk-csd-hide-titlebar-by-default: 1");
   expression_should_not_be_parseable("-moz-gtk-csd-transparent-background: 1");
   expression_should_not_be_parseable("-moz-gtk-csd-minimize-button: 1");
   expression_should_not_be_parseable("-moz-gtk-csd-maximize-button: 1");
   expression_should_not_be_parseable("-moz-gtk-csd-close-button: 1");
+  expression_should_not_be_parseable("-moz-gtk-csd-reversed-placement: 1");
   expression_should_be_parseable("-moz-touch-enabled: 1");
 
   expression_should_not_be_parseable("-moz-scrollbar-start-backward: -1");
   expression_should_not_be_parseable("-moz-scrollbar-start-forward: -1");
   expression_should_not_be_parseable("-moz-scrollbar-end-backward: -1");
   expression_should_not_be_parseable("-moz-scrollbar-end-forward: -1");
   expression_should_not_be_parseable("-moz-scrollbar-thumb-proportional: -1");
   expression_should_not_be_parseable("-moz-overlay-scrollbars: -1");
@@ -761,16 +764,17 @@ function run() {
   expression_should_not_be_parseable("-moz-touch-enabled: -1");
   expression_should_not_be_parseable("-moz-swipe-animation-enabled: -1");
   expression_should_not_be_parseable("-moz-gtk-csd-available: -1");
   expression_should_not_be_parseable("-moz-gtk-csd-hide-titlebar-by-default: -1");
   expression_should_not_be_parseable("-moz-gtk-csd-transparent-background: -1");
   expression_should_not_be_parseable("-moz-gtk-csd-minimize-button: -1");
   expression_should_not_be_parseable("-moz-gtk-csd-maximize-button: -1");
   expression_should_not_be_parseable("-moz-gtk-csd-close-button: -1");
+  expression_should_not_be_parseable("-moz-gtk-csd-reversed-placement: -1");
 
   expression_should_not_be_parseable("-moz-scrollbar-start-backward: true");
   expression_should_not_be_parseable("-moz-scrollbar-start-forward: true");
   expression_should_not_be_parseable("-moz-scrollbar-end-backward: true");
   expression_should_not_be_parseable("-moz-scrollbar-end-forward: true");
   expression_should_not_be_parseable("-moz-scrollbar-thumb-proportional: true");
   expression_should_not_be_parseable("-moz-overlay-scrollbars: true");
   expression_should_not_be_parseable("-moz-windows-default-theme: true");
@@ -783,16 +787,17 @@ function run() {
   expression_should_not_be_parseable("-moz-touch-enabled: true");
   expression_should_not_be_parseable("-moz-swipe-animation-enabled: true");
   expression_should_not_be_parseable("-moz-gtk-csd-available: true");
   expression_should_not_be_parseable("-moz-gtk-csd-hide-titlebar-by-default: true");
   expression_should_not_be_parseable("-moz-gtk-csd-transparent-background: true");
   expression_should_not_be_parseable("-moz-gtk-csd-minimize-button: true");
   expression_should_not_be_parseable("-moz-gtk-csd-maximize-button: true");
   expression_should_not_be_parseable("-moz-gtk-csd-close-button: true");
+  expression_should_not_be_parseable("-moz-gtk-csd-reversed-placement: true");
 
   // os version media queries (currently windows only)
   expression_should_not_be_parseable("-moz-os-version: windows-win7");
   expression_should_not_be_parseable("-moz-os-version: windows-win8");
   expression_should_not_be_parseable("-moz-os-version: windows-win10");
   expression_should_not_be_parseable("-moz-os-version: ");
 
   // OpenType SVG media features
--- a/servo/components/style/gecko/media_features.rs
+++ b/servo/components/style/gecko/media_features.rs
@@ -521,17 +521,17 @@ macro_rules! system_metric_feature {
 }
 
 lazy_static! {
     /// Adding new media features requires (1) adding the new feature to this
     /// array, with appropriate entries (and potentially any new code needed
     /// to support new types in these entries and (2) ensuring that either
     /// nsPresContext::MediaFeatureValuesChanged is called when the value that
     /// would be returned by the evaluator function could change.
-    pub static ref MEDIA_FEATURES: [MediaFeatureDescription; 51] = [
+    pub static ref MEDIA_FEATURES: [MediaFeatureDescription; 52] = [
         feature!(
             atom!("width"),
             AllowsRanges::Yes,
             Evaluator::Length(eval_width),
             ParsingRequirements::empty(),
         ),
         feature!(
             atom!("height"),
@@ -719,16 +719,17 @@ lazy_static! {
         system_metric_feature!(atom!("-moz-menubar-drag")),
         system_metric_feature!(atom!("-moz-swipe-animation-enabled")),
         system_metric_feature!(atom!("-moz-gtk-csd-available")),
         system_metric_feature!(atom!("-moz-gtk-csd-hide-titlebar-by-default")),
         system_metric_feature!(atom!("-moz-gtk-csd-transparent-background")),
         system_metric_feature!(atom!("-moz-gtk-csd-minimize-button")),
         system_metric_feature!(atom!("-moz-gtk-csd-maximize-button")),
         system_metric_feature!(atom!("-moz-gtk-csd-close-button")),
+        system_metric_feature!(atom!("-moz-gtk-csd-reversed-placement")),
         system_metric_feature!(atom!("-moz-system-dark-theme")),
         // This is the only system-metric media feature that's accessible to
         // content as of today.
         // FIXME(emilio): Restrict (or remove?) when bug 1035774 lands.
         feature!(
             atom!("-moz-touch-enabled"),
             AllowsRanges::No,
             Evaluator::BoolInteger(eval_moz_touch_enabled),
--- a/widget/LookAndFeel.h
+++ b/widget/LookAndFeel.h
@@ -444,16 +444,22 @@ class LookAndFeel {
 
     /*
      * A boolean value indicating whether client-side decorations should
      * contain a close button.
      */
     eIntID_GTKCSDCloseButton,
 
     /*
+     * A boolean value indicating whether titlebar buttons are located
+     * in left titlebar corner.
+     */
+    eIntID_GTKCSDReversedPlacement,
+
+    /*
      * A boolean value indicating whether or not the OS is using a dark theme,
      * which we may want to switch to as well if not overridden by the user.
      */
     eIntID_SystemUsesDarkTheme,
 
     /**
      * Corresponding to prefers-reduced-motion.
      * https://drafts.csswg.org/mediaqueries-5/#prefers-reduced-motion
--- a/widget/gtk/WidgetStyleCache.cpp
+++ b/widget/gtk/WidgetStyleCache.cpp
@@ -628,18 +628,19 @@ static void CreateHeaderBarButtons() {
 
   GtkWidget* buttonBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, buttonSpacing);
   gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_HEADER_BAR)), buttonBox);
   // We support only LTR headerbar layout for now.
   gtk_style_context_add_class(gtk_widget_get_style_context(buttonBox),
                               GTK_STYLE_CLASS_LEFT);
 
   WidgetNodeType buttonLayout[TOOLBAR_BUTTONS];
+
   int activeButtons =
-      GetGtkHeaderBarButtonLayout(buttonLayout, TOOLBAR_BUTTONS);
+      GetGtkHeaderBarButtonLayout(buttonLayout, TOOLBAR_BUTTONS, nullptr);
 
   if (IsToolbarButtonEnabled(buttonLayout, activeButtons,
                              MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE)) {
     CreateHeaderBarButton(buttonBox, MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE);
   }
   if (IsToolbarButtonEnabled(buttonLayout, activeButtons,
                              MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE)) {
     CreateHeaderBarButton(buttonBox, MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE);
--- a/widget/gtk/gtk3drawing.cpp
+++ b/widget/gtk/gtk3drawing.cpp
@@ -386,40 +386,54 @@ static void CalculateToolbarButtonSpacin
 
   aMetrics->minSizeWithBorderMargin.width +=
       aMetrics->buttonMargin.right + aMetrics->buttonMargin.left;
   aMetrics->minSizeWithBorderMargin.height +=
       aMetrics->buttonMargin.top + aMetrics->buttonMargin.bottom;
 }
 
 int GetGtkHeaderBarButtonLayout(WidgetNodeType* aButtonLayout,
-                                int aMaxButtonNums) {
-  NS_ASSERTION(aMaxButtonNums >= TOOLBAR_BUTTONS,
-               "Requested number of buttons is higher than storage capacity!");
+                                int aMaxButtonNums,
+                                bool* aReversedButtonsPlacement) {
+#if DEBUG
+  if (aButtonLayout) {
+    NS_ASSERTION(
+        aMaxButtonNums >= TOOLBAR_BUTTONS,
+        "Requested number of buttons is higher than storage capacity!");
+  }
+#endif
 
   const gchar* decorationLayout = nullptr;
   GtkSettings* settings = gtk_settings_get_for_screen(gdk_screen_get_default());
   g_object_get(settings, "gtk-decoration-layout", &decorationLayout, nullptr);
 
   // Use a default layout
   if (!decorationLayout) {
-    decorationLayout = "minimize,maximize,close";
+    decorationLayout = "menu:minimize,maximize,close";
   }
 
   // We support only default button order now:
   // minimize/maximize/close
   int activeButtonNums = 0;
-  if (strstr(decorationLayout, "minimize") != nullptr) {
-    aButtonLayout[activeButtonNums++] = MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE;
+  if (aButtonLayout) {
+    if (strstr(decorationLayout, "minimize") != nullptr) {
+      aButtonLayout[activeButtonNums++] = MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE;
+    }
+    if (strstr(decorationLayout, "maximize") != nullptr) {
+      aButtonLayout[activeButtonNums++] = MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE;
+    }
+    if (strstr(decorationLayout, "close") != nullptr) {
+      aButtonLayout[activeButtonNums++] = MOZ_GTK_HEADER_BAR_BUTTON_CLOSE;
+    }
   }
-  if (strstr(decorationLayout, "maximize") != nullptr) {
-    aButtonLayout[activeButtonNums++] = MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE;
-  }
-  if (strstr(decorationLayout, "close") != nullptr) {
-    aButtonLayout[activeButtonNums++] = MOZ_GTK_HEADER_BAR_BUTTON_CLOSE;
+
+  // "minimize,maximize,close:menu" layout means buttons are on the opposite
+  // titlebar side.
+  if (aReversedButtonsPlacement) {
+    *aReversedButtonsPlacement = strstr(decorationLayout, ":menu") != nullptr;
   }
 
   return activeButtonNums;
 }
 
 static void EnsureToolbarMetrics(void) {
   if (!sToolbarMetrics.initialized) {
     // Make sure we have clean cache after theme reset, etc.
@@ -430,17 +444,17 @@ static void EnsureToolbarMetrics(void) {
     if (gtk_check_version(3, 10, 0) != nullptr) {
       sToolbarMetrics.initialized = true;
       return;
     }
 
     // Calculate titlebar button visibility and positions.
     WidgetNodeType aButtonLayout[TOOLBAR_BUTTONS];
     int activeButtonNums =
-        GetGtkHeaderBarButtonLayout(aButtonLayout, TOOLBAR_BUTTONS);
+        GetGtkHeaderBarButtonLayout(aButtonLayout, TOOLBAR_BUTTONS, nullptr);
 
     for (int i = 0; i < activeButtonNums; i++) {
       int buttonIndex = (aButtonLayout[i] - MOZ_GTK_HEADER_BAR_BUTTON_CLOSE);
       ToolbarButtonGTKMetrics* metrics = sToolbarMetrics.button + buttonIndex;
       metrics->visible = true;
       // Mark first button
       if (!i) {
         metrics->firstButton = true;
@@ -2344,17 +2358,22 @@ gint moz_gtk_get_widget_border(WidgetNod
       moz_gtk_add_margin_border_padding(labelStyle, left, top, right, bottom);
 
       return MOZ_GTK_SUCCESS;
     }
     case MOZ_GTK_HEADER_BAR_BUTTON_BOX: {
       style = GetStyleContext(MOZ_GTK_HEADER_BAR);
       moz_gtk_add_border_padding(style, left, top, right, bottom);
       *top = *bottom = 0;
+      bool leftButtonsPlacement;
+      GetGtkHeaderBarButtonLayout(nullptr, 0, &leftButtonsPlacement);
       if (direction == GTK_TEXT_DIR_RTL) {
+        leftButtonsPlacement = !leftButtonsPlacement;
+      }
+      if (leftButtonsPlacement) {
         *right = 0;
       } else {
         *left = 0;
       }
       return MOZ_GTK_SUCCESS;
     }
     /* These widgets have no borders, since they are not containers. */
     case MOZ_GTK_CHECKBUTTON_LABEL:
--- a/widget/gtk/gtkdrawing.h
+++ b/widget/gtk/gtkdrawing.h
@@ -593,24 +593,27 @@ gint moz_gtk_get_tab_thickness(WidgetNod
  */
 const ToolbarButtonGTKMetrics* GetToolbarButtonMetrics(
     WidgetNodeType aAppearance);
 
 /**
  * Get toolbar button layout.
  * aButtonLayout:  [IN][OUT] An array which will be filled by WidgetNodeType
  *                           references to visible titlebar buttons.
-                             Must contains at least TOOLBAR_BUTTONS entries.
+ *                           Must contains at least TOOLBAR_BUTTONS entries.
  * aMaxButtonNums: [IN] Allocated aButtonLayout entries. Must be at least
-                        TOOLBAR_BUTTONS wide.
+ *                      TOOLBAR_BUTTONS wide.
+ * aReversedButtonsPlacement: [OUT] True if the buttons are placed in opposite
+ *                                  titlebar corner.
  *
  * returns:    Number of returned entries at aButtonLayout.
  */
 int GetGtkHeaderBarButtonLayout(WidgetNodeType* aButtonLayout,
-                                int aMaxButtonNums);
+                                int aMaxButtonNums,
+                                bool* aReversedButtonsPlacement);
 
 /**
  * Get size of CSD window extents of given GtkWindow.
  *
  * aGtkWindow      [IN]  Decorated window.
  * aDecorationSize [OUT] Returns calculated (or estimated) decoration
  *                       size of given aGtkWindow.
  *
--- a/widget/gtk/nsLookAndFeel.cpp
+++ b/widget/gtk/nsLookAndFeel.cpp
@@ -687,16 +687,20 @@ nsresult nsLookAndFeel::GetIntImpl(IntID
       break;
     case eIntID_GTKCSDCloseButton:
       EnsureInit();
       aResult = mCSDCloseButton;
       break;
     case eIntID_GTKCSDTransparentBackground:
       aResult = nsWindow::TopLevelWindowUseARGBVisual();
       break;
+    case eIntID_GTKCSDReversedPlacement:
+      EnsureInit();
+      aResult = mCSDReversedPlacement;
+      break;
     case eIntID_PrefersReducedMotion: {
       GtkSettings* settings;
       gboolean enableAnimations;
 
       settings = gtk_settings_get_default();
       g_object_get(settings, "gtk-enable-animations", &enableAnimations,
                    nullptr);
       aResult = enableAnimations ? 0 : 1;
@@ -1116,18 +1120,18 @@ void nsLookAndFeel::EnsureInit() {
   mCSDCloseButton = false;
   mCSDMinimizeButton = false;
   mCSDMaximizeButton = false;
 
   // We need to initialize whole CSD config explicitly because it's queried
   // as -moz-gtk* media features.
   WidgetNodeType buttonLayout[TOOLBAR_BUTTONS];
 
-  int activeButtons =
-      GetGtkHeaderBarButtonLayout(buttonLayout, TOOLBAR_BUTTONS);
+  int activeButtons = GetGtkHeaderBarButtonLayout(buttonLayout, TOOLBAR_BUTTONS,
+                                                  &mCSDReversedPlacement);
   for (int i = 0; i < activeButtons; i++) {
     switch (buttonLayout[i]) {
       case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE:
         mCSDMinimizeButton = true;
         break;
       case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE:
         mCSDMaximizeButton = true;
         break;
--- a/widget/gtk/nsLookAndFeel.h
+++ b/widget/gtk/nsLookAndFeel.h
@@ -85,16 +85,17 @@ class nsLookAndFeel final : public nsXPL
   char16_t mInvisibleCharacter;
   float mCaretRatio;
   bool mMenuSupportsDrag;
   bool mCSDAvailable;
   bool mCSDHideTitlebarByDefault;
   bool mCSDMaximizeButton;
   bool mCSDMinimizeButton;
   bool mCSDCloseButton;
+  bool mCSDReversedPlacement;
   bool mInitialized;
 
   void EnsureInit();
 
  private:
   nsresult InitCellHighlightColors();
 };
 
--- a/widget/headless/HeadlessLookAndFeelGTK.cpp
+++ b/widget/headless/HeadlessLookAndFeelGTK.cpp
@@ -279,16 +279,19 @@ nsresult HeadlessLookAndFeel::GetIntImpl
       aResult = 0;
       break;
     case eIntID_GTKCSDMaximizeButton:
       aResult = 0;
       break;
     case eIntID_GTKCSDCloseButton:
       aResult = 1;
       break;
+    case eIntID_GTKCSDReversedPlacement:
+      aResult = 0;
+      break;
     default:
       NS_WARNING(
           "HeadlessLookAndFeel::GetIntImpl called with an unrecognized aID");
       aResult = 0;
       res = NS_ERROR_FAILURE;
       break;
   }
   return res;
--- a/widget/nsXPLookAndFeel.cpp
+++ b/widget/nsXPLookAndFeel.cpp
@@ -73,16 +73,17 @@ nsLookAndFeelIntPref nsXPLookAndFeel::sI
     {"ui.GtkCSDAvailable", eIntID_GTKCSDAvailable, false, 0},
     {"ui.GtkCSDHideTitlebarByDefault", eIntID_GTKCSDHideTitlebarByDefault,
      false, 0},
     {"ui.GtkCSDTransparentBackground", eIntID_GTKCSDTransparentBackground,
      false, 0},
     {"ui.GtkCSDMinimizeButton", eIntID_GTKCSDMinimizeButton, false, 0},
     {"ui.GtkCSDMaximizeButton", eIntID_GTKCSDMaximizeButton, false, 0},
     {"ui.GtkCSDCloseButton", eIntID_GTKCSDCloseButton, false, 0},
+    {"ui.GtkCSDReversedPlacement", eIntID_GTKCSDReversedPlacement, false, 0},
     {"ui.systemUsesDarkTheme", eIntID_SystemUsesDarkTheme, false, 0},
     {"ui.prefersReducedMotion", eIntID_PrefersReducedMotion, false, 0},
     {"ui.primaryPointerCapabilities", eIntID_PrimaryPointerCapabilities, false,
      6 /* fine and hover-capable pointer, i.e. mouse-type */},
     {"ui.allPointerCapabilities", eIntID_AllPointerCapabilities, false,
      6 /* fine and hover-capable pointer, i.e. mouse-type */},
 };
 
--- a/xpcom/ds/StaticAtoms.py
+++ b/xpcom/ds/StaticAtoms.py
@@ -2163,16 +2163,17 @@ STATIC_ATOMS = [
     Atom("_moz_is_resource_document", "-moz-is-resource-document"),
     Atom("_moz_swipe_animation_enabled", "-moz-swipe-animation-enabled"),
     Atom("_moz_gtk_csd_available", "-moz-gtk-csd-available"),
     Atom("_moz_gtk_csd_hide_titlebar_by_default", "-moz-gtk-csd-hide-titlebar-by-default"),
     Atom("_moz_gtk_csd_transparent_background", "-moz-gtk-csd-transparent-background"),
     Atom("_moz_gtk_csd_minimize_button", "-moz-gtk-csd-minimize-button"),
     Atom("_moz_gtk_csd_maximize_button", "-moz-gtk-csd-maximize-button"),
     Atom("_moz_gtk_csd_close_button", "-moz-gtk-csd-close-button"),
+    Atom("_moz_gtk_csd_reversed_placement", "-moz-gtk-csd-reversed-placement"),
     Atom("_moz_system_dark_theme", "-moz-system-dark-theme"),
 
     # application commands
     Atom("Back", "Back"),
     Atom("Forward", "Forward"),
     Atom("Reload", "Reload"),
     Atom("Stop", "Stop"),
     Atom("Search", "Search"),