Repository: electron/electron (language: cpp)

Issue:
[Feature Request]: Support full frame transparency with Mica background material
### 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 feature request that matches the one I want to file, without success.

### Problem Description

The [Mica `backgroundMaterial`](https://github.com/electron/electron/pull/38163) effect does not appear below the WebContents.

### Proposed Solution

The `backgroundMaterial` option should behave the same as when setting the `vibrancy` option.

| macOS: `vibrancy: 'medium-light'` | Windows: `backgroundMaterial: 'acrylic'` | Windows: `backgroundMaterial: 'acrylic'` + `transparent: true` |
| --- | --- | --- |
| ![Screenshot 2023-05-31 at 4 30 04 PM](https://github.com/electron/electron/assets/1656324/3f2a47e7-ed33-4f88-ae0d-2f67b1cb9b1d) | ![image (5)](https://github.com/electron/electron/assets/1656324/cb4c3a2e-284f-4a54-a968-7870af8be549) | ![image (6)](https://github.com/electron/electron/assets/1656324/318797e0-63ea-49d6-9feb-50cd66db0664) |

When `vibrancy` is set, the WebContents is transparent by default. The same should apply to `backgroundMaterial`. Setting `transparent: true` doesn't seem to be a valid workaround, but ideally should still work.

The code below needs to be updated:
https://github.com/electron/electron/blob/9ffffdb6ef202bd93be496b53fdd4156dd3504ce/shell/browser/api/electron_api_browser_window.cc#L58-L64

### Alternatives Considered

None.

### Additional Information

Tested on:
```
Edition	Windows 11 Pro
Version	22H2
OS build	22621.1778
```

Fiddle gist: https://gist.github.com/34cf667b21a5075d1f6fbe093d7ad7f9


Discussion / hints:
`backgroundMaterial` works well with `transparent: true`, but window cannot be frameless
> `backgroundMaterial` works well with `transparent: true`, but window cannot be frameless

Do you know why it works like this?
I've been looking into this and I've discovered that something about the `remove_standard_frame` option in `views` is causing the background to be non-translucent. Still investigating...
This is what I have on my `clavin/frameless-bg-mat` branch with `remove_standard_frame` set to false:

![A window with the acrylic background material, but ](https://github.com/electron/electron/assets/9995434/808ba762-ec58-43e6-b0bd-f4c043475bc7)

Obviously, it has a frame, which is not great, but turning on the option causes the window to no longer be translucent, strangely. Still, it's progress 😄

Fix patch:
diff --git a/patches/chromium/.patches b/patches/chromium/.patches
index 804fdbed7de9c..90c6a79a2780b 100644
--- a/patches/chromium/.patches
+++ b/patches/chromium/.patches
@@ -130,4 +130,5 @@ fix_harden_blink_scriptstate_maybefrom.patch
 chore_add_buildflag_guard_around_new_include.patch
 fix_use_delegated_generic_capturer_when_available.patch
 build_remove_ent_content_analysis_assert.patch
+fix_activate_background_material_on_windows.patch
 fix_move_autopipsettingshelper_behind_branding_buildflag.patch
diff --git a/patches/chromium/fix_activate_background_material_on_windows.patch b/patches/chromium/fix_activate_background_material_on_windows.patch
new file mode 100644
index 0000000000000..cc6d4a4e421ad
--- /dev/null
+++ b/patches/chromium/fix_activate_background_material_on_windows.patch
@@ -0,0 +1,24 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: clavin <clavin@electronjs.org>
+Date: Wed, 30 Aug 2023 18:15:36 -0700
+Subject: fix: activate background material on windows
+
+This patch adds a condition to the HWND message handler to allow windows
+with translucent background materials to become activated.
+
+This patch likely can't be upstreamed as-is, as Chromium doesn't have
+this use case in mind currently.
+
+diff --git a/ui/views/win/hwnd_message_handler.cc b/ui/views/win/hwnd_message_handler.cc
+index 13268bd89c710690eed5296f4b2157e9476f195e..37de479e95d49f4d2b1d8164c9e3f6a7bcd82612 100644
+--- a/ui/views/win/hwnd_message_handler.cc
++++ b/ui/views/win/hwnd_message_handler.cc
+@@ -1094,7 +1094,7 @@ void HWNDMessageHandler::FrameTypeChanged() {
+ 
+ void HWNDMessageHandler::PaintAsActiveChanged() {
+   if (!delegate_->HasNonClientView() || !delegate_->CanActivate() ||
+-      !delegate_->HasFrame() ||
++      (!delegate_->HasFrame() && !is_translucent_) ||
+       (delegate_->GetFrameMode() == FrameMode::CUSTOM_DRAWN)) {
+     return;
+   }
diff --git a/shell/browser/api/electron_api_browser_window.cc b/shell/browser/api/electron_api_browser_window.cc
index f699aa82737d1..99a61fa29f240 100644
--- a/shell/browser/api/electron_api_browser_window.cc
+++ b/shell/browser/api/electron_api_browser_window.cc
@@ -42,19 +42,11 @@ BrowserWindow::BrowserWindow(gin::Arguments* args,
   auto web_preferences = gin_helper::Dictionary::CreateEmpty(isolate);
   options.Get(options::kWebPreferences, &web_preferences);
 
-  bool transparent = false;
-  options.Get(options::kTransparent, &transparent);
-
-  std::string vibrancy_type;
-#if BUILDFLAG(IS_MAC)
-  options.Get(options::kVibrancyType, &vibrancy_type);
-#endif
-
   // Copy the backgroundColor to webContents.
   std::string color;
   if (options.Get(options::kBackgroundColor, &color)) {
     web_preferences.SetHidden(options::kBackgroundColor, color);
-  } else if (!vibrancy_type.empty() || transparent) {
+  } else if (window_->IsTranslucent()) {
     // If the BrowserWindow is transparent or a vibrancy type has been set,
     // also propagate transparency to the WebContents unless a separate
     // backgroundColor has been set.
diff --git a/shell/browser/native_window.cc b/shell/browser/native_window.cc
index aaa9676f6a0dc..9b3d8b24e20c8 100644
--- a/shell/browser/native_window.cc
+++ b/shell/browser/native_window.cc
@@ -13,6 +13,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
 #include "content/public/browser/web_contents_user_data.h"
+#include "include/core/SkColor.h"
 #include "shell/browser/browser.h"
 #include "shell/browser/native_window_features.h"
 #include "shell/browser/ui/drag_util.h"
@@ -259,13 +260,15 @@ void NativeWindow::InitFromOptions(const gin_helper::Dictionary& options) {
     SetBackgroundMaterial(material);
   }
 #endif
-  std::string color;
-  if (options.Get(options::kBackgroundColor, &color)) {
-    SetBackgroundColor(ParseCSSColor(color));
-  } else if (!transparent()) {
-    // For normal window, use white as default background.
-    SetBackgroundColor(SK_ColorWHITE);
+
+  SkColor background_color = SK_ColorWHITE;
+  if (std::string color; options.Get(options::kBackgroundColor, &color)) {
+    background_color = ParseCSSColor(color);
+  } else if (IsTranslucent()) {
+    background_color = SK_ColorTRANSPARENT;
   }
+  SetBackgroundColor(background_color);
+
   std::string title(Browser::Get()->GetName());
   options.Get(options::kTitle, &title);
   SetTitle(title);
@@ -472,9 +475,13 @@ bool NativeWindow::AddTabbedWindow(NativeWindow* window) {
   return true;  // for non-Mac platforms
 }
 
-void NativeWindow::SetVibrancy(const std::string& type) {}
+void NativeWindow::SetVibrancy(const std::string& type) {
+  vibrancy_ = type;
+}
 
-void NativeWindow::SetBackgroundMaterial(const std::string& type) {}
+void NativeWindow::SetBackgroundMaterial(const std::string& type) {
+  background_material_ = type;
+}
 
 void NativeWindow::SetTouchBar(
     std::vector<gin_helper::PersistentDictionary> items) {}
@@ -798,6 +805,30 @@ void NativeWindow::HandlePendingFullscreenTransitions() {
 // static
 int32_t NativeWindow::next_id_ = 0;
 
+bool NativeWindow::IsTranslucent() const {
+  // Transparent windows are translucent
+  if (transparent()) {
+    return true;
+  }
+
+#if BUILDFLAG(IS_MAC)
+  // Windows with vibrancy set are translucent
+  if (!vibrancy().empty()) {
+    return true;
+  }
+#endif
+
+#if BUILDFLAG(IS_WIN)
+  // Windows with certain background materials may be translucent
+  const std::string& bg_material = background_material();
+  if (!bg_material.empty() && bg_material != "none") {
+    return true;
+  }
+#endif
+
+  return false;
+}
+
 // static
 void NativeWindowRelay::CreateForWebContents(
     content::WebContents* web_contents,
diff --git a/shell/browser/native_window.h b/shell/browser/native_window.h
index 8ce87494582ab..a091c834f7f34 100644
--- a/shell/browser/native_window.h
+++ b/shell/browser/native_window.h
@@ -218,8 +218,12 @@ class NativeWindow : public base::SupportsUserData,
   virtual void SetAutoHideCursor(bool auto_hide);
 
   // Vibrancy API
+  const std::string& vibrancy() const { return vibrancy_; }
   virtual void SetVibrancy(const std::string& type);
 
+  const std::string& background_material() const {
+    return background_material_;
+  }
   virtual void SetBackgroundMaterial(const std::string& type);
 
   // Traffic Light API
@@ -395,6 +399,8 @@ class NativeWindow : public base::SupportsUserData,
   void AddDraggableRegionProvider(DraggableRegionProvider* provider);
   void RemoveDraggableRegionProvider(DraggableRegionProvider* provider);
 
+  bool IsTranslucent() const;
+
  protected:
   friend class api::BrowserView;
 
@@ -492,6 +498,9 @@ class NativeWindow : public base::SupportsUserData,
   // Accessible title.
   std::u16string accessible_title_;
 
+  std::string vibrancy_;
+  std::string background_material_;
+
   gfx::Rect overlay_rect_;
 
   base::WeakPtrFactory<NativeWindow> weak_factory_{this};
diff --git a/shell/browser/native_window_mac.mm b/shell/browser/native_window_mac.mm
index d3d0152497d3b..2dc3c3c42a8b0 100644
--- a/shell/browser/native_window_mac.mm
+++ b/shell/browser/native_window_mac.mm
@@ -1458,6 +1458,8 @@ void ReorderChildWindowAbove(NSWindow* child_window, NSWindow* other_window) {
 }
 
 void NativeWindowMac::SetVibrancy(const std::string& type) {
+  NativeWindow::SetVibrancy(type);
+
   NSVisualEffectView* vibrantView = [window_ vibrantView];
 
   if (type.empty()) {
diff --git a/shell/browser/native_window_views.cc b/shell/browser/native_window_views.cc
index 0ce17d437ab09..2cf0682a896d3 100644
--- a/shell/browser/native_window_views.cc
+++ b/shell/browser/native_window_views.cc
@@ -284,12 +284,12 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options,
   params.remove_standard_frame = !has_frame() || has_client_frame();
 
   // If a client frame, we need to draw our own shadows.
-  if (transparent() || has_client_frame())
+  if (IsTranslucent() || has_client_frame())
     params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
 
-  // The given window is most likely not rectangular since it uses
-  // transparency and has no standard frame, don't show a shadow for it.
-  if (transparent() && !has_frame())
+  // The given window is most likely not rectangular since it is translucent and
+  // has no standard frame, don't show a shadow for it.
+  if (IsTranslucent() && !has_frame())
     params.shadow_type = views::Widget::InitParams::ShadowType::kNone;
 
   bool focusable;
@@ -1457,6 +1457,8 @@ bool NativeWindowViews::IsMenuBarVisible() {
 }
 
 void NativeWindowViews::SetBackgroundMaterial(const std::string& material) {
+  NativeWindow::SetBackgroundMaterial(material);
+
 #if BUILDFLAG(IS_WIN)
   // DWMWA_USE_HOSTBACKDROPBRUSH is only supported on Windows 11 22H2 and up.
   if (base::win::GetVersion() < base::win::Version::WIN11_22H2)
@@ -1468,6 +1470,20 @@ void NativeWindowViews::SetBackgroundMaterial(const std::string& material) {
                             &backdrop_type, sizeof(backdrop_type));
   if (FAILED(result))
     LOG(WARNING) << "Failed to set background material to " << material;
+
+  // For frameless windows with a background material set, we also need to
+  // remove the caption color so it doesn't render a caption bar (since the
+  // window is frameless)
+  COLORREF caption_color = DWMWA_COLOR_DEFAULT;
+  if (backdrop_type != DWMSBT_NONE && backdrop_type != DWMSBT_AUTO &&
+      !has_frame()) {
+    caption_color = DWMWA_COLOR_NONE;
+  }
+  result = DwmSetWindowAttribute(GetAcceleratedWidget(), DWMWA_CAPTION_COLOR,
+                                 &caption_color, sizeof(caption_color));
+
+  if (FAILED(result))
+    LOG(WARNING) << "Failed to set caption color to transparent";
 #endif
 }
 


Test patch: