Compare commits

..

16 Commits

Author SHA1 Message Date
Samuel Attard
1e5c3b9533 repro: surface and retry MkdirAll errors during dir creation
Parallel relative-path MkdirAll on the bindflt emptyDir was silently
failing for some dirs (error discarded), so later file writes hit
'path not found'. Count errno-87 from MkdirAll and retry up to 5x so
the synthetic repro can proceed — and so we can see whether the
bindflt race also affects CreateDirectoryW.
2026-04-08 23:10:24 -07:00
Samuel Attard
b46d82bd82 repro: add pure-C / Win32 version of the double-open scanner
Rules Go out of the causal chain if it reproduces: raw CreateFileW +
_beginthreadex, no Go runtime. Cross-compiles with
'zig cc -target x86_64-windows-gnu -O2 -municode'.
2026-04-08 21:37:33 -07:00
Samuel Attard
82b6816e6c repro: make -skip-create extension filter opt-in via -ext
Default is now to scan all files under -dir; pass -ext .ninja to
restore the previous behavior. Needed so the direct-AKS pod can scan
the extracted src/ tree (which has no .ninja files) without running
gn.
2026-04-08 20:18:32 -07:00
Samuel Attard
4eb99755ef ci: split writer and scanner into separate processes
Add -no-create which regenerates the deterministic path list from
-dir/-files without writing or walking, so the scanner process touches
nothing before its first open. Workflow now runs repro.exe twice: a
writer process creates the 1.1M-file deep tree and exits, then a fresh
scanner process double-open scans the first 60k.
2026-04-08 19:43:57 -07:00
Samuel Attard
6a457738b9 ci: use a deep, many-directory synthetic tree
1.1M files in 4096 depth-2 dirs still produced 0 hits with the double-
open scan, while the real src+out tree (~99k dirs, depth 5-10, long
component names) produced 131-157. Per-open cost was ~2.6x higher on
the real paths, suggesting the race lives in path-component
resolution. Switch the synthetic tree to 6 levels at fanout 7
(117,649 leaf dirs, ~137k total), with ~18-char components and ~35-
char filenames so full paths are ~120 chars.
2026-04-08 19:41:55 -07:00
Samuel Attard
faa60cde9d ci: standalone-only; create ~1.1M random-byte files in parallel then double-open scan a 60k subset
Double-open on a 60k synthetic set over an empty emptyDir got 0 hits,
so scale is a required ingredient. Drop the checkout/build jobs for
now and scale the synthetic repro: parallel writers (NumCPU) create
~1.1M 512-byte random-payload files across 4096 two-level dirs, then
double-open scan just the first 60k so the scan stays fast while the
mapped directory carries the full ~1.1M-file load.
2026-04-08 19:24:07 -07:00
Samuel Attard
d533967130 ci: add standalone double-open repro on synthetic files
The double-open shape reproduced on the real out/Default tree:
stdlib 131/141456, nobackup 157/141456 err87 — so FILE_FLAG_BACKUP_
SEMANTICS is not the cause. Add a standalone job (no src cache) that
creates 60k synthetic files on the otherwise-empty emptyDir and runs
the same double-open scan, to confirm the trigger is the overlapping-
handle open itself rather than the prior filesystem state.
2026-04-08 18:55:26 -07:00
Samuel Attard
41362e6ca5 ci: replicate siso's double-open readFile shape in the repro
Scanning the real out/Default .ninja tree with the full src-cache
state loaded still produced 0/141k errno-87 hits with plain
open+close, while siso on the same runner gets 3-8 per scan.

Change the stdlib strategy to mirror fileParser.readFile exactly:
outer os.Open + Stat, then a goroutine (gated by a NumCPU-wide sema)
that does a second os.Open on the same path + ReadAt while the outer
handle is still held. Apply the same double-open shape to the nobackup
strategy. Also build repro.exe before gn gen and drop the
Get-ChildItem walk so nothing enumerates the .ninja files between gn
writing them and the scan.
2026-04-08 18:30:22 -07:00
Samuel Attard
d04ad3df51 ci: run checkout-windows before the build segment
pipeline-segment-electron-build restores the src cache by DEPS hash,
which requires the checkout job to have generated/uploaded it first.
Inline the checkout-windows job (with the hardcoded build-image sha
from build.yml) and chain the build segment behind it.
2026-04-08 18:04:56 -07:00
Samuel Attard
80216f0f73 ci: run io-repro right after 'e build --only-gen', skip the actual build
Runs gn gen via 'e build --only-gen' so the real .ninja tree exists on
the loaded emptyDir, then scans it with repro.exe, then skips the full
siso build on this branch so the job doesn't spend ~30min compiling.
Downstream verify/packaging steps will fail for lack of dist.zip;
that's expected on this diagnostic branch.
2026-04-08 17:59:59 -07:00
Samuel Attard
fd483641bf ci: run io-repro scanner inside the real win-x64 build
The synthetic repro never hit errno 87 across ~4.3M opens on the same
emptyDir, while siso on the same runner hits it 3-8x per scan — and
the arm64 chromedriver invocation ~11 min after gn gen still hit it,
so it is not a freshly-written-file race. The missing ingredient is
the ~1.2M-file state from the src cache plus out/.

