Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: BrowserView background color in webContents #33545

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 19 additions & 2 deletions shell/browser/api/electron_api_browser_view.cc
Expand Up @@ -6,10 +6,13 @@

#include <vector>

#include "content/browser/renderer_host/render_widget_host_view_base.h" // nogncheck
#include "content/public/browser/render_widget_host_view.h"
#include "shell/browser/api/electron_api_web_contents.h"
#include "shell/browser/browser.h"
#include "shell/browser/native_browser_view.h"
#include "shell/browser/ui/drag_util.h"
#include "shell/browser/web_contents_preferences.h"
#include "shell/common/color_util.h"
#include "shell/common/gin_converters/gfx_converter.h"
#include "shell/common/gin_helper/dictionary.h"
Expand Down Expand Up @@ -155,11 +158,25 @@ gfx::Rect BrowserView::GetBounds() {
}

void BrowserView::SetBackgroundColor(const std::string& color_name) {
view_->SetBackgroundColor(ParseHexColor(color_name));
SkColor color = ParseHexColor(color_name);
view_->SetBackgroundColor(color);

if (web_contents()) {
auto* wc = web_contents()->web_contents();
wc->SetPageBaseBackgroundColor(ParseHexColor(color_name));
wc->SetPageBaseBackgroundColor(color);

auto* const rwhv = wc->GetRenderWidgetHostView();
if (rwhv) {
rwhv->SetBackgroundColor(color);
static_cast<content::RenderWidgetHostViewBase*>(rwhv)
->SetContentBackgroundColor(color);
}

// Ensure new color is stored in webPreferences, otherwise
// the color will be reset on the next load via HandleNewRenderFrame.
auto* web_preferences = WebContentsPreferences::From(wc);
if (web_preferences)
web_preferences->SetBackgroundColor(color);
}
}

Expand Down
10 changes: 6 additions & 4 deletions shell/browser/api/electron_api_web_contents.cc
Expand Up @@ -1463,11 +1463,13 @@ void WebContents::HandleNewRenderFrame(
// Set the background color of RenderWidgetHostView.
auto* web_preferences = WebContentsPreferences::From(web_contents());
if (web_preferences) {
absl::optional<SkColor> maybe_color = web_preferences->GetBackgroundColor();
web_contents()->SetPageBaseBackgroundColor(maybe_color);

bool guest = IsGuest() || type_ == Type::kBrowserView;
absl::optional<SkColor> color =
guest ? SK_ColorTRANSPARENT : web_preferences->GetBackgroundColor();
web_contents()->SetPageBaseBackgroundColor(color);
SetBackgroundColor(rwhv, color.value_or(SK_ColorWHITE));
SkColor color =
maybe_color.value_or(guest ? SK_ColorTRANSPARENT : SK_ColorWHITE);
SetBackgroundColor(rwhv, color);
}

if (!background_throttling_)
Expand Down
55 changes: 53 additions & 2 deletions spec-main/api-browser-view-spec.ts
@@ -1,9 +1,10 @@
import { expect } from 'chai';
import * as path from 'path';
import { emittedOnce } from './events-helpers';
import { BrowserView, BrowserWindow, webContents } from 'electron/main';
import { BrowserView, BrowserWindow, screen, webContents } from 'electron/main';
import { closeWindow } from './window-helpers';
import { defer, startRemoteControlApp } from './spec-helpers';
import { defer, ifit, startRemoteControlApp } from './spec-helpers';
import { areColorsSimilar, captureScreen, getPixelColor } from './screen-helpers';

describe('BrowserView module', () => {
const fixtures = path.resolve(__dirname, '..', 'spec', 'fixtures');
Expand Down Expand Up @@ -60,6 +61,56 @@ describe('BrowserView module', () => {
view.setBackgroundColor(null as any);
}).to.throw(/conversion failure/);
});

// Linux and arm64 platforms (WOA and macOS) do not return any capture sources
ifit(process.platform !== 'linux' && process.arch !== 'arm64')('sets the background color to transparent if none is set', async () => {
const display = screen.getPrimaryDisplay();
const WINDOW_BACKGROUND_COLOR = '#55ccbb';

w.show();
w.setBounds(display.bounds);
w.setBackgroundColor(WINDOW_BACKGROUND_COLOR);
await w.loadURL('about:blank');

view = new BrowserView();
view.setBounds(display.bounds);
w.setBrowserView(view);
await view.webContents.loadURL('data:text/html,hello there');

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 !== 'linux' && process.arch !== 'arm64')('successfully applies the background color', async () => {
const WINDOW_BACKGROUND_COLOR = '#55ccbb';
const VIEW_BACKGROUND_COLOR = '#ff00ff';
const display = screen.getPrimaryDisplay();

w.show();
w.setBounds(display.bounds);
w.setBackgroundColor(WINDOW_BACKGROUND_COLOR);
await w.loadURL('about:blank');

view = new BrowserView();
view.setBounds(display.bounds);
w.setBrowserView(view);
w.setBackgroundColor(VIEW_BACKGROUND_COLOR);
await view.webContents.loadURL('data:text/html,hello there');

const screenCapture = await captureScreen();
const centerColor = getPixelColor(screenCapture, {
x: display.size.width / 2,
y: display.size.height / 2
});

expect(areColorsSimilar(centerColor, VIEW_BACKGROUND_COLOR)).to.be.true();
});
});

describe('BrowserView.setAutoResize()', () => {
Expand Down
87 changes: 87 additions & 0 deletions spec-main/screen-helpers.ts
@@ -0,0 +1,87 @@
import * as path from 'path';
import * as fs from 'fs';
import { screen, desktopCapturer, NativeImage } from 'electron';

const fixtures = path.resolve(__dirname, '..', 'spec', 'fixtures');

/** Chroma key green. */
export const CHROMA_COLOR_HEX = '#00b140';

/**
* Capture the screen at the given point.
*
* NOTE: Not yet supported on Linux in CI due to empty sources list.
*/
export const captureScreen = async (point: Electron.Point = { x: 0, y: 0 }): Promise<NativeImage> => {
const display = screen.getDisplayNearestPoint(point);
const sources = await desktopCapturer.getSources({ types: ['screen'], thumbnailSize: display.size });
// Toggle to save screen captures for debugging.
const DEBUG_CAPTURE = false;
if (DEBUG_CAPTURE) {
for (const source of sources) {
await fs.promises.writeFile(path.join(fixtures, `screenshot_${source.display_id}.png`), source.thumbnail.toPNG());
}
}
const screenCapture = sources.find(source => source.display_id === `${display.id}`);
// Fails when HDR is enabled on Windows.
// https://bugs.chromium.org/p/chromium/issues/detail?id=1247730
if (!screenCapture) {
const displayIds = sources.map(source => source.display_id);
throw new Error(`Unable to find screen capture for display '${display.id}'\n\tAvailable displays: ${displayIds.join(', ')}`);
}
return screenCapture.thumbnail;
};

const formatHexByte = (val: number): string => {
const str = val.toString(16);
return str.length === 2 ? str : `0${str}`;
};

/**
* Get the hex color at the given pixel coordinate in an image.
*/
export const getPixelColor = (image: Electron.NativeImage, point: Electron.Point): string => {
const pixel = image.crop({ ...point, width: 1, height: 1 });
// TODO(samuelmaddock): NativeImage.toBitmap() should return the raw pixel
// color, but it sometimes differs. Why is that?
const [b, g, r] = pixel.toBitmap();
return `#${formatHexByte(r)}${formatHexByte(g)}${formatHexByte(b)}`;
};

const hexToRgba = (hexColor: string) => {
const match = hexColor.match(/^#([0-9a-fA-F]{6,8})$/);
if (!match) return;

const colorStr = match[1];
return [
parseInt(colorStr.substring(0, 2), 16),
parseInt(colorStr.substring(2, 4), 16),
parseInt(colorStr.substring(4, 6), 16),
parseInt(colorStr.substring(6, 8), 16) || 0xFF
];
};

/** Calculate euclidian distance between colors. */
const colorDistance = (hexColorA: string, hexColorB: string) => {
const colorA = hexToRgba(hexColorA);
const colorB = hexToRgba(hexColorB);
if (!colorA || !colorB) return -1;
return Math.sqrt(
Math.pow(colorB[0] - colorA[0], 2) +
Math.pow(colorB[1] - colorA[1], 2) +
Math.pow(colorB[2] - colorA[2], 2)
);
};

/**
* Determine if colors are similar based on distance. This can be useful when
* comparing colors which may differ based on lossy compression.
*/
export const areColorsSimilar = (
hexColorA: string,
hexColorB: string,
distanceThreshold = 90
): boolean => {
const distance = colorDistance(hexColorA, hexColorB);
return distance <= distanceThreshold;
};