Repository: electron/electron
Pull request: fix: window maximizing with Mica

#### Description of Change

Closes https://github.com/electron/electron/issues/41824
Depends on https://github.com/electron/electron/pull/45422.
Refs https://github.com/electron/electron/pull/39708.

This PR mitigates an issue where the window lost its `backgroundMaterial` effects on maximization - turning black and losing its rounded corners. This happened in part because we made changes in an earlier PR to set transparency for correct Mica/Acrylic visuals, but didn't take into account that we'd then need to treat it in the special ways we treat transparent windows on Windows beyond that. This PR addresses those issues by expanding transparent handling to include `backgroundMaterial` translucency.

This PR comes at cost of losing the smooth window animation, which is something I'm hopeful i can fix as a follow up. Our stock transparent window also has this issue.

#### Checklist
<!-- Remove items that do not apply. For completed items, change [ ] to [x]. -->

- [x] PR description included and stakeholders cc'd
- [x] `npm test` passes
- [x] [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: Fixed an issue where windows on Windows with `backgroundMaterial` lost effect on maximization.


Changed files:
filename: shell/browser/native_window_views.cc
patch: @@ -642,8 +642,22 @@ void NativeWindowViews::SetEnabledInternal(bool enable) {
 #endif
 }
 
-#if BUILDFLAG(IS_LINUX)
 void NativeWindowViews::Maximize() {
+#if BUILDFLAG(IS_WIN)
+  if (IsTranslucent()) {
+    // If a window is translucent but not transparent on Windows,
+    // that means it must have a backgroundMaterial set.
+    if (!transparent())
+      SetRoundedCorners(false);
+    restore_bounds_ = GetBounds();
+    auto display = display::Screen::GetScreen()->GetDisplayNearestWindow(
+        GetNativeWindow());
+    SetBounds(display.work_area(), false);
+    NotifyWindowMaximize();
+    return;
+  }
+#endif
+
   if (IsVisible()) {
     widget()->Maximize();
   } else {
@@ -652,44 +666,46 @@ void NativeWindowViews::Maximize() {
     NotifyWindowShow();
   }
 }
-#endif
 
 void NativeWindowViews::Unmaximize() {
-  if (IsMaximized()) {
+  if (!IsMaximized())
+    return;
+
 #if BUILDFLAG(IS_WIN)
+  if (IsTranslucent()) {
+    SetBounds(restore_bounds_, false);
+    NotifyWindowUnmaximize();
     if (transparent()) {
-      SetBounds(restore_bounds_, false);
-      NotifyWindowUnmaximize();
       UpdateThickFrame();
-      return;
+    } else {
+      SetRoundedCorners(true);
     }
+    return;
+  }
 #endif
 
-    widget()->Restore();
+  widget()->Restore();
 
 #if BUILDFLAG(IS_WIN)
-    UpdateThickFrame();
+  UpdateThickFrame();
 #endif
-  }
 }
 
 bool NativeWindowViews::IsMaximized() const {
-  if (widget()->IsMaximized()) {
+  if (widget()->IsMaximized())
     return true;
-  } else {
+
 #if BUILDFLAG(IS_WIN)
-    if (transparent() && !IsMinimized()) {
-      // Compare the size of the window with the size of the display
-      auto display = display::Screen::GetScreen()->GetDisplayNearestWindow(
-          GetNativeWindow());
-      // Maximized if the window is the same dimensions and placement as the
-      // display
-      return GetBounds() == display.work_area();
-    }
+  if (IsTranslucent() && !IsMinimized()) {
+    // If the window is the same dimensions and placement as the
+    // display, we consider it maximized.
+    auto display = display::Screen::GetScreen()->GetDisplayNearestWindow(
+        GetNativeWindow());
+    return GetBounds() == display.work_area();
+  }
 #endif
 
-    return false;
-  }
+  return false;
 }
 
 void NativeWindowViews::Minimize() {
@@ -702,10 +718,14 @@ void NativeWindowViews::Minimize() {
 
 void NativeWindowViews::Restore() {
 #if BUILDFLAG(IS_WIN)
-  if (IsMaximized() && transparent()) {
+  if (IsMaximized() && IsTranslucent()) {
     SetBounds(restore_bounds_, false);
     NotifyWindowRestore();
-    UpdateThickFrame();
+    if (transparent()) {
+      UpdateThickFrame();
+    } else {
+      SetRoundedCorners(true);
+    }
     return;
   }
 #endif
@@ -845,7 +865,7 @@ gfx::Size NativeWindowViews::GetContentSize() const {
 
 gfx::Rect NativeWindowViews::GetNormalBounds() const {
 #if BUILDFLAG(IS_WIN)
-  if (IsMaximized() && transparent())
+  if (IsMaximized() && IsTranslucent())
     return restore_bounds_;
 #endif
   return widget()->GetRestoredBounds();
@@ -860,8 +880,8 @@ void NativeWindowViews::SetContentSizeConstraints(
   if (!thick_frame_)
     return;
 #endif
-  // widget_delegate() is only available after Init() is called, we make use of
-  // this to determine whether native widget has initialized.
+  // widget_delegate() is only available after Init() is called, we make use
+  // of this to determine whether native widget has initialized.
   if (widget() && widget()->widget_delegate())
     widget()->OnSizeConstraintsChanged();
   if (resizable_)
@@ -1639,9 +1659,9 @@ void NativeWindowViews::UpdateThickFrame() {
     return;
 
   if (IsMaximized() && !transparent()) {
-    // For maximized window add thick frame always, otherwise it will be removed
-    // in HWNDMessageHandler::SizeConstraintsChanged() which will result in
-    // maximized window bounds change.
+    // For maximized window add thick frame always, otherwise it will be
+    // removed in HWNDMessageHandler::SizeConstraintsChanged() which will
+    // result in maximized window bounds change.
     FlipWindowStyle(GetAcceleratedWidget(), true, WS_THICKFRAME);
   } else if (has_frame()) {
     FlipWindowStyle(GetAcceleratedWidget(), resizable_, WS_THICKFRAME);
@@ -1679,10 +1699,10 @@ void NativeWindowViews::OnWidgetBoundsChanged(views::Widget* changed_widget,
   // WidgetObserver::OnWidgetBoundsChanged is being called from
   // Widget::OnNativeWidgetMove() and not Widget::OnNativeWidgetSizeChanged.
   // |GetWindowBoundsInScreen| has a ~1 pixel margin
-  // of error because it converts from floats to integers between calculations,
-  // so if we check existing bounds directly against the new bounds without
-  // accounting for that we'll have constant false positives when the window is
-  // moving but the user hasn't changed its size at all.
+  // of error because it converts from floats to integers between
+  // calculations, so if we check existing bounds directly against the new
+  // bounds without accounting for that we'll have constant false positives
+  // when the window is moving but the user hasn't changed its size at all.
   auto isWithinOnePixel = [](gfx::Size old_size, gfx::Size new_size) -> bool {
     return base::IsApproximatelyEqual(old_size.width(), new_size.width(), 1) &&
            base::IsApproximatelyEqual(old_size.height(), new_size.height(), 1);
@@ -1807,8 +1827,8 @@ void NativeWindowViews::OnMouseEvent(ui::MouseEvent* event) {
 ui::mojom::WindowShowState NativeWindowViews::GetRestoredState() {
   if (IsMaximized()) {
 #if BUILDFLAG(IS_WIN)
-    // Only restore Maximized state when window is NOT transparent style
-    if (!transparent()) {
+    // Restore maximized state for windows that are not translucent.
+    if (!IsTranslucent()) {
       return ui::mojom::WindowShowState::kMaximized;
     }
 #else

filename: shell/browser/native_window_views.h
patch: @@ -202,11 +202,9 @@ class NativeWindowViews : public NativeWindow,
   void OnWidgetMove() override;
 #if BUILDFLAG(IS_WIN)
   bool ExecuteWindowsCommand(int command_id) override;
-#endif
-
-#if BUILDFLAG(IS_WIN)
   void HandleSizeEvent(WPARAM w_param, LPARAM l_param);
   void ResetWindowControls();
+  void SetRoundedCorners(bool rounded);
   void SetForwardMouseMessages(bool forward);
   static LRESULT CALLBACK SubclassProc(HWND hwnd,
                                        UINT msg,

filename: shell/browser/native_window_views_win.cc
patch: @@ -8,6 +8,7 @@
 
 #include "base/win/atl.h"  // Must be before UIAutomationCore.h
 #include "base/win/scoped_handle.h"
+#include "base/win/windows_version.h"
 #include "content/public/browser/browser_accessibility_state.h"
 #include "shell/browser/browser.h"
 #include "shell/browser/native_window_views.h"
@@ -225,25 +226,6 @@ bool IsScreenReaderActive() {
 std::set<NativeWindowViews*> NativeWindowViews::forwarding_windows_;
 HHOOK NativeWindowViews::mouse_hook_ = nullptr;
 
-void NativeWindowViews::Maximize() {
-  // Only use Maximize() when window is NOT transparent style
-  if (!transparent()) {
-    if (IsVisible()) {
-      widget()->Maximize();
-    } else {
-      widget()->native_widget_private()->Show(
-          ui::mojom::WindowShowState::kMaximized, gfx::Rect());
-      NotifyWindowShow();
-    }
-  } else {
-    restore_bounds_ = GetBounds();
-    auto display = display::Screen::GetScreen()->GetDisplayNearestWindow(
-        GetNativeWindow());
-    SetBounds(display.work_area(), false);
-    NotifyWindowMaximize();
-  }
-}
-
 bool NativeWindowViews::ExecuteWindowsCommand(int command_id) {
   const auto command_name = AppCommandToString(command_id);
   NotifyWindowExecuteAppCommand(command_name);
@@ -535,6 +517,24 @@ void NativeWindowViews::ResetWindowControls() {
   }
 }
 
+// Windows with |backgroundMaterial| expand to the same dimensions and
+// placement as the display to approximate maximization - unless we remove
+// rounded corners there will be a gap between the window and the display
+// at the corners noticable to users.
+void NativeWindowViews::SetRoundedCorners(bool rounded) {
+  // DWMWA_WINDOW_CORNER_PREFERENCE is supported after Windows 11 Build 22000.
+  if (base::win::GetVersion() < base::win::Version::WIN11)
+    return;
+
+  DWM_WINDOW_CORNER_PREFERENCE round_pref =
+      rounded ? DWMWCP_ROUND : DWMWCP_DONOTROUND;
+  HRESULT result = DwmSetWindowAttribute(GetAcceleratedWidget(),
+                                         DWMWA_WINDOW_CORNER_PREFERENCE,
+                                         &round_pref, sizeof(round_pref));
+  if (FAILED(result))
+    LOG(WARNING) << "Failed to set rounded corners to " << rounded;
+}
+
 void NativeWindowViews::SetForwardMouseMessages(bool forward) {
   if (forward && !forwarding_mouse_messages_) {
     forwarding_mouse_messages_ = true;