--- 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;