Compare commits

..

5 Commits

Author SHA1 Message Date
deepak1556
dc24fde4bf fix: crash on shutdown when unregistering power notification
Refs https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-unregistersuspendresumenotification
the handle should be the return value of registeration api
not the window handle.
2026-04-10 05:33:38 +09:00
deepak1556
2e78fd640f chore: remove redundant selfkeepalive
Followup to 7c0cb61b3c
2026-04-09 17:44:32 +09:00
deepak1556
22739efc7d spec: reduce menu count to remove CI flakiness 2026-04-09 15:49:22 +09:00
deepak1556
676ce61e76 spec: add menu leak regression test 2026-04-09 12:48:59 +09:00
deepak1556
676605bdf3 chore: address blink gc plugin errors
Key fixes:
- Replace `base::WeakPtrFactory` with `gin::WeakCellFactory` in
  MenuMac, MenuViews, and NetLog, since weak pointers to cppgc-managed
  objects must go through weak cells
- Replace `v8::Global<v8::Value>` with `cppgc::Persistent<Menu>` for
  the menu reference in BaseWindow
- Stop using `gin_helper::Handle<T>` with cppgc types; use raw `T*`
  and add a `static_assert` to prevent future misuse
- Add proper `Trace()` overrides for Menu, MenuMac, MenuViews, and
  NetLog to ensure cppgc members are visited during garbage collection
- Replace `SelfKeepAlive` prevent-GC mechanism in Menu with a
  `cppgc::Persistent` prevent-GC captured in `BindSelfToClosure`
- Introduce `GC_PLUGIN_IGNORE` macro to suppress
  known-safe violations: mojo::Remote fields, ObjC bridging pointers,
  and intentional persistent self-references
- Mark `ArgumentHolder` as `CPPGC_STACK_ALLOCATED()` in both Electron's
  and gin's function_template.h to silence raw-pointer-to-GC-type
  warnings
2026-04-09 12:31:25 +09:00
72 changed files with 908 additions and 449 deletions

View File

@@ -442,7 +442,34 @@ jobs:
contents: read
needs: [docs-only, macos-x64, macos-arm64, linux-x64, linux-x64-asan, linux-arm, linux-arm64, windows-x64, windows-x86, windows-arm64]
if: always() && github.repository == 'electron/electron' && !contains(needs.*.result, 'failure')
steps:
steps:
- name: GitHub Actions Jobs Done
run: |
echo "All GitHub Actions Jobs are done"
check-signed-commits:
name: Check signed commits in green PR
needs: gha-done
if: ${{ contains(github.event.pull_request.labels.*.name, 'needs-signed-commits')}}
runs-on: ubuntu-slim
permissions:
contents: read
pull-requests: write
steps:
- name: Check signed commits in PR
uses: 1Password/check-signed-commits-action@ed2885f3ed2577a4f5d3c3fe895432a557d23d52 # v1
with:
comment: |
⚠️ This PR contains unsigned commits. This repository enforces [commit signatures](https://docs.github.com/en/authentication/managing-commit-signature-verification)
for all incoming PRs. To get your PR merged, please sign those commits
(`git rebase --exec 'git commit -S --amend --no-edit -n' @{upstream}`) and force push them to this branch
(`git push --force-with-lease`)
For more information on signing commits, see GitHub's documentation on [Telling Git about your signing key](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key).
- name: Remove needs-signed-commits label
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_URL: ${{ github.event.pull_request.html_url }}
run: |
gh pr edit $PR_URL --remove-label needs-signed-commits

View File

@@ -13,6 +13,7 @@ permissions: {}
jobs:
check-signed-commits:
name: Check signed commits in PR
if: ${{ !contains(github.event.pull_request.labels.*.name, 'needs-signed-commits')}}
runs-on: ubuntu-slim
permissions:
contents: read
@@ -22,9 +23,9 @@ jobs:
uses: 1Password/check-signed-commits-action@ed2885f3ed2577a4f5d3c3fe895432a557d23d52 # v1
with:
comment: |
⚠️ This PR contains unsigned commits. This repository enforces [commit signatures](https://docs.github.com/en/authentication/managing-commit-signature-verification)
for all incoming PRs. To get your PR merged, please sign those commits
(`git rebase --exec 'git commit -S --amend --no-edit -n' @{upstream}`) and force push them to this branch
⚠️ This PR contains unsigned commits. This repository enforces [commit signatures](https://docs.github.com/en/authentication/managing-commit-signature-verification)
for all incoming PRs. To get your PR merged, please sign those commits
(`git rebase --exec 'git commit -S --amend --no-edit -n' @{upstream}`) and force push them to this branch
(`git push --force-with-lease`)
For more information on signing commits, see GitHub's documentation on [Telling Git about your signing key](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key).
@@ -36,11 +37,3 @@ jobs:
PR_URL: ${{ github.event.pull_request.html_url }}
run: |
gh pr edit $PR_URL --add-label needs-signed-commits
- name: Remove needs-signed-commits label
if: ${{ success() && contains(github.event.pull_request.labels.*.name, 'needs-signed-commits') }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_URL: ${{ github.event.pull_request.html_url }}
run: |
gh pr edit $PR_URL --remove-label needs-signed-commits

View File

@@ -147,6 +147,25 @@ config("branding") {
config("electron_lib_config") {
include_dirs = [ "." ]
cflags = []
if (is_clang && clang_use_chrome_plugins) {
# The plugin is built directly into clang, so there's no need to load it
# dynamically.
cflags += [
"-Xclang",
"-add-plugin",
"-Xclang",
"blink-gc-plugin",
"-Xclang",
"-plugin-arg-blink-gc-plugin",
"-Xclang",
"check-directory=electron/shell/",
"-Xclang",
"-plugin-arg-blink-gc-plugin",
"-Xclang",
"check-directory=gin/",
]
}
}
# We generate the definitions twice here, once in //electron/electron.d.ts

View File

@@ -1,5 +1,5 @@
import { shell } from 'electron/common';
import { app, dialog, BrowserWindow, ipcMain, Menu } from 'electron/main';
import { app, dialog, BrowserWindow, ipcMain } from 'electron/main';
import * as path from 'node:path';
import * as url from 'node:url';
@@ -11,53 +11,6 @@ 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`, and `Window`.
> It contains standard items such as `File`, `Edit`, `View`, `Window` and `Help`.
#### `Menu.getApplicationMenu()`

View File

@@ -1,4 +1,5 @@
import { Menu } from 'electron/main';
import { shell } from 'electron/common';
import { app, Menu } from 'electron/main';
const isMac = process.platform === 'darwin';
@@ -11,13 +12,47 @@ 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' }
{ role: 'windowMenu' },
helpMenu
];
const menu = Menu.buildFromTemplate(template);

View File

@@ -21,7 +21,7 @@
"@types/semver": "^7.5.8",
"@types/stream-json": "^1.7.8",
"@types/temp": "^0.9.4",
"@xmldom/xmldom": "^0.8.12",
"@xmldom/xmldom": "^0.8.11",
"buffer": "^6.0.3",
"chalk": "^4.1.0",
"check-for-leaks": "^1.2.1",

View File

@@ -125,6 +125,7 @@ feat_separate_content_settings_callback_for_sync_and_async_clipboard.patch
fix_win32_synchronous_spellcheck.patch
chore_grandfather_in_electron_views_and_delegates.patch
refactor_patch_electron_permissiontypes_into_blink.patch
revert_views_remove_desktopwindowtreehostwin_window_enlargement.patch
fix_add_macos_memory_query_fallback_to_avoid_crash.patch
fix_resolve_dynamic_background_material_update_issue_on_windows_11.patch
feat_add_support_for_embedder_snapshot_validation.patch
@@ -150,3 +151,4 @@ fix_pulseaudio_stream_and_icon_names.patch
fix_fire_menu_popup_start_for_dynamically_created_aria_menus.patch
feat_allow_enabling_extensions_on_custom_protocols.patch
fix_initialize_com_on_desktopmedialistcapturethread_on_windows.patch
gin_mark_argumentholder_as_cppgc_stack_allocated.patch

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..9f7e1b1b8d871721891255c1f21de825d0df1e30 100644
index fee622ebde42211de6f702b754cfa38595df5a1c..6b524632ebb405e473cf4fe8e253bd13bf7b67e5 100644
--- a/gin/public/wrappable_pointer_tags.h
+++ b/gin/public/wrappable_pointer_tags.h
@@ -77,7 +77,21 @@ enum WrappablePointerTag : uint16_t {
@@ -77,7 +77,20 @@ enum WrappablePointerTag : uint16_t {
kWebAXObjectProxy, // content::WebAXObjectProxy
kWrappedExceptionHandler, // extensions::WrappedExceptionHandler
kIndigoContext, // indigo::IndigoContext
@@ -24,7 +24,6 @@ index fee622ebde42211de6f702b754cfa38595df5a1c..9f7e1b1b8d871721891255c1f21de825
+ 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

@@ -8,10 +8,10 @@ such as the background turning black when maximizing the window and
dynamic background material settings not taking effect.
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
index e4da40256ce94d6a0896792a8ef2faa18e1fa5d2..3a5833fcc018f32e86c0a95a42937fb9ac6c5a40 100644
index d1e06b675b19226cf3b78e1aada8d8f2d684fada..ce810555b8501797643987916a728cad8f5adaa5 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
@@ -167,6 +167,10 @@ void DesktopWindowTreeHostWin::FinishTouchDrag(gfx::Point screen_point) {
@@ -184,6 +184,10 @@ void DesktopWindowTreeHostWin::FinishTouchDrag(gfx::Point screen_point) {
}
}
@@ -23,7 +23,7 @@ index e4da40256ce94d6a0896792a8ef2faa18e1fa5d2..3a5833fcc018f32e86c0a95a42937fb9
void DesktopWindowTreeHostWin::Init(const Widget::InitParams& params) {
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
index 27322ef34edf3fa8bfbd20b1baddcaf3b7555618..b8d1fa863fd05ebc3ab8ac5ef8c4d81361ce45fe 100644
index a40bd9f25fa07a553c011cf19f155f8158f4ae5f..ae2baec731b5fcd8be97f2177d23b860d67ab8bc 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
@@ -93,6 +93,8 @@ class VIEWS_EXPORT DesktopWindowTreeHostWin

View File

@@ -0,0 +1,48 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: deepak1556 <hop2deep@gmail.com>
Date: Tue, 7 Apr 2026 02:34:49 +0900
Subject: [gin] Mark ArgumentHolder as CPPGC_STACK_ALLOCATED
ArgumentHolder is always stack-allocated from Invoker.
When ArgType is a pointer to a cppgc-managed type, the blink-gc plugin
reports an error for the raw pointer `value` field:
[blink-gc] Class 'ArgumentHolder<0, T*>' contains invalid fields.
[blink-gc] Raw pointer field 'value' to a GC managed class.
Disallow any heap allocations and make the lifetime explicit.
Backports https://chromium-review.googlesource.com/c/chromium/src/+/7728865
diff --git a/gin/function_template.h b/gin/function_template.h
index 873015289db9709c00c32080e5387d9cdfea1f69..bc84040952b79f02df783833005cc9583f3378a5 100644
--- a/gin/function_template.h
+++ b/gin/function_template.h
@@ -20,6 +20,7 @@
#include "gin/gin_export.h"
#include "gin/per_isolate_data.h"
#include "gin/public/gin_embedders.h"
+#include "v8/include/cppgc/macros.h"
#include "v8/include/v8-external.h"
#include "v8/include/v8-forward.h"
#include "v8/include/v8-persistent-handle.h"
@@ -162,6 +163,9 @@ GIN_EXPORT void ThrowConversionError(Arguments* args,
// at position |index|.
template <size_t index, typename ArgType, typename = void>
struct ArgumentHolder {
+ CPPGC_STACK_ALLOCATED();
+
+ public:
using ArgLocalType = typename CallbackParamTraits<ArgType>::LocalType;
ArgLocalType value;
@@ -184,6 +188,9 @@ template <size_t index, typename ArgType>
std::is_constructible_v<typename CallbackParamTraits<ArgType>::LocalType,
v8::Isolate*>)
struct ArgumentHolder<index, ArgType> {
+ CPPGC_STACK_ALLOCATED();
+
+ public:
using ArgLocalType = typename CallbackParamTraits<ArgType>::LocalType;
ArgLocalType value;

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..5ca7920c8525c3c72fd96b14709fb35a9cc28daf 100644
index dc2a15ab4d784b0b6c85b84a30c3c08a17ed8e3d..e197026e8a7f132c1bf90a0f5f1eabb4f5f064ee 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..5ca7920c8525c3c72fd96b14709fb35a
// Want the same PrintBackend service as the query so that we use the same
// device context.
print_document_client_id_ =
@@ -189,6 +189,28 @@ void PrinterQueryOop::GetSettingsWithUI(uint32_t document_page_count,
@@ -189,6 +189,21 @@ 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,19 +643,12 @@ index dc2a15ab4d784b0b6c85b84a30c3c08a17ed8e3d..5ca7920c8525c3c72fd96b14709fb35a
+ // remote service context, not the local one used by the native dialog.
+ if (settings().dpi()) {
+ printing_context()->SetPrintSettings(settings());
+ if (printing_context()->UpdatePrinterSettings(
+ PrintingContext::PrinterSettings{
+ printing_context()->UpdatePrinterSettings(PrintingContext::PrinterSettings{
+#if BUILDFLAG(IS_MAC)
+ .external_preview = false,
+ .external_preview = false,
+#endif
+ .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();
+ }
+ .show_system_dialog = false,
+ });
+ }
+
PrinterQuery::GetSettingsWithUI(

View File

@@ -0,0 +1,285 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: John Kleinschmidt <jkleinsc@electronjs.org>
Date: Sat, 14 Jun 2025 16:21:07 -0400
Subject: Revert "[views] Remove DesktopWindowTreeHostWin::window_enlargement_"
This reverts commit 1771dbae6961e7bb7c22bbc6c77f84d90ef2be46.
Electron needs this patch to allow windows smaller than 64x64
on Windows. We should refactor our code so that this patch isn't
necessary.
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 988866d79a5d1dbd366ebdbff0e8eb2c0c498168..5761ac48be0a64618be0a94606149dd944e46e27 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -22626,6 +22626,21 @@
]
}
],
+ "TransparentHwndEnlargement": [
+ {
+ "platforms": [
+ "windows"
+ ],
+ "experiments": [
+ {
+ "name": "DisableTransparentHwndEnlargement",
+ "disable_features": [
+ "EnableTransparentHwndEnlargement"
+ ]
+ }
+ ]
+ }
+ ],
"TransportSecurityFileWriterScheduleAndroid": [
{
"platforms": [
diff --git a/ui/views/views_features.cc b/ui/views/views_features.cc
index 47077e16870889ef8f8c8b2adf58015bd5aff7fa..ba59e6e1609e61579bf49aca095490b083d72051 100644
--- a/ui/views/views_features.cc
+++ b/ui/views/views_features.cc
@@ -30,6 +30,14 @@ BASE_FEATURE(kEnableInputProtection, base::FEATURE_DISABLED_BY_DEFAULT);
// crbug.com/370856871.
BASE_FEATURE(kEnableTouchDragCursorSync, base::FEATURE_ENABLED_BY_DEFAULT);
+// Enables enlargement of HWNDs to a minimum size of 64x64 to handle reported
+// graphical glitches on certain hardware.
+// TODO(crbug.com/401996981): Remove this once enlargement is confirmed to no
+// longer be needed.
+BASE_FEATURE(kEnableTransparentHwndEnlargement,
+ "EnableTransparentHwndEnlargement",
+ base::FEATURE_DISABLED_BY_DEFAULT);
+
// Used to enable keyboard-accessible tooltips in Views UI, as opposed
// to kKeyboardAccessibleTooltip in //ui/base/ui_base_features.cc.
BASE_FEATURE(kKeyboardAccessibleTooltipInViews,
diff --git a/ui/views/views_features.h b/ui/views/views_features.h
index 888a16fb6213eceb131ae636dc643d7f2d5bcad9..6077412165081cd4abeaf0b061feb2f795ee8131 100644
--- a/ui/views/views_features.h
+++ b/ui/views/views_features.h
@@ -15,6 +15,7 @@ namespace views::features {
VIEWS_EXPORT BASE_DECLARE_FEATURE(kApplyInitialUrlToWebContents);
VIEWS_EXPORT BASE_DECLARE_FEATURE(kEnableInputProtection);
VIEWS_EXPORT BASE_DECLARE_FEATURE(kEnableTouchDragCursorSync);
+VIEWS_EXPORT BASE_DECLARE_FEATURE(kEnableTransparentHwndEnlargement);
VIEWS_EXPORT BASE_DECLARE_FEATURE(kKeyboardAccessibleTooltipInViews);
} // namespace views::features
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
index e4da40256ce94d6a0896792a8ef2faa18e1fa5d2..d1e06b675b19226cf3b78e1aada8d8f2d684fada 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
@@ -85,6 +85,23 @@ namespace {
// This constant controls how many pixels wide that border is.
const int kMouseCaptureRegionBorder = 5;
+gfx::Size GetExpandedWindowSize(bool is_translucent, gfx::Size size) {
+ if (!base::FeatureList::IsEnabled(
+ features::kEnableTransparentHwndEnlargement) ||
+ !is_translucent) {
+ return size;
+ }
+
+ // Some AMD drivers can't display windows that are less than 64x64 pixels,
+ // so expand them to be at least that size. http://crbug.com/286609
+ gfx::Size expanded(std::max(size.width(), 64), std::max(size.height(), 64));
+ return expanded;
+}
+
+void InsetBottomRight(gfx::Rect* rect, const gfx::Vector2d& vector) {
+ rect->Inset(gfx::Insets::TLBR(0, 0, vector.y(), vector.x()));
+}
+
// Updates the cursor clip region. Used for mouse locking.
void UpdateMouseLockRegion(aura::Window* window, bool locked) {
if (!locked) {
@@ -342,9 +359,14 @@ bool DesktopWindowTreeHostWin::IsVisible() const {
}
void DesktopWindowTreeHostWin::SetSize(const gfx::Size& size) {
- const gfx::Size size_in_pixels =
+ gfx::Size size_in_pixels =
display::win::GetScreenWin()->DIPToScreenSize(GetHWND(), size);
- message_handler_->SetSize(size_in_pixels);
+ gfx::Size expanded =
+ GetExpandedWindowSize(message_handler_->is_translucent(), size_in_pixels);
+ window_enlargement_ =
+ gfx::Vector2d(expanded.width() - size_in_pixels.width(),
+ expanded.height() - size_in_pixels.height());
+ message_handler_->SetSize(expanded);
}
void DesktopWindowTreeHostWin::StackAbove(aura::Window* window) {
@@ -359,30 +381,40 @@ void DesktopWindowTreeHostWin::StackAtTop() {
}
void DesktopWindowTreeHostWin::CenterWindow(const gfx::Size& size) {
- const gfx::Size size_in_pixels =
+ gfx::Size size_in_pixels =
display::win::GetScreenWin()->DIPToScreenSize(GetHWND(), size);
- message_handler_->CenterWindow(size_in_pixels);
+ gfx::Size expanded_size;
+ expanded_size =
+ GetExpandedWindowSize(message_handler_->is_translucent(), size_in_pixels);
+ window_enlargement_ =
+ gfx::Vector2d(expanded_size.width() - size_in_pixels.width(),
+ expanded_size.height() - size_in_pixels.height());
+ message_handler_->CenterWindow(expanded_size);
}
void DesktopWindowTreeHostWin::GetWindowPlacement(
gfx::Rect* bounds,
ui::mojom::WindowShowState* show_state) const {
message_handler_->GetWindowPlacement(bounds, show_state);
+ InsetBottomRight(bounds, window_enlargement_);
*bounds = display::win::GetScreenWin()->ScreenToDIPRect(GetHWND(), *bounds);
}
gfx::Rect DesktopWindowTreeHostWin::GetWindowBoundsInScreen() const {
gfx::Rect pixel_bounds = message_handler_->GetWindowBoundsInScreen();
+ InsetBottomRight(&pixel_bounds, window_enlargement_);
return display::win::GetScreenWin()->ScreenToDIPRect(GetHWND(), pixel_bounds);
}
gfx::Rect DesktopWindowTreeHostWin::GetClientAreaBoundsInScreen() const {
gfx::Rect pixel_bounds = message_handler_->GetClientAreaBoundsInScreen();
+ InsetBottomRight(&pixel_bounds, window_enlargement_);
return display::win::GetScreenWin()->ScreenToDIPRect(GetHWND(), pixel_bounds);
}
gfx::Rect DesktopWindowTreeHostWin::GetRestoredBounds() const {
gfx::Rect pixel_bounds = message_handler_->GetRestoredBounds();
+ InsetBottomRight(&pixel_bounds, window_enlargement_);
return display::win::GetScreenWin()->ScreenToDIPRect(GetHWND(), pixel_bounds);
}
@@ -701,37 +733,44 @@ void DesktopWindowTreeHostWin::HideImpl() {
// other get/set methods work in DIP.
gfx::Rect DesktopWindowTreeHostWin::GetBoundsInPixels() const {
- const gfx::Rect bounds_px(message_handler_->GetClientAreaBounds());
+ gfx::Rect bounds(message_handler_->GetClientAreaBounds());
// If the window bounds were expanded we need to return the original bounds
// To achieve this we do the reverse of the expansion, i.e. add the
// window_expansion_top_left_delta_ to the origin and subtract the
// window_expansion_bottom_right_delta_ from the width and height.
- const gfx::Rect without_expansion_bounds_px(
- bounds_px.x() + window_expansion_top_left_delta_.x(),
- bounds_px.y() + window_expansion_top_left_delta_.y(),
- bounds_px.width() - window_expansion_bottom_right_delta_.x(),
- bounds_px.height() - window_expansion_bottom_right_delta_.y());
- return without_expansion_bounds_px;
+ gfx::Rect without_expansion(
+ bounds.x() + window_expansion_top_left_delta_.x(),
+ bounds.y() + window_expansion_top_left_delta_.y(),
+ bounds.width() - window_expansion_bottom_right_delta_.x() -
+ window_enlargement_.x(),
+ bounds.height() - window_expansion_bottom_right_delta_.y() -
+ window_enlargement_.y());
+ return without_expansion;
}
-void DesktopWindowTreeHostWin::SetBoundsInPixels(
- const gfx::Rect& bounds_in_pixels) {
+void DesktopWindowTreeHostWin::SetBoundsInPixels(const gfx::Rect& bounds) {
// If the window bounds have to be expanded we need to subtract the
// window_expansion_top_left_delta_ from the origin and add the
// window_expansion_bottom_right_delta_ to the width and height
- const gfx::Size old_content_size_px = GetBoundsInPixels().size();
-
- const gfx::Rect expanded_bounds_px(
- bounds_in_pixels.x() - window_expansion_top_left_delta_.x(),
- bounds_in_pixels.y() - window_expansion_top_left_delta_.y(),
- bounds_in_pixels.width() + window_expansion_bottom_right_delta_.x(),
- bounds_in_pixels.height() + window_expansion_bottom_right_delta_.y());
-
- // When `expanded_bounds_px` causes the window to be moved to a display with a
+ gfx::Size old_content_size = GetBoundsInPixels().size();
+
+ gfx::Rect expanded(
+ bounds.x() - window_expansion_top_left_delta_.x(),
+ bounds.y() - window_expansion_top_left_delta_.y(),
+ bounds.width() + window_expansion_bottom_right_delta_.x(),
+ bounds.height() + window_expansion_bottom_right_delta_.y());
+
+ gfx::Rect new_expanded(
+ expanded.origin(),
+ GetExpandedWindowSize(message_handler_->is_translucent(),
+ expanded.size()));
+ window_enlargement_ =
+ gfx::Vector2d(new_expanded.width() - expanded.width(),
+ new_expanded.height() - expanded.height());
+ // When |new_expanded| causes the window to be moved to a display with a
// different DSF, HWNDMessageHandler::OnDpiChanged() will be called and the
// window size will be scaled automatically.
- message_handler_->SetBounds(expanded_bounds_px,
- old_content_size_px != bounds_in_pixels.size());
+ message_handler_->SetBounds(new_expanded, old_content_size != bounds.size());
}
gfx::Rect
@@ -943,18 +982,26 @@ int DesktopWindowTreeHostWin::GetNonClientComponent(
void DesktopWindowTreeHostWin::GetWindowMask(const gfx::Size& size_px,
SkPath* path) {
- Widget* widget = GetWidget();
- if (!widget || !widget->non_client_view()) {
- return;
- }
+ // Request the window mask for hwnd of `size_px`. The hwnd size must be
+ // adjusted by `window_enlargement` to return to the client-expected window
+ // size (see crbug.com/41047830).
+ const gfx::Size adjusted_size_in_px =
+ size_px - gfx::Size(window_enlargement_.x(), window_enlargement_.y());
- widget->non_client_view()->GetWindowMask(
- display::win::GetScreenWin()->ScreenToDIPSize(GetHWND(), size_px), path);
- // Convert path in DIPs to pixels.
- if (!path->isEmpty()) {
- const float scale =
- display::win::GetScreenWin()->GetScaleFactorForHWND(GetHWND());
- *path = path->makeTransform(SkMatrix::Scale(scale, scale));
+ if (Widget* widget = GetWidget(); widget && widget->non_client_view()) {
+ widget->non_client_view()->GetWindowMask(
+ display::win::GetScreenWin()->ScreenToDIPSize(GetHWND(),
+ adjusted_size_in_px),
+ path);
+ // Convert path in DIPs to pixels.
+ if (!path->isEmpty()) {
+ const float scale =
+ display::win::GetScreenWin()->GetScaleFactorForHWND(GetHWND());
+ *path = path->makeTransform(SkMatrix::Scale(scale, scale));
+ }
+ } else if (!window_enlargement_.IsZero()) {
+ *path = SkPath::Rect(SkRect::MakeXYWH(0, 0, adjusted_size_in_px.width(),
+ adjusted_size_in_px.height()));
}
}
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
index 27322ef34edf3fa8bfbd20b1baddcaf3b7555618..a40bd9f25fa07a553c011cf19f155f8158f4ae5f 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
@@ -175,7 +175,7 @@ class VIEWS_EXPORT DesktopWindowTreeHostWin
void ShowImpl() override;
void HideImpl() override;
gfx::Rect GetBoundsInPixels() const override;
- void SetBoundsInPixels(const gfx::Rect& bounds_in_pixels) override;
+ void SetBoundsInPixels(const gfx::Rect& bounds) override;
gfx::Rect GetBoundsInAcceleratedWidgetPixelCoordinates() override;
gfx::Point GetLocationOnScreenInPixels() const override;
void SetCapture() override;
@@ -330,6 +330,12 @@ class VIEWS_EXPORT DesktopWindowTreeHostWin
gfx::Vector2d window_expansion_top_left_delta_;
gfx::Vector2d window_expansion_bottom_right_delta_;
+ // Windows are enlarged to be at least 64x64 pixels, so keep track of the
+ // extra added here.
+ // TODO(crbug.com/401996981): This is likely no longer necessary and should be
+ // removed.
+ gfx::Vector2d window_enlargement_;
+
// Whether the window close should be converted to a hide, and then actually
// closed on the completion of the hide animation. This is cached because
// the property is set on the contained window which has a shorter lifetime.

View File

@@ -8,9 +8,7 @@ from pathlib import Path
SRC_DIR = Path(__file__).resolve().parents[3]
sys.path.append(os.path.join(SRC_DIR, 'third_party/electron_node/tools'))
sys.path.append(str(Path(__file__).resolve().parents[1])) # electron/script/
from lib.util import get_out_dir
import install
class LoadPythonDictionaryError(Exception):
@@ -33,6 +31,13 @@ def LoadPythonDictionary(path):
)
return file_data
def get_out_dir():
out_dir = 'Testing'
override = os.environ.get('ELECTRON_OUT_DIR')
if override is not None:
out_dir = override
return os.path.join(SRC_DIR, 'out', out_dir)
if __name__ == '__main__':
node_root_dir = os.path.join(SRC_DIR, 'third_party/electron_node')
out = {}

View File

@@ -76,6 +76,7 @@
#include "shell/common/gin_converters/value_converter.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/error_thrower.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/language_util.h"

View File

@@ -732,10 +732,10 @@ bool BaseWindow::IsFocusable() const {
void BaseWindow::SetMenu(v8::Isolate* isolate, v8::Local<v8::Value> value) {
auto context = isolate->GetCurrentContext();
gin_helper::Handle<Menu> menu;
Menu* menu = nullptr;
v8::Local<v8::Object> object;
if (value->IsObject() && value->ToObject(context).ToLocal(&object) &&
gin::ConvertFromV8(isolate, value, &menu) && !menu.IsEmpty()) {
gin::ConvertFromV8(isolate, value, &menu) && menu) {
// We only want to update the menu if the menu has a non-zero item count,
// or we risk crashes.
if (menu->model()->GetItemCount() == 0) {
@@ -744,7 +744,7 @@ void BaseWindow::SetMenu(v8::Isolate* isolate, v8::Local<v8::Value> value) {
window_->SetMenu(menu->model());
}
menu_.Reset(isolate, menu.ToV8());
menu_ = menu;
} else if (value->IsNull()) {
RemoveMenu();
} else {
@@ -754,7 +754,7 @@ void BaseWindow::SetMenu(v8::Isolate* isolate, v8::Local<v8::Value> value) {
}
void BaseWindow::RemoveMenu() {
menu_.Reset();
menu_.Clear();
window_->SetMenu(nullptr);
}

View File

@@ -17,6 +17,7 @@
#include "shell/browser/native_window_observer.h"
#include "shell/common/api/electron_api_native_image.h"
#include "shell/common/gin_helper/trackable_object.h"
#include "v8/include/cppgc/persistent.h"
namespace gin {
class Arguments;
@@ -34,6 +35,7 @@ class NativeWindow;
namespace api {
class Menu;
class View;
class BaseWindow : public gin_helper::TrackableObject<BaseWindow>,
@@ -284,7 +286,7 @@ class BaseWindow : public gin_helper::TrackableObject<BaseWindow>,
#endif
v8::Global<v8::Value> content_view_;
v8::Global<v8::Value> menu_;
cppgc::Persistent<Menu> menu_;
v8::Global<v8::Value> parent_window_;
std::unique_ptr<NativeWindow> window_;

View File

@@ -11,6 +11,7 @@
#include "mojo/public/cpp/bindings/remote.h"
#include "services/network/public/cpp/data_element.h"
#include "services/network/public/mojom/data_pipe_getter.mojom.h"
#include "shell/common/gc_plugin.h"
namespace electron::api {
@@ -45,6 +46,8 @@ class DataPipeHolder final : public gin::Wrappable<DataPipeHolder> {
private:
std::string id_;
GC_PLUGIN_IGNORE(
"Context tracking of remote is not needed in the browser process.")
mojo::Remote<network::mojom::DataPipeGetter> data_pipe_;
};

View File

@@ -22,6 +22,7 @@
#include "shell/common/gin_helper/object_template_builder.h"
#include "shell/common/node_includes.h"
#include "ui/base/models/image_model.h"
#include "v8/include/cppgc/persistent.h"
#if BUILDFLAG(IS_MAC)
@@ -71,6 +72,11 @@ Menu::~Menu() {
RemoveModelObserver();
}
void Menu::Trace(cppgc::Visitor* visitor) const {
gin::Wrappable<Menu>::Trace(visitor);
visitor->Trace(parent_);
}
void Menu::RemoveModelObserver() {
if (model_) {
model_->RemoveObserver(this);
@@ -189,20 +195,11 @@ void Menu::OnMenuWillShow(ui::SimpleMenuModel* source) {
}
base::OnceClosure Menu::BindSelfToClosure(base::OnceClosure callback) {
// return ((callback, ref) => { callback() }).bind(null, callback, this)
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::HandleScope scope(isolate);
v8::Local<v8::Object> self;
if (GetWrapper(isolate).ToLocal(&self)) {
v8::Global<v8::Value> ref(isolate, self);
return base::BindOnce(
[](base::OnceClosure callback, v8::Global<v8::Value> ref) {
std::move(callback).Run();
},
std::move(callback), std::move(ref));
} else {
return base::DoNothing();
}
return base::BindOnce(
[](base::OnceClosure callback, cppgc::Persistent<Menu> prevent_gc) {
std::move(callback).Run();
},
std::move(callback), cppgc::Persistent<Menu>(this));
}
void Menu::InsertItemAt(int index,
@@ -271,12 +268,10 @@ std::u16string Menu::GetAcceleratorTextAtForTesting(int index) const {
}
void Menu::OnMenuWillClose() {
keep_alive_.Clear();
Emit("menu-will-close");
}
void Menu::OnMenuWillShow() {
keep_alive_ = this;
Emit("menu-will-show");
}

View File

@@ -8,13 +8,12 @@
#include <memory>
#include <string>
#include "base/memory/raw_ptr.h"
#include "gin/wrappable.h"
#include "shell/browser/event_emitter_mixin.h"
#include "shell/browser/ui/electron_menu_model.h"
#include "shell/common/gin_helper/constructible.h"
#include "shell/common/gin_helper/self_keep_alive.h"
#include "ui/base/mojom/menu_source_type.mojom-forward.h"
#include "v8/include/cppgc/member.h"
namespace gin {
class Arguments;
@@ -45,6 +44,7 @@ class Menu : public gin::Wrappable<Menu>,
static const gin::WrapperInfo kWrapperInfo;
const gin::WrapperInfo* wrapper_info() const override;
const char* GetHumanReadableName() const override;
void Trace(cppgc::Visitor*) const override;
// gin_helper::Constructible
static void FillObjectTemplate(v8::Isolate*, v8::Local<v8::ObjectTemplate>);
@@ -102,9 +102,6 @@ class Menu : public gin::Wrappable<Menu>,
virtual void ClosePopupAt(int32_t window_id) = 0;
virtual std::u16string GetAcceleratorTextAtForTesting(int index) const;
std::unique_ptr<ElectronMenuModel> model_;
raw_ptr<Menu> parent_ = nullptr;
// Observable:
void OnMenuWillClose() override;
void OnMenuWillShow() override;
@@ -132,7 +129,8 @@ class Menu : public gin::Wrappable<Menu>,
int GetIndexOfCommandId(int command_id) const;
int GetItemCount() const;
gin_helper::SelfKeepAlive<Menu> keep_alive_{nullptr};
std::unique_ptr<ElectronMenuModel> model_;
cppgc::Member<Menu> parent_;
};
} // namespace electron::api

View File

@@ -9,6 +9,7 @@
#include <map>
#include "gin/weak_cell.h"
#import "shell/browser/ui/cocoa/electron_menu_controller.h"
namespace electron {
@@ -23,6 +24,8 @@ class MenuMac : public Menu {
explicit MenuMac(gin::Arguments* args);
~MenuMac() override;
void Trace(cppgc::Visitor*) const override;
protected:
// Menu
void PopupAt(BaseWindow* window,
@@ -53,7 +56,7 @@ class MenuMac : public Menu {
// window ID -> open context menu
std::map<int32_t, ElectronMenuController*> popup_controllers_;
base::WeakPtrFactory<MenuMac> weak_factory_{this};
gin::WeakCellFactory<MenuMac> weak_cell_factory_{this};
};
} // namespace api

View File

@@ -15,6 +15,7 @@
#include "content/app_shim_remote_cocoa/render_widget_host_view_cocoa.h" // nogncheck
#include "content/browser/renderer_host/render_widget_host_view_mac.h" // nogncheck
#include "content/public/browser/browser_task_traits.h"
#include "gin/persistent.h"
#include "shell/browser/api/electron_api_base_window.h"
#include "shell/browser/api/electron_api_web_frame_main.h"
#include "shell/browser/native_window.h"
@@ -57,6 +58,11 @@ MenuMac::~MenuMac() {
RemoveModelObserver();
}
void MenuMac::Trace(cppgc::Visitor* visitor) const {
Menu::Trace(visitor);
visitor->Trace(weak_cell_factory_);
}
void MenuMac::PopupAt(BaseWindow* window,
std::optional<WebFrameMain*> frame,
int x,
@@ -77,10 +83,13 @@ void MenuMac::PopupAt(BaseWindow* window,
// has run.
base::OnceClosure callback_with_ref = BindSelfToClosure(std::move(callback));
auto popup = base::BindOnce(&MenuMac::PopupOnUI, weak_factory_.GetWeakPtr(),
native_window->GetWeakPtr(), weak_frame,
window->weak_map_id(), x, y, positioning_item,
std::move(callback_with_ref));
v8::Isolate* isolate = v8::Isolate::GetCurrent();
auto popup = base::BindOnce(
&MenuMac::PopupOnUI,
gin::WrapPersistent(weak_cell_factory_.GetWeakCell(
isolate->GetCppHeap()->GetAllocationHandle())),
native_window->GetWeakPtr(), weak_frame, window->weak_map_id(), x, y,
positioning_item, std::move(callback_with_ref));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
std::move(popup));
}
@@ -121,9 +130,12 @@ void MenuMac::PopupOnUI(const base::WeakPtr<NativeWindow>& native_window,
return;
NSWindow* nswindow = native_window->GetNativeWindow().GetNativeNSWindow();
v8::Isolate* isolate = v8::Isolate::GetCurrent();
base::OnceClosure close_callback =
base::BindOnce(&MenuMac::OnClosed, weak_factory_.GetWeakPtr(), window_id,
std::move(callback));
base::BindOnce(&MenuMac::OnClosed,
gin::WrapPersistent(weak_cell_factory_.GetWeakCell(
isolate->GetCppHeap()->GetAllocationHandle())),
window_id, std::move(callback));
popup_controllers_[window_id] =
[[ElectronMenuController alloc] initWithModel:model()
useDefaultAccelerator:NO];
@@ -204,8 +216,12 @@ void MenuMac::PopupOnUI(const base::WeakPtr<NativeWindow>& native_window,
}
void MenuMac::ClosePopupAt(int32_t window_id) {
auto close_popup = base::BindOnce(&MenuMac::ClosePopupOnUI,
weak_factory_.GetWeakPtr(), window_id);
v8::Isolate* isolate = v8::Isolate::GetCurrent();
auto close_popup =
base::BindOnce(&MenuMac::ClosePopupOnUI,
gin::WrapPersistent(weak_cell_factory_.GetWeakCell(
isolate->GetCppHeap()->GetAllocationHandle())),
window_id);
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(close_popup));
}

View File

@@ -7,6 +7,7 @@
#include <memory>
#include <utility>
#include "gin/persistent.h"
#include "shell/browser/api/electron_api_base_window.h"
#include "shell/browser/api/electron_api_web_frame_main.h"
#include "shell/browser/native_window_views.h"
@@ -23,6 +24,11 @@ MenuViews::MenuViews(gin::Arguments* args) : Menu(args) {}
MenuViews::~MenuViews() = default;
void MenuViews::Trace(cppgc::Visitor* visitor) const {
Menu::Trace(visitor);
visitor->Trace(weak_cell_factory_);
}
void MenuViews::PopupAt(BaseWindow* window,
std::optional<WebFrameMain*> frame,
int x,
@@ -55,8 +61,11 @@ void MenuViews::PopupAt(BaseWindow* window,
// callback, it is fine passing OnceCallback to it because we reset the
// menu runner immediately when the menu is closed.
int32_t window_id = window->weak_map_id();
v8::Isolate* isolate = v8::Isolate::GetCurrent();
auto close_callback = electron::AdaptCallbackForRepeating(
base::BindOnce(&MenuViews::OnClosed, weak_factory_.GetWeakPtr(),
base::BindOnce(&MenuViews::OnClosed,
gin::WrapPersistent(weak_cell_factory_.GetWeakCell(
isolate->GetCppHeap()->GetAllocationHandle())),
window_id, std::move(callback_with_ref)));
auto& runner = menu_runners_[window_id] =
std::make_unique<MenuRunner>(model(), flags, std::move(close_callback));

View File

@@ -8,7 +8,7 @@
#include <memory>
#include "base/containers/flat_map.h"
#include "base/memory/weak_ptr.h"
#include "gin/weak_cell.h"
#include "shell/browser/api/electron_api_menu.h"
#include "ui/views/controls/menu/menu_runner.h"
@@ -19,6 +19,8 @@ class MenuViews : public Menu {
explicit MenuViews(gin::Arguments* args);
~MenuViews() override;
void Trace(cppgc::Visitor*) const override;
protected:
// Menu
void PopupAt(BaseWindow* window,
@@ -36,7 +38,7 @@ class MenuViews : public Menu {
// window ID -> open context menu
base::flat_map<int32_t, std::unique_ptr<views::MenuRunner>> menu_runners_;
base::WeakPtrFactory<MenuViews> weak_factory_{this};
gin::WeakCellFactory<MenuViews> weak_cell_factory_{this};
};
} // namespace electron::api

View File

@@ -16,6 +16,7 @@
#include "content/public/browser/storage_partition.h"
#include "electron/electron_version.h"
#include "gin/object_template_builder.h"
#include "gin/persistent.h"
#include "net/log/net_log_capture_mode.h"
#include "shell/browser/electron_browser_context.h"
#include "shell/browser/net/system_network_context_manager.h"
@@ -147,8 +148,9 @@ v8::Local<v8::Promise> NetLog::StartLogging(base::FilePath log_path,
file_task_runner_->PostTaskAndReplyWithResult(
FROM_HERE, base::BindOnce(OpenFileForWriting, log_path),
base::BindOnce(&NetLog::StartNetLogAfterCreateFile,
weak_ptr_factory_.GetWeakPtr(), capture_mode,
max_file_size, std::move(custom_constants)));
gin::WrapPersistent(weak_factory_.GetWeakCell(
args->isolate()->GetCppHeap()->GetAllocationHandle())),
capture_mode, max_file_size, std::move(custom_constants)));
return handle;
}
@@ -236,6 +238,11 @@ const char* NetLog::GetHumanReadableName() const {
return "Electron / NetLog";
}
void NetLog::Trace(cppgc::Visitor* visitor) const {
gin::Wrappable<NetLog>::Trace(visitor);
visitor->Trace(weak_factory_);
}
// static
NetLog* NetLog::Create(v8::Isolate* isolate,
ElectronBrowserContext* browser_context) {

View File

@@ -8,12 +8,13 @@
#include <optional>
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/values.h"
#include "gin/weak_cell.h"
#include "gin/wrappable.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/log/net_log_capture_mode.h"
#include "services/network/public/mojom/net_log.mojom.h"
#include "shell/common/gc_plugin.h"
#include "shell/common/gin_helper/promise.h"
namespace base {
@@ -56,6 +57,7 @@ class NetLog final : public gin::Wrappable<NetLog> {
v8::Isolate* isolate) override;
const gin::WrapperInfo* wrapper_info() const override;
const char* GetHumanReadableName() const override;
void Trace(cppgc::Visitor*) const override;
v8::Local<v8::Promise> StartLogging(base::FilePath log_path,
gin::Arguments* args);
@@ -74,13 +76,15 @@ class NetLog final : public gin::Wrappable<NetLog> {
private:
raw_ptr<ElectronBrowserContext> browser_context_;
GC_PLUGIN_IGNORE(
"Context tracking of remote is not needed in the browser process.")
mojo::Remote<network::mojom::NetLogExporter> net_log_exporter_;
std::optional<gin_helper::Promise<void>> pending_start_promise_;
scoped_refptr<base::TaskRunner> file_task_runner_;
base::WeakPtrFactory<NetLog> weak_ptr_factory_{this};
gin::WeakCellFactory<NetLog> weak_factory_{this};
};
} // namespace api

View File

@@ -8,7 +8,6 @@
#include "base/power_monitor/power_observer.h"
#include "gin/wrappable.h"
#include "shell/browser/event_emitter_mixin.h"
#include "shell/common/gin_helper/self_keep_alive.h"
#if BUILDFLAG(IS_LINUX)
#include "shell/browser/lib/power_observer_linux.h"
@@ -89,13 +88,14 @@ class PowerMonitor final : public gin::Wrappable<PowerMonitor>,
// The window used for processing events.
HWND window_;
// Handle returned by RegisterSuspendResumeNotification.
HPOWERNOTIFY power_notify_handle_ = nullptr;
#endif
#if BUILDFLAG(IS_LINUX)
PowerObserverLinux power_observer_linux_{this};
#endif
gin_helper::SelfKeepAlive<PowerMonitor> keep_alive_{this};
};
} // namespace electron::api

View File

@@ -6,11 +6,14 @@
#include <vector>
#include "shell/common/gc_plugin.h"
#import <ApplicationServices/ApplicationServices.h>
#import <Cocoa/Cocoa.h>
@interface MacLockMonitor : NSObject {
@private
GC_PLUGIN_IGNORE("ObjC class cannot participate in cppgc tracing")
std::vector<electron::api::PowerMonitor*> emitters;
}

View File

@@ -45,14 +45,16 @@ void PowerMonitor::InitPlatformSpecificMonitors() {
// For Windows 8 and later, a new "connected standby" mode has been added and
// we must explicitly register for its notifications.
RegisterSuspendResumeNotification(static_cast<HANDLE>(window_),
DEVICE_NOTIFY_WINDOW_HANDLE);
power_notify_handle_ = RegisterSuspendResumeNotification(
static_cast<HANDLE>(window_), DEVICE_NOTIFY_WINDOW_HANDLE);
}
void PowerMonitor::DestroyPlatformSpecificMonitors() {
if (window_) {
WTSUnRegisterSessionNotification(window_);
UnregisterSuspendResumeNotification(static_cast<HANDLE>(window_));
if (power_notify_handle_) {
UnregisterSuspendResumeNotification(power_notify_handle_);
}
gfx::SetWindowUserData(window_, nullptr);
DestroyWindow(window_);
window_ = nullptr;

View File

@@ -14,7 +14,6 @@
#include "services/device/public/mojom/wake_lock_provider.mojom.h"
#include "services/service_manager/public/cpp/connector.h"
#include "shell/browser/javascript_environment.h"
#include "shell/common/gin_helper/handle.h"
#include "shell/common/node_includes.h"
#include "v8/include/cppgc/allocation.h"
#include "v8/include/v8-cppgc.h"
@@ -53,11 +52,9 @@ PowerSaveBlocker::PowerSaveBlocker(v8::Isolate* isolate)
PowerSaveBlocker::~PowerSaveBlocker() = default;
// static
gin_helper::Handle<PowerSaveBlocker> PowerSaveBlocker::Create(
v8::Isolate* isolate) {
return gin_helper::CreateHandle(
isolate, cppgc::MakeGarbageCollected<PowerSaveBlocker>(
isolate->GetCppHeap()->GetAllocationHandle(), isolate));
PowerSaveBlocker* PowerSaveBlocker::Create(v8::Isolate* isolate) {
return cppgc::MakeGarbageCollected<PowerSaveBlocker>(
isolate->GetCppHeap()->GetAllocationHandle(), isolate);
}
const gin::WrapperInfo* PowerSaveBlocker::wrapper_info() const {

View File

@@ -9,21 +9,17 @@
#include "gin/wrappable.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/device/public/mojom/wake_lock.mojom.h"
#include "shell/common/gc_plugin.h"
namespace gin {
class ObjectTemplateBuilder;
} // namespace gin
namespace gin_helper {
template <typename T>
class Handle;
} // namespace gin_helper
namespace electron::api {
class PowerSaveBlocker final : public gin::Wrappable<PowerSaveBlocker> {
public:
static gin_helper::Handle<PowerSaveBlocker> Create(v8::Isolate* isolate);
static PowerSaveBlocker* Create(v8::Isolate* isolate);
// gin::Wrappable
static const gin::WrapperInfo kWrapperInfo;
@@ -57,6 +53,8 @@ class PowerSaveBlocker final : public gin::Wrappable<PowerSaveBlocker> {
// Map from id to the corresponding blocker type for each request.
base::flat_map<int, device::mojom::WakeLockType> wake_lock_types_;
GC_PLUGIN_IGNORE(
"Context tracking of remote is not needed in the browser process.")
mojo::Remote<device::mojom::WakeLock> wake_lock_;
};

View File

@@ -12,7 +12,6 @@
#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"
@@ -20,13 +19,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 {
@@ -82,8 +81,7 @@ struct Converter<CustomScheme> {
namespace electron::api {
const gin::WrapperInfo Protocol::kWrapperInfo = {{gin::kEmbedderNativeGin},
gin::kElectronProtocol};
gin::DeprecatedWrapperInfo Protocol::kWrapperInfo = {gin::kEmbedderNativeGin};
std::vector<std::string>& GetStandardSchemes() {
static base::NoDestructor<std::vector<std::string>> g_standard_schemes;
@@ -298,22 +296,23 @@ void Protocol::HandleOptionalCallback(gin::Arguments* args, Error error) {
}
// static
Protocol* Protocol::Create(v8::Isolate* isolate,
ProtocolRegistry* protocol_registry) {
return cppgc::MakeGarbageCollected<Protocol>(
isolate->GetCppHeap()->GetAllocationHandle(), protocol_registry);
gin_helper::Handle<Protocol> Protocol::Create(
v8::Isolate* isolate,
ProtocolRegistry* protocol_registry) {
return gin_helper::CreateHandle(isolate, new Protocol{protocol_registry});
}
// static
Protocol* Protocol::New(gin_helper::ErrorThrower thrower) {
gin_helper::Handle<Protocol> Protocol::New(gin_helper::ErrorThrower thrower) {
thrower.ThrowError("Protocol cannot be created from JS");
return {};
}
// static
void Protocol::FillObjectTemplate(v8::Isolate* isolate,
v8::Local<v8::ObjectTemplate> tmpl) {
gin::ObjectTemplateBuilder(isolate, GetClassName(), tmpl)
v8::Local<v8::ObjectTemplate> Protocol::FillObjectTemplate(
v8::Isolate* isolate,
v8::Local<v8::ObjectTemplate> tmpl) {
return gin::ObjectTemplateBuilder(isolate, GetClassName(), tmpl)
.SetMethod("registerStringProtocol",
&Protocol::RegisterProtocolFor<ProtocolType::kString>)
.SetMethod("registerBufferProtocol",
@@ -346,12 +345,8 @@ void Protocol::FillObjectTemplate(v8::Isolate* isolate,
.Build();
}
const gin::WrapperInfo* Protocol::wrapper_info() const {
return &kWrapperInfo;
}
const char* Protocol::GetHumanReadableName() const {
return "Electron / Protocol";
const char* Protocol::GetTypeName() {
return GetClassName();
}
} // namespace electron::api
@@ -377,8 +372,7 @@ 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::kWrapperInfo));
electron::api::Protocol::GetConstructor(isolate, context));
dict.SetMethod("registerSchemesAsPrivileged", &RegisterSchemesAsPrivileged);
dict.SetMethod("getStandardSchemes", &electron::api::GetStandardSchemes);
}

View File

@@ -9,14 +9,20 @@
#include <vector>
#include "base/memory/raw_ptr.h"
#include "gin/wrappable.h"
#include "content/public/browser/content_browser_client.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;
@@ -32,25 +38,23 @@ void RegisterSchemesAsPrivileged(gin_helper::ErrorThrower thrower,
v8::Local<v8::Value> val);
// Protocol implementation based on network services.
class Protocol final : public gin::Wrappable<Protocol>,
class Protocol final : public gin_helper::DeprecatedWrappable<Protocol>,
public gin_helper::Constructible<Protocol> {
public:
static Protocol* Create(v8::Isolate* isolate,
ProtocolRegistry* protocol_registry);
static gin_helper::Handle<Protocol> Create(
v8::Isolate* isolate,
ProtocolRegistry* protocol_registry);
// gin_helper::Constructible
static Protocol* New(gin_helper::ErrorThrower thrower);
static void FillObjectTemplate(v8::Isolate* isolate,
v8::Local<v8::ObjectTemplate> tmpl);
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 const char* GetClassName() { return "Protocol"; }
// 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;
// gin_helper::Wrappable
static gin::DeprecatedWrapperInfo kWrapperInfo;
const char* GetTypeName() override;
private:
// Possible errors.
@@ -66,6 +70,9 @@ class Protocol final : public gin::Wrappable<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,7 +17,6 @@
#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"
@@ -77,7 +76,6 @@
#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"
@@ -560,7 +558,9 @@ Session::Session(v8::Isolate* isolate, ElectronBrowserContext* browser_context)
SessionPreferences::CreateForBrowserContext(browser_context);
protocol_ = Protocol::Create(isolate, browser_context->protocol_registry());
protocol_.Reset(
isolate,
Protocol::Create(isolate, browser_context->protocol_registry()).ToV8());
browser_context->SetUserData(
kElectronApiSessionKey,
@@ -1354,8 +1354,8 @@ v8::Local<v8::Value> Session::Extensions(v8::Isolate* isolate) {
return extensions_.Get(isolate);
}
api::Protocol* Session::Protocol() {
return protocol_.Get();
v8::Local<v8::Value> Session::Protocol(v8::Isolate* isolate) {
return protocol_.Get(isolate);
}
v8::Local<v8::Value> Session::ServiceWorkerContext(v8::Isolate* isolate) {

View File

@@ -10,6 +10,7 @@
#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"
@@ -19,6 +20,7 @@
#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"
@@ -58,7 +60,6 @@ struct PreloadScript;
namespace api {
class NetLog;
class Protocol;
class WebRequest;
class Session final : public gin::Wrappable<Session>,
@@ -166,7 +167,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);
api::Protocol* Protocol();
v8::Local<v8::Value> Protocol(v8::Isolate* isolate);
v8::Local<v8::Value> ServiceWorkerContext(v8::Isolate* isolate);
WebRequest* WebRequest(v8::Isolate* isolate);
api::NetLog* NetLog(v8::Isolate* isolate);
@@ -213,7 +214,7 @@ class Session final : public gin::Wrappable<Session>,
// Cached gin_helper::Wrappable objects.
v8::TracedReference<v8::Value> cookies_;
v8::TracedReference<v8::Value> extensions_;
cppgc::Member<api::Protocol> protocol_;
v8::TracedReference<v8::Value> protocol_;
cppgc::Member<api::NetLog> net_log_;
v8::TracedReference<v8::Value> service_worker_context_;
cppgc::Member<api::WebRequest> web_request_;

View File

@@ -21,7 +21,6 @@
#include "shell/common/gin_converters/image_converter.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/error_thrower.h"
#include "shell/common/gin_helper/handle.h"
#include "shell/common/node_includes.h"
#include "v8/include/cppgc/allocation.h"
#include "v8/include/v8-cppgc.h"
@@ -345,7 +344,7 @@ void Tray::Focus() {
void Tray::PopUpContextMenu(gin::Arguments* args) {
if (!CheckAlive())
return;
gin_helper::Handle<Menu> menu;
Menu* menu = nullptr;
gfx::Point pos;
v8::Local<v8::Value> first_arg;
@@ -363,8 +362,8 @@ void Tray::PopUpContextMenu(gin::Arguments* args) {
}
}
tray_icon_->PopUpContextMenu(
pos, menu.IsEmpty() ? nullptr : menu->model()->GetWeakPtr());
tray_icon_->PopUpContextMenu(pos,
menu ? menu->model()->GetWeakPtr() : nullptr);
}
void Tray::CloseContextMenu() {

View File

@@ -972,12 +972,11 @@ WebContents::WebContents(v8::Isolate* isolate,
void WebContents::InitZoomController(content::WebContents* web_contents,
const gin_helper::Dictionary& options) {
WebContentsZoomController* const zoom_controller =
WebContentsZoomController::GetOrCreateForWebContents(web_contents);
WebContentsZoomController::CreateForWebContents(web_contents);
zoom_controller_ = WebContentsZoomController::FromWebContents(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!
@@ -2890,8 +2889,8 @@ v8::Local<v8::Promise> WebContents::SavePage(
return handle;
}
auto* handler = new SavePageHandler{std::move(promise)};
handler->Handle(full_file_path, save_type, web_contents());
auto* handler = new SavePageHandler(web_contents(), std::move(promise));
handler->Handle(full_file_path, save_type);
return handle;
}
@@ -3153,18 +3152,16 @@ void OnGetDeviceNameToUse(base::WeakPtr<content::WebContents> web_contents,
.Set(printing::kSettingMediaSizeIsDefault, true);
};
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));
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)
@@ -3870,16 +3867,12 @@ gfx::Size WebContents::GetSizeForNewRenderView(content::WebContents* wc) {
return {};
}
WebContentsZoomController* WebContents::GetZoomController() const {
return WebContentsZoomController::FromWebContents(web_contents());
}
void WebContents::SetZoomLevel(double level) {
GetZoomController()->SetZoomLevel(level);
zoom_controller_->SetZoomLevel(level);
}
double WebContents::GetZoomLevel() const {
return GetZoomController()->GetZoomLevel();
return zoom_controller_->GetZoomLevel();
}
void WebContents::SetZoomFactor(gin_helper::ErrorThrower thrower,
@@ -3899,7 +3892,7 @@ double WebContents::GetZoomFactor() const {
}
void WebContents::SetTemporaryZoomLevel(double level) {
GetZoomController()->SetTemporaryZoomLevel(level);
zoom_controller_->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();
[[nodiscard]] WebContentsZoomController* GetZoomController() const;
WebContentsZoomController* GetZoomController() { return zoom_controller_; }
void AddObserver(ExtendedWebContentsObserver* obs) {
observers_.AddObserver(obs);
@@ -858,6 +858,11 @@ 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

@@ -12,8 +12,9 @@
namespace electron::api {
SavePageHandler::SavePageHandler(gin_helper::Promise<void> promise)
: promise_{std::move(promise)} {}
SavePageHandler::SavePageHandler(content::WebContents* web_contents,
gin_helper::Promise<void> promise)
: web_contents_(web_contents), promise_(std::move(promise)) {}
SavePageHandler::~SavePageHandler() = default;
@@ -25,10 +26,9 @@ void SavePageHandler::OnDownloadCreated(content::DownloadManager* manager,
}
bool SavePageHandler::Handle(const base::FilePath& full_path,
const content::SavePageType& save_type,
content::WebContents* web_contents) {
const content::SavePageType& save_type) {
auto* download_manager =
web_contents->GetBrowserContext()->GetDownloadManager();
web_contents_->GetBrowserContext()->GetDownloadManager();
download_manager->AddObserver(this);
// Chromium will create a 'foo_files' directory under the directory of saving
// page 'foo.html' for holding other resource files of 'foo.html'.
@@ -36,7 +36,7 @@ bool SavePageHandler::Handle(const base::FilePath& full_path,
full_path.RemoveExtension().BaseName().value() +
FILE_PATH_LITERAL("_files"));
bool result =
web_contents->SavePage(full_path, saved_main_directory_path, save_type);
web_contents_->SavePage(full_path, saved_main_directory_path, save_type);
download_manager->RemoveObserver(this);
// If initialization fails which means fail to create |DownloadItem|, we need
// to delete the |SavePageHandler| instance to avoid memory-leak.

View File

@@ -5,6 +5,7 @@
#ifndef ELECTRON_SHELL_BROWSER_API_SAVE_PAGE_HANDLER_H_
#define ELECTRON_SHELL_BROWSER_API_SAVE_PAGE_HANDLER_H_
#include "base/memory/raw_ptr.h"
#include "components/download/public/common/download_item.h"
#include "content/public/browser/download_manager.h"
#include "content/public/browser/save_page_type.h"
@@ -25,12 +26,12 @@ namespace electron::api {
class SavePageHandler : private content::DownloadManager::Observer,
private download::DownloadItem::Observer {
public:
explicit SavePageHandler(gin_helper::Promise<void> promise);
SavePageHandler(content::WebContents* web_contents,
gin_helper::Promise<void> promise);
~SavePageHandler() override;
bool Handle(const base::FilePath& full_path,
const content::SavePageType& save_type,
content::WebContents* web_contents);
const content::SavePageType& save_type);
private:
void Destroy(download::DownloadItem* item);
@@ -42,6 +43,7 @@ class SavePageHandler : private content::DownloadManager::Observer,
// download::DownloadItem::Observer:
void OnDownloadUpdated(download::DownloadItem* item) override;
raw_ptr<content::WebContents> web_contents_; // weak
gin_helper::Promise<void> promise_;
};

View File

@@ -63,7 +63,6 @@
#include "mojo/public/cpp/bindings/self_owned_associated_receiver.h"
#include "net/ssl/ssl_cert_request_info.h"
#include "net/ssl/ssl_private_key.h"
#include "pdf/pdf_features.h"
#include "printing/buildflags/buildflags.h"
#include "services/device/public/cpp/geolocation/geolocation_system_permission_manager.h"
#include "services/device/public/cpp/geolocation/location_provider.h"
@@ -1592,46 +1591,6 @@ bool ElectronBrowserClient::ShouldEnableStrictSiteIsolation() {
return true;
}
bool ElectronBrowserClient::ShouldEnableSubframeZoom() {
#if BUILDFLAG(ENABLE_PDF_VIEWER)
return chrome_pdf::features::IsOopifPdfEnabled();
#else
return false;
#endif
}
#if BUILDFLAG(ENABLE_PDF_VIEWER)
std::optional<network::CrossOriginEmbedderPolicy>
ElectronBrowserClient::MaybeOverrideLocalURLCrossOriginEmbedderPolicy(
content::NavigationHandle* navigation_handle) {
if (!chrome_pdf::features::IsOopifPdfEnabled() ||
!navigation_handle->IsPdf()) {
return std::nullopt;
}
content::RenderFrameHost* pdf_extension = navigation_handle->GetParentFrame();
if (!pdf_extension) {
return std::nullopt;
}
content::RenderFrameHost* pdf_embedder = pdf_extension->GetParent();
CHECK(pdf_embedder);
return pdf_embedder->GetCrossOriginEmbedderPolicy();
}
#endif // BUILDFLAG(ENABLE_PDF_VIEWER)
bool ElectronBrowserClient::DoesSiteRequireDedicatedProcess(
content::BrowserContext* browser_context,
const GURL& effective_site_url) {
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
return GetEnabledExtensionFromEffectiveURL(browser_context,
effective_site_url) != nullptr;
#else
return content::ContentBrowserClient::DoesSiteRequireDedicatedProcess(
browser_context, effective_site_url);
#endif
}
void ElectronBrowserClient::BindHostReceiverForRenderer(
content::RenderProcessHost* render_process_host,
mojo::GenericPendingReceiver receiver) {

View File

@@ -30,7 +30,6 @@ class FilePath;
namespace content {
class ClientCertificateDelegate;
class NavigationHandle;
class PlatformNotificationService;
class NavigationThrottleRegistry;
class QuotaPermissionContext;
@@ -83,14 +82,6 @@ class ElectronBrowserClient : public content::ContentBrowserClient,
// content::ContentBrowserClient:
std::string GetApplicationLocale() override;
bool ShouldEnableStrictSiteIsolation() override;
bool ShouldEnableSubframeZoom() override;
#if BUILDFLAG(ENABLE_PDF_VIEWER)
std::optional<network::CrossOriginEmbedderPolicy>
MaybeOverrideLocalURLCrossOriginEmbedderPolicy(
content::NavigationHandle* navigation_handle) override;
#endif // BUILDFLAG(ENABLE_PDF_VIEWER)
bool DoesSiteRequireDedicatedProcess(content::BrowserContext* browser_context,
const GURL& effective_site_url) override;
void BindHostReceiverForRenderer(
content::RenderProcessHost* render_process_host,
mojo::GenericPendingReceiver receiver) override;

View File

@@ -12,8 +12,7 @@
#include "gin/object_template_builder.h"
#include "shell/browser/javascript_environment.h"
#include "shell/common/gin_helper/event.h"
#include "shell/common/gin_helper/event_emitter.h"
#include "shell/common/gin_helper/handle.h"
#include "shell/common/gin_helper/event_emitter_caller.h"
namespace gin_helper {

View File

@@ -71,7 +71,7 @@ void StreamsPrivateAPI::SendExecuteMimeTypeHandlerEvent(
std::move(transferrable_loader), original_url);
#if BUILDFLAG(ENABLE_PDF_VIEWER)
if (chrome_pdf::features::IsOopifPdfEnabled() &&
if (base::FeatureList::IsEnabled(chrome_pdf::features::kPdfOopif) &&
extension_id == extension_misc::kPdfExtensionId) {
pdf::PdfViewerStreamManager::Create(web_contents);
pdf::PdfViewerStreamManager::FromWebContents(web_contents)

View File

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

View File

@@ -11,6 +11,7 @@
#include "extensions/browser/media_capture_util.h"
#include "shell/browser/api/electron_api_web_contents.h"
#include "shell/browser/extensions/electron_extension_web_contents_observer.h"
#include "shell/common/gin_helper/handle.h"
#include "v8/include/v8.h"
namespace extensions {

View File

@@ -15,6 +15,7 @@
#include "shell/browser/extensions/api/management/electron_management_api_delegate.h"
#include "shell/browser/extensions/electron_extension_web_contents_observer.h"
#include "shell/browser/extensions/electron_messaging_delegate.h"
#include "shell/common/gin_helper/handle.h"
#include "v8/include/v8.h"
#if BUILDFLAG(ENABLE_PRINTING)

View File

@@ -34,6 +34,10 @@
#include "printing/printing_features.h"
#endif
#if BUILDFLAG(IS_WIN)
#include "ui/views/views_features.h"
#endif
namespace electron {
void InitializeFeatureList() {
@@ -67,6 +71,13 @@ void InitializeFeatureList() {
blink::features::kDropInputEventsWhilePaintHolding.name;
#if BUILDFLAG(IS_WIN)
// Refs https://issues.chromium.org/issues/401996981
// TODO(deepak1556): Remove this once test added in
// https://github.com/electron/electron/pull/12904
// can work without this feature.
enable_features += std::string(",") +
views::features::kEnableTransparentHwndEnlargement.name;
// See https://chromium-review.googlesource.com/c/chromium/src/+/7204292
// This feature causes the following sandbox failure on Windows:
// sandbox\policy\win\sandbox_win.cc:777 Sandbox cannot access executable

View File

@@ -31,6 +31,11 @@
#include "shell/browser/ui/views/frameless_view.h"
#endif
#if BUILDFLAG(IS_WIN)
#include "ui/display/win/screen_win.h"
#include "ui/views/views_features.h"
#endif
#if defined(USE_OZONE)
#include "ui/base/ui_base_features.h"
#include "ui/ozone/public/ozone_platform.h"
@@ -66,6 +71,31 @@ struct Converter<electron::NativeWindow::TitleBarStyle> {
namespace electron {
namespace {
#if BUILDFLAG(IS_WIN)
gfx::Size GetExpandedWindowSize(const NativeWindow* window,
bool transparent,
gfx::Size size) {
if (!base::FeatureList::IsEnabled(
views::features::kEnableTransparentHwndEnlargement) ||
!transparent) {
return size;
}
gfx::Size min_size = display::win::GetScreenWin()->ScreenToDIPSize(
window->GetAcceleratedWidget(), gfx::Size{64, 64});
// Some AMD drivers can't display windows that are less than 64x64 pixels,
// so expand them to be at least that size. http://crbug.com/286609
gfx::Size expanded(std::max(size.width(), min_size.width()),
std::max(size.height(), min_size.height()));
return expanded;
}
#endif
} // namespace
NativeWindow::NativeWindow(const int32_t base_window_id,
const gin_helper::Dictionary& options,
NativeWindow* parent)
@@ -368,7 +398,15 @@ gfx::Size NativeWindow::GetContentMinimumSize() const {
}
gfx::Size NativeWindow::GetContentMaximumSize() const {
return GetContentSizeConstraints().GetMaximumSize();
const auto size_constraints = GetContentSizeConstraints();
gfx::Size maximum_size = size_constraints.GetMaximumSize();
#if BUILDFLAG(IS_WIN)
if (size_constraints.HasMaximumSize())
maximum_size = GetExpandedWindowSize(this, transparent(), maximum_size);
#endif
return maximum_size;
}
void NativeWindow::SetSheetOffset(const double offsetX, const double offsetY) {

View File

@@ -440,11 +440,13 @@ NativeWindowViews::NativeWindowViews(const int32_t base_window_id,
if (window)
window->AddPreTargetHandler(this);
#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);
#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);
#endif
}
@@ -904,9 +906,7 @@ gfx::Rect NativeWindowViews::GetNormalBounds() const {
if (IsMaximized() && transparent())
return restore_bounds_;
#endif
gfx::Rect bounds = widget()->GetRestoredBounds();
bounds.Inset(GetRestoredFrameBorderInsets());
return bounds;
return WidgetToLogicalBounds(widget()->GetRestoredBounds());
}
void NativeWindowViews::SetContentSizeConstraints(
@@ -1676,24 +1676,17 @@ 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,7 +194,6 @@ 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

@@ -675,9 +675,9 @@ void ElectronURLLoaderFactory::StartLoadingHttp(
request->method != net::HttpRequestHeaders::kHeadMethod)
dict.Get("uploadData", &upload_data);
gin_helper::Handle<api::Session> session;
api::Session* session = nullptr;
auto* browser_context =
dict.Get("session", &session) && !session.IsEmpty()
dict.Get("session", &session) && session
? session->browser_context()
: ElectronBrowserContext::GetDefaultBrowserContext();

View File

@@ -158,43 +158,45 @@ using TitleBarStyle = electron::NativeWindowMac::TitleBarStyle;
windowSize.width() - contentSize.width() + extraSize.width();
double extraHeightPlusFrame = titleBarHeight + extraSize.height();
auto widthForHeight = [&](double h) {
return (h - extraHeightPlusFrame) * aspectRatio + extraWidthPlusFrame;
};
auto heightForWidth = [&](double w) {
return (w - extraWidthPlusFrame) / aspectRatio + extraHeightPlusFrame;
};
newSize.width = roundf(widthForHeight(frameSize.height));
newSize.height = roundf(heightForWidth(newSize.width));
newSize.width =
roundf((frameSize.height - extraHeightPlusFrame) * aspectRatio +
extraWidthPlusFrame);
newSize.height =
roundf((newSize.width - extraWidthPlusFrame) / aspectRatio +
extraHeightPlusFrame);
// Clamp to minimum width/height while ensuring aspect ratio remains.
NSSize minSize = [window minSize];
NSSize zeroSize =
shell_->has_frame() ? NSMakeSize(0, titleBarHeight) : NSZeroSize;
if (!NSEqualSizes(minSize, zeroSize)) {
double minWidthForAspectRatio =
(minSize.height - titleBarHeight) * aspectRatio;
bool atMinHeight =
minSize.height > zeroSize.height && newSize.height <= minSize.height;
newSize.width = atMinHeight ? widthForHeight(minSize.height)
newSize.width = atMinHeight ? minWidthForAspectRatio
: std::max(newSize.width, minSize.width);
double minHeightForAspectRatio = minSize.width / aspectRatio;
bool atMinWidth =
minSize.width > zeroSize.width && newSize.width <= minSize.width;
newSize.height = atMinWidth ? heightForWidth(minSize.width)
newSize.height = atMinWidth ? minHeightForAspectRatio
: std::max(newSize.height, minSize.height);
}
// Clamp to maximum width/height while ensuring aspect ratio remains.
NSSize maxSize = [window maxSize];
if (!NSEqualSizes(maxSize, NSMakeSize(FLT_MAX, FLT_MAX))) {
double maxWidthForAspectRatio = maxSize.height * aspectRatio;
bool atMaxHeight =
maxSize.height < FLT_MAX && newSize.height >= maxSize.height;
newSize.width = atMaxHeight ? widthForHeight(maxSize.height)
newSize.width = atMaxHeight ? maxWidthForAspectRatio
: std::min(newSize.width, maxSize.width);
double maxHeightForAspectRatio = maxSize.width / aspectRatio;
bool atMaxWidth =
maxSize.width < FLT_MAX && newSize.width >= maxSize.width;
newSize.height = atMaxWidth ? heightForWidth(maxSize.width)
newSize.height = atMaxWidth ? maxHeightForAspectRatio
: std::min(newSize.height, maxSize.height);
}
}

View File

@@ -53,6 +53,7 @@
#include "shell/browser/ui/inspectable_web_contents_view.h"
#include "shell/browser/ui/inspectable_web_contents_view_delegate.h"
#include "shell/common/application_info.h"
#include "shell/common/gin_helper/handle.h"
#include "shell/common/platform_util.h"
#include "third_party/abseil-cpp/absl/strings/str_format.h"
#include "third_party/blink/public/common/logging/logging_utils.h"

View File

@@ -106,19 +106,29 @@ int WinFrameView::NonClientHitTest(const gfx::Point& point) {
if (SUCCEEDED(DwmGetWindowAttribute(
views::HWNDForWidget(frame()), DWMWA_CAPTION_BUTTON_BOUNDS,
&button_bounds, sizeof(button_bounds)))) {
gfx::Rect button_bounds_px(button_bounds);
// There is a small one-pixel strip right above the caption buttons in
// which the resize border "peeks" through. Inset in physical pixels
// before converting to DIPs so the resize strip remains exposed at
// fractional scale factors.
button_bounds_px.Inset(gfx::Insets::TLBR(1, 0, 0, 0));
const gfx::RectF button_bounds_in_dips =
gfx::ConvertRectToDips(button_bounds_px, display::win::GetDPIScale());
// GetMirroredRect() requires an integer rect. Use ToEnclosedRect() so
// the top inset is preserved (rounded up) at fractional scale factors.
gfx::RectF button_bounds_in_dips = gfx::ConvertRectToDips(
gfx::Rect(button_bounds), display::win::GetDPIScale());
// TODO(crbug.com/1131681): GetMirroredRect() requires an integer rect,
// but the size in DIPs may not be an integer with a fractional device
// scale factor. If we want to keep using integers, the choice to use
// ToFlooredRectDeprecated() seems to be doing the wrong thing given the
// comment below about insetting 1 DIP instead of 1 physical pixel. We
// should probably use ToEnclosedRect() and then we could have inset 1
// physical pixel here.
gfx::Rect buttons =
GetMirroredRect(gfx::ToEnclosedRect(button_bounds_in_dips));
GetMirroredRect(gfx::ToFlooredRectDeprecated(button_bounds_in_dips));
// There is a small one-pixel strip right above the caption buttons in
// which the resize border "peeks" through.
constexpr int kCaptionButtonTopInset = 1;
// The sizing region at the window edge above the caption buttons is
// 1 px regardless of scale factor. If we inset by 1 before converting
// to DIPs, the precision loss might eliminate this region entirely. The
// best we can do is to inset after conversion. This guarantees we'll
// show the resize cursor when resizing is possible. The cost of which
// is also maybe showing it over the portion of the DIP that isn't the
// outermost pixel.
buttons.Inset(gfx::Insets::TLBR(0, kCaptionButtonTopInset, 0, 0));
if (buttons.Contains(point))
return HTNOWHERE;
}
@@ -228,15 +238,14 @@ void WinFrameView::LayoutCaptionButtons() {
int custom_height = window()->titlebar_overlay_height();
int height = TitlebarHeight(custom_height);
// 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);
// 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;
caption_button_container_->SetBounds(width() - preferred_size.width(),
WindowTopY(), variable_width, height);
@@ -268,33 +277,22 @@ bool WinFrameView::GetShouldPaintAsActive() {
gfx::Size WinFrameView::GetMinimumSize() const {
if (!window_)
return gfx::Size();
// Chromium expects minimum size to be in content dimensions on Windows.
// If WidgetSizeIsClientSize() is true, it will account for frame borders and
// insets automatically.
// Chromium expects minimum size to be in content dimensions on Windows
// because it adds the frame border automatically in OnGetMinMaxInfo.
return window_->GetContentMinimumSize();
}
gfx::Size WinFrameView::GetMaximumSize() const {
if (!window_)
return gfx::Size();
// See comment in GetMinimumSize().
// Chromium expects minimum size to be in content dimensions on Windows
// because it adds the frame border automatically in OnGetMinMaxInfo.
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,9 +36,6 @@ 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,45 +89,24 @@ 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 {
if (!native_window_view_->has_frame()) {
// 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.
const int thickness = ::GetSystemMetrics(SM_CXSIZEFRAME) +
::GetSystemMetrics(SM_CXPADDEDBORDER);
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;
}
*insets = gfx::Insets::TLBR(thickness, thickness, thickness, thickness);
return true;
}
return false;
}

View File

@@ -40,7 +40,6 @@ 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

@@ -5,11 +5,11 @@
#include "shell/common/crash_keys.h"
#include <cstdint>
#include <deque>
#include <map>
#include <string>
#include "base/command_line.h"
#include "base/containers/circular_deque.h"
#include "base/environment.h"
#include "base/no_destructor.h"
#include "base/strings/strcat.h"
@@ -28,22 +28,17 @@ namespace electron::crash_keys {
namespace {
// Do NOT replace with base::circular_deque. CrashKeyString wraps a
// crashpad::Annotation that holds self-referential pointers and registers
// in a process-global linked list; relocating elements (as circular_deque
// does on growth) corrupts that list and hangs the crashpad handler.
// std::deque never relocates existing elements. See #50795.
auto& GetExtraCrashKeys() {
constexpr size_t kMaxCrashKeyValueSize = 20320;
static_assert(kMaxCrashKeyValueSize < crashpad::Annotation::kValueMaxSize,
"max crash key value length above what crashpad supports");
using CrashKeyString = crash_reporter::CrashKeyString<kMaxCrashKeyValueSize>;
static base::NoDestructor<std::deque<CrashKeyString>> extra_keys;
static base::NoDestructor<base::circular_deque<CrashKeyString>> extra_keys;
return *extra_keys;
}
auto& GetExtraCrashKeyNames() {
static base::NoDestructor<std::deque<std::string>> crash_key_names;
static base::NoDestructor<base::circular_deque<std::string>> crash_key_names;
return *crash_key_names;
}

19
shell/common/gc_plugin.h Normal file
View File

@@ -0,0 +1,19 @@
// Copyright (c) 2025 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_COMMON_GC_PLUGIN_H_
#define ELECTRON_SHELL_COMMON_GC_PLUGIN_H_
// GC_PLUGIN_IGNORE is used to make the Blink GC plugin ignore a particular
// class or field when checking for proper usage. When using GC_PLUGIN_IGNORE
// a reason must be provided as an argument.
// Based on third_party/blink/renderer/platform/wtf/gc_plugin.h
#if defined(__clang__)
#define GC_PLUGIN_IGNORE(reason) \
__attribute__((annotate("blink_gc_plugin_ignore")))
#else
#define GC_PLUGIN_IGNORE(reason)
#endif
#endif // ELECTRON_SHELL_COMMON_GC_PLUGIN_H_

View File

@@ -20,6 +20,7 @@
#include "shell/common/gin_converters/gfx_converter.h"
#include "shell/common/gin_converters/gurl_converter.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/handle.h"
#include "third_party/blink/public/common/context_menu_data/untrustworthy_context_menu_params.h"
#include "third_party/blink/public/mojom/permissions/permission_status.mojom.h"
#include "ui/base/mojom/menu_source_type.mojom.h"

View File

@@ -8,6 +8,7 @@
#include "content/public/browser/render_process_host.h"
#include "shell/browser/api/electron_api_web_frame_main.h"
#include "shell/common/gin_helper/accessor.h"
#include "shell/common/gin_helper/handle.h"
#include "shell/common/node_util.h"
namespace gin {

View File

@@ -16,6 +16,7 @@
#include "gin/public/gin_embedders.h"
#include "shell/common/gin_helper/destroyable.h"
#include "shell/common/gin_helper/error_thrower.h"
#include "v8/include/cppgc/macros.h"
#include "v8/include/v8-external.h"
#include "v8/include/v8-microtask-queue.h"
#include "v8/include/v8-template.h"
@@ -189,6 +190,9 @@ void ThrowConversionError(gin::Arguments* args,
// at position |index|.
template <size_t index, typename ArgType, typename = void>
struct ArgumentHolder {
CPPGC_STACK_ALLOCATED();
public:
using ArgLocalType = typename CallbackParamTraits<ArgType>::LocalType;
ArgLocalType value;
@@ -222,6 +226,9 @@ struct ArgumentHolder<
std::is_constructible_v<
typename CallbackParamTraits<ArgType>::LocalType,
v8::Isolate*>>> {
CPPGC_STACK_ALLOCATED();
public:
using ArgLocalType = typename CallbackParamTraits<ArgType>::LocalType;
ArgLocalType value;

View File

@@ -7,6 +7,7 @@
#include "base/memory/raw_ptr.h"
#include "gin/converter.h"
#include "v8/include/cppgc/type-traits.h"
namespace gin_helper {
@@ -16,8 +17,15 @@ namespace gin_helper {
// C++ to V8 can cause memory leaks. Copied from
// https://chromium-review.googlesource.com/c/chromium/src/+/6734440 Should be
// removed once https://github.com/electron/electron/issues/47922 is complete.
//
// This class must NOT be used with cppgc-managed types (gin::Wrappable).
// For cppgc types, use T* directly and gin::Converter<T*> for V8 conversion.
template <typename T>
class Handle {
static_assert(!cppgc::IsGarbageCollectedTypeV<T>,
"gin_helper::Handle must not be used with cppgc "
"garbage-collected types. Use T* directly instead.");
public:
Handle() : object_(nullptr) {}

View File

@@ -6,6 +6,7 @@
#define ELECTRON_SHELL_COMMON_GIN_HELPER_SELF_KEEP_ALIVE_H_
#include "gin/weak_cell.h"
#include "shell/common/gc_plugin.h"
namespace gin_helper {
@@ -28,6 +29,7 @@ class SelfKeepAlive final {
explicit operator bool() const { return keep_alive_; }
private:
GC_PLUGIN_IGNORE("Allowed to keep a Persistent to itself.")
cppgc::Persistent<Self> keep_alive_;
};

View File

@@ -8,6 +8,7 @@
#include "base/process/process.h"
#include "base/process/process_metrics.h"
#include "shell/common/api/electron_bindings.h"
#include "shell/common/gc_plugin.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/node_includes.h"
#include "shell/common/node_util.h"
@@ -204,6 +205,9 @@ class PreloadRealmLifetimeController
std::unique_ptr<base::ProcessMetrics> metrics_;
raw_ptr<ServiceWorkerData> service_worker_data_;
GC_PLUGIN_IGNORE(
"Intentional GC root to keep this object alive until the context is "
"destroyed")
blink::Persistent<PreloadRealmLifetimeController> self_;
};

View File

@@ -10,7 +10,6 @@
#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"

View File

@@ -5641,6 +5641,21 @@ describe('BrowserWindow module', () => {
expectBoundsEqual(w.getSize(), [400, 300]);
});
ifit(process.platform !== 'darwin')('works for a window smaller than 64x64', () => {
const w = new BrowserWindow({
show: false,
frame: false,
resizable: false,
transparent: true
});
w.setContentSize(60, 60);
expectBoundsEqual(w.getContentSize(), [60, 60]);
w.setContentSize(30, 30);
expectBoundsEqual(w.getContentSize(), [30, 30]);
w.setContentSize(10, 10);
expectBoundsEqual(w.getContentSize(), [10, 10]);
});
ifit(process.platform === 'win32')('do not change window with frame bounds when maximized', () => {
const w = new BrowserWindow({
show: true,

View File

@@ -250,34 +250,6 @@ ifdescribe(!isLinuxOnArm && !process.mas && !process.env.DISABLE_CRASH_REPORTER_
expect(crash.addedThenRemoved).to.be.undefined();
});
// Regression: base::circular_deque relocates elements on growth,
// corrupting crashpad::Annotation's self-referential pointers and
// causing missing crash keys or a hung handler. See crash_keys.cc.
it('does not corrupt the crashpad annotation list after deque reallocation', async function () {
// Tight timeout so a hanging handler fails fast instead of waiting
// for the mocha default of 120s.
this.timeout(45000);
const { port, waitForCrash } = await startServer();
runCrashApp('renderer-dynamic-keys', port);
const crash = await Promise.race([
waitForCrash(),
new Promise<never>((_resolve, reject) => {
global.setTimeout(
() => reject(new Error('crashpad handler hung walking corrupted annotation list; crash upload did not arrive within 30s')),
30000
);
})
]);
expect(crash.process_type).to.equal('renderer');
const missing: string[] = [];
for (let i = 0; i < 50; i++) {
if ((crash as any)[`dyn-key-${i}`] !== `val-${i}`) {
missing.push(`dyn-key-${i}`);
}
}
expect(missing, `missing dynamic crash keys: ${missing.join(', ')}`).to.be.empty();
});
it('contains v8 crash keys when a v8 crash occurs', async () => {
const { remotely } = await startRemoteControlApp();
const { port, waitForCrash } = await startServer();

View File

@@ -290,6 +290,92 @@ describe('cpp heap', () => {
});
});
describe('menu module', () => {
// Regression test for https://github.com/electron/electron/issues/50791
it('should not leak when rebuilding application menu', async () => {
const { remotely } = await startRemoteControlApp(['--js-flags=--expose-gc']);
const result = await remotely(async () => {
const { Menu } = require('electron');
const v8Util = (process as any)._linkedBinding('electron_common_v8_util');
const { getCppHeapStatistics } = require('node:v8');
function buildLargeMenu () {
return Menu.buildFromTemplate(
Array.from({ length: 10 }, (_, i) => ({
label: `Menu ${i}`,
submenu: Array.from({ length: 20 }, (_, j) => ({
label: `Item ${i}-${j}`,
click: () => {}
}))
}))
);
}
async function rebuildAndMeasure (n: number) {
for (let i = 0; i < n; i++) {
Menu.setApplicationMenu(buildLargeMenu());
}
for (let i = 0; i < 10; i++) {
await new Promise(resolve => setTimeout(resolve, 0));
v8Util.requestGarbageCollectionForTesting();
}
return getCppHeapStatistics('brief').used_size_bytes;
}
await rebuildAndMeasure(50);
const after1 = await rebuildAndMeasure(50);
const after2 = await rebuildAndMeasure(50);
return { after1, after2 };
});
const growth = result.after2 - result.after1;
expect(growth).to.be.at.most(
result.after1 * 0.1,
`C++ heap grew by ${growth} bytes between two identical rounds of 100 menu rebuilds — likely a leak`
);
});
});
describe('powerMonitor module', () => {
it('should retain native PowerMonitor via JS module reference', async () => {
const rc = await startRemoteControlApp(['--expose-internals', '--js-flags=--expose-gc']);
const result = await rc.remotely(async (heap: string) => {
const { powerMonitor, app } = require('electron');
const { recordState } = require(heap);
const v8Util = (process as any)._linkedBinding('electron_common_v8_util');
// Register a listener to trigger native PowerMonitor creation.
const listener = () => {};
powerMonitor.on('suspend', listener);
// Add and remove several listeners to exercise the path,
// then GC to ensure no duplicate native objects are created.
for (let i = 0; i < 5; i++) {
const tmp = () => {};
powerMonitor.on('resume', tmp);
powerMonitor.removeListener('resume', tmp);
}
v8Util.requestGarbageCollectionForTesting();
const state = recordState();
const nodes = state.snapshot.filter(
(node: any) => node.name === 'Electron / PowerMonitor'
);
const found = nodes.length > 0;
const noDuplicates = nodes.length === 1;
setTimeout(() => app.quit());
return { found, noDuplicates };
}, path.join(__dirname, '../../third_party/electron_node/test/common/heap'));
const [code] = await once(rc.process, 'exit');
expect(code).to.equal(0);
expect(result.found).to.equal(true, 'PowerMonitor should be in snapshot (held by JS module)');
expect(result.noDuplicates).to.equal(true, 'should have exactly one PowerMonitor instance');
});
});
describe('internal event', () => {
it('should record as node in heap snapshot', async () => {
const { remotely } = await startRemoteControlApp(['--expose-internals']);

View File

@@ -51,19 +51,6 @@ app.whenReady().then(() => {
});
w.loadURL(`about:blank?set_extra=${setExtraParameters ? 1 : 0}`);
w.webContents.on('render-process-gone', () => process.exit(0));
} else if (crashType === 'renderer-dynamic-keys') {
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
w.webContents.on('render-process-gone', () => process.exit(0));
w.webContents.on('did-finish-load', () => {
w.webContents.executeJavaScript(`
const { crashReporter } = require('electron');
for (let i = 0; i < 50; i++) {
crashReporter.addExtraParameter('dyn-key-' + i, 'val-' + i);
}
process.crash();
`);
});
w.loadURL('about:blank');
} else if (crashType === 'node') {
const crashPath = path.join(__dirname, 'node-crash.js');
const child = childProcess.fork(crashPath, { silent: true });

View File

@@ -590,7 +590,7 @@ __metadata:
"@types/semver": "npm:^7.5.8"
"@types/stream-json": "npm:^1.7.8"
"@types/temp": "npm:^0.9.4"
"@xmldom/xmldom": "npm:^0.8.12"
"@xmldom/xmldom": "npm:^0.8.11"
buffer: "npm:^6.0.3"
chalk: "npm:^4.1.0"
check-for-leaks: "npm:^1.2.1"
@@ -2503,14 +2503,7 @@ __metadata:
languageName: node
linkType: hard
"@xmldom/xmldom@npm:^0.8.12":
version: 0.8.12
resolution: "@xmldom/xmldom@npm:0.8.12"
checksum: 10c0/b733c84292d1bee32ef21a05aba8f9063456b51a54068d0b4a1abf5545156ee0b9894b7ae23775b5881b11c35a8a03871d1b514fb7e1b11654cdbee57e1c2707
languageName: node
linkType: hard
"@xmldom/xmldom@npm:^0.8.8":
"@xmldom/xmldom@npm:^0.8.11, @xmldom/xmldom@npm:^0.8.8":
version: 0.8.11
resolution: "@xmldom/xmldom@npm:0.8.11"
checksum: 10c0/e768623de72c95d3dae6b5da8e33dda0d81665047811b5498d23a328d45b13feb5536fe921d0308b96a4a8dd8addf80b1f6ef466508051c0b581e63e0dc74ed5