Repository: electron/electron
Pull request: fix: `BrowserWindow.setBackgroundColor` should work with transparency

#### Description of Change

Closes https://github.com/electron/electron/issues/42704.

Fixes an issue where calling `BrowserWindow.setBackgroundColor` and passing any level of transparency would cause a crash via CHECK [here](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/render_widget_host_view_base.cc;l=313;drc=6d0ae42edc5f1bca00bdbd076d469ee675d3306c;bpv=1;bpt=1). This used to be a DCHECK but was recently turned into a CHECK in order to tighten up various expected values in WebContents. As many of my previous PRs have noted, background color logic is pretty scattered in Chromium. However, some digging shows that we only need to call `RenderWidgetHostViewBase::SetBackgroundColor` to designate whether or not the color is fully opaque - if there's any level of transparency we should just pass `SK_ColorTRANSPARENT`. Testing shows that this preserves existing behavior and expected transparency.

This wasn't caught in existing tests because we were checking background color without loading a webpage, so there wasn't a `RenderWidgetHostView` to speak of. I've added better transparency tests to address that.

#### 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] tests are [changed or added](https://github.com/electron/electron/blob/main/docs/development/testing.md)
- [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: Fixes an issue where calling `BrowserWindow.setBackgroundColor` with some level of transparency could cause a crash


Changed files:
filename: shell/browser/api/electron_api_web_contents.cc
patch: @@ -3729,14 +3729,17 @@ void WebContents::SetBackgroundColor(std::optional<SkColor> maybe_color) {
                                                type_ == Type::kBrowserView
                                            ? SK_ColorTRANSPARENT
                                            : SK_ColorWHITE);
+  bool is_opaque = SkColorGetA(color) == SK_AlphaOPAQUE;
   web_contents()->SetPageBaseBackgroundColor(color);
 
   content::RenderFrameHost* rfh = web_contents()->GetPrimaryMainFrame();
   if (!rfh)
     return;
+
   content::RenderWidgetHostView* rwhv = rfh->GetView();
   if (rwhv) {
-    rwhv->SetBackgroundColor(color);
+    // RenderWidgetHostView doesn't allow setting an alpha that's not 0 or 255.
+    rwhv->SetBackgroundColor(is_opaque ? color : SK_ColorTRANSPARENT);
     static_cast<content::RenderWidgetHostViewBase*>(rwhv)
         ->SetContentBackgroundColor(color);
   }

filename: spec/api-browser-window-spec.ts
patch: @@ -1660,6 +1660,7 @@ describe('BrowserWindow module', () => {
         w = new BrowserWindow({});
         expect(w.getBackgroundColor()).to.equal('#FFFFFF');
       });
+
       it('returns correct value if backgroundColor is set', () => {
         const backgroundColor = '#BBAAFF';
         w.destroy();
@@ -1668,31 +1669,50 @@ describe('BrowserWindow module', () => {
         });
         expect(w.getBackgroundColor()).to.equal(backgroundColor);
       });
+
       it('returns correct value from setBackgroundColor()', () => {
         const backgroundColor = '#AABBFF';
         w.destroy();
         w = new BrowserWindow({});
         w.setBackgroundColor(backgroundColor);
         expect(w.getBackgroundColor()).to.equal(backgroundColor);
       });
-      it('returns correct color with multiple passed formats', () => {
+
+      it('returns correct color with multiple passed formats', async () => {
         w.destroy();
         w = new BrowserWindow({});
 
-        w.setBackgroundColor('#AABBFF');
-        expect(w.getBackgroundColor()).to.equal('#AABBFF');
+        await w.loadURL('about:blank');
+
+        const colors = new Map([
+          ['blueviolet', '#8A2BE2'],
+          ['rgb(255, 0, 185)', '#FF00B9'],
+          ['hsl(155, 100%, 50%)', '#00FF95'],
+          ['#355E3B', '#355E3B']
+        ]);
+
+        for (const [color, hex] of colors) {
+          w.setBackgroundColor(color);
+          expect(w.getBackgroundColor()).to.equal(hex);
+        }
+      });
 
-        w.setBackgroundColor('blueviolet');
-        expect(w.getBackgroundColor()).to.equal('#8A2BE2');
+      it('can set the background color with transparency', async () => {
+        w.destroy();
+        w = new BrowserWindow({});
 
-        w.setBackgroundColor('rgb(255, 0, 185)');
-        expect(w.getBackgroundColor()).to.equal('#FF00B9');
+        await w.loadURL('about:blank');
 
-        w.setBackgroundColor('rgba(245, 40, 145, 0.8)');
-        expect(w.getBackgroundColor()).to.equal('#F52891');
+        const colors = new Map([
+          ['hsl(155, 100%, 50%)', '#00FF95'],
+          ['rgba(245, 40, 145, 0.8)', '#F52891'],
+          ['#1D1F21d9', '#1F21D9']
+        ]);
 
-        w.setBackgroundColor('hsl(155, 100%, 50%)');
-        expect(w.getBackgroundColor()).to.equal('#00FF95');
+        for (const [color, hex] of colors) {
+          w.setBackgroundColor(color);
+          expect(w.getBackgroundColor()).to.equal(hex);
+        }
       });
     });