Compare commits

...

6 Commits

Author SHA1 Message Date
Charles Kerr
b417696d6b refactor: migrate electron::api::Protocol to cppgc (#50857)
refactor: migrate api::Protocol to cppgc
2026-04-10 15:58:33 +09:00
Mitchell Cohen
4203d7688f fix: external resize hit targets for frameless windows on Windows (#50706) 2026-04-09 18:13:13 -05:00
Zeenat Lawal
62e637275a fix: move Electron help menu links to default app only (#50629)
* fix: remove Electron links from default help menu

* fix: remove help menu entirely from default menu

* fix: move Electron help menu links to default app

* docs: update default menu items list in menu.md
2026-04-09 12:14:22 -07:00
Shelley Vohr
28c0eb29df fix: webContents.print() ignoring mediaSize when silent (#50808)
fix: webContents.print() ignoring mediaSize when silent

PR #49523 moved the default media size fallback into OnGetDeviceNameToUse,
but the new code unconditionally writes kSettingMediaSize — clobbering
any mediaSize the caller had already set in WebContents::Print() from
options.mediaSize / pageSize. As a result, silent prints with an
explicit pageSize (e.g. "Letter") fell back to A4 with tiny content.

Only populate the default/printer media size when the caller hasn't
already supplied one, preserving the precedence:
  1. user-supplied mediaSize / pageSize
  2. printer default (when usePrinterDefaultPageSize is true)
  3. A4 fallback
2026-04-09 12:16:40 -05:00
Charles Kerr
8a730e2aec fix: remove dangling raw_ptr api::WebContents::zoom_controller_ (#50812)
fix: remove dangling raw_ptr api::WebContents::zoom_controller_
2026-04-09 12:16:17 -05:00
Shelley Vohr
044be7ce40 fix: avoid crash in window.print() when prefilling native print dialog (#50843)
fix: avoid crash in window.print() when prefilling native print dialog

When UpdatePrinterSettings() fails (e.g. the printer rejects the
requested resolution), OnError() nullifies print_info_ via
ReleaseContext(). The return value was not checked, so
AskUserForSettings() passed nil to [NSPrintPanel runModalWithPrintInfo:],
crashing in PJCSessionHasApplicationSetPrinter with a null PMPrintSession.

Check the return value and fall back to UseDefaultSettings() on failure
so the dialog opens with defaults instead of crashing.
2026-04-09 13:14:36 -04:00
19 changed files with 216 additions and 152 deletions

View File

@@ -1,5 +1,5 @@
import { shell } from 'electron/common';
import { app, dialog, BrowserWindow, ipcMain } from 'electron/main';
import { app, dialog, BrowserWindow, ipcMain, Menu } from 'electron/main';
import * as path from 'node:path';
import * as url from 'node:url';
@@ -11,6 +11,53 @@ app.on('window-all-closed', () => {
app.quit();
});
const isMac = process.platform === 'darwin';
app.whenReady().then(() => {
const helpMenu: Electron.MenuItemConstructorOptions = {
role: 'help',
submenu: [
{
label: 'Learn More',
click: async () => {
await shell.openExternal('https://electronjs.org');
}
},
{
label: 'Documentation',
click: async () => {
const version = process.versions.electron;
await shell.openExternal(`https://github.com/electron/electron/tree/v${version}/docs#readme`);
}
},
{
label: 'Community Discussions',
click: async () => {
await shell.openExternal('https://discord.gg/electronjs');
}
},
{
label: 'Search Issues',
click: async () => {
await shell.openExternal('https://github.com/electron/electron/issues');
}
}
]
};
const macAppMenu: Electron.MenuItemConstructorOptions = { role: 'appMenu' };
const template: Electron.MenuItemConstructorOptions[] = [
...(isMac ? [macAppMenu] : []),
{ role: 'fileMenu' },
{ role: 'editMenu' },
{ role: 'viewMenu' },
{ role: 'windowMenu' },
helpMenu
];
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
});
function decorateURL (url: string) {
// safely add `?utm_source=default_app
const parsedUrl = new URL(url);

View File

@@ -46,7 +46,7 @@ this has the additional effect of removing the menu bar from the window.
> [!NOTE]
> The default menu will be created automatically if the app does not set one.
> It contains standard items such as `File`, `Edit`, `View`, `Window` and `Help`.
> It contains standard items such as `File`, `Edit`, `View`, and `Window`.
#### `Menu.getApplicationMenu()`

View File

@@ -1,5 +1,4 @@
import { shell } from 'electron/common';
import { app, Menu } from 'electron/main';
import { Menu } from 'electron/main';
const isMac = process.platform === 'darwin';
@@ -12,47 +11,13 @@ export const setApplicationMenuWasSet = () => {
export const setDefaultApplicationMenu = () => {
if (applicationMenuWasSet) return;
const helpMenu: Electron.MenuItemConstructorOptions = {
role: 'help',
submenu: app.isPackaged
? []
: [
{
label: 'Learn More',
click: async () => {
await shell.openExternal('https://electronjs.org');
}
},
{
label: 'Documentation',
click: async () => {
const version = process.versions.electron;
await shell.openExternal(`https://github.com/electron/electron/tree/v${version}/docs#readme`);
}
},
{
label: 'Community Discussions',
click: async () => {
await shell.openExternal('https://discord.gg/electronjs');
}
},
{
label: 'Search Issues',
click: async () => {
await shell.openExternal('https://github.com/electron/electron/issues');
}
}
]
};
const macAppMenu: Electron.MenuItemConstructorOptions = { role: 'appMenu' };
const template: Electron.MenuItemConstructorOptions[] = [
...(isMac ? [macAppMenu] : []),
{ role: 'fileMenu' },
{ role: 'editMenu' },
{ role: 'viewMenu' },
{ role: 'windowMenu' },
helpMenu
{ role: 'windowMenu' }
];
const menu = Menu.buildFromTemplate(template);

View File

@@ -8,10 +8,10 @@ electron objects that extend gin::Wrappable and gets
allocated on the cpp heap
diff --git a/gin/public/wrappable_pointer_tags.h b/gin/public/wrappable_pointer_tags.h
index fee622ebde42211de6f702b754cfa38595df5a1c..6b524632ebb405e473cf4fe8e253bd13bf7b67e5 100644
index fee622ebde42211de6f702b754cfa38595df5a1c..9f7e1b1b8d871721891255c1f21de825d0df1e30 100644
--- a/gin/public/wrappable_pointer_tags.h
+++ b/gin/public/wrappable_pointer_tags.h
@@ -77,7 +77,20 @@ enum WrappablePointerTag : uint16_t {
@@ -77,7 +77,21 @@ enum WrappablePointerTag : uint16_t {
kWebAXObjectProxy, // content::WebAXObjectProxy
kWrappedExceptionHandler, // extensions::WrappedExceptionHandler
kIndigoContext, // indigo::IndigoContext
@@ -24,6 +24,7 @@ index fee622ebde42211de6f702b754cfa38595df5a1c..6b524632ebb405e473cf4fe8e253bd13
+ kElectronNetLog, // electron::api::NetLog
+ kElectronPowerMonitor, // electron::api::PowerMonitor
+ kElectronPowerSaveBlocker, // electron::api::PowerSaveBlocker
+ kElectronProtocol, // electron::api::Protocol
+ kElectronReplyChannel, // gin_helper::internal::ReplyChannel
+ kElectronScreen, // electron::api::Screen
+ kElectronSession, // electron::api::Session

View File

@@ -620,7 +620,7 @@ index 2a477e820d9f0126a05f86cd44f02c2189275bad..a2e9442ff9f5acf8e301f457b1806251
#if BUILDFLAG(IS_CHROMEOS)
diff --git a/chrome/browser/printing/printer_query_oop.cc b/chrome/browser/printing/printer_query_oop.cc
index dc2a15ab4d784b0b6c85b84a30c3c08a17ed8e3d..e197026e8a7f132c1bf90a0f5f1eabb4f5f064ee 100644
index dc2a15ab4d784b0b6c85b84a30c3c08a17ed8e3d..5ca7920c8525c3c72fd96b14709fb35a9cc28daf 100644
--- a/chrome/browser/printing/printer_query_oop.cc
+++ b/chrome/browser/printing/printer_query_oop.cc
@@ -126,7 +126,7 @@ void PrinterQueryOop::OnDidAskUserForSettings(
@@ -632,7 +632,7 @@ index dc2a15ab4d784b0b6c85b84a30c3c08a17ed8e3d..e197026e8a7f132c1bf90a0f5f1eabb4
// Want the same PrintBackend service as the query so that we use the same
// device context.
print_document_client_id_ =
@@ -189,6 +189,21 @@ void PrinterQueryOop::GetSettingsWithUI(uint32_t document_page_count,
@@ -189,6 +189,28 @@ void PrinterQueryOop::GetSettingsWithUI(uint32_t document_page_count,
// browser process.
// - Other platforms don't have a system print UI or do not use OOP
// printing, so this does not matter.
@@ -643,12 +643,19 @@ index dc2a15ab4d784b0b6c85b84a30c3c08a17ed8e3d..e197026e8a7f132c1bf90a0f5f1eabb4
+ // remote service context, not the local one used by the native dialog.
+ if (settings().dpi()) {
+ printing_context()->SetPrintSettings(settings());
+ printing_context()->UpdatePrinterSettings(PrintingContext::PrinterSettings{
+ if (printing_context()->UpdatePrinterSettings(
+ PrintingContext::PrinterSettings{
+#if BUILDFLAG(IS_MAC)
+ .external_preview = false,
+ .external_preview = false,
+#endif
+ .show_system_dialog = false,
+ });
+ .show_system_dialog = false,
+ }) != mojom::ResultCode::kSuccess) {
+ // Prefilling failed (e.g. the printer does not support the requested
+ // resolution). Reinitialize with defaults so that AskUserForSettings
+ // does not crash due to a null print_info_. The dialog will simply
+ // open without prefilled values.
+ printing_context()->UseDefaultSettings();
+ }
+ }
+
PrinterQuery::GetSettingsWithUI(

View File

@@ -12,6 +12,7 @@
#include "base/no_destructor.h"
#include "content/common/url_schemes.h"
#include "content/public/browser/child_process_security_policy.h"
#include "gin/converter.h"
#include "gin/object_template_builder.h"
#include "shell/browser/browser.h"
#include "shell/browser/javascript_environment.h"
@@ -19,13 +20,13 @@
#include "shell/common/gin_converters/callback_converter.h"
#include "shell/common/gin_converters/net_converter.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/handle.h"
#include "shell/common/gin_helper/object_template_builder.h"
#include "shell/common/gin_helper/promise.h"
#include "shell/common/node_includes.h"
#include "shell/common/node_util.h"
#include "shell/common/options_switches.h"
#include "url/url_util.h"
#include "v8/include/cppgc/allocation.h"
namespace {
@@ -81,7 +82,8 @@ struct Converter<CustomScheme> {
namespace electron::api {
gin::DeprecatedWrapperInfo Protocol::kWrapperInfo = {gin::kEmbedderNativeGin};
const gin::WrapperInfo Protocol::kWrapperInfo = {{gin::kEmbedderNativeGin},
gin::kElectronProtocol};
std::vector<std::string>& GetStandardSchemes() {
static base::NoDestructor<std::vector<std::string>> g_standard_schemes;
@@ -296,23 +298,22 @@ void Protocol::HandleOptionalCallback(gin::Arguments* args, Error error) {
}
// static
gin_helper::Handle<Protocol> Protocol::Create(
v8::Isolate* isolate,
ProtocolRegistry* protocol_registry) {
return gin_helper::CreateHandle(isolate, new Protocol{protocol_registry});
Protocol* Protocol::Create(v8::Isolate* isolate,
ProtocolRegistry* protocol_registry) {
return cppgc::MakeGarbageCollected<Protocol>(
isolate->GetCppHeap()->GetAllocationHandle(), protocol_registry);
}
// static
gin_helper::Handle<Protocol> Protocol::New(gin_helper::ErrorThrower thrower) {
Protocol* Protocol::New(gin_helper::ErrorThrower thrower) {
thrower.ThrowError("Protocol cannot be created from JS");
return {};
}
// static
v8::Local<v8::ObjectTemplate> Protocol::FillObjectTemplate(
v8::Isolate* isolate,
v8::Local<v8::ObjectTemplate> tmpl) {
return gin::ObjectTemplateBuilder(isolate, GetClassName(), tmpl)
void Protocol::FillObjectTemplate(v8::Isolate* isolate,
v8::Local<v8::ObjectTemplate> tmpl) {
gin::ObjectTemplateBuilder(isolate, GetClassName(), tmpl)
.SetMethod("registerStringProtocol",
&Protocol::RegisterProtocolFor<ProtocolType::kString>)
.SetMethod("registerBufferProtocol",
@@ -345,8 +346,12 @@ v8::Local<v8::ObjectTemplate> Protocol::FillObjectTemplate(
.Build();
}
const char* Protocol::GetTypeName() {
return GetClassName();
const gin::WrapperInfo* Protocol::wrapper_info() const {
return &kWrapperInfo;
}
const char* Protocol::GetHumanReadableName() const {
return "Electron / Protocol";
}
} // namespace electron::api
@@ -372,7 +377,8 @@ void Initialize(v8::Local<v8::Object> exports,
v8::Isolate* const isolate = electron::JavascriptEnvironment::GetIsolate();
gin_helper::Dictionary dict{isolate, exports};
dict.Set("Protocol",
electron::api::Protocol::GetConstructor(isolate, context));
electron::api::Protocol::GetConstructor(
isolate, context, &electron::api::Protocol::kWrapperInfo));
dict.SetMethod("registerSchemesAsPrivileged", &RegisterSchemesAsPrivileged);
dict.SetMethod("getStandardSchemes", &electron::api::GetStandardSchemes);
}

View File

@@ -9,20 +9,14 @@
#include <vector>
#include "base/memory/raw_ptr.h"
#include "content/public/browser/content_browser_client.h"
#include "gin/wrappable.h"
#include "shell/browser/net/electron_url_loader_factory.h"
#include "shell/common/gin_helper/constructible.h"
#include "shell/common/gin_helper/wrappable.h"
namespace gin {
class Arguments;
} // namespace gin
namespace gin_helper {
template <typename T>
class Handle;
} // namespace gin_helper
namespace electron {
class ProtocolRegistry;
@@ -38,23 +32,25 @@ void RegisterSchemesAsPrivileged(gin_helper::ErrorThrower thrower,
v8::Local<v8::Value> val);
// Protocol implementation based on network services.
class Protocol final : public gin_helper::DeprecatedWrappable<Protocol>,
class Protocol final : public gin::Wrappable<Protocol>,
public gin_helper::Constructible<Protocol> {
public:
static gin_helper::Handle<Protocol> Create(
v8::Isolate* isolate,
ProtocolRegistry* protocol_registry);
static Protocol* Create(v8::Isolate* isolate,
ProtocolRegistry* protocol_registry);
// gin_helper::Constructible
static gin_helper::Handle<Protocol> New(gin_helper::ErrorThrower thrower);
static v8::Local<v8::ObjectTemplate> FillObjectTemplate(
v8::Isolate* isolate,
v8::Local<v8::ObjectTemplate> tmpl);
static Protocol* New(gin_helper::ErrorThrower thrower);
static void FillObjectTemplate(v8::Isolate* isolate,
v8::Local<v8::ObjectTemplate> tmpl);
static const char* GetClassName() { return "Protocol"; }
// gin_helper::Wrappable
static gin::DeprecatedWrapperInfo kWrapperInfo;
const char* GetTypeName() override;
// gin::Wrappable
static const gin::WrapperInfo kWrapperInfo;
const gin::WrapperInfo* wrapper_info() const override;
const char* GetHumanReadableName() const override;
explicit Protocol(ProtocolRegistry* protocol_registry);
~Protocol() override;
private:
// Possible errors.
@@ -70,9 +66,6 @@ class Protocol final : public gin_helper::DeprecatedWrappable<Protocol>,
using CompletionCallback =
base::RepeatingCallback<void(v8::Local<v8::Value>)>;
explicit Protocol(ProtocolRegistry* protocol_registry);
~Protocol() override;
[[nodiscard]] static std::string_view ErrorCodeToString(Error error);
// JS APIs.

View File

@@ -17,6 +17,7 @@
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation.h"
#include "base/strings/string_util.h"
#include "base/types/pass_key.h"
@@ -76,6 +77,7 @@
#include "shell/browser/media/media_device_id_salt.h"
#include "shell/browser/net/cert_verifier_client.h"
#include "shell/browser/net/resolve_host_function.h"
#include "shell/browser/net/resolve_proxy_helper.h"
#include "shell/browser/session_preferences.h"
#include "shell/common/gin_converters/callback_converter.h"
#include "shell/common/gin_converters/content_converter.h"
@@ -558,9 +560,7 @@ Session::Session(v8::Isolate* isolate, ElectronBrowserContext* browser_context)
SessionPreferences::CreateForBrowserContext(browser_context);
protocol_.Reset(
isolate,
Protocol::Create(isolate, browser_context->protocol_registry()).ToV8());
protocol_ = Protocol::Create(isolate, browser_context->protocol_registry());
browser_context->SetUserData(
kElectronApiSessionKey,
@@ -1354,8 +1354,8 @@ v8::Local<v8::Value> Session::Extensions(v8::Isolate* isolate) {
return extensions_.Get(isolate);
}
v8::Local<v8::Value> Session::Protocol(v8::Isolate* isolate) {
return protocol_.Get(isolate);
api::Protocol* Session::Protocol() {
return protocol_.Get();
}
v8::Local<v8::Value> Session::ServiceWorkerContext(v8::Isolate* isolate) {

View File

@@ -10,7 +10,6 @@
#include <vector>
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/values.h"
#include "content/public/browser/download_manager.h"
#include "electron/buildflags/buildflags.h"
@@ -20,7 +19,6 @@
#include "services/network/public/mojom/ssl_config.mojom-forward.h"
#include "shell/browser/api/ipc_dispatcher.h"
#include "shell/browser/event_emitter_mixin.h"
#include "shell/browser/net/resolve_proxy_helper.h"
#include "shell/common/gin_helper/constructible.h"
#include "shell/common/gin_helper/self_keep_alive.h"
@@ -60,6 +58,7 @@ struct PreloadScript;
namespace api {
class NetLog;
class Protocol;
class WebRequest;
class Session final : public gin::Wrappable<Session>,
@@ -167,7 +166,7 @@ class Session final : public gin::Wrappable<Session>,
const gin_helper::Dictionary& options);
v8::Local<v8::Value> Cookies(v8::Isolate* isolate);
v8::Local<v8::Value> Extensions(v8::Isolate* isolate);
v8::Local<v8::Value> Protocol(v8::Isolate* isolate);
api::Protocol* Protocol();
v8::Local<v8::Value> ServiceWorkerContext(v8::Isolate* isolate);
WebRequest* WebRequest(v8::Isolate* isolate);
api::NetLog* NetLog(v8::Isolate* isolate);
@@ -214,7 +213,7 @@ class Session final : public gin::Wrappable<Session>,
// Cached gin_helper::Wrappable objects.
v8::TracedReference<v8::Value> cookies_;
v8::TracedReference<v8::Value> extensions_;
v8::TracedReference<v8::Value> protocol_;
cppgc::Member<api::Protocol> protocol_;
cppgc::Member<api::NetLog> net_log_;
v8::TracedReference<v8::Value> service_worker_context_;
cppgc::Member<api::WebRequest> web_request_;

View File

@@ -972,11 +972,12 @@ WebContents::WebContents(v8::Isolate* isolate,
void WebContents::InitZoomController(content::WebContents* web_contents,
const gin_helper::Dictionary& options) {
WebContentsZoomController::CreateForWebContents(web_contents);
zoom_controller_ = WebContentsZoomController::FromWebContents(web_contents);
WebContentsZoomController* const zoom_controller =
WebContentsZoomController::GetOrCreateForWebContents(web_contents);
double zoom_factor;
if (options.Get(options::kZoomFactor, &zoom_factor))
zoom_controller_->SetDefaultZoomFactor(zoom_factor);
zoom_controller->SetDefaultZoomFactor(zoom_factor);
// Nothing to do with ZoomController, but this function gets called in all
// init cases!
@@ -3152,16 +3153,18 @@ void OnGetDeviceNameToUse(base::WeakPtr<content::WebContents> web_contents,
.Set(printing::kSettingMediaSizeIsDefault, true);
};
const bool use_default_size =
print_settings.FindBool(kUseDefaultPrinterPageSize).value_or(false);
std::optional<gfx::Size> paper_size;
if (use_default_size)
paper_size = GetPrinterDefaultPaperSize(base::UTF16ToUTF8(info.second));
if (!print_settings.Find(printing::kSettingMediaSize)) {
const bool use_default_size =
print_settings.FindBool(kUseDefaultPrinterPageSize).value_or(false);
std::optional<gfx::Size> paper_size;
if (use_default_size)
paper_size = GetPrinterDefaultPaperSize(base::UTF16ToUTF8(info.second));
print_settings.Set(
printing::kSettingMediaSize,
paper_size ? make_media_size(paper_size->height(), paper_size->width())
: make_media_size(297000, 210000));
print_settings.Set(
printing::kSettingMediaSize,
paper_size ? make_media_size(paper_size->height(), paper_size->width())
: make_media_size(297000, 210000));
}
content::RenderFrameHost* rfh = GetRenderFrameHostToUse(web_contents.get());
if (!rfh)
@@ -3867,12 +3870,16 @@ gfx::Size WebContents::GetSizeForNewRenderView(content::WebContents* wc) {
return {};
}
WebContentsZoomController* WebContents::GetZoomController() const {
return WebContentsZoomController::FromWebContents(web_contents());
}
void WebContents::SetZoomLevel(double level) {
zoom_controller_->SetZoomLevel(level);
GetZoomController()->SetZoomLevel(level);
}
double WebContents::GetZoomLevel() const {
return zoom_controller_->GetZoomLevel();
return GetZoomController()->GetZoomLevel();
}
void WebContents::SetZoomFactor(gin_helper::ErrorThrower thrower,
@@ -3892,7 +3899,7 @@ double WebContents::GetZoomFactor() const {
}
void WebContents::SetTemporaryZoomLevel(double level) {
zoom_controller_->SetTemporaryZoomLevel(level);
GetZoomController()->SetTemporaryZoomLevel(level);
}
std::optional<PreloadScript> WebContents::GetPreloadScript() const {

View File

@@ -380,7 +380,7 @@ class WebContents final : public ExclusiveAccessContext,
content::RenderFrameHost* Opener();
content::RenderFrameHost* FocusedFrame();
WebContentsZoomController* GetZoomController() { return zoom_controller_; }
[[nodiscard]] WebContentsZoomController* GetZoomController() const;
void AddObserver(ExtendedWebContentsObserver* obs) {
observers_.AddObserver(obs);
@@ -858,11 +858,6 @@ class WebContents final : public ExclusiveAccessContext,
// destroyed before dialog_manager_, otherwise a crash would happen.
std::unique_ptr<InspectableWebContents> inspectable_web_contents_;
// The zoom controller for this webContents.
// Note: owned by inspectable_web_contents_, so declare this *after*
// that field to ensure the dtor destroys them in the right order.
raw_ptr<WebContentsZoomController> zoom_controller_ = nullptr;
std::optional<GURL> pending_unload_url_ = std::nullopt;
// Maps url to file path, used by the file requests sent from devtools.

View File

@@ -398,9 +398,7 @@ ExtensionFunction::ResponseAction TabsGetZoomFunction::Run() {
if (!contents)
return RespondNow(Error("No such tab"));
double zoom_level = contents->GetZoomController()->GetZoomLevel();
double zoom_factor = blink::ZoomLevelToZoomFactor(zoom_level);
const double zoom_factor = contents->GetZoomFactor();
return RespondNow(ArgumentList(tabs::GetZoom::Results::Create(zoom_factor)));
}
@@ -414,9 +412,9 @@ ExtensionFunction::ResponseAction TabsGetZoomSettingsFunction::Run() {
if (!contents)
return RespondNow(Error("No such tab"));
auto* zoom_controller = contents->GetZoomController();
WebContentsZoomController::ZoomMode zoom_mode =
contents->GetZoomController()->zoom_mode();
const auto* zoom_controller = contents->GetZoomController();
const WebContentsZoomController::ZoomMode zoom_mode =
zoom_controller->zoom_mode();
tabs::ZoomSettings zoom_settings;
ZoomModeToZoomSettings(zoom_mode, &zoom_settings);
zoom_settings.default_zoom_factor =

View File

@@ -440,13 +440,11 @@ NativeWindowViews::NativeWindowViews(const int32_t base_window_id,
if (window)
window->AddPreTargetHandler(this);
#if BUILDFLAG(IS_LINUX)
// We need to set bounds again after widget init for two reasons:
// 1. For CSD windows, user-specified bounds need to be inflated by frame
// insets, but the frame view isn't available at first.
// 2. The widget clamps bounds to fit the screen, but we want to allow
// windows larger than the display.
SetBounds(gfx::Rect(GetPosition(), size), false);
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN)
// The initial params.bounds was applied before the frame view existed, so
// non-client insets weren't accounted for and bounds need to be set again.
if (!GetRestoredFrameBorderInsets().IsEmpty())
SetBounds(gfx::Rect(GetPosition(), size), false);
#endif
}
@@ -906,7 +904,9 @@ gfx::Rect NativeWindowViews::GetNormalBounds() const {
if (IsMaximized() && transparent())
return restore_bounds_;
#endif
return WidgetToLogicalBounds(widget()->GetRestoredBounds());
gfx::Rect bounds = widget()->GetRestoredBounds();
bounds.Inset(GetRestoredFrameBorderInsets());
return bounds;
}
void NativeWindowViews::SetContentSizeConstraints(
@@ -1676,17 +1676,24 @@ NativeWindowHandle NativeWindowViews::GetNativeWindowHandle() const {
gfx::Rect NativeWindowViews::LogicalToWidgetBounds(
const gfx::Rect& bounds) const {
// Use widget() directly since NativeWindowViews::IsMaximized() can
// call GetBounds and end up in a loop.
if (widget()->IsMaximized() || widget()->IsFullscreen())
return bounds;
gfx::Rect widget_bounds(bounds);
const gfx::Insets frame_insets = GetRestoredFrameBorderInsets();
widget_bounds.Outset(
gfx::Outsets::TLBR(frame_insets.top(), frame_insets.left(),
frame_insets.bottom(), frame_insets.right()));
return widget_bounds;
}
gfx::Rect NativeWindowViews::WidgetToLogicalBounds(
const gfx::Rect& bounds) const {
if (widget()->IsMaximized() || widget()->IsFullscreen())
return bounds;
gfx::Rect logical_bounds(bounds);
logical_bounds.Inset(GetRestoredFrameBorderInsets());
return logical_bounds;

View File

@@ -194,6 +194,7 @@ class NativeWindowViews : public NativeWindow,
TaskbarHost& taskbar_host() { return taskbar_host_; }
void UpdateThickFrame();
void SetLayered();
bool has_thick_frame() const { return thick_frame_; }
#endif
SkColor overlay_button_color() const { return overlay_button_color_; }

View File

@@ -228,14 +228,15 @@ void WinFrameView::LayoutCaptionButtons() {
int custom_height = window()->titlebar_overlay_height();
int height = TitlebarHeight(custom_height);
// TODO(mlaurencin): This -1 creates a 1 pixel margin between the right
// edge of the button container and the edge of the window, allowing for this
// edge portion to return the correct hit test and be manually resized
// properly. Alternatives can be explored, but the differences in view
// structures between Electron and Chromium may result in this as the best
// option.
int variable_width =
IsMaximized() ? preferred_size.width() : preferred_size.width() - 1;
// Insets place the resize hit targets outside of the frame, so the caption
// buttons can go right at the edge. Without insets, the resize hit
// targets are inside the frame, and a 1px margin is needed to click and drag
// next to the button container. The margin can be removed if support is added
// for insets on non-thick frames.
int variable_width = !RestoredFrameBorderInsets().IsEmpty()
? preferred_size.width()
: (IsMaximized() ? preferred_size.width()
: preferred_size.width() - 1);
caption_button_container_->SetBounds(width() - preferred_size.width(),
WindowTopY(), variable_width, height);
@@ -267,22 +268,33 @@ bool WinFrameView::GetShouldPaintAsActive() {
gfx::Size WinFrameView::GetMinimumSize() const {
if (!window_)
return gfx::Size();
// Chromium expects minimum size to be in content dimensions on Windows
// because it adds the frame border automatically in OnGetMinMaxInfo.
// Chromium expects minimum size to be in content dimensions on Windows.
// If WidgetSizeIsClientSize() is true, it will account for frame borders and
// insets automatically.
return window_->GetContentMinimumSize();
}
gfx::Size WinFrameView::GetMaximumSize() const {
if (!window_)
return gfx::Size();
// Chromium expects minimum size to be in content dimensions on Windows
// because it adds the frame border automatically in OnGetMinMaxInfo.
// See comment in GetMinimumSize().
gfx::Size size = window_->GetContentMaximumSize();
// Electron public APIs returns (0, 0) when maximum size is not set, but it
// would break internal window APIs like HWNDMessageHandler::SetAspectRatio.
return size.IsEmpty() ? gfx::Size(INT_MAX, INT_MAX) : size;
}
gfx::Insets WinFrameView::RestoredFrameBorderInsets() const {
if (window_->has_frame() || !window_->has_thick_frame() ||
!window_->IsResizable())
return {};
const int thickness =
display::win::GetScreenWin()->GetSystemMetricsInDIP(SM_CXSIZEFRAME) +
display::win::GetScreenWin()->GetSystemMetricsInDIP(SM_CXPADDEDBORDER);
return gfx::Insets::TLBR(0, thickness, thickness, thickness);
}
BEGIN_METADATA(WinFrameView)
END_METADATA

View File

@@ -36,6 +36,9 @@ class WinFrameView : public FramelessView {
gfx::Size GetMinimumSize() const override;
gfx::Size GetMaximumSize() const override;
// views::FramelessView:
gfx::Insets RestoredFrameBorderInsets() const override;
WinCaptionButtonContainer* caption_button_container() {
return caption_button_container_;
}

View File

@@ -89,24 +89,45 @@ bool ElectronDesktopWindowTreeHostWin::GetDwmFrameInsetsInPixels(
return false;
}
bool ElectronDesktopWindowTreeHostWin::WidgetSizeIsClientSize() const {
// For both framed and frameless windows with resize insets (thick frames),
// this should return true so that the aura layer is sized to the client area
// rather than the full HWND, and so insets are accounted for when handling
// size/aspect ratio constraints.
if (native_window_view_->has_thick_frame())
return true;
return views::DesktopWindowTreeHostWin::WidgetSizeIsClientSize();
}
bool ElectronDesktopWindowTreeHostWin::GetClientAreaInsets(
gfx::Insets* insets,
int frame_thickness) const {
// Windows by default extends the maximized window slightly larger than
// current workspace, for frameless window since the standard frame has been
// removed, the client area would then be drew outside current workspace.
//
// Indenting the client area can fix this behavior.
if (IsMaximized() && !native_window_view_->has_frame()) {
// The insets would be eventually passed to WM_NCCALCSIZE, which takes
// the metrics under the DPI of _main_ monitor instead of current monitor.
//
// Please make sure you tested maximized frameless window under multiple
// monitors with different DPIs before changing this code.
if (!native_window_view_->has_frame()) {
const int thickness = ::GetSystemMetrics(SM_CXSIZEFRAME) +
::GetSystemMetrics(SM_CXPADDEDBORDER);
*insets = gfx::Insets::TLBR(thickness, thickness, thickness, thickness);
return true;
if (IsMaximized()) {
// Windows by default extends the maximized window slightly larger than
// current workspace, for frameless window since the standard frame has
// been removed, the client area would then be drew outside current
// workspace.
//
// Indenting the client area can fix this behavior.
//
// The insets would be eventually passed to WM_NCCALCSIZE, which takes
// the metrics under the DPI of _main_ monitor instead of current monitor.
//
// Please make sure you tested maximized frameless window under multiple
// monitors with different DPIs before changing this code.
*insets = gfx::Insets::TLBR(thickness, thickness, thickness, thickness);
return true;
} else if (native_window_view_->has_thick_frame() &&
native_window_view_->IsResizable()) {
// Grow the insets to support resize targets past the frame edge like in
// windows with standard frames.
*insets = gfx::Insets::TLBR(0, thickness, thickness, thickness);
return true;
}
}
return false;
}

View File

@@ -40,6 +40,7 @@ class ElectronDesktopWindowTreeHostWin : public views::DesktopWindowTreeHostWin,
LRESULT* result) override;
bool ShouldPaintAsActive() const override;
bool GetDwmFrameInsetsInPixels(gfx::Insets* insets) const override;
bool WidgetSizeIsClientSize() const override;
bool GetClientAreaInsets(gfx::Insets* insets,
int frame_thickness) const override;
bool HandleMouseEventForCaption(UINT message) const override;

View File

@@ -10,6 +10,7 @@
#include <vector>
#include "base/command_line.h"
#include "base/functional/callback_helpers.h"
#include "base/strings/string_split.h"
#include "components/network_hints/renderer/web_prescient_networking_impl.h"
#include "content/common/buildflags.h"