Call pipeline-segment-electron-build for win/x64 from the push-
triggered workflow, and add a continue-on-error step in build-electron
that builds repro.exe and scans the real out/Default .ninja tree
(stdlib os.Open vs CreateFileW without FILE_FLAG_BACKUP_SEMANTICS)
right after the main Windows build. Cherry-picked the SISO_PATH
override so the build itself does not flake.
2026-04-08 17:58:12 -07:00
Samuel Attard
131baed06a ci: bump patched siso to v1.5.7-electron.2
Picks up the stderr logging for ERROR_INVALID_PARAMETER retries so
they're visible in the Windows build step output rather than only in
the glog .WARNING file.

(cherry picked from commit 0d6eec0137)
2026-04-08 17:56:56 -07:00
Samuel Attard
03ab888127 ci: use patched siso on Windows to retry transient ninja-load failures
Downloads a checksum-pinned build from electron/siso and sets SISO_PATH
so depot_tools picks it up instead of the CIPD binary. The patched build
retries ERROR_INVALID_PARAMETER from CreateFileW during the subninja
scan, which intermittently fires on container bind-mounted out/ dirs and
otherwise aborts the whole build. Drop this once the fix rolls into
Chromium's siso_version.

(cherry picked from commit 463c0d5e24)
2026-04-08 17:56:56 -07:00
Samuel Attard
752952d8b8 ci: reset $LASTEXITCODE after diagnostics step
fsutil reparsepoint query returns non-zero for 'not a reparse point',
and GHA's shell: powershell wrapper propagates the trailing
$LASTEXITCODE, so the step failed before the repro ran.
2026-04-08 17:49:24 -07:00
Samuel Attard
4887c81de1 ci: target repro at src\out and add cross-process write->scan mode
First run showed 0/1.44M errno-87 hits, but the test dir was under the
workspace root rather than src\out. fltmc is blind inside the
container, and fsutil reparsepoint can't see HCS mapped directories,
so instead: probe src\out with a 512MB free-space delta test to
determine whether it lands on container scratch or mapped host
storage, enumerate known FS filter services from the registry, and run
the stress test (a) same-process under src\out and (b) with the file
set written by a separate process that exits before the scanner
starts, mirroring the gn->siso handoff.
2026-04-08 17:47:30 -07:00
Samuel Attard
a7ae394260 ci: add Windows bind-mount I/O stress repro and platform diagnostics
Push-triggered workflow on the 32-core Windows ARC runner that logs
fltmc/isolation/reparse/hotfix info and runs a Go stress test:
NumCPU-parallel opens across ~60k small files under the workspace bind
mount, comparing os.Open (which passes FILE_FLAG_BACKUP_SEMANTICS)
against a direct CreateFileW without it, counting errno-87 hits per
strategy. Intended to root-cause the intermittent
'The parameter is incorrect.' failures seen in siso's subninja scan.
2026-04-08 17:40:32 -07:00
47 changed files with 1157 additions and 383 deletions

View File

@@ -93,8 +93,26 @@ 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' }}
if: ${{ inputs.target-platform == 'win' && github.ref_name != 'sam/windows-io-repro' }}
shell: powershell
run: |
cd src\electron

View File

@@ -30,3 +30,19 @@ 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,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

42
.github/workflows/windows-io-repro.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
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, 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

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

@@ -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

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

View File

@@ -0,0 +1,322 @@
// 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

@@ -0,0 +1,152 @@
// 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,7 +276,6 @@ 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_{nullptr};
gin_helper::SelfKeepAlive<Menu> keep_alive_{this};
};
} // namespace electron::api

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

@@ -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

@@ -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

@@ -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

@@ -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

@@ -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;
}

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

@@ -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