Repository: electron/electron (language: cpp)

Issue:
[Bug]: Setting color-scheme to dark changes text color but not background color for webviews
### 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

27.0.0

### What operating system are you using?

Windows

### Operating System Version

Windows 11 22H2

### What arch are you using?

x64

### Last Known Working Electron version

None

### Expected Behavior

The background colour of the webview should be black and the text colour should be white.

### Actual Behavior

The background colour of the webview is white and the text colour is also white, making it impossible to read the text.

### Testcase Gist URL

https://gist.github.com/BrandonXLF/4a6f65cba592bece8d1413f7a8730dca

### Additional Information

Continuation of #36122 as it became stale

Issue applies to both the `color-scheme` CSS property when it's applied to `:root`/`html` and the `color-scheme` meta tag, but it works fine when applied to other elements like `textarea`. The issue is not present in iframes.


Discussion / hints:

Fix patch:
diff --git a/docs/api/webview-tag.md b/docs/api/webview-tag.md
index d34026e97faed..eecd2e86f90f3 100644
--- a/docs/api/webview-tag.md
+++ b/docs/api/webview-tag.md
@@ -221,7 +221,9 @@ windows. Popups are disabled by default.
 ```
 
 A `string` which is a comma separated list of strings which specifies the web preferences to be set on the webview.
-The full list of supported preference strings can be found in [BrowserWindow](browser-window.md#new-browserwindowoptions).
+The full list of supported preference strings can be found in [BrowserWindow](browser-window.md#new-browserwindowoptions). In addition, webview supports the following preferences:
+
+* `transparent` boolean (optional) - Whether to enable background transparency for the guest page. Default is `true`. **Note:** The guest page's text and background colors are derived from the [color scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/color-scheme) of its root element. When transparency is enabled, the text color will still change accordingly but the background will remain transparent.
 
 The string follows the same format as the features string in `window.open`.
 A name by itself is given a `true` boolean value.
diff --git a/shell/browser/api/electron_api_web_contents.cc b/shell/browser/api/electron_api_web_contents.cc
index 29cfc8537bc32..c24d3288831fe 100644
--- a/shell/browser/api/electron_api_web_contents.cc
+++ b/shell/browser/api/electron_api_web_contents.cc
@@ -822,6 +822,9 @@ WebContents::WebContents(v8::Isolate* isolate,
   // Get type
   options.Get("type", &type_);
 
+  // Get transparent for guest view
+  options.Get("transparent", &guest_transparent_);
+
   bool b = false;
   if (options.Get(options::kOffscreen, &b) && b)
     type_ = Type::kOffScreen;
@@ -3778,7 +3781,7 @@ void WebContents::SetImageAnimationPolicy(const std::string& new_policy) {
 }
 
 void WebContents::SetBackgroundColor(absl::optional<SkColor> maybe_color) {
-  SkColor color = maybe_color.value_or(type_ == Type::kWebView ||
+  SkColor color = maybe_color.value_or((IsGuest() && guest_transparent_) ||
                                                type_ == Type::kBrowserView
                                            ? SK_ColorTRANSPARENT
                                            : SK_ColorWHITE);
diff --git a/shell/browser/api/electron_api_web_contents.h b/shell/browser/api/electron_api_web_contents.h
index 49c1a1f81ada4..00c3ad8648c70 100644
--- a/shell/browser/api/electron_api_web_contents.h
+++ b/shell/browser/api/electron_api_web_contents.h
@@ -801,6 +801,9 @@ class WebContents : public ExclusiveAccessContext,
   // The type of current WebContents.
   Type type_ = Type::kBrowserWindow;
 
+  // Weather the guest view should be transparent
+  bool guest_transparent_ = true;
+
   int32_t id_;
 
   // Request id used for findInPage request.
diff --git a/spec/fixtures/pages/flex-webview.html b/spec/fixtures/pages/flex-webview.html
new file mode 100644
index 0000000000000..7d5369946c684
--- /dev/null
+++ b/spec/fixtures/pages/flex-webview.html
@@ -0,0 +1,15 @@
+<style>
+    html,
+    body {
+        height: 100%;
+    }
+
+    body {
+        display: flex;
+        margin: 0;
+    }
+    
+    webview {
+        flex: 1;
+    }
+</style>
diff --git a/spec/webview-spec.ts b/spec/webview-spec.ts
index 57770e797ca7b..3e4dca739f290 100644
--- a/spec/webview-spec.ts
+++ b/spec/webview-spec.ts
@@ -1,6 +1,6 @@
 import * as path from 'node:path';
 import * as url from 'node:url';
-import { BrowserWindow, session, ipcMain, app, WebContents } from 'electron/main';
+import { BrowserWindow, session, ipcMain, app, WebContents, screen } from 'electron/main';
 import { closeAllWindows } from './lib/window-helpers';
 import { emittedUntil } from './lib/events-helpers';
 import { ifit, ifdescribe, defer, itremote, useRemoteContext, listen } from './lib/spec-helpers';
@@ -9,6 +9,7 @@ import * as http from 'node:http';
 import * as auth from 'basic-auth';
 import { once } from 'node:events';
 import { setTimeout } from 'node:timers/promises';
+import { areColorsSimilar, captureScreen, HexColors, getPixelColor } from './lib/screen-helpers';
 
 declare let WebView: any;
 const features = process._linkedBinding('electron_common_features');
@@ -773,6 +774,85 @@ describe('<webview> tag', function () {
     });
   });
 
+  describe('webpreferences attribute', () => {
+    const WINDOW_BACKGROUND_COLOR = '#55ccbb';
+
+    let w: BrowserWindow;
+    before(async () => {
+      w = new BrowserWindow({
+        webPreferences: {
+          webviewTag: true,
+          nodeIntegration: true,
+          contextIsolation: false
+        }
+      });
+      await w.loadURL(`file://${fixtures}/pages/flex-webview.html`);
+      w.setBackgroundColor(WINDOW_BACKGROUND_COLOR);
+    });
+    afterEach(async () => {
+      await w.webContents.executeJavaScript(`{
+        for (const el of document.querySelectorAll('webview')) el.remove();
+      }`);
+    });
+    after(() => w.close());
+
+    // Linux and arm64 platforms (WOA and macOS) do not return any capture sources
+    ifit(process.platform === 'darwin' && process.arch === 'x64')('is transparent by default', async () => {
+      await loadWebView(w.webContents, {
+        src: 'data:text/html,foo'
+      });
+
+      await setTimeout(1000);
+
+      const display = screen.getPrimaryDisplay();
+      const screenCapture = await captureScreen();
+      const centerColor = getPixelColor(screenCapture, {
+        x: display.size.width / 2,
+        y: display.size.height / 2
+      });
+
+      expect(areColorsSimilar(centerColor, WINDOW_BACKGROUND_COLOR)).to.be.true();
+    });
+
+    // Linux and arm64 platforms (WOA and macOS) do not return any capture sources
+    ifit(process.platform === 'darwin' && process.arch === 'x64')('remains transparent when set', async () => {
+      await loadWebView(w.webContents, {
+        src: 'data:text/html,foo',
+        webpreferences: 'transparent=yes'
+      });
+
+      await setTimeout(1000);
+
+      const display = screen.getPrimaryDisplay();
+      const screenCapture = await captureScreen();
+      const centerColor = getPixelColor(screenCapture, {
+        x: display.size.width / 2,
+        y: display.size.height / 2
+      });
+
+      expect(areColorsSimilar(centerColor, WINDOW_BACKGROUND_COLOR)).to.be.true();
+    });
+
+    // Linux and arm64 platforms (WOA and macOS) do not return any capture sources
+    ifit(process.platform === 'darwin' && process.arch === 'x64')('can disable transparency', async () => {
+      await loadWebView(w.webContents, {
+        src: 'data:text/html,foo',
+        webpreferences: 'transparent=no'
+      });
+
+      await setTimeout(1000);
+
+      const display = screen.getPrimaryDisplay();
+      const screenCapture = await captureScreen();
+      const centerColor = getPixelColor(screenCapture, {
+        x: display.size.width / 2,
+        y: display.size.height / 2
+      });
+
+      expect(areColorsSimilar(centerColor, HexColors.WHITE)).to.be.true();
+    });
+  });
+
   describe('permission request handlers', () => {
     let w: BrowserWindow;
     beforeEach(async () => {


Test patch: