Repository: electron/electron
Pull request: fix: CSD window frame tiles properly on Wayland

#### Description of Change

<!--
Thank you for your Pull Request. Please provide a description above and review
the requirements below.

Contributors guide: https://github.com/electron/electron/blob/main/CONTRIBUTING.md
-->

On Wayland compositors that use CSD (GNOME), tiled windows had a non-tiled appearance with rounded corners and shadows on all sides, and they showed resize handles on all edges.

Reasons for the issue:

1. A bug in the GNOME compositor on Ubuntu reports all edges as tiled even if only some are. (This also affected Chrome: https://issues.chromium.org/issues/454083284.)
2. The CSD frame was applying positive insets on all sides of the frame, when only the visible edges should have insets when tiled/maximized. 

Some issues with tiling and resize handles were addressed previously in https://github.com/electron/electron/pull/46155. This PR fully restores the correct tiled appearance and behaviours without regressing those fixes.

Relates to https://github.com/electron/electron/issues/45916. This PR fixes tiling resize handles for CSD windows, but not (yet) for fully frameless windows (frame: false/WCO) because this technique requires a GTK client frame. This solution will however also apply to frameless windows when they get a client frame + shadows.

**Before:**

<img width="1081" height="889" alt="image" src="https://github.com/user-attachments/assets/3ca3937e-4fc6-4900-b00e-79c3095e52bc" />


**After:**

<img width="1081" height="889" alt="image" src="https://github.com/user-attachments/assets/a435b04b-21af-4b15-8c3b-d0e9238e484a" />

#### Checklist
<!-- Remove items that do not apply. For completed items, change [ ] to [x]. -->

- [ ] PR description included and stakeholders cc'd
- [ ] `npm test` passes
- [ ] tests are [changed or added](https://github.com/electron/electron/blob/main/docs/development/testing.md)
- [ ] relevant API documentation, tutorials, and examples are updated and follow the [documentation style guide](https://github.com/electron/electron/blob/main/docs/development/style-guide.md)
- [ ] [PR release notes](https://github.com/electron/clerk/blob/main/README.md) describe the change in a way relevant to app developers, and are [capitalized, punctuated, and past tense](https://github.com/electron/clerk/blob/main/README.md#examples).

#### Release Notes

Notes: Corrected the appearance of tiled windows on GNOME (when frame: true), and removed resize handles from tiled edges.

Changed files:
filename: shell/browser/ui/electron_desktop_window_tree_host_linux.cc
patch: @@ -100,13 +100,19 @@ void ElectronDesktopWindowTreeHostLinux::OnWindowStateChanged(
 void ElectronDesktopWindowTreeHostLinux::OnWindowTiledStateChanged(
     ui::WindowTiledEdges new_tiled_edges) {
   if (auto* const view = native_window_view_->GetClientFrameViewLinux()) {
-    bool maximized = new_tiled_edges.top && new_tiled_edges.left &&
-                     new_tiled_edges.bottom && new_tiled_edges.right;
+    // GNOME on Ubuntu reports all edges as tiled
+    // even if the window is only half-tiled so do not trust individual edge
+    // values.
+    bool maximized = native_window_view_->IsMaximized();
     bool tiled = new_tiled_edges.top || new_tiled_edges.left ||
                  new_tiled_edges.bottom || new_tiled_edges.right;
     view->set_tiled(tiled && !maximized);
   }
   UpdateFrameHints();
+  ScheduleRelayout();
+  if (GetWidget()->non_client_view()) {
+    GetWidget()->non_client_view()->SchedulePaint();
+  }
 }
 
 void ElectronDesktopWindowTreeHostLinux::UpdateWindowState(

filename: shell/browser/ui/views/client_frame_view_linux.cc
patch: @@ -41,7 +41,6 @@ namespace {
 
 // These values should be the same as Chromium uses.
 constexpr int kResizeBorder = 10;
-constexpr int kResizeInsideBoundsSize = 5;
 
 ui::NavButtonProvider::ButtonState ButtonStateToNavButtonProviderState(
     views::Button::ButtonState state) {
@@ -152,8 +151,19 @@ gfx::Insets ClientFrameViewLinux::RestoredMirroredFrameBorderInsets() const {
 
 gfx::Insets ClientFrameViewLinux::RestoredFrameBorderInsets() const {
   gfx::Insets insets = GetFrameProvider()->GetFrameThicknessDip();
-  insets.SetToMax(GetInputInsets());
-  return insets;
+  const gfx::Insets input = GetInputInsets();
+
+  auto expand_if_visible = [](int side_thickness, int min_band) {
+    return side_thickness > 0 ? std::max(side_thickness, min_band) : 0;
+  };
+
+  gfx::Insets merged;
+  merged.set_top(expand_if_visible(insets.top(), input.top()));
+  merged.set_left(expand_if_visible(insets.left(), input.left()));
+  merged.set_bottom(expand_if_visible(insets.bottom(), input.bottom()));
+  merged.set_right(expand_if_visible(insets.right(), input.right()));
+
+  return merged;
 }
 
 gfx::Insets ClientFrameViewLinux::GetInputInsets() const {
@@ -198,9 +208,7 @@ void ClientFrameViewLinux::OnWindowButtonOrderingChange() {
 }
 
 int ClientFrameViewLinux::ResizingBorderHitTest(const gfx::Point& point) {
-  return ResizingBorderHitTestImpl(point,
-                                   RestoredMirroredFrameBorderInsets() +
-                                       gfx::Insets(kResizeInsideBoundsSize));
+  return ResizingBorderHitTestImpl(point, RestoredMirroredFrameBorderInsets());
 }
 
 gfx::Rect ClientFrameViewLinux::GetBoundsForClientView() const {