Compare commits

...

7 Commits

Author SHA1 Message Date
trop[bot]
b91d3d33fa fix: potential std::stoi crash in Windows Toasts (#49953)
fix: potential std::stoi crash in Windows Toasts

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-02-25 22:58:28 -08:00
trop[bot]
de2ebae944 build: exit upload with error code if github upload fails (#49942)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: John Kleinschmidt <kleinschmidtorama@gmail.com>
2026-02-25 14:32:00 -05:00
trop[bot]
6dcbf464f4 feat: add support for --experimental-transform-types (#49882)
* feat: add support for `--experimental-transform-types`

Co-authored-by: Niklas Wenzel <dev@nikwen.de>

* chore: add tests

Co-authored-by: Niklas Wenzel <dev@nikwen.de>

* docs: add `--experimental-transform-types` to docs

Co-authored-by: Niklas Wenzel <dev@nikwen.de>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Niklas Wenzel <dev@nikwen.de>
2026-02-25 12:56:35 -05:00
trop[bot]
3a2b7d3720 feat: Shadows and CSD for frameless windows on Wayland (#49885)
feat: Shadows and CSD for frameless windows on Wayland (#49295)

* fix window sizing and content sizing on Linux when CSD is in use

* fixed size constraints

* layout helper

* CSD shadows for frameless windows on Linux

* simplify min/max size calculation

* use base window size for min/max

* respect HasShadow option

* moved windows min/max size overrides

* add newline at end of file

* fix setting background color for frameless csd windows

* fix wco positioning nad sizing to match prod

* safety improvements

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Mitchell Cohen <mitch.cohen@me.com>
2026-02-25 12:53:49 -05:00
trop[bot]
f86822163f fix: crash after win.showAllTabs() new tab (#49933)
fix: crash after win.showAllTabs new tab

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-02-25 11:54:26 -05:00
trop[bot]
e600ad9dac docs: mark "Show hidden files" file dialog setting as deprecated on Linux (#49948)
* fix: don't overwrite "Show hidden files" setting on Linux/GTK

Co-authored-by: zonescape <44441590+zonescape@users.noreply.github.com>

* docs: deprecate showHiddenFiles property in dialogs on Linux

Co-authored-by: zonescape <44441590+zonescape@users.noreply.github.com>

* docs: mark Electron 42 as the removal date for this feature

Co-authored-by: Charles Kerr <charles@charleskerr.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: zonescape <44441590+zonescape@users.noreply.github.com>
Co-authored-by: Charles Kerr <charles@charleskerr.com>
2026-02-25 11:40:50 -05:00
trop[bot]
f3f3e113ab build: fix Chromium roll linting merge base determination in CI (#49946)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: David Sanders <dsanders11@ucsbalum.com>
2026-02-25 11:40:06 -05:00
26 changed files with 530 additions and 179 deletions

View File

@@ -354,6 +354,11 @@ Affects the default output directory of [v8.setHeapSnapshotNearHeapLimit](https:
Disable exposition of [Navigator API][] on the global scope from Node.js.
### `--experimental-transform-types`
Enables the [transformation](https://nodejs.org/api/typescript.html#type-stripping)
of TypeScript-only syntax into JavaScript code.
## Chromium Flags
There isn't a documented list of all Chromium switches, but there are a few ways to find them.

View File

@@ -30,7 +30,7 @@ The `dialog` module has the following methods:
* `openFile` - Allow files to be selected.
* `openDirectory` - Allow directories to be selected.
* `multiSelections` - Allow multiple paths to be selected.
* `showHiddenFiles` - Show hidden files in dialog.
* `showHiddenFiles` _macOS_ _Windows_ _Deprecated_ - Show hidden files in dialog. Deprecated on Linux.
* `createDirectory` _macOS_ - Allow creating new directories from dialog.
* `promptToCreate` _Windows_ - Prompt for creation if the file path entered
in the dialog does not exist. This does not actually create the file at
@@ -102,7 +102,7 @@ dialog.showOpenDialogSync(mainWindow, {
* `openFile` - Allow files to be selected.
* `openDirectory` - Allow directories to be selected.
* `multiSelections` - Allow multiple paths to be selected.
* `showHiddenFiles` - Show hidden files in dialog.
* `showHiddenFiles` _macOS_ _Windows_ _Deprecated_ - Show hidden files in dialog. Deprecated on Linux.
* `createDirectory` _macOS_ - Allow creating new directories from dialog.
* `promptToCreate` _Windows_ - Prompt for creation if the file path entered
in the dialog does not exist. This does not actually create the file at
@@ -185,7 +185,7 @@ dialog.showOpenDialog(mainWindow, {
* `showsTagField` boolean (optional) _macOS_ - Show the tags input box,
defaults to `true`.
* `properties` string[]&#32;(optional)
* `showHiddenFiles` - Show hidden files in dialog.
* `showHiddenFiles` _macOS_ _Windows_ _Deprecated_ - Show hidden files in dialog. Deprecated on Linux.
* `createDirectory` _macOS_ - Allow creating new directories from dialog.
* `treatPackageAsDirectory` _macOS_ - Treat packages, such as `.app` folders,
as a directory instead of a file.
@@ -215,7 +215,7 @@ The `filters` specifies an array of file types that can be displayed, see
displayed in front of the filename text field.
* `showsTagField` boolean (optional) _macOS_ - Show the tags input box, defaults to `true`.
* `properties` string[]&#32;(optional)
* `showHiddenFiles` - Show hidden files in dialog.
* `showHiddenFiles` _macOS_ _Windows_ _Deprecated_ - Show hidden files in dialog. Deprecated on Linux.
* `createDirectory` _macOS_ - Allow creating new directories from dialog.
* `treatPackageAsDirectory` _macOS_ - Treat packages, such as `.app` folders,
as a directory instead of a file.

View File

@@ -41,6 +41,12 @@ your preload script and expose it using the [contextBridge](https://www.electron
Debug symbols for MacOS (dSYM) now use xz compression in order to handle larger file sizes. `dsym.zip` files are now
`dsym.tar.xz` files. End users using debug symbols may need to update their zip utilities.
### Deprecated: `showHiddenFiles` in Dialogs on Linux
This property will still be honored on macOS and Windows, but support on Linux
will be removed in Electron 42. GTK intends for this to be a user choice rather
than an app choice and has removed the API to do this programmatically.
## Planned Breaking API Changes (39.0)
### Deprecated: `--host-rules` command line switch

View File

@@ -50,6 +50,8 @@ filenames = {
"shell/browser/ui/views/caption_button_placeholder_container.h",
"shell/browser/ui/views/client_frame_view_linux.cc",
"shell/browser/ui/views/client_frame_view_linux.h",
"shell/browser/ui/views/linux_frame_layout.cc",
"shell/browser/ui/views/linux_frame_layout.h",
"shell/common/application_info_linux.cc",
"shell/common/language_util_linux.cc",
"shell/common/node_bindings_linux.cc",

View File

@@ -124,7 +124,7 @@ async function main () {
// Get the merge base with the target branch
let mergeBase;
try {
mergeBase = execSync(`git merge-base ${currentBranch} origin/${targetBranch}`, {
mergeBase = execSync(`git merge-base HEAD origin/${targetBranch}`, {
cwd: ELECTRON_DIR,
encoding: 'utf8'
}).trim();

View File

@@ -368,6 +368,9 @@ def upload_io_to_github(release, filename, filepath, version):
for c in iter(lambda: upload_process.stdout.read(1), b""):
sys.stdout.buffer.write(c)
sys.stdout.flush()
upload_process.wait()
if upload_process.returncode != 0:
sys.exit(upload_process.returncode)
if "GITHUB_OUTPUT" in os.environ:
output_path = os.environ["GITHUB_OUTPUT"]

View File

@@ -192,8 +192,7 @@ void NativeWindow::InitFromOptions(const gin_helper::Dictionary& options) {
if (bool val; options.Get(options::kMovable, &val))
SetMovable(val);
if (bool val; options.Get(options::kHasShadow, &val))
SetHasShadow(val);
SetHasShadow(options.ValueOrDefault(options::kHasShadow, true));
if (double val; options.Get(options::kOpacity, &val))
SetOpacity(val);

View File

@@ -65,6 +65,7 @@
#include "shell/browser/linux/x11_util.h"
#include "shell/browser/ui/electron_desktop_window_tree_host_linux.h"
#include "shell/browser/ui/views/client_frame_view_linux.h"
#include "shell/browser/ui/views/linux_frame_layout.h"
#include "shell/browser/ui/views/native_frame_view.h"
#include "shell/browser/ui/views/opaque_frame_view.h"
#include "shell/common/platform_util.h"
@@ -262,6 +263,8 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options,
gfx::Rect bounds{0, 0, width, height};
widget_size_ = bounds.size();
has_shadow_ = options.ValueOrDefault(options::kHasShadow, true);
widget()->AddObserver(this);
using InitParams = views::Widget::InitParams;
@@ -418,7 +421,7 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options,
}
gfx::Size size = bounds.size();
if (has_frame() && use_content_size_)
if ((has_frame() || has_client_frame()) && use_content_size_)
size = ContentBoundsToWindowBounds(gfx::Rect(size)).size();
widget()->CenterWindow(size);
@@ -1275,18 +1278,27 @@ void NativeWindowViews::SetBackgroundColor(SkColor background_color) {
DeleteObject((HBRUSH)previous_brush);
InvalidateRect(GetAcceleratedWidget(), nullptr, 1);
#endif
widget()->GetCompositor()->SetBackgroundColor(background_color);
SkColor compositor_color = background_color;
#if BUILDFLAG(IS_LINUX)
// Widget background needs to stay transparent for CSD shadow regions.
LinuxFrameLayout* frame_layout = GetLinuxFrameLayout();
const bool uses_csd =
frame_layout && frame_layout->SupportsClientFrameShadow();
if (transparent() || uses_csd)
compositor_color = SK_ColorTRANSPARENT;
#endif
widget()->GetCompositor()->SetBackgroundColor(compositor_color);
}
void NativeWindowViews::SetHasShadow(bool has_shadow) {
has_shadow_ = has_shadow;
wm::SetShadowElevation(GetNativeWindow(),
has_shadow ? wm::kShadowElevationInactiveWindow
: wm::kShadowElevationNone);
}
bool NativeWindowViews::HasShadow() const {
return GetNativeWindow()->GetProperty(wm::kShadowElevationKey) !=
wm::kShadowElevationNone;
return has_shadow_;
}
void NativeWindowViews::SetOpacity(const double opacity) {
@@ -1688,7 +1700,7 @@ gfx::Rect NativeWindowViews::WidgetToLogicalBounds(
gfx::Rect NativeWindowViews::ContentBoundsToWindowBounds(
const gfx::Rect& bounds) const {
if (!has_frame())
if (!has_frame() && !has_client_frame())
return bounds;
gfx::Rect window_bounds(bounds);
@@ -1715,7 +1727,7 @@ gfx::Rect NativeWindowViews::ContentBoundsToWindowBounds(
gfx::Rect NativeWindowViews::WindowBoundsToContentBounds(
const gfx::Rect& bounds) const {
if (!has_frame())
if (!has_frame() && !has_client_frame())
return bounds;
gfx::Rect content_bounds(bounds);
@@ -1920,15 +1932,10 @@ std::unique_ptr<views::FrameView> NativeWindowViews::CreateFrameView(
}
#if BUILDFLAG(IS_LINUX)
electron::ClientFrameViewLinux* NativeWindowViews::GetClientFrameViewLinux() {
// Check to make sure this window's non-client frame view is a
// ClientFrameViewLinux. If either has_frame() or has_client_frame()
// are false, it will be an OpaqueFrameView or NativeFrameView instead.
// See NativeWindowViews::CreateFrameView.
if (!has_frame() || !has_client_frame())
return {};
return static_cast<ClientFrameViewLinux*>(
LinuxFrameLayout* NativeWindowViews::GetLinuxFrameLayout() {
auto* view = views::AsViewClass<FramelessView>(
widget()->non_client_view()->frame_view());
return view ? view->GetLinuxFrameLayout() : nullptr;
}
#endif

View File

@@ -35,6 +35,7 @@ namespace electron {
#if BUILDFLAG(IS_LINUX)
class ClientFrameViewLinux;
class GlobalMenuBarX11;
class LinuxFrameLayout;
#endif
#if BUILDFLAG(SUPPORTS_OZONE_X11)
@@ -198,9 +199,7 @@ class NativeWindowViews : public NativeWindow,
SkColor overlay_symbol_color() const { return overlay_symbol_color_; }
#if BUILDFLAG(IS_LINUX)
// returns the ClientFrameViewLinux iff that is our FrameView type,
// nullptr otherwise.
ClientFrameViewLinux* GetClientFrameViewLinux();
LinuxFrameLayout* GetLinuxFrameLayout();
#endif
private:
@@ -367,6 +366,7 @@ class NativeWindowViews : public NativeWindow,
bool maximizable_ = true;
bool minimizable_ = true;
bool fullscreenable_ = true;
bool has_shadow_ = true;
gfx::Size widget_size_;
double opacity_ = 1.0;
bool widget_destroyed_ = false;

View File

@@ -371,7 +371,7 @@ void HandleToastActivation(const std::wstring& invoked_args,
int action_index = -1;
if (!action_index_str.empty()) {
action_index = std::stoi(action_index_str);
base::StringToInt(base::WideToUTF8(action_index_str), &action_index);
}
std::string reply_text;

View File

@@ -35,6 +35,41 @@ int ScopedDisableResize::disable_resize_ = 0;
typedef void (*MouseDownImpl)(id, SEL, NSEvent*);
// Work around an Apple bug where the visual tab picker's
// grid animation creates NSLayoutConstraints against nil layout anchors,
// crashing in NSVisualTabPickerShadowTileView. This happens when a new tabbed
// window is created while the tab picker is open — the "+" tile (and possibly
// others) have broken internal state. Rather than patching individual tile
// animation methods, short-circuit the entire grid animation by swizzling
// NSVisualTabPickerGridView's -startGridAnimation:completionHandler: to
// immediately invoke the completion handler without running the animation.
typedef void (*StartGridAnimationIMP)(id, SEL, id, id);
static StartGridAnimationIMP g_orig_startGridAnimation = nullptr;
static void Patched_startGridAnimation(id self,
SEL _cmd,
id animation,
void (^completionHandler)(void)) {
if (completionHandler)
completionHandler();
}
static void SwizzleTabPickerGridAnimation() {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class cls = NSClassFromString(@"NSVisualTabPickerGridView");
if (!cls)
return;
SEL sel = @selector(startGridAnimation:completionHandler:);
Method method = class_getInstanceMethod(cls, sel);
if (!method)
return;
g_orig_startGridAnimation =
(StartGridAnimationIMP)method_getImplementation(method);
method_setImplementation(method, (IMP)Patched_startGridAnimation);
});
}
namespace {
MouseDownImpl g_nsthemeframe_mousedown;
MouseDownImpl g_nsnextstepframe_mousedown;
@@ -125,6 +160,7 @@ void SwizzleSwipeWithEvent(NSView* view, SEL swiz_selector) {
- (id)initWithShell:(electron::NativeWindowMac*)shell
styleMask:(NSUInteger)styleMask {
SwizzleTabPickerGridAnimation();
if ((self = [super initWithContentRect:ui::kWindowSizeDeterminedLater
styleMask:styleMask
backing:NSBackingStoreBuffered

View File

@@ -16,7 +16,7 @@
#include "shell/browser/linux/x11_util.h"
#include "shell/browser/native_window_features.h"
#include "shell/browser/native_window_views.h"
#include "shell/browser/ui/views/client_frame_view_linux.h"
#include "shell/browser/ui/views/linux_frame_layout.h"
#include "third_party/skia/include/core/SkRegion.h"
#include "ui/aura/window_delegate.h"
#include "ui/base/hit_test.h"
@@ -85,11 +85,11 @@ gfx::Insets ElectronDesktopWindowTreeHostLinux::CalculateInsetsInDIP(
return gfx::Insets();
}
auto* const view = native_window_view_->GetClientFrameViewLinux();
if (!view)
auto* const layout = native_window_view_->GetLinuxFrameLayout();
if (!layout)
return {};
gfx::Insets insets = view->RestoredFrameBorderInsets();
gfx::Insets insets = layout->RestoredFrameBorderInsets();
return insets;
}
@@ -117,14 +117,14 @@ void ElectronDesktopWindowTreeHostLinux::OnWindowStateChanged(
void ElectronDesktopWindowTreeHostLinux::OnWindowTiledStateChanged(
ui::WindowTiledEdges new_tiled_edges) {
if (auto* const view = native_window_view_->GetClientFrameViewLinux()) {
if (auto* layout = native_window_view_->GetLinuxFrameLayout()) {
// GNOME on Ubuntu reports all edges as tiled
// even if the window is only half-tiled so do not trust individual edge
// values.
bool maximized = native_window_view_->IsMaximized();
bool tiled = new_tiled_edges.top || new_tiled_edges.left ||
new_tiled_edges.bottom || new_tiled_edges.right;
view->set_tiled(tiled && !maximized);
layout->set_tiled(tiled && !maximized);
}
UpdateFrameHints();
ScheduleRelayout();
@@ -180,15 +180,14 @@ void ElectronDesktopWindowTreeHostLinux::OnDeviceScaleFactorChanged() {
void ElectronDesktopWindowTreeHostLinux::UpdateFrameHints() {
if (base::FeatureList::IsEnabled(features::kWaylandWindowDecorations)) {
auto* const view = native_window_view_->GetClientFrameViewLinux();
if (!view)
auto* const layout = native_window_view_->GetLinuxFrameLayout();
if (!layout)
return;
ui::PlatformWindow* window = platform_window();
auto window_state = window->GetPlatformWindowState();
float scale = device_scale_factor();
const gfx::Size widget_size =
view->GetWidget()->GetWindowBoundsInScreen().size();
const gfx::Size widget_size = GetWidget()->GetWindowBoundsInScreen().size();
if (SupportsClientFrameShadow()) {
auto insets = CalculateInsetsInDIP(window_state);
@@ -196,7 +195,7 @@ void ElectronDesktopWindowTreeHostLinux::UpdateFrameHints() {
window->SetInputRegion(std::nullopt);
} else {
gfx::Rect input_bounds(widget_size);
input_bounds.Inset(insets - view->GetInputInsets());
input_bounds.Inset(insets - layout->GetInputInsets());
input_bounds = gfx::ScaleToEnclosingRect(input_bounds, scale);
window->SetInputRegion(
std::optional<std::vector<gfx::Rect>>({input_bounds}));
@@ -210,8 +209,8 @@ void ElectronDesktopWindowTreeHostLinux::UpdateFrameHints() {
// The opaque region is a list of rectangles that contain only fully
// opaque pixels of the window. We need to convert the clipping
// rounded-rect into this format.
SkRRect rrect = view->GetRoundedWindowContentBounds();
gfx::RectF rectf(view->GetWindowContentBounds());
SkRRect rrect = layout->GetRoundedWindowContentBounds();
gfx::RectF rectf(layout->GetWindowContentBounds());
rectf.Scale(scale);
// It is acceptable to omit some pixels that are opaque, but the region
// must not include any translucent pixels. Therefore, we must
@@ -245,7 +244,8 @@ void ElectronDesktopWindowTreeHostLinux::UpdateFrameHints() {
auto translucent_top_area_rect = SkIRect::MakeXYWH(
rect.x(), rect.y(), rect.width(),
std::ceil(view->GetTranslucentTopAreaHeight() * scale - rect.y()));
std::ceil(layout->GetTranslucentTopAreaHeight() * scale -
rect.y()));
region.op(translucent_top_area_rect, SkRegion::kDifference_Op);
// Convert the region to a list of rectangles.
@@ -256,7 +256,7 @@ void ElectronDesktopWindowTreeHostLinux::UpdateFrameHints() {
// The entire window except for the translucent top is opaque.
gfx::Rect opaque_region_dip(widget_size);
gfx::Insets insets;
insets.set_top(view->GetTranslucentTopAreaHeight());
insets.set_top(layout->GetTranslucentTopAreaHeight());
opaque_region_dip.Inset(insets);
opaque_region.push_back(
gfx::ScaleToEnclosingRect(opaque_region_dip, scale));

View File

@@ -20,7 +20,6 @@
namespace electron {
class ClientFrameViewLinux;
class NativeWindowViews;
class ElectronDesktopWindowTreeHostLinux

View File

@@ -10,8 +10,8 @@
#include "cc/paint/paint_filter.h"
#include "cc/paint/paint_flags.h"
#include "shell/browser/native_window_views.h"
#include "shell/browser/ui/electron_desktop_window_tree_host_linux.h"
#include "shell/browser/ui/views/frameless_view.h"
#include "shell/browser/ui/views/linux_frame_layout.h"
#include "ui/base/hit_test.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
@@ -20,7 +20,6 @@
#include "ui/gfx/font_list.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/gfx/text_constants.h"
#include "ui/gtk/gtk_compat.h" // nogncheck
#include "ui/gtk/gtk_util.h" // nogncheck
@@ -39,9 +38,6 @@ namespace electron {
namespace {
// These values should be the same as Chromium uses.
constexpr int kResizeBorder = 10;
ui::NavButtonProvider::ButtonState ButtonStateToNavButtonProviderState(
views::Button::ButtonState state) {
switch (state) {
@@ -116,6 +112,7 @@ ClientFrameViewLinux::~ClientFrameViewLinux() {
void ClientFrameViewLinux::Init(NativeWindowViews* window,
views::Widget* frame) {
FramelessView::Init(window, frame);
linux_frame_layout_ = std::make_unique<LinuxCSDFrameLayout>(window);
// Unretained() is safe because the subscription is saved into an instance
// member and thus will be cancelled upon the instance's destruction.
@@ -123,11 +120,6 @@ void ClientFrameViewLinux::Init(NativeWindowViews* window,
frame_->RegisterPaintAsActiveChangedCallback(base::BindRepeating(
&ClientFrameViewLinux::PaintAsActiveChanged, base::Unretained(this)));
auto* tree_host = static_cast<ElectronDesktopWindowTreeHostLinux*>(
ElectronDesktopWindowTreeHostLinux::GetHostForWidget(
window->GetAcceleratedWidget()));
host_supports_client_frame_shadow_ = tree_host->SupportsClientFrameShadow();
UpdateWindowTitle();
for (auto& button : nav_buttons_) {
@@ -143,50 +135,11 @@ void ClientFrameViewLinux::Init(NativeWindowViews* window,
}
gfx::Insets ClientFrameViewLinux::RestoredFrameBorderInsets() const {
gfx::Insets insets = GetFrameProvider()->GetFrameThicknessDip();
const gfx::Insets input = GetInputInsets();
auto expand_if_visible = [](int side_thickness, int min_band) {
return side_thickness > 0 ? std::max(side_thickness, min_band) : 0;
};
gfx::Insets merged;
merged.set_top(expand_if_visible(insets.top(), input.top()));
merged.set_left(expand_if_visible(insets.left(), input.left()));
merged.set_bottom(expand_if_visible(insets.bottom(), input.bottom()));
merged.set_right(expand_if_visible(insets.right(), input.right()));
return base::i18n::IsRTL() ? gfx::Insets::TLBR(merged.top(), merged.right(),
merged.bottom(), merged.left())
: merged;
return linux_frame_layout_->RestoredFrameBorderInsets();
}
gfx::Insets ClientFrameViewLinux::GetInputInsets() const {
bool showing_shadow = host_supports_client_frame_shadow_ &&
!frame_->IsMaximized() && !frame_->IsFullscreen();
return gfx::Insets(showing_shadow ? kResizeBorder : 0);
}
gfx::Rect ClientFrameViewLinux::GetWindowContentBounds() const {
gfx::Rect content_bounds = bounds();
content_bounds.Inset(RestoredFrameBorderInsets());
return content_bounds;
}
SkRRect ClientFrameViewLinux::GetRoundedWindowContentBounds() const {
SkRect rect = gfx::RectToSkRect(GetWindowContentBounds());
SkRRect rrect;
if (!frame_->IsMaximized()) {
SkPoint round_point{theme_values_.window_border_radius,
theme_values_.window_border_radius};
SkPoint radii[] = {round_point, round_point, {}, {}};
rrect.setRectRadii(rect, radii);
} else {
rrect.setRect(rect);
}
return rrect;
LinuxFrameLayout* ClientFrameViewLinux::GetLinuxFrameLayout() const {
return linux_frame_layout_.get();
}
void ClientFrameViewLinux::OnNativeThemeUpdated(
@@ -245,11 +198,6 @@ int ClientFrameViewLinux::NonClientHitTest(const gfx::Point& point) {
return FramelessView::NonClientHitTest(point);
}
ui::WindowFrameProvider* ClientFrameViewLinux::GetFrameProvider() const {
return ui::LinuxUiTheme::GetForProfile(nullptr)->GetWindowFrameProvider(
!host_supports_client_frame_shadow_, tiled(), frame_->IsMaximized());
}
void ClientFrameViewLinux::GetWindowMask(const gfx::Size& size,
SkPath* window_mask) {
// Nothing to do here, as transparency is used for decorations, not masks.
@@ -287,11 +235,8 @@ void ClientFrameViewLinux::Layout(PassKey) {
}
void ClientFrameViewLinux::OnPaint(gfx::Canvas* canvas) {
if (!frame_->IsFullscreen()) {
GetFrameProvider()->PaintWindowFrame(
canvas, GetLocalBounds(), GetTitlebarBounds().bottom(),
ShouldPaintAsActive(), GetInputInsets());
}
linux_frame_layout_->PaintWindowFrame(
canvas, GetLocalBounds(), GetTitlebarBounds(), ShouldPaintAsActive());
}
void ClientFrameViewLinux::PaintAsActiveChanged() {
@@ -322,7 +267,7 @@ void ClientFrameViewLinux::UpdateThemeValues() {
}
theme_values_.window_border_radius =
GetFrameProvider()->GetTopCornerRadiusDip();
linux_frame_layout_->GetFrameProvider()->GetTopCornerRadiusDip();
gtk::GtkStyleContextGet(headerbar_context, "min-height",
&theme_values_.titlebar_min_height, nullptr);
@@ -479,10 +424,6 @@ views::View* ClientFrameViewLinux::TargetForRect(views::View* root,
return views::FrameView::TargetForRect(root, rect);
}
int ClientFrameViewLinux::GetTranslucentTopAreaHeight() const {
return 0;
}
BEGIN_METADATA(ClientFrameViewLinux) END_METADATA
} // namespace electron

View File

@@ -13,13 +13,12 @@
#include "base/memory/raw_ptr_exclusion.h"
#include "base/scoped_observation.h"
#include "shell/browser/ui/views/frameless_view.h"
#include "third_party/skia/include/core/SkRRect.h"
#include "shell/browser/ui/views/linux_frame_layout.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/ui_base_types.h"
#include "ui/linux/linux_ui.h"
#include "ui/linux/nav_button_provider.h"
#include "ui/linux/window_button_order_observer.h"
#include "ui/linux/window_frame_provider.h"
#include "ui/native_theme/native_theme.h"
#include "ui/native_theme/native_theme_observer.h"
#include "ui/views/controls/button/image_button.h"
@@ -44,15 +43,7 @@ class ClientFrameViewLinux : public FramelessView,
// FramelessView:
gfx::Insets RestoredFrameBorderInsets() const override;
gfx::Insets GetInputInsets() const;
gfx::Rect GetWindowContentBounds() const;
SkRRect GetRoundedWindowContentBounds() const;
int GetTranslucentTopAreaHeight() const;
// Returns whether the frame is in a tiled state.
bool tiled() const { return tiled_; }
void set_tiled(bool tiled) { tiled_ = tiled; }
LinuxFrameLayout* GetLinuxFrameLayout() const override;
protected:
// ui::NativeThemeObserver:
@@ -80,8 +71,6 @@ class ClientFrameViewLinux : public FramelessView,
// Overridden from views::ViewTargeterDelegate
views::View* TargetForRect(views::View* root, const gfx::Rect& rect) override;
ui::WindowFrameProvider* GetFrameProvider() const;
private:
static constexpr int kNavButtonCount = 4;
@@ -123,6 +112,8 @@ class ClientFrameViewLinux : public FramelessView,
gfx::Insets GetTitlebarContentInsets() const;
gfx::Rect GetTitlebarContentBounds() const;
std::unique_ptr<LinuxFrameLayout> linux_frame_layout_;
raw_ptr<ui::NativeTheme> theme_;
ThemeValues theme_values_;
@@ -134,16 +125,12 @@ class ClientFrameViewLinux : public FramelessView,
std::vector<views::FrameButton> leading_frame_buttons_;
std::vector<views::FrameButton> trailing_frame_buttons_;
bool host_supports_client_frame_shadow_ = false;
base::ScopedObservation<ui::NativeTheme, ui::NativeThemeObserver>
native_theme_observer_{this};
base::ScopedObservation<ui::LinuxUi, ui::WindowButtonOrderObserver>
window_button_order_observer_{this};
base::CallbackListSubscription paint_as_active_changed_subscription_;
bool tiled_ = false;
};
} // namespace electron

View File

@@ -122,6 +122,13 @@ gfx::Size FramelessView::GetMaximumSize() const {
return gfx::Size();
return window_->GetMaximumSize();
}
#if BUILDFLAG(IS_LINUX)
LinuxFrameLayout* FramelessView::GetLinuxFrameLayout() const {
return nullptr;
}
#endif
BEGIN_METADATA(FramelessView)
END_METADATA

View File

@@ -10,6 +10,10 @@
#include "ui/gfx/geometry/insets.h"
#include "ui/views/window/non_client_view.h"
#if BUILDFLAG(IS_LINUX)
#include "shell/browser/ui/views/linux_frame_layout.h"
#endif
namespace views {
class Widget;
}
@@ -42,6 +46,10 @@ class FramelessView : public views::FrameView {
// bounds of the view, used for CSD and resize targets on some platforms.
virtual gfx::Insets RestoredFrameBorderInsets() const;
#if BUILDFLAG(IS_LINUX)
virtual LinuxFrameLayout* GetLinuxFrameLayout() const;
#endif
NativeWindowViews* window() const { return window_; }
views::Widget* frame() const { return frame_; }

View File

@@ -0,0 +1,171 @@
// Copyright (c) 2025 Mitchell Cohen.
// Copyright (c) 2021 Ryan Gonzalez.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/ui/views/linux_frame_layout.h"
#include "base/i18n/rtl.h"
#include "shell/browser/native_window_views.h"
#include "shell/browser/ui/electron_desktop_window_tree_host_linux.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/linux/linux_ui.h"
#include "ui/native_theme/native_theme.h"
#include "ui/views/widget/widget.h"
namespace electron {
namespace {
// This should match Chromium's value.
constexpr int kResizeBorder = 10;
// This should match FramelessView's inside resize band.
constexpr int kResizeInsideBoundsSize = 5;
} // namespace
// static
std::unique_ptr<LinuxFrameLayout> LinuxFrameLayout::Create(
NativeWindowViews* window,
bool wants_shadow) {
if (x11_util::IsX11() || window->IsTranslucent() || !wants_shadow) {
return std::make_unique<LinuxUndecoratedFrameLayout>(window);
} else {
return std::make_unique<LinuxCSDFrameLayout>(window);
}
}
LinuxCSDFrameLayout::LinuxCSDFrameLayout(NativeWindowViews* window)
: window_(window) {
host_supports_client_frame_shadow_ = SupportsClientFrameShadow();
}
bool LinuxCSDFrameLayout::tiled() const {
return tiled_;
}
void LinuxCSDFrameLayout::set_tiled(bool tiled) {
tiled_ = tiled;
}
gfx::Insets LinuxCSDFrameLayout::RestoredFrameBorderInsets() const {
gfx::Insets insets = GetFrameProvider()->GetFrameThicknessDip();
const gfx::Insets input = GetInputInsets();
auto expand_if_visible = [](int side_thickness, int min_band) {
return side_thickness > 0 ? std::max(side_thickness, min_band) : 0;
};
gfx::Insets merged;
merged.set_top(expand_if_visible(insets.top(), input.top()));
merged.set_left(expand_if_visible(insets.left(), input.left()));
merged.set_bottom(expand_if_visible(insets.bottom(), input.bottom()));
merged.set_right(expand_if_visible(insets.right(), input.right()));
return base::i18n::IsRTL() ? gfx::Insets::TLBR(merged.top(), merged.right(),
merged.bottom(), merged.left())
: merged;
}
gfx::Insets LinuxCSDFrameLayout::GetInputInsets() const {
bool showing_shadow = host_supports_client_frame_shadow_ &&
!window_->IsMaximized() && !window_->IsFullscreen();
return gfx::Insets(showing_shadow ? kResizeBorder : 0);
}
bool LinuxCSDFrameLayout::SupportsClientFrameShadow() const {
auto* tree_host = static_cast<ElectronDesktopWindowTreeHostLinux*>(
ElectronDesktopWindowTreeHostLinux::GetHostForWidget(
window_->GetAcceleratedWidget()));
return tree_host->SupportsClientFrameShadow();
}
void LinuxCSDFrameLayout::PaintWindowFrame(gfx::Canvas* canvas,
gfx::Rect local_bounds,
gfx::Rect titlebar_bounds,
bool active) {
GetFrameProvider()->PaintWindowFrame(
canvas, local_bounds, titlebar_bounds.bottom(), active, GetInputInsets());
}
gfx::Rect LinuxCSDFrameLayout::GetWindowContentBounds() const {
gfx::Rect content_bounds = window_->widget()->GetWindowBoundsInScreen();
content_bounds.Inset(RestoredFrameBorderInsets());
return content_bounds;
}
SkRRect LinuxCSDFrameLayout::GetRoundedWindowContentBounds() const {
SkRect rect = gfx::RectToSkRect(GetWindowContentBounds());
SkRRect rrect;
if (!window_->IsMaximized()) {
float radius = GetFrameProvider()->GetTopCornerRadiusDip();
SkPoint round_point{radius, radius};
SkPoint radii[] = {round_point, round_point, {}, {}};
rrect.setRectRadii(rect, radii);
} else {
rrect.setRect(rect);
}
return rrect;
}
int LinuxCSDFrameLayout::GetTranslucentTopAreaHeight() const {
return 0;
}
ui::WindowFrameProvider* LinuxCSDFrameLayout::GetFrameProvider() const {
return ui::LinuxUiTheme::GetForProfile(nullptr)->GetWindowFrameProvider(
!host_supports_client_frame_shadow_, tiled(), window_->IsMaximized());
}
LinuxUndecoratedFrameLayout::LinuxUndecoratedFrameLayout(
NativeWindowViews* window)
: window_(window) {}
gfx::Insets LinuxUndecoratedFrameLayout::RestoredFrameBorderInsets() const {
return gfx::Insets();
}
gfx::Insets LinuxUndecoratedFrameLayout::GetInputInsets() const {
return gfx::Insets(kResizeInsideBoundsSize);
}
bool LinuxUndecoratedFrameLayout::SupportsClientFrameShadow() const {
return false;
}
bool LinuxUndecoratedFrameLayout::tiled() const {
return tiled_;
}
void LinuxUndecoratedFrameLayout::set_tiled(bool tiled) {
tiled_ = tiled;
}
void LinuxUndecoratedFrameLayout::PaintWindowFrame(gfx::Canvas* canvas,
gfx::Rect local_bounds,
gfx::Rect titlebar_bounds,
bool active) {
// No-op
}
gfx::Rect LinuxUndecoratedFrameLayout::GetWindowContentBounds() const {
// With no transparent insets, widget bounds and logical bounds match.
return window_->widget()->GetWindowBoundsInScreen();
}
SkRRect LinuxUndecoratedFrameLayout::GetRoundedWindowContentBounds() const {
SkRRect rrect;
rrect.setRect(gfx::RectToSkRect(GetWindowContentBounds()));
return rrect;
}
int LinuxUndecoratedFrameLayout::GetTranslucentTopAreaHeight() const {
return 0;
}
ui::WindowFrameProvider* LinuxUndecoratedFrameLayout::GetFrameProvider() const {
return nullptr;
}
} // namespace electron

View File

@@ -0,0 +1,117 @@
// Copyright (c) 2025 Mitchell Cohen.
// Copyright (c) 2021 Ryan Gonzalez.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_BROWSER_UI_VIEWS_LINUX_FRAME_LAYOUT_H_
#define ELECTRON_SHELL_BROWSER_UI_VIEWS_LINUX_FRAME_LAYOUT_H_
#include <memory>
#include "base/i18n/rtl.h"
#include "shell/browser/linux/x11_util.h"
#include "shell/browser/native_window_views.h"
#include "shell/browser/ui/electron_desktop_window_tree_host_linux.h"
#include "third_party/skia/include/core/SkRRect.h"
#include "ui/base/ozone_buildflags.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/linux/linux_ui.h"
#include "ui/linux/window_frame_provider.h"
namespace electron {
class NativeWindowViews;
// Shared helper for CSD layout and frame painting on Linux (shadows, resize
// regions, titlebars, etc.). Also helps views determine insets and perform
// bounds conversions between widget and logical coordinates.
class LinuxFrameLayout {
public:
virtual ~LinuxFrameLayout() = default;
static std::unique_ptr<LinuxFrameLayout> Create(NativeWindowViews* window,
bool wants_shadow);
// Insets from the transparent widget border to the opaque part of the window
virtual gfx::Insets RestoredFrameBorderInsets() const = 0;
// Insets for parts of the surface that should be counted for user input
virtual gfx::Insets GetInputInsets() const = 0;
virtual bool SupportsClientFrameShadow() const = 0;
virtual bool tiled() const = 0;
virtual void set_tiled(bool tiled) = 0;
virtual void PaintWindowFrame(gfx::Canvas* canvas,
gfx::Rect local_bounds,
gfx::Rect titlebar_bounds,
bool active) = 0;
// The logical bounds of the window
virtual gfx::Rect GetWindowContentBounds() const = 0;
// The logical bounds as a rounded rect with corner radii applied
virtual SkRRect GetRoundedWindowContentBounds() const = 0;
virtual int GetTranslucentTopAreaHeight() const = 0;
virtual ui::WindowFrameProvider* GetFrameProvider() const = 0;
};
// Client-side decoration (CSD) Linux frame layout implementation.
class LinuxCSDFrameLayout : public LinuxFrameLayout {
public:
explicit LinuxCSDFrameLayout(NativeWindowViews* window);
~LinuxCSDFrameLayout() override = default;
gfx::Insets RestoredFrameBorderInsets() const override;
gfx::Insets GetInputInsets() const override;
bool SupportsClientFrameShadow() const override;
bool tiled() const override;
void set_tiled(bool tiled) override;
void PaintWindowFrame(gfx::Canvas* canvas,
gfx::Rect local_bounds,
gfx::Rect titlebar_bounds,
bool active) override;
gfx::Rect GetWindowContentBounds() const override;
SkRRect GetRoundedWindowContentBounds() const override;
int GetTranslucentTopAreaHeight() const override;
ui::WindowFrameProvider* GetFrameProvider() const override;
private:
raw_ptr<NativeWindowViews> window_;
bool tiled_ = false;
bool host_supports_client_frame_shadow_ = false;
};
// No-decoration Linux frame layout implementation.
//
// Intended for cases where we do not allocate a transparent inset area around
// the window (e.g. X11 / server-side decorations, or when insets are disabled).
// All inset math returns 0 and frame painting is skipped.
class LinuxUndecoratedFrameLayout : public LinuxFrameLayout {
public:
explicit LinuxUndecoratedFrameLayout(NativeWindowViews* window);
~LinuxUndecoratedFrameLayout() override = default;
gfx::Insets RestoredFrameBorderInsets() const override;
gfx::Insets GetInputInsets() const override;
bool SupportsClientFrameShadow() const override;
bool tiled() const override;
void set_tiled(bool tiled) override;
void PaintWindowFrame(gfx::Canvas* canvas,
gfx::Rect local_bounds,
gfx::Rect titlebar_bounds,
bool active) override;
gfx::Rect GetWindowContentBounds() const override;
SkRRect GetRoundedWindowContentBounds() const override;
int GetTranslucentTopAreaHeight() const override;
ui::WindowFrameProvider* GetFrameProvider() const override;
private:
raw_ptr<NativeWindowViews> window_;
bool tiled_ = false;
};
} // namespace electron
#endif // ELECTRON_SHELL_BROWSER_UI_VIEWS_LINUX_FRAME_LAYOUT_H_

View File

@@ -60,6 +60,13 @@ OpaqueFrameView::~OpaqueFrameView() = default;
void OpaqueFrameView::Init(NativeWindowViews* window, views::Widget* frame) {
FramelessView::Init(window, frame);
linux_frame_layout_ = LinuxFrameLayout::Create(window, window->HasShadow());
// Unretained() is safe because the subscription is saved into an instance
// member and thus will be cancelled upon the instance's destruction.
paint_as_active_changed_subscription_ =
frame->RegisterPaintAsActiveChangedCallback(base::BindRepeating(
&OpaqueFrameView::PaintAsActiveChanged, base::Unretained(this)));
if (!window->IsWindowControlsOverlayEnabled())
return;
@@ -88,16 +95,12 @@ void OpaqueFrameView::Init(NativeWindowViews* window, views::Widget* frame) {
base::BindRepeating(&views::Widget::CloseWithReason,
base::Unretained(frame),
views::Widget::ClosedReason::kCloseButtonClicked));
// Unretained() is safe because the subscription is saved into an instance
// member and thus will be cancelled upon the instance's destruction.
paint_as_active_changed_subscription_ =
frame->RegisterPaintAsActiveChangedCallback(base::BindRepeating(
&OpaqueFrameView::PaintAsActiveChanged, base::Unretained(this)));
}
int OpaqueFrameView::ResizingBorderHitTest(const gfx::Point& point) {
return FramelessView::ResizingBorderHitTest(point);
auto insets = RestoredFrameBorderInsets();
return ResizingBorderHitTestImpl(
point, insets.IsEmpty() ? linux_frame_layout_->GetInputInsets() : insets);
}
void OpaqueFrameView::InvalidateCaptionButtons() {
@@ -108,40 +111,28 @@ void OpaqueFrameView::InvalidateCaptionButtons() {
}
gfx::Rect OpaqueFrameView::GetBoundsForClientView() const {
if (window()->IsWindowControlsOverlayEnabled()) {
auto border_thickness = FrameBorderInsets(false);
int top_height = border_thickness.top();
return gfx::Rect(
border_thickness.left(), top_height,
std::max(0, width() - border_thickness.width()),
std::max(0, height() - top_height - border_thickness.bottom()));
gfx::Rect client_bounds = bounds();
if (!frame_->IsFullscreen()) {
client_bounds.Inset(FrameBorderInsets(false));
}
return FramelessView::GetBoundsForClientView();
return client_bounds;
}
gfx::Rect OpaqueFrameView::GetWindowBoundsForClientBounds(
const gfx::Rect& client_bounds) const {
if (window()->IsWindowControlsOverlayEnabled()) {
int top_height = NonClientTopHeight(false);
auto border_insets = FrameBorderInsets(false);
return gfx::Rect(
std::max(0, client_bounds.x() - border_insets.left()),
std::max(0, client_bounds.y() - top_height),
client_bounds.width() + border_insets.width(),
client_bounds.height() + top_height + border_insets.bottom());
}
return FramelessView::GetWindowBoundsForClientBounds(client_bounds);
gfx::Insets insets = bounds().InsetsFrom(GetBoundsForClientView());
return gfx::Rect(std::max(0, client_bounds.x() - insets.left()),
std::max(0, client_bounds.y() - insets.top()),
client_bounds.width() + insets.width(),
client_bounds.height() + insets.height());
}
int OpaqueFrameView::NonClientHitTest(const gfx::Point& point) {
if (window()->IsWindowControlsOverlayEnabled()) {
// Ensure support for resizing frameless window with border drag.
int frame_component = ResizingBorderHitTest(point);
if (frame_component != HTNOWHERE)
return frame_component;
int frame_component = ResizingBorderHitTest(point);
if (frame_component != HTNOWHERE)
return frame_component;
if (window()->IsWindowControlsOverlayEnabled()) {
if (HitTestCaptionButton(close_button_, point))
return HTCLOSE;
if (HitTestCaptionButton(restore_button_, point))
@@ -185,41 +176,53 @@ void OpaqueFrameView::Layout(PassKey) {
// Reset all our data so that everything is invisible.
TopAreaPadding top_area_padding = GetTopAreaPadding();
available_space_leading_x_ = top_area_padding.leading;
available_space_trailing_x_ = width() - top_area_padding.trailing;
gfx::Rect client_bounds = GetBoundsForClientView();
available_space_leading_x_ = client_bounds.x() + top_area_padding.leading;
available_space_trailing_x_ =
client_bounds.right() - top_area_padding.trailing;
minimum_size_for_buttons_ =
available_space_leading_x_ + width() - available_space_trailing_x_;
(available_space_leading_x_ - client_bounds.x()) +
(client_bounds.right() - available_space_trailing_x_);
placed_leading_button_ = false;
placed_trailing_button_ = false;
LayoutWindowControls();
int height = NonClientTopHeight(false);
auto insets = FrameBorderInsets(false);
int container_x = placed_trailing_button_ ? available_space_trailing_x_ : 0;
int container_x =
placed_trailing_button_ ? available_space_trailing_x_ : client_bounds.x();
caption_button_placeholder_container_->SetBounds(
container_x, insets.top(), minimum_size_for_buttons_ - insets.width(),
height - insets.top());
container_x, client_bounds.y(), minimum_size_for_buttons_, height);
LayoutWindowControlsOverlay();
}
void OpaqueFrameView::OnPaint(gfx::Canvas* canvas) {
if (!window()->IsWindowControlsOverlayEnabled())
if (frame()->IsFullscreen())
return;
if (frame()->IsFullscreen())
// Titlebar height must be at least the frame border insets to avoid
// a negative height calculation in the GTK frame provider. We add 1 to
// ensure it's always positive even when insets are 0.
int top_area_height = RestoredFrameBorderInsets().top() + 1;
linux_frame_layout_->PaintWindowFrame(
canvas, GetLocalBounds(), gfx::Rect(0, 0, width(), top_area_height),
ShouldPaintAsActive());
if (!window()->IsWindowControlsOverlayEnabled())
return;
UpdateFrameCaptionButtons();
}
void OpaqueFrameView::PaintAsActiveChanged() {
if (!window()->IsWindowControlsOverlayEnabled())
return;
if (window()->IsWindowControlsOverlayEnabled()) {
UpdateCaptionButtonPlaceholderContainerBackground();
UpdateFrameCaptionButtons();
}
UpdateCaptionButtonPlaceholderContainerBackground();
UpdateFrameCaptionButtons();
InvalidateLayout();
SchedulePaint();
}
void OpaqueFrameView::UpdateFrameCaptionButtons() {
@@ -284,7 +287,8 @@ void OpaqueFrameView::LayoutWindowControlsOverlay() {
: caption_button_placeholder_container_->size().height() + 1;
}
int overlay_width = caption_button_placeholder_container_->size().width();
int bounding_rect_width = width() - overlay_width;
gfx::Rect client_bounds = GetBoundsForClientView();
int bounding_rect_width = client_bounds.width() - overlay_width;
auto bounding_rect =
GetMirroredRect(gfx::Rect(0, 0, bounding_rect_width, overlay_height));
@@ -317,8 +321,9 @@ views::Button* OpaqueFrameView::CreateButton(
}
gfx::Insets OpaqueFrameView::FrameBorderInsets(bool restored) const {
return !restored && IsFrameCondensed() ? gfx::Insets()
: RestoredFrameBorderInsets();
return !restored && IsFrameCondensed()
? gfx::Insets()
: linux_frame_layout_->RestoredFrameBorderInsets();
}
int OpaqueFrameView::FrameTopBorderThickness(bool restored) const {
@@ -331,8 +336,7 @@ int OpaqueFrameView::FrameTopBorderThickness(bool restored) const {
OpaqueFrameView::TopAreaPadding OpaqueFrameView::GetTopAreaPadding(
bool has_leading_buttons,
bool has_trailing_buttons) const {
const auto padding = FrameBorderInsets(false);
return TopAreaPadding{padding.left(), padding.right()};
return {};
}
bool OpaqueFrameView::IsFrameCondensed() const {
@@ -340,7 +344,11 @@ bool OpaqueFrameView::IsFrameCondensed() const {
}
gfx::Insets OpaqueFrameView::RestoredFrameBorderInsets() const {
return {};
return linux_frame_layout_->RestoredFrameBorderInsets();
}
LinuxFrameLayout* OpaqueFrameView::GetLinuxFrameLayout() const {
return linux_frame_layout_.get();
}
gfx::Insets OpaqueFrameView::RestoredFrameEdgeInsets() const {
@@ -378,7 +386,7 @@ int OpaqueFrameView::DefaultCaptionButtonY(bool restored) const {
// the top to take Fitts' Law into account).
const bool start_at_top_of_frame = !restored && IsFrameCondensed();
return start_at_top_of_frame
? FrameBorderInsets(false).top()
? 0
: OpaqueBrowserFrameViewLayout::kFrameShadowThickness;
}
@@ -462,7 +470,8 @@ void OpaqueFrameView::HideButton(views::FrameButton button_id) {
void OpaqueFrameView::SetBoundsForButton(views::FrameButton button_id,
views::Button* button,
ButtonAlignment alignment) {
const int caption_y = CaptionButtonY(button_id, false);
gfx::Rect client_bounds = GetBoundsForClientView();
const int caption_y = CaptionButtonY(button_id, false) + client_bounds.y();
// There should always be the same number of non-shadow pixels visible to the
// side of the caption buttons. In maximized mode we extend buttons to the

View File

@@ -10,6 +10,7 @@
#include "base/memory/raw_ptr.h"
#include "chrome/browser/ui/view_ids.h"
#include "shell/browser/ui/views/frameless_view.h"
#include "shell/browser/ui/views/linux_frame_layout.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/linux/window_button_order_observer.h"
#include "ui/views/controls/button/button.h"
@@ -40,6 +41,7 @@ class OpaqueFrameView : public FramelessView {
int ResizingBorderHitTest(const gfx::Point& point) override;
void InvalidateCaptionButtons() override;
gfx::Insets RestoredFrameBorderInsets() const override;
LinuxFrameLayout* GetLinuxFrameLayout() const override;
// views::FrameView:
gfx::Rect GetBoundsForClientView() const override;
@@ -163,6 +165,8 @@ class OpaqueFrameView : public FramelessView {
bool leading_spacing,
bool is_leading_button) const;
std::unique_ptr<LinuxFrameLayout> linux_frame_layout_;
// Window controls.
raw_ptr<views::Button> minimize_button_;
raw_ptr<views::Button> maximize_button_;

View File

@@ -413,6 +413,7 @@ bool IsAllowedOption(const std::string_view option) {
"--inspect-port",
"--inspect-publish-uid",
"--experimental-network-inspection",
"--experimental-transform-types",
});
// This should be aligned with what's possible to set via the process object.

7
spec/fixtures/type-stripping/basic.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
import { app } from 'electron/main';
const logMessage = (message: string): void => console.log(message);
logMessage('running');
app.exit(0);

View File

@@ -0,0 +1,9 @@
enum Test {
A,
B,
C,
}
console.log(Test.A);
process.exit(0);

View File

@@ -0,0 +1,11 @@
import { app } from 'electron/main';
enum Test {
A,
B,
C,
}
console.log(Test.A);
app.exit(0);

View File

@@ -1030,4 +1030,26 @@ describe('node feature', () => {
});
});
});
describe('type stripping', () => {
it('strips TypeScript types automatically in the main process', async () => {
const child = childProcess.spawn(process.execPath, [path.join(fixtures, 'type-stripping', 'basic.ts')]);
const [code] = await once(child, 'exit');
expect(code).to.equal(0);
});
it('will not transform TypeScript types without --experimental-transform-types', async () => {
const child = childProcess.spawn(process.execPath, [path.join(fixtures, 'type-stripping', 'transform-types-node.ts')], {
env: { ELECTRON_RUN_AS_NODE: 'true' }
});
const [code] = await once(child, 'exit');
expect(code).to.not.equal(0);
});
it('transforms TypeScript types with --experimental-transform-types', async () => {
const child = childProcess.spawn(process.execPath, ['--experimental-transform-types', path.join(fixtures, 'type-stripping', 'transform-types.ts')]);
const [code] = await once(child, 'exit');
expect(code).to.equal(0);
});
});
});