Bug 1521012 - [Linux/GNOME] Use X shape mask to draw transparent corners when we draw to titlebar, r=lsalzman
authorMartin Stransky <stransky@redhat.com>
Wed, 23 Jan 2019 12:36:25 +0000
changeset 515111 64d38928ff5a59fb8214a0ee2472dccd6f16705d
parent 515110 1c227ac918eb1ab0b702d4424603faeb836f2b1c
child 515112 c997411e8d31c1b407712279582fe53fbe2d5f95
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)
reviewerslsalzman
bugs1521012, 1516224
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 1521012 - [Linux/GNOME] Use X shape mask to draw transparent corners when we draw to titlebar, r=lsalzman To support rounded corners of Gtk+ titlebar themes (Adwaita, Radiance..) in GNOME we need to use X shape mask as fully transparent toplevel window causes various issues (like Bug 1516224). We draw mShell as transparent and mContainer as non-transparent with shape mask applied. The shape mask is generated only when titlebar rendering is enabled and it's generated from GtkHeaderBar Widget to match the exact look. We use existing mTransparencyBitmap for the shape mask where mTransparencyBitmapForTitlebar controls whether it's a general shape mask or our specialised shape for titlebar only. This is already enabled for GNOME environment by default. So there's a new preference widget.default-hidden-titlebar added to easily disable it if any issue appears during testing. Differential Revision: https://phabricator.services.mozilla.com/D17283
modules/libpref/init/all.js
widget/gtk/mozcontainer.cpp
widget/gtk/mozcontainer.h
widget/gtk/nsWindow.cpp
widget/gtk/nsWindow.h
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5990,8 +5990,12 @@ pref("dom.datatransfer.mozAtAPIs", true)
 // Whether or not Prio is supported on this platform.
 #ifdef MOZ_LIBPRIO
 pref("prio.enabled", false);
 #endif
 
 // External.AddSearchProvider is deprecated and it will be removed in the next
 // cycles.
 pref("dom.sidebar.enabled", true);
+
+#if defined(MOZ_WIDGET_GTK)
+pref("widget.default-hidden-titlebar", true);
+#endif
--- a/widget/gtk/mozcontainer.cpp
+++ b/widget/gtk/mozcontainer.cpp
@@ -307,18 +307,22 @@ void moz_container_realize(GtkWidget *wi
 
     gtk_widget_get_allocation(widget, &allocation);
     attributes.event_mask = gtk_widget_get_events(widget);
     attributes.x = allocation.x;
     attributes.y = allocation.y;
     attributes.width = allocation.width;
     attributes.height = allocation.height;
     attributes.wclass = GDK_INPUT_OUTPUT;
-    attributes.visual = gtk_widget_get_visual(widget);
     attributes.window_type = GDK_WINDOW_CHILD;
+    MozContainer *container = MOZ_CONTAINER(widget);
+    attributes.visual =
+        container->force_default_visual
+            ? gdk_screen_get_system_visual(gtk_widget_get_screen(widget))
+            : gtk_widget_get_visual(widget);
 
     window = gdk_window_new(parent, &attributes, attributes_mask);
     gdk_window_set_user_data(window, widget);
   } else {
     window = parent;
     g_object_ref(window);
   }
 
@@ -544,8 +548,12 @@ gboolean moz_container_has_wl_egl_window
 }
 
 gboolean moz_container_surface_needs_clear(MozContainer *container) {
   gboolean state = container->surface_needs_clear;
   container->surface_needs_clear = false;
   return state;
 }
 #endif
+
+void moz_container_force_default_visual(MozContainer *container) {
+  container->force_default_visual = true;
+}
--- a/widget/gtk/mozcontainer.h
+++ b/widget/gtk/mozcontainer.h
@@ -74,26 +74,28 @@ struct _MozContainer {
 #ifdef MOZ_WAYLAND
   struct wl_surface *surface;
   struct wl_subsurface *subsurface;
   struct wl_egl_window *eglwindow;
   struct wl_callback *frame_callback_handler;
   gboolean surface_needs_clear;
   gboolean ready_to_draw;
 #endif
+  gboolean force_default_visual;
 };
 
 struct _MozContainerClass {
   GtkContainerClass parent_class;
 };
 
 GType moz_container_get_type(void);
 GtkWidget *moz_container_new(void);
 void moz_container_put(MozContainer *container, GtkWidget *child_widget, gint x,
                        gint y);
