Repository: electron/electron (language: cpp)

Issue:
[Bug]: wayland window decorations mangled and not clickable
### Preflight Checklist

- [X] I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/main/CONTRIBUTING.md) for this project.
- [X] I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/main/CODE_OF_CONDUCT.md) that this project adheres to.
- [X] I have searched the [issue tracker](https://www.github.com/electron/electron/issues) for a bug report that matches the one I want to file, without success.

### Electron Version

19.0.7

### What operating system are you using?

Ubuntu

### Operating System Version

22.04

### What arch are you using?

x64

### Last Known Working Electron version

19.0.0-alpha.3

### Expected Behavior

Running under wayland natively (`--ozone-platform=wayland`) and enabling window decorations (`app.commandLine.appendSwitch('enable-features', 'WaylandWindowDecorations');` in main.js) should show a title bar which is clickable.

Below is from 19.0.0-alpha.3 where it still worked:

![grafik](https://user-images.githubusercontent.com/1392875/177206042-21015934-5a2b-4484-bc5c-111198be4008.png)



### Actual Behavior

Running under wayland natively (`--ozone-platform=wayland`) and enabling window decorations (`app.commandLine.appendSwitch('enable-features', 'WaylandWindowDecorations');` in main.js) shows mangled transparent border on the left side instead of a title bar and is not clickable:

On **electron 19** is broke during the alpha and beta cycle:

Below is from 19.0.0-alpha.4 where it first broke, here the buttons are still clickable:

![grafik](https://user-images.githubusercontent.com/1392875/177205464-db1274be-63dc-4d5d-93e2-14157984966d.png)

Starting with 19.0.0-beta.4 the buttons are also no longer clickable:

![grafik](https://user-images.githubusercontent.com/1392875/177205697-3e1d4362-2cd3-4ceb-a4f1-5ee6ef68643f.png)

With 19.0.0 up and until 19.0.7 the same, broken titlebar and not clickable:

![grafik](https://user-images.githubusercontent.com/1392875/177205907-2ed403b8-df96-4c9e-8292-52092cb4db8b.png)





On **electron 18-x-y** it also broke between 18.2.0 and 18.2.1, but only the buttons cannot be clicked any longer:

18.2.0 still everything ok, titlebar there and clickable:

![grafik](https://user-images.githubusercontent.com/1392875/177205350-a175f92b-5b28-40e3-a108-c05e6b87aa7d.png)

18.2.1: Titlebar still there, but no longer clickable:

![grafik](https://user-images.githubusercontent.com/1392875/177205401-2dafdd0f-a8de-4a39-8cec-e101bcfb74e5.png)

Up and including 18.3.5 the titlebar stays there, but not clickable:

![grafik](https://user-images.githubusercontent.com/1392875/177205824-60381433-c39b-440d-9aa2-a7c62592f642.png)


### Testcase Gist URL

https://gist.github.com/csett86/5d45c47cb1d5331b9ede0788ff5ae284

### Additional Information

To reproduce with the gist, you need to add ```--ozone-platform=wayland``` to the user-provided flags, as I could find a way to add this to the fiddle (the WaylandWindowDecorations are part of the fiddle):

![grafik](https://user-images.githubusercontent.com/1392875/177206134-6371daec-5e63-4127-a344-0bf72ffba907.png)


[Bug]: Window has borders while maximized in Wayland
### Preflight Checklist

- [X] I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/main/CONTRIBUTING.md) for this project.
- [X] I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/main/CODE_OF_CONDUCT.md) that this project adheres to.
- [X] I have searched the [issue tracker](https://www.github.com/electron/electron/issues) for a bug report that matches the one I want to file, without success.

### Electron Version

17.1.0 and 18.0.0-alpha.5

### What operating system are you using?

Other Linux

### Operating System Version

Arch Linux

### What arch are you using?

x64

### Last Known Working Electron version

_No response_

### Expected Behavior

The window should have no borders when maximized.

### Actual Behavior

Window has borders when maximized (at least in GNOME, I have not tested in other interfaces/window managers).

### Testcase Gist URL

_No response_

### Additional Information

To reproduce:
 - Use a Wayland environment.
 - Start the Electron demo app with the flags: `--enable-features=WaylandWindowDecorations --ozone-platform=wayland`.
 - Maximize the window.
 - See that the window isn't really maximized and there are still borders.

Some notes:
 - The issue is not reproducible in Chromium 99.0.4844.51 with the same start flags, so that's probably not a bug in the window manager.
 - When removing the `WaylandWindowDecorations` feature flag, window maximizes correctly under Wayland (though there are no window decorations, so I have to maximize window using keyboard).
 - Maybe CC #29618 author?


![Screenshot](https://user-images.githubusercontent.com/29029632/156944326-4ac421cc-a2c1-4099-bf12-f0af1c29de4a.png)



Discussion / hints:
I also already tried myself if I could spot an obvious commit that broke it on either 18 or 19 between the mentioned releases, but so far had no luck there.
FYI @refi64 
I'll make sure to look into this soon-ish, but it might take a hot minute.
I ran a git bisect. The mangled decorations were introduced by 7c0ed9e8373e1589af8b213f338caa75853e294e … somehow. I double checked too; no mangled decorations in the parent commit, mangled in 7c0ed9e8373e1589af8b213f338caa75853e294e.

I did not test for when clickability broke.
The same problem also appears in weston, the wayland compositor reference implementation (because it also doesn't support server-side decorations).

However, electron works correctly in compositors with server-side decorations (e.g., KDE, sway), because the client-side decorations (that fail to detect maximized state) are disabled there as per https://github.com/electron/electron/pull/29618#issuecomment-858080436
I have this problem too, also enabling CSD with `--enable-features=WaylandWindowDecorations` option with Gnome 41.

`./electron --enable-features=UseOzonePlatform --ozone-platform=wayland --enable-features=WaylandWindowDecorations`
![immagine](https://user-images.githubusercontent.com/20546217/161393696-8742c8e8-6714-43ee-b071-b28d30d0c199.png)


Hi all,
is there any progress on this issue? Actually with gnome (at least 41 and 42) and wayland session, CSD is unusable and without CSD any electron based applications doesn't have window borders and mouse cursor has wrong dimension, sometimes too small, sometimes too large.

Thanks for your work,
ENx
I have this issue too on GNOME 42, is there any fix in progress?
I have recently begun writing my own Wayland compositor for fun and, having now become significantly more familiar with the way the system works, I can say pretty confidently that this is a bug on Electron's part. The compositor can tell a client that it's now maximized, and it's the client's responsibility to update its decorations to match that state. Electron is not doing that, resulting in it simply being resized into a 'maximized' area by the compositor without removing the shadow, which is technically part of the decorations.

Fix patch:
diff --git a/patches/chromium/.patches b/patches/chromium/.patches
index b2a8de2dd1ba0..9548fc29857e7 100644
--- a/patches/chromium/.patches
+++ b/patches/chromium/.patches
@@ -115,3 +115,4 @@ disable_freezing_flags_after_init_in_node.patch
 short-circuit_permissions_checks_in_mediastreamdevicescontroller.patch
 chore_add_electron_deps_to_gitignores.patch
 chore_allow_chromium_to_handle_synthetic_mouse_events_for_touch.patch
+add_maximized_parameter_to_linuxui_getwindowframeprovider.patch
diff --git a/patches/chromium/add_maximized_parameter_to_linuxui_getwindowframeprovider.patch b/patches/chromium/add_maximized_parameter_to_linuxui_getwindowframeprovider.patch
new file mode 100644
index 0000000000000..a7d6873b9ed49
--- /dev/null
+++ b/patches/chromium/add_maximized_parameter_to_linuxui_getwindowframeprovider.patch
@@ -0,0 +1,176 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: msizanoen1 <msizanoen@qtmlabs.xyz>
+Date: Tue, 19 Jul 2022 05:11:06 +0200
+Subject: Add maximized parameter to LinuxUI::GetWindowFrameProvider
+
+This allows ClientFrameViewLinux to instruct the toolkit to draw the window
+decorations in maximized mode where needed, preventing empty space caused
+by decoration shadows and rounded titlebars around the window while maximized.
+
+diff --git a/ui/gtk/gtk_ui.cc b/ui/gtk/gtk_ui.cc
+index 50c01fb42bc1a7350fae4773f1b549355c6cdc25..ab49a67e3b20cd240f532cfbf868533bb1f293d8 100644
+--- a/ui/gtk/gtk_ui.cc
++++ b/ui/gtk/gtk_ui.cc
+@@ -511,13 +511,15 @@ std::unique_ptr<ui::NavButtonProvider> GtkUi::CreateNavButtonProvider() {
+   return nullptr;
+ }
+ 
+-ui::WindowFrameProvider* GtkUi::GetWindowFrameProvider(bool solid_frame) {
++ui::WindowFrameProvider* GtkUi::GetWindowFrameProvider(bool solid_frame, bool maximized) {
+   if (!GtkCheckVersion(3, 14))
+     return nullptr;
+   auto& provider =
+-      solid_frame ? solid_frame_provider_ : transparent_frame_provider_;
++      maximized
++        ? (solid_frame ? solid_maximized_frame_provider_ : transparent_maximized_frame_provider_)
++        : (solid_frame ? solid_frame_provider_ : transparent_frame_provider_);
+   if (!provider)
+-    provider = std::make_unique<gtk::WindowFrameProviderGtk>(solid_frame);
++    provider = std::make_unique<gtk::WindowFrameProviderGtk>(solid_frame, maximized);
+   return provider.get();
+ }
+ 
+diff --git a/ui/gtk/gtk_ui.h b/ui/gtk/gtk_ui.h
+index 5206acf475346f50e0f31b7432d9b93afc3a8ad0..9f2b3cca2337b4528f9451c2fdec7c67021c949f 100644
+--- a/ui/gtk/gtk_ui.h
++++ b/ui/gtk/gtk_ui.h
+@@ -93,7 +93,7 @@ class GtkUi : public ui::LinuxUi {
+   bool PreferDarkTheme() const override;
+   bool AnimationsEnabled() const override;
+   std::unique_ptr<ui::NavButtonProvider> CreateNavButtonProvider() override;
+-  ui::WindowFrameProvider* GetWindowFrameProvider(bool solid_frame) override;
++  ui::WindowFrameProvider* GetWindowFrameProvider(bool solid_frame, bool maximized) override;
+   base::flat_map<std::string, std::string> GetKeyboardLayoutMap() override;
+   std::string GetCursorThemeName() override;
+   int GetCursorThemeSize() override;
+@@ -197,6 +197,8 @@ class GtkUi : public ui::LinuxUi {
+   // while Chrome is running.
+   std::unique_ptr<ui::WindowFrameProvider> solid_frame_provider_;
+   std::unique_ptr<ui::WindowFrameProvider> transparent_frame_provider_;
++  std::unique_ptr<ui::WindowFrameProvider> solid_maximized_frame_provider_;
++  std::unique_ptr<ui::WindowFrameProvider> transparent_maximized_frame_provider_;
+ };
+ 
+ }  // namespace gtk
+diff --git a/ui/gtk/window_frame_provider_gtk.cc b/ui/gtk/window_frame_provider_gtk.cc
+index e4dbdad327eb77994ffd7f068c67336a19897915..d3ae0636455489a7c7443df85cb769952c98aca2 100644
+--- a/ui/gtk/window_frame_provider_gtk.cc
++++ b/ui/gtk/window_frame_provider_gtk.cc
+@@ -38,16 +38,18 @@ std::string GetThemeName() {
+   return theme_string;
+ }
+ 
+-GtkCssContext WindowContext(bool solid_frame, bool focused) {
++GtkCssContext WindowContext(bool solid_frame, bool maximized, bool focused) {
+   std::string selector = "#window.background.";
+   selector += solid_frame ? "solid-csd" : "csd";
++  if (maximized)
++    selector += ".maximized";
+   if (!focused)
+     selector += ":inactive";
+   return AppendCssNodeToStyleContext({}, selector);
+ }
+ 
+-GtkCssContext DecorationContext(bool solid_frame, bool focused) {
+-  auto context = WindowContext(solid_frame, focused);
++GtkCssContext DecorationContext(bool solid_frame, bool maximized, bool focused) {
++  auto context = WindowContext(solid_frame, maximized, focused);
+   // GTK4 renders the decoration directly on the window.
+   if (!GtkCheckVersion(4))
+     context = AppendCssNodeToStyleContext(context, "#decoration");
+@@ -64,8 +66,8 @@ GtkCssContext DecorationContext(bool solid_frame, bool focused) {
+   return context;
+ }
+ 
+-GtkCssContext HeaderContext(bool solid_frame, bool focused) {
+-  auto context = WindowContext(solid_frame, focused);
++GtkCssContext HeaderContext(bool solid_frame, bool maximized, bool focused) {
++  auto context = WindowContext(solid_frame, maximized, focused);
+   context =
+       AppendCssNodeToStyleContext(context, "#headerbar.header-bar.titlebar");
+   if (!focused)
+@@ -110,8 +112,8 @@ int ComputeTopCornerRadius() {
+   // need to experimentally determine the corner radius by rendering a sample.
+   // Additionally, in GTK4, the headerbar corners get clipped by the window
+   // rather than the headerbar having its own rounded corners.
+-  auto context = GtkCheckVersion(4) ? DecorationContext(false, false)
+-                                    : HeaderContext(false, false);
++  auto context = GtkCheckVersion(4) ? DecorationContext(false, false, false)
++                                    : HeaderContext(false, false, false);
+   ApplyCssToContext(context, R"(window, headerbar {
+     background-image: none;
+     background-color: black;
+@@ -169,8 +171,8 @@ void WindowFrameProviderGtk::Asset::CloneFrom(
+   unfocused_bitmap = src.unfocused_bitmap;
+ }
+ 
+-WindowFrameProviderGtk::WindowFrameProviderGtk(bool solid_frame)
+-    : solid_frame_(solid_frame) {}
++WindowFrameProviderGtk::WindowFrameProviderGtk(bool solid_frame, bool maximized)
++    : solid_frame_(solid_frame), maximized_(maximized) {}
+ 
+ WindowFrameProviderGtk::~WindowFrameProviderGtk() = default;
+ 
+@@ -264,7 +266,7 @@ void WindowFrameProviderGtk::PaintWindowFrame(gfx::Canvas* canvas,
+       top_area_height_dip * scale - asset.frame_thickness_px.top();
+ 
+   auto header = PaintHeaderbar({client_bounds_px.width(), top_area_height_px},
+-                               HeaderContext(solid_frame_, focused), scale);
++                               HeaderContext(solid_frame_, maximized_, focused), scale);
+   image = gfx::ImageSkia::CreateFrom1xBitmap(header);
+   // In GTK4, the headerbar gets clipped by the window.
+   if (GtkCheckVersion(4)) {
+@@ -296,7 +298,7 @@ void WindowFrameProviderGtk::MaybeUpdateBitmaps(float scale) {
+ 
+   gfx::Rect frame_bounds_dip(kMaxFrameSizeDip, kMaxFrameSizeDip,
+                              2 * kMaxFrameSizeDip, 2 * kMaxFrameSizeDip);
+-  auto focused_context = DecorationContext(solid_frame_, true);
++  auto focused_context = DecorationContext(solid_frame_, maximized_, true);
+   frame_bounds_dip.Inset(-GtkStyleContextGetPadding(focused_context));
+   frame_bounds_dip.Inset(-GtkStyleContextGetBorder(focused_context));
+   gfx::Size bitmap_size(BitmapSizePx(asset), BitmapSizePx(asset));
+@@ -304,7 +306,7 @@ void WindowFrameProviderGtk::MaybeUpdateBitmaps(float scale) {
+       PaintBitmap(bitmap_size, frame_bounds_dip, focused_context, scale);
+   asset.unfocused_bitmap =
+       PaintBitmap(bitmap_size, frame_bounds_dip,
+-                  DecorationContext(solid_frame_, false), scale);
++                  DecorationContext(solid_frame_, maximized_, false), scale);
+ 
+   // In GTK4, there's no way to obtain the frame thickness from CSS values
+   // directly, so we must determine it experimentally based on the drawn
+diff --git a/ui/gtk/window_frame_provider_gtk.h b/ui/gtk/window_frame_provider_gtk.h
+index da0f7c33a260f8d98a73c126def30944589001df..a2999a6dcea30a9e468f974301b3a4f03dcb8aeb 100644
+--- a/ui/gtk/window_frame_provider_gtk.h
++++ b/ui/gtk/window_frame_provider_gtk.h
+@@ -14,7 +14,7 @@ namespace gtk {
+ 
+ class WindowFrameProviderGtk : public ui::WindowFrameProvider {
+  public:
+-  explicit WindowFrameProviderGtk(bool solid_frame);
++  explicit WindowFrameProviderGtk(bool solid_frame, bool maximized);
+ 
+   WindowFrameProviderGtk(const WindowFrameProviderGtk&) = delete;
+   WindowFrameProviderGtk& operator=(const WindowFrameProviderGtk&) = delete;
+@@ -69,6 +69,9 @@ class WindowFrameProviderGtk : public ui::WindowFrameProvider {
+ 
+   // Cached bitmaps and metrics.  The scale is rounded to percent.
+   base::flat_map<int, Asset> assets_;
++
++  // Whether to draw the window decorations as maximized.
++  bool maximized_;
+ };
+ 
+ }  // namespace gtk
+diff --git a/ui/linux/linux_ui.h b/ui/linux/linux_ui.h
+index 0cd8a0e101de186e11ead65526c7f0abc60c6577..7db580ce04a407877ca4108ec087120cdb9ce2ad 100644
+--- a/ui/linux/linux_ui.h
++++ b/ui/linux/linux_ui.h
+@@ -176,7 +176,7 @@ class COMPONENT_EXPORT(LINUX_UI) LinuxUi
+   // if transparency is unsupported and the frame should be rendered opaque.
+   // The returned object is not owned by the caller and will remain alive until
+   // the process ends.
+-  virtual WindowFrameProvider* GetWindowFrameProvider(bool solid_frame) = 0;
++  virtual WindowFrameProvider* GetWindowFrameProvider(bool solid_frame, bool maximized) = 0;
+ 
+   // Returns a map of KeyboardEvent code to KeyboardEvent key values.
+   virtual base::flat_map<std::string, std::string> GetKeyboardLayoutMap() = 0;
diff --git a/shell/browser/ui/views/client_frame_view_linux.cc b/shell/browser/ui/views/client_frame_view_linux.cc
index 011764e864f96..97905462a45b9 100644
--- a/shell/browser/ui/views/client_frame_view_linux.cc
+++ b/shell/browser/ui/views/client_frame_view_linux.cc
@@ -131,7 +131,7 @@ void ClientFrameViewLinux::Init(NativeWindowViews* window,
   host_supports_client_frame_shadow_ = tree_host->SupportsClientFrameShadow();
 
   frame_provider_ = ui::LinuxUi::instance()->GetWindowFrameProvider(
-      !host_supports_client_frame_shadow_);
+      !host_supports_client_frame_shadow_, frame_->IsMaximized());
 
   UpdateWindowTitle();
 
@@ -202,7 +202,7 @@ gfx::Rect ClientFrameViewLinux::GetBoundsForClientView() const {
   if (!frame_->IsFullscreen()) {
     client_bounds.Inset(GetBorderDecorationInsets());
     client_bounds.Inset(
-        gfx::Insets::TLBR(0, GetTitlebarBounds().height(), 0, 0));
+        gfx::Insets::TLBR(GetTitlebarBounds().height(), 0, 0, 0));
   }
   return client_bounds;
 }
@@ -274,6 +274,9 @@ void ClientFrameViewLinux::Layout() {
     return;
   }
 
+  frame_provider_ = ui::LinuxUi::instance()->GetWindowFrameProvider(
+      !host_supports_client_frame_shadow_, frame_->IsMaximized());
+
   UpdateButtonImages();
   LayoutButtons();
 
@@ -485,4 +488,9 @@ gfx::Size ClientFrameViewLinux::SizeWithDecorations(gfx::Size size) const {
   return size;
 }
 
+views::View* ClientFrameViewLinux::TargetForRect(views::View* root,
+                                                 const gfx::Rect& rect) {
+  return views::NonClientFrameView::TargetForRect(root, rect);
+}
+
 }  // namespace electron
diff --git a/shell/browser/ui/views/client_frame_view_linux.h b/shell/browser/ui/views/client_frame_view_linux.h
index c76512465e023..cd0acf7b8af07 100644
--- a/shell/browser/ui/views/client_frame_view_linux.h
+++ b/shell/browser/ui/views/client_frame_view_linux.h
@@ -67,6 +67,9 @@ class ClientFrameViewLinux : public FramelessView,
   void OnPaint(gfx::Canvas* canvas) override;
   const char* GetClassName() const override;
 
+  // Overriden from views::ViewTargeterDelegate
+  views::View* TargetForRect(views::View* root, const gfx::Rect& rect) override;
+
  private:
   static constexpr int kNavButtonCount = 4;
 


Test patch: