зеркало из https://github.com/electron/electron.git
feat: customize border radius of Views (#42320)
* feat: add View#setBorderRadius test: initial setBorderRadius tests fix: robustly set border radius chore: add PAUSE_CAPTURE_TESTS for easier screencap dev feat: add view border radius support test: view border radius refactor: cleanup view code * maybe delay capture to fix tests? * refactor: retry screen captures in an attempt to fix flakiness * refactor: ScreenCapture constructor no longer async * increase screen capture timeout, feels a little short * refactor: move rounded rect util into chromium_src * skip some capture tests on mas
This commit is contained in:
Родитель
cbd11bb605
Коммит
778d3098a0
|
@ -14,6 +14,8 @@ import("//third_party/widevine/cdm/widevine.gni")
|
||||||
static_library("chrome") {
|
static_library("chrome") {
|
||||||
visibility = [ "//electron:electron_lib" ]
|
visibility = [ "//electron:electron_lib" ]
|
||||||
sources = [
|
sources = [
|
||||||
|
"//ash/style/rounded_rect_cutout_path_builder.cc",
|
||||||
|
"//ash/style/rounded_rect_cutout_path_builder.h",
|
||||||
"//chrome/browser/app_mode/app_mode_utils.cc",
|
"//chrome/browser/app_mode/app_mode_utils.cc",
|
||||||
"//chrome/browser/app_mode/app_mode_utils.h",
|
"//chrome/browser/app_mode/app_mode_utils.h",
|
||||||
"//chrome/browser/browser_features.cc",
|
"//chrome/browser/browser_features.cc",
|
||||||
|
|
|
@ -94,6 +94,12 @@ Examples of valid `color` values:
|
||||||
|
|
||||||
**Note:** Hex format with alpha takes `AARRGGBB` or `ARGB`, _not_ `RRGGBBAA` or `RGB`.
|
**Note:** Hex format with alpha takes `AARRGGBB` or `ARGB`, _not_ `RRGGBBAA` or `RGB`.
|
||||||
|
|
||||||
|
#### `view.setBorderRadius(radius)`
|
||||||
|
|
||||||
|
* `radius` Integer - Border radius size in pixels.
|
||||||
|
|
||||||
|
**Note:** The area cutout of the view's border still captures clicks.
|
||||||
|
|
||||||
#### `view.setVisible(visible)`
|
#### `view.setVisible(visible)`
|
||||||
|
|
||||||
* `visible` boolean - If false, the view will be hidden from display.
|
* `visible` boolean - If false, the view will be hidden from display.
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#include "ash/style/rounded_rect_cutout_path_builder.h"
|
||||||
#include "gin/data_object_builder.h"
|
#include "gin/data_object_builder.h"
|
||||||
#include "gin/wrappable.h"
|
#include "gin/wrappable.h"
|
||||||
#include "shell/browser/javascript_environment.h"
|
#include "shell/browser/javascript_environment.h"
|
||||||
|
@ -338,6 +339,38 @@ void View::SetBackgroundColor(std::optional<WrappedSkColor> color) {
|
||||||
view_->SetBackground(color ? views::CreateSolidBackground(*color) : nullptr);
|
view_->SetBackground(color ? views::CreateSolidBackground(*color) : nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void View::SetBorderRadius(int radius) {
|
||||||
|
border_radius_ = radius;
|
||||||
|
ApplyBorderRadius();
|
||||||
|
}
|
||||||
|
|
||||||
|
void View::ApplyBorderRadius() {
|
||||||
|
if (!border_radius_.has_value() || !view_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto size = view_->bounds().size();
|
||||||
|
|
||||||
|
// Restrict border radius to the constraints set in the path builder class.
|
||||||
|
// If the constraints are exceeded, the builder will crash.
|
||||||
|
int radius;
|
||||||
|
{
|
||||||
|
float r = border_radius_.value() * 1.f;
|
||||||
|
r = std::min(r, size.width() / 2.f);
|
||||||
|
r = std::min(r, size.height() / 2.f);
|
||||||
|
r = std::max(r, 0.f);
|
||||||
|
radius = std::floor(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoundedRectCutoutPathBuilder has a minimum size of 32 x 32.
|
||||||
|
if (radius > 0 && size.width() >= 32 && size.height() >= 32) {
|
||||||
|
auto builder = ash::RoundedRectCutoutPathBuilder(gfx::SizeF(size));
|
||||||
|
builder.CornerRadius(radius);
|
||||||
|
view_->SetClipPath(builder.Build());
|
||||||
|
} else {
|
||||||
|
view_->SetClipPath(SkPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void View::SetVisible(bool visible) {
|
void View::SetVisible(bool visible) {
|
||||||
if (!view_)
|
if (!view_)
|
||||||
return;
|
return;
|
||||||
|
@ -345,6 +378,7 @@ void View::SetVisible(bool visible) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void View::OnViewBoundsChanged(views::View* observed_view) {
|
void View::OnViewBoundsChanged(views::View* observed_view) {
|
||||||
|
ApplyBorderRadius();
|
||||||
Emit("bounds-changed");
|
Emit("bounds-changed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -393,6 +427,7 @@ void View::BuildPrototype(v8::Isolate* isolate,
|
||||||
.SetMethod("setBounds", &View::SetBounds)
|
.SetMethod("setBounds", &View::SetBounds)
|
||||||
.SetMethod("getBounds", &View::GetBounds)
|
.SetMethod("getBounds", &View::GetBounds)
|
||||||
.SetMethod("setBackgroundColor", &View::SetBackgroundColor)
|
.SetMethod("setBackgroundColor", &View::SetBackgroundColor)
|
||||||
|
.SetMethod("setBorderRadius", &View::SetBorderRadius)
|
||||||
.SetMethod("setLayout", &View::SetLayout)
|
.SetMethod("setLayout", &View::SetLayout)
|
||||||
.SetMethod("setVisible", &View::SetVisible);
|
.SetMethod("setVisible", &View::SetVisible);
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ class View : public gin_helper::EventEmitter<View>,
|
||||||
void SetLayout(v8::Isolate* isolate, v8::Local<v8::Object> value);
|
void SetLayout(v8::Isolate* isolate, v8::Local<v8::Object> value);
|
||||||
std::vector<v8::Local<v8::Value>> GetChildren();
|
std::vector<v8::Local<v8::Value>> GetChildren();
|
||||||
void SetBackgroundColor(std::optional<WrappedSkColor> color);
|
void SetBackgroundColor(std::optional<WrappedSkColor> color);
|
||||||
|
void SetBorderRadius(int radius);
|
||||||
void SetVisible(bool visible);
|
void SetVisible(bool visible);
|
||||||
|
|
||||||
// views::ViewObserver
|
// views::ViewObserver
|
||||||
|
@ -44,6 +45,7 @@ class View : public gin_helper::EventEmitter<View>,
|
||||||
void OnViewIsDeleting(views::View* observed_view) override;
|
void OnViewIsDeleting(views::View* observed_view) override;
|
||||||
|
|
||||||
views::View* view() const { return view_; }
|
views::View* view() const { return view_; }
|
||||||
|
std::optional<int> border_radius() const { return border_radius_; }
|
||||||
|
|
||||||
// disable copy
|
// disable copy
|
||||||
View(const View&) = delete;
|
View(const View&) = delete;
|
||||||
|
@ -58,9 +60,11 @@ class View : public gin_helper::EventEmitter<View>,
|
||||||
void set_delete_view(bool should) { delete_view_ = should; }
|
void set_delete_view(bool should) { delete_view_ = should; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void ApplyBorderRadius();
|
||||||
void ReorderChildView(gin::Handle<View> child, size_t index);
|
void ReorderChildView(gin::Handle<View> child, size_t index);
|
||||||
|
|
||||||
std::vector<v8::Global<v8::Object>> child_views_;
|
std::vector<v8::Global<v8::Object>> child_views_;
|
||||||
|
std::optional<int> border_radius_;
|
||||||
|
|
||||||
bool delete_view_ = true;
|
bool delete_view_ = true;
|
||||||
raw_ptr<views::View> view_ = nullptr;
|
raw_ptr<views::View> view_ = nullptr;
|
||||||
|
|
|
@ -20,6 +20,8 @@
|
||||||
#include "shell/common/options_switches.h"
|
#include "shell/common/options_switches.h"
|
||||||
#include "third_party/skia/include/core/SkRegion.h"
|
#include "third_party/skia/include/core/SkRegion.h"
|
||||||
#include "ui/base/hit_test.h"
|
#include "ui/base/hit_test.h"
|
||||||
|
#include "ui/gfx/geometry/rounded_corners_f.h"
|
||||||
|
#include "ui/views/controls/webview/webview.h"
|
||||||
#include "ui/views/layout/flex_layout_types.h"
|
#include "ui/views/layout/flex_layout_types.h"
|
||||||
#include "ui/views/view_class_properties.h"
|
#include "ui/views/view_class_properties.h"
|
||||||
#include "ui/views/widget/widget.h"
|
#include "ui/views/widget/widget.h"
|
||||||
|
@ -65,6 +67,25 @@ void WebContentsView::SetBackgroundColor(std::optional<WrappedSkColor> color) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WebContentsView::SetBorderRadius(int radius) {
|
||||||
|
View::SetBorderRadius(radius);
|
||||||
|
ApplyBorderRadius();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebContentsView::ApplyBorderRadius() {
|
||||||
|
if (border_radius().has_value() && api_web_contents_ && view()->GetWidget()) {
|
||||||
|
auto* web_view = api_web_contents_->inspectable_web_contents()
|
||||||
|
->GetView()
|
||||||
|
->contents_web_view();
|
||||||
|
|
||||||
|
// WebView won't exist for offscreen rendering.
|
||||||
|
if (web_view) {
|
||||||
|
web_view->holder()->SetCornerRadii(
|
||||||
|
gfx::RoundedCornersF(border_radius().value()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int WebContentsView::NonClientHitTest(const gfx::Point& point) {
|
int WebContentsView::NonClientHitTest(const gfx::Point& point) {
|
||||||
if (api_web_contents_) {
|
if (api_web_contents_) {
|
||||||
gfx::Point local_point(point);
|
gfx::Point local_point(point);
|
||||||
|
@ -93,6 +114,7 @@ void WebContentsView::OnViewAddedToWidget(views::View* observed_view) {
|
||||||
// because that's handled in the WebContents dtor called prior.
|
// because that's handled in the WebContents dtor called prior.
|
||||||
api_web_contents_->SetOwnerWindow(native_window);
|
api_web_contents_->SetOwnerWindow(native_window);
|
||||||
native_window->AddDraggableRegionProvider(this);
|
native_window->AddDraggableRegionProvider(this);
|
||||||
|
ApplyBorderRadius();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebContentsView::OnViewRemovedFromWidget(views::View* observed_view) {
|
void WebContentsView::OnViewRemovedFromWidget(views::View* observed_view) {
|
||||||
|
@ -198,6 +220,7 @@ void WebContentsView::BuildPrototype(
|
||||||
prototype->SetClassName(gin::StringToV8(isolate, "WebContentsView"));
|
prototype->SetClassName(gin::StringToV8(isolate, "WebContentsView"));
|
||||||
gin_helper::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
|
gin_helper::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
|
||||||
.SetMethod("setBackgroundColor", &WebContentsView::SetBackgroundColor)
|
.SetMethod("setBackgroundColor", &WebContentsView::SetBackgroundColor)
|
||||||
|
.SetMethod("setBorderRadius", &WebContentsView::SetBorderRadius)
|
||||||
.SetProperty("webContents", &WebContentsView::GetWebContents);
|
.SetProperty("webContents", &WebContentsView::GetWebContents);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,7 @@ class WebContentsView : public View,
|
||||||
// Public APIs.
|
// Public APIs.
|
||||||
gin::Handle<WebContents> GetWebContents(v8::Isolate* isolate);
|
gin::Handle<WebContents> GetWebContents(v8::Isolate* isolate);
|
||||||
void SetBackgroundColor(std::optional<WrappedSkColor> color);
|
void SetBackgroundColor(std::optional<WrappedSkColor> color);
|
||||||
|
void SetBorderRadius(int radius);
|
||||||
|
|
||||||
int NonClientHitTest(const gfx::Point& point) override;
|
int NonClientHitTest(const gfx::Point& point) override;
|
||||||
|
|
||||||
|
@ -57,6 +58,8 @@ class WebContentsView : public View,
|
||||||
private:
|
private:
|
||||||
static gin_helper::WrappableBase* New(gin_helper::Arguments* args);
|
static gin_helper::WrappableBase* New(gin_helper::Arguments* args);
|
||||||
|
|
||||||
|
void ApplyBorderRadius();
|
||||||
|
|
||||||
// Keep a reference to v8 wrapper.
|
// Keep a reference to v8 wrapper.
|
||||||
v8::Global<v8::Value> web_contents_;
|
v8::Global<v8::Value> web_contents_;
|
||||||
raw_ptr<api::WebContents> api_web_contents_;
|
raw_ptr<api::WebContents> api_web_contents_;
|
||||||
|
|
|
@ -84,14 +84,14 @@ InspectableWebContentsView::InspectableWebContentsView(
|
||||||
auto* contents_web_view = new views::WebView(nullptr);
|
auto* contents_web_view = new views::WebView(nullptr);
|
||||||
contents_web_view->SetWebContents(
|
contents_web_view->SetWebContents(
|
||||||
inspectable_web_contents_->GetWebContents());
|
inspectable_web_contents_->GetWebContents());
|
||||||
contents_web_view_ = contents_web_view;
|
contents_view_ = contents_web_view_ = contents_web_view;
|
||||||
} else {
|
} else {
|
||||||
contents_web_view_ = new views::Label(u"No content under offscreen mode");
|
contents_view_ = new views::Label(u"No content under offscreen mode");
|
||||||
}
|
}
|
||||||
|
|
||||||
devtools_web_view_->SetVisible(false);
|
devtools_web_view_->SetVisible(false);
|
||||||
AddChildView(devtools_web_view_.get());
|
AddChildView(devtools_web_view_.get());
|
||||||
AddChildView(contents_web_view_.get());
|
AddChildView(contents_view_.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
InspectableWebContentsView::~InspectableWebContentsView() {
|
InspectableWebContentsView::~InspectableWebContentsView() {
|
||||||
|
@ -209,7 +209,7 @@ const std::u16string InspectableWebContentsView::GetTitle() {
|
||||||
|
|
||||||
void InspectableWebContentsView::Layout(PassKey) {
|
void InspectableWebContentsView::Layout(PassKey) {
|
||||||
if (!devtools_web_view_->GetVisible()) {
|
if (!devtools_web_view_->GetVisible()) {
|
||||||
contents_web_view_->SetBoundsRect(GetContentsBounds());
|
contents_view_->SetBoundsRect(GetContentsBounds());
|
||||||
// Propagate layout call to all children, for example browser views.
|
// Propagate layout call to all children, for example browser views.
|
||||||
LayoutSuperclass<View>(this);
|
LayoutSuperclass<View>(this);
|
||||||
return;
|
return;
|
||||||
|
@ -227,7 +227,7 @@ void InspectableWebContentsView::Layout(PassKey) {
|
||||||
new_contents_bounds.set_x(GetMirroredXForRect(new_contents_bounds));
|
new_contents_bounds.set_x(GetMirroredXForRect(new_contents_bounds));
|
||||||
|
|
||||||
devtools_web_view_->SetBoundsRect(new_devtools_bounds);
|
devtools_web_view_->SetBoundsRect(new_devtools_bounds);
|
||||||
contents_web_view_->SetBoundsRect(new_contents_bounds);
|
contents_view_->SetBoundsRect(new_contents_bounds);
|
||||||
|
|
||||||
// Propagate layout call to all children, for example browser views.
|
// Propagate layout call to all children, for example browser views.
|
||||||
LayoutSuperclass<View>(this);
|
LayoutSuperclass<View>(this);
|
||||||
|
|
|
@ -36,6 +36,8 @@ class InspectableWebContentsView : public views::View {
|
||||||
return inspectable_web_contents_;
|
return inspectable_web_contents_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
views::WebView* contents_web_view() const { return contents_web_view_; }
|
||||||
|
|
||||||
// The delegate manages its own life.
|
// The delegate manages its own life.
|
||||||
void SetDelegate(InspectableWebContentsViewDelegate* delegate) {
|
void SetDelegate(InspectableWebContentsViewDelegate* delegate) {
|
||||||
delegate_ = delegate;
|
delegate_ = delegate;
|
||||||
|
@ -67,8 +69,9 @@ class InspectableWebContentsView : public views::View {
|
||||||
|
|
||||||
std::unique_ptr<views::Widget> devtools_window_;
|
std::unique_ptr<views::Widget> devtools_window_;
|
||||||
raw_ptr<views::WebView> devtools_window_web_view_ = nullptr;
|
raw_ptr<views::WebView> devtools_window_web_view_ = nullptr;
|
||||||
raw_ptr<views::View> contents_web_view_ = nullptr;
|
|
||||||
raw_ptr<views::WebView> devtools_web_view_ = nullptr;
|
raw_ptr<views::WebView> devtools_web_view_ = nullptr;
|
||||||
|
raw_ptr<views::WebView> contents_web_view_ = nullptr;
|
||||||
|
raw_ptr<views::View> contents_view_ = nullptr;
|
||||||
|
|
||||||
DevToolsContentsResizingStrategy strategy_;
|
DevToolsContentsResizingStrategy strategy_;
|
||||||
bool devtools_visible_ = false;
|
bool devtools_visible_ = false;
|
||||||
|
|
|
@ -3,7 +3,7 @@ import * as path from 'node:path';
|
||||||
import { BrowserView, BrowserWindow, screen, webContents } from 'electron/main';
|
import { BrowserView, BrowserWindow, screen, webContents } from 'electron/main';
|
||||||
import { closeWindow } from './lib/window-helpers';
|
import { closeWindow } from './lib/window-helpers';
|
||||||
import { defer, ifit, startRemoteControlApp } from './lib/spec-helpers';
|
import { defer, ifit, startRemoteControlApp } from './lib/spec-helpers';
|
||||||
import { ScreenCapture } from './lib/screen-helpers';
|
import { ScreenCapture, hasCapturableScreen } from './lib/screen-helpers';
|
||||||
import { once } from 'node:events';
|
import { once } from 'node:events';
|
||||||
|
|
||||||
describe('BrowserView module', () => {
|
describe('BrowserView module', () => {
|
||||||
|
@ -75,8 +75,7 @@ describe('BrowserView module', () => {
|
||||||
}).not.to.throw();
|
}).not.to.throw();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Linux and arm64 platforms (WOA and macOS) do not return any capture sources
|
ifit(hasCapturableScreen())('sets the background color to transparent if none is set', async () => {
|
||||||
ifit(process.platform === 'darwin' && process.arch === 'x64')('sets the background color to transparent if none is set', async () => {
|
|
||||||
const display = screen.getPrimaryDisplay();
|
const display = screen.getPrimaryDisplay();
|
||||||
const WINDOW_BACKGROUND_COLOR = '#55ccbb';
|
const WINDOW_BACKGROUND_COLOR = '#55ccbb';
|
||||||
|
|
||||||
|
@ -90,12 +89,11 @@ describe('BrowserView module', () => {
|
||||||
w.setBrowserView(view);
|
w.setBrowserView(view);
|
||||||
await view.webContents.loadURL('data:text/html,hello there');
|
await view.webContents.loadURL('data:text/html,hello there');
|
||||||
|
|
||||||
const screenCapture = await ScreenCapture.createForDisplay(display);
|
const screenCapture = new ScreenCapture(display);
|
||||||
await screenCapture.expectColorAtCenterMatches(WINDOW_BACKGROUND_COLOR);
|
await screenCapture.expectColorAtCenterMatches(WINDOW_BACKGROUND_COLOR);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Linux and arm64 platforms (WOA and macOS) do not return any capture sources
|
ifit(hasCapturableScreen())('successfully applies the background color', async () => {
|
||||||
ifit(process.platform === 'darwin' && process.arch === 'x64')('successfully applies the background color', async () => {
|
|
||||||
const WINDOW_BACKGROUND_COLOR = '#55ccbb';
|
const WINDOW_BACKGROUND_COLOR = '#55ccbb';
|
||||||
const VIEW_BACKGROUND_COLOR = '#ff00ff';
|
const VIEW_BACKGROUND_COLOR = '#ff00ff';
|
||||||
const display = screen.getPrimaryDisplay();
|
const display = screen.getPrimaryDisplay();
|
||||||
|
@ -111,7 +109,7 @@ describe('BrowserView module', () => {
|
||||||
w.setBackgroundColor(VIEW_BACKGROUND_COLOR);
|
w.setBackgroundColor(VIEW_BACKGROUND_COLOR);
|
||||||
await view.webContents.loadURL('data:text/html,hello there');
|
await view.webContents.loadURL('data:text/html,hello there');
|
||||||
|
|
||||||
const screenCapture = await ScreenCapture.createForDisplay(display);
|
const screenCapture = new ScreenCapture(display);
|
||||||
await screenCapture.expectColorAtCenterMatches(VIEW_BACKGROUND_COLOR);
|
await screenCapture.expectColorAtCenterMatches(VIEW_BACKGROUND_COLOR);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -6510,8 +6510,8 @@ describe('BrowserWindow module', () => {
|
||||||
expect(w.getBounds()).to.deep.equal(newBounds);
|
expect(w.getBounds()).to.deep.equal(newBounds);
|
||||||
});
|
});
|
||||||
|
|
||||||
// FIXME(codebytere): figure out why these are failing on macOS arm64.
|
// FIXME(codebytere): figure out why these are failing on MAS arm64.
|
||||||
ifit(process.platform === 'darwin' && process.arch !== 'arm64')('should not display a visible background', async () => {
|
ifit(hasCapturableScreen() && !(process.mas && process.arch === 'arm64'))('should not display a visible background', async () => {
|
||||||
const display = screen.getPrimaryDisplay();
|
const display = screen.getPrimaryDisplay();
|
||||||
|
|
||||||
const backgroundWindow = new BrowserWindow({
|
const backgroundWindow = new BrowserWindow({
|
||||||
|
@ -6534,9 +6534,7 @@ describe('BrowserWindow module', () => {
|
||||||
const colorFile = path.join(__dirname, 'fixtures', 'pages', 'half-background-color.html');
|
const colorFile = path.join(__dirname, 'fixtures', 'pages', 'half-background-color.html');
|
||||||
await foregroundWindow.loadFile(colorFile);
|
await foregroundWindow.loadFile(colorFile);
|
||||||
|
|
||||||
await setTimeout(1000);
|
const screenCapture = new ScreenCapture(display);
|
||||||
|
|
||||||
const screenCapture = await ScreenCapture.createForDisplay(display);
|
|
||||||
await screenCapture.expectColorAtPointOnDisplayMatches(
|
await screenCapture.expectColorAtPointOnDisplayMatches(
|
||||||
HexColors.GREEN,
|
HexColors.GREEN,
|
||||||
(size) => ({
|
(size) => ({
|
||||||
|
@ -6553,8 +6551,8 @@ describe('BrowserWindow module', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// FIXME(codebytere): figure out why these are failing on macOS arm64.
|
// FIXME(codebytere): figure out why these are failing on MAS arm64.
|
||||||
ifit(process.platform === 'darwin' && process.arch !== 'arm64')('Allows setting a transparent window via CSS', async () => {
|
ifit(hasCapturableScreen() && !(process.mas && process.arch === 'arm64'))('Allows setting a transparent window via CSS', async () => {
|
||||||
const display = screen.getPrimaryDisplay();
|
const display = screen.getPrimaryDisplay();
|
||||||
|
|
||||||
const backgroundWindow = new BrowserWindow({
|
const backgroundWindow = new BrowserWindow({
|
||||||
|
@ -6580,14 +6578,11 @@ describe('BrowserWindow module', () => {
|
||||||
foregroundWindow.loadFile(path.join(__dirname, 'fixtures', 'pages', 'css-transparent.html'));
|
foregroundWindow.loadFile(path.join(__dirname, 'fixtures', 'pages', 'css-transparent.html'));
|
||||||
await once(ipcMain, 'set-transparent');
|
await once(ipcMain, 'set-transparent');
|
||||||
|
|
||||||
await setTimeout(1000);
|
const screenCapture = new ScreenCapture(display);
|
||||||
|
|
||||||
const screenCapture = await ScreenCapture.createForDisplay(display);
|
|
||||||
await screenCapture.expectColorAtCenterMatches(HexColors.PURPLE);
|
await screenCapture.expectColorAtCenterMatches(HexColors.PURPLE);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Linux and arm64 platforms (WOA and macOS) do not return any capture sources
|
ifit(hasCapturableScreen())('should not make background transparent if falsy', async () => {
|
||||||
ifit(process.platform === 'darwin' && process.arch === 'x64')('should not make background transparent if falsy', async () => {
|
|
||||||
const display = screen.getPrimaryDisplay();
|
const display = screen.getPrimaryDisplay();
|
||||||
|
|
||||||
for (const transparent of [false, undefined]) {
|
for (const transparent of [false, undefined]) {
|
||||||
|
@ -6599,8 +6594,7 @@ describe('BrowserWindow module', () => {
|
||||||
await once(window, 'show');
|
await once(window, 'show');
|
||||||
await window.webContents.loadURL('data:text/html,<head><meta name="color-scheme" content="dark"></head>');
|
await window.webContents.loadURL('data:text/html,<head><meta name="color-scheme" content="dark"></head>');
|
||||||
|
|
||||||
await setTimeout(1000);
|
const screenCapture = new ScreenCapture(display);
|
||||||
const screenCapture = await ScreenCapture.createForDisplay(display);
|
|
||||||
// color-scheme is set to dark so background should not be white
|
// color-scheme is set to dark so background should not be white
|
||||||
await screenCapture.expectColorAtCenterDoesNotMatch(HexColors.WHITE);
|
await screenCapture.expectColorAtCenterDoesNotMatch(HexColors.WHITE);
|
||||||
|
|
||||||
|
@ -6612,8 +6606,7 @@ describe('BrowserWindow module', () => {
|
||||||
describe('"backgroundColor" option', () => {
|
describe('"backgroundColor" option', () => {
|
||||||
afterEach(closeAllWindows);
|
afterEach(closeAllWindows);
|
||||||
|
|
||||||
// Linux/WOA doesn't return any capture sources.
|
ifit(hasCapturableScreen())('should display the set color', async () => {
|
||||||
ifit(process.platform === 'darwin')('should display the set color', async () => {
|
|
||||||
const display = screen.getPrimaryDisplay();
|
const display = screen.getPrimaryDisplay();
|
||||||
|
|
||||||
const w = new BrowserWindow({
|
const w = new BrowserWindow({
|
||||||
|
@ -6625,9 +6618,7 @@ describe('BrowserWindow module', () => {
|
||||||
w.loadURL('about:blank');
|
w.loadURL('about:blank');
|
||||||
await once(w, 'ready-to-show');
|
await once(w, 'ready-to-show');
|
||||||
|
|
||||||
await setTimeout(1000);
|
const screenCapture = new ScreenCapture(display);
|
||||||
|
|
||||||
const screenCapture = await ScreenCapture.createForDisplay(display);
|
|
||||||
await screenCapture.expectColorAtCenterMatches(HexColors.BLUE);
|
await screenCapture.expectColorAtCenterMatches(HexColors.BLUE);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -54,4 +54,15 @@ describe('View', () => {
|
||||||
w.contentView.addChildView(v2);
|
w.contentView.addChildView(v2);
|
||||||
expect(w.contentView.children).to.deep.equal([v3, v1, v2]);
|
expect(w.contentView.children).to.deep.equal([v3, v1, v2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('allows setting various border radius values', () => {
|
||||||
|
w = new BaseWindow({ show: false });
|
||||||
|
const v = new View();
|
||||||
|
w.setContentView(v);
|
||||||
|
v.setBorderRadius(10);
|
||||||
|
v.setBorderRadius(0);
|
||||||
|
v.setBorderRadius(-10);
|
||||||
|
v.setBorderRadius(9999999);
|
||||||
|
v.setBorderRadius(-9999999);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { closeAllWindows } from './lib/window-helpers';
|
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
|
import { BaseWindow, BrowserWindow, View, WebContentsView, webContents, screen } from 'electron/main';
|
||||||
import { BaseWindow, View, WebContentsView, webContents } from 'electron/main';
|
|
||||||
import { once } from 'node:events';
|
import { once } from 'node:events';
|
||||||
import { defer } from './lib/spec-helpers';
|
|
||||||
import { BrowserWindow } from 'electron';
|
import { closeAllWindows } from './lib/window-helpers';
|
||||||
|
import { defer, ifdescribe } from './lib/spec-helpers';
|
||||||
|
import { HexColors, ScreenCapture, hasCapturableScreen, nextFrameTime } from './lib/screen-helpers';
|
||||||
|
|
||||||
describe('WebContentsView', () => {
|
describe('WebContentsView', () => {
|
||||||
afterEach(closeAllWindows);
|
afterEach(closeAllWindows);
|
||||||
|
@ -224,4 +224,92 @@ describe('WebContentsView', () => {
|
||||||
expect(visibilityState).to.equal('visible');
|
expect(visibilityState).to.equal('visible');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('setBorderRadius', () => {
|
||||||
|
ifdescribe(hasCapturableScreen())('capture', () => {
|
||||||
|
let w: Electron.BaseWindow;
|
||||||
|
let v: Electron.WebContentsView;
|
||||||
|
let display: Electron.Display;
|
||||||
|
let corners: Electron.Point[];
|
||||||
|
|
||||||
|
const backgroundUrl = `data:text/html,<style>html{background:${encodeURIComponent(HexColors.GREEN)}}</style>`;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
display = screen.getPrimaryDisplay();
|
||||||
|
|
||||||
|
w = new BaseWindow({
|
||||||
|
...display.workArea,
|
||||||
|
show: true,
|
||||||
|
frame: false,
|
||||||
|
hasShadow: false,
|
||||||
|
backgroundColor: HexColors.BLUE,
|
||||||
|
roundedCorners: false
|
||||||
|
});
|
||||||
|
|
||||||
|
v = new WebContentsView();
|
||||||
|
w.setContentView(v);
|
||||||
|
v.setBorderRadius(100);
|
||||||
|
|
||||||
|
const readyForCapture = once(v.webContents, 'ready-to-show');
|
||||||
|
v.webContents.loadURL(backgroundUrl);
|
||||||
|
|
||||||
|
const inset = 10;
|
||||||
|
corners = [
|
||||||
|
{ x: display.workArea.x + inset, y: display.workArea.y + inset }, // top-left
|
||||||
|
{ x: display.workArea.x + display.workArea.width - inset, y: display.workArea.y + inset }, // top-right
|
||||||
|
{ x: display.workArea.x + display.workArea.width - inset, y: display.workArea.y + display.workArea.height - inset }, // bottom-right
|
||||||
|
{ x: display.workArea.x + inset, y: display.workArea.y + display.workArea.height - inset } // bottom-left
|
||||||
|
];
|
||||||
|
|
||||||
|
await readyForCapture;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
w.destroy();
|
||||||
|
w = v = null!;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render with cutout corners', async () => {
|
||||||
|
const screenCapture = new ScreenCapture(display);
|
||||||
|
|
||||||
|
for (const corner of corners) {
|
||||||
|
await screenCapture.expectColorAtPointOnDisplayMatches(HexColors.BLUE, () => corner);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Center should be WebContents page background color
|
||||||
|
await screenCapture.expectColorAtCenterMatches(HexColors.GREEN);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow resetting corners', async () => {
|
||||||
|
const corner = corners[0];
|
||||||
|
v.setBorderRadius(0);
|
||||||
|
|
||||||
|
await nextFrameTime();
|
||||||
|
const screenCapture = new ScreenCapture(display);
|
||||||
|
await screenCapture.expectColorAtPointOnDisplayMatches(HexColors.GREEN, () => corner);
|
||||||
|
await screenCapture.expectColorAtCenterMatches(HexColors.GREEN);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render when set before attached', async () => {
|
||||||
|
v = new WebContentsView();
|
||||||
|
v.setBorderRadius(100); // must set before
|
||||||
|
|
||||||
|
w.setContentView(v);
|
||||||
|
|
||||||
|
const readyForCapture = once(v.webContents, 'ready-to-show');
|
||||||
|
v.webContents.loadURL(backgroundUrl);
|
||||||
|
await readyForCapture;
|
||||||
|
|
||||||
|
const corner = corners[0];
|
||||||
|
const screenCapture = new ScreenCapture(display);
|
||||||
|
await screenCapture.expectColorAtPointOnDisplayMatches(HexColors.BLUE, () => corner);
|
||||||
|
await screenCapture.expectColorAtCenterMatches(HexColors.GREEN);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow setting when not attached', async () => {
|
||||||
|
const v = new WebContentsView();
|
||||||
|
v.setBorderRadius(100);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import { BrowserWindow, screen } from 'electron';
|
import { BrowserWindow, screen } from 'electron';
|
||||||
import { expect, assert } from 'chai';
|
import { expect, assert } from 'chai';
|
||||||
import { HexColors, ScreenCapture } from './lib/screen-helpers';
|
import { HexColors, ScreenCapture, hasCapturableScreen } from './lib/screen-helpers';
|
||||||
import { ifit, listen } from './lib/spec-helpers';
|
import { ifit, listen } from './lib/spec-helpers';
|
||||||
import { closeAllWindows } from './lib/window-helpers';
|
import { closeAllWindows } from './lib/window-helpers';
|
||||||
import { once } from 'node:events';
|
import { once } from 'node:events';
|
||||||
import { setTimeout as setTimeoutAsync } from 'node:timers/promises';
|
|
||||||
import * as http from 'node:http';
|
import * as http from 'node:http';
|
||||||
|
|
||||||
describe('webContents.setWindowOpenHandler', () => {
|
describe('webContents.setWindowOpenHandler', () => {
|
||||||
|
@ -201,8 +200,7 @@ describe('webContents.setWindowOpenHandler', () => {
|
||||||
expect(await browserWindow.webContents.executeJavaScript('42')).to.equal(42);
|
expect(await browserWindow.webContents.executeJavaScript('42')).to.equal(42);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Linux and arm64 platforms (WOA and macOS) do not return any capture sources
|
ifit(hasCapturableScreen())('should not make child window background transparent', async () => {
|
||||||
ifit(process.platform === 'darwin' && process.arch === 'x64')('should not make child window background transparent', async () => {
|
|
||||||
browserWindow.webContents.setWindowOpenHandler(() => ({ action: 'allow' }));
|
browserWindow.webContents.setWindowOpenHandler(() => ({ action: 'allow' }));
|
||||||
const didCreateWindow = once(browserWindow.webContents, 'did-create-window');
|
const didCreateWindow = once(browserWindow.webContents, 'did-create-window');
|
||||||
browserWindow.webContents.executeJavaScript("window.open('about:blank') && true");
|
browserWindow.webContents.executeJavaScript("window.open('about:blank') && true");
|
||||||
|
@ -210,8 +208,7 @@ describe('webContents.setWindowOpenHandler', () => {
|
||||||
const display = screen.getPrimaryDisplay();
|
const display = screen.getPrimaryDisplay();
|
||||||
childWindow.setBounds(display.bounds);
|
childWindow.setBounds(display.bounds);
|
||||||
await childWindow.webContents.executeJavaScript("const meta = document.createElement('meta'); meta.name = 'color-scheme'; meta.content = 'dark'; document.head.appendChild(meta); true;");
|
await childWindow.webContents.executeJavaScript("const meta = document.createElement('meta'); meta.name = 'color-scheme'; meta.content = 'dark'; document.head.appendChild(meta); true;");
|
||||||
await setTimeoutAsync(1000);
|
const screenCapture = new ScreenCapture(display);
|
||||||
const screenCapture = await ScreenCapture.createForDisplay(display);
|
|
||||||
// color-scheme is set to dark so background should not be white
|
// color-scheme is set to dark so background should not be white
|
||||||
await screenCapture.expectColorAtCenterDoesNotMatch(HexColors.WHITE);
|
await screenCapture.expectColorAtCenterDoesNotMatch(HexColors.WHITE);
|
||||||
});
|
});
|
||||||
|
|
|
@ -75,67 +75,72 @@ function areColorsSimilar (
|
||||||
return distance <= distanceThreshold;
|
return distance <= distanceThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
function imageCenter (image: NativeImage): Electron.Point {
|
function displayCenter (display: Electron.Display): Electron.Point {
|
||||||
const size = image.getSize();
|
|
||||||
return {
|
return {
|
||||||
x: size.width / 2,
|
x: display.size.width / 2,
|
||||||
y: size.height / 2
|
y: display.size.height / 2
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Resolve when approx. one frame has passed (30FPS) */
|
||||||
|
export async function nextFrameTime (): Promise<void> {
|
||||||
|
return await new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 1000 / 30);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utilities for creating and inspecting a screen capture.
|
* Utilities for creating and inspecting a screen capture.
|
||||||
*
|
*
|
||||||
|
* Set `PAUSE_CAPTURE_TESTS` env var to briefly pause during screen
|
||||||
|
* capture for easier inspection.
|
||||||
|
*
|
||||||
* NOTE: Not yet supported on Linux in CI due to empty sources list.
|
* NOTE: Not yet supported on Linux in CI due to empty sources list.
|
||||||
*/
|
*/
|
||||||
export class ScreenCapture {
|
export class ScreenCapture {
|
||||||
/** Use the async constructor `ScreenCapture.create()` instead. */
|
/** Timeout to wait for expected color to match. */
|
||||||
private constructor (image: NativeImage) {
|
static TIMEOUT = 3000;
|
||||||
this.image = image;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async create (): Promise<ScreenCapture> {
|
constructor (display?: Electron.Display) {
|
||||||
const display = screen.getPrimaryDisplay();
|
this.display = display || screen.getPrimaryDisplay();
|
||||||
return ScreenCapture._createImpl(display);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async createForDisplay (
|
|
||||||
display: Electron.Display
|
|
||||||
): Promise<ScreenCapture> {
|
|
||||||
return ScreenCapture._createImpl(display);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async expectColorAtCenterMatches (hexColor: string) {
|
public async expectColorAtCenterMatches (hexColor: string) {
|
||||||
return this._expectImpl(imageCenter(this.image), hexColor, true);
|
return this._expectImpl(displayCenter(this.display), hexColor, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async expectColorAtCenterDoesNotMatch (hexColor: string) {
|
public async expectColorAtCenterDoesNotMatch (hexColor: string) {
|
||||||
return this._expectImpl(imageCenter(this.image), hexColor, false);
|
return this._expectImpl(displayCenter(this.display), hexColor, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async expectColorAtPointOnDisplayMatches (
|
public async expectColorAtPointOnDisplayMatches (
|
||||||
hexColor: string,
|
hexColor: string,
|
||||||
findPoint: (displaySize: Electron.Size) => Electron.Point
|
findPoint: (displaySize: Electron.Size) => Electron.Point
|
||||||
) {
|
) {
|
||||||
return this._expectImpl(findPoint(this.image.getSize()), hexColor, true);
|
return this._expectImpl(findPoint(this.display.size), hexColor, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async _createImpl (display: Electron.Display) {
|
private async captureFrame (): Promise<NativeImage> {
|
||||||
const sources = await desktopCapturer.getSources({
|
const sources = await desktopCapturer.getSources({
|
||||||
types: ['screen'],
|
types: ['screen'],
|
||||||
thumbnailSize: display.size
|
thumbnailSize: this.display.size
|
||||||
});
|
});
|
||||||
|
|
||||||
const captureSource = sources.find(
|
const captureSource = sources.find(
|
||||||
(source) => source.display_id === display.id.toString()
|
(source) => source.display_id === this.display.id.toString()
|
||||||
);
|
);
|
||||||
if (captureSource === undefined) {
|
if (captureSource === undefined) {
|
||||||
const displayIds = sources.map((source) => source.display_id).join(', ');
|
const displayIds = sources.map((source) => source.display_id).join(', ');
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unable to find screen capture for display '${display.id}'\n\tAvailable displays: ${displayIds}`
|
`Unable to find screen capture for display '${this.display.id}'\n\tAvailable displays: ${displayIds}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ScreenCapture(captureSource.thumbnail);
|
if (process.env.PAUSE_CAPTURE_TESTS) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
||||||
|
}
|
||||||
|
|
||||||
|
return captureSource.thumbnail;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _expectImpl (
|
private async _expectImpl (
|
||||||
|
@ -143,15 +148,29 @@ export class ScreenCapture {
|
||||||
expectedColor: string,
|
expectedColor: string,
|
||||||
matchIsExpected: boolean
|
matchIsExpected: boolean
|
||||||
) {
|
) {
|
||||||
const actualColor = getPixelColor(this.image, point);
|
let frame: Electron.NativeImage;
|
||||||
const colorsMatch = areColorsSimilar(expectedColor, actualColor);
|
let actualColor: string;
|
||||||
const gotExpectedResult = matchIsExpected ? colorsMatch : !colorsMatch;
|
let gotExpectedResult: boolean = false;
|
||||||
|
const expiration = Date.now() + ScreenCapture.TIMEOUT;
|
||||||
|
|
||||||
|
// Continuously capture frames until we either see the expected result or
|
||||||
|
// reach a timeout. This helps avoid flaky tests in which a short waiting
|
||||||
|
// period is required for the expected result.
|
||||||
|
do {
|
||||||
|
frame = await this.captureFrame();
|
||||||
|
actualColor = getPixelColor(frame, point);
|
||||||
|
const colorsMatch = areColorsSimilar(expectedColor, actualColor);
|
||||||
|
gotExpectedResult = matchIsExpected ? colorsMatch : !colorsMatch;
|
||||||
|
if (gotExpectedResult) break;
|
||||||
|
|
||||||
|
await nextFrameTime(); // limit framerate
|
||||||
|
} while (Date.now() < expiration);
|
||||||
|
|
||||||
if (!gotExpectedResult) {
|
if (!gotExpectedResult) {
|
||||||
// Save the image as an artifact for better debugging
|
// Save the image as an artifact for better debugging
|
||||||
const artifactName = await createArtifactWithRandomId(
|
const artifactName = await createArtifactWithRandomId(
|
||||||
(id) => `color-mismatch-${id}.png`,
|
(id) => `color-mismatch-${id}.png`,
|
||||||
this.image.toPNG()
|
frame.toPNG()
|
||||||
);
|
);
|
||||||
throw new AssertionError(
|
throw new AssertionError(
|
||||||
`Expected color at (${point.x}, ${point.y}) to ${
|
`Expected color at (${point.x}, ${point.y}) to ${
|
||||||
|
@ -161,7 +180,7 @@ export class ScreenCapture {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private image: NativeImage;
|
private display: Electron.Display;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -174,5 +193,5 @@ export class ScreenCapture {
|
||||||
* - Win32 x64: virtual screen display is 0x0
|
* - Win32 x64: virtual screen display is 0x0
|
||||||
*/
|
*/
|
||||||
export const hasCapturableScreen = () => {
|
export const hasCapturableScreen = () => {
|
||||||
return process.platform === 'darwin';
|
return process.env.CI ? process.platform === 'darwin' : true;
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,7 +9,7 @@ import * as http from 'node:http';
|
||||||
import * as auth from 'basic-auth';
|
import * as auth from 'basic-auth';
|
||||||
import { once } from 'node:events';
|
import { once } from 'node:events';
|
||||||
import { setTimeout } from 'node:timers/promises';
|
import { setTimeout } from 'node:timers/promises';
|
||||||
import { HexColors, ScreenCapture } from './lib/screen-helpers';
|
import { HexColors, ScreenCapture, hasCapturableScreen } from './lib/screen-helpers';
|
||||||
|
|
||||||
declare let WebView: any;
|
declare let WebView: any;
|
||||||
const features = process._linkedBinding('electron_common_features');
|
const features = process._linkedBinding('electron_common_features');
|
||||||
|
@ -796,41 +796,32 @@ describe('<webview> tag', function () {
|
||||||
});
|
});
|
||||||
after(() => w.close());
|
after(() => w.close());
|
||||||
|
|
||||||
// Linux and arm64 platforms (WOA and macOS) do not return any capture sources
|
ifit(hasCapturableScreen())('is transparent by default', async () => {
|
||||||
ifit(process.platform === 'darwin' && process.arch === 'x64')('is transparent by default', async () => {
|
|
||||||
await loadWebView(w.webContents, {
|
await loadWebView(w.webContents, {
|
||||||
src: 'data:text/html,foo'
|
src: 'data:text/html,foo'
|
||||||
});
|
});
|
||||||
|
|
||||||
await setTimeout(1000);
|
const screenCapture = new ScreenCapture();
|
||||||
|
|
||||||
const screenCapture = await ScreenCapture.create();
|
|
||||||
await screenCapture.expectColorAtCenterMatches(WINDOW_BACKGROUND_COLOR);
|
await screenCapture.expectColorAtCenterMatches(WINDOW_BACKGROUND_COLOR);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Linux and arm64 platforms (WOA and macOS) do not return any capture sources
|
ifit(hasCapturableScreen())('remains transparent when set', async () => {
|
||||||
ifit(process.platform === 'darwin' && process.arch === 'x64')('remains transparent when set', async () => {
|
|
||||||
await loadWebView(w.webContents, {
|
await loadWebView(w.webContents, {
|
||||||
src: 'data:text/html,foo',
|
src: 'data:text/html,foo',
|
||||||
webpreferences: 'transparent=yes'
|
webpreferences: 'transparent=yes'
|
||||||
});
|
});
|
||||||
|
|
||||||
await setTimeout(1000);
|
const screenCapture = new ScreenCapture();
|
||||||
|
|
||||||
const screenCapture = await ScreenCapture.create();
|
|
||||||
await screenCapture.expectColorAtCenterMatches(WINDOW_BACKGROUND_COLOR);
|
await screenCapture.expectColorAtCenterMatches(WINDOW_BACKGROUND_COLOR);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Linux and arm64 platforms (WOA and macOS) do not return any capture sources
|
ifit(hasCapturableScreen())('can disable transparency', async () => {
|
||||||
ifit(process.platform === 'darwin' && process.arch === 'x64')('can disable transparency', async () => {
|
|
||||||
await loadWebView(w.webContents, {
|
await loadWebView(w.webContents, {
|
||||||
src: 'data:text/html,foo',
|
src: 'data:text/html,foo',
|
||||||
webpreferences: 'transparent=no'
|
webpreferences: 'transparent=no'
|
||||||
});
|
});
|
||||||
|
|
||||||
await setTimeout(1000);
|
const screenCapture = new ScreenCapture();
|
||||||
|
|
||||||
const screenCapture = await ScreenCapture.create();
|
|
||||||
await screenCapture.expectColorAtCenterMatches(HexColors.WHITE);
|
await screenCapture.expectColorAtCenterMatches(HexColors.WHITE);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Загрузка…
Ссылка в новой задаче