Compare commits

..

16 Commits

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

* fix: remove help menu entirely from default menu

* fix: move Electron help menu links to default app

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

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

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

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

Check the return value and fall back to UseDefaultSettings() on failure
so the dialog opens with defaults instead of crashing.
2026-04-09 13:14:36 -04:00
Shelley Vohr
7245c6a3f0 ci: re-check signed commits on every PR synchronize (#50811)
The needs-signed-commits label was previously added by the lightweight
synchronize workflow but only removed by a job in build.yml gated on
`gha-done`, which requires every macOS/Linux/Windows build to finish
green. That made label removal both slow (waits on the full pipeline)
and fragile (any unrelated build failure leaves the label pinned even
after commits are properly signed).

Drop the `if` guard on the synchronize job so it re-evaluates signing
on every push, and add a removal step that runs on success when the
label is present. Force-pushing signed commits now clears the label as
soon as the check completes, with no dependency on the build pipeline.
2026-04-09 11:02:01 -04:00
Charles Kerr
b484b0bde9 fix: fix inset and stop using gfx::ToFlooredRectDeprecated() (#50809)
fix: fix inset and stop using ToFlooredRectDeprecated()
2026-04-09 09:55:11 -05:00
Charles Kerr
6c8a910232 refactor: remove unnecessary raw_ptr SavePageHandler::web_contents_ (#50810)
refactor: remove unnecessary field raw_ptr<content::WebContents> SavePageHandler::web_contents_
2026-04-09 09:54:44 -05:00
Noah Gregory
cc3d4f5f58 fix: PDF support when site isolation trials disabled (#50689)
* fix: use proper OOPIF PDF check in `StreamsPrivateAPI`

* fix: add `ShouldEnableSubframeZoom` override to `ElectronBrowserClient` for upstream parity

* fix: add `MaybeOverrideLocalURLCrossOriginEmbedderPolicy` override to `ElectronBrowserClient` for upstream parity

* fix: add `DoesSiteRequireDedicatedProcess` override to `ElectronBrowserClient` for upstream parity

* style: move `DoesSiteRequireDedicatedProcess` to correct override section
2026-04-09 15:35:26 +02:00
Shelley Vohr
b711ce7b04 chore: remove window enlargement revert patch (#50612)
* chore: remove window enlargement revert patch

Chromium removed the `window_enlargement_` system from
DesktopWindowTreeHostWin (1771dbae), which was a workaround for an AMD
driver bug from 2013 (crbug.com/286609) where translucent HWNDs smaller
than 64x64 caused graphical glitches. Chromium confirmed this is no
longer needed and shipped the removal.

This removes the revert patch and all Electron-side code that depended
on the `kEnableTransparentHwndEnlargement` feature flag, including the
`GetExpandedWindowSize` helper and max size constraint expansion in
`NativeWindow::GetContentMaximumSize`.

* test: remove obsolete <64x64 transparent window test

The test was added in 2018 (#12904) to verify the AMD driver
workaround that artificially enlarged translucent HWNDs smaller than
64x64 (crbug.com/286609). The workaround set the real HWND to 64x64
and subtracted a stored window_enlargement_ from every client/window
bounds query, so getContentSize() reported the originally-requested
size even though the actual HWND was larger.

With both the Chromium window_enlargement_ system and Electron's
GetExpandedWindowSize gone, setContentSize on a transparent
thickFrame window calls SetWindowPos directly. WS_THICKFRAME windows
are subject to DefWindowProc's MINMAXINFO.ptMinTrackSize clamp on
programmatic resizes (Chromium's OnGetMinMaxInfo ends with
SetMsgHandled(FALSE), so DefWindowProc overwrites the zeroed
min-track with system defaults), which on Windows Server 2025
floors at 32x39 — hence the failing [32, 39] vs [30, 30].

The removed feature_list.cc comment explicitly flagged this test as
the blocker for retiring kEnableTransparentHwndEnlargement, so
delete it alongside the workaround it was validating.
2026-04-09 15:34:10 +02:00
Alexey
adf9a6e303 fix: restore std::deque for dynamic crash key storage (#50795)
#47171 migrated `std::deque` to `base::circular_deque` in
`shell/common/crash_keys.cc`. However, `CrashKeyString` wraps a
`crashpad::Annotation` that holds self-referential pointers and
registers itself in a process-global linked list. `circular_deque`
relocates elements on growth (via `VectorBuffer::MoveConstructRange`),
leaving those pointers dangling — causing missing crash keys or a hung
crashpad handler (especially on macOS). The `base/containers/README.md`
warns: "Since `base::deque` does not have stable iterators and it will
move the objects it contains, it may not be appropriate for all uses."

Reverts to `std::deque`, whose block-based layout never relocates
existing elements. Adds a regression test that registers 50 dynamic
crash keys and verifies they all survive a renderer crash.

Notes: Fixed crash keys being lost and the crash reporter hanging on
macOS when many dynamic crash keys were registered.

Made-with: Cursor
2026-04-09 10:50:32 +02:00
Calvin
6744293e96 fix: account for extraSize in aspect ratio min/max clamping on macOS (#50794)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 10:50:17 +02:00
dependabot[bot]
0d3342debf build(deps-dev): bump @xmldom/xmldom from 0.8.11 to 0.8.12 in the npm_and_yarn group across 1 directory (#50824)
build(deps-dev): bump @xmldom/xmldom

Bumps the npm_and_yarn group with 1 update in the / directory: [@xmldom/xmldom](https://github.com/xmldom/xmldom).


Updates `@xmldom/xmldom` from 0.8.11 to 0.8.12
- [Release notes](https://github.com/xmldom/xmldom/releases)
- [Changelog](https://github.com/xmldom/xmldom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/xmldom/xmldom/compare/0.8.11...0.8.12)

---
updated-dependencies:
- dependency-name: "@xmldom/xmldom"
  dependency-version: 0.8.12
  dependency-type: direct:development
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-09 09:54:13 +02:00
Calvin
157cdac4b9 test: use shared get_out_dir() in generate_node_headers.py (#50828)
The local get_out_dir() defaulted to 'Testing' instead of 'Default',
causing e test to fail when using a non-Testing build config. Replace
it with the canonical version from script/lib/util.py.
2026-04-09 09:52:14 +02:00
Shelley Vohr
4dfada86ce fix: menu items not cleaned up after rebuild (#50806)
Menu was holding a SelfKeepAlive to itself from construction, so any
Menu that was never opened (e.g. an application menu replaced before
being shown) stayed pinned in cppgc forever. Repeated calls to
Menu.setApplicationMenu leaked every prior Menu along with its model
and items.

Restore the original Pin/Unpin lifecycle: start keep_alive_ empty and
only assign `this` in OnMenuWillShow. OnMenuWillClose already clears
it.
2026-04-09 11:56:39 +09:00
47 changed files with 383 additions and 1157 deletions

View File

@@ -93,26 +93,8 @@ runs:
echo "Skipping build-stats.mjs upload because DD_API_KEY is not set"
fi
node electron/script/build-stats.mjs $BUILD_STATS_ARGS || true
- name: io-repro — set up Go
if: ${{ inputs.target-platform == 'win' && github.ref_name == 'sam/windows-io-repro' }}
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: '1.26'
cache: false
- name: io-repro — gn gen then scan real out/Default subninjas
if: ${{ inputs.target-platform == 'win' && github.ref_name == 'sam/windows-io-repro' }}
shell: powershell
run: |
cd src\electron
git pack-refs
cd ..
cd electron\script\windows-io-repro
go build -o repro.exe .
cd ..\..\..
e build --only-gen
electron\script\windows-io-repro\repro.exe -skip-create -dir out\Default -rounds 12
- name: Build Electron (Windows) ${{ inputs.step-suffix }}
if: ${{ inputs.target-platform == 'win' && github.ref_name != 'sam/windows-io-repro' }}
if: ${{ inputs.target-platform == 'win' }}
shell: powershell
run: |
cd src\electron

View File

@@ -30,19 +30,3 @@ runs:
echo "$HOME/.electron_build_tools/third_party/depot_tools" >> $GITHUB_PATH
echo "$HOME/.electron_build_tools/third_party/depot_tools/python-bin" >> $GITHUB_PATH
fi
- name: Install patched siso (Windows)
# Overrides the CIPD siso with a build from electron/siso that carries a
# retry for transient ERROR_INVALID_PARAMETER during the subninja scan on
# container bind-mounted out/ directories. Remove once the fix has rolled
# into Chromium's siso_version.
if: ${{ runner.os == 'Windows' }}
shell: bash
env:
ELECTRON_SISO_URL: https://github.com/electron/siso/releases/download/v1.5.7-electron.2/siso-windows-amd64.exe
ELECTRON_SISO_SHA256: 0e6b754820be3324d5ea4ca3af3634b4cfcf806d89140d78fec4e2a8ef636c9d
run: |
set -eo pipefail
mkdir -p /c/electron-siso
curl --fail --retry 3 -sSL "$ELECTRON_SISO_URL" -o /c/electron-siso/siso.exe
echo "$ELECTRON_SISO_SHA256 /c/electron-siso/siso.exe" | sha256sum --check --strict -
echo "SISO_PATH=C:\\electron-siso\\siso.exe" >> "$GITHUB_ENV"

View File

@@ -442,34 +442,7 @@ 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,7 +13,6 @@ 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
@@ -23,9 +22,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).
@@ -37,3 +36,11 @@ 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

@@ -1,42 +0,0 @@
name: windows-io-repro
on:
push:
branches:
- sam/windows-io-repro
permissions: {}
jobs:
standalone:
permissions:
contents: read
runs-on: electron-arc-centralus-windows-amd64-32core
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: '1.26'
cache: false
- name: Build repro
shell: powershell
run: |
cd script/windows-io-repro
go build -o repro.exe .
- name: Writer process — create 1.1M files in deep tree
shell: powershell
env:
REPRO_DIR: ${{ github.workspace }}\repro-files
run: |
script\windows-io-repro\repro.exe -dir "$env:REPRO_DIR" -files 1100000 -write-only
- name: Scanner process — double-open scan 60k subset
shell: powershell
env:
REPRO_DIR: ${{ github.workspace }}\repro-files
run: |
script\windows-io-repro\repro.exe -dir "$env:REPRO_DIR" -files 1100000 -no-create -scan-first 60000 -rounds 12

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 d1e06b675b19226cf3b78e1aada8d8f2d684fada..ce810555b8501797643987916a728cad8f5adaa5 100644
index e4da40256ce94d6a0896792a8ef2faa18e1fa5d2..3a5833fcc018f32e86c0a95a42937fb9ac6c5a40 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
@@ -184,6 +184,10 @@ void DesktopWindowTreeHostWin::FinishTouchDrag(gfx::Point screen_point) {
@@ -167,6 +167,10 @@ void DesktopWindowTreeHostWin::FinishTouchDrag(gfx::Point screen_point) {
}
}
@@ -23,7 +23,7 @@ index d1e06b675b19226cf3b78e1aada8d8f2d684fada..ce810555b8501797643987916a728cad
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 a40bd9f25fa07a553c011cf19f155f8158f4ae5f..ae2baec731b5fcd8be97f2177d23b860d67ab8bc 100644
index 27322ef34edf3fa8bfbd20b1baddcaf3b7555618..b8d1fa863fd05ebc3ab8ac5ef8c4d81361ce45fe 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

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

View File

@@ -1,285 +0,0 @@
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,7 +8,9 @@ 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):
@@ -31,13 +33,6 @@ 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

@@ -1,3 +0,0 @@
module github.com/electron/electron/script/windows-io-repro
go 1.26

View File

@@ -1,322 +0,0 @@
// Command windows-io-repro stress-tests concurrent file opens on Windows
// to reproduce the intermittent ERROR_INVALID_PARAMETER (errno 87) seen
// in siso's subninja scan when out/ is served through a container
// bind-mount filter driver.
//
// It creates a large set of small files in -dir, then runs multiple
// NumCPU-parallel scan rounds using two strategies:
// - stdlib: os.Open (Go passes FILE_FLAG_BACKUP_SEMANTICS)
// - nobackup: direct CreateFileW without FILE_FLAG_BACKUP_SEMANTICS
//
// Each errno-87 hit is logged with whether a 5ms retry recovers, so the
// two strategies can be compared head-to-head.
//
//go:build windows
package main
import (
"crypto/rand"
"errors"
"flag"
"fmt"
"os"
"path/filepath"
"runtime"
"sync"
"sync/atomic"
"syscall"
"time"
)
const errnoInvalidParameter = syscall.Errno(87)
// innerSema mirrors siso's fsema: the chunk-read goroutine acquires a
// NumCPU-wide slot before doing its second open.
var innerSema = make(chan struct{}, runtime.NumCPU())
// openStdlib replicates siso's fileParser.readFile: outer os.Open + Stat,
// then a goroutine that does a second os.Open on the same path and ReadAt,
// with the outer handle still held until the goroutine returns.
func openStdlib(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
st, err := f.Stat()
if err != nil {
return err
}
buf := make([]byte, st.Size())
errCh := make(chan error, 1)
go func() {
innerSema <- struct{}{}
defer func() { <-innerSema }()
f2, err := os.Open(path)
if err != nil {
errCh <- err
return
}
defer f2.Close()
_, err = f2.ReadAt(buf, 0)
errCh <- err
}()
return <-errCh
}
func createNoBackup(path string) (syscall.Handle, error) {
p, err := syscall.UTF16PtrFromString(path)
if err != nil {
return syscall.InvalidHandle, err
}
h, err := syscall.CreateFile(
p,
syscall.GENERIC_READ,
syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE,
nil,
syscall.OPEN_EXISTING,
syscall.FILE_ATTRIBUTE_NORMAL,
0,
)
if err != nil {
return syscall.InvalidHandle, &os.PathError{Op: "CreateFile", Path: path, Err: err}
}
return h, nil
}
// openNoBackup mirrors openStdlib's double-open shape but uses
// CreateFileW without FILE_FLAG_BACKUP_SEMANTICS for both opens.
func openNoBackup(path string) error {
h1, err := createNoBackup(path)
if err != nil {
return err
}
defer syscall.CloseHandle(h1)
errCh := make(chan error, 1)
go func() {
innerSema <- struct{}{}
defer func() { <-innerSema }()
h2, err := createNoBackup(path)
if err != nil {
errCh <- err
return
}
errCh <- syscall.CloseHandle(h2)
}()
return <-errCh
}
type result struct {
opens atomic.Int64
err87 atomic.Int64
err87Retry atomic.Int64 // errno 87 that cleared on immediate retry
otherErr atomic.Int64
}
func scan(name string, open func(string) error, files []string, workers int, r *result) time.Duration {
start := time.Now()
ch := make(chan string, workers)
var wg sync.WaitGroup
for range workers {
wg.Add(1)
go func() {
defer wg.Done()
for path := range ch {
r.opens.Add(1)
err := open(path)
if err == nil {
continue
}
if errors.Is(err, errnoInvalidParameter) {
r.err87.Add(1)
time.Sleep(5 * time.Millisecond)
if open(path) == nil {
r.err87Retry.Add(1)
fmt.Printf("[%s] errno87 %s — recovered on retry\n", name, path)
} else {
fmt.Printf("[%s] errno87 %s — retry FAILED\n", name, path)
}
} else {
r.otherErr.Add(1)
fmt.Printf("[%s] other error %s: %v\n", name, path, err)
}
}
}()
}
for _, f := range files {
ch <- f
}
close(ch)
wg.Wait()
return time.Since(start)
}
func main() {
dir := flag.String("dir", "repro-files", "directory to create/scan test files in")
nfiles := flag.Int("files", 50000, "number of test files to create")
rounds := flag.Int("rounds", 10, "scan rounds per strategy")
workers := flag.Int("workers", runtime.NumCPU(), "concurrent openers per scan")
writeOnly := flag.Bool("write-only", false, "create files then exit (for cross-process write→scan mode)")
skipCreate := flag.Bool("skip-create", false, "scan existing files in -dir (walks the tree)")
ext := flag.String("ext", "", "with -skip-create, only scan files with this extension (e.g. .ninja); empty = all")
noCreate := flag.Bool("no-create", false, "regenerate the deterministic path list from -dir/-files but do not write files")
scanFirst := flag.Int("scan-first", 0, "if >0, scan only the first N files (create all, scan a subset)")
flag.Parse()
fmt.Printf("GOOS=%s GOARCH=%s NumCPU=%d GOMAXPROCS=%d workers=%d\n",
runtime.GOOS, runtime.GOARCH, runtime.NumCPU(), runtime.GOMAXPROCS(0), *workers)
if err := os.MkdirAll(*dir, 0o755); err != nil {
fmt.Fprintln(os.Stderr, "mkdir:", err)
os.Exit(1)
}
abs, _ := filepath.Abs(*dir)
fmt.Printf("test dir: %s\n", abs)
var files []string
if *skipCreate {
err := filepath.WalkDir(*dir, func(p string, d os.DirEntry, err error) error {
if err == nil && !d.IsDir() && (*ext == "" || filepath.Ext(p) == *ext) {
files = append(files, p)
}
return nil
})
if err != nil {
fmt.Fprintln(os.Stderr, "walk:", err)
os.Exit(1)
}
fmt.Printf("scanning %d existing files\n", len(files))
} else {
// Deep tree: 6 levels, fanout 7 → 7^6 = 117,649 leaf dirs (~137k dirs
// total), long-ish component names so full paths are ~120 chars — closer
// to the real src/out tree (~99k dirs, depth 510, long paths).
const depth = 6
const fanout = 7
leafDir := func(i int) string {
parts := make([]string, depth)
n := i
for d := depth - 1; d >= 0; d-- {
parts[d] = fmt.Sprintf("lvl%d_component_%02d", d, n%fanout)
n /= fanout
}
return filepath.Join(parts...)
}
leaves := 1
for range depth {
leaves *= fanout
}
payload := make([]byte, 512)
_, _ = rand.Read(payload)
files = make([]string, *nfiles)
for i := range *nfiles {
files[i] = filepath.Join(*dir, leafDir(i%leaves), fmt.Sprintf("some_build_target_name_%07d.ninja", i))
}
if *noCreate {
fmt.Printf("regenerated %d paths (no-create)\n", len(files))
goto scan
}
fmt.Printf("creating %d leaf dirs (depth %d, fanout %d)...\n", leaves, depth, fanout)
{
start := time.Now()
ch := make(chan int, *workers)
var wg sync.WaitGroup
var mkdirErr87 atomic.Int64
for range *workers {
wg.Add(1)
go func() {
defer wg.Done()
for i := range ch {
p := filepath.Join(*dir, leafDir(i))
for attempt := 0; ; attempt++ {
err := os.MkdirAll(p, 0o755)
if err == nil {
break
}
if errors.Is(err, errnoInvalidParameter) {
mkdirErr87.Add(1)
}
if attempt >= 4 {
fmt.Fprintf(os.Stderr, "mkdirall %s: %v (giving up)\n", p, err)
break
}
time.Sleep(5 * time.Millisecond)
}
}
}()
}
for i := range leaves {
ch <- i
}
close(ch)
wg.Wait()
fmt.Printf("dirs created in %s (mkdir err87=%d)\n", time.Since(start).Round(time.Second), mkdirErr87.Load())
}
fmt.Printf("creating %d files (%d-byte random payload, %d parallel writers)...\n", *nfiles, len(payload), *workers)
start := time.Now()
var created atomic.Int64
ch := make(chan string, *workers)
var wg sync.WaitGroup
for range *workers {
wg.Add(1)
go func() {
defer wg.Done()
for p := range ch {
if err := os.WriteFile(p, payload, 0o644); err != nil {
fmt.Fprintln(os.Stderr, "write:", err)
os.Exit(1)
}
if n := created.Add(1); n%100000 == 0 {
fmt.Printf(" ... %d/%d (%s)\n", n, *nfiles, time.Since(start).Round(time.Second))
}
}
}()
}
for _, p := range files {
ch <- p
}
close(ch)
wg.Wait()
fmt.Printf("created %d files across %d leaf dirs in %s\n", len(files), leaves, time.Since(start).Round(time.Second))
}
if *writeOnly {
fmt.Println("write-only mode; exiting")
return
}
scan:
if *scanFirst > 0 && *scanFirst < len(files) {
files = files[:*scanFirst]
fmt.Printf("scanning first %d of %d files\n", len(files), *nfiles)
}
type strat struct {
name string
fn func(string) error
r result
}
strats := []*strat{
{name: "stdlib", fn: openStdlib},
{name: "nobackup", fn: openNoBackup},
}
for round := 1; round <= *rounds; round++ {
for _, s := range strats {
d := scan(s.name, s.fn, files, *workers, &s.r)
fmt.Printf("round %d/%d [%s] %d opens in %s (err87 so far: %d)\n",
round, *rounds, s.name, len(files), d.Round(time.Millisecond), s.r.err87.Load())
}
}
fmt.Println("\n=== summary ===")
for _, s := range strats {
fmt.Printf("[%s] opens=%d err87=%d err87_recovered=%d other_err=%d\n",
s.name, s.r.opens.Load(), s.r.err87.Load(), s.r.err87Retry.Load(), s.r.otherErr.Load())
}
var total int64
for _, s := range strats {
total += s.r.err87.Load()
}
if total == 0 {
fmt.Println("no ERROR_INVALID_PARAMETER observed")
}
}

View File

@@ -1,152 +0,0 @@
// Double-open errno-87 repro in plain C / Win32, to rule Go in/out.
//
// Build (cross, macOS/Linux):
// zig cc -target x86_64-windows-gnu -O2 -o repro_c.exe repro.c
// or on Windows:
// cl /O2 repro.c /Fe:repro_c.exe
//
// Usage:
// cd C:\work
// repro_c.exe src\third_party 32 12
// arg1 = relative dir to walk for files (.h only)
// arg2 = worker threads (default 32)
// arg3 = rounds (default 12)
#define WIN32_LEAN_AND_MEAN
#include <process.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#define MAX_FILES 200000
static wchar_t* g_files[MAX_FILES];
static volatile LONG g_nfiles = 0;
static volatile LONG g_idx;
static volatile LONG g_err87 = 0;
static volatile LONG g_err87_recovered = 0;
static volatile LONG g_other = 0;
static HANDLE g_inner_sema;
static void walk(const wchar_t* dir) {
wchar_t pat[1024];
_snwprintf(pat, 1024, L"%ls\\*", dir);
WIN32_FIND_DATAW fd;
HANDLE h = FindFirstFileW(pat, &fd);
if (h == INVALID_HANDLE_VALUE)
return;
do {
if (!wcscmp(fd.cFileName, L".") || !wcscmp(fd.cFileName, L".."))
continue;
wchar_t full[1024];
_snwprintf(full, 1024, L"%ls\\%ls", dir, fd.cFileName);
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
walk(full);
} else {
size_t n = wcslen(full);
if (n > 2 && !_wcsicmp(full + n - 2, L".h") && g_nfiles < MAX_FILES) {
g_files[g_nfiles] = _wcsdup(full);
InterlockedIncrement(&g_nfiles);
}
}
} while (FindNextFileW(h, &fd));
FindClose(h);
}
static HANDLE open_ro(const wchar_t* p) {
return CreateFileW(p, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
}
struct inner_arg {
const wchar_t* path;
volatile LONG done;
DWORD err;
};
static unsigned __stdcall inner_thread(void* arg) {
struct inner_arg* a = (struct inner_arg*)arg;
WaitForSingleObject(g_inner_sema, INFINITE);
HANDLE h = open_ro(a->path);
if (h == INVALID_HANDLE_VALUE) {
a->err = GetLastError();
} else {
a->err = 0;
CloseHandle(h);
}
ReleaseSemaphore(g_inner_sema, 1, NULL);
InterlockedExchange(&a->done, 1);
return 0;
}
static unsigned __stdcall worker(void* unused) {
(void)unused;
for (;;) {
LONG i = InterlockedIncrement(&g_idx) - 1;
if (i >= g_nfiles)
return 0;
const wchar_t* p = g_files[i];
HANDLE h1 = open_ro(p);
if (h1 == INVALID_HANDLE_VALUE) {
DWORD e = GetLastError();
if (e == ERROR_INVALID_PARAMETER)
InterlockedIncrement(&g_err87);
else
InterlockedIncrement(&g_other);
continue;
}
struct inner_arg a = {p, 0, 0};
HANDLE th = (HANDLE)_beginthreadex(NULL, 0, inner_thread, &a, 0, NULL);
WaitForSingleObject(th, INFINITE);
CloseHandle(th);
if (a.err == ERROR_INVALID_PARAMETER) {
InterlockedIncrement(&g_err87);
Sleep(5);
HANDLE h2 = open_ro(p);
if (h2 != INVALID_HANDLE_VALUE) {
InterlockedIncrement(&g_err87_recovered);
CloseHandle(h2);
}
fwprintf(stderr, L"[c] errno87 %ls\n", p);
} else if (a.err != 0) {
InterlockedIncrement(&g_other);
}
CloseHandle(h1);
}
}
int wmain(int argc, wchar_t** argv) {
const wchar_t* dir = (argc > 1) ? argv[1] : L"src\\third_party";
int nworkers = (argc > 2) ? _wtoi(argv[2]) : 32;
int rounds = (argc > 3) ? _wtoi(argv[3]) : 12;
wprintf(L"dir=%ls workers=%d rounds=%d\n", dir, nworkers, rounds);
walk(dir);
wprintf(L"files=%ld\n", g_nfiles);
if (g_nfiles == 0)
return 1;
g_inner_sema = CreateSemaphoreW(NULL, nworkers, nworkers, NULL);
HANDLE* ths = (HANDLE*)calloc(nworkers, sizeof(HANDLE));
for (int r = 1; r <= rounds; r++) {
g_idx = 0;
DWORD t0 = GetTickCount();
for (int w = 0; w < nworkers; w++)
ths[w] = (HANDLE)_beginthreadex(NULL, 0, worker, NULL, 0, NULL);
for (int w = 0; w < nworkers; w++) {
WaitForSingleObject(ths[w], INFINITE);
CloseHandle(ths[w]);
}
wprintf(L"round %d/%d: %ld opens in %lums (err87 so far: %ld)\n", r, rounds,
g_nfiles, GetTickCount() - t0, g_err87);
}
wprintf(L"\n=== summary ===\nopens=%ld err87=%ld recovered=%ld other=%ld\n",
(long)g_nfiles * rounds, g_err87, g_err87_recovered, g_other);
return 0;
}

View File

@@ -276,6 +276,7 @@ void Menu::OnMenuWillClose() {
}
void Menu::OnMenuWillShow() {
keep_alive_ = this;
Emit("menu-will-show");
}

View File

@@ -132,7 +132,7 @@ class Menu : public gin::Wrappable<Menu>,
int GetIndexOfCommandId(int command_id) const;
int GetItemCount() const;
gin_helper::SelfKeepAlive<Menu> keep_alive_{this};
gin_helper::SelfKeepAlive<Menu> keep_alive_{nullptr};
};
} // namespace electron::api

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,9 +12,8 @@
namespace electron::api {
SavePageHandler::SavePageHandler(content::WebContents* web_contents,
gin_helper::Promise<void> promise)
: web_contents_(web_contents), promise_(std::move(promise)) {}
SavePageHandler::SavePageHandler(gin_helper::Promise<void> promise)
: promise_{std::move(promise)} {}
SavePageHandler::~SavePageHandler() = default;
@@ -26,9 +25,10 @@ void SavePageHandler::OnDownloadCreated(content::DownloadManager* manager,
}
bool SavePageHandler::Handle(const base::FilePath& full_path,
const content::SavePageType& save_type) {
const content::SavePageType& save_type,
content::WebContents* web_contents) {
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,7 +5,6 @@
#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"
@@ -26,12 +25,12 @@ namespace electron::api {
class SavePageHandler : private content::DownloadManager::Observer,
private download::DownloadItem::Observer {
public:
SavePageHandler(content::WebContents* web_contents,
gin_helper::Promise<void> promise);
explicit SavePageHandler(gin_helper::Promise<void> promise);
~SavePageHandler() override;
bool Handle(const base::FilePath& full_path,
const content::SavePageType& save_type);
const content::SavePageType& save_type,
content::WebContents* web_contents);
private:
void Destroy(download::DownloadItem* item);
@@ -43,7 +42,6 @@ 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,6 +63,7 @@
#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"
@@ -1591,6 +1592,46 @@ 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,6 +30,7 @@ class FilePath;
namespace content {
class ClientCertificateDelegate;
class NavigationHandle;
class PlatformNotificationService;
class NavigationThrottleRegistry;
class QuotaPermissionContext;
@@ -82,6 +83,14 @@ 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

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

View File

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

View File

@@ -34,10 +34,6 @@
#include "printing/printing_features.h"
#endif
#if BUILDFLAG(IS_WIN)
#include "ui/views/views_features.h"
#endif
namespace electron {
void InitializeFeatureList() {
@@ -71,13 +67,6 @@ 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,11 +31,6 @@
#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"
@@ -71,31 +66,6 @@ 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)
@@ -398,15 +368,7 @@ gfx::Size NativeWindow::GetContentMinimumSize() const {
}
gfx::Size NativeWindow::GetContentMaximumSize() const {
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;
return GetContentSizeConstraints().GetMaximumSize();
}
void NativeWindow::SetSheetOffset(const double offsetX, const double offsetY) {

View File

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

View File

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

View File

@@ -158,45 +158,43 @@ using TitleBarStyle = electron::NativeWindowMac::TitleBarStyle;
windowSize.width() - contentSize.width() + extraSize.width();
double extraHeightPlusFrame = titleBarHeight + extraSize.height();
newSize.width =
roundf((frameSize.height - extraHeightPlusFrame) * aspectRatio +
extraWidthPlusFrame);
newSize.height =
roundf((newSize.width - extraWidthPlusFrame) / aspectRatio +
extraHeightPlusFrame);
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));
// 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 ? minWidthForAspectRatio
newSize.width = atMinHeight ? widthForHeight(minSize.height)
: std::max(newSize.width, minSize.width);
double minHeightForAspectRatio = minSize.width / aspectRatio;
bool atMinWidth =
minSize.width > zeroSize.width && newSize.width <= minSize.width;
newSize.height = atMinWidth ? minHeightForAspectRatio
newSize.height = atMinWidth ? heightForWidth(minSize.width)
: 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 ? maxWidthForAspectRatio
newSize.width = atMaxHeight ? widthForHeight(maxSize.height)
: std::min(newSize.width, maxSize.width);
double maxHeightForAspectRatio = maxSize.width / aspectRatio;
bool atMaxWidth =
maxSize.width < FLT_MAX && newSize.width >= maxSize.width;
newSize.height = atMaxWidth ? maxHeightForAspectRatio
newSize.height = atMaxWidth ? heightForWidth(maxSize.width)
: std::min(newSize.height, maxSize.height);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,17 +28,22 @@ 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<base::circular_deque<CrashKeyString>> extra_keys;
static base::NoDestructor<std::deque<CrashKeyString>> extra_keys;
return *extra_keys;
}
auto& GetExtraCrashKeyNames() {
static base::NoDestructor<base::circular_deque<std::string>> crash_key_names;
static base::NoDestructor<std::deque<std::string>> crash_key_names;
return *crash_key_names;
}

View File

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

View File

@@ -5641,21 +5641,6 @@ 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,6 +250,34 @@ 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

@@ -51,6 +51,19 @@ 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.11"
"@xmldom/xmldom": "npm:^0.8.12"
buffer: "npm:^6.0.3"
chalk: "npm:^4.1.0"
check-for-leaks: "npm:^1.2.1"
@@ -2503,7 +2503,14 @@ __metadata:
languageName: node
linkType: hard
"@xmldom/xmldom@npm:^0.8.11, @xmldom/xmldom@npm:^0.8.8":
"@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":
version: 0.8.11
resolution: "@xmldom/xmldom@npm:0.8.11"
checksum: 10c0/e768623de72c95d3dae6b5da8e33dda0d81665047811b5498d23a328d45b13feb5536fe921d0308b96a4a8dd8addf80b1f6ef466508051c0b581e63e0dc74ed5