Compare commits

...

58 Commits

Author SHA1 Message Date
trop[bot]
faa40332ad fix: handle out of order recording errors in skia graphite (#49615)
* fix: handle out of order recording errors in skia graphite

Refs https://issues.chromium.org/issues/458722690

Co-authored-by: deepak1556 <hop2deep@gmail.com>

* chore: fixup patches

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: deepak1556 <hop2deep@gmail.com>
Co-authored-by: Niklas Wenzel <dev@nikwen.de>
2026-02-02 15:06:17 -08:00
David Sanders
e1ac9d5d1b build(dev-deps): bump @electron/lint-roller to 3.2.0 (#49588)
build(dev-deps): bump @electron/lint-roller to 3.2.0 (#49565)
2026-02-02 11:34:17 +01:00
trop[bot]
dc254e4bfc fix: duplicate fullscreen macOS menu item (#49597)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-02-02 09:39:24 +01:00
trop[bot]
5631882390 refactor: address PathProvider TODO (#49600)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-02-02 09:39:05 +01:00
trop[bot]
e6b53033dd feat: msix auto-updater (#49585)
* feat: native auto updater for MSIX on Windows

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* doc: added MSIX debug documentation

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* fix: allow downgrade with json release file and emit update-available

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* test: msix auot-update tests

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* doc: API documentation

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* test: add package version validation

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* fix: docs typo

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* fix: don't allow auto-updating when using appinstaller manifest

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* fix: getPackageInfo interface implementation

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* fix: review feedback, add comment

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* fix: missed filename commit

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* fix: install test cert on demand

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* fix: time stamp mismatch in tests

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* fix: feedback - rename to MSIXPackageInfo

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* fix: update and reference windowsStore property

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* fix: remove getPackagInfo from public API

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* fix: type error bcause of removed API

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* fix: fix Windows MSIX release build errors (#49613)

* fix: fix MSIX release build

* fix: add C++/WinRT headers

* build: modify include paths

* fix: compile msix as seperate source set

* build: add additional needed deps for msix

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>
Co-authored-by: Keeley Hammond <vertedinde@electronjs.org>
2026-02-02 09:33:46 +01:00
trop[bot]
fd4f835f37 fix: macOS menu item accelerators when item disabled (#49593)
fix: macOS menu item acceerators when item disabled

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-01-30 14:52:17 -05:00
David Sanders
f36da597ff ci: rework reapply patches (#49589)
ci: rework reapply patches (#49552)
2026-01-30 13:17:05 -05:00
trop[bot]
87badb84f8 test: remove split dependency (#49555)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: David Sanders <dsanders11@ucsbalum.com>
2026-01-28 15:38:05 +01:00
trop[bot]
2f3a1ca461 fix: chrome://accessibility drift (#49559)
https://chromium-review.googlesource.com/c/chromium/src/+/6870052

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-01-28 15:37:39 +01:00
trop[bot]
81ae20905c ci: reapply patches if PR base branch updates them (#49533)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: David Sanders <dsanders11@ucsbalum.com>
2026-01-27 17:40:35 -05:00
trop[bot]
da4a808af7 docs: correct type for process.noDeprecation (#49545)
* docs: correct type for process.noDeprecation

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>

* docs: mark `noDeprecation` as optional instead

Co-authored-by: René <contact.9a5d6388@renegade334.me.uk>

Co-authored-by: David Sanders <dsanders11@ucsbalum.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
Co-authored-by: David Sanders <dsanders11@ucsbalum.com>
2026-01-27 11:37:32 +01:00
trop[bot]
71579e4749 fix: second argument to shell.writeShortcutLink is optional (#49502)
fix: second argument to shell.writeShortcutLink is optional

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-01-23 14:31:09 -05:00
trop[bot]
06fdee87b3 fix: return early from platform_util::Beep() on Linux if there is no default GDK display (#49484)
fix: return early from beep on linux if there is no default gdk display

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Noah Gregory <noahmgregory@gmail.com>
2026-01-23 11:07:47 +01:00
trop[bot]
6ce52ad792 fix: potential devtools crash on empty (#49489)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-01-22 11:59:57 -05:00
electron-roller[bot]
7784d25821 chore: bump node to v22.22.0 (39-x-y) (#49389)
* chore: bump node in DEPS to v22.22.0

* chore: update patches

* chore: fixup sandboxed pointers patch

(cherry picked from commit f52fbdbe51)

* chore: fixup v8 sandbox pointers node patch

* tls: route callback exceptions through error handlers

https://github.com/nodejs-private/node-private/pull/782
(cherry picked from commit 87bc8ebd34)

* chore:remove zero-fill sandbox patch component

xref https://github.com/electron/electron/pull/49452

* fixup! chore:remove zero-fill sandbox patch component

* test: correct conditional secure heap flags test

xref:  https://github.com/nodejs/node/pull/60385

---------

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: John Kleinschmidt <kleinschmidtorama@gmail.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
Co-authored-by: deepak1556 <hop2deep@gmail.com>
2026-01-22 09:53:44 -05:00
trop[bot]
126a32c5d2 docs: document that shell.trashItem requires backslashes (#49479)
docs: Update shell.md: Document that shell.trashItem requires backslashes

In Windows many functions relating to files (e.g. shell.openItem, the Node fs functions, as well as native Win32 APIs) will accept either type of slash / or \ as a folder separator.

shell.trashItem does not work with / as folder separator in Windows. This documentation change explains that.

See also:
https://github.com/electron/electron/issues/28831

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: sam marshall <s.marshall@open.ac.uk>
2026-01-21 14:52:13 -05:00
trop[bot]
811b1d6326 revert: use deprecated setAllowedFileTypes in macOS dialogs (#49472)
* revert: use deprectated setAllowedFileTypes in macOS dialogs

Closes https://github.com/electron/electron/issues/48191

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>

* chore: remove stray import

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-01-21 17:57:35 +01:00
trop[bot]
50fc493ae6 ci: detect patch needs update error with problem matcher (#49412)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: David Sanders <dsanders11@ucsbalum.com>
2026-01-21 12:32:04 +01:00
trop[bot]
9453e8bfe1 build: roll build-image to a82b87d (#49450)
build: roll build-image to a82b87d

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-01-19 19:01:42 +01:00
trop[bot]
a4eb213a04 fix: restore AXDocument accessibility attribute for representedFilename on macOS (#49418)
Starting from Chromium 134.0.6989.0 (Electron 35.0.0-beta.5), the
NativeWidgetMacNSWindow class overrides accessibilityDocument to return
the web content URL from the accessibility tree, but doesn't fall back
to NSWindow's default behavior when that URL is empty.

This broke Electron's setRepresentedFilename() API - the file path was
still set on the NSWindow, but no longer exposed via the AXDocument
accessibility attribute that screen readers use.

This fix adds an accessibilityDocument override in ElectronNSWindow that
checks representedFilename first, falling back to Chromium's behavior
for web content URLs.

Fixes: https://github.com/electron/electron/issues/XXXXX

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Daniel Gräfe <Daniel.Alm@ForumD.net>
2026-01-19 15:02:51 +01:00
trop[bot]
81c08e80f6 docs: fix webContents.hostWebContents types (#49445)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Niklas Wenzel <dev@nikwen.de>
2026-01-19 10:58:28 +01:00
trop[bot]
5f630c7de7 fix: try clearing InspectableWebContents delegate earlier (#49423)
fix: try clearing InspectableWebContents delegate earlier

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-01-18 09:52:20 +01:00
trop[bot]
2dc82ea1f3 fix: make toplevel icon Wayland protocol work (#49415)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Alex Schwartz <alexschwartz01@gmail.com>
2026-01-16 11:04:40 -05:00
Shelley Vohr
f57d6f92b6 feat: support WebSocket authentication handling (#49065)
* feat: support WebSocket authentication handling

* chore: make linter happy

---------

Co-authored-by: Charles Kerr <charles@charleskerr.com>
2026-01-13 08:52:14 -05:00
Robo
744142fe54 fix: reduce stack memory consumption in BytecodeGenerator (#49360)
Reduce stack memory consumption in BytecodeGenerator

Backports

1) https://chromium-review.googlesource.com/c/v8/v8/+/7180480
2) https://chromium-review.googlesource.com/c/v8/v8/+/7160576
3) https://chromium-review.googlesource.com/c/v8/v8/+/7062734

2 and 3 are needed to cleanly land 1. However, most of the code
changes are noop since v8_flags.proto_assign_seq_opt is experimental
and disabled by default for feature. The reason why stack memory
consumption is improved for all scenarios can be found in
https://github.com/microsoft/vscode/issues/283403#issuecomment-3737968271
2026-01-13 19:04:51 +09:00
trop[bot]
b200b8d6c0 build: roll build-tools SHA to 4430e4a (#49367)
build: roll build-tools SHA to 4430e4a

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-01-12 15:44:02 -05:00
electron-roller[bot]
cdaf0e96b6 chore: bump chromium to 142.0.7444.265 (39-x-y) (#49322)
chore: bump chromium in DEPS to 142.0.7444.265

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
2026-01-12 11:29:53 +01:00
trop[bot]
981df181c1 chore: improvements to script/run-clang-tidy.ts (#49341)
* chore: disable color output for clang-tidy in CI

Co-authored-by: David Sanders <dsanders11@ucsbalum.com>

* chore: small QoL improvements to run-clang-tidy.ts

Co-authored-by: David Sanders <dsanders11@ucsbalum.com>

* chore: add --fix option to script/run-clang-tidy.ts

Co-authored-by: David Sanders <dsanders11@ucsbalum.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: David Sanders <dsanders11@ucsbalum.com>
2026-01-09 14:43:00 -06:00
John Kleinschmidt
218300e57f build: use @electron-ci/dev-root for package.json default (#49319)
* build: use @electron-ci/dev-root for package.json default (#49154)

(cherry picked from commit bab6bd3dae)

* fxiup
2026-01-07 09:48:08 -05:00
Charles Kerr
6ccee512e4 chore: remove patches/v8/cherry-pick-e0052e7af9c9 (#49309)
chore: remove patches/v8/cherry-pick-e0052e7af9c9.patch

included in latest roll of upstream w/v8 14.2.231.22
2026-01-06 12:43:37 -06:00
trop[bot]
b6e4f514d8 docs: update roundedCorners documentation (#49310)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Niklas Wenzel <dev@nikwen.de>
2026-01-06 11:00:23 -05:00
trop[bot]
ade4c00984 fix: webRequest.onBeforeSendHeaders not being able to modify reserved headers (#49242)
* fix: `webRequest.onBeforeSendHeaders` not being able to modify reserved headers

Co-authored-by: Samuel Attard <sattard@anthropic.com>

* chore: add unit test for reserved header

Co-authored-by: Samuel Attard <sattard@anthropic.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Samuel Attard <sattard@anthropic.com>
2026-01-05 16:30:50 -05:00
trop[bot]
d8687cfc9d build: fixup release notes generation (#49304)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: John Kleinschmidt <kleinschmidtorama@gmail.com>
2026-01-05 15:56:30 -05:00
electron-roller[bot]
2ab4489447 chore: bump chromium to 142.0.7444.243 (39-x-y) (#49228)
chore: bump chromium in DEPS to 142.0.7444.243

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
2026-01-02 13:49:22 +09:00
Keeley Hammond
ab9b156113 chore: cherry-pick e0052e7af9c9 from v8 (#49287)
* chore: cherry-pick e0052e7af9c9 from v8

* chore: update patches
2025-12-31 17:02:33 +13:00
trop[bot]
35a531953b build: drop dugite as a dependency (#49205)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: John Kleinschmidt <kleinschmidtorama@gmail.com>
2025-12-15 16:39:41 -05:00
trop[bot]
4d18062d0f ci: disallow non-maintainer changes to Yarn files (#49192)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: David Sanders <dsanders11@ucsbalum.com>
2025-12-11 17:14:14 -05:00
electron-roller[bot]
832ffb2330 chore: bump chromium to 142.0.7444.235 (39-x-y) (#49190)
chore: bump chromium in DEPS to 142.0.7444.235

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
2025-12-11 15:25:12 -05:00
trop[bot]
03121eeaef build: upgrade yarn to 4.12.0 (#49179)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: John Kleinschmidt <kleinschmidtorama@gmail.com>
2025-12-10 19:20:43 -08:00
trop[bot]
8282c07a0f build: upgrade github-app-auth to 3.2.0 (#49177)
build: upgrade github-app-auth to 3.2.0 (#49152)

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
2025-12-10 14:20:43 -05:00
Niklas Wenzel
f2d1cb21b0 fix: visual artifacts while resizing on Windows (#49138)
Manual backports of:

- crrev.com/c/7129658
- crrev.com/c/7210913
- crrev.com/c/7115438
2025-12-04 16:42:24 -08:00
electron-roller[bot]
ef9b4162af chore: bump chromium to 142.0.7444.226 (39-x-y) (#49137)
* chore: bump chromium in DEPS to 142.0.7444.226

* chore: update patches

---------

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: Niklas Wenzel <dev@nikwen.de>
2025-12-03 15:50:16 -05:00
trop[bot]
6e97bca80d fix: run toast creation on background thread (#49130)
* fix: run toast creation on background thread

notes: attempts to fix app freeze when triggering notifications and the COM server in WindowsShellExperienceHost hangs

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* fix: comments

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>
2025-12-02 20:41:24 -08:00
trop[bot]
c511fc5c3f chore: reclaim macOS disk space (#49121)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
2025-12-01 15:30:07 -08:00
trop[bot]
22dfbb0822 ci: use clang problem matcher with nan spec runner (#49099)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: David Sanders <dsanders11@ucsbalum.com>
2025-12-01 09:45:35 -05:00
trop[bot]
85913a38da fix: ensure menu-did-close is emitted for application menus (#49093)
fix: ensure menu-did-close is emitted for application menus

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2025-11-26 11:47:13 -06:00
trop[bot]
a327629ca2 ci: don't build yarn modules for linux arm (#49088)
This should fix the oom errors

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
2025-11-26 10:32:30 -05:00
Niklas Wenzel
7deed2b980 fix: reduce visual artifacts while resizing on Windows (#49076) 2025-11-26 10:23:52 +01:00
trop[bot]
65fc06a9f7 chore: backport 744f40f from devtools-frontend (#49044)
* chore: backport 744f40f from devtools-frontend

Co-authored-by: deepak1556 <hop2deep@gmail.com>

* chore: update patch

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: deepak1556 <hop2deep@gmail.com>
Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
2025-11-25 12:07:04 -06:00
John Kleinschmidt
245e70aedd test: fixup test failures on linux (#49059)
* test: fixup spec runner to properly fail on linux when tests fail

* test: fixup dbus tests

* test: disable context menu spellcheck tests on linux

https://github.com/electron/electron/pull/48657 broke those tests
(cherry picked from commit cc3c999148)

* test:rebuild native modules

(cherry picked from commit bb8e2a924b)

* fix: wait for devtools blur event in focus test to avoid race condition

(cherry picked from commit 6fd2575cbc)

* fix: wait for devtools blur event in focus test to avoid race condition

(cherry picked from commit ea830139af)

---------

Co-authored-by: Alice Zhao <alicelovescake@anthropic.com>
2025-11-24 17:02:14 -05:00
trop[bot]
2a8164f499 fix: exception when reading system certificates via nodejs (#49042)
* fix: exception when reading system certificates via nodejs

Co-authored-by: deepak1556 <hop2deep@gmail.com>

* fixup! fix: exception when reading system certificates via nodejs

chore: fix trop patch shear

* chore: update patches

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: deepak1556 <hop2deep@gmail.com>
Co-authored-by: Charles Kerr <charles@charleskerr.com>
2025-11-24 14:47:53 -06:00
trop[bot]
2f7024dbcc docs: update linux build instructions (#49060)
* docs: update linux build instructions

Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>

* Update docs/development/build-instructions-linux.md

Co-authored-by: Erick Zhao <ezhao@slack-corp.com>

Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>

* Update docs/development/build-instructions-linux.md

Co-authored-by: Erick Zhao <ezhao@slack-corp.com>

Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
2025-11-24 13:50:33 -06:00
trop[bot]
d53d3bb99e docs: explain how to create transparent window using BaseWindow (#49051)
docs: clarify how to create transparent BaseWindow

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: teoyuqi <83915879+teoyuqi@users.noreply.github.com>
2025-11-24 09:41:48 +01:00
trop[bot]
c2c1d40294 fix: only call popup closecallback for top-level menu (#49045)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2025-11-23 21:35:47 +01:00
electron-roller[bot]
0e9decd459 chore: bump chromium to 142.0.7444.177 (39-x-y) (#49037)
chore: bump chromium in DEPS to 142.0.7444.177

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
2025-11-21 10:21:05 -05:00
John Kleinschmidt
b2e73d28e2 build: update to yarn v4 (#48994)
* build: update to yarn v4

(cherry picked from commit 6adec744f3)

* chore: fixup types after yarn v4 migration

* chore: update nan yarn.lock patch

* build: automatically install git for dugite
2025-11-19 17:32:30 -05:00
trop[bot]
aeb5af803f fix: abort more descriptively for beforeunload (#49011)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2025-11-19 17:00:51 -05:00
trop[bot]
53819a8a2a fix: revert the parent window remained interactive after the modal window was opened (#49019)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: bill.shen <15865969+cucbin@users.noreply.github.com>
2025-11-19 16:56:33 -05:00
159 changed files with 23892 additions and 11878 deletions

View File

@@ -2,7 +2,7 @@ version: '3'
services:
buildtools:
image: ghcr.io/electron/devcontainer:933c7d6ff6802706875270bec2e3c891cf8add3f
image: ghcr.io/electron/devcontainer:a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb
volumes:
- ..:/workspaces/gclient/src/electron:cached

View File

@@ -184,7 +184,7 @@ runs:
shell: bash
run: |
cd src/electron
node script/yarn create-typescript-definitions
node script/yarn.js create-typescript-definitions
- name: Publish Electron Dist ${{ inputs.step-suffix }}
if: ${{ inputs.is-release == 'true' }}
shell: bash

View File

@@ -143,16 +143,17 @@ runs:
echo "No changes to patches detected"
fi
fi
- name: Remove patch conflict problem matcher
- name: Remove patch conflict problem matchers
shell: bash
run: |
echo "::remove-matcher owner=merge-conflict::"
echo "::remove-matcher owner=patch-conflict::"
echo "::remove-matcher owner=patch-needs-update::"
- name: Upload patches stats
if: ${{ inputs.target-platform == 'linux' && github.ref == 'refs/heads/main' }}
shell: bash
run: |
npx node src/electron/script/patches-stats.mjs --upload-stats || true
node src/electron/script/patches-stats.mjs --upload-stats || true
# delete all .git directories under src/ except for
# third_party/angle/ and third_party/dawn/ because of build time generation of files
# gen/angle/commit.h depends on third_party/angle/.git/HEAD

View File

@@ -64,15 +64,24 @@ runs:
sudo rm -rf /Applications/Xcode_16.1.app
sudo rm -rf /Applications/Xcode_16.2.app
sudo rm -rf /Applications/Xcode_16.3.app
sudo rm -rf /Applications/Xcode_26*
sudo rm -rf /Applications/Google Chrome.app
sudo rm -rf /Applications/Google Chrome for Testing.app
sudo rm -rf /Applications/Firefox.app
sudo rm -rf /Applications/Firefox.app
sudo rm -rf /Applications/Microsoft Edge.app
sudo rm -rf ~/project/src/third_party/catapult/tracing/test_data
sudo rm -rf ~/project/src/third_party/angle/third_party/VK-GL-CTS
sudo rm -rf /Users/runner/Library/Android
sudo rm -rf $JAVA_HOME_11_arm64
sudo rm -rf $JAVA_HOME_17_arm64
sudo rm -rf $JAVA_HOME_21_arm64
sudo rm -rf $JAVA_HOME_25_arm64
sudo rm -rf /Users/runner/.dotnet/
sudo rm -rf /Users/runner/.rustup
# remove homebrew packages we don't need
brew uninstall -f --zap aws-sam-cli session-manager-plugin gcc gcc@13 gcc@14 llvm@18 gradle maven ant azure-cli
brew autoremove
# lipo off some huge binaries arm64 versions to save space
strip_universal_deep $(xcode-select -p)/../SharedFrameworks

View File

@@ -13,12 +13,16 @@ runs:
- name: Generating Types for SHA in ${{ inputs.sha-file }}
shell: bash
run: |
git checkout $(cat ${{ inputs.sha-file }})
rm -rf node_modules
yarn install --frozen-lockfile --ignore-scripts
export ELECTRON_DIR=$(pwd)
if [ "${{ inputs.sha-file }}" == ".dig-old" ]; then
cd /tmp
git clone https://github.com/electron/electron.git
cd electron
fi
git checkout $(cat $ELECTRON_DIR/${{ inputs.sha-file }})
node script/yarn.js install --immutable
echo "#!/usr/bin/env node\nglobal.x=1" > node_modules/typescript/bin/tsc
node node_modules/.bin/electron-docs-parser --dir=./ --outDir=./ --moduleVersion=0.0.0-development
node node_modules/.bin/electron-typescript-definitions --api=electron-api.json --outDir=artifacts
mv artifacts/electron.d.ts artifacts/${{ inputs.filename }}
git checkout .
mv artifacts/electron.d.ts $ELECTRON_DIR/artifacts/${{ inputs.filename }}
working-directory: ./electron

View File

@@ -15,7 +15,7 @@ runs:
git config --global core.preloadindex true
git config --global core.longpaths true
fi
export BUILD_TOOLS_SHA=a5d9f9052dcc36ee88bef5c8b13acbefd87b7d8d
export BUILD_TOOLS_SHA=4430e4a505e0f4fa2a41b707a10a36f780bbdd26
npm i -g @electron/build-tools
# Update depot_tools to ensure python
e d update_depot_tools

View File

@@ -6,7 +6,7 @@ runs:
- name: Get yarn cache directory path
shell: bash
id: yarn-cache-dir-path
run: echo "dir=$(node src/electron/script/yarn cache dir)" >> $GITHUB_OUTPUT
run: echo "dir=$(node src/electron/script/yarn.js config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
id: yarn-cache
with:
@@ -18,4 +18,14 @@ runs:
shell: bash
run: |
cd src/electron
node script/yarn install --frozen-lockfile --prefer-offline
if [ "$TARGET_ARCH" = "x86" ]; then
export npm_config_arch="ia32"
fi
# if running on linux arm skip yarn Builds
ARCH=$(uname -m)
if [ "$ARCH" = "armv7l" ]; then
echo "Skipping yarn build on linux arm"
node script/yarn.js install --immutable --mode=skip-build
else
node script/yarn.js install --immutable
fi

View File

@@ -19,6 +19,16 @@
"line": 3
}
]
},
{
"owner": "patch-needs-update",
"pattern": [
{
"regexp": "^((patches\/.*): needs update)$",
"message": 1,
"file": 2
}
]
}
]
}

72
.github/workflows/apply-patches.yml vendored Normal file
View File

@@ -0,0 +1,72 @@
name: Apply Patches
on:
pull_request:
permissions: {}
concurrency:
group: apply-patches-${{ github.ref }}
cancel-in-progress: true
jobs:
setup:
if: github.repository == 'electron/electron'
runs-on: ubuntu-slim
permissions:
contents: read
pull-requests: read
outputs:
has-patches: ${{ steps.filter.outputs.patches }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
persist-credentials: false
ref: ${{ github.event.pull_request.head.sha }}
# Use dorny/paths-filter instead of the path filter under the on: pull_request: block
# so that the output can be used to conditionally run the apply-patches job, which lets
# the job be marked as a required status check (conditional skip counts as a success).
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: filter
with:
filters: |
patches:
- DEPS
- 'patches/**'
apply-patches:
needs: setup
if: ${{ needs.setup.outputs.has-patches == 'true' }}
runs-on: electron-arc-centralus-linux-amd64-32core
permissions:
contents: read
container:
image: ghcr.io/electron/build:a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb
options: --user root
volumes:
- /mnt/cross-instance-cache:/mnt/cross-instance-cache
- /var/run/sas:/var/run/sas
env:
CHROMIUM_GIT_COOKIE: ${{ secrets.CHROMIUM_GIT_COOKIE }}
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_arm=True --custom-var=checkout_arm64=True'
steps:
- name: Checkout Electron
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
path: src/electron
fetch-depth: 0
persist-credentials: false
ref: ${{ github.event.pull_request.head.sha }}
- name: Rebase onto Base Branch
working-directory: src/electron
env:
BASE_REF: ${{ github.event.pull_request.base.ref }}
run: |
git config user.email "electron@github.com"
git config user.name "Electron Bot"
git fetch origin ${BASE_REF}
git rebase origin/${BASE_REF}
- name: Checkout & Sync & Save
uses: ./src/electron/.github/actions/checkout
with:
target-platform: linux

View File

@@ -14,7 +14,7 @@ jobs:
permissions:
contents: read
container:
image: ghcr.io/electron/build:bc2f48b2415a670de18d13605b1cf0eb5fdbaae1
image: ghcr.io/electron/build:a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb
options: --user root
volumes:
- /mnt/cross-instance-cache:/mnt/cross-instance-cache
@@ -37,7 +37,7 @@ jobs:
permissions:
contents: read
container:
image: ghcr.io/electron/build:bc2f48b2415a670de18d13605b1cf0eb5fdbaae1
image: ghcr.io/electron/build:a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb
options: --user root --device /dev/fuse --cap-add SYS_ADMIN
volumes:
- /mnt/win-cache:/mnt/win-cache
@@ -63,7 +63,7 @@ jobs:
# This job updates the same git cache as linux, so it needs to run after the linux one.
needs: build-git-cache-linux
container:
image: ghcr.io/electron/build:bc2f48b2415a670de18d13605b1cf0eb5fdbaae1
image: ghcr.io/electron/build:a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb
options: --user root
volumes:
- /mnt/cross-instance-cache:/mnt/cross-instance-cache

View File

@@ -6,7 +6,7 @@ on:
build-image-sha:
type: string
description: 'SHA for electron/build image'
default: '933c7d6ff6802706875270bec2e3c891cf8add3f'
default: 'a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb'
required: true
skip-macos:
type: boolean
@@ -76,7 +76,7 @@ jobs:
id: set-output
run: |
if [ -z "${{ inputs.build-image-sha }}" ]; then
echo "build-image-sha=933c7d6ff6802706875270bec2e3c891cf8add3f" >> "$GITHUB_OUTPUT"
echo "build-image-sha=a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb" >> "$GITHUB_OUTPUT"
else
echo "build-image-sha=${{ inputs.build-image-sha }}" >> "$GITHUB_OUTPUT"
fi
@@ -333,7 +333,7 @@ jobs:
build-runs-on: electron-arc-centralus-linux-amd64-32core
test-runs-on: electron-arc-centralus-linux-arm64-4core
build-container: '{"image":"ghcr.io/electron/build:${{ needs.checkout-linux.outputs.build-image-sha }}","options":"--user root","volumes":["/mnt/cross-instance-cache:/mnt/cross-instance-cache"]}'
test-container: '{"image":"ghcr.io/electron/test:arm32v7-${{ needs.checkout-linux.outputs.build-image-sha }}","options":"--user root --privileged --init","volumes":["/home/runner/externals:/mnt/runner-externals"]}'
test-container: '{"image":"ghcr.io/electron/test:arm32v7-${{ needs.checkout-linux.outputs.build-image-sha }}","options":"--user root --privileged --init --memory=12g","volumes":["/home/runner/externals:/mnt/runner-externals"]}'
target-platform: linux
target-arch: arm
is-release: false

View File

@@ -6,7 +6,7 @@ on:
build-image-sha:
type: string
description: 'SHA for electron/build image'
default: '933c7d6ff6802706875270bec2e3c891cf8add3f'
default: 'a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb'
upload-to-storage:
description: 'Uploads to Azure storage'
required: false

View File

@@ -6,7 +6,7 @@ on:
build-image-sha:
type: string
description: 'SHA for electron/build image'
default: '933c7d6ff6802706875270bec2e3c891cf8add3f'
default: 'a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb'
required: true
upload-to-storage:
description: 'Uploads to Azure storage'

View File

@@ -7,6 +7,8 @@ on:
- 'spec/yarn.lock'
- '.github/workflows/**'
- '.github/actions/**'
- '.yarn/**'
- '.yarnrc.yml'
permissions: {}

View File

@@ -54,12 +54,12 @@ jobs:
shell: bash
run: |
cd src/electron
node script/yarn create-typescript-definitions
node script/yarn tsc -p tsconfig.default_app.json --noEmit
node script/yarn.js create-typescript-definitions
node script/yarn.js tsc -p tsconfig.default_app.json --noEmit
for f in build/webpack/*.js
do
out="${f:29}"
if [ "$out" != "base.js" ]; then
node script/yarn webpack --config $f --output-filename=$out --output-path=./.tmp --env mode=development
node script/yarn.js webpack --config $f --output-filename=$out --output-path=./.tmp --env mode=development
fi
done

View File

@@ -78,11 +78,11 @@ jobs:
# but then we would lint its contents (at least gn format), and it doesn't pass it.
cd src/electron
node script/yarn install --frozen-lockfile
node script/yarn lint
node script/yarn.js install --immutable
node script/yarn.js lint
- name: Run Script Typechecker
shell: bash
run: |
cd src/electron
node script/yarn tsc -p tsconfig.script.json
node script/yarn.js tsc -p tsconfig.script.json

View File

@@ -196,10 +196,7 @@ jobs:
# sudo security authorizationdb write com.apple.trust-settings.admin allow
# cd src/electron
# ./script/codesign/generate-identity.sh
- name: Install Datadog CLI
run: |
cd src/electron
node script/yarn global add @datadog/datadog-ci
- name: Run Electron Tests
shell: bash
env:
@@ -225,7 +222,7 @@ jobs:
export ELECTRON_FORCE_TEST_SUITE_EXIT="true"
fi
fi
node script/yarn test --runners=main --enableRerun=3 --trace-uncaught --enable-logging --files $tests_files
node script/yarn.js test --runners=main --enableRerun=3 --trace-uncaught --enable-logging --files $tests_files
else
chown :builduser .. && chmod g+w ..
chown -R :builduser . && chmod -R g+w .
@@ -242,9 +239,14 @@ jobs:
export MOCHA_TIMEOUT=180000
echo "Piping output to ASAN_SYMBOLIZE ($ASAN_SYMBOLIZE)"
cd electron
runuser -u builduser -- xvfb-run script/actions/run-tests.sh script/yarn test --runners=main --trace-uncaught --enable-logging --files $tests_files | $ASAN_SYMBOLIZE
runuser -u builduser -- xvfb-run script/actions/run-tests.sh script/yarn.js test --runners=main --trace-uncaught --enable-logging --files $tests_files | $ASAN_SYMBOLIZE
else
runuser -u builduser -- xvfb-run script/actions/run-tests.sh script/yarn test --runners=main --trace-uncaught --enable-logging --files $tests_files
if [ "${{ inputs.target-arch }}" = "arm" ]; then
runuser -u builduser -- xvfb-run script/actions/run-tests.sh script/yarn.js test --skipYarnInstall --runners=main --enableRerun=3 --trace-uncaught --enable-logging --files $tests_files
else
runuser -u builduser -- xvfb-run script/actions/run-tests.sh script/yarn.js test --runners=main --enableRerun=3 --trace-uncaught --enable-logging --files $tests_files
fi
fi
fi
- name: Upload Test results to Datadog
@@ -256,9 +258,10 @@ jobs:
DD_TAGS: "os.architecture:${{ inputs.target-arch }},os.family:${{ inputs.target-platform }},os.platform:${{ inputs.target-platform }},asan:${{ inputs.is-asan }}"
run: |
if ! [ -z $DD_API_KEY ] && [ -f src/electron/junit/test-results-main.xml ]; then
export DATADOG_PATH=`node src/electron/script/yarn global bin`
$DATADOG_PATH/datadog-ci junit upload src/electron/junit/test-results-main.xml
fi
cd src/electron
export DATADOG_PATH=`node script/yarn.js bin datadog-ci`
$DATADOG_PATH junit upload junit/test-results-main.xml
fi
if: always() && !cancelled()
- name: Upload Test Artifacts
if: always() && !cancelled()

View File

@@ -138,10 +138,16 @@ jobs:
unzip -:o dist.zip
- name: Setup Linux for Headless Testing
run: sh -e /etc/init.d/xvfb start
- name: Add Clang problem matcher
shell: bash
run: echo "::add-matcher::src/electron/.github/problem-matchers/clang.json"
- name: Run Nan Tests
run: |
cd src
node electron/script/nan-spec-runner.js
- name: Remove Clang problem matcher
shell: bash
run: echo "::remove-matcher owner=clang::"
- name: Wait for active SSH sessions
shell: bash
if: always() && !cancelled()

View File

@@ -0,0 +1,71 @@
name: Rerun PR Apply Patches
on:
push:
branches:
- main
- '[1-9][0-9]-x-y'
paths:
- 'DEPS'
- 'patches/**'
permissions: {}
jobs:
rerun-apply-patches:
runs-on: ubuntu-latest
permissions:
actions: write
checks: read
contents: read
pull-requests: read
steps:
- name: Find PRs and Rerun Apply Patches
env:
GH_REPO: ${{ github.repository }}
GH_TOKEN: ${{ github.token }}
run: |
BRANCH="${GITHUB_REF#refs/heads/}"
# Find all open PRs targeting this branch
PRS=$(gh pr list --base "$BRANCH" --state open --limit 250 --json number)
echo "$PRS" | jq -c '.[]' | while read -r pr; do
PR_NUMBER=$(echo "$pr" | jq -r '.number')
echo "Processing PR #${PR_NUMBER}"
# Find the Apply Patches workflow check for this PR
CHECK=$(gh pr checks "$PR_NUMBER" --json link,name,state,workflow --jq '[.[] | select(.workflow == "Apply Patches" and .name == "apply-patches")] | first')
if [ -z "$CHECK" ] || [ "$CHECK" = "null" ]; then
echo " No Apply Patches workflow found for PR #${PR_NUMBER}"
continue
fi
STATE=$(echo "$CHECK" | jq -r '.state')
if [ "$STATE" = "SKIPPED" ]; then
echo " apply-patches job was skipped for PR #${PR_NUMBER} (no patches)"
continue
fi
LINK=$(echo "$CHECK" | jq -r '.link')
# Extract the run ID from the link (format: .../runs/RUN_ID/job/JOB_ID)
RUN_ID=$(echo "$LINK" | grep -oE 'runs/[0-9]+' | cut -d'/' -f2)
if [ -z "$RUN_ID" ]; then
echo " Could not extract run ID from link: ${LINK}"
continue
fi
# Check if the workflow is currently in progress
RUN_STATUS=$(gh run view "$RUN_ID" --json status --jq '.status')
if [ "$RUN_STATUS" = "in_progress" ] || [ "$RUN_STATUS" = "queued" ] || [ "$RUN_STATUS" = "waiting" ]; then
echo " Workflow run ${RUN_ID} is ${RUN_STATUS}, cancelling..."
gh run cancel "$RUN_ID" --force
gh run watch "$RUN_ID"
fi
gh run rerun "$RUN_ID"
done

View File

@@ -6,7 +6,7 @@ on:
build-image-sha:
type: string
description: 'SHA for electron/build image'
default: '933c7d6ff6802706875270bec2e3c891cf8add3f'
default: 'a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb'
required: true
upload-to-storage:
description: 'Uploads to Azure storage'

2
.gitignore vendored
View File

@@ -53,3 +53,5 @@ ts-gen
patches/mtime-cache.json
spec/fixtures/logo.png
.yarn/install-state.gz

942
.yarn/releases/yarn-4.12.0.cjs vendored Executable file

File diff suppressed because one or more lines are too long

12
.yarnrc.yml Normal file
View File

@@ -0,0 +1,12 @@
enableScripts: false
nmHoistingLimits: workspaces
nodeLinker: node-modules
npmMinimalAgeGate: 10080
npmPreapprovedPackages:
- "@electron/*"
yarnPath: .yarn/releases/yarn-4.12.0.cjs

View File

@@ -420,6 +420,37 @@ action("electron_generate_node_defines") {
args = [ rebase_path(target_gen_dir) ] + rebase_path(inputs)
}
# MSIX updater needs to be in a separate source_set because it uses C++/WinRT
# headers that require exceptions to be enabled.
source_set("electron_msix_updater") {
sources = [
"shell/browser/api/electron_api_msix_updater.cc",
"shell/browser/api/electron_api_msix_updater.h",
]
configs += [ "//third_party/electron_node:node_external_config" ]
public_configs = [ ":electron_lib_config" ]
if (is_win) {
cflags_cc = [
"/EHsc", # Enable C++ exceptions for C++/WinRT
"-Wno-c++98-compat-extra-semi", #Suppress C++98 compatibility warnings
]
include_dirs = [ "//third_party/nearby/src/internal/platform/implementation/windows/generated" ]
}
deps = [
"//base",
"//content/public/browser",
"//gin",
"//third_party/electron_node/deps/simdjson",
"//third_party/electron_node/deps/uv",
"//v8",
]
}
source_set("electron_lib") {
configs += [
"//v8:external_startup_data",
@@ -435,6 +466,7 @@ source_set("electron_lib") {
":electron_fuses",
":electron_generate_node_defines",
":electron_js2c",
":electron_msix_updater",
":electron_version_header",
":resources",
"buildflags",

9
DEPS
View File

@@ -2,9 +2,9 @@ gclient_gn_args_from = 'src'
vars = {
'chromium_version':
'142.0.7444.175',
'142.0.7444.265',
'node_version':
'v22.21.1',
'v22.22.0',
'nan_version':
'e14bdcd1f72d62bca1d541b66da43130384ec213',
'squirrel.mac_version':
@@ -30,9 +30,6 @@ vars = {
# The path of the sysroots.json file.
'sysroots_json_path': 'electron/script/sysroots.json',
# KEEP IN SYNC WITH utils.js FILE
'yarn_version': '1.22.22',
# To be able to build clean Chromium from sources.
'apply_patches': True,
@@ -155,7 +152,7 @@ hooks = [
'action': [
'python3',
'-c',
'import os, subprocess; os.chdir(os.path.join("src", "electron")); subprocess.check_call(["python3", "script/lib/npx.py", "yarn@' + (Var("yarn_version")) + '", "install", "--frozen-lockfile"]);',
'import os, subprocess; os.chdir(os.path.join("src", "electron")); subprocess.check_call(["node", ".yarn/releases/yarn-4.12.0.cjs", "install", "--immutable"]);',
],
},
{

View File

@@ -32,9 +32,19 @@ update process. Apps that need to disable ATS can add the
### Windows
On Windows, you have to install your app into a user's machine before you can
use the `autoUpdater`, so it is recommended that you use
[electron-winstaller][installer-lib] or [Electron Forge's Squirrel.Windows maker][electron-forge-lib] to generate a Windows installer.
On Windows, the `autoUpdater` module automatically selects the appropriate update mechanism
based on how your app is packaged:
* **MSIX packages**: If your app is running as an MSIX package (created with [electron-windows-msix][msix-lib] and detected via [`process.windowsStore`](process.md#processwindowsstore-readonly)),
the module uses the MSIX updater, which supports direct MSIX file links and JSON update feeds.
* **Squirrel.Windows**: For apps installed via traditional installers (created with
[electron-winstaller][installer-lib] or [Electron Forge's Squirrel.Windows maker][electron-forge-lib]),
the module uses Squirrel.Windows for updates.
You don't need to configure which updater to use; Electron automatically detects the packaging
format and uses the appropriate one.
#### Squirrel.Windows
Apps built with Squirrel.Windows will trigger [custom launch events](https://github.com/Squirrel/Squirrel.Windows/blob/51f5e2cb01add79280a53d51e8d0cfa20f8c9f9f/docs/using/custom-squirrel-events-non-cs.md#application-startup-commands)
that must be handled by your Electron application to ensure proper setup and teardown.
@@ -55,6 +65,14 @@ The installer generated with Squirrel.Windows will create a shortcut icon with a
same ID for your app with `app.setAppUserModelId` API, otherwise Windows will
not be able to pin your app properly in task bar.
#### MSIX Packages
When your app is packaged as an MSIX, the `autoUpdater` module provides additional
functionality:
* Use the `allowAnyVersion` option in `setFeedURL()` to allow updates to older versions (downgrades)
* Support for direct MSIX file links or JSON update feeds (similar to Squirrel.Mac format)
## Events
The `autoUpdater` object emits the following events:
@@ -92,7 +110,7 @@ Returns:
Emitted when an update has been downloaded.
On Windows only `releaseName` is available.
With Squirrel.Windows only `releaseName` is available.
> [!NOTE]
> It is not strictly necessary to handle this event. A successfully
@@ -111,10 +129,12 @@ The `autoUpdater` object has the following methods:
### `autoUpdater.setFeedURL(options)`
* `options` Object
* `url` string
* `url` string - The update server URL. For _Windows_ MSIX, this can be either a direct link to an MSIX file (e.g., `https://example.com/update.msix`) or a JSON endpoint that returns update information (see the [Squirrel.Mac][squirrel-mac] README for more information).
* `headers` Record\<string, string\> (optional) _macOS_ - HTTP request headers.
* `serverType` string (optional) _macOS_ - Can be `json` or `default`, see the [Squirrel.Mac][squirrel-mac]
README for more information.
* `allowAnyVersion` boolean (optional) _Windows_ - If `true`, allows downgrades to older versions for MSIX packages.
Defaults to `false`.
Sets the `url` and initialize the auto updater.
@@ -151,3 +171,4 @@ closed.
[electron-forge-lib]: https://www.electronforge.io/config/makers/squirrel.windows
[app-user-model-id]: https://learn.microsoft.com/en-us/windows/win32/shell/appids
[event-emitter]: https://nodejs.org/api/events.html#events_class_eventemitter
[msix-lib]: https://github.com/electron-userland/electron-windows-msix

View File

@@ -1252,7 +1252,8 @@ Captures a snapshot of the page within `rect`. Omitting `rect` will capture the
Returns `Promise<void>` - the promise will resolve when the page has finished loading
(see [`did-finish-load`](web-contents.md#event-did-finish-load)), and rejects
if the page fails to load (see [`did-fail-load`](web-contents.md#event-did-fail-load)).
if the page fails to load (see
[`did-fail-load`](web-contents.md#event-did-fail-load)). A noop rejection handler is already attached, which avoids unhandled rejection errors. If the existing page has a beforeUnload handler, [`did-fail-load`](web-contents.md#event-did-fail-load) will be called unless [`will-prevent-unload`](web-contents.md#event-did-fail-load) is handled.
Same as [`webContents.loadURL(url[, options])`](web-contents.md#contentsloadurlurl-options).

View File

@@ -159,6 +159,22 @@ Notification activated (com.github.Electron:notification:EAF7B87C-A113-43D7-8E76
Notification replied to (com.github.Electron:notification:EAF7B87C-A113-43D7-8E76-F88EC9D73D44)
```
### `ELECTRON_DEBUG_MSIX_UPDATER`
Adds extra logs to MSIX updater operations on Windows to aid in debugging. Extra logging will be displayed when MSIX update operations are initiated, including package updates, package registration, and restart registration. This helps diagnose issues with MSIX package updates and deployments.
Sample output:
```sh
UpdateMsix called with URI: https://example.com/app.msix
DoUpdateMsix: Starting
Calling AddPackageByUriAsync... URI: https://example.com/app.msix
Update options - deferRegistration: true, developerMode: false, forceShutdown: false, forceTargetShutdown: false, forceUpdateFromAnyVersion: false
Waiting for deployment...
Deployment finished.
MSIX Deployment completed.
```
### `ELECTRON_LOG_ASAR_READS`
When Electron reads from an ASAR file, log the read offset and file path to

View File

@@ -71,7 +71,7 @@ will disable the support for `asar` archives in Node's built-in modules.
### `process.noDeprecation`
A `boolean` that controls whether or not deprecation warnings are printed to `stderr`.
A `boolean` (optional) that controls whether or not deprecation warnings are printed to `stderr`.
Setting this to `true` will silence deprecation warnings. This property is used
instead of the `--no-deprecation` command line flag.
@@ -128,8 +128,8 @@ A `string` representing Electron's version string.
### `process.windowsStore` _Readonly_
A `boolean`. If the app is running as a Windows Store app (appx), this property is `true`,
for otherwise it is `undefined`.
A `boolean`. If the app is running as an MSIX package (including AppX for Windows Store),
this property is `true`, otherwise it is `undefined`.
### `process.contextId` _Readonly_

View File

@@ -58,6 +58,10 @@ Rejects if there was an error while deleting the requested item.
This moves a path to the OS-specific trash location (Trash on macOS, Recycle
Bin on Windows, and a desktop-environment-specific location on Linux).
The path must use the default path separator for the platform (backslash on
Windows). Use `path.resolve()` from the `node:path` module to ensure correct
handling on all filesystems.
### `shell.beep()`
Play the beep sound.

View File

@@ -72,6 +72,9 @@
some GTK+3 desktop environments. Default is `false`.
* `transparent` boolean (optional) - Makes the window [transparent](../../tutorial/custom-window-styles.md#transparent-windows).
Default is `false`. On Windows, does not work unless the window is frameless.
When you add a [`View`](../view.md) to a `BaseWindow`, you'll need to call
[`view.setBackgroundColor`](../view.md#viewsetbackgroundcolorcolor) with a transparent
background color on that view to make its background transparent as well.
* `type` string (optional) - The type of window, default is normal window. See more about
this below.
* `visualEffectState` string (optional) _macOS_ - Specify how the material
@@ -99,9 +102,9 @@
* `trafficLightPosition` [Point](point.md) (optional) _macOS_ -
Set a custom position for the traffic light buttons in frameless windows.
* `roundedCorners` boolean (optional) _macOS_ _Windows_ - Whether frameless window
should have rounded corners. Default is `true`. Setting this property
to `false` will prevent the window from being fullscreenable on macOS.
On Windows versions older than Windows 11 Build 22000 this property has no effect, and frameless windows will not have rounded corners.
should have rounded corners. Default is `true`. On Windows versions older than
Windows 11 Build 22000 this property has no effect, and frameless windows will
not have rounded corners.
* `thickFrame` boolean (optional) _Windows_ - Use `WS_THICKFRAME` style for
frameless windows on Windows, which adds the standard window frame. Setting it
to `false` will remove window shadow and window animations, and disable window

View File

@@ -1079,7 +1079,7 @@ Emitted when the [mainFrame](web-contents.md#contentsmainframe-readonly), an `<i
Returns `Promise<void>` - the promise will resolve when the page has finished loading
(see [`did-finish-load`](web-contents.md#event-did-finish-load)), and rejects
if the page fails to load (see
[`did-fail-load`](web-contents.md#event-did-fail-load)). A noop rejection handler is already attached, which avoids unhandled rejection errors.
[`did-fail-load`](web-contents.md#event-did-fail-load)). A noop rejection handler is already attached, which avoids unhandled rejection errors. If the existing page has a beforeUnload handler, [`did-fail-load`](web-contents.md#event-did-fail-load) will be called unless [`will-prevent-unload`](web-contents.md#event-did-fail-load) is handled.
Loads the `url` in the window. The `url` must contain the protocol prefix,
e.g. the `http://` or `file://`. If the load should bypass http cache then
@@ -2410,7 +2410,8 @@ A [`NavigationHistory`](navigation-history.md) used by this webContents.
#### `contents.hostWebContents` _Readonly_
A [`WebContents`](web-contents.md) instance that might own this `WebContents`.
A `WebContents | null` property that represents a [`WebContents`](web-contents.md)
instance that might own this `WebContents`.
#### `contents.devToolsWebContents` _Readonly_

View File

@@ -6,77 +6,17 @@ Follow the guidelines below for building **Electron itself** on Linux, for the p
## Prerequisites
* At least 25GB disk space and 8GB RAM.
* Python >= 3.9.
* [Node.js](https://nodejs.org/download/) >= 22.12.0
* [clang](https://clang.llvm.org/get_started.html) 3.4 or later.
* Development headers of GTK 3 and libnotify.
Due to Electron's dependency on Chromium, prerequisites and dependencies for Electron change over time. [Chromium's documentation on building on Linux](https://chromium.googlesource.com/chromium/src/+/HEAD/docs/linux/build_instructions.md) has up to date information for building Chromium on Linux. This documentation can generally
be followed for building Electron on Linux as well.
On Ubuntu >= 20.04, install the following libraries:
```sh
$ sudo apt-get install build-essential clang libdbus-1-dev libgtk-3-dev \
libnotify-dev libasound2-dev libcap-dev \
libcups2-dev libxtst-dev \
libxss1 libnss3-dev gcc-multilib g++-multilib curl \
gperf bison python3-dbusmock openjdk-8-jre
```
On Ubuntu < 20.04, install the following libraries:
```sh
$ sudo apt-get install build-essential clang libdbus-1-dev libgtk-3-dev \
libnotify-dev libgnome-keyring-dev \
libasound2-dev libcap-dev libcups2-dev libxtst-dev \
libxss1 libnss3-dev gcc-multilib g++-multilib curl \
gperf bison python-dbusmock openjdk-8-jre
```
On RHEL / CentOS, install the following libraries:
```sh
$ sudo yum install clang dbus-devel gtk3-devel libnotify-devel \
libgnome-keyring-devel xorg-x11-server-utils libcap-devel \
cups-devel libXtst-devel alsa-lib-devel libXrandr-devel \
nss-devel python-dbusmock openjdk-8-jre
```
On Fedora, install the following libraries:
```sh
$ sudo dnf install clang dbus-devel gperf gtk3-devel \
libnotify-devel libgnome-keyring-devel libcap-devel \
cups-devel libXtst-devel alsa-lib-devel libXrandr-devel \
nss-devel python-dbusmock
```
On Arch Linux / Manjaro, install the following libraries:
```sh
$ sudo pacman -Syu base-devel clang libdbus gtk2 libnotify \
libgnome-keyring alsa-lib libcap libcups libxtst \
libxss nss gcc-multilib curl gperf bison \
python2 python-dbusmock jdk8-openjdk
```
Other distributions may offer similar packages for installation via package
managers such as pacman. Or one can compile from source code.
Additionally, Electron's [Linux dependency installer](https://github.com/electron/build-images/blob/main/tools/install-deps.sh) can be referenced to get the current dependencies that Electron requires in addition to what Chromium installs via [build/install-deps.sh](https://chromium.googlesource.com/chromium/src/+/HEAD/build/install-build-deps.sh).
### Cross compilation
If you want to build for an `arm` target you should also install the following
dependencies:
If you want to build for an `arm` target, you can use Electron's [Linux dependency installer](https://github.com/electron/build-images/blob/main/tools/install-deps.sh) to install the additional dependencies by passing the `--arm argument`:
```sh
$ sudo apt-get install libc6-dev-armhf-cross linux-libc-dev-armhf-cross \
g++-arm-linux-gnueabihf
```
Similarly for `arm64`, install the following:
```sh
$ sudo apt-get install libc6-dev-arm64-cross linux-libc-dev-arm64-cross \
g++-aarch64-linux-gnu
$ sudo install-deps.sh --arm
```
And to cross-compile for `arm` or targets, you should pass the

View File

@@ -222,8 +222,10 @@ auto_filenames = {
browser_bundle_deps = [
"lib/browser/api/app.ts",
"lib/browser/api/auto-updater.ts",
"lib/browser/api/auto-updater/auto-updater-msix.ts",
"lib/browser/api/auto-updater/auto-updater-native.ts",
"lib/browser/api/auto-updater/auto-updater-win.ts",
"lib/browser/api/auto-updater/msix-update-win.ts",
"lib/browser/api/auto-updater/squirrel-update-win.ts",
"lib/browser/api/base-window.ts",
"lib/browser/api/browser-view.ts",

View File

@@ -587,6 +587,7 @@ filenames = {
"shell/common/electron_command_line.cc",
"shell/common/electron_command_line.h",
"shell/common/electron_constants.h",
"shell/common/electron_paths.cc",
"shell/common/electron_paths.h",
"shell/common/gin_converters/accelerator_converter.cc",
"shell/common/gin_converters/accelerator_converter.h",

View File

@@ -1,5 +1,10 @@
if (process.platform === 'win32') {
module.exports = require('./auto-updater/auto-updater-win');
// windowsStore indicates whether the app is running as a packaged app (MSIX), even outside of the store
if (process.windowsStore) {
module.exports = require('./auto-updater/auto-updater-msix');
} else {
module.exports = require('./auto-updater/auto-updater-win');
}
} else {
module.exports = require('./auto-updater/auto-updater-native');
}

View File

@@ -0,0 +1,449 @@
import * as msixUpdate from '@electron/internal/browser/api/auto-updater/msix-update-win';
import { app, net } from 'electron/main';
import { EventEmitter } from 'events';
interface UpdateInfo {
ok: boolean; // False if error encountered
available?: boolean; // True if the update is available, false if not
updateUrl?: string; // The URL of the update
releaseNotes?: string; // The release notes of the update
releaseName?: string; // The release name of the update
releaseDate?: Date; // The release date of the update
}
interface MSIXPackageInfo {
id: string;
familyName: string;
developmentMode: boolean;
version: string;
signatureKind: 'developer' | 'enterprise' | 'none' | 'store' | 'system';
appInstallerUri?: string;
}
/**
* Options for updating an MSIX package.
* Used with `updateMsix()` to control how the package update behaves.
*
* These options correspond to the Windows.Management.Deployment.AddPackageOptions class properties.
*
* @see https://learn.microsoft.com/en-us/uwp/api/windows.management.deployment.addpackageoptions?view=winrt-26100
*/
export interface UpdateMsixOptions {
/**
* Gets or sets a value that indicates whether to delay registration of the main package
* or dependency packages if the packages are currently in use.
*
* Corresponds to `AddPackageOptions.DeferRegistrationWhenPackagesAreInUse`
*
* @default false
*/
deferRegistration?: boolean;
/**
* Gets or sets a value that indicates whether the app is installed in developer mode.
* When set, the app is installed in development mode which allows for a more rapid
* development cycle. The BlockMap.xml, [Content_Types].xml, and digital signature
* files are not required for app installation.
*
* Corresponds to `AddPackageOptions.DeveloperMode`
*
* @default false
*/
developerMode?: boolean;
/**
* Gets or sets a value that indicates whether the processes associated with the package
* will be shut down forcibly so that registration can continue if the package, or any
* package that depends on the package, is currently in use.
*
* Corresponds to `AddPackageOptions.ForceAppShutdown`
*
* @default false
*/
forceShutdown?: boolean;
/**
* Gets or sets a value that indicates whether the processes associated with the package
* will be shut down forcibly so that registration can continue if the package is
* currently in use.
*
* Corresponds to `AddPackageOptions.ForceTargetAppShutdown`
*
* @default false
*/
forceTargetShutdown?: boolean;
/**
* Gets or sets a value that indicates whether to force a specific version of a package
* to be added, regardless of if a higher version is already added.
*
* Corresponds to `AddPackageOptions.ForceUpdateFromAnyVersion`
*
* @default false
*/
forceUpdateFromAnyVersion?: boolean;
}
/**
* Options for registering an MSIX package.
* Used with `registerPackage()` to control how the package registration behaves.
*
* These options correspond to the Windows.Management.Deployment.DeploymentOptions enum.
*
* @see https://learn.microsoft.com/en-us/uwp/api/windows.management.deployment.deploymentoptions?view=winrt-26100
*/
interface RegisterPackageOptions {
/**
* Force shutdown of the application if it's currently running.
* If this package, or any package that depends on this package, is currently in use,
* the processes associated with the package are shut down forcibly so that registration can continue.
*
* Corresponds to `DeploymentOptions.ForceApplicationShutdown` (value: 1)
*
* @default false
*/
forceShutdown?: boolean;
/**
* Force shutdown of the target application if it's currently running.
* If this package is currently in use, the processes associated with the package
* are shut down forcibly so that registration can continue.
*
* Corresponds to `DeploymentOptions.ForceTargetApplicationShutdown` (value: 64)
*
* @default false
*/
forceTargetShutdown?: boolean;
/**
* Force a specific version of a package to be staged/registered, regardless of if
* a higher version is already staged/registered.
*
* Corresponds to `DeploymentOptions.ForceUpdateFromAnyVersion` (value: 262144)
*
* @default false
*/
forceUpdateFromAnyVersion?: boolean;
}
class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
updateAvailable: boolean = false;
updateURL: string | null = null;
updateHeaders: Record<string, string> | null = null;
allowAnyVersion: boolean = false;
// Private: Validate that the URL points to an MSIX file (following redirects)
private async validateMsixUrl (url: string): Promise<void> {
try {
// Make a HEAD request to follow redirects and get the final URL
const response = await net.fetch(url, {
method: 'HEAD',
headers: this.updateHeaders ? new Headers(this.updateHeaders) : undefined,
redirect: 'follow' // Follow redirects to get the final URL
});
// Get the final URL after redirects (response.url contains the final URL)
const finalUrl = response.url || url;
const urlObj = new URL(finalUrl);
const pathname = urlObj.pathname.toLowerCase();
// Check if final URL ends with .msix or .msixbundle extension
const hasMsixExtension = pathname.endsWith('.msix') || pathname.endsWith('.msixbundle');
if (!hasMsixExtension) {
throw new Error(`Update URL does not point to an MSIX file. Expected .msix or .msixbundle extension, got final URL: ${finalUrl}`);
}
} catch (error) {
if (error instanceof TypeError) {
throw new Error(`Invalid MSIX URL: ${url}`);
}
throw error;
}
}
// Private: Check if URL is a direct MSIX file (following redirects)
private async isDirectMsixUrl (url: string, emitError: boolean = false): Promise<boolean> {
try {
await this.validateMsixUrl(url);
return true;
} catch (error) {
if (emitError) {
this.emitError(error as Error);
}
return false;
}
}
// Supports both versioning (x.y.z) and Windows version format (x.y.z.a)
// Returns: 1 if v1 > v2, -1 if v1 < v2, 0 if v1 === v2
private compareVersions (v1: string, v2: string): number {
const parts1 = v1.split('.').map(part => {
const parsed = parseInt(part, 10);
return isNaN(parsed) ? 0 : parsed;
});
const parts2 = v2.split('.').map(part => {
const parsed = parseInt(part, 10);
return isNaN(parsed) ? 0 : parsed;
});
const maxLength = Math.max(parts1.length, parts2.length);
for (let i = 0; i < maxLength; i++) {
const part1 = parts1[i] ?? 0;
const part2 = parts2[i] ?? 0;
if (part1 > part2) return 1;
if (part1 < part2) return -1;
}
return 0;
}
// Private: Parse the static releases array format
// This is a static JSON file containing all releases
private parseStaticReleasFile (json: any, currentVersion: string): { ok: boolean; available: boolean; url?: string; name?: string; notes?: string; pub_date?: string } {
if (!Array.isArray(json.releases) || !json.currentRelease || typeof json.currentRelease !== 'string') {
this.emitError(new Error('Invalid releases format. Expected \'releases\' array and \'currentRelease\' string.'));
return { ok: false, available: false };
}
// Use currentRelease property to determine if update is available
const currentReleaseVersion = json.currentRelease;
// Compare current version with currentRelease
const versionComparison = this.compareVersions(currentReleaseVersion, currentVersion);
// If versions match, we're up to date
if (versionComparison === 0) {
return { ok: true, available: false };
}
// If currentRelease is older than current version, check allowAnyVersion
if (versionComparison < 0) {
// If allowAnyVersion is true, allow downgrades
if (this.allowAnyVersion) {
// Continue to find the release entry for downgrade
} else {
return { ok: true, available: false };
}
}
// currentRelease is newer, find the release entry
const releaseEntry = json.releases.find((r: any) => r.version === currentReleaseVersion);
if (!releaseEntry || !releaseEntry.updateTo) {
this.emitError(new Error(`Release entry for version '${currentReleaseVersion}' not found or missing 'updateTo' property.`));
return { ok: false, available: false };
}
const updateTo = releaseEntry.updateTo;
if (!updateTo.url) {
this.emitError(new Error(`Invalid release entry. 'updateTo.url' is missing for version ${currentReleaseVersion}.`));
return { ok: false, available: false };
}
return {
ok: true,
available: true,
url: updateTo.url,
name: updateTo.name,
notes: updateTo.notes,
pub_date: updateTo.pub_date
};
}
private parseDynamicReleasFile (json: any): { ok: boolean; available: boolean; url?: string; name?: string; notes?: string; pub_date?: string } {
if (!json.url) {
this.emitError(new Error('Invalid releases format. Expected \'url\' string property.'));
return { ok: false, available: false };
}
return { ok: true, available: true, url: json.url, name: json.name, notes: json.notes, pub_date: json.pub_date };
}
private async fetchSquirrelJson (url: string) {
const headers: Record<string, string> = {
...this.updateHeaders,
Accept: 'application/json' // Always set Accept header, overriding any user-provided Accept
};
const response = await net.fetch(url, {
headers
});
if (response.status === 204) {
return { ok: true, available: false };
} else if (response.status === 200) {
const updateJson = await response.json();
// Check if this is the static releases array format
if (Array.isArray(updateJson.releases)) {
// Get current package version
const packageInfo = msixUpdate.getPackageInfo();
const currentVersion = packageInfo.version;
if (!currentVersion) {
this.emitError(new Error('Cannot determine current package version.'));
return { ok: false, available: false };
}
return this.parseStaticReleasFile(updateJson, currentVersion);
} else {
// Dynamic format: server returns JSON with update info for current version
return this.parseDynamicReleasFile(updateJson);
}
} else {
this.emitError(new Error(`Unexpected response status: ${response.status}`));
return { ok: false, available: false };
}
}
private async getUpdateInfo (url: string): Promise<UpdateInfo> {
if (url && await this.isDirectMsixUrl(url)) {
return { ok: true, available: true, updateUrl: url, releaseDate: new Date() };
} else {
const updateJson = await this.fetchSquirrelJson(url);
if (!updateJson.ok) {
return { ok: false };
} else if (updateJson.ok && !updateJson.available) {
return { ok: true, available: false };
} else {
// updateJson.ok && updateJson.available must be true here
// Parse the publication date if present (ISO 8601 format)
let releaseDate: Date | null = null;
if (updateJson.pub_date) {
releaseDate = new Date(updateJson.pub_date);
}
const updateUrl = updateJson.url ?? '';
const releaseNotes = updateJson.notes ?? '';
const releaseName = updateJson.name ?? '';
releaseDate = releaseDate ?? new Date();
if (!await this.isDirectMsixUrl(updateUrl, true)) {
return { ok: false };
} else {
return {
ok: true,
available: true,
updateUrl,
releaseNotes,
releaseName,
releaseDate
};
}
}
}
}
getFeedURL () {
return this.updateURL ?? '';
}
setFeedURL (options: { url: string; headers?: Record<string, string>; allowAnyVersion?: boolean } | string) {
let updateURL: string;
let headers: Record<string, string> | undefined;
let allowAnyVersion: boolean | undefined;
if (typeof options === 'object') {
if (typeof options.url === 'string') {
updateURL = options.url;
headers = options.headers;
allowAnyVersion = options.allowAnyVersion;
} else {
throw new TypeError('Expected options object to contain a \'url\' string property in setFeedUrl call');
}
} else if (typeof options === 'string') {
updateURL = options;
} else {
throw new TypeError('Expected an options object with a \'url\' property to be provided');
}
this.updateURL = updateURL;
this.updateHeaders = headers ?? null;
this.allowAnyVersion = allowAnyVersion ?? false;
}
getPackageInfo (): MSIXPackageInfo {
return msixUpdate.getPackageInfo() as MSIXPackageInfo;
}
async checkForUpdates () {
const url = this.updateURL;
if (!url) {
return this.emitError(new Error('Update URL is not set'));
}
// Check if running in MSIX package
const packageInfo = msixUpdate.getPackageInfo();
if (!packageInfo.familyName) {
return this.emitError(new Error('MSIX updates are not supported'));
}
// If appInstallerUri is set, Windows App Installer manages updates automatically
// Prevent updates here to avoid conflicts
if (packageInfo.appInstallerUri) {
return this.emitError(new Error('Auto-updates are managed by Windows App Installer. Updates are not allowed when installed via Application Manifest.'));
}
this.emit('checking-for-update');
try {
const msixUrlInfo = await this.getUpdateInfo(url);
if (!msixUrlInfo.ok) {
return this.emitError(new Error('Invalid update or MSIX URL. See previous errors.'));
}
if (!msixUrlInfo.available) {
this.emit('update-not-available');
} else {
this.updateAvailable = true;
this.emit('update-available');
await msixUpdate.updateMsix(msixUrlInfo.updateUrl, {
deferRegistration: true,
developerMode: false,
forceShutdown: false,
forceTargetShutdown: false,
forceUpdateFromAnyVersion: this.allowAnyVersion
} as UpdateMsixOptions);
this.emit('update-downloaded', {}, msixUrlInfo.releaseNotes, msixUrlInfo.releaseName, msixUrlInfo.releaseDate, msixUrlInfo.updateUrl, () => {
this.quitAndInstall();
});
}
} catch (error) {
this.emitError(error as Error);
}
}
async quitAndInstall () {
if (!this.updateAvailable) {
this.emitError(new Error('No update available, can\'t quit and install'));
app.quit();
return;
}
try {
// Get package info to get family name
const packageInfo = msixUpdate.getPackageInfo();
if (!packageInfo.familyName) {
return this.emitError(new Error('MSIX updates are not supported'));
}
msixUpdate.registerRestartOnUpdate('');
this.emit('before-quit-for-update');
// force shutdown of the application and register the package to be installed on restart
await msixUpdate.registerPackage(packageInfo.familyName, {
forceShutdown: true
} as RegisterPackageOptions);
} catch (error) {
this.emitError(error as Error);
}
}
// Private: Emit both error object and message, this is to keep compatibility
// with Old APIs.
emitError (error: Error) {
this.emit('error', error, error.message);
}
}
export default new AutoUpdater();

View File

@@ -20,6 +20,11 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
return this.updateURL ?? '';
}
getPackageInfo () {
// Squirrel-based Windows apps don't have MSIX package information
return undefined;
}
setFeedURL (options: { url: string } | string) {
let updateURL: string;
if (typeof options === 'object') {

View File

@@ -0,0 +1,4 @@
const { updateMsix, registerPackage, registerRestartOnUpdate, getPackageInfo } =
process._linkedBinding('electron_browser_msix_updater');
export { updateMsix, registerPackage, registerRestartOnUpdate, getPackageInfo };

View File

@@ -119,7 +119,10 @@ export function fetchWithSession (input: RequestInfo, init: (RequestInit & {bypa
p.reject(err);
});
if (!req.body?.pipeTo(Writable.toWeb(r as unknown as Writable)).then(() => r.end())) { r.end(); }
// pipeTo expects a WritableStream<Uint8Array>. Node.js' Writable.toWeb returns WritableStream<any>,
// which causes a TS structural mismatch.
const writable = Writable.toWeb(r as unknown as Writable) as unknown as WritableStream<Uint8Array>;
if (!req.body?.pipeTo(writable).then(() => r.end())) { r.end(); }
return p.promise;
}

View File

@@ -4,6 +4,8 @@ import { createReadStream } from 'fs';
import { Readable } from 'stream';
import { ReadableStream } from 'stream/web';
import type { ReadableStreamDefaultReader } from 'stream/web';
// Global protocol APIs.
const { registerSchemesAsPrivileged, getStandardSchemes, Protocol } = process._linkedBinding('electron_browser_protocol');
@@ -12,7 +14,7 @@ const ERR_UNEXPECTED = -9;
const isBuiltInScheme = (scheme: string) => ['http', 'https', 'file'].includes(scheme);
function makeStreamFromPipe (pipe: any): ReadableStream {
function makeStreamFromPipe (pipe: any): ReadableStream<Uint8Array> {
const buf = new Uint8Array(1024 * 1024 /* 1 MB */);
return new ReadableStream({
async pull (controller) {
@@ -38,21 +40,26 @@ function makeStreamFromFileInfo ({
filePath: string;
offset?: number;
length?: number;
}): ReadableStream {
}): ReadableStream<Uint8Array> {
// Node's Readable.toWeb produces a WHATWG ReadableStream whose chunks are Uint8Array.
return Readable.toWeb(createReadStream(filePath, {
start: offset,
end: length >= 0 ? offset + length : undefined
}));
})) as ReadableStream<Uint8Array>;
}
function convertToRequestBody (uploadData: ProtocolRequest['uploadData']): RequestInit['body'] {
if (!uploadData) return null;
// Optimization: skip creating a stream if the request is just a single buffer.
if (uploadData.length === 1 && (uploadData[0] as any).type === 'rawData') return uploadData[0].bytes;
if (uploadData.length === 1 && (uploadData[0] as any).type === 'rawData') {
return uploadData[0].bytes as any;
}
const chunks = [...uploadData] as any[]; // TODO: types are wrong
let current: ReadableStreamDefaultReader | null = null;
return new ReadableStream({
const chunks = [...uploadData] as any[]; // TODO: refine ProtocolRequest types
// Use Node's web stream types explicitly to avoid DOM lib vs Node lib structural mismatches.
// Generic <Uint8Array> ensures reader.read() returns value?: Uint8Array consistent with enqueue.
let current: ReadableStreamDefaultReader<Uint8Array> | null = null;
return new ReadableStream<Uint8Array>({
async pull (controller) {
if (current) {
const { done, value } = await current.read();
@@ -67,7 +74,7 @@ function convertToRequestBody (uploadData: ProtocolRequest['uploadData']): Reque
if (!chunks.length) { return controller.close(); }
const chunk = chunks.shift()!;
if (chunk.type === 'rawData') {
controller.enqueue(chunk.bytes);
controller.enqueue(chunk.bytes as Uint8Array);
} else if (chunk.type === 'file') {
current = makeStreamFromFileInfo(chunk).getReader();
return this.pull!(controller);

View File

@@ -40,7 +40,7 @@ process.on('uncaughtException', function (error) {
// Emit 'exit' event on quit.
const { app } = require('electron');
app.on('quit', (_event, exitCode) => {
app.on('quit', (_event: any, exitCode: number) => {
process.emit('exit', exitCode);
});

View File

@@ -11,12 +11,14 @@ const { contextIsolationEnabled } = internalContextBridge;
* 1) Use menu API to show context menu.
*/
window.onload = function () {
if (contextIsolationEnabled) {
internalContextBridge.overrideGlobalValueFromIsolatedWorld([
'InspectorFrontendHost', 'showContextMenuAtPoint'
], createMenu);
} else {
window.InspectorFrontendHost!.showContextMenuAtPoint = createMenu;
if (window.InspectorFrontendHost) {
if (contextIsolationEnabled) {
internalContextBridge.overrideGlobalValueFromIsolatedWorld([
'InspectorFrontendHost', 'showContextMenuAtPoint'
], createMenu);
} else {
window.InspectorFrontendHost.showContextMenuAtPoint = createMenu;
}
}
};

View File

@@ -1,15 +1,16 @@
{
"name": "electron",
"name": "@electron-ci/dev-root",
"version": "0.0.0-development",
"repository": "https://github.com/electron/electron",
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
"devDependencies": {
"@azure/storage-blob": "^12.28.0",
"@datadog/datadog-ci": "^4.1.2",
"@electron/asar": "^3.2.13",
"@electron/docs-parser": "^2.0.0",
"@electron/fiddle-core": "^1.3.4",
"@electron/github-app-auth": "^2.2.1",
"@electron/lint-roller": "^3.1.2",
"@electron/github-app-auth": "^3.2.0",
"@electron/lint-roller": "^3.2.0",
"@electron/typescript-definitions": "^9.1.2",
"@octokit/rest": "^20.1.2",
"@primer/octicons": "^10.0.0",
@@ -24,7 +25,6 @@
"buffer": "^6.0.3",
"chalk": "^4.1.0",
"check-for-leaks": "^1.2.1",
"dugite": "^2.7.1",
"eslint": "^8.57.1",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.32.0",
@@ -40,6 +40,7 @@
"lint-staged": "^16.1.0",
"markdownlint-cli2": "^0.18.0",
"minimist": "^1.2.8",
"node-gyp": "^11.4.2",
"null-loader": "^4.0.1",
"pre-flight": "^2.0.0",
"process": "^0.11.10",
@@ -134,6 +135,18 @@
]
},
"resolutions": {
"nan": "nodejs/nan#e14bdcd1f72d62bca1d541b66da43130384ec213"
"dbus-native/xml2js": "0.5.0",
"abstract-socket": "github:deepak1556/node-abstractsocket#928cc591decd12aff7dad96449da8afc29832c19",
"minimist@npm:~0.0.1": "0.2.4"
},
"packageManager": "yarn@4.12.0",
"workspaces": [
"spec",
"spec/fixtures/native-addon/*"
],
"dependenciesMeta": {
"abstract-socket": {
"built": true
}
}
}

View File

@@ -143,3 +143,8 @@ allow_electron_to_depend_on_components_os_crypt_sync.patch
expose_referrerscriptinfo_hostdefinedoptionsindex.patch
chore_disable_protocol_handler_dcheck.patch
fix_release_mouse_buttons_on_focus_loss_on_wayland.patch
viz_fix_visual_artifacts_due_to_resizing_root_render_pass_with_dcomp.patch
viz_do_not_overallocate_surface_on_initial_render.patch
viz_create_isbufferqueuesupportedandenabled.patch
viz_fix_visual_artifacts_while_resizing_window_with_dcomp.patch
graphite_handle_out_of_order_recording_errors.patch

View File

@@ -46,7 +46,7 @@ index 7280ef29b85c1b16b11dd9e4d628a9eb579bb4dd..ab56e29d402a400a8c91aa97b6e82ad7
# than here in :chrome_dll.
deps += [ "//chrome:packed_resources_integrity_header" ]
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 15cfa64348e80a3507fc83c3819e9abdb238bffb..4650080f45da4e321d4b0f0dabd98f2bf097c7c2 100644
index d8978eb678048850cc0e629d0c978f23417b5be9..219c9ae3dde4f2239b5c1331b99c8f2e9a170628 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -7571,9 +7571,12 @@ test("unit_tests") {

View File

@@ -312,7 +312,7 @@ index 7e3d46902fbf736b4240eb3fcb89975a7b222197..57fdc89fc265ad70cb0bff8443cc1026
auto DrawAsSinglePath = [&]() {
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index a25e99fac5f5a171becff833464cdb3dfe7522c2..123a7c1fc980b1b3bcbe0e3e00afa594632d720b 100644
index 2e464ef7bbfd7e0d84df348d012e8020d4758726..b59eceacbd4143ef183bb67c39f9ccccc7582adb 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -214,6 +214,10 @@

View File

@@ -0,0 +1,348 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Sunny Sachanandani <sunnyps@chromium.org>
Date: Fri, 30 Jan 2026 12:51:05 -0800
Subject: [graphite] Handle out of order recording errors
Explicitly handle out of order recording errors to crash just like async
shader compile failed errors. Also, emit the insert status to an UMA
histogram at 1% subsampling and log all error statuses at runtime.
Bug: 458722690
Change-Id: Id94657be6ae870dcf8adba71aff216386a6a6964
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7533855
Commit-Queue: Sunny Sachanandani <sunnyps@chromium.org>
Auto-Submit: Sunny Sachanandani <sunnyps@chromium.org>
Reviewed-by: Jonathan Ross <jonross@chromium.org>
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Cr-Commit-Position: refs/heads/main@{#1577487}
diff --git a/gpu/command_buffer/service/graphite_shared_context.cc b/gpu/command_buffer/service/graphite_shared_context.cc
index f9a47ac16bddc2352d81f43629d8ce8c2d2b3b99..9856d68f893e4329fda5d002835613161b346bc1 100644
--- a/gpu/command_buffer/service/graphite_shared_context.cc
+++ b/gpu/command_buffer/service/graphite_shared_context.cc
@@ -4,7 +4,10 @@
#include "gpu/command_buffer/service/graphite_shared_context.h"
+#include "base/logging.h"
#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/rand_util.h"
#include "base/task/single_thread_task_runner.h"
#include "gpu/command_buffer/common/shm_count.h"
#include "third_party/skia/include/core/SkColorSpace.h"
@@ -14,6 +17,44 @@
namespace gpu {
namespace {
+// This is emitted to UMA - values should not be reordered, only appended!
+// LINT.IfChange(InsertRecordingStatusUma)
+enum class InsertRecordingStatusUma {
+ kSuccess,
+ kInvalidRecording,
+ kPromiseImageInstantiationFailed,
+ kAddCommandsFailed,
+ kAsyncShaderCompilesFailed,
+ kOutOfOrderRecording,
+ kMaxValue = kOutOfOrderRecording
+};
+// LINT.ThenChange(//tools/metrics/histograms/metadata/gpu/enums.xml:GraphiteInsertRecordingStatus)
+
+InsertRecordingStatusUma InsertRecordingStatusUma(
+ skgpu::graphite::InsertStatus insert_status) {
+ // InsertStatus almost behaves like an enum class, but not quite since it can
+ // convert to both bool and integer types and can't be used in a switch.
+ if (insert_status == skgpu::graphite::InsertStatus::kSuccess) {
+ return InsertRecordingStatusUma::kSuccess;
+ } else if (insert_status ==
+ skgpu::graphite::InsertStatus::kInvalidRecording) {
+ return InsertRecordingStatusUma::kInvalidRecording;
+ } else if (insert_status ==
+ skgpu::graphite::InsertStatus::kPromiseImageInstantiationFailed) {
+ return InsertRecordingStatusUma::kPromiseImageInstantiationFailed;
+ } else if (insert_status ==
+ skgpu::graphite::InsertStatus::kAddCommandsFailed) {
+ return InsertRecordingStatusUma::kAddCommandsFailed;
+ } else if (insert_status ==
+ skgpu::graphite::InsertStatus::kAsyncShaderCompilesFailed) {
+ return InsertRecordingStatusUma::kAsyncShaderCompilesFailed;
+ } else if (insert_status ==
+ skgpu::graphite::InsertStatus::kOutOfOrderRecording) {
+ return InsertRecordingStatusUma::kOutOfOrderRecording;
+ }
+ NOTREACHED();
+}
+
struct RecordingContext {
skgpu::graphite::GpuFinishedProc old_finished_proc;
skgpu::graphite::GpuFinishedContext old_context;
@@ -216,20 +257,38 @@ bool GraphiteSharedContext::InsertRecordingImpl(
auto insert_status = graphite_context_->insertRecording(*info_ptr);
- // TODO(433845560): Check the kAddCommandsFailed failures.
- // Crash only if we're not simulating a failure for testing.
const bool simulating_insert_failure =
info_ptr->fSimulatedStatus != skgpu::graphite::InsertStatus::kSuccess;
- // InsertStatus::kAsyncShaderCompilesFailed is also an unrecoverable error for
- // which we should also clear the disk shader cache in case the error was due
- // to a corrupted cached shader blob.
+ // Crash, log, or emit UMA only if we're not simulating a failure for testing.
+ if (!simulating_insert_failure) {
+ if (base::ShouldRecordSubsampledMetric(0.01)) {
+ UMA_HISTOGRAM_ENUMERATION("GPU.Graphite.InsertRecordingStatus",
+ InsertRecordingStatusUma(insert_status));
+ }
+ if (insert_status != skgpu::graphite::InsertStatus::kSuccess) {
+ // skgpu::graphite::InsertStatus almost behaves like an enum class, but
+ // not quite - it can't be static_cast to an int.
+ LOG(ERROR) << "Graphite insertRecording failed with status "
+ << static_cast<int>(InsertRecordingStatusUma(insert_status));
+ }
+ }
+
+ // kAsyncShaderCompilesFailed and kOutOfOrderRecording are unrecoverable
+ // failures because they cause future recordings to be rendered incorrectly.
+ // TODO(433845560): Check the kAddCommandsFailed failures.
+ const bool is_unrecoverable_failure =
+ insert_status ==
+ skgpu::graphite::InsertStatus::kAsyncShaderCompilesFailed ||
+ insert_status == skgpu::graphite::InsertStatus::kOutOfOrderRecording;
+ // For kAsyncShaderCompilesFailed, we should also clear the disk shader
+ // cache in case the error was due to a corrupted cached shader blob.
+ std::optional<GpuProcessShmCount::ScopedIncrement> use_shader_cache;
if (insert_status ==
skgpu::graphite::InsertStatus::kAsyncShaderCompilesFailed) {
- GpuProcessShmCount::ScopedIncrement use_shader_cache(
- use_shader_cache_shm_count_);
- CHECK(simulating_insert_failure);
+ use_shader_cache.emplace(use_shader_cache_shm_count_);
}
+ CHECK(simulating_insert_failure || !is_unrecoverable_failure);
// All other failure modes are recoverable in the sense that future recordings
// will be rendered correctly, so merely return a boolean here so that callers
diff --git a/gpu/command_buffer/service/graphite_shared_context_unittest.cc b/gpu/command_buffer/service/graphite_shared_context_unittest.cc
index 0de107a9d86ff8217a4c537598f198f313ecf9df..852343ab6804efed962b707b748ddf1920c20ea7 100644
--- a/gpu/command_buffer/service/graphite_shared_context_unittest.cc
+++ b/gpu/command_buffer/service/graphite_shared_context_unittest.cc
@@ -6,6 +6,7 @@
#include "base/threading/thread.h"
#include "gpu/command_buffer/common/shm_count.h"
+#include "gpu/command_buffer/service/skia_utils.h"
#include "skia/buildflags.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -86,8 +87,7 @@ class GraphiteSharedContextTest : public testing::TestWithParam<bool> {
wgpu::DeviceDescriptor device_desc = {};
- wgpu::Device device =
- wgpu::Adapter(adapters[0].Get()).CreateDevice(&device_desc);
+ auto device = wgpu::Adapter(adapters[0].Get()).CreateDevice(&device_desc);
CHECK(device);
skgpu::graphite::DawnBackendContext backend_context = {};
@@ -95,7 +95,11 @@ class GraphiteSharedContextTest : public testing::TestWithParam<bool> {
backend_context.fDevice = device;
backend_context.fQueue = device.GetQueue();
- skgpu::graphite::ContextOptions context_options = {};
+ // Use the default Graphite context options that Chromium uses e.g. disallow
+ // things like out of order recordings.
+ gpu::GpuDriverBugWorkarounds workarounds;
+ auto context_options = GetDefaultGraphiteContextOptions(workarounds);
+
graphite_shared_context_ = std::make_unique<GraphiteSharedContext>(
skgpu::graphite::ContextFactory::MakeDawn(backend_context,
context_options),
@@ -133,14 +137,12 @@ TEST_P(GraphiteSharedContextTest, ConcurrentAccess) {
auto run_graphite_functions =
[](GraphiteSharedContext* graphite_shared_context) {
// Call a method that acquires the lock
- std::unique_ptr<skgpu::graphite::Recorder> recorder =
- graphite_shared_context->makeRecorder();
+ auto recorder = graphite_shared_context->makeRecorder();
EXPECT_TRUE(recorder);
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 10; ++j) {
- std::unique_ptr<skgpu::graphite::Recording> recording =
- recorder->snap();
+ auto recording = recorder->snap();
skgpu::graphite::InsertRecordingInfo info = {};
info.fRecording = recording.get();
EXPECT_TRUE(recording);
@@ -172,18 +174,17 @@ TEST_P(GraphiteSharedContextTest, ConcurrentAccess) {
}
TEST_P(GraphiteSharedContextTest, AsyncShaderCompilesFailed) {
- std::unique_ptr<skgpu::graphite::Recorder> recorder =
- graphite_shared_context_->makeRecorder();
+ auto recorder = graphite_shared_context_->makeRecorder();
EXPECT_TRUE(recorder);
auto ii = SkImageInfo::Make(64, 64, kN32_SkColorType, kPremul_SkAlphaType);
- sk_sp<SkSurface> surface1 = SkSurfaces::RenderTarget(recorder.get(), ii);
+ auto surface1 = SkSurfaces::RenderTarget(recorder.get(), ii);
surface1->getCanvas()->clear(SK_ColorRED);
- sk_sp<SkSurface> surface2 = SkSurfaces::RenderTarget(recorder.get(), ii);
+ auto surface2 = SkSurfaces::RenderTarget(recorder.get(), ii);
surface2->getCanvas()->drawImage(surface1->makeTemporaryImage(), 0, 0);
- std::unique_ptr<skgpu::graphite::Recording> recording = recorder->snap();
+ auto recording = recorder->snap();
EXPECT_TRUE(recording);
skgpu::graphite::InsertRecordingInfo info = {};
@@ -197,19 +198,43 @@ TEST_P(GraphiteSharedContextTest, AsyncShaderCompilesFailed) {
EXPECT_FALSE(graphite_shared_context_->insertRecording(info));
}
+TEST_P(GraphiteSharedContextTest, OutOfOrderRecording) {
+ auto recorder = graphite_shared_context_->makeRecorder();
+ EXPECT_TRUE(recorder);
+
+ auto ii = SkImageInfo::Make(64, 64, kN32_SkColorType, kPremul_SkAlphaType);
+ auto surface1 = SkSurfaces::RenderTarget(recorder.get(), ii);
+ surface1->getCanvas()->clear(SK_ColorRED);
+ auto recording1 = recorder->snap();
+ EXPECT_TRUE(recording1);
+
+ auto surface2 = SkSurfaces::RenderTarget(recorder.get(), ii);
+ surface2->getCanvas()->drawImage(surface1->makeTemporaryImage(), 0, 0);
+ auto recording2 = recorder->snap();
+ EXPECT_TRUE(recording2);
+
+ skgpu::graphite::InsertRecordingInfo info = {};
+
+ info.fRecording = recording2.get();
+ graphite_shared_context_->insertRecording(info);
+
+ info.fRecording = recording1.get();
+ EXPECT_DEATH_IF_SUPPORTED(graphite_shared_context_->insertRecording(info),
+ "");
+}
+
TEST_P(GraphiteSharedContextTest, AddCommandsFailed) {
- std::unique_ptr<skgpu::graphite::Recorder> recorder =
- graphite_shared_context_->makeRecorder();
+ auto recorder = graphite_shared_context_->makeRecorder();
EXPECT_TRUE(recorder);
auto ii = SkImageInfo::Make(64, 64, kN32_SkColorType, kPremul_SkAlphaType);
- sk_sp<SkSurface> surface1 = SkSurfaces::RenderTarget(recorder.get(), ii);
+ auto surface1 = SkSurfaces::RenderTarget(recorder.get(), ii);
surface1->getCanvas()->clear(SK_ColorRED);
- sk_sp<SkSurface> surface2 = SkSurfaces::RenderTarget(recorder.get(), ii);
+ auto surface2 = SkSurfaces::RenderTarget(recorder.get(), ii);
surface2->getCanvas()->drawImage(surface1->makeTemporaryImage(), 0, 0);
- std::unique_ptr<skgpu::graphite::Recording> recording = recorder->snap();
+ auto recording = recorder->snap();
EXPECT_TRUE(recording);
skgpu::graphite::InsertRecordingInfo info = {};
@@ -223,39 +248,37 @@ TEST_P(GraphiteSharedContextTest, AddCommandsFailed) {
}
TEST_P(GraphiteSharedContextTest, LowPendingRecordings) {
- std::unique_ptr<skgpu::graphite::Recorder> recorder =
- graphite_shared_context_->makeRecorder();
+ auto recorder = graphite_shared_context_->makeRecorder();
EXPECT_TRUE(recorder);
- std::unique_ptr<skgpu::graphite::Recording> recording = recorder->snap();
- EXPECT_TRUE(recording);
-
- skgpu::graphite::InsertRecordingInfo info = {};
- info.fRecording = recording.get();
-
// No flush is expected if the number of pending recordings is low.
EXPECT_CALL(backend_flush_callback_, Flush()).Times(0);
for (size_t i = 0; i < kMaxPendingRecordings - 1; ++i) {
+ auto recording = recorder->snap();
+ EXPECT_TRUE(recording);
+
+ skgpu::graphite::InsertRecordingInfo info = {};
+ info.fRecording = recording.get();
+
EXPECT_TRUE(graphite_shared_context_->insertRecording(info));
}
}
TEST_P(GraphiteSharedContextTest, MaxPendingRecordings) {
- std::unique_ptr<skgpu::graphite::Recorder> recorder =
- graphite_shared_context_->makeRecorder();
+ auto recorder = graphite_shared_context_->makeRecorder();
EXPECT_TRUE(recorder);
- std::unique_ptr<skgpu::graphite::Recording> recording = recorder->snap();
- EXPECT_TRUE(recording);
-
- skgpu::graphite::InsertRecordingInfo info = {};
- info.fRecording = recording.get();
-
// Expect a flush when the number of pending recordings reaches the max.
EXPECT_CALL(backend_flush_callback_, Flush()).Times(1);
for (size_t i = 0; i < kMaxPendingRecordings; ++i) {
+ auto recording = recorder->snap();
+ EXPECT_TRUE(recording);
+
+ skgpu::graphite::InsertRecordingInfo info = {};
+ info.fRecording = recording.get();
+
EXPECT_TRUE(graphite_shared_context_->insertRecording(info));
}
}
diff --git a/tools/metrics/histograms/metadata/gpu/enums.xml b/tools/metrics/histograms/metadata/gpu/enums.xml
index 58471a1d69c65cdf559f98ec2e11550bc16520d2..b62d8b78d34bfec491bcd5e6c65ff63eaedf00dd 100644
--- a/tools/metrics/histograms/metadata/gpu/enums.xml
+++ b/tools/metrics/histograms/metadata/gpu/enums.xml
@@ -1104,6 +1104,19 @@ Called by update_gpu_driver_bug_workaround_entries.py.-->
<int value="10" label="NoKillForGpuProgressInCrashDump"/>
</enum>
+<!-- LINT.IfChange(GraphiteInsertRecordingStatus) -->
+
+<enum name="GraphiteInsertRecordingStatus">
+ <int value="0" label="Success"/>
+ <int value="1" label="InvalidRecording"/>
+ <int value="2" label="PromiseImageInstantiationFailed"/>
+ <int value="3" label="AddCommandsFailed"/>
+ <int value="4" label="AsyncShaderCompilesFailed"/>
+ <int value="5" label="OutOfOrderRecording"/>
+</enum>
+
+<!-- LINT.ThenChange(//gpu/command_buffer/service/graphite_shared_context.cc:InsertRecordingStatusUma) -->
+
<enum name="HasDiscreteGpu">
<int value="0" label="No"/>
<int value="1" label="Yes"/>
diff --git a/tools/metrics/histograms/metadata/gpu/histograms.xml b/tools/metrics/histograms/metadata/gpu/histograms.xml
index ea8b129a2f4aba4d8afd9c3338f21574a12185dd..80bf969cbf66eac7f970b7c096da83c69424c38a 100644
--- a/tools/metrics/histograms/metadata/gpu/histograms.xml
+++ b/tools/metrics/histograms/metadata/gpu/histograms.xml
@@ -1004,6 +1004,16 @@ chromium-metrics-reviews@google.com.
</summary>
</histogram>
+<histogram name="Gpu.Graphite.InsertRecordingStatus"
+ enum="GraphiteInsertRecordingStatus" expires_after="2026-04-30">
+ <owner>sunnyps@chromium.org</owner>
+ <owner>michaelludwig@google.com</owner>
+ <owner>chrome-gpu-metric-alerts@chromium.org</owner>
+ <summary>
+ The return value for each Graphite insertRecording call made by Chromium.
+ </summary>
+</histogram>
+
<histogram name="GPU.GraphValidation.Duration" units="ms"
expires_after="2025-11-15">
<owner>vasilyt@chromium.org</owner>

View File

@@ -0,0 +1,93 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Niklas Wenzel <dev@nikwen.de>
Date: Wed, 3 Dec 2025 17:10:02 +0100
Subject: viz: Create IsBufferQueueSupportedAndEnabled()
Manual backport of crrev.com/c/7210913
Bug: 457463689
Change-Id: I31bbaa6b5d79697c6bb5e1fc6738f6ea5a937b4f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7210913
Reviewed-by: Michael Tang <tangm@microsoft.com>
Commit-Queue: David Sanders <dsanders11@ucsbalum.com>
Reviewed-by: Vasiliy Telezhnikov <vasilyt@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1553190}
diff --git a/components/viz/service/display/output_surface.cc b/components/viz/service/display/output_surface.cc
index ff795eb057ac64f40aa842fec8e053d12f57f538..71287614c627d39f8d019889498205ab3eff2a69 100644
--- a/components/viz/service/display/output_surface.cc
+++ b/components/viz/service/display/output_surface.cc
@@ -21,6 +21,16 @@
namespace viz {
+namespace {
+
+#if BUILDFLAG(IS_WIN)
+// Use BufferQueue for the primary plane instead of a DXGI swap chain or DComp
+// surface.
+BASE_FEATURE(kBufferQueue, base::FEATURE_DISABLED_BY_DEFAULT);
+#endif
+
+} // namespace
+
OutputSurface::Capabilities::Capabilities() = default;
OutputSurface::Capabilities::~Capabilities() = default;
OutputSurface::Capabilities::Capabilities(const Capabilities& capabilities) =
@@ -94,6 +104,12 @@ bool IsDelegatedCompositingSupportedAndEnabled(
// Ensure we check the feature flag iff the feature is supported.
return features::IsDelegatedCompositingEnabled();
}
+
+bool IsBufferQueueSupportedAndEnabled(
+ OutputSurface::DCSupportLevel support_level) {
+ return support_level >= OutputSurface::DCSupportLevel::kDCompDynamicTexture &&
+ base::FeatureList::IsEnabled(kBufferQueue);
+}
#endif
} // namespace viz
diff --git a/components/viz/service/display/output_surface.h b/components/viz/service/display/output_surface.h
index 25306ab6e18a266efdc329e4ddd81f5303033f4c..589f4c10dad9c807c9e3ce7baba63795b629435b 100644
--- a/components/viz/service/display/output_surface.h
+++ b/components/viz/service/display/output_surface.h
@@ -307,6 +307,9 @@ class VIZ_SERVICE_EXPORT OutputSurface {
// `features::IsDelegatedCompositingEnabled()`.
bool IsDelegatedCompositingSupportedAndEnabled(
OutputSurface::DCSupportLevel support_level);
+
+bool IsBufferQueueSupportedAndEnabled(
+ OutputSurface::DCSupportLevel support_level);
#endif
} // namespace viz
diff --git a/components/viz/service/display/skia_renderer.cc b/components/viz/service/display/skia_renderer.cc
index e34e15dda13a183568fc3e186d3b89da1e828ad4..9b9a4c02c975799fe42b04f0d5b680274d28b09e 100644
--- a/components/viz/service/display/skia_renderer.cc
+++ b/components/viz/service/display/skia_renderer.cc
@@ -121,12 +121,6 @@ namespace {
BASE_FEATURE(kDumpWithoutCrashingOnMissingRenderPassBacking,
base::FEATURE_ENABLED_BY_DEFAULT);
-#if BUILDFLAG(IS_WIN)
-// Use BufferQueue for the primary plane instead of a DXGI swap chain or DComp
-// surface.
-BASE_FEATURE(kBufferQueue, base::FEATURE_DISABLED_BY_DEFAULT);
-#endif
-
// Smallest unit that impacts anti-aliasing output. We use this to determine
// when an exterior edge (with AA) has been clipped (no AA). The specific value
// was chosen to match that used by gl_renderer.
@@ -992,10 +986,8 @@ SkiaRenderer::SkiaRenderer(const RendererSettings* settings,
// It's possible to use BufferQueue with DComp textures, so we can optionally
// enable it behind a feature flag.
- const bool want_buffer_queue =
- output_surface_->capabilities().dc_support_level >=
- OutputSurface::DCSupportLevel::kDCompDynamicTexture &&
- base::FeatureList::IsEnabled(kBufferQueue);
+ const bool want_buffer_queue = IsBufferQueueSupportedAndEnabled(
+ output_surface_->capabilities().dc_support_level);
#else
const bool want_buffer_queue = true;
#endif

View File

@@ -0,0 +1,45 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Niklas Wenzel <dev@nikwen.de>
Date: Wed, 3 Dec 2025 17:08:55 +0100
Subject: viz: Do not overallocate surface on initial render
Manual backport of crrev.com/c/7129658
Change-Id: I0baa5865dbe66efc7b0f3f793c8e89cdc6aaa0b6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7129658
Commit-Queue: Vasiliy Telezhnikov <vasilyt@chromium.org>
Reviewed-by: Vasiliy Telezhnikov <vasilyt@chromium.org>
Reviewed-by: Michael Tang <tangm@microsoft.com>
Cr-Commit-Position: refs/heads/main@{#1544293}
diff --git a/components/viz/service/display/direct_renderer.cc b/components/viz/service/display/direct_renderer.cc
index c03a4b3db61371021a94128f698126b5fef2f577..01abf5d36e2e117acf6f9cdc91307c9ac5616453 100644
--- a/components/viz/service/display/direct_renderer.cc
+++ b/components/viz/service/display/direct_renderer.cc
@@ -1081,10 +1081,13 @@ gfx::Size DirectRenderer::CalculateTextureSizeForRenderPass(
// buffer area and number of reallocations to quantify the trade-off.
gfx::Size DirectRenderer::CalculateSizeForOutputSurface(
const gfx::Size& requested_viewport_size) {
+ const gfx::Size surface_size = surface_size_for_swap_buffers();
+
// We're not able to clip back buffers if output surface does not support
- // clipping.
- if (requested_viewport_size == surface_size_for_swap_buffers() ||
+ // clipping. We don't round on the initial frame when a window is first shown.
+ if (requested_viewport_size == surface_size ||
!output_surface_->capabilities().supports_viewporter ||
+ surface_size.IsZero() ||
settings_->dont_round_texture_sizes_for_pixel_tests) {
device_viewport_size_ = requested_viewport_size;
return requested_viewport_size;
@@ -1102,8 +1105,8 @@ gfx::Size DirectRenderer::CalculateSizeForOutputSurface(
// allows backings to be more easily reused during a resize operation.
const int request_width = requested_viewport_size.width();
const int request_height = requested_viewport_size.height();
- int surface_width = surface_size_for_swap_buffers().width();
- int surface_height = surface_size_for_swap_buffers().height();
+ int surface_width = surface_size.width();
+ int surface_height = surface_size.height();
constexpr int multiple = 256;
// If |request_width| or |request_height| is already a multiple of |multiple|,

View File

@@ -0,0 +1,43 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Niklas Wenzel <dev@nikwen.de>
Date: Thu, 20 Nov 2025 11:51:44 -0800
Subject: viz: Fix visual artifacts due to resizing root render pass with DComp
Backport of crrev.com/c/7156576
Refs https://github.com/electron/electron/issues/36280#issuecomment-3560964534
Bug: 457463689
Change-Id: I7e00eadb2a1c3b44d64939d1a870d89befee158b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7156576
Reviewed-by: Vasiliy Telezhnikov <vasilyt@chromium.org>
Reviewed-by: Jonathan Ross <jonross@chromium.org>
Commit-Queue: Jonathan Ross <jonross@chromium.org>
Reviewed-by: Michael Tang <tangm@microsoft.com>
Cr-Commit-Position: refs/heads/main@{#1547987}
diff --git a/components/viz/service/display_embedder/skia_output_device_dcomp.cc b/components/viz/service/display_embedder/skia_output_device_dcomp.cc
index 81649eb59eb71ac950779af0f521a1f2a02e3add..7ac9ea1cdc4416a7af8dc2a75404cbc15be6cfad 100644
--- a/components/viz/service/display_embedder/skia_output_device_dcomp.cc
+++ b/components/viz/service/display_embedder/skia_output_device_dcomp.cc
@@ -39,6 +39,11 @@
namespace viz {
namespace {
+// With DirectComposition, resize surface based on root render pass size to
+// avoid gutter which shows stale pixels.
+BASE_FEATURE(kDirectCompositionResizeBasedOnRootSurface,
+ base::FEATURE_ENABLED_BY_DEFAULT);
+
base::TimeTicks g_last_reshape_failure = base::TimeTicks();
NOINLINE void CheckForLoopFailures() {
@@ -155,6 +160,8 @@ SkiaOutputDeviceDComp::SkiaOutputDeviceDComp(
capabilities_.renderer_allocates_images = true;
capabilities_.supports_viewporter = presenter_->SupportsViewporter();
capabilities_.supports_non_backed_solid_color_overlays = true;
+ capabilities_.resize_based_on_root_surface =
+ base::FeatureList::IsEnabled(kDirectCompositionResizeBasedOnRootSurface);
DCHECK(context_state_);
DCHECK(context_state_->gr_context() ||

View File

@@ -0,0 +1,122 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Niklas Wenzel <dev@nikwen.de>
Date: Wed, 3 Dec 2025 17:42:58 +0100
Subject: viz: Fix visual artifacts while resizing window with DComp
Manual backport of crrev.com/c/7115438
Bug: 457463689
Change-Id: I9c684effe15e0b112ae533faa243e5a035e9c875
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7115438
Commit-Queue: David Sanders <dsanders11@ucsbalum.com>
Reviewed-by: Michael Tang <tangm@microsoft.com>
Reviewed-by: Vasiliy Telezhnikov <vasilyt@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1553192}
diff --git a/components/viz/service/display/direct_renderer.cc b/components/viz/service/display/direct_renderer.cc
index 01abf5d36e2e117acf6f9cdc91307c9ac5616453..7e45d9ea46d0d4095169daf748e20c98db21969d 100644
--- a/components/viz/service/display/direct_renderer.cc
+++ b/components/viz/service/display/direct_renderer.cc
@@ -243,6 +243,31 @@ void DirectRenderer::DrawFrame(
current_frame()->device_viewport_size = device_viewport_size;
current_frame()->display_color_spaces = display_color_spaces;
+ gfx::Size surface_resource_size =
+ CalculateSizeForOutputSurface(device_viewport_size);
+
+#if BUILDFLAG(IS_WIN)
+ if (output_surface_->capabilities().clear_drawn_areas_outside_viewport &&
+ device_viewport_size != surface_resource_size) {
+ // On Windows with DirectComposition, we cannot synchronize the swap chain
+ // |Present| and the DComp |Commit| calls to take effect at the same time.
+ // (Both take effect asynchronously.) Hence, presenting a frame and changing
+ // the DComp layer clip rect can happen at different times. This can lead to
+ // ugly visual artifacts while resizing the window because it can reveal
+ // areas of the surface that are outside the viewport (crbug.com/457463689).
+ // To prevent those artifacts, we clear areas outside of the viewport with a
+ // transparent color. Transparency is expensive, so we use it only while
+ // resizing.
+ // This line gives us a transparent image format and triggers the background
+ // to be cleared in |SkiaRenderer::ClearFramebuffer|.
+ root_render_pass->has_transparent_background = true;
+ // Redraw and swap the whole surface.
+ root_render_pass->output_rect = gfx::Rect(surface_resource_size);
+ current_frame()->root_damage_rect = gfx::Rect(surface_resource_size);
+ current_frame()->device_viewport_size = surface_resource_size;
+ }
+#endif
+
output_surface_->SetNeedsMeasureNextDrawLatency();
BeginDrawingFrame();
@@ -274,8 +299,6 @@ void DirectRenderer::DrawFrame(
current_frame()->display_color_spaces.GetOutputBufferFormat(
current_frame()->root_render_pass->content_color_usage,
frame_has_alpha));
- gfx::Size surface_resource_size =
- CalculateSizeForOutputSurface(device_viewport_size);
if (overlay_processor_) {
// Display transform and viewport size are needed for overlay validator on
// Android SurfaceControl, and viewport size is need on Windows. These need
@@ -397,8 +420,10 @@ void DirectRenderer::DrawFrame(
// If we need to redraw the frame, the whole output should be considered
// damaged.
- if (needs_full_frame_redraw)
- current_frame()->root_damage_rect = gfx::Rect(device_viewport_size);
+ if (needs_full_frame_redraw) {
+ current_frame()->root_damage_rect =
+ gfx::Rect(current_frame()->device_viewport_size);
+ }
if (!skip_drawing_root_render_pass) {
DrawRenderPassAndExecuteCopyRequests(root_render_pass);
diff --git a/components/viz/service/display/output_surface.h b/components/viz/service/display/output_surface.h
index 589f4c10dad9c807c9e3ce7baba63795b629435b..641bbfc732c88141ddd929a4c334360462259ee4 100644
--- a/components/viz/service/display/output_surface.h
+++ b/components/viz/service/display/output_surface.h
@@ -106,6 +106,11 @@ class VIZ_SERVICE_EXPORT OutputSurface {
#if BUILDFLAG(IS_WIN)
// Whether this OutputSurface supports direct composition layers.
DCSupportLevel dc_support_level = DCSupportLevel::kNone;
+ // Whether to 1) clear all drawn areas outside the viewport with a
+ // transparent background color when drawing a frame and 2) swap them. This
+ // is necessary if the surface clip rect can get out of sync with the
+ // viewport size (e.g., due to a race condition).
+ bool clear_drawn_areas_outside_viewport = false;
#endif
// Whether this OutputSurface should skip DrawAndSwap(). This is true for
// the unified display on Chrome OS. All drawing is handled by the physical
diff --git a/components/viz/service/display_embedder/skia_output_device_dcomp.cc b/components/viz/service/display_embedder/skia_output_device_dcomp.cc
index 7ac9ea1cdc4416a7af8dc2a75404cbc15be6cfad..8e027d1382c5d639c1e114b8e25b6cea3af3445d 100644
--- a/components/viz/service/display_embedder/skia_output_device_dcomp.cc
+++ b/components/viz/service/display_embedder/skia_output_device_dcomp.cc
@@ -39,10 +39,8 @@
namespace viz {
namespace {
-// With DirectComposition, resize surface based on root render pass size to
-// avoid gutter which shows stale pixels.
-BASE_FEATURE(kDirectCompositionResizeBasedOnRootSurface,
- base::FEATURE_ENABLED_BY_DEFAULT);
+// Apply fixes for crbug.com/457463689.
+BASE_FEATURE(kDirectCompositionResizeFixes, base::FEATURE_ENABLED_BY_DEFAULT);
base::TimeTicks g_last_reshape_failure = base::TimeTicks();
@@ -161,7 +159,14 @@ SkiaOutputDeviceDComp::SkiaOutputDeviceDComp(
capabilities_.supports_viewporter = presenter_->SupportsViewporter();
capabilities_.supports_non_backed_solid_color_overlays = true;
capabilities_.resize_based_on_root_surface =
- base::FeatureList::IsEnabled(kDirectCompositionResizeBasedOnRootSurface);
+ base::FeatureList::IsEnabled(kDirectCompositionResizeFixes);
+ // With delegated compositing or a buffer queue, |Present| and |Commit| are
+ // synchronized and the clear is not needed.
+ capabilities_.clear_drawn_areas_outside_viewport =
+ base::FeatureList::IsEnabled(kDirectCompositionResizeFixes) &&
+ !IsDelegatedCompositingSupportedAndEnabled(
+ capabilities_.dc_support_level) &&
+ !IsBufferQueueSupportedAndEnabled(capabilities_.dc_support_level);
DCHECK(context_state_);
DCHECK(context_state_->gr_context() ||

View File

@@ -12,5 +12,6 @@
{ "patch_dir": "src/electron/patches/ReactiveObjC", "repo": "src/third_party/squirrel.mac/vendor/ReactiveObjC" },
{ "patch_dir": "src/electron/patches/webrtc", "repo": "src/third_party/webrtc" },
{ "patch_dir": "src/electron/patches/reclient-configs", "repo": "src/third_party/engflow-reclient-configs" },
{ "patch_dir": "src/electron/patches/sqlite", "repo": "src/third_party/sqlite/src" }
{ "patch_dir": "src/electron/patches/sqlite", "repo": "src/third_party/sqlite/src" },
{ "patch_dir": "src/electron/patches/skia", "repo": "src/third_party/skia" }
]

View File

@@ -1 +1,2 @@
chore_expose_ui_to_allow_electron_to_set_dock_side.patch
fix_element_tree_flickering_with_node_inspection.patch

View File

@@ -0,0 +1,107 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Alex Rudenko <alexrudenko@chromium.org>
Date: Thu, 20 Nov 2025 11:18:30 +0100
Subject: Fix element tree flickering with node inspection
Regressed in https://crrev.com/c/6888910. The highlighting in the tree
outline emits the INSPECT_MODE_WILL_BE_TOGGLED on the overlay model
causing the tree to clear the highlight immediately. This CL
recovers the logic missed in the https://crrev.com/c/6888910
to prevent re-entrancy during highlighting.
Fixed: 462120622
Change-Id: I08af098f7a142085c2fb16511031855623e07c4b
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/7170551
Reviewed-by: Philip Pfaffe <pfaffe@chromium.org>
Commit-Queue: Philip Pfaffe <pfaffe@chromium.org>
Auto-Submit: Alex Rudenko <alexrudenko@chromium.org>
diff --git a/front_end/panels/elements/DOMTreeWidget.test.ts b/front_end/panels/elements/DOMTreeWidget.test.ts
index 43ae133087a61c47c84349c7550485bd52b23847..d83fddd7afc1871b8064631d63c1b6b1e5357b70 100644
--- a/front_end/panels/elements/DOMTreeWidget.test.ts
+++ b/front_end/panels/elements/DOMTreeWidget.test.ts
@@ -24,6 +24,7 @@ describeWithMockConnection('DOMTreeWidget', () => {
elementsTreeOutline,
alreadyExpandedParentTreeElement: null,
highlightedTreeElement: null,
+ isUpdatingHighlights: false,
});
const domTree = new Elements.ElementsTreeOutline.DOMTreeWidget(undefined, view);
domTree.performUpdate();
diff --git a/front_end/panels/elements/ElementsTreeOutline.ts b/front_end/panels/elements/ElementsTreeOutline.ts
index bd8093e1c0961648e782234ed5826273d6ace6d9..c6c8d9d29a84f872126ca0b7c847dffe08c285ad 100644
--- a/front_end/panels/elements/ElementsTreeOutline.ts
+++ b/front_end/panels/elements/ElementsTreeOutline.ts
@@ -105,6 +105,7 @@ interface ViewInput {
interface ViewOutput {
elementsTreeOutline?: ElementsTreeOutline;
highlightedTreeElement: ElementsTreeElement|null;
+ isUpdatingHighlights: boolean;
alreadyExpandedParentTreeElement: ElementsTreeElement|null;
}
@@ -136,6 +137,7 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
// Node highlighting logic. FIXME: express as a lit template.
const previousHighlightedNode = output.highlightedTreeElement?.node() ?? null;
if (previousHighlightedNode !== input.currentHighlightedNode) {
+ output.isUpdatingHighlights = true;
let treeElement: ElementsTreeElement|null = null;
if (output.highlightedTreeElement) {
@@ -173,6 +175,7 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
output.highlightedTreeElement = treeElement;
output.elementsTreeOutline.setHoverEffect(treeElement);
treeElement?.reveal(true);
+ output.isUpdatingHighlights = false;
}
};
@@ -225,6 +228,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
#viewOutput: ViewOutput = {
highlightedTreeElement: null,
alreadyExpandedParentTreeElement: null,
+ isUpdatingHighlights: false,
};
#highlightThrottler = new Common.Throttler.Throttler(100);
@@ -239,8 +243,8 @@ export class DOMTreeWidget extends UI.Widget.Widget {
SDK.OverlayModel.OverlayModel, SDK.OverlayModel.Events.HIGHLIGHT_NODE_REQUESTED, this.#highlightNode, this,
{scoped: true});
SDK.TargetManager.TargetManager.instance().addModelListener(
- SDK.OverlayModel.OverlayModel, SDK.OverlayModel.Events.INSPECT_MODE_WILL_BE_TOGGLED, this.#clearState, this,
- {scoped: true});
+ SDK.OverlayModel.OverlayModel, SDK.OverlayModel.Events.INSPECT_MODE_WILL_BE_TOGGLED,
+ this.#clearHighlightedNode, this, {scoped: true});
}
}
@@ -251,7 +255,13 @@ export class DOMTreeWidget extends UI.Widget.Widget {
});
}
- #clearState(): void {
+ #clearHighlightedNode(): void {
+ // Highlighting an element via tree outline will emit the
+ // INSPECT_MODE_WILL_BE_TOGGLED event, therefore, we skip it if the view
+ // informed us that it is updating the element.
+ if (this.#viewOutput.isUpdatingHighlights) {
+ return;
+ }
this.#currentHighlightedNode = null;
this.requestUpdate();
}
@@ -305,11 +315,11 @@ export class DOMTreeWidget extends UI.Widget.Widget {
currentHighlightedNode: this.#currentHighlightedNode,
onElementsTreeUpdated: this.onElementsTreeUpdated.bind(this),
onSelectedNodeChanged: event => {
- this.#clearState();
+ this.#clearHighlightedNode();
this.onSelectedNodeChanged(event);
},
- onElementCollapsed: this.#clearState.bind(this),
- onElementExpanded: this.#clearState.bind(this),
+ onElementCollapsed: this.#clearHighlightedNode.bind(this),
+ onElementExpanded: this.#clearHighlightedNode.bind(this),
},
this.#viewOutput, this.contentElement);
}

View File

@@ -7,3 +7,4 @@ fix_support_new_variant_of_namedpropertyhandlerconfiguration_and.patch
fix_correct_usages_of_v8_returnvalue_void_set_nonempty_for_new.patch
chore_remove_deprecated_functioncallbackinfo_holder.patch
fix_replace_deprecated_get_setprototype.patch
chore_add_yarnrc_yml_and_yarn_lock_file_to_use_yarn_v4.patch

File diff suppressed because it is too large Load Diff

View File

@@ -53,3 +53,7 @@ fix_replace_deprecated_setprototype.patch
fix_redefined_macos_sdk_header_symbols.patch
src_use_cp_utf8_for_wide_file_names_on_win32.patch
fix_ensure_traverseparent_bails_on_resource_path_exit.patch
src_handle_der_decoding_errors_from_system_certificates.patch
remove_obsolete_noarraybufferzerofillscope.patch
src_prepare_for_v8_sandboxing.patch
test_correct_conditional_secure_heap_flags_test.patch

View File

@@ -6,10 +6,10 @@ Subject: Remove deprecated `GetIsolate`
https://chromium-review.googlesource.com/c/v8/v8/+/6905244
diff --git a/src/api/environment.cc b/src/api/environment.cc
index 8e227ddd1be50c046a8cf2895a31d607eb7d31de..82f53bba29613de212f64be440ca20d7c630fddf 100644
index ceac508418f489a8077c1bc85a2feaf85bf60480..33827edce63c9fe08b52aea59571391a83853443 100644
--- a/src/api/environment.cc
+++ b/src/api/environment.cc
@@ -654,7 +654,7 @@ std::unique_ptr<MultiIsolatePlatform> MultiIsolatePlatform::Create(
@@ -646,7 +646,7 @@ std::unique_ptr<MultiIsolatePlatform> MultiIsolatePlatform::Create(
MaybeLocal<Object> GetPerContextExports(Local<Context> context,
IsolateData* isolate_data) {
@@ -18,7 +18,7 @@ index 8e227ddd1be50c046a8cf2895a31d607eb7d31de..82f53bba29613de212f64be440ca20d7
EscapableHandleScope handle_scope(isolate);
Local<Object> global = context->Global();
@@ -700,7 +700,7 @@ void ProtoThrower(const FunctionCallbackInfo<Value>& info) {
@@ -692,7 +692,7 @@ void ProtoThrower(const FunctionCallbackInfo<Value>& info) {
// This runs at runtime, regardless of whether the context
// is created from a snapshot.
Maybe<void> InitializeContextRuntime(Local<Context> context) {
@@ -27,7 +27,7 @@ index 8e227ddd1be50c046a8cf2895a31d607eb7d31de..82f53bba29613de212f64be440ca20d7
HandleScope handle_scope(isolate);
// When `IsCodeGenerationFromStringsAllowed` is true, V8 takes the fast path
@@ -779,7 +779,7 @@ Maybe<void> InitializeContextRuntime(Local<Context> context) {
@@ -771,7 +771,7 @@ Maybe<void> InitializeContextRuntime(Local<Context> context) {
}
Maybe<void> InitializeBaseContextForSnapshot(Local<Context> context) {
@@ -36,7 +36,7 @@ index 8e227ddd1be50c046a8cf2895a31d607eb7d31de..82f53bba29613de212f64be440ca20d7
HandleScope handle_scope(isolate);
// Delete `Intl.v8BreakIterator`
@@ -804,7 +804,7 @@ Maybe<void> InitializeBaseContextForSnapshot(Local<Context> context) {
@@ -796,7 +796,7 @@ Maybe<void> InitializeBaseContextForSnapshot(Local<Context> context) {
}
Maybe<void> InitializeMainContextForSnapshot(Local<Context> context) {
@@ -45,7 +45,7 @@ index 8e227ddd1be50c046a8cf2895a31d607eb7d31de..82f53bba29613de212f64be440ca20d7
HandleScope handle_scope(isolate);
// Initialize the default values.
@@ -822,7 +822,7 @@ Maybe<void> InitializeMainContextForSnapshot(Local<Context> context) {
@@ -814,7 +814,7 @@ Maybe<void> InitializeMainContextForSnapshot(Local<Context> context) {
MaybeLocal<Object> InitializePrivateSymbols(Local<Context> context,
IsolateData* isolate_data) {
CHECK(isolate_data);
@@ -54,7 +54,7 @@ index 8e227ddd1be50c046a8cf2895a31d607eb7d31de..82f53bba29613de212f64be440ca20d7
EscapableHandleScope scope(isolate);
Context::Scope context_scope(context);
@@ -846,7 +846,7 @@ MaybeLocal<Object> InitializePrivateSymbols(Local<Context> context,
@@ -838,7 +838,7 @@ MaybeLocal<Object> InitializePrivateSymbols(Local<Context> context,
MaybeLocal<Object> InitializePerIsolateSymbols(Local<Context> context,
IsolateData* isolate_data) {
CHECK(isolate_data);
@@ -63,7 +63,7 @@ index 8e227ddd1be50c046a8cf2895a31d607eb7d31de..82f53bba29613de212f64be440ca20d7
EscapableHandleScope scope(isolate);
Context::Scope context_scope(context);
@@ -872,7 +872,7 @@ MaybeLocal<Object> InitializePerIsolateSymbols(Local<Context> context,
@@ -864,7 +864,7 @@ MaybeLocal<Object> InitializePerIsolateSymbols(Local<Context> context,
Maybe<void> InitializePrimordials(Local<Context> context,
IsolateData* isolate_data) {
// Run per-context JS files.
@@ -508,10 +508,10 @@ index 492d5f455f45a5c8a957ecdabed38709a633f640..48f9917113555c7ed87e37750c45d152
Local<Array> keys;
if (!entries->GetOwnPropertyNames(context).ToLocal(&keys))
diff --git a/src/node_errors.cc b/src/node_errors.cc
index 4386a1bc5678e351ce084cd2c47202561619b164..8d51201ad24999ed8f54e16c7878432d41841cf2 100644
index 36a21b9523351fe2f225ffe7fca184d737640b62..c6e9e2c00064348c435ae10871a4013cb49376e6 100644
--- a/src/node_errors.cc
+++ b/src/node_errors.cc
@@ -633,7 +633,7 @@ v8::ModifyCodeGenerationFromStringsResult ModifyCodeGenerationFromStrings(
@@ -631,7 +631,7 @@ v8::ModifyCodeGenerationFromStringsResult ModifyCodeGenerationFromStrings(
v8::Local<v8::Context> context,
v8::Local<v8::Value> source,
bool is_code_like) {
@@ -520,7 +520,7 @@ index 4386a1bc5678e351ce084cd2c47202561619b164..8d51201ad24999ed8f54e16c7878432d
if (context->GetNumberOfEmbedderDataFields() <=
ContextEmbedderIndex::kAllowCodeGenerationFromStrings) {
@@ -1000,7 +1000,7 @@ const char* errno_string(int errorno) {
@@ -1037,7 +1037,7 @@ const char* errno_string(int errorno) {
}
void PerIsolateMessageListener(Local<Message> message, Local<Value> error) {
@@ -529,7 +529,7 @@ index 4386a1bc5678e351ce084cd2c47202561619b164..8d51201ad24999ed8f54e16c7878432d
switch (message->ErrorLevel()) {
case Isolate::MessageErrorLevel::kMessageWarning: {
Environment* env = Environment::GetCurrent(isolate);
@@ -1161,7 +1161,7 @@ void Initialize(Local<Object> target,
@@ -1198,7 +1198,7 @@ void Initialize(Local<Object> target,
SetMethod(
context, target, "getErrorSourcePositions", GetErrorSourcePositions);
@@ -640,7 +640,7 @@ index 66a8ee48fc68b22eaf6c9d9209cc5cb2439e55ff..b31e244f8af7d37c35319853a478776c
env->AssignToContext(context, this, ContextInfo(""));
}
diff --git a/src/node_report.cc b/src/node_report.cc
index 8ff711f12e19f73e50daac2b9c0fd26773d32758..6966710e8f0be542364850776ddf76f9223a02e7 100644
index e1f4e30417d17f85d2294f3a01e6d90f0a443948..12a59307b452b0d67ebe909cae0458b8a0ca085b 100644
--- a/src/node_report.cc
+++ b/src/node_report.cc
@@ -399,7 +399,7 @@ static void PrintJavaScriptErrorProperties(JSONWriter* writer,

View File

@@ -8,10 +8,10 @@ they use themselves as the entry point. We should try to upstream some form
of this.
diff --git a/lib/internal/process/pre_execution.js b/lib/internal/process/pre_execution.js
index d12d21905c4823f45cffeea4423e99e81b1008f5..4987cd3f6c9eefb440bca3f58113df6cd5b410ac 100644
index 15443a710ccf53fae333da3b1fbb52a970c658d5..464b34829c1a566836bfca6bbc2b87fcf5e50016 100644
--- a/lib/internal/process/pre_execution.js
+++ b/lib/internal/process/pre_execution.js
@@ -267,12 +267,14 @@ function patchProcessObject(expandArgv1) {
@@ -265,12 +265,14 @@ function patchProcessObject(expandArgv1) {
// the entry point.
if (expandArgv1 && process.argv[1] && process.argv[1][0] !== '-') {
// Expand process.argv[1] into a full path.

View File

@@ -86,10 +86,10 @@ index 0ca643aa74d13f278685d2330b791182b55c15b4..cbcecfba33070b820aca0e2814982160
NODE_DEFINE_CONSTANT(target, ETIMEDOUT);
#endif
diff --git a/src/node_errors.cc b/src/node_errors.cc
index ae8553ee2022d60fea4572976b14ba9cd253aa45..4386a1bc5678e351ce084cd2c47202561619b164 100644
index 238942d45a136facec55ca5a2534e2dc407137e9..36a21b9523351fe2f225ffe7fca184d737640b62 100644
--- a/src/node_errors.cc
+++ b/src/node_errors.cc
@@ -862,10 +862,6 @@ const char* errno_string(int errorno) {
@@ -899,10 +899,6 @@ const char* errno_string(int errorno) {
ERRNO_CASE(ENOBUFS);
#endif
@@ -100,7 +100,7 @@ index ae8553ee2022d60fea4572976b14ba9cd253aa45..4386a1bc5678e351ce084cd2c4720256
#ifdef ENODEV
ERRNO_CASE(ENODEV);
#endif
@@ -904,14 +900,6 @@ const char* errno_string(int errorno) {
@@ -941,14 +937,6 @@ const char* errno_string(int errorno) {
ERRNO_CASE(ENOSPC);
#endif
@@ -115,7 +115,7 @@ index ae8553ee2022d60fea4572976b14ba9cd253aa45..4386a1bc5678e351ce084cd2c4720256
#ifdef ENOSYS
ERRNO_CASE(ENOSYS);
#endif
@@ -994,10 +982,6 @@ const char* errno_string(int errorno) {
@@ -1031,10 +1019,6 @@ const char* errno_string(int errorno) {
ERRNO_CASE(ESTALE);
#endif

View File

@@ -48,7 +48,7 @@ index fe669d40c31a29334b047b9cfee3067f64ef0a7b..9e5de7bbe574add017cd12ee091304d0
static CFunction fast_timing_safe_equal(CFunction::Make(FastTimingSafeEqual));
diff --git a/src/node_buffer.cc b/src/node_buffer.cc
index e39852c8e0392e0a9ae5d4ea58be115416e19233..c94b14741c827a81d69a6f036426a344e563ad72 100644
index b9f0c97938203b4652780a7d707c5e83319330b0..8a5b6b57321c2843a965a7e51b2ebed991a1e424 100644
--- a/src/node_buffer.cc
+++ b/src/node_buffer.cc
@@ -44,6 +44,14 @@
@@ -74,7 +74,7 @@ index e39852c8e0392e0a9ae5d4ea58be115416e19233..c94b14741c827a81d69a6f036426a344
using v8::FunctionCallbackInfo;
using v8::Global;
using v8::HandleScope;
@@ -584,19 +591,24 @@ void SlowCopy(const FunctionCallbackInfo<Value>& args) {
@@ -583,19 +590,24 @@ void SlowCopy(const FunctionCallbackInfo<Value>& args) {
// Assume caller has properly validated args.
uint32_t FastCopy(Local<Value> receiver,
@@ -107,7 +107,7 @@ index e39852c8e0392e0a9ae5d4ea58be115416e19233..c94b14741c827a81d69a6f036426a344
return to_copy;
}
@@ -865,19 +877,17 @@ void Compare(const FunctionCallbackInfo<Value> &args) {
@@ -864,19 +876,17 @@ void Compare(const FunctionCallbackInfo<Value> &args) {
}
int32_t FastCompare(v8::Local<v8::Value>,
@@ -135,7 +135,7 @@ index e39852c8e0392e0a9ae5d4ea58be115416e19233..c94b14741c827a81d69a6f036426a344
}
static v8::CFunction fast_compare(v8::CFunction::Make(FastCompare));
@@ -1149,14 +1159,13 @@ void SlowIndexOfNumber(const FunctionCallbackInfo<Value>& args) {
@@ -1148,14 +1158,13 @@ void SlowIndexOfNumber(const FunctionCallbackInfo<Value>& args) {
}
int32_t FastIndexOfNumber(v8::Local<v8::Value>,
@@ -153,7 +153,7 @@ index e39852c8e0392e0a9ae5d4ea58be115416e19233..c94b14741c827a81d69a6f036426a344
}
static v8::CFunction fast_index_of_number(
@@ -1496,21 +1505,31 @@ void SlowWriteString(const FunctionCallbackInfo<Value>& args) {
@@ -1510,21 +1519,31 @@ void SlowWriteString(const FunctionCallbackInfo<Value>& args) {
template <encoding encoding>
uint32_t FastWriteString(Local<Value> receiver,

View File

@@ -9,10 +9,10 @@ This is already applied in newer versions of Node so we can drop
this patch once we upgrade to v23.
diff --git a/src/api/environment.cc b/src/api/environment.cc
index 0a358735c331767e8eb563a80e9aaccfb544c27b..d7d18d5fcbf008ac131db189a141315af4f6410b 100644
index 79327877b0f34cbafb3efcc21617027d4011f806..8a12475857135bd3e904610b7eb887c397c8e73c 100644
--- a/src/api/environment.cc
+++ b/src/api/environment.cc
@@ -835,7 +835,7 @@ MaybeLocal<Object> InitializePrivateSymbols(Local<Context> context,
@@ -827,7 +827,7 @@ MaybeLocal<Object> InitializePrivateSymbols(Local<Context> context,
Local<Object> private_symbols_object;
if (!private_symbols->NewInstance(context).ToLocal(&private_symbols_object) ||
@@ -21,7 +21,7 @@ index 0a358735c331767e8eb563a80e9aaccfb544c27b..d7d18d5fcbf008ac131db189a141315a
.IsNothing()) {
return MaybeLocal<Object>();
}
@@ -861,7 +861,7 @@ MaybeLocal<Object> InitializePerIsolateSymbols(Local<Context> context,
@@ -853,7 +853,7 @@ MaybeLocal<Object> InitializePerIsolateSymbols(Local<Context> context,
Local<Object> per_isolate_symbols_object;
if (!per_isolate_symbols->NewInstance(context).ToLocal(
&per_isolate_symbols_object) ||

View File

@@ -18,10 +18,10 @@ This can be removed when Node.js upgrades to a version of V8 containing CLs
from the above issue.
diff --git a/src/api/environment.cc b/src/api/environment.cc
index cb37fa080fc8e8d524cfa2758c4a8c2c5652324d..8e227ddd1be50c046a8cf2895a31d607eb7d31de 100644
index fd71ceac65ccef1d2832b45b0b5612877cee22c1..ceac508418f489a8077c1bc85a2feaf85bf60480 100644
--- a/src/api/environment.cc
+++ b/src/api/environment.cc
@@ -316,6 +316,10 @@ Isolate* NewIsolate(Isolate::CreateParams* params,
@@ -308,6 +308,10 @@ Isolate* NewIsolate(Isolate::CreateParams* params,
MultiIsolatePlatform* platform,
const SnapshotData* snapshot_data,
const IsolateSettings& settings) {
@@ -32,7 +32,7 @@ index cb37fa080fc8e8d524cfa2758c4a8c2c5652324d..8e227ddd1be50c046a8cf2895a31d607
Isolate* isolate = Isolate::Allocate();
if (isolate == nullptr) return nullptr;
@@ -359,9 +363,12 @@ Isolate* NewIsolate(ArrayBufferAllocator* allocator,
@@ -351,9 +355,12 @@ Isolate* NewIsolate(ArrayBufferAllocator* allocator,
uv_loop_t* event_loop,
MultiIsolatePlatform* platform,
const EmbedderSnapshotData* snapshot_data,

View File

@@ -0,0 +1,653 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Robo <hop2deep@gmail.com>
Date: Wed, 21 Jan 2026 09:53:15 +0000
Subject: remove obsolete NoArrayBufferZeroFillScope
Replace the scope in favor of the V8 api added in
https://chromium-review.googlesource.com/c/v8/v8/+/5679067
Ports changes from
1) https://github.com/nodejs/node/commit/869ea331f3a8215229290e2e6038956874c382a6
2) https://github.com/nodejs/node/commit/ef9dc0857a73610f5de5dc9f37afd0a927c4c17f
3) partially from https://github.com/nodejs/node/commit/e0a71517fef4ca83f2d40d2d1600022bc82a7f9f
This is needed to remove dependency on the zero_fill_field_
that is exposed to JS
Refs https://github.com/nodejs/node/commit/3cdb1cd437f63dd256ae2ab3b7e9016257326cb4
diff --git a/src/api/environment.cc b/src/api/environment.cc
index 8a12475857135bd3e904610b7eb887c397c8e73c..3702672f4e2c3b1bff9f9f5c66ca251af447826a 100644
--- a/src/api/environment.cc
+++ b/src/api/environment.cc
@@ -107,11 +107,7 @@ MaybeLocal<Value> PrepareStackTraceCallback(Local<Context> context,
}
void* NodeArrayBufferAllocator::Allocate(size_t size) {
- void* ret;
- if (zero_fill_field_ || per_process::cli_options->zero_fill_all_buffers)
- ret = allocator_->Allocate(size);
- else
- ret = allocator_->AllocateUninitialized(size);
+ void* ret = allocator_->Allocate(size);
if (ret != nullptr) [[likely]] {
total_mem_usage_.fetch_add(size, std::memory_order_relaxed);
}
diff --git a/src/crypto/crypto_cipher.cc b/src/crypto/crypto_cipher.cc
index c00d3616e08b00b1e0a3a29b2dbb5278e1e14fcc..8939c5e5085d00b098f66074b9ee033f5be55d08 100644
--- a/src/crypto/crypto_cipher.cc
+++ b/src/crypto/crypto_cipher.cc
@@ -20,6 +20,7 @@ using ncrypto::SSLPointer;
using v8::Array;
using v8::ArrayBuffer;
using v8::BackingStore;
+using v8::BackingStoreInitializationMode;
using v8::Context;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
@@ -774,10 +775,10 @@ CipherBase::UpdateResult CipherBase::Update(
return kErrorState;
}
- {
- NoArrayBufferZeroFillScope no_zero_fill_scope(env()->isolate_data());
- *out = ArrayBuffer::NewBackingStore(env()->isolate(), buf_len);
- }
+ *out = ArrayBuffer::NewBackingStore(
+ env()->isolate(),
+ buf_len,
+ BackingStoreInitializationMode::kUninitialized);
buffer = {
.data = reinterpret_cast<const unsigned char*>(data),
@@ -852,11 +853,10 @@ bool CipherBase::Final(std::unique_ptr<BackingStore>* out) {
const int mode = ctx_.getMode();
- {
- NoArrayBufferZeroFillScope no_zero_fill_scope(env()->isolate_data());
- *out = ArrayBuffer::NewBackingStore(
- env()->isolate(), static_cast<size_t>(ctx_.getBlockSize()));
- }
+ *out = ArrayBuffer::NewBackingStore(
+ env()->isolate(),
+ static_cast<size_t>(ctx_.getBlockSize()),
+ BackingStoreInitializationMode::kUninitialized);
if (kind_ == kDecipher &&
Cipher::FromCtx(ctx_).isSupportedAuthenticatedMode()) {
@@ -972,10 +972,10 @@ bool PublicKeyCipher::Cipher(
return false;
}
- {
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
- *out = ArrayBuffer::NewBackingStore(env->isolate(), out_len);
- }
+ *out = ArrayBuffer::NewBackingStore(
+ env->isolate(),
+ out_len,
+ BackingStoreInitializationMode::kUninitialized);
if (EVP_PKEY_cipher(
ctx.get(),
diff --git a/src/crypto/crypto_common.cc b/src/crypto/crypto_common.cc
index b81b9005365272217c77e2b9289bd9f877c0e77c..185b1d8fe657b5db64dc92497ca742d69f7a2764 100644
--- a/src/crypto/crypto_common.cc
+++ b/src/crypto/crypto_common.cc
@@ -37,6 +37,7 @@ using ncrypto::X509Pointer;
using ncrypto::X509View;
using v8::ArrayBuffer;
using v8::BackingStore;
+using v8::BackingStoreInitializationMode;
using v8::Context;
using v8::EscapableHandleScope;
using v8::Integer;
@@ -307,11 +308,10 @@ MaybeLocal<Object> ECPointToBuffer(Environment* env,
return MaybeLocal<Object>();
}
- std::unique_ptr<BackingStore> bs;
- {
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
- bs = ArrayBuffer::NewBackingStore(env->isolate(), len);
- }
+ std::unique_ptr<BackingStore> bs = ArrayBuffer::NewBackingStore(
+ env->isolate(),
+ len,
+ BackingStoreInitializationMode::kUninitialized);
len = EC_POINT_point2oct(group,
point,
diff --git a/src/crypto/crypto_ec.cc b/src/crypto/crypto_ec.cc
index 2a3107dbbf5c0dfe70c2e105338d186e5620ddbf..f36c84a77313bd57d0a7a902d1a2529636ca1422 100644
--- a/src/crypto/crypto_ec.cc
+++ b/src/crypto/crypto_ec.cc
@@ -29,6 +29,7 @@ using ncrypto::MarkPopErrorOnReturn;
using v8::Array;
using v8::ArrayBuffer;
using v8::BackingStore;
+using v8::BackingStoreInitializationMode;
using v8::Context;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
@@ -201,14 +202,10 @@ void ECDH::ComputeSecret(const FunctionCallbackInfo<Value>& args) {
return;
}
- std::unique_ptr<BackingStore> bs;
- {
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
- // NOTE: field_size is in bits
- int field_size = EC_GROUP_get_degree(ecdh->group_);
- size_t out_len = (field_size + 7) / 8;
- bs = ArrayBuffer::NewBackingStore(env->isolate(), out_len);
- }
+ int field_size = EC_GROUP_get_degree(ecdh->group_);
+ size_t out_len = (field_size + 7) / 8;
+ auto bs = ArrayBuffer::NewBackingStore(
+ env->isolate(), out_len, BackingStoreInitializationMode::kUninitialized);
if (!ECDH_compute_key(
bs->Data(), bs->ByteLength(), pub, ecdh->key_.get(), nullptr))
@@ -257,12 +254,10 @@ void ECDH::GetPrivateKey(const FunctionCallbackInfo<Value>& args) {
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to get ECDH private key");
- std::unique_ptr<BackingStore> bs;
- {
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
- bs = ArrayBuffer::NewBackingStore(env->isolate(),
- BignumPointer::GetByteCount(b));
- }
+ auto bs = ArrayBuffer::NewBackingStore(
+ env->isolate(),
+ BignumPointer::GetByteCount(b),
+ BackingStoreInitializationMode::kUninitialized);
CHECK_EQ(bs->ByteLength(),
BignumPointer::EncodePaddedInto(
b, static_cast<unsigned char*>(bs->Data()), bs->ByteLength()));
diff --git a/src/crypto/crypto_rsa.cc b/src/crypto/crypto_rsa.cc
index 1f2fccce6ed8f14525557644e0bdd130eedf3337..1099a8f89bb53083f01942ee14fff453d8cdc0af 100644
--- a/src/crypto/crypto_rsa.cc
+++ b/src/crypto/crypto_rsa.cc
@@ -20,6 +20,7 @@ using ncrypto::EVPKeyPointer;
using ncrypto::RSAPointer;
using v8::ArrayBuffer;
using v8::BackingStore;
+using v8::BackingStoreInitializationMode;
using v8::FunctionCallbackInfo;
using v8::Int32;
using v8::JustVoid;
@@ -535,12 +536,10 @@ Maybe<void> GetRsaKeyDetail(Environment* env,
return Nothing<void>();
}
- std::unique_ptr<BackingStore> public_exponent;
- {
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
- public_exponent = ArrayBuffer::NewBackingStore(
- env->isolate(), BignumPointer::GetByteCount(e));
- }
+ auto public_exponent = ArrayBuffer::NewBackingStore(
+ env->isolate(),
+ BignumPointer::GetByteCount(e),
+ BackingStoreInitializationMode::kUninitialized);
CHECK_EQ(BignumPointer::EncodePaddedInto(
e,
static_cast<unsigned char*>(public_exponent->Data()),
diff --git a/src/crypto/crypto_sig.cc b/src/crypto/crypto_sig.cc
index 2f6e683e3497d4315259773d09443e5215bff28f..c33e93c34ef32c18e6de6bc03698ed6b851b4aa3 100644
--- a/src/crypto/crypto_sig.cc
+++ b/src/crypto/crypto_sig.cc
@@ -21,6 +21,7 @@ using ncrypto::EVPKeyPointer;
using ncrypto::EVPMDCtxPointer;
using v8::ArrayBuffer;
using v8::BackingStore;
+using v8::BackingStoreInitializationMode;
using v8::Boolean;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
@@ -92,11 +93,8 @@ std::unique_ptr<BackingStore> Node_SignFinal(Environment* env,
return nullptr;
size_t sig_len = pkey.size();
- std::unique_ptr<BackingStore> sig;
- {
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
- sig = ArrayBuffer::NewBackingStore(env->isolate(), sig_len);
- }
+ auto sig = ArrayBuffer::NewBackingStore(
+ env->isolate(), sig_len, BackingStoreInitializationMode::kUninitialized);
EVPKeyCtxPointer pkctx = pkey.newCtx();
if (pkctx && EVP_PKEY_sign_init(pkctx.get()) > 0 &&
ApplyRSAOptions(pkey, pkctx.get(), padding, pss_salt_len) &&
@@ -168,11 +166,9 @@ std::unique_ptr<BackingStore> ConvertSignatureToP1363(
if (n == kNoDsaSignature)
return std::move(signature);
- std::unique_ptr<BackingStore> buf;
- {
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
- buf = ArrayBuffer::NewBackingStore(env->isolate(), 2 * n);
- }
+ auto buf = ArrayBuffer::NewBackingStore(
+ env->isolate(), 2 * n, BackingStoreInitializationMode::kUninitialized);
+
if (!ExtractP1363(static_cast<unsigned char*>(signature->Data()),
static_cast<unsigned char*>(buf->Data()),
signature->ByteLength(), n))
diff --git a/src/crypto/crypto_tls.cc b/src/crypto/crypto_tls.cc
index a80685790bd29102d99929ff81f866e0aee370f1..24336b86f6f25533a7b668e9f9806a5635e3a398 100644
--- a/src/crypto/crypto_tls.cc
+++ b/src/crypto/crypto_tls.cc
@@ -46,6 +46,7 @@ using v8::Array;
using v8::ArrayBuffer;
using v8::ArrayBufferView;
using v8::BackingStore;
+using v8::BackingStoreInitializationMode;
using v8::Boolean;
using v8::Context;
using v8::DontDelete;
@@ -1087,10 +1088,10 @@ int TLSWrap::DoWrite(WriteWrap* w,
// and copying it when it could just be used.
if (nonempty_count != 1) {
- {
- NoArrayBufferZeroFillScope no_zero_fill_scope(env()->isolate_data());
- bs = ArrayBuffer::NewBackingStore(env()->isolate(), length);
- }
+ bs = ArrayBuffer::NewBackingStore(
+ env()->isolate(),
+ length,
+ BackingStoreInitializationMode::kUninitialized);
size_t offset = 0;
for (i = 0; i < count; i++) {
memcpy(static_cast<char*>(bs->Data()) + offset,
@@ -1107,8 +1108,10 @@ int TLSWrap::DoWrite(WriteWrap* w,
written = SSL_write(ssl_.get(), buf->base, buf->len);
if (written == -1) {
- NoArrayBufferZeroFillScope no_zero_fill_scope(env()->isolate_data());
- bs = ArrayBuffer::NewBackingStore(env()->isolate(), length);
+ bs = ArrayBuffer::NewBackingStore(
+ env()->isolate(),
+ length,
+ BackingStoreInitializationMode::kUninitialized);
memcpy(bs->Data(), buf->base, buf->len);
}
}
@@ -1746,11 +1749,8 @@ void TLSWrap::GetFinished(const FunctionCallbackInfo<Value>& args) {
if (len == 0)
return;
- std::unique_ptr<BackingStore> bs;
- {
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
- bs = ArrayBuffer::NewBackingStore(env->isolate(), len);
- }
+ auto bs = ArrayBuffer::NewBackingStore(
+ env->isolate(), len, BackingStoreInitializationMode::kUninitialized);
CHECK_EQ(bs->ByteLength(),
SSL_get_finished(w->ssl_.get(), bs->Data(), bs->ByteLength()));
@@ -1777,11 +1777,8 @@ void TLSWrap::GetPeerFinished(const FunctionCallbackInfo<Value>& args) {
if (len == 0)
return;
- std::unique_ptr<BackingStore> bs;
- {
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
- bs = ArrayBuffer::NewBackingStore(env->isolate(), len);
- }
+ auto bs = ArrayBuffer::NewBackingStore(
+ env->isolate(), len, BackingStoreInitializationMode::kUninitialized);
CHECK_EQ(bs->ByteLength(),
SSL_get_peer_finished(w->ssl_.get(), bs->Data(), bs->ByteLength()));
@@ -1806,11 +1803,8 @@ void TLSWrap::GetSession(const FunctionCallbackInfo<Value>& args) {
if (slen <= 0)
return; // Invalid or malformed session.
- std::unique_ptr<BackingStore> bs;
- {
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
- bs = ArrayBuffer::NewBackingStore(env->isolate(), slen);
- }
+ auto bs = ArrayBuffer::NewBackingStore(
+ env->isolate(), slen, BackingStoreInitializationMode::kUninitialized);
unsigned char* p = static_cast<unsigned char*>(bs->Data());
CHECK_LT(0, i2d_SSL_SESSION(sess, &p));
@@ -1993,11 +1987,8 @@ void TLSWrap::ExportKeyingMaterial(const FunctionCallbackInfo<Value>& args) {
uint32_t olen = args[0].As<Uint32>()->Value();
Utf8Value label(env->isolate(), args[1]);
- std::unique_ptr<BackingStore> bs;
- {
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
- bs = ArrayBuffer::NewBackingStore(env->isolate(), olen);
- }
+ auto bs = ArrayBuffer::NewBackingStore(
+ env->isolate(), olen, BackingStoreInitializationMode::kUninitialized);
ByteSource context;
bool use_context = !args[2]->IsUndefined();
diff --git a/src/crypto/crypto_x509.cc b/src/crypto/crypto_x509.cc
index fd29d17de195017970856ce30d7a9c5785b0b8ee..7fe3d09851d4476fa3f77ea0a0b49e8af13fae4f 100644
--- a/src/crypto/crypto_x509.cc
+++ b/src/crypto/crypto_x509.cc
@@ -28,6 +28,7 @@ using v8::Array;
using v8::ArrayBuffer;
using v8::ArrayBufferView;
using v8::BackingStore;
+using v8::BackingStoreInitializationMode;
using v8::Boolean;
using v8::Context;
using v8::Date;
@@ -683,11 +684,8 @@ MaybeLocal<Object> GetPubKey(Environment* env, OSSL3_CONST RSA* rsa) {
int size = i2d_RSA_PUBKEY(rsa, nullptr);
CHECK_GE(size, 0);
- std::unique_ptr<BackingStore> bs;
- {
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
- bs = ArrayBuffer::NewBackingStore(env->isolate(), size);
- }
+ auto bs = ArrayBuffer::NewBackingStore(
+ env->isolate(), size, BackingStoreInitializationMode::kUninitialized);
unsigned char* serialized = reinterpret_cast<unsigned char*>(bs->Data());
CHECK_GE(i2d_RSA_PUBKEY(rsa, &serialized), 0);
diff --git a/src/encoding_binding.cc b/src/encoding_binding.cc
index 5ace688bb7ffc86eedf5aff11ab0ab487ad9440e..31058543a164bb45ba989e8c433994e0857a98b9 100644
--- a/src/encoding_binding.cc
+++ b/src/encoding_binding.cc
@@ -15,6 +15,7 @@ namespace encoding_binding {
using v8::ArrayBuffer;
using v8::BackingStore;
+using v8::BackingStoreInitializationMode;
using v8::Context;
using v8::FunctionCallbackInfo;
using v8::Isolate;
@@ -123,9 +124,8 @@ void BindingData::EncodeUtf8String(const FunctionCallbackInfo<Value>& args) {
Local<ArrayBuffer> ab;
{
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
- std::unique_ptr<BackingStore> bs =
- ArrayBuffer::NewBackingStore(isolate, length);
+ std::unique_ptr<BackingStore> bs = ArrayBuffer::NewBackingStore(
+ isolate, length, BackingStoreInitializationMode::kUninitialized);
CHECK(bs);
diff --git a/src/env-inl.h b/src/env-inl.h
index 98e1e1e75bae94038bba0049447ab48b0acfb8cc..fe395bf89f9c1e5bb2dabc8fceda7b9b2b877415 100644
--- a/src/env-inl.h
+++ b/src/env-inl.h
@@ -44,16 +44,6 @@
namespace node {
-NoArrayBufferZeroFillScope::NoArrayBufferZeroFillScope(
- IsolateData* isolate_data)
- : node_allocator_(isolate_data->node_allocator()) {
- if (node_allocator_ != nullptr) node_allocator_->zero_fill_field()[0] = 0;
-}
-
-NoArrayBufferZeroFillScope::~NoArrayBufferZeroFillScope() {
- if (node_allocator_ != nullptr) node_allocator_->zero_fill_field()[0] = 1;
-}
-
inline v8::Isolate* IsolateData::isolate() const {
return isolate_;
}
diff --git a/src/env.cc b/src/env.cc
index 161d577e0ea6a251c83ba1903b1ec9a582a5317c..52713421465c23c959afac1955168294b2048341 100644
--- a/src/env.cc
+++ b/src/env.cc
@@ -39,6 +39,9 @@ namespace node {
using errors::TryCatchScope;
using v8::Array;
+using v8::ArrayBuffer;
+using v8::BackingStore;
+using v8::BackingStoreInitializationMode;
using v8::Boolean;
using v8::Context;
using v8::CppHeap;
@@ -724,9 +727,10 @@ void Environment::add_refs(int64_t diff) {
}
uv_buf_t Environment::allocate_managed_buffer(const size_t suggested_size) {
- NoArrayBufferZeroFillScope no_zero_fill_scope(isolate_data());
- std::unique_ptr<v8::BackingStore> bs =
- v8::ArrayBuffer::NewBackingStore(isolate(), suggested_size);
+ std::unique_ptr<BackingStore> bs = ArrayBuffer::NewBackingStore(
+ isolate(),
+ suggested_size,
+ BackingStoreInitializationMode::kUninitialized);
uv_buf_t buf = uv_buf_init(static_cast<char*>(bs->Data()), bs->ByteLength());
released_allocated_buffers_.emplace(buf.base, std::move(bs));
return buf;
diff --git a/src/env.h b/src/env.h
index c346e3a9c827993036438685d758a734f9ce8c05..28c8df87c8e2f06e2ed8c554260bfdedb860bb4a 100644
--- a/src/env.h
+++ b/src/env.h
@@ -114,19 +114,6 @@ class ModuleWrap;
class Environment;
class Realm;
-// Disables zero-filling for ArrayBuffer allocations in this scope. This is
-// similar to how we implement Buffer.allocUnsafe() in JS land.
-class NoArrayBufferZeroFillScope {
- public:
- inline explicit NoArrayBufferZeroFillScope(IsolateData* isolate_data);
- inline ~NoArrayBufferZeroFillScope();
-
- private:
- NodeArrayBufferAllocator* node_allocator_;
-
- friend class Environment;
-};
-
struct IsolateDataSerializeInfo {
std::vector<SnapshotIndex> primitive_values;
std::vector<PropInfo> template_values;
diff --git a/src/node_buffer.cc b/src/node_buffer.cc
index e844fe6cb33acefd075516e675075421ad5c3cff..06c84eb6ec097e3cb39502116135a7802aed13ce 100644
--- a/src/node_buffer.cc
+++ b/src/node_buffer.cc
@@ -66,6 +66,7 @@ namespace Buffer {
using v8::ArrayBuffer;
using v8::ArrayBufferView;
using v8::BackingStore;
+using v8::BackingStoreInitializationMode;
using v8::Context;
using v8::EscapableHandleScope;
using v8::FunctionCallbackInfo;
@@ -376,9 +377,8 @@ MaybeLocal<Object> New(Environment* env, size_t length) {
Local<ArrayBuffer> ab;
{
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
- std::unique_ptr<BackingStore> bs =
- ArrayBuffer::NewBackingStore(isolate, length);
+ std::unique_ptr<BackingStore> bs = ArrayBuffer::NewBackingStore(
+ isolate, length, BackingStoreInitializationMode::kUninitialized);
CHECK(bs);
@@ -417,18 +417,14 @@ MaybeLocal<Object> Copy(Environment* env, const char* data, size_t length) {
return Local<Object>();
}
- Local<ArrayBuffer> ab;
- {
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
- std::unique_ptr<BackingStore> bs =
- ArrayBuffer::NewBackingStore(isolate, length);
+ std::unique_ptr<BackingStore> bs = ArrayBuffer::NewBackingStore(
+ isolate, length, BackingStoreInitializationMode::kUninitialized);
- CHECK(bs);
+ CHECK(bs);
- memcpy(bs->Data(), data, length);
+ memcpy(bs->Data(), data, length);
- ab = ArrayBuffer::New(isolate, std::move(bs));
- }
+ Local<ArrayBuffer> ab = ArrayBuffer::New(isolate, std::move(bs));
MaybeLocal<Object> obj =
New(env, ab, 0, ab->ByteLength())
@@ -1440,14 +1436,16 @@ void CreateUnsafeArrayBuffer(const FunctionCallbackInfo<Value>& args) {
Local<ArrayBuffer> buf;
- NodeArrayBufferAllocator* allocator = env->isolate_data()->node_allocator();
// 0-length, or zero-fill flag is set, or building snapshot
if (size == 0 || per_process::cli_options->zero_fill_all_buffers ||
- allocator == nullptr) {
+ env->isolate_data()->is_building_snapshot()) {
buf = ArrayBuffer::New(isolate, size);
} else {
- std::unique_ptr<BackingStore> store =
- ArrayBuffer::NewBackingStore(isolate, size);
+ std::unique_ptr<BackingStore> store = ArrayBuffer::NewBackingStore(
+ isolate,
+ size,
+ BackingStoreInitializationMode::kUninitialized,
+ v8::BackingStoreOnFailureMode::kReturnNull);
if (!store) {
return env->ThrowRangeError("Array buffer allocation failed");
}
diff --git a/src/node_http2.cc b/src/node_http2.cc
index 8237c9b7d325dd925ae8798d7795fcd94eeb13d0..a22cf6c4e33e5cf2d3168ce03dc35af8a9584af7 100644
--- a/src/node_http2.cc
+++ b/src/node_http2.cc
@@ -27,6 +27,7 @@ using v8::Array;
using v8::ArrayBuffer;
using v8::ArrayBufferView;
using v8::BackingStore;
+using v8::BackingStoreInitializationMode;
using v8::Boolean;
using v8::Context;
using v8::EscapableHandleScope;
@@ -298,11 +299,10 @@ Local<Value> Http2Settings::Pack(
size_t count,
const nghttp2_settings_entry* entries) {
EscapableHandleScope scope(env->isolate());
- std::unique_ptr<BackingStore> bs;
- {
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
- bs = ArrayBuffer::NewBackingStore(env->isolate(), count * 6);
- }
+ std::unique_ptr<BackingStore> bs = ArrayBuffer::NewBackingStore(
+ env->isolate(),
+ count * 6,
+ BackingStoreInitializationMode::kUninitialized);
if (nghttp2_pack_settings_payload(static_cast<uint8_t*>(bs->Data()),
bs->ByteLength(),
entries,
@@ -468,13 +468,11 @@ Origins::Origins(
return;
}
- {
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
- bs_ = ArrayBuffer::NewBackingStore(env->isolate(),
- alignof(nghttp2_origin_entry) - 1 +
- count_ * sizeof(nghttp2_origin_entry) +
- origin_string_len);
- }
+ bs_ = ArrayBuffer::NewBackingStore(
+ env->isolate(),
+ alignof(nghttp2_origin_entry) - 1 +
+ count_ * sizeof(nghttp2_origin_entry) + origin_string_len,
+ BackingStoreInitializationMode::kUninitialized);
// Make sure the start address is aligned appropriately for an nghttp2_nv*.
char* start = nbytes::AlignUp(static_cast<char*>(bs_->Data()),
@@ -2120,12 +2118,10 @@ void Http2Session::OnStreamRead(ssize_t nread, const uv_buf_t& buf_) {
// happen, we concatenate the data we received with the already-stored
// pending input data, slicing off the already processed part.
size_t pending_len = stream_buf_.len - stream_buf_offset_;
- std::unique_ptr<BackingStore> new_bs;
- {
- NoArrayBufferZeroFillScope no_zero_fill_scope(env()->isolate_data());
- new_bs = ArrayBuffer::NewBackingStore(env()->isolate(),
- pending_len + nread);
- }
+ std::unique_ptr<BackingStore> new_bs = ArrayBuffer::NewBackingStore(
+ env()->isolate(),
+ pending_len + nread,
+ BackingStoreInitializationMode::kUninitialized);
memcpy(static_cast<char*>(new_bs->Data()),
stream_buf_.base + stream_buf_offset_,
pending_len);
diff --git a/src/node_internals.h b/src/node_internals.h
index 12ea72b61b0a5e194207bb369dfed4b8667107cb..18844e18a32d6b07e62481138fa2342765643484 100644
--- a/src/node_internals.h
+++ b/src/node_internals.h
@@ -121,8 +121,6 @@ v8::MaybeLocal<v8::Object> InitializePrivateSymbols(
class NodeArrayBufferAllocator : public ArrayBufferAllocator {
public:
- inline uint32_t* zero_fill_field() { return &zero_fill_field_; }
-
void* Allocate(size_t size) override; // Defined in src/node.cc
void* AllocateUninitialized(size_t size) override;
void Free(void* data, size_t size) override;
@@ -139,7 +137,6 @@ class NodeArrayBufferAllocator : public ArrayBufferAllocator {
}
private:
- uint32_t zero_fill_field_ = 1; // Boolean but exposed as uint32 to JS land.
std::atomic<size_t> total_mem_usage_ {0};
// Delegate to V8's allocator for compatibility with the V8 memory cage.
diff --git a/src/stream_base.cc b/src/stream_base.cc
index fc81108120f0066f2d5dabedc74e22cb6c84d8e4..0bf2642599ee91e2d2aa20d6d066c80b3026ecc5 100644
--- a/src/stream_base.cc
+++ b/src/stream_base.cc
@@ -19,6 +19,7 @@ namespace node {
using v8::Array;
using v8::ArrayBuffer;
using v8::BackingStore;
+using v8::BackingStoreInitializationMode;
using v8::ConstructorBehavior;
using v8::Context;
using v8::DontDelete;
@@ -243,8 +244,8 @@ int StreamBase::Writev(const FunctionCallbackInfo<Value>& args) {
std::unique_ptr<BackingStore> bs;
if (storage_size > 0) {
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
- bs = ArrayBuffer::NewBackingStore(isolate, storage_size);
+ bs = ArrayBuffer::NewBackingStore(
+ isolate, storage_size, BackingStoreInitializationMode::kUninitialized);
}
offset = 0;
@@ -398,14 +399,14 @@ int StreamBase::WriteString(const FunctionCallbackInfo<Value>& args) {
if (try_write) {
// Copy partial data
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
- bs = ArrayBuffer::NewBackingStore(isolate, buf.len);
+ bs = ArrayBuffer::NewBackingStore(
+ isolate, buf.len, BackingStoreInitializationMode::kUninitialized);
memcpy(bs->Data(), buf.base, buf.len);
data_size = buf.len;
} else {
// Write it
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
- bs = ArrayBuffer::NewBackingStore(isolate, storage_size);
+ bs = ArrayBuffer::NewBackingStore(
+ isolate, storage_size, BackingStoreInitializationMode::kUninitialized);
data_size = StringBytes::Write(isolate,
static_cast<char*>(bs->Data()),
storage_size,

View File

@@ -0,0 +1,74 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Joyee Cheung <joyeec9h3@gmail.com>
Date: Thu, 20 Nov 2025 13:50:28 +0900
Subject: src: handle DER decoding errors from system certificates
When decoding certificates from the system store, it's not actually
guaranteed to succeed. In case the system returns a certificate
that cannot be decoded (might be related to SSL implementation issues),
skip them.
diff --git a/src/crypto/crypto_context.cc b/src/crypto/crypto_context.cc
index 0c6b12f8e17b4a7e86ebc836a4e1cc77333f211a..dacf10c3c2e663b03a251c86d69276d0be0dff9d 100644
--- a/src/crypto/crypto_context.cc
+++ b/src/crypto/crypto_context.cc
@@ -505,7 +505,11 @@ void ReadMacOSKeychainCertificates(
CFRelease(search);
if (ortn) {
- fprintf(stderr, "ERROR: SecItemCopyMatching failed %d\n", ortn);
+ per_process::Debug(DebugCategory::CRYPTO,
+ "Cannot read certificates from system because "
+ "SecItemCopyMatching failed %d\n",
+ ortn);
+ return;
}
CFIndex count = CFArrayGetCount(curr_anchors);
@@ -516,7 +520,9 @@ void ReadMacOSKeychainCertificates(
CFDataRef der_data = SecCertificateCopyData(cert_ref);
if (!der_data) {
- fprintf(stderr, "ERROR: SecCertificateCopyData failed\n");
+ per_process::Debug(DebugCategory::CRYPTO,
+ "Skipping read of a system certificate "
+ "because SecCertificateCopyData failed\n");
continue;
}
auto data_buffer_pointer = CFDataGetBytePtr(der_data);
@@ -524,9 +530,19 @@ void ReadMacOSKeychainCertificates(
X509* cert =
d2i_X509(nullptr, &data_buffer_pointer, CFDataGetLength(der_data));
CFRelease(der_data);
+
+ if (cert == nullptr) {
+ per_process::Debug(DebugCategory::CRYPTO,
+ "Skipping read of a system certificate "
+ "because decoding failed\n");
+ continue;
+ }
+
bool is_valid = IsCertificateTrustedForPolicy(cert, cert_ref);
if (is_valid) {
system_root_certificates_X509->emplace_back(cert);
+ } else {
+ X509_free(cert);
}
}
CFRelease(curr_anchors);
@@ -636,7 +652,14 @@ void GatherCertsForLocation(std::vector<X509*>* vector,
reinterpret_cast<const unsigned char*>(cert_from_store->pbCertEncoded);
const size_t cert_size = cert_from_store->cbCertEncoded;
- vector->emplace_back(d2i_X509(nullptr, &cert_data, cert_size));
+ X509* x509 = d2i_X509(nullptr, &cert_data, cert_size);
+ if (x509 == nullptr) {
+ per_process::Debug(DebugCategory::CRYPTO,
+ "Skipping read of a system certificate "
+ "because decoding failed\n");
+ } else {
+ vector->emplace_back(x509);
+ }
}
}

View File

@@ -0,0 +1,276 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: James M Snell <jasnell@gmail.com>
Date: Sun, 18 May 2025 10:46:30 -0700
Subject: src: prepare for v8 sandboxing
PR-URL: https://github.com/nodejs/node/pull/58376
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
diff --git a/src/crypto/crypto_dh.cc b/src/crypto/crypto_dh.cc
index f23cedf4f2449d8edc9a8de1b70332e75d693cdd..5ac2b1a83688fe99b13c37bf375ca6e22497dc18 100644
--- a/src/crypto/crypto_dh.cc
+++ b/src/crypto/crypto_dh.cc
@@ -22,6 +22,8 @@ using ncrypto::DHPointer;
using ncrypto::EVPKeyCtxPointer;
using ncrypto::EVPKeyPointer;
using v8::ArrayBuffer;
+using v8::BackingStoreInitializationMode;
+using v8::BackingStoreOnFailureMode;
using v8::ConstructorBehavior;
using v8::Context;
using v8::DontDelete;
@@ -55,12 +57,27 @@ void DiffieHellman::MemoryInfo(MemoryTracker* tracker) const {
namespace {
MaybeLocal<Value> DataPointerToBuffer(Environment* env, DataPointer&& data) {
+#ifdef V8_ENABLE_SANDBOX
+ auto backing = ArrayBuffer::NewBackingStore(
+ env->isolate(),
+ data.size(),
+ BackingStoreInitializationMode::kUninitialized,
+ BackingStoreOnFailureMode::kReturnNull);
+ if (!backing) {
+ THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
+ return MaybeLocal<Value>();
+ }
+ if (data.size() > 0) {
+ memcpy(backing->Data(), data.get(), data.size());
+ }
+#else
auto backing = ArrayBuffer::NewBackingStore(
data.get(),
data.size(),
[](void* data, size_t len, void* ptr) { DataPointer free_me(data, len); },
nullptr);
data.release();
+#endif // V8_ENABLE_SANDBOX
auto ab = ArrayBuffer::New(env->isolate(), std::move(backing));
return Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local<Value>());
diff --git a/src/crypto/crypto_util.cc b/src/crypto/crypto_util.cc
index eab18ab9888e2f7c0757fefab80505d8c99dc742..7ecf810ea0f4106c7c44593dae1b0a3cf0673380 100644
--- a/src/crypto/crypto_util.cc
+++ b/src/crypto/crypto_util.cc
@@ -37,6 +37,8 @@ using ncrypto::SSLCtxPointer;
using ncrypto::SSLPointer;
using v8::ArrayBuffer;
using v8::BackingStore;
+using v8::BackingStoreInitializationMode;
+using v8::BackingStoreOnFailureMode;
using v8::BigInt;
using v8::Context;
using v8::Exception;
@@ -359,34 +361,29 @@ ByteSource& ByteSource::operator=(ByteSource&& other) noexcept {
return *this;
}
-std::unique_ptr<BackingStore> ByteSource::ReleaseToBackingStore(Environment* env) {
+std::unique_ptr<BackingStore> ByteSource::ReleaseToBackingStore(
+ Environment* env) {
// It's ok for allocated_data_ to be nullptr but
// only if size_ is zero.
CHECK_IMPLIES(size_ > 0, allocated_data_ != nullptr);
-#if defined(V8_ENABLE_SANDBOX)
- // When V8 sandboxed pointers are enabled, we have to copy into the memory
- // cage. We still want to ensure we erase the data on free though, so
- // provide a custom deleter that calls OPENSSL_cleanse.
- if (!size())
- return ArrayBuffer::NewBackingStore(env->isolate(), 0);
- std::unique_ptr<ArrayBuffer::Allocator> allocator(ArrayBuffer::Allocator::NewDefaultAllocator());
- void* v8_data = allocator->Allocate(size());
- CHECK(v8_data);
- memcpy(v8_data, allocated_data_, size());
- OPENSSL_clear_free(allocated_data_, size());
+#ifdef V8_ENABLE_SANDBOX
+ // If the v8 sandbox is enabled, then all array buffers must be allocated
+ // via the isolate. External buffers are not allowed. So, instead of wrapping
+ // the allocated data we'll copy it instead.
+
+ // TODO(@jasnell): It would be nice to use an abstracted utility to do this
+ // branch instead of duplicating the V8_ENABLE_SANDBOX check each time.
std::unique_ptr<BackingStore> ptr = ArrayBuffer::NewBackingStore(
- v8_data,
+ env->isolate(),
size(),
- [](void* data, size_t length, void*) {
- OPENSSL_cleanse(data, length);
- std::unique_ptr<ArrayBuffer::Allocator> allocator(ArrayBuffer::Allocator::NewDefaultAllocator());
- allocator->Free(data, length);
- }, nullptr);
- CHECK(ptr);
- allocated_data_ = nullptr;
- data_ = nullptr;
- size_ = 0;
- return ptr;
+ BackingStoreInitializationMode::kUninitialized,
+ BackingStoreOnFailureMode::kReturnNull);
+ if (!ptr) {
+ THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
+ return nullptr;
+ }
+ memcpy(ptr->Data(), allocated_data_, size());
+ OPENSSL_clear_free(allocated_data_, size_);
#else
std::unique_ptr<BackingStore> ptr = ArrayBuffer::NewBackingStore(
allocated_data_,
@@ -394,12 +391,12 @@ std::unique_ptr<BackingStore> ByteSource::ReleaseToBackingStore(Environment* env
[](void* data, size_t length, void* deleter_data) {
OPENSSL_clear_free(deleter_data, length);
}, allocated_data_);
+#endif // V8_ENABLE_SANDBOX
CHECK(ptr);
allocated_data_ = nullptr;
data_ = nullptr;
size_ = 0;
return ptr;
-#endif // defined(V8_ENABLE_SANDBOX)
}
Local<ArrayBuffer> ByteSource::ToArrayBuffer(Environment* env) {
@@ -711,8 +708,19 @@ void SecureBuffer(const FunctionCallbackInfo<Value>& args) {
}
#else
void SecureBuffer(const FunctionCallbackInfo<Value>& args) {
- CHECK(args[0]->IsUint32());
Environment* env = Environment::GetCurrent(args);
+#ifdef V8_ENABLE_SANDBOX
+ // The v8 sandbox is enabled, so we cannot use the secure heap because
+ // the sandbox requires that all array buffers be allocated via the isolate.
+ // That is fundamentally incompatible with the secure heap which allocates
+ // in openssl's secure heap area. Instead we'll just throw an error here.
+ //
+ // That said, we really shouldn't get here in the first place since the
+ // option to enable the secure heap is only available when the sandbox
+ // is disabled.
+ UNREACHABLE();
+#else
+ CHECK(args[0]->IsUint32());
uint32_t len = args[0].As<Uint32>()->Value();
void* data = OPENSSL_malloc(len);
if (data == nullptr) {
@@ -730,6 +738,7 @@ void SecureBuffer(const FunctionCallbackInfo<Value>& args) {
data);
Local<ArrayBuffer> buffer = ArrayBuffer::New(env->isolate(), store);
args.GetReturnValue().Set(Uint8Array::New(buffer, 0, len));
+#endif // V8_ENABLE_SANDBOX
}
#endif // defined(V8_ENABLE_SANDBOX)
diff --git a/src/crypto/crypto_x509.cc b/src/crypto/crypto_x509.cc
index 7fe3d09851d4476fa3f77ea0a0b49e8af13fae4f..b60ad6b1cdb0b923ba2de1b7205ed5ce07612b81 100644
--- a/src/crypto/crypto_x509.cc
+++ b/src/crypto/crypto_x509.cc
@@ -29,6 +29,7 @@ using v8::ArrayBuffer;
using v8::ArrayBufferView;
using v8::BackingStore;
using v8::BackingStoreInitializationMode;
+using v8::BackingStoreOnFailureMode;
using v8::Boolean;
using v8::Context;
using v8::Date;
@@ -168,18 +169,20 @@ MaybeLocal<Value> ToV8Value(Local<Context> context, const BIOPointer& bio) {
MaybeLocal<Value> ToBuffer(Environment* env, BIOPointer* bio) {
if (bio == nullptr || !*bio) return {};
BUF_MEM* mem = *bio;
-#if defined(V8_ENABLE_SANDBOX)
- std::unique_ptr<ArrayBuffer::Allocator> allocator(ArrayBuffer::Allocator::NewDefaultAllocator());
- void* v8_data = allocator->Allocate(mem->length);
- CHECK(v8_data);
- memcpy(v8_data, mem->data, mem->length);
- std::unique_ptr<v8::BackingStore> backing = ArrayBuffer::NewBackingStore(
- v8_data,
+#ifdef V8_ENABLE_SANDBOX
+ // If the v8 sandbox is enabled, then all array buffers must be allocated
+ // via the isolate. External buffers are not allowed. So, instead of wrapping
+ // the BIOPointer we'll copy it instead.
+ auto backing = ArrayBuffer::NewBackingStore(
+ env->isolate(),
mem->length,
- [](void* data, size_t length, void*) {
- std::unique_ptr<ArrayBuffer::Allocator> allocator(ArrayBuffer::Allocator::NewDefaultAllocator());
- allocator->Free(data, length);
- }, nullptr);
+ BackingStoreInitializationMode::kUninitialized,
+ BackingStoreOnFailureMode::kReturnNull);
+ if (!backing) {
+ THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
+ return MaybeLocal<Value>();
+ }
+ memcpy(backing->Data(), mem->data, mem->length);
#else
auto backing = ArrayBuffer::NewBackingStore(
mem->data,
@@ -188,8 +191,7 @@ MaybeLocal<Value> ToBuffer(Environment* env, BIOPointer* bio) {
BIOPointer free_me(static_cast<BIO*>(data));
},
bio->release());
-#endif
-
+#endif // V8_ENABLE_SANDBOX
auto ab = ArrayBuffer::New(env->isolate(), std::move(backing));
Local<Value> ret;
if (!Buffer::New(env, ab, 0, ab->ByteLength()).ToLocal(&ret)) return {};
diff --git a/src/js_native_api_v8.cc b/src/js_native_api_v8.cc
index 413db3ed9b88d7b7fb2ac6dd1153dade9ff830fd..6da93b8569a34841e846c320ec0a6ca7f1ea0da6 100644
--- a/src/js_native_api_v8.cc
+++ b/src/js_native_api_v8.cc
@@ -114,7 +114,7 @@ napi_status NewExternalString(napi_env env,
CHECK_NEW_STRING_ARGS(env, str, length, result);
napi_status status;
-#if defined(V8_ENABLE_SANDBOX)
+#ifdef V8_ENABLE_SANDBOX
status = create_api(env, str, length, result);
if (status == napi_ok) {
if (copied != nullptr) {
diff --git a/src/node_api.cc b/src/node_api.cc
index 2769997f0ede0e921fcb8826942609e497e5f9cb..d9b17780f6143f1c3f8488a20144376963e43fbc 100644
--- a/src/node_api.cc
+++ b/src/node_api.cc
@@ -1056,7 +1056,7 @@ napi_create_external_buffer(napi_env env,
NAPI_PREAMBLE(env);
CHECK_ARG(env, result);
-#if defined(V8_ENABLE_SANDBOX)
+#ifdef V8_ENABLE_SANDBOX
return napi_set_last_error(env, napi_no_external_buffers_allowed);
#else
v8::Isolate* isolate = env->isolate;
diff --git a/src/node_options.cc b/src/node_options.cc
index e93e8684e518b30a2514768a269be6d32d1f5b94..547cda780376f578b0f78eb9158dc14a3faf874d 100644
--- a/src/node_options.cc
+++ b/src/node_options.cc
@@ -83,6 +83,8 @@ void PerProcessOptions::CheckOptions(std::vector<std::string>* errors,
}
// Any value less than 2 disables use of the secure heap.
+#ifndef V8_ENABLE_SANDBOX
+ // The secure heap is not supported when V8_ENABLE_SANDBOX is enabled.
if (secure_heap >= 2) {
if ((secure_heap & (secure_heap - 1)) != 0)
errors->push_back("--secure-heap must be a power of 2");
@@ -95,6 +97,7 @@ void PerProcessOptions::CheckOptions(std::vector<std::string>* errors,
if ((secure_heap_min & (secure_heap_min - 1)) != 0)
errors->push_back("--secure-heap-min must be a power of 2");
}
+#endif // V8_ENABLE_SANDBOX
#endif // HAVE_OPENSSL
if (use_largepages != "off" &&
@@ -1243,6 +1246,7 @@ PerProcessOptionsParser::PerProcessOptionsParser(
"force FIPS crypto (cannot be disabled)",
&PerProcessOptions::force_fips_crypto,
kAllowedInEnvvar);
+#ifndef V8_ENABLE_SANDBOX
AddOption("--secure-heap",
"total size of the OpenSSL secure heap",
&PerProcessOptions::secure_heap,
@@ -1251,6 +1255,7 @@ PerProcessOptionsParser::PerProcessOptionsParser(
"minimum allocation size from the OpenSSL secure heap",
&PerProcessOptions::secure_heap_min,
kAllowedInEnvvar);
+#endif // V8_ENABLE_SANDBOX
#endif // HAVE_OPENSSL
#if OPENSSL_VERSION_MAJOR >= 3
AddOption("--openssl-legacy-provider",

View File

@@ -10,10 +10,10 @@ Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: James M Snell <jasnell@gmail.com>
diff --git a/src/api/environment.cc b/src/api/environment.cc
index 82f53bba29613de212f64be440ca20d7c630fddf..0a358735c331767e8eb563a80e9aaccfb544c27b 100644
index 33827edce63c9fe08b52aea59571391a83853443..79327877b0f34cbafb3efcc21617027d4011f806 100644
--- a/src/api/environment.cc
+++ b/src/api/environment.cc
@@ -886,7 +886,7 @@ Maybe<void> InitializePrimordials(Local<Context> context,
@@ -878,7 +878,7 @@ Maybe<void> InitializePrimordials(Local<Context> context,
CHECK(!exports->Has(context, primordials_string).FromJust());
Local<Object> primordials = Object::New(isolate);
@@ -51,10 +51,10 @@ index 7b2efa49468c0bed2f5935552addd3ab37d0a50b..413db3ed9b88d7b7fb2ac6dd1153dade
return GET_RETURN_STATUS(env);
}
diff --git a/src/node_buffer.cc b/src/node_buffer.cc
index c94b14741c827a81d69a6f036426a344e563ad72..15129e4455fdc8792f21511a04d534ba3a4ebb5f 100644
index 8a5b6b57321c2843a965a7e51b2ebed991a1e424..e844fe6cb33acefd075516e675075421ad5c3cff 100644
--- a/src/node_buffer.cc
+++ b/src/node_buffer.cc
@@ -284,8 +284,9 @@ MaybeLocal<Uint8Array> New(Environment* env,
@@ -283,8 +283,9 @@ MaybeLocal<Uint8Array> New(Environment* env,
size_t length) {
CHECK(!env->buffer_prototype_object().IsEmpty());
Local<Uint8Array> ui = Uint8Array::New(ab, byte_offset, length);

View File

@@ -6,63 +6,6 @@ Subject: support V8 sandboxed pointers
This refactors several allocators to allocate within the V8 memory cage,
allowing them to be compatible with the V8_SANDBOXED_POINTERS feature.
diff --git a/src/api/environment.cc b/src/api/environment.cc
index fd71ceac65ccef1d2832b45b0b5612877cee22c1..cb37fa080fc8e8d524cfa2758c4a8c2c5652324d 100644
--- a/src/api/environment.cc
+++ b/src/api/environment.cc
@@ -106,6 +106,14 @@ MaybeLocal<Value> PrepareStackTraceCallback(Local<Context> context,
return result;
}
+NodeArrayBufferAllocator::NodeArrayBufferAllocator() {
+ zero_fill_field_ = static_cast<uint32_t*>(allocator_->Allocate(sizeof(*zero_fill_field_)));
+}
+
+NodeArrayBufferAllocator::~NodeArrayBufferAllocator() {
+ allocator_->Free(zero_fill_field_, sizeof(*zero_fill_field_));
+}
+
void* NodeArrayBufferAllocator::Allocate(size_t size) {
void* ret;
if (zero_fill_field_ || per_process::cli_options->zero_fill_all_buffers)
diff --git a/src/crypto/crypto_dh.cc b/src/crypto/crypto_dh.cc
index f23cedf4f2449d8edc9a8de1b70332e75d693cdd..976653dd1e9363e046788fc3419a9b649ceb2ea4 100644
--- a/src/crypto/crypto_dh.cc
+++ b/src/crypto/crypto_dh.cc
@@ -55,13 +55,32 @@ void DiffieHellman::MemoryInfo(MemoryTracker* tracker) const {
namespace {
MaybeLocal<Value> DataPointerToBuffer(Environment* env, DataPointer&& data) {
+#if defined(V8_ENABLE_SANDBOX)
+ std::unique_ptr<v8::BackingStore> backing;
+ if (data.size() > 0) {
+ std::unique_ptr<ArrayBuffer::Allocator> allocator(ArrayBuffer::Allocator::NewDefaultAllocator());
+ void* v8_data = allocator->Allocate(data.size());
+ CHECK(v8_data);
+ memcpy(v8_data, data.get(), data.size());
+ backing = ArrayBuffer::NewBackingStore(
+ v8_data,
+ data.size(),
+ [](void* data, size_t length, void*) {
+ std::unique_ptr<ArrayBuffer::Allocator> allocator(ArrayBuffer::Allocator::NewDefaultAllocator());
+ allocator->Free(data, length);
+ }, nullptr);
+ } else {
+ NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
+ backing = v8::ArrayBuffer::NewBackingStore(env->isolate(), data.size());
+ }
+#else
auto backing = ArrayBuffer::NewBackingStore(
data.get(),
data.size(),
[](void* data, size_t len, void* ptr) { DataPointer free_me(data, len); },
nullptr);
data.release();
-
+#endif
auto ab = ArrayBuffer::New(env->isolate(), std::move(backing));
return Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local<Value>());
}
diff --git a/src/crypto/crypto_util.cc b/src/crypto/crypto_util.cc
index 4505786745c54a529f904d5e7813a86204e0a78b..eab18ab9888e2f7c0757fefab80505d8c99dc742 100644
--- a/src/crypto/crypto_util.cc
@@ -188,6 +131,28 @@ index f616223cfb0f6e10f7cf57ada9704316bde2797e..eb6dad44a49d997097c8fb5009eeb60a
auto ab = ArrayBuffer::New(env->isolate(), std::move(backing));
Local<Value> ret;
if (!Buffer::New(env, ab, 0, ab->ByteLength()).ToLocal(&ret)) return {};
diff --git a/src/node_buffer.cc b/src/node_buffer.cc
index 357dc5f6d1c1c2d3756a94c1326b0502403e1eaf..b9f0c97938203b4652780a7d707c5e83319330b0 100644
--- a/src/node_buffer.cc
+++ b/src/node_buffer.cc
@@ -1412,7 +1412,7 @@ inline size_t CheckNumberToSize(Local<Value> number) {
CHECK(value >= 0 && value < maxSize);
size_t size = static_cast<size_t>(value);
#ifdef V8_ENABLE_SANDBOX
- CHECK_LE(size, kMaxSafeBufferSizeForSandbox);
+ CHECK_LE(size, v8::internal::kMaxSafeBufferSizeForSandbox);
#endif
return size;
}
@@ -1437,7 +1437,7 @@ void CreateUnsafeArrayBuffer(const FunctionCallbackInfo<Value>& args) {
buf = ArrayBuffer::New(isolate, size);
} else {
std::unique_ptr<BackingStore> store =
- ArrayBuffer::NewBackingStoreForNodeLTS(isolate, size);
+ ArrayBuffer::NewBackingStore(isolate, size);
if (!store) {
return env->ThrowRangeError("Array buffer allocation failed");
}
diff --git a/src/node_i18n.cc b/src/node_i18n.cc
index 6be3920632b25db450025ebab6a2636e4811cdbe..b49916d2b5fc5e58cf3fb67329430fd3df8fb813 100644
--- a/src/node_i18n.cc
@@ -228,30 +193,6 @@ index 6be3920632b25db450025ebab6a2636e4811cdbe..b49916d2b5fc5e58cf3fb67329430fd3
}
constexpr const char* EncodingName(const enum encoding encoding) {
diff --git a/src/node_internals.h b/src/node_internals.h
index 12ea72b61b0a5e194207bb369dfed4b8667107cb..64442215714a98f648971e517ddd9c77e38fe3f2 100644
--- a/src/node_internals.h
+++ b/src/node_internals.h
@@ -121,7 +121,9 @@ v8::MaybeLocal<v8::Object> InitializePrivateSymbols(
class NodeArrayBufferAllocator : public ArrayBufferAllocator {
public:
- inline uint32_t* zero_fill_field() { return &zero_fill_field_; }
+ NodeArrayBufferAllocator();
+ ~NodeArrayBufferAllocator() override;
+ inline uint32_t* zero_fill_field() { return zero_fill_field_; }
void* Allocate(size_t size) override; // Defined in src/node.cc
void* AllocateUninitialized(size_t size) override;
@@ -139,7 +141,7 @@ class NodeArrayBufferAllocator : public ArrayBufferAllocator {
}
private:
- uint32_t zero_fill_field_ = 1; // Boolean but exposed as uint32 to JS land.
+ uint32_t* zero_fill_field_ = nullptr; // Boolean but exposed as uint32 to JS land.
std::atomic<size_t> total_mem_usage_ {0};
// Delegate to V8's allocator for compatibility with the V8 memory cage.
diff --git a/src/node_serdes.cc b/src/node_serdes.cc
index c55a2e28066147ae5ca5def10ec76ccc03c634b4..c54183c72944989219b6437c9e571a3f7f3f8dd5 100644
--- a/src/node_serdes.cc

View File

@@ -0,0 +1,33 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Shelley Vohr <shelley.vohr@gmail.com>
Date: Tue, 4 Nov 2025 21:20:26 +0100
Subject: test: correct conditional secure heap flags test
PR-URL: https://github.com/nodejs/node/pull/60385
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
(cherry picked from commit 53c4a39fec941e04150554fdd3e654b48f2e1b31)
diff --git a/test/parallel/test-process-env-allowed-flags-are-documented.js b/test/parallel/test-process-env-allowed-flags-are-documented.js
index afd43cfffe638f4f084f1c36949068e7239eadc3..c70e073bab888c349e8f5c691f5679a3796c896c 100644
--- a/test/parallel/test-process-env-allowed-flags-are-documented.js
+++ b/test/parallel/test-process-env-allowed-flags-are-documented.js
@@ -49,6 +49,8 @@ if (!hasOpenSSL3) {
documented.delete('--openssl-shared-config');
}
+const isV8Sandboxed = process.config.variables.v8_enable_sandbox;
+
// Filter out options that are conditionally present.
const conditionalOpts = [
{
@@ -74,6 +76,9 @@ const conditionalOpts = [
}, {
include: process.features.inspector,
filter: (opt) => opt.startsWith('--inspect') || opt === '--debug-port'
+ }, {
+ include: !isV8Sandboxed,
+ filter: (opt) => ['--secure-heap', '--secure-heap-min'].includes(opt)
},
];
documented.forEach((opt) => {

1
patches/skia/.patches Normal file
View File

@@ -0,0 +1 @@
graphite_add_insertstatus_koutoforderrecording.patch

View File

@@ -0,0 +1,54 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Michael Ludwig <michaelludwig@google.com>
Date: Fri, 16 Jan 2026 15:28:27 -0500
Subject: [graphite] Add InsertStatus::kOutOfOrderRecording
Bug: b/458722690
Change-Id: I1c3661b9c765f46f039f0044ae3557a0e7f619cf
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1143598
Auto-Submit: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Thomas Smith <thomsmit@google.com>
Commit-Queue: Nicolette Prevost <nicolettep@google.com>
Reviewed-by: Nicolette Prevost <nicolettep@google.com>
diff --git a/include/gpu/graphite/GraphiteTypes.h b/include/gpu/graphite/GraphiteTypes.h
index a638da8988b2e8963255537c36f98d0233c917e8..a8e40871531ce0c2a2b98a4c8ba1c44e9b374c2d 100644
--- a/include/gpu/graphite/GraphiteTypes.h
+++ b/include/gpu/graphite/GraphiteTypes.h
@@ -52,7 +52,12 @@ public:
kAddCommandsFailed,
// Internal failure, shader pipeline compilation failed (driver issue, or disk corruption),
// state unrecoverable.
- kAsyncShaderCompilesFailed
+ kAsyncShaderCompilesFailed,
+ // The inserted Recording is out of order from what the Context expects (when
+ // `[Context|Recorder]Options::fRequireOrderedRecordings` is true), which can either
+ // represent a client synchronization error or an internal failure when a prior dependent
+ // Recording failed for some reason, no CB changes but state likely unrecoverable.
+ kOutOfOrderRecording,
};
constexpr InsertStatus() : fValue(kSuccess) {}
diff --git a/relnotes/out_of_order_status.md b/relnotes/out_of_order_status.md
new file mode 100644
index 0000000000000000000000000000000000000000..74f7c889338925d132a2d80cdba40d321807c614
--- /dev/null
+++ b/relnotes/out_of_order_status.md
@@ -0,0 +1,4 @@
+* Graphite's InsertStatus now has an additional kOutOfOrderRecording to differentiate this
+ unrecoverable error from programming errors that would lead to kInvalidRecording. Out of order
+ recordings can currently arise "naturally" if prior dependent recordings failed due to resource
+ creation or update errors from the GPU driver.
diff --git a/src/gpu/graphite/QueueManager.cpp b/src/gpu/graphite/QueueManager.cpp
index 2948e071390095f3a1a1d7fdb6c7878adeaf9c53..f5f97f23e4957b819f9d92e8f03f49cd6fad1ab3 100644
--- a/src/gpu/graphite/QueueManager.cpp
+++ b/src/gpu/graphite/QueueManager.cpp
@@ -122,7 +122,7 @@ InsertStatus QueueManager::addRecording(const InsertRecordingInfo& info, Context
if (recorderID != SK_InvalidGenID) {
uint32_t* recordingID = fLastAddedRecordingIDs.find(recorderID);
RETURN_FAIL_IF(recordingID && info.fRecording->priv().uniqueID() != *recordingID + 1,
- InsertStatus::kInvalidRecording,
+ InsertStatus::kOutOfOrderRecording,
"Recordings are expected to be replayed in order");
// Note the new Recording ID.

View File

@@ -1,2 +1,5 @@
chore_allow_customizing_microtask_policy_per_context.patch
turboshaft_avoid_introducing_too_many_variables.patch
runtime_setprototypeproperties_handling_of.patch
runtime_correcting_setprototypeproperties.patch
reduce_stack_memory_consumption_in_bytecodegenerator.patch

View File

@@ -0,0 +1,263 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jakob Kummerow <jkummerow@chromium.org>
Date: Fri, 21 Nov 2025 11:52:15 +0100
Subject: Reduce stack memory consumption in BytecodeGenerator
Using a SmallVector with 64 stack-allocated entries is not great
for functions that can be used in deep recursions, so this patch
replaces that with a ZoneVector.
By allocating any backing store only when it's actually needed,
and presizing that initial backing store when that happens, this
should have similar performance while using a lot less stack space,
allowing us to compile more deeply nested expressions.
For the highly artificial example of a function full of nested
empty blocks `function f() {{{{{...}}}}}`, this increases the max
nesting level from around 670 to around 2600. With more aggressive
inlining in official builds, it can have similar effects on patterns
that don't even call `VisitStatements()` recursively.
Bug: 429332174
Change-Id: Id11e15ba0fd9bc39efa68bf83e1181fe0e7274a7
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7180480
Reviewed-by: Raphael Herouart <rherouart@chromium.org>
Commit-Queue: Jakob Kummerow <jkummerow@chromium.org>
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
Cr-Commit-Position: refs/heads/main@{#103870}
diff --git a/src/interpreter/bytecode-generator.cc b/src/interpreter/bytecode-generator.cc
index 63400abcc7ca0bd5369d566dd37e2ae93d7c3efe..a1cf142edc4c0224731ebab988bcbf9272acd57e 100644
--- a/src/interpreter/bytecode-generator.cc
+++ b/src/interpreter/bytecode-generator.cc
@@ -2306,10 +2306,7 @@ void BytecodeGenerator::VisitDeclarations(Declaration::List* declarations) {
// The subsequent ones must be about the same <var> to return true.
bool BytecodeGenerator::IsPrototypeAssignment(
- Statement* stmt, Variable** var, HoleCheckMode* hole_check_mode,
- SmallZoneVector<std::pair<const AstRawString*, Expression*>,
- kInitialPropertyCount>& properties,
- std::unordered_set<const AstRawString*>& duplicates) {
+ Statement* stmt, std::unique_ptr<PrototypeAssignments>* assignments) {
// The expression Statement is an assignment
// ========================================
ExpressionStatement* expr_stmt = stmt->AsExpressionStatement();
@@ -2381,41 +2378,46 @@ bool BytecodeGenerator::IsPrototypeAssignment(
return false;
}
- if (!duplicates.insert(prop_str).second) {
- return false;
- }
-
- if (*var == nullptr) {
- // This is the first proto assignment in the sequence
- *var = tmp_var;
- *hole_check_mode = proto_prop->obj()->AsVariableProxy()->hole_check_mode();
- } else if (*var != tmp_var) {
- // This prototype assignment is about another var
- return false;
+ if (!*assignments) {
+ // This is the first prototype assignment in the sequence.
+ *assignments = std::make_unique<PrototypeAssignments>(
+ tmp_var, proto_prop->obj()->AsVariableProxy()->hole_check_mode(),
+ ZoneVector<PrototypeAssignment>(zone()));
+ (*assignments)->properties.reserve(8);
+ (*assignments)->duplicates.insert(prop_str);
+ } else {
+ if (!(*assignments)->duplicates.insert(prop_str).second) return false;
+ if ((*assignments)->var != tmp_var) {
+ // This prototype assignment is about another var.
+ return false;
+ }
+ DCHECK_EQ((*assignments)->hole_check_mode,
+ proto_prop->obj()->AsVariableProxy()->hole_check_mode());
}
- // Success
- properties.push_back(std::make_pair(
- prop_str,
- value)); // This will be reused as part of an ObjectLiteral
+ // Success!
+ (*assignments)
+ ->properties.push_back(std::make_pair(
+ prop_str,
+ value)); // This will be reused as part of an ObjectLiteral.
- DCHECK_EQ(*hole_check_mode,
- proto_prop->obj()->AsVariableProxy()->hole_check_mode());
return true;
}
void BytecodeGenerator::VisitConsecutivePrototypeAssignments(
- const SmallZoneVector<std::pair<const AstRawString*, Expression*>,
- kInitialPropertyCount>& properties,
- Variable* var, HoleCheckMode hole_check_mode) {
+ std::unique_ptr<PrototypeAssignments> assignments) {
// Create a boiler plate object in the constant pool to be merged into the
- // proto
+ // proto.
size_t entry = builder()->AllocateDeferredConstantPoolEntry();
- proto_assign_seq_.push_back(std::make_pair(
- zone()->New<ProtoAssignmentSeqBuilder>(properties), entry));
+ proto_assign_seq_.push_back(
+ std::make_pair(zone()->New<ProtoAssignmentSeqBuilder>(
+ std::move(assignments->properties)),
+ entry));
+ const ZoneVector<PrototypeAssignment>* props =
+ proto_assign_seq_.back().first->properties();
int first_idx = -1;
- for (auto& p : properties) {
+ for (auto& p : *props) {
auto func = p.second->AsFunctionLiteral();
if (func) {
int idx = GetNewClosureSlot(func);
@@ -2426,13 +2428,13 @@ void BytecodeGenerator::VisitConsecutivePrototypeAssignments(
}
}
- // We need it to be valid, even if unused
+ // We need {first_idx} to be valid, even if it's unused.
if (first_idx == -1) {
first_idx = 0;
}
- // Load the variable whose prototype is to be set into the Accumulator
- BuildVariableLoad(var, hole_check_mode);
- // Merge in-place proto-def boilerplate object into Accumulator
+ // Load the variable whose prototype is to be set into the Accumulator.
+ BuildVariableLoad(assignments->var, assignments->hole_check_mode);
+ // Merge in-place proto-def boilerplate object into the Accumulator.
builder()->SetPrototypeProperties(entry, first_idx);
}
@@ -2440,25 +2442,23 @@ void BytecodeGenerator::VisitStatements(
const ZonePtrList<Statement>* statements, int start) {
for (int stmt_idx = start; stmt_idx < statements->length(); stmt_idx++) {
if (v8_flags.proto_assign_seq_opt) {
- Variable* var = nullptr;
- HoleCheckMode hole_check_mode;
-
int proto_assign_idx = stmt_idx;
- SmallZoneVector<std::pair<const AstRawString*, Expression*>,
- kInitialPropertyCount>
- properties(zone());
- std::unordered_set<const AstRawString*> duplicates;
+ // {VisitStatements} can be used for deep recursions, so this is a
+ // stack-friendly design: statically we only need one {unique_ptr}, and
+ // the actual storage is heap-allocated when it is needed.
+ std::unique_ptr<PrototypeAssignments> assignments;
while (proto_assign_idx < statements->length() &&
- IsPrototypeAssignment(statements->at(proto_assign_idx), &var,
- &hole_check_mode, properties, duplicates)) {
+ IsPrototypeAssignment(statements->at(proto_assign_idx),
+ &assignments)) {
++proto_assign_idx;
}
if (proto_assign_idx - stmt_idx > 1) {
- DCHECK_EQ((size_t)(proto_assign_idx - stmt_idx), properties.size());
- VisitConsecutivePrototypeAssignments(properties, var, hole_check_mode);
- stmt_idx = proto_assign_idx - 1; // the outer loop should now ignore
- // these statements
+ DCHECK_EQ(static_cast<size_t>(proto_assign_idx - stmt_idx),
+ assignments->properties.size());
+ VisitConsecutivePrototypeAssignments(std::move(assignments));
+ stmt_idx = proto_assign_idx - 1; // The outer loop should now ignore
+ // these statements.
DCHECK(!builder()->RemainderOfBlockIsDead());
continue;
}
diff --git a/src/interpreter/bytecode-generator.h b/src/interpreter/bytecode-generator.h
index 5dd136a01848aaf4a182dd31aa05a6ebe845fae8..b2e4cf6d40c3cd66042f5f52533b4883ff159418 100644
--- a/src/interpreter/bytecode-generator.h
+++ b/src/interpreter/bytecode-generator.h
@@ -30,6 +30,13 @@ class LoopBuilder;
class BlockCoverageBuilder;
class BytecodeJumpTable;
+struct PrototypeAssignments {
+ Variable* var;
+ HoleCheckMode hole_check_mode;
+ ZoneVector<PrototypeAssignment> properties;
+ std::unordered_set<const AstRawString*> duplicates;
+};
+
class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
public:
enum TypeHint : uint8_t {
@@ -69,18 +76,12 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
#define DECLARE_VISIT(type) void Visit##type(type* node);
AST_NODE_LIST(DECLARE_VISIT)
#undef DECLARE_VISIT
- static constexpr int kInitialPropertyCount = 64;
bool IsPrototypeAssignment(
- Statement* stmt, Variable** var, HoleCheckMode* hole_check_mode,
- SmallZoneVector<std::pair<const AstRawString*, Expression*>,
- kInitialPropertyCount>& properties,
- std::unordered_set<const AstRawString*>& duplicate);
-
- void VisitConsecutivePrototypeAssignments(
- const SmallZoneVector<std::pair<const AstRawString*, Expression*>,
- kInitialPropertyCount>& properties,
- Variable* var, HoleCheckMode hole_check_mode);
+ Statement* stmt, std::unique_ptr<PrototypeAssignments>* assignments);
+ V8_NOINLINE void VisitConsecutivePrototypeAssignments(
+ std::unique_ptr<PrototypeAssignments> assignments);
+
// Visiting function for declarations list and statements are overridden.
void VisitModuleDeclarations(Declaration::List* declarations);
void VisitGlobalDeclarations(Declaration::List* declarations);
diff --git a/src/interpreter/prototype-assignment-sequence-builder.cc b/src/interpreter/prototype-assignment-sequence-builder.cc
index a7c05b6e8555fde41ba6a81523437fd03b16c3e6..fb427c825c2753d3e12f557279a5230985a1ec44 100644
--- a/src/interpreter/prototype-assignment-sequence-builder.cc
+++ b/src/interpreter/prototype-assignment-sequence-builder.cc
@@ -25,7 +25,7 @@ void ProtoAssignmentSeqBuilder::BuildBoilerplateDescription(
int position = 0;
for (size_t i = 0; i < properties_.size(); i++) {
- auto pair = properties_.at(i);
+ PrototypeAssignment pair = properties_.at(i);
const AstRawString* key_str = pair.first;
DirectHandle<Object> key = Cast<Object>(key_str->string());
diff --git a/src/interpreter/prototype-assignment-sequence-builder.h b/src/interpreter/prototype-assignment-sequence-builder.h
index 0c29f4f4bf0843877b2bd568866d8e290d4f3b51..6583022aa43a30df707325e6b7a14891f97a4ef1 100644
--- a/src/interpreter/prototype-assignment-sequence-builder.h
+++ b/src/interpreter/prototype-assignment-sequence-builder.h
@@ -7,17 +7,17 @@
#include "src/ast/ast.h"
#include "src/objects/literal-objects.h"
+#include "src/zone/zone-containers.h"
namespace v8 {
namespace internal {
+using PrototypeAssignment = std::pair<const AstRawString*, Expression*>;
+
class ProtoAssignmentSeqBuilder final : public ZoneObject {
public:
- static constexpr int kInitialPropertyCount = 64;
-
explicit ProtoAssignmentSeqBuilder(
- const SmallZoneVector<std::pair<const AstRawString*, Expression*>,
- kInitialPropertyCount>& properties)
+ ZoneVector<PrototypeAssignment>&& properties)
: properties_(std::move(properties)) {}
~ProtoAssignmentSeqBuilder() = default;
@@ -53,10 +53,10 @@ class ProtoAssignmentSeqBuilder final : public ZoneObject {
IsolateT* isolate,
Handle<Script> script);
+ const ZoneVector<PrototypeAssignment>* properties() { return &properties_; }
+
private:
- SmallZoneVector<std::pair<const AstRawString*, Expression*>,
- kInitialPropertyCount>
- properties_;
+ ZoneVector<PrototypeAssignment> properties_;
IndirectHandle<ObjectBoilerplateDescription> boilerplate_description_;
};

View File

@@ -0,0 +1,148 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Raphael Herouart <rherouart@chromium.org>
Date: Tue, 18 Nov 2025 11:00:26 +0000
Subject: [runtime] Correcting SetPrototypeProperties:
- Off by One Error in code generation
- Attributes should be preserved when setting properties
- SmallVector Memory Leak
Bug: 429332174
Change-Id: I8a669a98f44777ad54ea7e7bb06fc4f8c552c865
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7160576
Reviewed-by: Toon Verwaest <verwaest@chromium.org>
Commit-Queue: Raphael Herouart <rherouart@chromium.org>
Cr-Commit-Position: refs/heads/main@{#103784}
diff --git a/src/interpreter/bytecode-generator.cc b/src/interpreter/bytecode-generator.cc
index bf874d94ec34056d499fea9cbd034f959d506b58..63400abcc7ca0bd5369d566dd37e2ae93d7c3efe 100644
--- a/src/interpreter/bytecode-generator.cc
+++ b/src/interpreter/bytecode-generator.cc
@@ -2307,8 +2307,8 @@ void BytecodeGenerator::VisitDeclarations(Declaration::List* declarations) {
bool BytecodeGenerator::IsPrototypeAssignment(
Statement* stmt, Variable** var, HoleCheckMode* hole_check_mode,
- base::SmallVector<std::pair<const AstRawString*, Expression*>,
- kInitialPropertyCount>& properties,
+ SmallZoneVector<std::pair<const AstRawString*, Expression*>,
+ kInitialPropertyCount>& properties,
std::unordered_set<const AstRawString*>& duplicates) {
// The expression Statement is an assignment
// ========================================
@@ -2405,8 +2405,8 @@ bool BytecodeGenerator::IsPrototypeAssignment(
}
void BytecodeGenerator::VisitConsecutivePrototypeAssignments(
- const base::SmallVector<std::pair<const AstRawString*, Expression*>,
- kInitialPropertyCount>& properties,
+ const SmallZoneVector<std::pair<const AstRawString*, Expression*>,
+ kInitialPropertyCount>& properties,
Variable* var, HoleCheckMode hole_check_mode) {
// Create a boiler plate object in the constant pool to be merged into the
// proto
@@ -2444,9 +2444,9 @@ void BytecodeGenerator::VisitStatements(
HoleCheckMode hole_check_mode;
int proto_assign_idx = stmt_idx;
- base::SmallVector<std::pair<const AstRawString*, Expression*>,
- kInitialPropertyCount>
- properties;
+ SmallZoneVector<std::pair<const AstRawString*, Expression*>,
+ kInitialPropertyCount>
+ properties(zone());
std::unordered_set<const AstRawString*> duplicates;
while (proto_assign_idx < statements->length() &&
IsPrototypeAssignment(statements->at(proto_assign_idx), &var,
@@ -2457,10 +2457,10 @@ void BytecodeGenerator::VisitStatements(
if (proto_assign_idx - stmt_idx > 1) {
DCHECK_EQ((size_t)(proto_assign_idx - stmt_idx), properties.size());
VisitConsecutivePrototypeAssignments(properties, var, hole_check_mode);
- stmt_idx = proto_assign_idx; // the outer loop should now ignore these
- // statements
+ stmt_idx = proto_assign_idx - 1; // the outer loop should now ignore
+ // these statements
DCHECK(!builder()->RemainderOfBlockIsDead());
- if (stmt_idx == statements->length()) break;
+ continue;
}
}
diff --git a/src/interpreter/bytecode-generator.h b/src/interpreter/bytecode-generator.h
index 41c5cda1ca6dd4017b329e41bad8af3bcc4ee242..5dd136a01848aaf4a182dd31aa05a6ebe845fae8 100644
--- a/src/interpreter/bytecode-generator.h
+++ b/src/interpreter/bytecode-generator.h
@@ -73,13 +73,13 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
bool IsPrototypeAssignment(
Statement* stmt, Variable** var, HoleCheckMode* hole_check_mode,
- base::SmallVector<std::pair<const AstRawString*, Expression*>,
- kInitialPropertyCount>& properties,
+ SmallZoneVector<std::pair<const AstRawString*, Expression*>,
+ kInitialPropertyCount>& properties,
std::unordered_set<const AstRawString*>& duplicate);
void VisitConsecutivePrototypeAssignments(
- const base::SmallVector<std::pair<const AstRawString*, Expression*>,
- kInitialPropertyCount>& properties,
+ const SmallZoneVector<std::pair<const AstRawString*, Expression*>,
+ kInitialPropertyCount>& properties,
Variable* var, HoleCheckMode hole_check_mode);
// Visiting function for declarations list and statements are overridden.
void VisitModuleDeclarations(Declaration::List* declarations);
diff --git a/src/interpreter/prototype-assignment-sequence-builder.h b/src/interpreter/prototype-assignment-sequence-builder.h
index 880ed4dcec8399e9b52e6d1924b8773bff1db063..0c29f4f4bf0843877b2bd568866d8e290d4f3b51 100644
--- a/src/interpreter/prototype-assignment-sequence-builder.h
+++ b/src/interpreter/prototype-assignment-sequence-builder.h
@@ -16,8 +16,8 @@ class ProtoAssignmentSeqBuilder final : public ZoneObject {
static constexpr int kInitialPropertyCount = 64;
explicit ProtoAssignmentSeqBuilder(
- const base::SmallVector<std::pair<const AstRawString*, Expression*>,
- kInitialPropertyCount>& properties)
+ const SmallZoneVector<std::pair<const AstRawString*, Expression*>,
+ kInitialPropertyCount>& properties)
: properties_(std::move(properties)) {}
~ProtoAssignmentSeqBuilder() = default;
@@ -54,8 +54,8 @@ class ProtoAssignmentSeqBuilder final : public ZoneObject {
Handle<Script> script);
private:
- base::SmallVector<std::pair<const AstRawString*, Expression*>,
- kInitialPropertyCount>
+ SmallZoneVector<std::pair<const AstRawString*, Expression*>,
+ kInitialPropertyCount>
properties_;
IndirectHandle<ObjectBoilerplateDescription> boilerplate_description_;
};
diff --git a/src/runtime/runtime-literals.cc b/src/runtime/runtime-literals.cc
index ded3b466625c99da6f27936ae4e5e0c879baec1b..961b9ee5b7d329bb018300fe2f18bea60e1cd883 100644
--- a/src/runtime/runtime-literals.cc
+++ b/src/runtime/runtime-literals.cc
@@ -730,6 +730,13 @@ RUNTIME_FUNCTION(Runtime_SetPrototypeProperties) {
feedback_cell_array, current_slot));
}
+ if (IsSpecialReceiverMap(js_proto->map())) {
+ RETURN_RESULT_OR_FAILURE(
+ isolate, SetPrototypePropertiesSlow(isolate, context, obj,
+ object_boilerplate_description,
+ feedback_cell_array, current_slot));
+ }
+
bool is_default_func_prototype =
IsDefaultFunctionPrototype(js_proto, isolate);
@@ -782,7 +789,11 @@ RUNTIME_FUNCTION(Runtime_SetPrototypeProperties) {
value = InstantiateIfSharedFunctionInfo(
context, isolate, value, feedback_cell_array, current_slot);
DirectHandle<String> name = Cast<String>(key);
- JSObject::SetOwnPropertyIgnoreAttributes(js_proto, name, value, NONE)
+ Maybe<PropertyAttributes> maybe = JSReceiver::GetPropertyAttributes(&it);
+ PropertyAttributes attr = maybe.ToChecked();
+ if (attr == ABSENT) attr = NONE;
+
+ JSObject::SetOwnPropertyIgnoreAttributes(js_proto, name, value, attr)
.Check();
result = value;

View File

@@ -0,0 +1,153 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Raphael Herouart <rherouart@chromium.org>
Date: Tue, 21 Oct 2025 14:49:06 +0000
Subject: [runtime] SetPrototypeProperties handling of
Hole/Undefined/Non-Object Values
Bug: 451750214
Change-Id: Ida29afe0b92ed4a6acff97214005ed3589093294
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7062734
Commit-Queue: Raphael Herouart <rherouart@chromium.org>
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
Cr-Commit-Position: refs/heads/main@{#103261}
diff --git a/src/interpreter/bytecode-generator.cc b/src/interpreter/bytecode-generator.cc
index 6d444622bacbb71c4754bc055da5cae39ed213b7..bf874d94ec34056d499fea9cbd034f959d506b58 100644
--- a/src/interpreter/bytecode-generator.cc
+++ b/src/interpreter/bytecode-generator.cc
@@ -2306,7 +2306,7 @@ void BytecodeGenerator::VisitDeclarations(Declaration::List* declarations) {
// The subsequent ones must be about the same <var> to return true.
bool BytecodeGenerator::IsPrototypeAssignment(
- Statement* stmt, Variable** var,
+ Statement* stmt, Variable** var, HoleCheckMode* hole_check_mode,
base::SmallVector<std::pair<const AstRawString*, Expression*>,
kInitialPropertyCount>& properties,
std::unordered_set<const AstRawString*>& duplicates) {
@@ -2388,6 +2388,7 @@ bool BytecodeGenerator::IsPrototypeAssignment(
if (*var == nullptr) {
// This is the first proto assignment in the sequence
*var = tmp_var;
+ *hole_check_mode = proto_prop->obj()->AsVariableProxy()->hole_check_mode();
} else if (*var != tmp_var) {
// This prototype assignment is about another var
return false;
@@ -2398,13 +2399,15 @@ bool BytecodeGenerator::IsPrototypeAssignment(
prop_str,
value)); // This will be reused as part of an ObjectLiteral
+ DCHECK_EQ(*hole_check_mode,
+ proto_prop->obj()->AsVariableProxy()->hole_check_mode());
return true;
}
void BytecodeGenerator::VisitConsecutivePrototypeAssignments(
const base::SmallVector<std::pair<const AstRawString*, Expression*>,
kInitialPropertyCount>& properties,
- Variable* var) {
+ Variable* var, HoleCheckMode hole_check_mode) {
// Create a boiler plate object in the constant pool to be merged into the
// proto
size_t entry = builder()->AllocateDeferredConstantPoolEntry();
@@ -2428,7 +2431,7 @@ void BytecodeGenerator::VisitConsecutivePrototypeAssignments(
first_idx = 0;
}
// Load the variable whose prototype is to be set into the Accumulator
- BuildVariableLoad(var, HoleCheckMode::kElided);
+ BuildVariableLoad(var, hole_check_mode);
// Merge in-place proto-def boilerplate object into Accumulator
builder()->SetPrototypeProperties(entry, first_idx);
}
@@ -2438,6 +2441,8 @@ void BytecodeGenerator::VisitStatements(
for (int stmt_idx = start; stmt_idx < statements->length(); stmt_idx++) {
if (v8_flags.proto_assign_seq_opt) {
Variable* var = nullptr;
+ HoleCheckMode hole_check_mode;
+
int proto_assign_idx = stmt_idx;
base::SmallVector<std::pair<const AstRawString*, Expression*>,
kInitialPropertyCount>
@@ -2445,13 +2450,13 @@ void BytecodeGenerator::VisitStatements(
std::unordered_set<const AstRawString*> duplicates;
while (proto_assign_idx < statements->length() &&
IsPrototypeAssignment(statements->at(proto_assign_idx), &var,
- properties, duplicates)) {
+ &hole_check_mode, properties, duplicates)) {
++proto_assign_idx;
}
if (proto_assign_idx - stmt_idx > 1) {
DCHECK_EQ((size_t)(proto_assign_idx - stmt_idx), properties.size());
- VisitConsecutivePrototypeAssignments(properties, var);
+ VisitConsecutivePrototypeAssignments(properties, var, hole_check_mode);
stmt_idx = proto_assign_idx; // the outer loop should now ignore these
// statements
DCHECK(!builder()->RemainderOfBlockIsDead());
diff --git a/src/interpreter/bytecode-generator.h b/src/interpreter/bytecode-generator.h
index b6421819c353febe283069852645a6b85e4c855a..41c5cda1ca6dd4017b329e41bad8af3bcc4ee242 100644
--- a/src/interpreter/bytecode-generator.h
+++ b/src/interpreter/bytecode-generator.h
@@ -72,7 +72,7 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
static constexpr int kInitialPropertyCount = 64;
bool IsPrototypeAssignment(
- Statement* stmt, Variable** var,
+ Statement* stmt, Variable** var, HoleCheckMode* hole_check_mode,
base::SmallVector<std::pair<const AstRawString*, Expression*>,
kInitialPropertyCount>& properties,
std::unordered_set<const AstRawString*>& duplicate);
@@ -80,7 +80,7 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
void VisitConsecutivePrototypeAssignments(
const base::SmallVector<std::pair<const AstRawString*, Expression*>,
kInitialPropertyCount>& properties,
- Variable* var);
+ Variable* var, HoleCheckMode hole_check_mode);
// Visiting function for declarations list and statements are overridden.
void VisitModuleDeclarations(Declaration::List* declarations);
void VisitGlobalDeclarations(Declaration::List* declarations);
diff --git a/src/runtime/runtime-literals.cc b/src/runtime/runtime-literals.cc
index 83074253314c438eed3006332c63c6bf7dfe4421..ded3b466625c99da6f27936ae4e5e0c879baec1b 100644
--- a/src/runtime/runtime-literals.cc
+++ b/src/runtime/runtime-literals.cc
@@ -613,7 +613,7 @@ static DirectHandle<Object> InstantiateIfSharedFunctionInfo(
}
static MaybeDirectHandle<Object> SetPrototypePropertiesSlow(
- Isolate* isolate, DirectHandle<Context> context, DirectHandle<JSObject> obj,
+ Isolate* isolate, DirectHandle<Context> context, DirectHandle<JSAny> obj,
Handle<ObjectBoilerplateDescription> object_boilerplate_description,
DirectHandle<ClosureFeedbackCellArray> feedback_cell_array,
int& current_slot, int start_index = 0) {
@@ -684,7 +684,7 @@ RUNTIME_FUNCTION(Runtime_SetPrototypeProperties) {
HandleScope scope(isolate);
DCHECK_EQ(4, args.length());
DirectHandle<Context> context(isolate->context(), isolate);
- DirectHandle<JSObject> obj = args.at<JSObject>(0); // acc JS Object
+ DirectHandle<JSAny> obj = args.at<JSAny>(0); // acc JS Object
Handle<ObjectBoilerplateDescription> object_boilerplate_description =
args.at<ObjectBoilerplateDescription>(1);
DirectHandle<ClosureFeedbackCellArray> feedback_cell_array =
diff --git a/test/unittests/interpreter/bytecode_expectations/SetPrototypePropertiesOptimization.golden b/test/unittests/interpreter/bytecode_expectations/SetPrototypePropertiesOptimization.golden
index 84ebbb755f8a5432c2e8da3d26ceb648bee2cf2d..bb9d697caec6e79810fa757abec4b71ad4cf3f2f 100644
--- a/test/unittests/interpreter/bytecode_expectations/SetPrototypePropertiesOptimization.golden
+++ b/test/unittests/interpreter/bytecode_expectations/SetPrototypePropertiesOptimization.golden
@@ -50,15 +50,17 @@ snippet: "
"
frame size: 0
parameter count: 1
-bytecode array length: 7
+bytecode array length: 9
bytecodes: [
B(LdaImmutableCurrentContextSlot), U8(2),
- /* 109 E> */ B(SetPrototypeProperties), U8(0), U8(0),
+ /* 109 E> */ B(ThrowReferenceErrorIfHole), U8(1),
+ B(SetPrototypeProperties), U8(0), U8(0),
B(LdaUndefined),
/* 245 S> */ B(Return),
]
constant pool: [
OBJECT_BOILERPLATE_DESCRIPTION_TYPE,
+ INTERNALIZED_ONE_BYTE_STRING_TYPE ["MyClass"],
]
handlers: [
]

View File

@@ -40,6 +40,7 @@ DevToolsSecurity -enable
# security import "$dir"/public.key -k $KEY_CHAIN
# Generate Trust Settings
# TODO: Remove NPX
npm_config_yes=true npx ts-node "$(dirname $0)"/gen-trust.ts "$dir"/certificate.cer "$dir"/trust.xml
# Import Trust Settings

View File

@@ -1,6 +1,6 @@
const chalk = require('chalk');
const { GitProcess } = require('dugite');
const childProcess = require('node:child_process');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
@@ -61,13 +61,16 @@ function getAbsoluteElectronExec () {
return path.resolve(SRC_DIR, getElectronExec());
}
async function handleGitCall (args, gitDir) {
const details = await GitProcess.exec(args, gitDir);
if (details.exitCode === 0) {
return details.stdout.replace(/^\*|\s+|\s+$/, '');
function handleGitCall (args, gitDir) {
const result = childProcess.spawnSync('git', args, {
cwd: gitDir,
encoding: 'utf8',
stdio: ['inherit', 'pipe', 'pipe']
});
if (result.status === 0) {
return result.stdout.replace(/^\*|\s+|\s+$/, '');
} else {
const error = GitProcess.parseError(details.stderr);
console.log(`${fail} couldn't parse git process call: `, error);
console.log(`${fail} couldn't parse git process call: `, result.stderr);
process.exit(1);
}
}

View File

@@ -1,6 +1,5 @@
#!/usr/bin/env node
const { GitProcess } = require('dugite');
const { ESLint } = require('eslint');
const minimist = require('minimist');
@@ -153,7 +152,7 @@ const LINTERS = [{
}, {
key: 'javascript',
roots: ['build', 'default_app', 'lib', 'npm', 'script', 'spec'],
ignoreRoots: ['spec/node_modules'],
ignoreRoots: ['spec/node_modules', 'spec/fixtures/native-addon'],
test: filename => filename.endsWith('.js') || filename.endsWith('.ts') || filename.endsWith('.mjs'),
run: async (opts, filenames) => {
const eslint = new ESLint({
@@ -282,7 +281,7 @@ const LINTERS = [{
}, {
key: 'md',
roots: ['.'],
ignoreRoots: ['.git', 'node_modules', 'spec/node_modules'],
ignoreRoots: ['.git', 'node_modules', 'spec/node_modules', 'spec/fixtures/native-addon'],
test: filename => filename.endsWith('.md'),
run: async (opts, filenames) => {
const { getCodeBlocks } = await import('@electron/lint-roller/dist/lib/markdown.js');
@@ -431,9 +430,13 @@ function populateLinterWithArgs (linter, opts) {
}
async function findChangedFiles (top) {
const result = await GitProcess.exec(['diff', '--name-only', '--cached'], top);
if (result.exitCode !== 0) {
console.log('Failed to find changed files', GitProcess.parseError(result.stderr));
const result = childProcess.spawnSync('git', ['diff', '--name-only', '--cached'], {
cwd: top,
encoding: 'utf8',
stdio: ['inherit', 'pipe', 'pipe']
});
if (result.status !== 0) {
console.log('Failed to find changed files', result.stderr);
process.exit(1);
}
const relativePaths = result.stdout.split(/\r\n|\r|\n/g);

View File

@@ -9,7 +9,7 @@ const NAN_DIR = path.resolve(BASE, 'third_party', 'nan');
const NPX_CMD = process.platform === 'win32' ? 'npx.cmd' : 'npx';
const utils = require('./lib/utils');
const { YARN_VERSION } = require('./yarn');
const { YARN_SCRIPT_PATH } = require('./yarn');
if (!require.main) {
throw new Error('Must call the nan spec runner directly');
@@ -106,13 +106,12 @@ async function main () {
stdio: 'inherit',
shell: process.platform === 'win32'
});
if (buildStatus !== 0 || signal != null) {
console.error('Failed to build nan test modules');
return process.exit(buildStatus !== 0 ? buildStatus : signal);
}
const { status: installStatus, signal: installSignal } = cp.spawnSync(NPX_CMD, [`yarn@${YARN_VERSION}`, 'install'], {
const { status: installStatus, signal: installSignal } = cp.spawnSync(process.execPath, [YARN_SCRIPT_PATH, 'install'], {
env,
cwd: NAN_DIR,
stdio: 'inherit',

View File

@@ -113,6 +113,7 @@
"parallel/test-tls-passphrase",
"parallel/test-tls-peer-certificate",
"parallel/test-tls-pfx-authorizationerror",
"parallel/test-tls-psk-alpn-callback-exception-handling",
"parallel/test-tls-psk-circuit",
"parallel/test-tls-reduced-SECLEVEL-in-cipher",
"parallel/test-tls-root-certificates",

View File

@@ -13,6 +13,7 @@ import { createGitHubTokenStrategy } from '../github-token';
import { ELECTRON_ORG, ELECTRON_REPO, ElectronReleaseRepo, NIGHTLY_REPO } from '../types';
const rootPackageJson = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../../../package.json'), 'utf-8'));
rootPackageJson.name = 'electron';
if (!process.env.ELECTRON_NPM_OTP) {
console.error('Please set ELECTRON_NPM_OTP');
@@ -211,6 +212,7 @@ new Promise<string>((resolve, reject) => {
});
})
.then((tarballPath) => {
// TODO: Remove NPX
const existingVersionJSON = childProcess.execSync(`npx npm@7 view ${rootPackageJson.name}@${currentElectronVersion} --json`).toString('utf-8');
// It's possible this is a re-run and we already have published the package, if not we just publish like normal
if (!existingVersionJSON) {

View File

@@ -1,9 +1,9 @@
#!/usr/bin/env node
import { Octokit } from '@octokit/rest';
import { GitProcess } from 'dugite';
import { valid, compare, gte, lte } from 'semver';
import { spawnSync } from 'node:child_process';
import { basename } from 'node:path';
import { parseArgs } from 'node:util';
@@ -20,8 +20,12 @@ const semverify = (version: string) => version.replace(/^origin\//, '').replace(
const runGit = async (args: string[]) => {
console.info(`Running: git ${args.join(' ')}`);
const response = await GitProcess.exec(args, ELECTRON_DIR);
if (response.exitCode !== 0) {
const response = spawnSync('git', args, {
cwd: ELECTRON_DIR,
encoding: 'utf8',
stdio: ['inherit', 'pipe', 'pipe']
});
if (response.status !== 0) {
throw new Error(response.stderr.trim());
}
return response.stdout.trim();

View File

@@ -1,8 +1,8 @@
#!/usr/bin/env node
import { Octokit } from '@octokit/rest';
import { GitProcess } from 'dugite';
import { spawnSync } from 'node:child_process';
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
import { resolve as _resolve } from 'node:path';
@@ -105,8 +105,13 @@ class Pool {
**/
const runGit = async (dir: string, args: string[]) => {
const response = await GitProcess.exec(args, dir);
if (response.exitCode !== 0) {
const response = spawnSync('git', args, {
cwd: dir,
encoding: 'utf8',
stdio: ['inherit', 'pipe', 'pipe'],
maxBuffer: 100 * 1024 * 1024 // 100MB buffer to handle large git outputs
});
if (response.status !== 0) {
throw new Error(response.stderr.trim());
}
return response.stdout.trim();

View File

@@ -1,8 +1,7 @@
import { Octokit } from '@octokit/rest';
import * as chalk from 'chalk';
import { GitProcess } from 'dugite';
import { execSync } from 'node:child_process';
import { execSync, spawnSync } from 'node:child_process';
import { join } from 'node:path';
import { createGitHubTokenStrategy } from './github-token';
@@ -166,11 +165,12 @@ async function createRelease (
}
async function pushRelease (branch: string) {
const pushDetails = await GitProcess.exec(
['push', 'origin', `HEAD:${branch}`, '--follow-tags'],
ELECTRON_DIR
);
if (pushDetails.exitCode === 0) {
const pushDetails = spawnSync('git', ['push', 'origin', `HEAD:${branch}`, '--follow-tags'], {
cwd: ELECTRON_DIR,
encoding: 'utf8',
stdio: ['inherit', 'pipe', 'pipe']
});
if (pushDetails.status === 0) {
console.log(
`${pass} Successfully pushed the release. Wait for ` +
'release builds to finish before running "npm run release".'
@@ -191,11 +191,12 @@ async function runReleaseBuilds (branch: string, newVersion: string) {
async function tagRelease (version: string) {
console.log(`Tagging release ${version}.`);
const checkoutDetails = await GitProcess.exec(
['tag', '-a', '-m', version, version],
ELECTRON_DIR
);
if (checkoutDetails.exitCode === 0) {
const checkoutDetails = spawnSync('git', ['tag', '-a', '-m', version, version], {
cwd: ELECTRON_DIR,
encoding: 'utf8',
stdio: ['inherit', 'pipe', 'pipe']
});
if (checkoutDetails.status === 0) {
console.log(`${pass} Successfully tagged ${version}.`);
} else {
console.log(

View File

@@ -1,6 +1,7 @@
import { GitProcess } from 'dugite';
import * as semver from 'semver';
import { spawnSync } from 'node:child_process';
import { ELECTRON_DIR } from '../lib/utils';
export enum PreType {
@@ -27,7 +28,11 @@ export const isStable = (v: string) => {
export async function nextAlpha (v: string) {
const next = semver.coerce(semver.clean(v));
const tagBlob = await GitProcess.exec(['tag', '--list', '-l', `v${next}-alpha.*`], ELECTRON_DIR);
const tagBlob = spawnSync('git', ['tag', '--list', '-l', `v${next}-alpha.*`], {
cwd: ELECTRON_DIR,
encoding: 'utf8',
stdio: ['inherit', 'pipe', 'pipe']
});
const tags = tagBlob.stdout.split('\n').filter(e => e !== '');
tags.sort((t1, t2) => {
const a = parseInt(t1.split('.').pop()!, 10);
@@ -41,7 +46,11 @@ export async function nextAlpha (v: string) {
export async function nextBeta (v: string) {
const next = semver.coerce(semver.clean(v));
const tagBlob = await GitProcess.exec(['tag', '--list', '-l', `v${next}-beta.*`], ELECTRON_DIR);
const tagBlob = spawnSync('git', ['tag', '--list', '-l', `v${next}-beta.*`], {
cwd: ELECTRON_DIR,
encoding: 'utf8',
stdio: ['inherit', 'pipe', 'pipe']
});
const tags = tagBlob.stdout.split('\n').filter(e => e !== '');
tags.sort((t1, t2) => {
const a = parseInt(t1.split('.').pop()!, 10);
@@ -57,7 +66,11 @@ export async function nextNightly (v: string) {
let next = semver.valid(semver.coerce(v));
const pre = `nightly.${getCurrentDate()}`;
const branch = (await GitProcess.exec(['rev-parse', '--abbrev-ref', 'HEAD'], ELECTRON_DIR)).stdout.trim();
const branch = spawnSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
cwd: ELECTRON_DIR,
encoding: 'utf8',
stdio: ['inherit', 'pipe', 'pipe']
}).stdout.trim();
if (branch === 'main') {
next = semver.inc(await getLastMajorForMain(), 'major');
} else if (isStable(v)) {
@@ -69,8 +82,12 @@ export async function nextNightly (v: string) {
async function getLastMajorForMain () {
let branchNames;
const result = await GitProcess.exec(['branch', '-a', '--remote', '--list', 'origin/[0-9]*-x-y'], ELECTRON_DIR);
if (result.exitCode === 0) {
const result = spawnSync('git', ['branch', '-a', '--remote', '--list', 'origin/[0-9]*-x-y'], {
cwd: ELECTRON_DIR,
encoding: 'utf8',
stdio: ['inherit', 'pipe', 'pipe']
});
if (result.status === 0) {
branchNames = result.stdout.trim().split('\n');
const filtered = branchNames.map(b => b.replace('origin/', ''));
return getNextReleaseBranch(filtered);

View File

@@ -114,11 +114,14 @@ async function runClangTidy (
outDir: string,
filenames: string[],
checks: string = '',
jobs: number = 1
jobs: number = 1,
fix: boolean = false
): Promise<boolean> {
const cmd = path.resolve(LLVM_BIN, 'clang-tidy');
const args = [`-p=${outDir}`, '--use-color'];
const args = [`-p=${outDir}`];
if (!process.env.CI) args.push('--use-color');
if (fix) args.push('--fix');
if (checks) args.push(`--checks=${checks}`);
// Remove any files that aren't in the compilation database to prevent
@@ -209,7 +212,7 @@ function parseCommandLine () {
if (!arg || arg.startsWith('-')) {
console.log(
'Usage: script/run-clang-tidy.ts [-h|--help] [--jobs|-j] ' +
'[--checks] --out-dir OUTDIR [file1 file2]'
'[--fix] [--checks] --out-dir OUTDIR [file1 file2]'
);
process.exit(0);
}
@@ -218,7 +221,7 @@ function parseCommandLine () {
};
const opts = minimist(process.argv.slice(2), {
boolean: ['help'],
boolean: ['fix', 'help'],
string: ['checks', 'out-dir'],
default: { jobs: 1 },
alias: { help: 'h', jobs: 'j' },
@@ -270,17 +273,23 @@ async function main (): Promise<boolean> {
const filenames = [];
if (opts._.length > 0) {
if (opts._.some((filename) => filename.endsWith('.h'))) {
throw new ErrorWithExitCode(
'Filenames must be for translation units, not headers', 3
);
}
filenames.push(...opts._.map((filename) => path.resolve(filename)));
} else {
filenames.push(
...(await findMatchingFiles(
path.resolve(SOURCE_ROOT, 'shell'),
(filename: string) => /.*\.(?:cc|h|mm)$/.test(filename)
(filename: string) => /.*\.(?:cc|mm)$/.test(filename)
))
);
}
return runClangTidy(outDir, filenames, opts.checks, opts.jobs);
return runClangTidy(outDir, filenames, opts.checks, opts.jobs, opts.fix);
}
if (require.main === module) {

View File

@@ -21,6 +21,7 @@ const fail = chalk.red('✗');
const FAILURE_STATUS_KEY = 'Electron_Spec_Runner_Failures';
const args = minimist(process.argv, {
boolean: ['skipYarnInstall'],
string: ['runners', 'target', 'electronVersion'],
number: ['enableRerun'],
unknown: arg => unknownFlags.push(arg)
@@ -36,10 +37,9 @@ for (const flag of unknownFlags) {
}
const utils = require('./lib/utils');
const { YARN_VERSION } = require('./yarn');
const { YARN_SCRIPT_PATH } = require('./yarn');
const BASE = path.resolve(__dirname, '../..');
const NPX_CMD = process.platform === 'win32' ? 'npx.cmd' : 'npx';
const runners = new Map([
['main', { description: 'Main process specs', run: runMainProcessElectronTests }]
@@ -96,7 +96,7 @@ async function main () {
const somethingChanged = (currentSpecHash !== lastSpecHash) ||
(lastSpecInstallHash !== currentSpecInstallHash);
if (somethingChanged) {
if (somethingChanged && !args.skipYarnInstall) {
await installSpecModules(path.resolve(__dirname, '..', 'spec'));
await getSpecHash().then(saveSpecHash);
}
@@ -375,7 +375,7 @@ async function runTestUsingElectron (specDir, testName, shouldRerun, additionalA
if (shouldRerun) {
await rerunFailedTests(specDir, testName);
} else {
return false;
process.exit(1);
}
}
console.log(`${pass} Electron ${testName} process tests passed.`);
@@ -419,7 +419,8 @@ async function installSpecModules (dir) {
if (fs.existsSync(path.resolve(dir, 'node_modules'))) {
await fs.promises.rm(path.resolve(dir, 'node_modules'), { force: true, recursive: true });
}
const { status } = childProcess.spawnSync(NPX_CMD, [`yarn@${YARN_VERSION}`, 'install', '--frozen-lockfile'], {
const yarnArgs = [YARN_SCRIPT_PATH, 'install', '--immutable'];
const { status } = childProcess.spawnSync(process.execPath, yarnArgs, {
env,
cwd: dir,
stdio: 'inherit',
@@ -429,14 +430,27 @@ async function installSpecModules (dir) {
console.log(`${fail} Failed to yarn install in '${dir}'`);
process.exit(1);
}
if (process.platform === 'linux') {
const { status: rebuildStatus } = childProcess.spawnSync('npm', ['rebuild', 'abstract-socket'], {
env,
cwd: dir,
stdio: 'inherit',
shell: process.platform === 'win32'
});
if (rebuildStatus !== 0) {
console.log(`${fail} Failed to rebuild abstract-socket native module`);
process.exit(1);
}
}
}
function getSpecHash () {
return Promise.all([
(async () => {
const hasher = crypto.createHash('SHA256');
hasher.update(fs.readFileSync(path.resolve(__dirname, '../yarn.lock')));
hasher.update(fs.readFileSync(path.resolve(__dirname, '../spec/package.json')));
hasher.update(fs.readFileSync(path.resolve(__dirname, '../spec/yarn.lock')));
hasher.update(fs.readFileSync(path.resolve(__dirname, '../script/spec-runner.js')));
return hasher.digest('hex');
})(),

18
script/yarn.js Normal file → Executable file
View File

@@ -1,21 +1,7 @@
const cp = require('node:child_process');
const fs = require('node:fs');
const path = require('node:path');
const YARN_VERSION = /'yarn_version': '(.+?)'/.exec(fs.readFileSync(path.resolve(__dirname, '../DEPS'), 'utf8'))[1];
const NPX_CMD = process.platform === 'win32' ? 'npx.cmd' : 'npx';
exports.YARN_SCRIPT_PATH = path.resolve(__dirname, '..', '.yarn/releases/yarn-4.12.0.cjs');
if (require.main === module) {
const child = cp.spawn(NPX_CMD, [`yarn@${YARN_VERSION}`, ...process.argv.slice(2)], {
stdio: 'inherit',
env: {
...process.env,
npm_config_yes: 'true'
},
shell: process.platform === 'win32'
});
child.on('exit', code => process.exit(code));
require(exports.YARN_SCRIPT_PATH);
}
exports.YARN_VERSION = YARN_VERSION;

View File

@@ -43,13 +43,10 @@
#include "shell/browser/electron_gpu_client.h"
#include "shell/browser/feature_list.h"
#include "shell/browser/relauncher.h"
#include "shell/common/application_info.h"
#include "shell/common/electron_paths.h"
#include "shell/common/logging.h"
#include "shell/common/options_switches.h"
#include "shell/common/platform_util.h"
#include "shell/common/process_util.h"
#include "shell/common/thread_restrictions.h"
#include "shell/renderer/electron_renderer_client.h"
#include "shell/renderer/electron_sandboxed_renderer_client.h"
#include "shell/utility/electron_content_utility_client.h"
@@ -121,100 +118,6 @@ void InvalidParameterHandler(const wchar_t*,
}
#endif
// TODO(nornagon): move path provider overriding to its own file in
// shell/common
bool ElectronPathProvider(int key, base::FilePath* result) {
bool create_dir = false;
base::FilePath cur;
switch (key) {
case chrome::DIR_USER_DATA:
if (!base::PathService::Get(DIR_APP_DATA, &cur))
return false;
cur = cur.Append(base::FilePath::FromUTF8Unsafe(
GetPossiblyOverriddenApplicationName()));
create_dir = true;
break;
case DIR_CRASH_DUMPS:
if (!base::PathService::Get(chrome::DIR_USER_DATA, &cur))
return false;
cur = cur.Append(FILE_PATH_LITERAL("Crashpad"));
create_dir = true;
break;
case chrome::DIR_APP_DICTIONARIES:
// TODO(nornagon): can we just default to using Chrome's logic here?
if (!base::PathService::Get(DIR_SESSION_DATA, &cur))
return false;
cur = cur.Append(base::FilePath::FromUTF8Unsafe("Dictionaries"));
create_dir = true;
break;
case DIR_SESSION_DATA:
// By default and for backward, equivalent to DIR_USER_DATA.
return base::PathService::Get(chrome::DIR_USER_DATA, result);
case DIR_USER_CACHE: {
#if BUILDFLAG(IS_POSIX)
int parent_key = base::DIR_CACHE;
#else
// On Windows, there's no OS-level centralized location for caches, so
// store the cache in the app data directory.
int parent_key = base::DIR_ROAMING_APP_DATA;
#endif
if (!base::PathService::Get(parent_key, &cur))
return false;
cur = cur.Append(base::FilePath::FromUTF8Unsafe(
GetPossiblyOverriddenApplicationName()));
create_dir = true;
break;
}
#if BUILDFLAG(IS_LINUX)
case DIR_APP_DATA: {
auto env = base::Environment::Create();
cur = base::nix::GetXDGDirectory(
env.get(), base::nix::kXdgConfigHomeEnvVar, base::nix::kDotConfigDir);
break;
}
#endif
#if BUILDFLAG(IS_WIN)
case DIR_RECENT:
if (!platform_util::GetFolderPath(DIR_RECENT, &cur))
return false;
create_dir = true;
break;
#endif
case DIR_APP_LOGS:
#if BUILDFLAG(IS_MAC)
if (!base::PathService::Get(base::DIR_HOME, &cur))
return false;
cur = cur.Append(FILE_PATH_LITERAL("Library"));
cur = cur.Append(FILE_PATH_LITERAL("Logs"));
cur = cur.Append(base::FilePath::FromUTF8Unsafe(
GetPossiblyOverriddenApplicationName()));
#else
if (!base::PathService::Get(chrome::DIR_USER_DATA, &cur))
return false;
cur = cur.Append(base::FilePath::FromUTF8Unsafe("logs"));
#endif
create_dir = true;
break;
default:
return false;
}
// TODO(bauerb): http://crbug.com/259796
ScopedAllowBlockingForElectron allow_blocking;
if (create_dir && !base::PathExists(cur) && !base::CreateDirectory(cur)) {
return false;
}
*result = cur;
return true;
}
void RegisterPathProvider() {
base::PathService::RegisterProvider(ElectronPathProvider, PATH_START,
PATH_END);
}
void ValidateV8Snapshot(v8::StartupData* data) {
if (data->data &&
electron::fuses::IsEmbeddedAsarIntegrityValidationEnabled()) {

View File

@@ -155,7 +155,8 @@ void MenuMac::PopupOnUI(const base::WeakPtr<NativeWindow>& native_window,
if (rightmostMenuPoint > screenRight)
position.x = position.x - [menu size].width;
[popup_controllers_[window_id] setCloseCallback:std::move(close_callback)];
[popup_controllers_[window_id]
setPopupCloseCallback:std::move(close_callback)];
if (frame && frame->render_frame_host()) {
auto* rfh = frame->render_frame_host()->GetOutermostMainFrameOrEmbedder();

View File

@@ -0,0 +1,528 @@
// Copyright (c) 2025 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/api/electron_api_msix_updater.h"
#include <sstream>
#include <string_view>
#include "base/environment.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "shell/browser/browser.h"
#include "shell/browser/javascript_environment.h"
#include "shell/browser/native_window.h"
#include "shell/browser/window_list.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/error_thrower.h"
#include "shell/common/gin_helper/promise.h"
#include "shell/common/node_includes.h"
#if BUILDFLAG(IS_WIN)
#include <appmodel.h>
#include <roapi.h>
#include <windows.applicationmodel.h>
#include <windows.foundation.collections.h>
#include <windows.foundation.h>
#include <windows.foundation.metadata.h>
#include <windows.h>
#include <windows.management.deployment.h>
// Use pre-generated C++/WinRT headers from //third_party/nearby instead of the
// SDK's cppwinrt headers, which are missing implementation files.
#include "third_party/nearby/src/internal/platform/implementation/windows/generated/winrt/Windows.ApplicationModel.h"
#include "third_party/nearby/src/internal/platform/implementation/windows/generated/winrt/Windows.Foundation.Collections.h"
#include "third_party/nearby/src/internal/platform/implementation/windows/generated/winrt/Windows.Foundation.Metadata.h"
#include "third_party/nearby/src/internal/platform/implementation/windows/generated/winrt/Windows.Foundation.h"
#include "third_party/nearby/src/internal/platform/implementation/windows/generated/winrt/Windows.Management.Deployment.h"
#include "third_party/nearby/src/internal/platform/implementation/windows/generated/winrt/base.h"
#include "base/win/scoped_com_initializer.h"
#endif
namespace electron {
const bool debug_msix_updater =
base::Environment::Create()->HasVar("ELECTRON_DEBUG_MSIX_UPDATER");
} // namespace electron
namespace {
#if BUILDFLAG(IS_WIN)
// Helper function for debug logging
void DebugLog(std::string_view log_msg) {
if (electron::debug_msix_updater)
LOG(INFO) << std::string(log_msg);
}
// Check if the process has a package identity
bool HasPackageIdentity() {
UINT32 length = 0;
LONG rc = GetCurrentPackageFullName(&length, NULL);
return rc != APPMODEL_ERROR_NO_PACKAGE;
}
// POD struct to hold MSIX update options
struct UpdateMsixOptions {
bool defer_registration = false;
bool developer_mode = false;
bool force_shutdown = false;
bool force_target_shutdown = false;
bool force_update_from_any_version = false;
};
// POD struct to hold package registration options
struct RegisterPackageOptions {
bool force_shutdown = false;
bool force_target_shutdown = false;
bool force_update_from_any_version = false;
};
// Performs MSIX update on IO thread
void DoUpdateMsix(const std::string& package_uri,
UpdateMsixOptions opts,
scoped_refptr<base::SingleThreadTaskRunner> reply_runner,
gin_helper::Promise<void> promise) {
DebugLog("DoUpdateMsix: Starting");
using winrt::Windows::Foundation::AsyncStatus;
using winrt::Windows::Foundation::Uri;
using winrt::Windows::Management::Deployment::AddPackageOptions;
using winrt::Windows::Management::Deployment::DeploymentResult;
using winrt::Windows::Management::Deployment::PackageManager;
std::string error;
std::wstring packageUriString =
std::wstring(package_uri.begin(), package_uri.end());
Uri uri{packageUriString};
PackageManager packageManager;
AddPackageOptions packageOptions;
// Use the pre-parsed options
packageOptions.DeferRegistrationWhenPackagesAreInUse(opts.defer_registration);
packageOptions.DeveloperMode(opts.developer_mode);
packageOptions.ForceAppShutdown(opts.force_shutdown);
packageOptions.ForceTargetAppShutdown(opts.force_target_shutdown);
packageOptions.ForceUpdateFromAnyVersion(opts.force_update_from_any_version);
{
std::ostringstream oss;
oss << "Calling AddPackageByUriAsync... URI: " << package_uri;
DebugLog(oss.str());
}
{
std::ostringstream oss;
oss << "Update options - deferRegistration: " << opts.defer_registration
<< ", developerMode: " << opts.developer_mode
<< ", forceShutdown: " << opts.force_shutdown
<< ", forceTargetShutdown: " << opts.force_target_shutdown
<< ", forceUpdateFromAnyVersion: "
<< opts.force_update_from_any_version;
DebugLog(oss.str());
}
auto deploymentOperation =
packageManager.AddPackageByUriAsync(uri, packageOptions);
if (!deploymentOperation) {
DebugLog("Deployment operation is null");
error =
"Deployment is NULL. See "
"http://go.microsoft.com/fwlink/?LinkId=235160 for diagnosing.";
} else {
if (!opts.force_shutdown && !opts.force_target_shutdown) {
DebugLog("Waiting for deployment...");
deploymentOperation.get();
DebugLog("Deployment finished.");
if (deploymentOperation.Status() == AsyncStatus::Error) {
auto deploymentResult{deploymentOperation.GetResults()};
std::string errorText = winrt::to_string(deploymentResult.ErrorText());
std::string errorCode =
std::to_string(static_cast<int>(deploymentOperation.ErrorCode()));
error = errorText + " (" + errorCode + ")";
{
std::ostringstream oss;
oss << "Deployment failed: " << error;
DebugLog(oss.str());
}
} else if (deploymentOperation.Status() == AsyncStatus::Canceled) {
DebugLog("Deployment canceled");
error = "Deployment canceled";
} else if (deploymentOperation.Status() == AsyncStatus::Completed) {
DebugLog("MSIX Deployment completed.");
} else {
error = "Deployment status unknown";
DebugLog("Deployment status unknown");
}
} else {
// At this point, we can not await the deployment because we require a
// shutdown of the app to continue, so we do a fire and forget. When the
// deployment process tries ot shutdown the app, the process waits for us
// to finish here. But to finish we need to shutdow. That leads to a 30s
// dealock, till we forcefully get shutdown by the OS.
DebugLog(
"Deployment initiated. Force shutdown or target shutdown requested. "
"Good bye!");
}
}
// Post result back
reply_runner->PostTask(
FROM_HERE, base::BindOnce(
[](gin_helper::Promise<void> promise, std::string error) {
if (error.empty()) {
promise.Resolve();
} else {
promise.RejectWithErrorMessage(error);
}
},
std::move(promise), error));
}
// Performs package registration on IO thread
void DoRegisterPackage(const std::string& family_name,
RegisterPackageOptions opts,
scoped_refptr<base::SingleThreadTaskRunner> reply_runner,
gin_helper::Promise<void> promise) {
DebugLog("DoRegisterPackage: Starting");
using winrt::Windows::Foundation::AsyncStatus;
using winrt::Windows::Foundation::Collections::IIterable;
using winrt::Windows::Management::Deployment::DeploymentOptions;
using winrt::Windows::Management::Deployment::PackageManager;
std::string error;
auto familyNameH = winrt::to_hstring(family_name);
PackageManager packageManager;
DeploymentOptions deploymentOptions = DeploymentOptions::None;
// Use the pre-parsed options (no V8 access needed)
if (opts.force_shutdown) {
deploymentOptions |= DeploymentOptions::ForceApplicationShutdown;
}
if (opts.force_target_shutdown) {
deploymentOptions |= DeploymentOptions::ForceTargetApplicationShutdown;
}
if (opts.force_update_from_any_version) {
deploymentOptions |= DeploymentOptions::ForceUpdateFromAnyVersion;
}
// Create empty collections for dependency and optional packages
IIterable<winrt::hstring> emptyDependencies{nullptr};
IIterable<winrt::hstring> emptyOptional{nullptr};
{
std::ostringstream oss;
oss << "Calling RegisterPackageByFamilyNameAsync... FamilyName: "
<< family_name;
DebugLog(oss.str());
}
{
std::ostringstream oss;
oss << "Registration options - forceShutdown: " << opts.force_shutdown
<< ", forceTargetShutdown: " << opts.force_target_shutdown
<< ", forceUpdateFromAnyVersion: "
<< opts.force_update_from_any_version;
DebugLog(oss.str());
}
auto deploymentOperation = packageManager.RegisterPackageByFamilyNameAsync(
familyNameH, emptyDependencies, deploymentOptions, nullptr,
emptyOptional);
if (!deploymentOperation) {
error =
"Deployment is NULL. See "
"http://go.microsoft.com/fwlink/?LinkId=235160 for diagnosing.";
} else {
if (!opts.force_shutdown && !opts.force_target_shutdown) {
DebugLog("Waiting for registration...");
deploymentOperation.get();
DebugLog("Registration finished.");
if (deploymentOperation.Status() == AsyncStatus::Error) {
auto deploymentResult{deploymentOperation.GetResults()};
std::string errorText = winrt::to_string(deploymentResult.ErrorText());
std::string errorCode =
std::to_string(static_cast<int>(deploymentOperation.ErrorCode()));
error = errorText + " (" + errorCode + ")";
{
std::ostringstream oss;
oss << "Registration failed: " << error;
DebugLog(oss.str());
}
} else if (deploymentOperation.Status() == AsyncStatus::Canceled) {
DebugLog("Registration canceled");
error = "Registration canceled";
} else if (deploymentOperation.Status() == AsyncStatus::Completed) {
DebugLog("MSIX Registration completed.");
} else {
error = "Registration status unknown";
DebugLog("Registration status unknown");
}
} else {
// At this point, we can not await the registration because we require a
// shutdown of the app to continue, so we do a fire and forget. When the
// registration process tries ot shutdown the app, the process waits for
// us to finish here. But to finish we need to shutdown. That leads to a
// 30s dealock, till we forcefully get shutdown by the OS.
DebugLog(
"Registration initiated. Force shutdown or target shutdown "
"requested. Good bye!");
}
}
// Post result back to UI thread
reply_runner->PostTask(
FROM_HERE, base::BindOnce(
[](gin_helper::Promise<void> promise, std::string error) {
if (error.empty()) {
promise.Resolve();
} else {
promise.RejectWithErrorMessage(error);
}
},
std::move(promise), error));
}
#endif
// Update MSIX package
v8::Local<v8::Promise> UpdateMsix(const std::string& package_uri,
gin_helper::Dictionary options) {
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
gin_helper::Promise<void> promise(isolate);
v8::Local<v8::Promise> handle = promise.GetHandle();
#if BUILDFLAG(IS_WIN)
if (!HasPackageIdentity()) {
DebugLog("UpdateMsix: The process has no package identity");
promise.RejectWithErrorMessage("The process has no package identity.");
return handle;
}
// Parse options on UI thread (where V8 is available)
UpdateMsixOptions opts;
options.Get("deferRegistration", &opts.defer_registration);
options.Get("developerMode", &opts.developer_mode);
options.Get("forceShutdown", &opts.force_shutdown);
options.Get("forceTargetShutdown", &opts.force_target_shutdown);
options.Get("forceUpdateFromAnyVersion", &opts.force_update_from_any_version);
{
std::ostringstream oss;
oss << "UpdateMsix called with URI: " << package_uri;
DebugLog(oss.str());
}
// Post to IO thread
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&DoUpdateMsix, package_uri, opts,
base::SingleThreadTaskRunner::GetCurrentDefault(),
std::move(promise)));
#else
promise.RejectWithErrorMessage(
"MSIX updates are only supported on Windows with identity.");
#endif
return handle;
}
// Register MSIX package
v8::Local<v8::Promise> RegisterPackage(const std::string& family_name,
gin_helper::Dictionary options) {
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
gin_helper::Promise<void> promise(isolate);
v8::Local<v8::Promise> handle = promise.GetHandle();
#if BUILDFLAG(IS_WIN)
if (!HasPackageIdentity()) {
DebugLog("RegisterPackage: The process has no package identity");
promise.RejectWithErrorMessage("The process has no package identity.");
return handle;
}
// Parse options on UI thread (where V8 is available)
RegisterPackageOptions opts;
options.Get("forceShutdown", &opts.force_shutdown);
options.Get("forceTargetShutdown", &opts.force_target_shutdown);
options.Get("forceUpdateFromAnyVersion", &opts.force_update_from_any_version);
{
std::ostringstream oss;
oss << "RegisterPackage called with family name: " << family_name;
DebugLog(oss.str());
}
// Post to IO thread with POD options (no V8 objects)
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&DoRegisterPackage, family_name, opts,
base::SingleThreadTaskRunner::GetCurrentDefault(),
std::move(promise)));
#else
promise.RejectWithErrorMessage(
"MSIX package registration is only supported on Windows.");
#endif
return handle;
}
// Register application restart
// Only registers for update restarts (not crashes, hangs, or reboots)
bool RegisterRestartOnUpdate(const std::string& command_line) {
#if BUILDFLAG(IS_WIN)
if (!HasPackageIdentity()) {
DebugLog("Cannot restart: no package identity");
return false;
}
const wchar_t* commandLine = nullptr;
// Flags: RESTART_NO_CRASH | RESTART_NO_HANG | RESTART_NO_REBOOT
// This means: only restart on updates (RESTART_NO_PATCH is NOT set)
const DWORD dwFlags = 1 | 2 | 8; // 11
if (!command_line.empty()) {
std::wstring commandLineW =
std::wstring(command_line.begin(), command_line.end());
commandLine = commandLineW.c_str();
}
HRESULT hr = RegisterApplicationRestart(commandLine, dwFlags);
if (FAILED(hr)) {
{
std::ostringstream oss;
oss << "RegisterApplicationRestart failed with error code: " << hr;
DebugLog(oss.str());
}
return false;
}
{
std::ostringstream oss;
oss << "RegisterApplicationRestart succeeded"
<< (command_line.empty() ? "" : " with command line");
DebugLog(oss.str());
}
return true;
#else
return false;
#endif
}
// Get package information
v8::Local<v8::Value> GetPackageInfo() {
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
#if BUILDFLAG(IS_WIN)
// Check if running in a package
if (!HasPackageIdentity()) {
DebugLog("GetPackageInfo: The process has no package identity");
gin_helper::ErrorThrower thrower(isolate);
thrower.ThrowTypeError("The process has no package identity.");
return v8::Null(isolate);
}
DebugLog("GetPackageInfo: Retrieving package information");
gin_helper::Dictionary result(isolate, v8::Object::New(isolate));
// Check API contract version (Windows 10 version 1703 or later)
if (winrt::Windows::Foundation::Metadata::ApiInformation::
IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", 7)) {
using winrt::Windows::ApplicationModel::Package;
using winrt::Windows::ApplicationModel::PackageSignatureKind;
Package package = Package::Current();
// Get package ID and family name
std::string packageId = winrt::to_string(package.Id().FullName());
std::string familyName = winrt::to_string(package.Id().FamilyName());
result.Set("id", packageId);
result.Set("familyName", familyName);
result.Set("developmentMode", package.IsDevelopmentMode());
// Get package version
auto packageVersion = package.Id().Version();
std::string version = std::to_string(packageVersion.Major) + "." +
std::to_string(packageVersion.Minor) + "." +
std::to_string(packageVersion.Build) + "." +
std::to_string(packageVersion.Revision);
result.Set("version", version);
// Convert signature kind to string
std::string signatureKind;
switch (package.SignatureKind()) {
case PackageSignatureKind::Developer:
signatureKind = "developer";
break;
case PackageSignatureKind::Enterprise:
signatureKind = "enterprise";
break;
case PackageSignatureKind::None:
signatureKind = "none";
break;
case PackageSignatureKind::Store:
signatureKind = "store";
break;
case PackageSignatureKind::System:
signatureKind = "system";
break;
default:
signatureKind = "none";
break;
}
result.Set("signatureKind", signatureKind);
// Get app installer info if available
auto appInstallerInfo = package.GetAppInstallerInfo();
if (appInstallerInfo != nullptr) {
std::string uriStr = winrt::to_string(appInstallerInfo.Uri().ToString());
result.Set("appInstallerUri", uriStr);
}
} else {
// Windows version doesn't meet minimum API requirements
result.Set("familyName", "");
result.Set("id", "");
result.Set("developmentMode", false);
result.Set("signatureKind", "none");
result.Set("version", "");
}
return result.GetHandle();
#else
// Non-Windows platforms
gin_helper::Dictionary result(isolate, v8::Object::New(isolate));
result.Set("familyName", "");
result.Set("id", "");
result.Set("developmentMode", false);
result.Set("signatureKind", "none");
result.Set("version", "");
return result.GetHandle();
#endif
}
void Initialize(v8::Local<v8::Object> exports,
v8::Local<v8::Value> unused,
v8::Local<v8::Context> context,
void* priv) {
v8::Isolate* const isolate = electron::JavascriptEnvironment::GetIsolate();
gin_helper::Dictionary dict(isolate, exports);
dict.SetMethod("updateMsix", base::BindRepeating(&UpdateMsix));
dict.SetMethod("registerPackage", base::BindRepeating(&RegisterPackage));
dict.SetMethod("registerRestartOnUpdate",
base::BindRepeating(&RegisterRestartOnUpdate));
dict.SetMethod("getPackageInfo",
base::BindRepeating([]() { return GetPackageInfo(); }));
}
} // namespace
NODE_LINKED_BINDING_CONTEXT_AWARE(electron_browser_msix_updater, Initialize)

View File

@@ -0,0 +1,14 @@
// Copyright (c) 2013 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_BROWSER_API_ELECTRON_API_MSIX_UPDATER_H_
#define ELECTRON_SHELL_BROWSER_API_ELECTRON_API_MSIX_UPDATER_H_
namespace electron {
extern const bool debug_msix_updater;
} // namespace electron
#endif // ELECTRON_SHELL_BROWSER_API_ELECTRON_API_MSIX_UPDATER_H_

View File

@@ -1037,6 +1037,9 @@ void WebContents::InitWithWebContents(
}
WebContents::~WebContents() {
if (inspectable_web_contents_)
inspectable_web_contents_->GetView()->SetDelegate(nullptr);
if (owner_window_) {
owner_window_->RemoveBackgroundThrottlingSource(this);
}
@@ -1051,8 +1054,6 @@ WebContents::~WebContents() {
return;
}
inspectable_web_contents_->GetView()->SetDelegate(nullptr);
// This event is only for internal use, which is emitted when WebContents is
// being destroyed.
Emit("will-destroy");
@@ -2189,8 +2190,8 @@ void WebContents::DevToolsOpened() {
// Inherit owner window in devtools when it doesn't have one.
auto* devtools = inspectable_web_contents_->GetDevToolsWebContents();
bool has_window = devtools->GetUserData(NativeWindowRelay::UserDataKey());
if (owner_window_ && !has_window) {
DCHECK(!owner_window_.WasInvalidated());
if (owner_window() && !has_window) {
CHECK(!owner_window_.WasInvalidated());
DCHECK_EQ(handle->owner_window(), nullptr);
handle->SetOwnerWindow(devtools, owner_window());
}
@@ -2395,6 +2396,9 @@ void WebContents::LoadURL(const GURL& url,
return;
}
if (web_contents()->NeedToFireBeforeUnloadOrUnloadEvents())
pending_unload_url_ = url;
// Discard non-committed entries to ensure we don't re-use a pending entry.
web_contents()->GetController().DiscardNonCommittedEntries();
web_contents()->GetController().LoadURLWithParams(params);
@@ -3897,8 +3901,15 @@ void WebContents::RunBeforeUnloadDialog(content::WebContents* web_contents,
content::RenderFrameHost* rfh,
bool is_reload,
DialogClosedCallback callback) {
// TODO: asyncify?
bool default_prevented = Emit("will-prevent-unload");
if (pending_unload_url_.has_value() && !default_prevented) {
Emit("did-fail-load", static_cast<int>(net::ERR_ABORTED),
net::ErrorToShortString(net::ERR_ABORTED),
pending_unload_url_.value().possibly_invalid_spec(), true);
pending_unload_url_.reset();
}
std::move(callback).Run(default_prevented, std::u16string());
}

View File

@@ -844,6 +844,8 @@ class WebContents final : public ExclusiveAccessContext,
// 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.
typedef std::map<std::string, base::FilePath> PathsMap;
PathsMap saved_files_;

View File

@@ -5,12 +5,14 @@
#include "shell/browser/api/electron_api_web_request.h"
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include "base/containers/fixed_flat_map.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/values.h"
#include "content/public/browser/web_contents.h"
@@ -25,6 +27,7 @@
#include "shell/browser/api/electron_api_web_frame_main.h"
#include "shell/browser/electron_browser_context.h"
#include "shell/browser/javascript_environment.h"
#include "shell/browser/login_handler.h"
#include "shell/common/gin_converters/callback_converter.h"
#include "shell/common/gin_converters/frame_converter.h"
#include "shell/common/gin_converters/gurl_converter.h"
@@ -108,7 +111,7 @@ v8::Local<v8::Value> HttpResponseHeadersToV8(
// Overloaded by multiple types to fill the |details| object.
void ToDictionary(gin_helper::Dictionary* details,
extensions::WebRequestInfo* info) {
const extensions::WebRequestInfo* info) {
details->Set("id", info->id);
details->Set("url", info->url);
details->Set("method", info->method);
@@ -255,7 +258,7 @@ bool WebRequest::RequestFilter::MatchesType(
}
bool WebRequest::RequestFilter::MatchesRequest(
extensions::WebRequestInfo* info) const {
const extensions::WebRequestInfo* info) const {
// Matches URL and type, and does not match exclude URL.
return MatchesURL(info->url, include_url_patterns_) &&
!MatchesURL(info->url, exclude_url_patterns_) &&
@@ -287,6 +290,10 @@ struct WebRequest::BlockedRequest {
net::CompletionOnceCallback callback;
// Only used for onBeforeSendHeaders.
BeforeSendHeadersCallback before_send_headers_callback;
// The callback to invoke for auth. If |auth_callback.is_null()| is false,
// |callback| must be NULL.
// Only valid for OnAuthRequired.
AuthCallback auth_callback;
// Only used for onBeforeSendHeaders.
raw_ptr<net::HttpRequestHeaders> request_headers = nullptr;
// Only used for onHeadersReceived.
@@ -297,6 +304,8 @@ struct WebRequest::BlockedRequest {
std::string status_line;
// Only used for onBeforeRequest.
raw_ptr<GURL> new_url = nullptr;
// Owns the LoginHandler while waiting for auth credentials.
std::unique_ptr<LoginHandler> login_handler;
};
WebRequest::SimpleListenerInfo::SimpleListenerInfo(RequestFilter filter_,
@@ -603,6 +612,36 @@ void WebRequest::OnSendHeaders(extensions::WebRequestInfo* info,
HandleSimpleEvent(SimpleEvent::kOnSendHeaders, info, request, headers);
}
WebRequest::AuthRequiredResponse WebRequest::OnAuthRequired(
const extensions::WebRequestInfo* request_info,
const net::AuthChallengeInfo& auth_info,
WebRequest::AuthCallback callback,
net::AuthCredentials* credentials) {
content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
request_info->render_process_id, request_info->frame_routing_id);
content::WebContents* web_contents = nullptr;
if (rfh)
web_contents = content::WebContents::FromRenderFrameHost(rfh);
BlockedRequest blocked_request;
blocked_request.auth_callback = std::move(callback);
blocked_requests_[request_info->id] = std::move(blocked_request);
auto login_callback =
base::BindOnce(&WebRequest::OnLoginAuthResult, base::Unretained(this),
request_info->id, credentials);
scoped_refptr<net::HttpResponseHeaders> response_headers =
request_info->response_headers;
blocked_requests_[request_info->id].login_handler =
std::make_unique<LoginHandler>(
auth_info, web_contents,
static_cast<base::ProcessId>(request_info->render_process_id),
request_info->url, response_headers, std::move(login_callback));
return AuthRequiredResponse::AUTH_REQUIRED_RESPONSE_IO_PENDING;
}
void WebRequest::OnBeforeRedirect(extensions::WebRequestInfo* info,
const network::ResourceRequest& request,
const GURL& new_location) {
@@ -732,6 +771,26 @@ void WebRequest::HandleSimpleEvent(SimpleEvent event,
info.listener.Run(gin::ConvertToV8(isolate, details));
}
void WebRequest::OnLoginAuthResult(
uint64_t id,
net::AuthCredentials* credentials,
const std::optional<net::AuthCredentials>& maybe_creds) {
auto iter = blocked_requests_.find(id);
if (iter == blocked_requests_.end())
NOTREACHED();
AuthRequiredResponse action =
AuthRequiredResponse::AUTH_REQUIRED_RESPONSE_NO_ACTION;
if (maybe_creds.has_value()) {
*credentials = maybe_creds.value();
action = AuthRequiredResponse::AUTH_REQUIRED_RESPONSE_SET_AUTH;
}
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(iter->second.auth_callback), action));
blocked_requests_.erase(iter);
}
// static
gin_helper::Handle<WebRequest> WebRequest::FromOrCreate(
v8::Isolate* isolate,

View File

@@ -82,6 +82,11 @@ class WebRequest final : public gin_helper::DeprecatedWrappable<WebRequest>,
void OnSendHeaders(extensions::WebRequestInfo* info,
const network::ResourceRequest& request,
const net::HttpRequestHeaders& headers) override;
AuthRequiredResponse OnAuthRequired(
const extensions::WebRequestInfo* info,
const net::AuthChallengeInfo& auth_info,
AuthCallback callback,
net::AuthCredentials* credentials) override;
void OnBeforeRedirect(extensions::WebRequestInfo* info,
const network::ResourceRequest& request,
const GURL& new_location) override;
@@ -157,6 +162,12 @@ class WebRequest final : public gin_helper::DeprecatedWrappable<WebRequest>,
v8::Local<v8::Value> response);
void OnHeadersReceivedListenerResult(uint64_t id,
v8::Local<v8::Value> response);
// Callback invoked by LoginHandler when auth credentials are supplied via
// the unified 'login' event. Bridges back into WebRequest's AuthCallback.
void OnLoginAuthResult(
uint64_t id,
net::AuthCredentials* credentials,
const std::optional<net::AuthCredentials>& maybe_creds);
class RequestFilter {
public:
@@ -174,7 +185,7 @@ class WebRequest final : public gin_helper::DeprecatedWrappable<WebRequest>,
bool is_match_pattern = true);
void AddType(extensions::WebRequestResourceType type);
bool MatchesRequest(extensions::WebRequestInfo* info) const;
bool MatchesRequest(const extensions::WebRequestInfo* info) const;
private:
bool MatchesURL(const GURL& url,

View File

@@ -42,6 +42,23 @@ LoginHandler::LoginHandler(
response_headers, first_auth_attempt));
}
LoginHandler::LoginHandler(
const net::AuthChallengeInfo& auth_info,
content::WebContents* web_contents,
base::ProcessId process_id,
const GURL& url,
scoped_refptr<net::HttpResponseHeaders> response_headers,
content::LoginDelegate::LoginAuthRequiredCallback auth_required_callback)
: LoginHandler(auth_info,
web_contents,
/*is_request_for_primary_main_frame=*/false,
/*is_request_for_navigation=*/false,
process_id,
url,
std::move(response_headers),
/*first_auth_attempt=*/false,
std::move(auth_required_callback)) {}
void LoginHandler::EmitEvent(
net::AuthChallengeInfo auth_info,
content::WebContents* web_contents,

View File

@@ -32,6 +32,13 @@ class LoginHandler : public content::LoginDelegate {
scoped_refptr<net::HttpResponseHeaders> response_headers,
bool first_auth_attempt,
content::LoginDelegate::LoginAuthRequiredCallback auth_required_callback);
LoginHandler(
const net::AuthChallengeInfo& auth_info,
content::WebContents* web_contents,
base::ProcessId process_id,
const GURL& url,
scoped_refptr<net::HttpResponseHeaders> response_headers,
content::LoginDelegate::LoginAuthRequiredCallback auth_required_callback);
~LoginHandler() override;
// disable copy

View File

@@ -56,11 +56,6 @@ static NSDictionary* UNNotificationResponseToNSDictionary(
}
- (void)applicationWillFinishLaunching:(NSNotification*)notify {
// Don't add the "Enter Full Screen" menu item automatically.
[[NSUserDefaults standardUserDefaults]
setBool:NO
forKey:@"NSFullScreenMenuItemEverywhere"];
[[[NSWorkspace sharedWorkspace] notificationCenter]
addObserver:self
selector:@selector(willPowerOff:)

Some files were not shown because too many files have changed in this diff Show More