+void moz_container_force_default_visual(MozContainer *container);
 
 #ifdef MOZ_WAYLAND
 struct wl_surface *moz_container_get_wl_surface(MozContainer *container);
 struct wl_egl_window *moz_container_get_wl_egl_window(MozContainer *container);
 
 gboolean moz_container_has_wl_egl_window(MozContainer *container);
 gboolean moz_container_surface_needs_clear(MozContainer *container);
 void moz_container_scale_changed(MozContainer *container,
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -420,16 +420,17 @@ nsWindow::nsWindow() {
   mLastMotionPressure = 0;
 
 #ifdef ACCESSIBILITY
   mRootAccessible = nullptr;
 #endif
 
   mIsTransparent = false;
   mTransparencyBitmap = nullptr;
+  mTransparencyBitmapForTitlebar = false;
 
   mTransparencyBitmapWidth = 0;
   mTransparencyBitmapHeight = 0;
 
 #if GTK_CHECK_VERSION(3, 4, 0)
   mLastScrollEventTime = GDK_CURRENT_TIME;
 #endif
   mPendingConfigures = 0;
@@ -1920,22 +1921,27 @@ gboolean nsWindow::OnExposeEvent(cairo_t
 
   // Our bounds may have changed after calling WillPaintWindow.  Clip
   // to the new bounds here.  The region is relative to this
   // window.
   region.And(region, LayoutDeviceIntRect(0, 0, mBounds.width, mBounds.height));
 
   bool shaped = false;
   if (eTransparencyTransparent == GetTransparencyMode()) {
-    if (mHasAlphaVisual) {
-      // Remove possible shape mask from when window manger was not
-      // previously compositing.
-      static_cast<nsWindow *>(GetTopLevelWidget())->ClearTransparencyBitmap();
+    if (mTransparencyBitmapForTitlebar) {
+      static_cast<nsWindow *>(GetTopLevelWidget())
+          ->UpdateTitlebarTransparencyBitmap();
     } else {
-      shaped = true;
+      if (mHasAlphaVisual) {
+        // Remove possible shape mask from when window manger was not
+        // previously compositing.
+        static_cast<nsWindow *>(GetTopLevelWidget())->ClearTransparencyBitmap();
+      } else {
+        shaped = true;
+      }
     }
   }
 
   if (!shaped) {
     GList *children = gdk_window_peek_children(mGdkWindow);
     while (children) {
       GdkWindow *gdkWin = GDK_WINDOW(children->data);
       nsWindow *kid = get_window_for_gdk_window(gdkWin);
@@ -3260,25 +3266,34 @@ nsresult nsWindow::Create(nsIWidget *aPa
       Unused << gfxPlatform::GetPlatform();
 
       bool useWebRender =
           gfx::gfxVars::UseWebRender() && AllowWebRenderForThisWindow();
 
       bool shouldAccelerate = ComputeShouldAccelerate();
       MOZ_ASSERT(shouldAccelerate | !useWebRender);
 
-      // Some Gtk+ themes use non-rectangular toplevel windows. To fully support
-      // such themes we need to make toplevel window transparent with ARGB
-      // visual. It may cause performanance issue so make it configurable and
-      // enable it by default for selected window managers. Also disable it for
-      // X11 SW rendering (Bug 1516224) by default.
-      if (mWindowType == eWindowType_toplevel &&
-          (shouldAccelerate || !mIsX11Display ||
-           Preferences::HasUserValue("mozilla.widget.use-argb-visuals"))) {
+      if (mWindowType == eWindowType_toplevel) {
+        // We enable titlebar rendering for toplevel windows only.
+        mCSDSupportLevel = GetSystemCSDSupportLevel();
+
+        // Some Gtk+ themes use non-rectangular toplevel windows. To fully
+        // support such themes we need to make toplevel window transparent
+        // with ARGB visual.
+        // It may cause performanance issue so make it configurable
+        // and enable it by default for selected window managers.
         needsAlphaVisual = TopLevelWindowUseARGBVisual();
+        if (needsAlphaVisual && mIsX11Display && !shouldAccelerate) {
+          // We want to draw a transparent titlebar but we can't use
+          // ARGB visual due to Bug 1516224.
+          // We use ARGB visual for mShell only and shape mask
+          // for mContainer where is all our content drawn.
+          mTransparencyBitmapForTitlebar = true;
+          mCSDSupportLevel = CSD_SUPPORT_CLIENT;
+        }
       }
 
       bool isSetVisual = false;
       // If using WebRender on X11, we need to select a visual with a depth
       // buffer, as well as an alpha channel if transparency is requested. This
       // must be done before the widget is realized.
 
       // Use GL/WebRender compatible visual only when it is necessary, since
@@ -3399,19 +3414,16 @@ nsresult nsWindow::Create(nsIWidget *aPa
         SetDefaultIcon();
         gtk_window_set_wmclass(GTK_WINDOW(mShell), "Toplevel",
                                gdk_get_program_class());
 
         // each toplevel window gets its own window group
         GtkWindowGroup *group = gtk_window_group_new();
         gtk_window_group_add_window(group, GTK_WINDOW(mShell));
         g_object_unref(group);
-
-        // We enable titlebar rendering for toplevel windows only.
-        mCSDSupportLevel = GetSystemCSDSupportLevel();
       }
 
       // Create a container to hold child windows and child GtkWidgets.
       GtkWidget *container = moz_container_new();
       mContainer = MOZ_CONTAINER(container);
 
       // "csd" style is set when widget is realized so we need to call
       // it explicitly now.
@@ -3437,16 +3449,19 @@ nsresult nsWindow::Create(nsIWidget *aPa
       // Prevent GtkWindow from painting a background to avoid flickering.
       gtk_widget_set_app_paintable(eventWidget, TRUE);
 
       gtk_widget_add_events(eventWidget, kEvents);
       if (drawToContainer) {
         gtk_widget_add_events(mShell, GDK_PROPERTY_CHANGE_MASK);
         gtk_widget_set_app_paintable(mShell, TRUE);
       }
+      if (mTransparencyBitmapForTitlebar) {
+        moz_container_force_default_visual(mContainer);
+      }
 
       // If we draw to mContainer window then configure it now because
       // gtk_container_add() realizes the child widget.
       gtk_widget_set_has_window(container, drawToContainer);
 
       gtk_container_add(GTK_CONTAINER(mShell), container);
       gtk_widget_realize(container);
 
@@ -4290,16 +4305,18 @@ nsresult nsWindow::UpdateTranslucentWind
     nsWindow *topWindow = get_window_for_gtk_widget(topWidget);
     if (!topWindow) return NS_ERROR_FAILURE;
 
     return topWindow->UpdateTranslucentWindowAlphaInternal(aRect, aAlphas,
                                                            aStride);
   }
 
   NS_ASSERTION(mIsTransparent, "Window is not transparent");
+  NS_ASSERTION(!mTransparencyBitmapForTitlebar,
+               "Transparency bitmap is already used for titlebar rendering");
 
   if (mTransparencyBitmap == nullptr) {
     int32_t size = GetBitmapStride(mBounds.width) * mBounds.height;
     mTransparencyBitmap = new gchar[size];
     memset(mTransparencyBitmap, 255, size);
     mTransparencyBitmapWidth = mBounds.width;
     mTransparencyBitmapHeight = mBounds.height;
   } else {
@@ -4319,16 +4336,101 @@ nsresult nsWindow::UpdateTranslucentWind
                  aAlphas, aStride);
 
   if (!mNeedsShow) {
     ApplyTransparencyBitmap();
   }
   return NS_OK;
 }
 
+// We need to shape only a few pixels of the titlebar as we care about
+// the corners only
+#define TITLEBAR_SHAPE_MASK_HEIGHT 10
+
+void nsWindow::UpdateTitlebarTransparencyBitmap() {
+  NS_ASSERTION(mTransparencyBitmapForTitlebar,
+               "Transparency bitmap is already used to draw window shape");
+
+  if (!mDrawInTitlebar || (mBounds.width == mTransparencyBitmapWidth &&
+                           mBounds.height == mTransparencyBitmapHeight)) {
+    return;
+  }
+
+  bool maskCreate =
+      !mTransparencyBitmap || mBounds.width > mTransparencyBitmapWidth;
+
+  bool maskUpdate =
+      !mTransparencyBitmap || mBounds.width != mTransparencyBitmapWidth;
+
+  if (maskCreate) {
+    if (mTransparencyBitmap) {
+      delete[] mTransparencyBitmap;
+    }
+    int32_t size = GetBitmapStride(mBounds.width) * TITLEBAR_SHAPE_MASK_HEIGHT;
+    mTransparencyBitmap = new gchar[size];
+    mTransparencyBitmapWidth = mBounds.width;
+  } else {
+    mTransparencyBitmapWidth = mBounds.width;
+  }
+  mTransparencyBitmapHeight = mBounds.height;
+
+  if (maskUpdate) {
+    cairo_surface_t *surface = cairo_image_surface_create(
+        CAIRO_FORMAT_A8, mTransparencyBitmapWidth, TITLEBAR_SHAPE_MASK_HEIGHT);
+    if (!surface) return;
+
+    cairo_t *cr = cairo_create(surface);
+
+    GtkWidgetState state;
+    memset((void *)&state, 0, sizeof(state));
+    GdkRectangle rect = {0, 0, mTransparencyBitmapWidth,
+                         TITLEBAR_SHAPE_MASK_HEIGHT};
+
+    moz_gtk_widget_paint(MOZ_GTK_HEADER_BAR, cr, &rect, &state, 0,
+                         GTK_TEXT_DIR_NONE);
+
+    cairo_destroy(cr);
+    cairo_surface_mark_dirty(surface);
+    cairo_surface_flush(surface);
+
+    UpdateMaskBits(
+        mTransparencyBitmap, mTransparencyBitmapWidth,
+        TITLEBAR_SHAPE_MASK_HEIGHT,
+        nsIntRect(0, 0, mTransparencyBitmapWidth, TITLEBAR_SHAPE_MASK_HEIGHT),
+        cairo_image_surface_get_data(surface),
+        cairo_format_stride_for_width(CAIRO_FORMAT_A8,
+                                      mTransparencyBitmapWidth));
+
+    cairo_surface_destroy(surface);
+  }
+
+  if (!mNeedsShow) {
+    Display *xDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow);
+    Window xDrawable = GDK_WINDOW_XID(mGdkWindow);
+
+    Pixmap maskPixmap = XCreateBitmapFromData(
+        xDisplay, xDrawable, mTransparencyBitmap, mTransparencyBitmapWidth,
+        TITLEBAR_SHAPE_MASK_HEIGHT);
+
+    XShapeCombineMask(xDisplay, xDrawable, ShapeBounding, 0, 0, maskPixmap,
+                      ShapeSet);
+
+    if (mTransparencyBitmapHeight > TITLEBAR_SHAPE_MASK_HEIGHT) {
+      XRectangle rect = {0, 0, (unsigned short)mTransparencyBitmapWidth,
+                         (unsigned short)(mTransparencyBitmapHeight -
+                                          TITLEBAR_SHAPE_MASK_HEIGHT)};
+      XShapeCombineRectangles(xDisplay, xDrawable, ShapeBounding, 0,
+                              TITLEBAR_SHAPE_MASK_HEIGHT, &rect, 1, ShapeUnion,
+                              0);
+    }
+
+    XFreePixmap(xDisplay, maskPixmap);
+  }
+}
+
 void nsWindow::GrabPointer(guint32 aTime) {
   LOG(("GrabPointer time=0x%08x retry=%d\n", (unsigned int)aTime,
        mRetryPointerGrab));
 
   mRetryPointerGrab = false;
   sRetryGrabTime = aTime;
 
   // If the window isn't visible, just set the flag to retry the
@@ -6022,16 +6124,24 @@ void nsWindow::SetDrawsInTitlebar(bool a
     if (aState) {
       UpdateClientOffsetForCSDWindow();
     }
 
     gtk_widget_destroy(tmpWindow);
   }
 
   mDrawInTitlebar = aState;
+
+  if (mTransparencyBitmapForTitlebar) {
+    if (mDrawInTitlebar) {
+      UpdateTitlebarTransparencyBitmap();
+    } else {
+      ClearTransparencyBitmap();
+    }
+  }
 }
 
 gint nsWindow::GdkScaleFactor() {
   // Available as of GTK 3.10+
   static auto sGdkWindowGetScaleFactorPtr =
       (gint(*)(GdkWindow *))dlsym(RTLD_DEFAULT, "gdk_window_get_scale_factor");
   if (sGdkWindowGetScaleFactorPtr && mGdkWindow)
     return (*sGdkWindowGetScaleFactorPtr)(mGdkWindow);
@@ -6362,16 +6472,21 @@ nsWindow::CSDSupportLevel nsWindow::GetS
 }
 
 bool nsWindow::HideTitlebarByDefault() {
   static int hideTitlebar = -1;
   if (hideTitlebar != -1) {
     return hideTitlebar;
   }
 
+  if (!Preferences::GetBool("widget.default-hidden-titlebar", false)) {
+    hideTitlebar = false;
+    return hideTitlebar;
+  }
+
   const char *currentDesktop = getenv("XDG_CURRENT_DESKTOP");
   hideTitlebar =
       (currentDesktop && GetSystemCSDSupportLevel() != CSD_SUPPORT_NONE);
 
   if (hideTitlebar) {
     hideTitlebar =
         (strstr(currentDesktop, "GNOME-Flashback:GNOME") != nullptr ||
          strstr(currentDesktop, "GNOME") != nullptr);
@@ -6381,16 +6496,21 @@ bool nsWindow::HideTitlebarByDefault() {
 }
 
 bool nsWindow::TopLevelWindowUseARGBVisual() {
   static int useARGBVisual = -1;
   if (useARGBVisual != -1) {
     return useARGBVisual;
   }
 
+  GdkScreen *screen = gdk_screen_get_default();
+  if (!gdk_screen_is_composited(screen)) {
+    useARGBVisual = false;
+  }
+
   if (Preferences::HasUserValue("mozilla.widget.use-argb-visuals")) {
     useARGBVisual =
         Preferences::GetBool("mozilla.widget.use-argb-visuals", false);
   } else {
     const char *currentDesktop = getenv("XDG_CURRENT_DESKTOP");
     useARGBVisual =
         (currentDesktop && GetSystemCSDSupportLevel() != CSD_SUPPORT_NONE);
 
--- a/widget/gtk/nsWindow.h
+++ b/widget/gtk/nsWindow.h
@@ -303,16 +303,17 @@ class nsWindow final : public nsBaseWidg
   virtual nsTransparencyMode GetTransparencyMode() override;
   virtual void UpdateOpaqueRegion(
       const LayoutDeviceIntRegion& aOpaqueRegion) override;
   virtual nsresult ConfigureChildren(
       const nsTArray<Configuration>& aConfigurations) override;
   nsresult UpdateTranslucentWindowAlphaInternal(const nsIntRect& aRect,
                                                 uint8_t* aAlphas,
                                                 int32_t aStride);
+  void UpdateTitlebarTransparencyBitmap();
 
   virtual void ReparentNativeWidget(nsIWidget* aNewParent) override;
 
   virtual nsresult SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
                                               uint32_t aNativeMessage,
                                               uint32_t aModifierFlags,
                                               nsIObserver* aObserver) override;
 
@@ -469,19 +470,16 @@ class nsWindow final : public nsBaseWidg
   MozContainer* mContainer;
   GdkWindow* mGdkWindow;
   bool mWindowShouldStartDragging = false;
   PlatformCompositorWidgetDelegate* mCompositorWidgetDelegate;
 
   uint32_t mHasMappedToplevel : 1, mIsFullyObscured : 1, mRetryPointerGrab : 1;
   nsSizeMode mSizeState;
 
-  int32_t mTransparencyBitmapWidth;
-  int32_t mTransparencyBitmapHeight;
-
   nsIntPoint mClientOffset;
 
 #if GTK_CHECK_VERSION(3, 4, 0)
   // This field omits duplicate scroll events caused by GNOME bug 726878.
   guint32 mLastScrollEventTime;
 
   // for touch event handling
   nsRefPtrHashtable<nsPtrHashKey<GdkEventSequence>, mozilla::dom::Touch>
@@ -560,16 +558,22 @@ class nsWindow final : public nsBaseWidg
   static GdkCursor* gsGtkCursorCache[eCursorCount];
 
   // Transparency
   bool mIsTransparent;
   // This bitmap tracks which pixels are transparent. We don't support
   // full translucency at this time; each pixel is either fully opaque
   // or fully transparent.
   gchar* mTransparencyBitmap;
+  int32_t mTransparencyBitmapWidth;
+  int32_t mTransparencyBitmapHeight;
+  // The transparency bitmap is used instead of ARGB visual for toplevel
+  // window to draw titlebar.
+  bool mTransparencyBitmapForTitlebar;
+
   // True when we're on compositing window manager and this
   // window is using visual with alpha channel.
   bool mHasAlphaVisual;
 
   // all of our DND stuff
   void InitDragEvent(mozilla::WidgetDragEvent& aEvent);
 
   float mLastMotionPressure;