Compare commits

..

33 Commits

Author SHA1 Message Date
trop[bot]
d9c33a951a build: add header for SetStackDumpFirstChanceCallback in renderer client (#48980)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Keeley Hammond <vertedinde@electronjs.org>
2025-11-15 10:04:28 -08:00
trop[bot]
8b02e33187 build: limit workflow gh token permissions (#48969)
* build: limit workflow gh token permissions

Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>

* feedback

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 <samuel.r.attard@gmail.com>
Co-authored-by: Samuel Attard <sattard@anthropic.com>
2025-11-15 11:32:21 +01:00
trop[bot]
eecca2cb19 fix: revert enabling WASM trap handlers in all Node.js processes (#48975)
Revert "fix: enable wasm trap handlers in all Node.js processes (#48788)"

This reverts commit ca0b46b413.

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Keeley Hammond <khammond@slack-corp.com>
2025-11-14 18:56:50 -08:00
trop[bot]
08b5ef556c test: add view.getBounds|setBounds tests (#48961)
test: add view.getBounds|setBounds tests

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2025-11-14 12:12:22 -05:00
Shelley Vohr
ab85f2c2f7 chore: cherry-pick 4cf9311 from v8 (#48951) 2025-11-13 14:50:46 -08:00
Fedor Indutny
1936243ce1 fix: crash on windows when UTF-8 is in path (#48944)
In 6399527761 we changed the path strings
that `node_modules.cc` operates on from single-byte to wide strings.
Unfortunately this means that `generic_path()` that the
"fix: ensure TraverseParent bails on resource path exit" patch was
calling was no longer a safe method to call on Windows if the underlying
string has unicode characters in it.

Here we fix it by using `ConvertGenericPathToUTF8` from the Node.js
internal utilities.
2025-11-13 13:56:30 -08:00
trop[bot]
e7e052f5b1 docs: fix docs for app.isHardwareAccelerationEnabled() (#48945)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Niklas Wenzel <dev@nikwen.de>
2025-11-13 14:59:43 -05:00
trop[bot]
349a9b6398 docs: explain how to load SF Symbols with nativeImage (#48939)
* docs: explain how to load SF Symbols with `nativeImage`

Co-authored-by: Niklas Wenzel <dev@nikwen.de>

* fix: use single quotes

Co-authored-by: Niklas Wenzel <dev@nikwen.de>

* fix: use single quotes

Co-authored-by: Niklas Wenzel <dev@nikwen.de>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Niklas Wenzel <dev@nikwen.de>
2025-11-13 11:19:21 -05:00
trop[bot]
b5f19ce974 feat: add bypassCustomProtocolHandlers option to net.request (#48882)
* feat: add bypassCustomProtocolHandlers option to net.request

Co-authored-by: Kai <udbmnm@163.com>

* style: fix lint errors in api-protocol-spec

Co-authored-by: Kai <udbmnm@163.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Kai <udbmnm@163.com>
2025-11-13 10:35:06 -05:00
trop[bot]
bb930b887b feat: add app.isHardwareAccelerationEnabled() (#48680)
* feat: add app.isHardwareAccelerationEnabled()

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

* chore: address review feedback

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>
2025-11-13 10:32:14 -05:00
trop[bot]
e962bc3743 docs: clarify meaning of string value for menu item icon (#48938)
* docs: clarify meaning of string value for menu item icon

Co-authored-by: Niklas Wenzel <dev@nikwen.de>

* fix: format

Co-authored-by: Niklas Wenzel <dev@nikwen.de>

* fix: wording

Co-authored-by: Niklas Wenzel <dev@nikwen.de>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Niklas Wenzel <dev@nikwen.de>
2025-11-13 10:28:01 -05:00
trop[bot]
895cf006e7 fix: Windows: Calling window.setFocusable(true) will no longer cause a window to lose focus (#48928)
Make setFocusable only deactivate a window if focusable is false. Do not deactivate a window when setting focusable to true.

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: vulture <isu@vulture.fm>
2025-11-13 09:52:58 +01:00
trop[bot]
bc1ca72dc7 docs: fix v40 stable release date (#48920)
* docs(timelines): Correct v40.0.0 stable release date

On the Electron Timelines tutorial page (/docs/latest/tutorial/electron-timelines), there is a clear typo in the release schedule for v40.0.0.

The table currently lists the dates as:
* Alpha: 2025-Oct-30
* Beta: 2025-Dec-03
* **Stable: 2025-Oct-28**

This is logically incorrect, as the 'Stable' release date (Oct 28) is listed *before* both the 'Alpha' (Oct 30) and 'Beta' (Dec 03) dates for the same version.

This appears to be a copy-paste error, as the 'Stable' date (2025-Oct-28) is identical to the 'Stable' date for the v39.0.0 release in the preceding row.

This commit updates the 'Stable' date for v40.0.0 to its correct value, ensuring the timeline is accurate and logical.

Co-authored-by: 정승규 <43807509+jsk41755@users.noreply.github.com>

* docs: Update v40.0.0 stable date to 2026-Jan-13 based on Chromium schedule

Co-authored-by: 정승규 <43807509+jsk41755@users.noreply.github.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: 정승규 <43807509+jsk41755@users.noreply.github.com>
2025-11-12 15:44:22 +01:00
electron-roller[bot]
a9a4c77353 chore: bump chromium to 142.0.7444.162 (39-x-y) (#48899)
* chore: bump chromium in DEPS to 142.0.7444.162

* chore: update patches

---------

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
2025-11-12 11:24:14 +01:00
trop[bot]
0f613246d9 fix: restore window's canHide property on macOS (#48901)
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-12 09:42:37 +01:00
trop[bot]
a77b92adf2 ci: exclude top-level docs files from full CI (#48895)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2025-11-11 21:06:23 +01:00
trop[bot]
d62c324567 fix: enable wasm trap handlers in all Node.js processes (#48837)
* fix: enable wasm trap handlers in all Node.js processes

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

* fix: separate registrations to account for featurelist init

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

* build: add missing header for SetStackDumpFirstChanceCallback

* fix: pdf spec

delay load pdfjs-dist which compiles wasm on load, trap handlers
will be initialized once the user script starts but before app#ready.

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: deepak1556 <hop2deep@gmail.com>
2025-11-11 18:45:57 +09:00
trop[bot]
108a26a0f9 docs: remove electronegativity (#48887)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Erick Zhao <ezhao@slack-corp.com>
2025-11-11 09:59:08 +01:00
Shelley Vohr
331f8cca47 feat: enable resetting accent color (#48852) 2025-11-10 16:44:20 -05:00
trop[bot]
215128715a feat: Focus DevTools when breakpoint is triggered (#48702)
`bringToFront` DevTools message is sent when breakpoint is triggered
or inspect is called and Chromium upon this message activates DevTools
via `DevToolsUIBindings::Delegate::ActivateWindow`:
```
void DevToolsWindow::ActivateWindow() {
  if (life_stage_ != kLoadCompleted)
    return;
\#if BUILDFLAG(IS_ANDROID)
  NOTIMPLEMENTED();
\#else
  if (is_docked_ && GetInspectedBrowserWindow())
    main_web_contents_->Focus();
  else if (!is_docked_ && browser_ && !browser_->window()->IsActive())
    browser_->window()->Activate();
\#endif
}
```
which implements: `DevToolsUIBindings::Delegate::ActivateWindow`.

Electron also implements this interface in:
`electron::InspectableWebContents`. However it was only setting
a zoom level, therefore this commit extends it with activation
of the DevTools.

Only supported for DevTools manged by `electron::InspectableWebContents`.

Closes: #37388

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Michał Pichliński <michal.pichlinski@here.io>
2025-11-10 16:42:40 -05:00
trop[bot]
efcab52714 feat: add SF Symbol support to NativeImage::CreateFromNamedImage (#48773)
* feat: add SF Symbol support to NativeImage::CreateFromNamedImage

Co-authored-by: TheCommieAxolotl <87679354+TheCommieAxolotl@users.noreply.github.com>

* use obj-c name in NSImage constructor

Co-authored-by: TheCommieAxolotl <87679354+TheCommieAxolotl@users.noreply.github.com>

* add test for named symbol image

Co-authored-by: TheCommieAxolotl <87679354+TheCommieAxolotl@users.noreply.github.com>

* apply suggested simplification

Co-authored-by: TheCommieAxolotl <87679354+TheCommieAxolotl@users.noreply.github.com>

* fix: support NX cocoa prefix

Co-authored-by: TheCommieAxolotl <87679354+TheCommieAxolotl@users.noreply.github.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: TheCommieAxolotl <87679354+TheCommieAxolotl@users.noreply.github.com>
2025-11-10 21:18:45 +01:00
Fedor Indutny
3495a3da69 fix: ESM-from-CJS import when CJK is in path (#48873)
Upstream fix: https://github.com/nodejs/node/pull/60575
2025-11-10 14:59:58 -05:00
trop[bot]
364f3ed265 refactor: remove spellcheck::kWinDelaySpellcheckServiceInit patch (#48857)
refactor: remove spellcheck::kWinDelaySpellcheckServiceInit patch

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2025-11-10 11:54:00 -05:00
trop[bot]
52f0b08bbb docs: update macOS version support in README (#48870)
Update macOS version support in README

Support for macOS 11 (BigSur) was removed from v38: https://www.electronjs.org/blog/electron-38-0#removed-macos-11-support

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Foad Lind <20255390+foadlind@users.noreply.github.com>
2025-11-10 09:52:05 -05:00
trop[bot]
8453434b7e fix: the parent window remained interactive after the modal window was opened (#48865)
fix: fix the issue where the parent window remained interactive after the modal window was opened in somecases.

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-10 13:55:25 +01:00
trop[bot]
8d2ad379a6 fix: CSD window frame tiles properly on Wayland (#48834)
fix: CSD window frame tiles properly on Linux

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Mitchell Cohen <mitch.cohen@me.com>
2025-11-08 10:50:05 +01:00
trop[bot]
b847900ad2 docs: Update 404 devtools extension documentation link (#48842)
* docs: Update 404 devtools extension documentation link

https://developer.chrome.com/extensions/devtools

↑Current link is not exists.

So update to most relevant developer.chrome.com page.

https://developer.chrome.com/docs/extensions/how-to/devtools/extend-devtools#creating

Co-authored-by: Ryota Murakami <dojce1048@gmail.com>

* docs: remove unnecessary anchor link

Co-authored-by: Erick Zhao <erick@hotmail.ca>

Co-authored-by: Ryota Murakami <dojce1048@gmail.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Ryota Murakami <dojce1048@gmail.com>
2025-11-07 18:42:14 +01:00
electron-roller[bot]
97a339250a chore: bump chromium to 142.0.7444.134 (39-x-y) (#48818)
* chore: bump chromium in DEPS to 142.0.7444.134

* chore: update patches

---------

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
2025-11-07 11:23:43 +01:00
trop[bot]
3fb81955bb fix(reland): allow disabling all NSMenuItems (#48830)
* fix: allow disabling all `NSMenuItems` (#48598)

fix: allow disabling all NSMenuItems

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

* fix: add guard for type

Co-authored-by: George Xu <george.xu@slack-corp.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: George Xu <george.xu@slack-corp.com>
2025-11-07 10:36:49 +01:00
trop[bot]
862129506f fix: oom crash in v8 when optimizing wasm (#48815)
* fix: oom crash in v8 when optimizing wasm

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

* 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: John Kleinschmidt <jkleinsc@electronjs.org>
2025-11-07 14:35:07 +09:00
trop[bot]
6972fbfea3 build: use --keep-non-patch flag with git am (#48808)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: David Sanders <dsanders11@ucsbalum.com>
2025-11-06 12:00:02 +01:00
trop[bot]
81332eaf65 fix: revert allow disabling all NSMenuItems, fix menu crash (#48800)
Revert "fix: allow disabling all `NSMenuItems` (#48598)"

This reverts commit 0cb4fdd0f2.

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Keeley Hammond <khammond@slack-corp.com>
2025-11-05 20:27:43 -08:00
trop[bot]
a06d00df6c fix: draw smoothing round rect corner (#48781)
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-05 18:26:06 -05:00
71 changed files with 1516 additions and 86 deletions

View File

@@ -3,10 +3,14 @@ name: Archaeologist
on:
pull_request:
permissions: {}
jobs:
archaeologist-dig:
name: Archaeologist Dig
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout Electron
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.0.2

View File

@@ -6,9 +6,13 @@ on:
schedule:
- cron: "0 0 * * *"
permissions: {}
jobs:
build-git-cache-linux:
runs-on: electron-arc-centralus-linux-amd64-32core
permissions:
contents: read
container:
image: ghcr.io/electron/build:bc2f48b2415a670de18d13605b1cf0eb5fdbaae1
options: --user root
@@ -30,6 +34,8 @@ jobs:
build-git-cache-windows:
runs-on: electron-arc-centralus-linux-amd64-32core
permissions:
contents: read
container:
image: ghcr.io/electron/build:bc2f48b2415a670de18d13605b1cf0eb5fdbaae1
options: --user root --device /dev/fuse --cap-add SYS_ADMIN
@@ -52,6 +58,8 @@ jobs:
build-git-cache-macos:
runs-on: electron-arc-centralus-linux-amd64-32core
permissions:
contents: read
# This job updates the same git cache as linux, so it needs to run after the linux one.
needs: build-git-cache-linux
container:

View File

@@ -43,10 +43,13 @@ defaults:
run:
shell: bash
permissions: {}
jobs:
setup:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
outputs:
docs: ${{ steps.filter.outputs.docs }}
@@ -63,6 +66,10 @@ jobs:
filters: |
docs:
- 'docs/**'
- README.md
- SECURITY.md
- CONTRIBUTING.md
- CODE_OF_CONDUCT.md
src:
- '!docs/**'
- name: Set Outputs for Build Image SHA & Docs Only
@@ -80,6 +87,8 @@ jobs:
needs: setup
if: ${{ !inputs.skip-lint }}
uses: ./.github/workflows/pipeline-electron-lint.yml
permissions:
contents: read
with:
container: '{"image":"ghcr.io/electron/build:${{ needs.setup.outputs.build-image-sha }}","options":"--user root"}'
secrets: inherit
@@ -89,6 +98,8 @@ jobs:
needs: [setup, checkout-linux]
if: ${{ needs.setup.outputs.docs-only == 'true' }}
uses: ./.github/workflows/pipeline-electron-docs-only.yml
permissions:
contents: read
with:
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"]}'
secrets: inherit
@@ -98,6 +109,8 @@ jobs:
needs: setup
if: ${{ needs.setup.outputs.src == 'true' && !inputs.skip-macos}}
runs-on: electron-arc-centralus-linux-amd64-32core
permissions:
contents: read
container:
image: ghcr.io/electron/build:${{ needs.setup.outputs.build-image-sha }}
options: --user root
@@ -126,6 +139,8 @@ jobs:
needs: setup
if: ${{ !inputs.skip-linux}}
runs-on: electron-arc-centralus-linux-amd64-32core
permissions:
contents: read
container:
image: ghcr.io/electron/build:${{ needs.setup.outputs.build-image-sha }}
options: --user root
@@ -155,6 +170,8 @@ jobs:
needs: setup
if: ${{ needs.setup.outputs.src == 'true' && !inputs.skip-windows }}
runs-on: electron-arc-centralus-linux-amd64-32core
permissions:
contents: read
container:
image: ghcr.io/electron/build:${{ needs.setup.outputs.build-image-sha }}
options: --user root --device /dev/fuse --cap-add SYS_ADMIN
@@ -185,6 +202,8 @@ jobs:
# GN Check Jobs
macos-gn-check:
uses: ./.github/workflows/pipeline-segment-electron-gn-check.yml
permissions:
contents: read
needs: checkout-macos
with:
target-platform: macos
@@ -195,6 +214,8 @@ jobs:
linux-gn-check:
uses: ./.github/workflows/pipeline-segment-electron-gn-check.yml
permissions:
contents: read
needs: checkout-linux
if: ${{ needs.setup.outputs.src == 'true' }}
with:
@@ -207,6 +228,8 @@ jobs:
windows-gn-check:
uses: ./.github/workflows/pipeline-segment-electron-gn-check.yml
permissions:
contents: read
needs: checkout-windows
with:
target-platform: win
@@ -400,6 +423,8 @@ jobs:
gha-done:
name: GitHub Actions Completed
runs-on: ubuntu-latest
permissions:
contents: read
needs: [docs-only, macos-x64, macos-arm64, linux-x64, linux-x64-asan, linux-arm, linux-arm64, windows-x64, windows-x86, windows-arm64]
if: always() && !contains(needs.*.result, 'failure')
steps:

View File

@@ -1,16 +1,20 @@
name: Clean Source Cache
description: |
This workflow cleans up the source cache on the cross-instance cache volume
to free up space. It runs daily at midnight and clears files older than 15 days.
# Description:
# This workflow cleans up the source cache on the cross-instance cache volume
# to free up space. It runs daily at midnight and clears files older than 15 days.
on:
schedule:
- cron: "0 0 * * *"
permissions: {}
jobs:
clean-src-cache:
runs-on: electron-arc-centralus-linux-amd64-32core
permissions:
contents: read
container:
image: ghcr.io/electron/build:bc2f48b2415a670de18d13605b1cf0eb5fdbaae1
options: --user root

View File

@@ -4,14 +4,15 @@ on:
issues:
types: [labeled]
permissions: # added using https://github.com/step-security/secure-workflows
contents: read
permissions: {}
jobs:
issue-labeled-with-status:
name: status/{confirmed,reviewed} label added
if: github.event.label.name == 'status/confirmed' || github.event.label.name == 'status/reviewed'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Generate GitHub App token
uses: electron/github-app-auth-action@384fd19694fe7b6dcc9a684746c6976ad78228ae # v1.1.1
@@ -31,6 +32,8 @@ jobs:
name: blocked/* label added
if: startsWith(github.event.label.name, 'blocked/')
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Generate GitHub App token
uses: electron/github-app-auth-action@384fd19694fe7b6dcc9a684746c6976ad78228ae # v1.1.1

View File

@@ -11,6 +11,7 @@ jobs:
add-to-issue-triage:
if: ${{ contains(github.event.issue.labels.*.name, 'bug :beetle:') }}
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Generate GitHub App token
uses: electron/github-app-auth-action@384fd19694fe7b6dcc9a684746c6976ad78228ae # v1.1.1
@@ -28,6 +29,7 @@ jobs:
set-labels:
if: ${{ contains(github.event.issue.labels.*.name, 'bug :beetle:') }}
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Generate GitHub App token
uses: electron/github-app-auth-action@384fd19694fe7b6dcc9a684746c6976ad78228ae # v1.1.1

View File

@@ -10,6 +10,7 @@ jobs:
issue-transferred:
name: Issue Transferred
runs-on: ubuntu-latest
permissions: {}
if: ${{ !github.event.changes.new_repository.private }}
steps:
- name: Generate GitHub App token

View File

@@ -4,14 +4,15 @@ on:
issues:
types: [unlabeled]
permissions:
contents: read
permissions: {}
jobs:
issue-unlabeled-blocked:
name: All blocked/* labels removed
if: startsWith(github.event.label.name, 'blocked/') && github.event.issue.state == 'open'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Check for any blocked labels
id: check-for-blocked-labels

View File

@@ -17,9 +17,13 @@ on:
type: boolean
default: false
permissions: {}
jobs:
checkout-linux:
runs-on: electron-arc-centralus-linux-amd64-32core
permissions:
contents: read
container:
image: ghcr.io/electron/build:${{ inputs.build-image-sha }}
options: --user root
@@ -40,6 +44,8 @@ jobs:
publish-x64:
uses: ./.github/workflows/pipeline-segment-electron-build.yml
permissions:
contents: read
needs: checkout-linux
with:
environment: production-release
@@ -55,6 +61,8 @@ jobs:
publish-arm:
uses: ./.github/workflows/pipeline-segment-electron-build.yml
permissions:
contents: read
needs: checkout-linux
with:
environment: production-release
@@ -70,6 +78,8 @@ jobs:
publish-arm64:
uses: ./.github/workflows/pipeline-segment-electron-build.yml
permissions:
contents: read
needs: checkout-linux
with:
environment: production-release

View File

@@ -18,9 +18,13 @@ on:
type: boolean
default: false
permissions: {}
jobs:
checkout-macos:
runs-on: electron-arc-centralus-linux-amd64-32core
permissions:
contents: read
container:
image: ghcr.io/electron/build:${{ inputs.build-image-sha }}
options: --user root
@@ -44,6 +48,8 @@ jobs:
publish-x64-darwin:
uses: ./.github/workflows/pipeline-segment-electron-build.yml
permissions:
contents: read
needs: checkout-macos
with:
environment: production-release
@@ -59,6 +65,8 @@ jobs:
publish-x64-mas:
uses: ./.github/workflows/pipeline-segment-electron-build.yml
permissions:
contents: read
needs: checkout-macos
with:
environment: production-release
@@ -74,6 +82,8 @@ jobs:
publish-arm64-darwin:
uses: ./.github/workflows/pipeline-segment-electron-build.yml
permissions:
contents: read
needs: checkout-macos
with:
environment: production-release
@@ -89,6 +99,8 @@ jobs:
publish-arm64-mas:
uses: ./.github/workflows/pipeline-segment-electron-build.yml
permissions:
contents: read
needs: checkout-macos
with:
environment: production-release

View File

@@ -55,6 +55,8 @@ on:
type: boolean
default: false
permissions: {}
concurrency:
group: electron-build-and-test-and-nan-${{ inputs.target-platform }}-${{ inputs.target-arch }}-${{ github.ref_protected == true && github.run_id || github.ref }}
cancel-in-progress: ${{ github.ref_protected != true }}
@@ -62,6 +64,8 @@ concurrency:
jobs:
build:
uses: ./.github/workflows/pipeline-segment-electron-build.yml
permissions:
contents: read
with:
build-runs-on: ${{ inputs.build-runs-on }}
build-container: ${{ inputs.build-container }}
@@ -74,6 +78,10 @@ jobs:
secrets: inherit
test:
uses: ./.github/workflows/pipeline-segment-electron-test.yml
permissions:
contents: read
issues: read
pull-requests: read
needs: build
with:
target-arch: ${{ inputs.target-arch }}
@@ -83,6 +91,8 @@ jobs:
secrets: inherit
nn-test:
uses: ./.github/workflows/pipeline-segment-node-nan-test.yml
permissions:
contents: read
needs: build
with:
target-arch: ${{ inputs.target-arch }}

View File

@@ -64,14 +64,13 @@ concurrency:
group: electron-build-and-test-${{ inputs.target-platform }}-${{ inputs.target-arch }}-${{ github.ref_protected == true && github.run_id || github.ref }}
cancel-in-progress: ${{ github.ref_protected != true }}
permissions:
contents: read
issues: read
pull-requests: read
permissions: {}
jobs:
build:
uses: ./.github/workflows/pipeline-segment-electron-build.yml
permissions:
contents: read
with:
build-runs-on: ${{ inputs.build-runs-on }}
build-container: ${{ inputs.build-container }}
@@ -86,6 +85,10 @@ jobs:
secrets: inherit
test:
uses: ./.github/workflows/pipeline-segment-electron-test.yml
permissions:
contents: read
issues: read
pull-requests: read
needs: build
with:
target-arch: ${{ inputs.target-arch }}

View File

@@ -8,6 +8,8 @@ on:
description: 'Container to run the docs-only ts compile in'
type: string
permissions: {}
concurrency:
group: electron-docs-only-${{ github.ref }}
cancel-in-progress: true
@@ -19,6 +21,8 @@ jobs:
docs-only:
name: Docs Only Compile
runs-on: electron-arc-centralus-linux-amd64-4core
permissions:
contents: read
timeout-minutes: 20
container: ${{ fromJSON(inputs.container) }}
steps:

View File

@@ -8,6 +8,8 @@ on:
description: 'Container to run lint in'
type: string
permissions: {}
concurrency:
group: electron-lint-${{ github.ref_protected == true && github.run_id || github.ref }}
cancel-in-progress: ${{ github.ref_protected != true }}
@@ -19,6 +21,8 @@ jobs:
lint:
name: Lint
runs-on: electron-arc-centralus-linux-amd64-4core
permissions:
contents: read
timeout-minutes: 20
container: ${{ fromJSON(inputs.container) }}
steps:

View File

@@ -59,6 +59,8 @@ on:
type: boolean
default: false
permissions: {}
concurrency:
group: electron-build-${{ inputs.target-platform }}-${{ inputs.target-arch }}-${{ inputs.target-variant }}-${{ inputs.is-asan }}-${{ github.ref_protected == true && github.run_id || github.ref }}
cancel-in-progress: ${{ github.ref_protected != true }}
@@ -81,6 +83,8 @@ jobs:
run:
shell: bash
runs-on: ${{ inputs.build-runs-on }}
permissions:
contents: read
container: ${{ fromJSON(inputs.build-container) }}
environment: ${{ inputs.environment }}
env:

View File

@@ -26,6 +26,8 @@ on:
type: string
default: testing
permissions: {}
concurrency:
group: electron-gn-check-${{ inputs.target-platform }}-${{ github.ref }}
cancel-in-progress: true
@@ -41,6 +43,8 @@ jobs:
run:
shell: bash
runs-on: ${{ inputs.check-runs-on }}
permissions:
contents: read
container: ${{ fromJSON(inputs.check-container) }}
steps:
- name: Checkout Electron

View File

@@ -35,10 +35,7 @@ concurrency:
group: electron-test-${{ inputs.target-platform }}-${{ inputs.target-arch }}-${{ inputs.is-asan }}-${{ github.ref_protected == true && github.run_id || github.ref }}
cancel-in-progress: ${{ github.ref_protected != true }}
permissions:
contents: read
issues: read
pull-requests: read
permissions: {}
env:
CHROMIUM_GIT_COOKIE: ${{ secrets.CHROMIUM_GIT_COOKIE }}
@@ -53,6 +50,10 @@ jobs:
run:
shell: bash
runs-on: ${{ inputs.test-runs-on }}
permissions:
contents: read
issues: read
pull-requests: read
container: ${{ fromJSON(inputs.test-container) }}
strategy:
fail-fast: false

View File

@@ -26,6 +26,8 @@ on:
type: string
default: testing
permissions: {}
concurrency:
group: electron-node-nan-test-${{ inputs.target-platform }}-${{ inputs.target-arch }}-${{ github.ref_protected == true && github.run_id || github.ref }}
cancel-in-progress: ${{ github.ref_protected != true }}
@@ -39,6 +41,8 @@ jobs:
node-tests:
name: Run Node.js Tests
runs-on: electron-arc-centralus-linux-amd64-8core
permissions:
contents: read
timeout-minutes: 30
env:
TARGET_ARCH: ${{ inputs.target-arch }}
@@ -93,6 +97,8 @@ jobs:
nan-tests:
name: Run Nan Tests
runs-on: electron-arc-centralus-linux-amd64-4core
permissions:
contents: read
timeout-minutes: 30
env:
TARGET_ARCH: ${{ inputs.target-arch }}

View File

@@ -11,6 +11,7 @@ jobs:
name: backport/requested label added
if: github.event.label.name == 'backport/requested 🗳'
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Trigger Slack workflow
uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1
@@ -28,6 +29,7 @@ jobs:
name: deprecation-review/complete label added
if: github.event.label.name == 'deprecation-review/complete ✅'
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Generate GitHub App token
uses: electron/github-app-auth-action@384fd19694fe7b6dcc9a684746c6976ad78228ae # v1.1.1

View File

@@ -7,8 +7,7 @@ on:
- edited
- synchronize
permissions:
contents: read
permissions: {}
jobs:
main:

View File

@@ -11,6 +11,7 @@ jobs:
check-stable-prep-items:
name: Check Stable Prep Items
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Generate GitHub App token
uses: electron/github-app-auth-action@384fd19694fe7b6dcc9a684746c6976ad78228ae # v1.1.1

View File

@@ -10,6 +10,7 @@ permissions: {}
jobs:
stale:
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Generate GitHub App token
uses: electron/github-app-auth-action@384fd19694fe7b6dcc9a684746c6976ad78228ae # v1.1.1
@@ -31,6 +32,7 @@ jobs:
only-pr-labels: not-a-real-label
pending-repro:
runs-on: ubuntu-latest
permissions: {}
if: ${{ always() }}
needs: stale
steps:

View File

@@ -18,9 +18,13 @@ on:
type: boolean
default: false
permissions: {}
jobs:
checkout-windows:
runs-on: electron-arc-centralus-linux-amd64-32core
permissions:
contents: read
container:
image: ghcr.io/electron/build:${{ inputs.build-image-sha }}
options: --user root --device /dev/fuse --cap-add SYS_ADMIN
@@ -48,6 +52,8 @@ jobs:
publish-x64-win:
uses: ./.github/workflows/pipeline-segment-electron-build.yml
permissions:
contents: read
needs: checkout-windows
with:
environment: production-release
@@ -62,6 +68,8 @@ jobs:
publish-arm64-win:
uses: ./.github/workflows/pipeline-segment-electron-build.yml
permissions:
contents: read
needs: checkout-windows
with:
environment: production-release
@@ -76,6 +84,8 @@ jobs:
publish-x86-win:
uses: ./.github/workflows/pipeline-segment-electron-build.yml
permissions:
contents: read
needs: checkout-windows
with:
environment: production-release

2
DEPS
View File

@@ -2,7 +2,7 @@ gclient_gn_args_from = 'src'
vars = {
'chromium_version':
'142.0.7444.59',
'142.0.7444.162',
'node_version':
'v22.21.1',
'nan_version':

View File

@@ -37,7 +37,7 @@ For more installation options and troubleshooting tips, see
Each Electron release provides binaries for macOS, Windows, and Linux.
* macOS (Big Sur and up): Electron provides 64-bit Intel and Apple Silicon / ARM binaries for macOS.
* macOS (Monterey and up): Electron provides 64-bit Intel and Apple Silicon / ARM binaries for macOS.
* Windows (Windows 10 and up): Electron provides `ia32` (`x86`), `x64` (`amd64`), and `arm64` binaries for Windows. Windows on ARM support was added in Electron 5.0.8. Support for Windows 7, 8 and 8.1 was [removed in Electron 23, in line with Chromium's Windows deprecation policy](https://www.electronjs.org/blog/windows-7-to-8-1-deprecation-notice).
* Linux: The prebuilt binaries of Electron are built on Ubuntu 22.04. They have also been verified to work on:
* Ubuntu 18.04 and newer

View File

@@ -1216,6 +1216,13 @@ Disables hardware acceleration for current app.
This method can only be called before app is ready.
### `app.isHardwareAccelerationEnabled()`
Returns `boolean` - whether hardware acceleration is currently enabled.
> [!NOTE]
> This information is only usable after the `gpu-info-update` event is emitted.
### `app.disableDomainBlockingFor3DAPIs()`
By default, Chromium disables 3D APIs (e.g. WebGL) until restart on a per

View File

@@ -1262,15 +1262,16 @@ Sets the properties for the window's taskbar button.
#### `win.setAccentColor(accentColor)` _Windows_
* `accentColor` boolean | string - The accent color for the window. By default, follows user preference in System Settings.
* `accentColor` boolean | string | null - The accent color for the window. By default, follows user preference in System Settings. To reset to system default, pass `null`.
Sets the system accent color and highlighting of active window border.
The `accentColor` parameter accepts the following values:
* **Color string** - Sets a custom accent color using standard CSS color formats (Hex, RGB, RGBA, HSL, HSLA, or named colors). Alpha values in RGBA/HSLA formats are ignored and the color is treated as fully opaque.
* **`true`** - Uses the system's default accent color from user preferences in System Settings.
* **`false`** - Explicitly disables accent color highlighting for the window.
* **Color string** - Like `true`, but sets a custom accent color using standard CSS color formats (Hex, RGB, RGBA, HSL, HSLA, or named colors). Alpha values in RGBA/HSLA formats are ignored and the color is treated as fully opaque.
* **`true`** - Enable accent color highlighting for the window with the system accent color regardless of whether accent colors are enabled for windows in System `Settings.`
* **`false`** - Disable accent color highlighting for the window regardless of whether accent colors are currently enabled for windows in System Settings.
* **`null`** - Reset window accent color behavior to follow behavior set in System Settings.
Examples:
@@ -1283,11 +1284,14 @@ win.setAccentColor('#ff0000')
// RGB format (alpha ignored if present).
win.setAccentColor('rgba(255,0,0,0.5)')
// Use system accent color.
// Enable accent color, using the color specified in System Settings.
win.setAccentColor(true)
// Disable accent color.
win.setAccentColor(false)
// Reset window accent color behavior to follow behavior set in System Settings.
win.setAccentColor(null)
```
#### `win.getAccentColor()` _Windows_

View File

@@ -1467,15 +1467,16 @@ Sets the properties for the window's taskbar button.
#### `win.setAccentColor(accentColor)` _Windows_
* `accentColor` boolean | string - The accent color for the window. By default, follows user preference in System Settings.
* `accentColor` boolean | string | null - The accent color for the window. By default, follows user preference in System Settings. To reset to system default, pass `null`.
Sets the system accent color and highlighting of active window border.
The `accentColor` parameter accepts the following values:
* **Color string** - Sets a custom accent color using standard CSS color formats (Hex, RGB, RGBA, HSL, HSLA, or named colors). Alpha values in RGBA/HSLA formats are ignored and the color is treated as fully opaque.
* **`true`** - Uses the system's default accent color from user preferences in System Settings.
* **`false`** - Explicitly disables accent color highlighting for the window.
* **Color string** - Like `true`, but sets a custom accent color using standard CSS color formats (Hex, RGB, RGBA, HSL, HSLA, or named colors). Alpha values in RGBA/HSLA formats are ignored and the color is treated as fully opaque.
* **`true`** - Enable accent color highlighting for the window with the system accent color regardless of whether accent colors are enabled for windows in System `Settings.`
* **`false`** - Disable accent color highlighting for the window regardless of whether accent colors are currently enabled for windows in System Settings.
* **`null`** - Reset window accent color behavior to follow behavior set in System Settings.
Examples:
@@ -1488,11 +1489,14 @@ win.setAccentColor('#ff0000')
// RGB format (alpha ignored if present).
win.setAccentColor('rgba(255,0,0,0.5)')
// Use system accent color.
// Enable accent color, using the color specified in System Settings.
win.setAccentColor(true)
// Disable accent color.
win.setAccentColor(false)
// Reset window accent color behavior to follow behavior set in System Settings.
win.setAccentColor(null)
```
#### `win.getAccentColor()` _Windows_

View File

@@ -25,6 +25,11 @@ following properties:
with which the request is associated. Defaults to the empty string. The
`session` option supersedes `partition`. Thus if a `session` is explicitly
specified, `partition` is ignored.
* `bypassCustomProtocolHandlers` boolean (optional) - When set to `true`,
custom protocol handlers registered for the request's URL scheme will not be
called. This allows forwarding an intercepted request to the built-in
handler. [webRequest](web-request.md) handlers will still be triggered
when bypassing custom protocols. Defaults to `false`.
* `credentials` string (optional) - Can be `include`, `omit` or
`same-origin`. Whether to send
[credentials](https://fetch.spec.whatwg.org/#credentials) with this

View File

@@ -34,7 +34,8 @@ See [`Menu`](menu.md) for examples.
* `sublabel` string (optional) _macOS_ - Available in macOS >= 14.4
* `toolTip` string (optional) _macOS_ - Hover text for this menu item.
* `accelerator` string (optional) - An [Accelerator](../tutorial/keyboard-shortcuts.md#accelerators) string.
* `icon` ([NativeImage](native-image.md) | string) (optional)
* `icon` ([NativeImage](native-image.md) | string) (optional) - Can be a
[NativeImage](native-image.md) or the file path of an icon.
* `enabled` boolean (optional) - If false, the menu item will be greyed out and
unclickable.
* `acceleratorWorksWhenHidden` boolean (optional) _macOS_ - default is `true`, and when `false` will prevent the accelerator from triggering the item if the item is not visible.

View File

@@ -202,8 +202,7 @@ Creates a new `NativeImage` instance from `dataUrl`, a base 64 encoded [Data URL
Returns `NativeImage`
Creates a new `NativeImage` instance from the `NSImage` that maps to the
given image name. See Apple's [`NSImageName`](https://developer.apple.com/documentation/appkit/nsimagename#2901388)
documentation for a list of possible values.
given image name. See Apple's [`NSImageName`](https://developer.apple.com/documentation/appkit/nsimagename#2901388) documentation and [SF Symbols](https://developer.apple.com/sf-symbols/) for a list of possible values.
The `hslShift` is applied to the image with the following rules:
@@ -231,6 +230,15 @@ echo -e '#import <Cocoa/Cocoa.h>\nint main() { NSLog(@"%@", SYSTEM_IMAGE_NAME);
where `SYSTEM_IMAGE_NAME` should be replaced with any value from [this list](https://developer.apple.com/documentation/appkit/nsimagename?language=objc).
For SF Symbols, usage looks as follows:
```js
const image = nativeImage.createFromNamedImage('square.and.pencil')
```
where `'square.and.pencil'` is the symbol name from the
[SF Symbols app](https://developer.apple.com/sf-symbols/).
## Class: NativeImage
> Natively wrap images such as tray, dock, and application icons.

View File

@@ -94,7 +94,7 @@ If the extension works on Chrome but not on Electron, file a bug in Electron's
[issue tracker][issue-tracker] and describe which part
of the extension is not working as expected.
[devtools-extension]: https://developer.chrome.com/extensions/devtools
[devtools-extension]: https://developer.chrome.com/docs/extensions/how-to/devtools/extend-devtools
[session]: ../api/session.md
[react-devtools]: https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi
[load-extension]: ../api/extensions-api.md#extensionsloadextensionpath-options

View File

@@ -9,7 +9,7 @@ check out our [Electron Versioning](./electron-versioning.md) doc.
| Electron | Alpha | Beta | Stable | EOL | Chrome | Node | Supported |
| ------- | ----- | ------- | ------ | ------ | ---- | ---- | ---- |
| 40.0.0 | 2025-Oct-30 | 2025-Dec-03 | 2025-Oct-28 | 2026-Jun-30 | M144 | TBD | ✅ |
| 40.0.0 | 2025-Oct-30 | 2025-Dec-03 | 2026-Jan-13 | 2026-Jun-30 | M144 | TBD | ✅ |
| 39.0.0 | 2025-Sep-04 | 2025-Oct-01 | 2025-Oct-28 | 2026-May-05 | M142 | v22.20 | ✅ |
| 38.0.0 | 2025-Jun-26 | 2025-Aug-06 | 2025-Sep-02 | 2026-Mar-10 | M140 | v22.18 | ✅ |
| 37.0.0 | 2025-May-01 | 2025-May-28 | 2025-Jun-24 | 2026-Jan-13 | M138 | v22.16 | ✅ |

View File

@@ -118,13 +118,6 @@ You should at least follow these steps to improve the security of your applicati
19. [Check which fuses you can change](#19-check-which-fuses-you-can-change)
20. [Do not expose Electron APIs to untrusted web content](#20-do-not-expose-electron-apis-to-untrusted-web-content)
To automate the detection of misconfigurations and insecure patterns, it is
possible to use
[Electronegativity](https://github.com/doyensec/electronegativity). For
additional details on potential weaknesses and implementation bugs when
developing applications using Electron, please refer to this
[guide for developers and auditors](https://doyensec.com/resources/us-17-Carettoni-Electronegativity-A-Study-Of-Electron-Security-wp.pdf).
### 1. Only load secure content
Any resources not included with your application should be loaded using a

View File

@@ -289,7 +289,8 @@ function parseOptions (optionsIn: ClientRequestConstructorOptions | string): Nod
referrerPolicy: options.referrerPolicy,
cache: options.cache,
allowNonHttpProtocols: Object.hasOwn(options, kAllowNonHttpProtocols),
priority: options.priority
priority: options.priority,
bypassCustomProtocolHandlers: options.bypassCustomProtocolHandlers
};
if ('priorityIncremental' in options) {
urlLoaderOptions.priorityIncremental = options.priorityIncremental;

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 4308450d0a0ac99561f8d1b1b110d0b29eeb1201..6af6ff976da4b3df2a2377babef1bf84da1db917 100644
index 15cfa64348e80a3507fc83c3819e9abdb238bffb..4650080f45da4e321d4b0f0dabd98f2bf097c7c2 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 b10b1fe3795149b2a4e74255b7f17ef7f5de52c3..1ab2b3a420df30cddd4a7a9f1d4fabdca967691c 100644
index a25e99fac5f5a171becff833464cdb3dfe7522c2..123a7c1fc980b1b3bcbe0e3e00afa594632d720b 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

@@ -10,10 +10,10 @@ on Windows. We should refactor our code so that this patch isn't
necessary.
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 0fa02e6a98c795795c67a17f0b6be16f3a066d91..4cc81c0eed1b797c2ec940718c43571990463e4d 100644
index 64fc07ab5ae7112be4226ef8f5bc13124bc13e78..da3237026408f71b5fb144801ba0be2390b5d65c 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -25445,6 +25445,21 @@
@@ -25439,6 +25439,21 @@
]
}
],

View File

@@ -39,7 +39,6 @@ build_change_crdtp_protocoltypetraits_signatures_to_avoid_conflict.patch
fix_adjust_wpt_and_webidl_tests_for_enabled_float16array.patch
chore_add_createexternalizabletwobytestring_to_globals.patch
refactor_attach_cppgc_heap_on_v8_isolate_creation.patch
fix_ensure_traverseparent_bails_on_resource_path_exit.patch
cli_move_--trace-atomics-wait_to_eol.patch
fix_cppgc_initializing_twice.patch
fix_task_starvation_in_inspector_context_test.patch
@@ -52,3 +51,5 @@ api_remove_deprecated_getisolate.patch
src_switch_from_get_setprototype_to_get_setprototypev2.patch
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

View File

@@ -592,7 +592,7 @@ index 3c5f38ba4f492749c9d7d82179d2a6563787602b..6e83da3ee975dea431e21209bba9227e
data_.Reset();
return ret;
diff --git a/src/node_modules.cc b/src/node_modules.cc
index ed22da844a61b14b8580cd3d6bb3a233b8559b38..14f2a35f87e8c2fa17898147d7247cc00c066f35 100644
index 62050791174563f88b8629d576eed8959b3c2e20..fa04b4a8cdd02a2cad1eaf2e5a848b951d1b1150 100644
--- a/src/node_modules.cc
+++ b/src/node_modules.cc
@@ -64,7 +64,7 @@ void BindingData::Deserialize(v8::Local<v8::Context> context,
@@ -604,7 +604,7 @@ index ed22da844a61b14b8580cd3d6bb3a233b8559b38..14f2a35f87e8c2fa17898147d7247cc0
Realm* realm = Realm::GetCurrent(context);
BindingData* binding = realm->AddBindingData<BindingData>(holder);
CHECK_NOT_NULL(binding);
@@ -656,7 +656,7 @@ void BindingData::CreatePerContextProperties(Local<Object> target,
@@ -617,7 +617,7 @@ void BindingData::CreatePerContextProperties(Local<Object> target,
Realm* realm = Realm::GetCurrent(context);
realm->AddBindingData<BindingData>(target);

View File

@@ -8,10 +8,10 @@ resource path. This commit ensures that the TraverseParent function
bails out if the parent path is outside of the resource path.
diff --git a/src/node_modules.cc b/src/node_modules.cc
index 60b03b1563b230f78d8a9bdd8480da4d2ae90a27..35bfada261258407982d9e24cf7b3e820235c941 100644
index 947513d5b91e8c13478b25d80a1157edbcd64b64..4e46df6afe6ca57f6df9a64d3fc572450baf7d5c 100644
--- a/src/node_modules.cc
+++ b/src/node_modules.cc
@@ -279,8 +279,41 @@ const BindingData::PackageConfig* BindingData::TraverseParent(
@@ -315,8 +315,41 @@ const BindingData::PackageConfig* BindingData::TraverseParent(
Realm* realm, const std::filesystem::path& check_path) {
std::filesystem::path current_path = check_path;
auto env = realm->env();
@@ -47,22 +47,22 @@ index 60b03b1563b230f78d8a9bdd8480da4d2ae90a27..35bfada261258407982d9e24cf7b3e82
+ });
+ };
+
+ bool did_original_path_start_with_resources_path = starts_with(check_path.
+ generic_string(), resources_path);
+ bool did_original_path_start_with_resources_path = starts_with(
+ ConvertGenericPathToUTF8(check_path), resources_path);
+
do {
current_path = current_path.parent_path();
@@ -299,6 +332,12 @@ const BindingData::PackageConfig* BindingData::TraverseParent(
return nullptr;
@@ -336,6 +369,12 @@ const BindingData::PackageConfig* BindingData::TraverseParent(
}
}
+ // If current path is outside the resources path, bail.
+ if (did_original_path_start_with_resources_path &&
+ !starts_with(current_path.generic_string(), resources_path)) {
+ !starts_with(ConvertGenericPathToUTF8(current_path), resources_path)) {
+ return nullptr;
+ }
+
// Check if the path ends with `/node_modules`
if (current_path.generic_string().ends_with("/node_modules")) {
if (current_path.filename() == "node_modules") {
return nullptr;

View File

@@ -20,7 +20,7 @@ index 82225b0a53dd828750991a4e15a060b736b6ea2b..4b0d31356a2496a7fc67876a22da2453
V(performance_entry_callback, v8::Function) \
V(prepare_stack_trace_callback, v8::Function) \
diff --git a/src/node_modules.cc b/src/node_modules.cc
index 35bfada261258407982d9e24cf7b3e820235c941..ed22da844a61b14b8580cd3d6bb3a233b8559b38 100644
index 60b03b1563b230f78d8a9bdd8480da4d2ae90a27..62050791174563f88b8629d576eed8959b3c2e20 100644
--- a/src/node_modules.cc
+++ b/src/node_modules.cc
@@ -21,6 +21,7 @@ namespace modules {
@@ -90,7 +90,7 @@ index 35bfada261258407982d9e24cf7b3e820235c941..ed22da844a61b14b8580cd3d6bb3a233
void BindingData::ReadPackageJSON(const FunctionCallbackInfo<Value>& args) {
CHECK_GE(args.Length(), 1); // path, [is_esm, base, specifier]
CHECK(args[0]->IsString()); // path
@@ -597,6 +633,8 @@ void InitImportMetaPathHelpers(const FunctionCallbackInfo<Value>& args) {
@@ -558,6 +594,8 @@ void InitImportMetaPathHelpers(const FunctionCallbackInfo<Value>& args) {
void BindingData::CreatePerIsolateProperties(IsolateData* isolate_data,
Local<ObjectTemplate> target) {
Isolate* isolate = isolate_data->isolate();
@@ -99,7 +99,7 @@ index 35bfada261258407982d9e24cf7b3e820235c941..ed22da844a61b14b8580cd3d6bb3a233
SetMethod(isolate, target, "readPackageJSON", ReadPackageJSON);
SetMethod(isolate,
target,
@@ -635,6 +673,8 @@ void BindingData::CreatePerContextProperties(Local<Object> target,
@@ -596,6 +634,8 @@ void BindingData::CreatePerContextProperties(Local<Object> target,
void BindingData::RegisterExternalReferences(
ExternalReferenceRegistry* registry) {

View File

@@ -0,0 +1,308 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Fedor Indutny <238531+indutny@users.noreply.github.com>
Date: Fri, 7 Nov 2025 19:41:44 -0800
Subject: src: use CP_UTF8 for wide file names on win32
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
`src/node_modules.cc` needs to be consistent with `src/node_file.cc` in
how it translates the utf8 strings to `std::wstring` otherwise we might
end up in situation where we can read the source code of imported
package from disk, but fail to recognize that it is an ESM (or CJS) and
cause runtime errors. This type of error is possible on Windows when the
path contains unicode characters and "Language for non-Unicode programs"
is set to "Chinese (Traditional, Taiwan)".
See: #58768
PR-URL: https://github.com/nodejs/node/pull/60575
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Darshan Sen <raisinten@gmail.com>
Reviewed-By: Stefan Stojanovic <stefan.stojanovic@janeasystems.com>
Reviewed-By: Juan José Arboleda <soyjuanarbol@gmail.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
diff --git a/src/node_file.cc b/src/node_file.cc
index 75be21c9e8b413f522240a906da06d26c44d5b71..e94c2b5f2cf7cac413cd5cb782fa1cca6d764960 100644
--- a/src/node_file.cc
+++ b/src/node_file.cc
@@ -3056,42 +3056,6 @@ static void GetFormatOfExtensionlessFile(
return args.GetReturnValue().Set(EXTENSIONLESS_FORMAT_JAVASCRIPT);
}
-#ifdef _WIN32
-#define BufferValueToPath(str) \
- std::filesystem::path(ConvertToWideString(str.ToString(), CP_UTF8))
-
-std::string ConvertWideToUTF8(const std::wstring& wstr) {
- if (wstr.empty()) return std::string();
-
- int size_needed = WideCharToMultiByte(CP_UTF8,
- 0,
- &wstr[0],
- static_cast<int>(wstr.size()),
- nullptr,
- 0,
- nullptr,
- nullptr);
- std::string strTo(size_needed, 0);
- WideCharToMultiByte(CP_UTF8,
- 0,
- &wstr[0],
- static_cast<int>(wstr.size()),
- &strTo[0],
- size_needed,
- nullptr,
- nullptr);
- return strTo;
-}
-
-#define PathToString(path) ConvertWideToUTF8(path.wstring());
-
-#else // _WIN32
-
-#define BufferValueToPath(str) std::filesystem::path(str.ToStringView());
-#define PathToString(path) path.native();
-
-#endif // _WIN32
-
static void CpSyncCheckPaths(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Isolate* isolate = env->isolate();
@@ -3104,7 +3068,7 @@ static void CpSyncCheckPaths(const FunctionCallbackInfo<Value>& args) {
THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kFileSystemRead, src.ToStringView());
- auto src_path = BufferValueToPath(src);
+ auto src_path = src.ToPath();
BufferValue dest(isolate, args[1]);
CHECK_NOT_NULL(*dest);
@@ -3112,7 +3076,7 @@ static void CpSyncCheckPaths(const FunctionCallbackInfo<Value>& args) {
THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kFileSystemWrite, dest.ToStringView());
- auto dest_path = BufferValueToPath(dest);
+ auto dest_path = dest.ToPath();
bool dereference = args[2]->IsTrue();
bool recursive = args[3]->IsTrue();
@@ -3141,8 +3105,8 @@ static void CpSyncCheckPaths(const FunctionCallbackInfo<Value>& args) {
(src_status.type() == std::filesystem::file_type::directory) ||
(dereference && src_status.type() == std::filesystem::file_type::symlink);
- auto src_path_str = PathToString(src_path);
- auto dest_path_str = PathToString(dest_path);
+ auto src_path_str = ConvertPathToUTF8(src_path);
+ auto dest_path_str = ConvertPathToUTF8(dest_path);
if (!error_code) {
// Check if src and dest are identical.
@@ -3237,7 +3201,7 @@ static bool CopyUtimes(const std::filesystem::path& src,
uv_fs_t req;
auto cleanup = OnScopeLeave([&req]() { uv_fs_req_cleanup(&req); });
- auto src_path_str = PathToString(src);
+ auto src_path_str = ConvertPathToUTF8(src);
int result = uv_fs_stat(nullptr, &req, src_path_str.c_str(), nullptr);
if (is_uv_error(result)) {
env->ThrowUVException(result, "stat", nullptr, src_path_str.c_str());
@@ -3248,7 +3212,7 @@ static bool CopyUtimes(const std::filesystem::path& src,
const double source_atime = s->st_atim.tv_sec + s->st_atim.tv_nsec / 1e9;
const double source_mtime = s->st_mtim.tv_sec + s->st_mtim.tv_nsec / 1e9;
- auto dest_file_path_str = PathToString(dest);
+ auto dest_file_path_str = ConvertPathToUTF8(dest);
int utime_result = uv_fs_utime(nullptr,
&req,
dest_file_path_str.c_str(),
@@ -3383,7 +3347,7 @@ static void CpSyncCopyDir(const FunctionCallbackInfo<Value>& args) {
std::error_code error;
for (auto dir_entry : std::filesystem::directory_iterator(src)) {
auto dest_file_path = dest / dir_entry.path().filename();
- auto dest_str = PathToString(dest);
+ auto dest_str = ConvertPathToUTF8(dest);
if (dir_entry.is_symlink()) {
if (verbatim_symlinks) {
@@ -3446,7 +3410,7 @@ static void CpSyncCopyDir(const FunctionCallbackInfo<Value>& args) {
}
} else if (std::filesystem::is_regular_file(dest_file_path)) {
if (!dereference || (!force && error_on_exist)) {
- auto dest_file_path_str = PathToString(dest_file_path);
+ auto dest_file_path_str = ConvertPathToUTF8(dest_file_path);
env->ThrowStdErrException(
std::make_error_code(std::errc::file_exists),
"cp",
diff --git a/src/node_modules.cc b/src/node_modules.cc
index fa04b4a8cdd02a2cad1eaf2e5a848b951d1b1150..947513d5b91e8c13478b25d80a1157edbcd64b64 100644
--- a/src/node_modules.cc
+++ b/src/node_modules.cc
@@ -327,22 +327,24 @@ const BindingData::PackageConfig* BindingData::TraverseParent(
// Stop the search when the process doesn't have permissions
// to walk upwards
- if (is_permissions_enabled &&
- !env->permission()->is_granted(
- env,
- permission::PermissionScope::kFileSystemRead,
- current_path.generic_string())) [[unlikely]] {
- return nullptr;
+ if (is_permissions_enabled) {
+ if (!env->permission()->is_granted(
+ env,
+ permission::PermissionScope::kFileSystemRead,
+ ConvertGenericPathToUTF8(current_path))) [[unlikely]] {
+ return nullptr;
+ }
}
// Check if the path ends with `/node_modules`
- if (current_path.generic_string().ends_with("/node_modules")) {
+ if (current_path.filename() == "node_modules") {
return nullptr;
}
auto package_json_path = current_path / "package.json";
+
auto package_json =
- GetPackageJSON(realm, package_json_path.string(), nullptr);
+ GetPackageJSON(realm, ConvertPathToUTF8(package_json_path), nullptr);
if (package_json != nullptr) {
return package_json;
}
@@ -364,20 +366,12 @@ void BindingData::GetNearestParentPackageJSONType(
ToNamespacedPath(realm->env(), &path_value);
- std::string path_value_str = path_value.ToString();
+ auto path = path_value.ToPath();
+
if (slashCheck) {
- path_value_str.push_back(kPathSeparator);
+ path /= "";
}
- std::filesystem::path path;
-
-#ifdef _WIN32
- std::wstring wide_path = ConvertToWideString(path_value_str, GetACP());
- path = std::filesystem::path(wide_path);
-#else
- path = std::filesystem::path(path_value_str);
-#endif
-
auto package_json = TraverseParent(realm, path);
if (package_json == nullptr) {
diff --git a/src/util-inl.h b/src/util-inl.h
index 816156282790383e896b28eb46a3b4703bbe17f0..7274e5da8cc9eb164ec46ec2f7932691ed6ba9dc 100644
--- a/src/util-inl.h
+++ b/src/util-inl.h
@@ -678,12 +678,11 @@ inline bool IsWindowsBatchFile(const char* filename) {
return !extension.empty() && (extension == "cmd" || extension == "bat");
}
-inline std::wstring ConvertToWideString(const std::string& str,
- UINT code_page) {
+inline std::wstring ConvertUTF8ToWideString(const std::string& str) {
int size_needed = MultiByteToWideChar(
- code_page, 0, &str[0], static_cast<int>(str.size()), nullptr, 0);
+ CP_UTF8, 0, &str[0], static_cast<int>(str.size()), nullptr, 0);
std::wstring wstrTo(size_needed, 0);
- MultiByteToWideChar(code_page,
+ MultiByteToWideChar(CP_UTF8,
0,
&str[0],
static_cast<int>(str.size()),
@@ -691,6 +690,59 @@ inline std::wstring ConvertToWideString(const std::string& str,
size_needed);
return wstrTo;
}
+
+std::string ConvertWideStringToUTF8(const std::wstring& wstr) {
+ if (wstr.empty()) return std::string();
+
+ int size_needed = WideCharToMultiByte(CP_UTF8,
+ 0,
+ &wstr[0],
+ static_cast<int>(wstr.size()),
+ nullptr,
+ 0,
+ nullptr,
+ nullptr);
+ std::string strTo(size_needed, 0);
+ WideCharToMultiByte(CP_UTF8,
+ 0,
+ &wstr[0],
+ static_cast<int>(wstr.size()),
+ &strTo[0],
+ size_needed,
+ nullptr,
+ nullptr);
+ return strTo;
+}
+
+template <typename T, size_t kStackStorageSize>
+std::filesystem::path MaybeStackBuffer<T, kStackStorageSize>::ToPath() const {
+ std::wstring wide_path = ConvertUTF8ToWideString(ToString());
+ return std::filesystem::path(wide_path);
+}
+
+std::string ConvertPathToUTF8(const std::filesystem::path& path) {
+ return ConvertWideStringToUTF8(path.wstring());
+}
+
+std::string ConvertGenericPathToUTF8(const std::filesystem::path& path) {
+ return ConvertWideStringToUTF8(path.generic_wstring());
+}
+
+#else // _WIN32
+
+template <typename T, size_t kStackStorageSize>
+std::filesystem::path MaybeStackBuffer<T, kStackStorageSize>::ToPath() const {
+ return std::filesystem::path(ToStringView());
+}
+
+std::string ConvertPathToUTF8(const std::filesystem::path& path) {
+ return path.native();
+}
+
+std::string ConvertGenericPathToUTF8(const std::filesystem::path& path) {
+ return path.generic_string();
+}
+
#endif // _WIN32
} // namespace node
diff --git a/src/util.h b/src/util.h
index dab48c59e1cd947a32cf08e5ab23cd60fe32303e..91bfb8d94b1c053b59c20e25306ef0f08e977f49 100644
--- a/src/util.h
+++ b/src/util.h
@@ -507,6 +507,8 @@ class MaybeStackBuffer {
inline std::basic_string_view<T> ToStringView() const {
return {out(), length()};
}
+ // This can only be used if the buffer contains path data in UTF8
+ inline std::filesystem::path ToPath() const;
private:
size_t length_;
@@ -1022,9 +1024,15 @@ class JSONOutputStream final : public v8::OutputStream {
// Returns true if OS==Windows and filename ends in .bat or .cmd,
// case insensitive.
inline bool IsWindowsBatchFile(const char* filename);
-inline std::wstring ConvertToWideString(const std::string& str, UINT code_page);
+inline std::wstring ConvertUTF8ToWideString(const std::string& str);
+inline std::string ConvertWideStringToUTF8(const std::wstring& wstr);
+
#endif // _WIN32
+inline std::filesystem::path ConvertUTF8ToPath(const std::string& str);
+inline std::string ConvertPathToUTF8(const std::filesystem::path& path);
+inline std::string ConvertGenericPathToUTF8(const std::filesystem::path& path);
+
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

View File

@@ -1 +1,3 @@
chore_allow_customizing_microtask_policy_per_context.patch
turboshaft_avoid_introducing_too_many_variables.patch
preserve_field_repr_in_property_array_extension.patch

View File

@@ -0,0 +1,302 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Leszek Swirski <leszeks@chromium.org>
Date: Wed, 12 Nov 2025 16:46:01 +0100
Subject: Preserve field repr in property array extension
Walk the descriptor array in lockstep with the property array when
extending the latter.
Fixed: 460017370
Change-Id: If0b4fc3c5f62fc0cc373588cbddc3c0a95c7225c
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7146166
Commit-Queue: Leszek Swirski <leszeks@chromium.org>
Reviewed-by: Nico Hartmann <nicohartmann@chromium.org>
Reviewed-by: Igor Sheludko <ishell@chromium.org>
Cr-Commit-Position: refs/heads/main@{#103674}
diff --git a/src/compiler/access-builder.cc b/src/compiler/access-builder.cc
index c0bf470ada9443c63805b9e331bfdceb230929a8..8517527ecc2df89289cde05d7bc124f34133a838 100644
--- a/src/compiler/access-builder.cc
+++ b/src/compiler/access-builder.cc
@@ -4,6 +4,8 @@
#include "src/compiler/access-builder.h"
+#include "src/codegen/machine-type.h"
+#include "src/compiler/property-access-builder.h"
#include "src/compiler/type-cache.h"
#include "src/handles/handles-inl.h"
#include "src/objects/arguments.h"
@@ -1097,12 +1099,16 @@ FieldAccess AccessBuilder::ForFeedbackVectorSlot(int index) {
}
// static
-FieldAccess AccessBuilder::ForPropertyArraySlot(int index) {
+FieldAccess AccessBuilder::ForPropertyArraySlot(int index,
+ Representation representation) {
int offset = PropertyArray::OffsetOfElementAt(index);
- FieldAccess access = {kTaggedBase, offset,
- Handle<Name>(), OptionalMapRef(),
- Type::Any(), MachineType::AnyTagged(),
- kFullWriteBarrier, "PropertyArraySlot"};
+ MachineType machine_type =
+ representation.IsHeapObject() || representation.IsDouble()
+ ? MachineType::TaggedPointer()
+ : MachineType::AnyTagged();
+ FieldAccess access = {
+ kTaggedBase, offset, Handle<Name>(), OptionalMapRef(),
+ Type::Any(), machine_type, kFullWriteBarrier, "PropertyArraySlot"};
return access;
}
diff --git a/src/compiler/access-builder.h b/src/compiler/access-builder.h
index 0363f0fc34593c8cb5dfda4c3b59a59664d689e3..b946fc58690cbe5b12903a832d0178c83a87416c 100644
--- a/src/compiler/access-builder.h
+++ b/src/compiler/access-builder.h
@@ -11,6 +11,7 @@
#include "src/compiler/write-barrier-kind.h"
#include "src/objects/elements-kind.h"
#include "src/objects/js-objects.h"
+#include "src/objects/property-details.h"
namespace v8 {
namespace internal {
@@ -323,7 +324,8 @@ class V8_EXPORT_PRIVATE AccessBuilder final
static FieldAccess ForFeedbackVectorSlot(int index);
// Provides access to PropertyArray slots.
- static FieldAccess ForPropertyArraySlot(int index);
+ static FieldAccess ForPropertyArraySlot(int index,
+ Representation representation);
// Provides access to ScopeInfo flags.
static FieldAccess ForScopeInfoFlags();
diff --git a/src/compiler/js-native-context-specialization.cc b/src/compiler/js-native-context-specialization.cc
index 60b4df5fbb6b762cee6609c5f0b3452e68e0fe64..697bb0526d465df96378ea3f81ad725f49fd3dec 100644
--- a/src/compiler/js-native-context-specialization.cc
+++ b/src/compiler/js-native-context-specialization.cc
@@ -38,6 +38,7 @@
#include "src/objects/elements-kind.h"
#include "src/objects/feedback-vector.h"
#include "src/objects/heap-number.h"
+#include "src/objects/property-details.h"
#include "src/objects/string.h"
namespace v8 {
@@ -4231,25 +4232,59 @@ Node* JSNativeContextSpecialization::BuildExtendPropertiesBackingStore(
// for intermediate states of chains of property additions. That makes
// it unclear what the best approach is here.
DCHECK_EQ(map.UnusedPropertyFields(), 0);
- int length = map.NextFreePropertyIndex() - map.GetInObjectProperties();
+ int in_object_length = map.GetInObjectProperties();
+ int length = map.NextFreePropertyIndex() - in_object_length;
// Under normal circumstances, NextFreePropertyIndex() will always be larger
// than GetInObjectProperties(). However, an attacker able to corrupt heap
// memory can break this invariant, in which case we'll get confused here,
// potentially causing a sandbox violation. This CHECK defends against that.
SBXCHECK_GE(length, 0);
int new_length = length + JSObject::kFieldsAdded;
+
+ // Find the descriptor index corresponding to the first out-of-object
+ // property.
+ DescriptorArrayRef descs = map.instance_descriptors(broker());
+ InternalIndex first_out_of_object_descriptor(in_object_length);
+ InternalIndex number_of_descriptors(descs.object()->number_of_descriptors());
+ for (InternalIndex i(in_object_length); i < number_of_descriptors; ++i) {
+ PropertyDetails details = descs.GetPropertyDetails(i);
+ // Skip over non-field properties.
+ if (details.location() != PropertyLocation::kField) {
+ continue;
+ }
+ // Skip over in-object fields.
+ // TODO(leszeks): We could make this smarter, like a binary search.
+ if (details.field_index() < in_object_length) {
+ continue;
+ }
+ first_out_of_object_descriptor = i;
+ break;
+ }
+
// Collect the field values from the {properties}.
- ZoneVector<Node*> values(zone());
+ ZoneVector<std::pair<Node*, Representation>> values(zone());
values.reserve(new_length);
- for (int i = 0; i < length; ++i) {
+
+ // Walk the property descriptors alongside the property values, to make
+ // sure to get and store them with the right machine type.
+ InternalIndex descriptor = first_out_of_object_descriptor;
+ for (int i = 0; i < length; ++i, ++descriptor) {
+ PropertyDetails details = descs.GetPropertyDetails(descriptor);
+ while (details.location() != PropertyLocation::kField) {
+ ++descriptor;
+ details = descs.GetPropertyDetails(descriptor);
+ }
+ DCHECK_EQ(i, details.field_index() - in_object_length);
Node* value = effect = graph()->NewNode(
- simplified()->LoadField(AccessBuilder::ForFixedArraySlot(i)),
+ simplified()->LoadField(
+ AccessBuilder::ForPropertyArraySlot(i, details.representation())),
properties, effect, control);
- values.push_back(value);
+ values.push_back({value, details.representation()});
}
// Initialize the new fields to undefined.
for (int i = 0; i < JSObject::kFieldsAdded; ++i) {
- values.push_back(jsgraph()->UndefinedConstant());
+ values.push_back(
+ {jsgraph()->UndefinedConstant(), Representation::Tagged()});
}
// Compute new length and hash.
@@ -4287,7 +4322,8 @@ Node* JSNativeContextSpecialization::BuildExtendPropertiesBackingStore(
a.Store(AccessBuilder::ForMap(), jsgraph()->PropertyArrayMapConstant());
a.Store(AccessBuilder::ForPropertyArrayLengthAndHash(), new_length_and_hash);
for (int i = 0; i < new_length; ++i) {
- a.Store(AccessBuilder::ForFixedArraySlot(i), values[i]);
+ a.Store(AccessBuilder::ForPropertyArraySlot(i, values[i].second),
+ values[i].first);
}
return a.Finish();
}
diff --git a/src/compiler/turboshaft/turbolev-early-lowering-reducer-inl.h b/src/compiler/turboshaft/turbolev-early-lowering-reducer-inl.h
index bf0832ecc67bec1a6da711b9bff7266ba3df7d51..a6452e7cfb9e7d7d6d222bd625d13dabe4f97cb7 100644
--- a/src/compiler/turboshaft/turbolev-early-lowering-reducer-inl.h
+++ b/src/compiler/turboshaft/turbolev-early-lowering-reducer-inl.h
@@ -14,6 +14,7 @@
#include "src/compiler/turboshaft/representations.h"
#include "src/deoptimizer/deoptimize-reason.h"
#include "src/objects/contexts.h"
+#include "src/objects/descriptor-array-inl.h"
#include "src/objects/instance-type-inl.h"
namespace v8::internal::compiler::turboshaft {
@@ -323,8 +324,32 @@ class TurbolevEarlyLoweringReducer : public Next {
}
V<PropertyArray> ExtendPropertiesBackingStore(
- V<PropertyArray> old_property_array, V<JSObject> object, int old_length,
+ V<PropertyArray> old_property_array, V<JSObject> object,
+ const compiler::MapRef& old_map, int old_length,
V<FrameState> frame_state, const FeedbackSource& feedback) {
+ int in_object_length = old_map.GetInObjectProperties();
+
+ // Find the descriptor index corresponding to the first out-of-object
+ // property.
+ DescriptorArrayRef descs = old_map.instance_descriptors(broker_);
+ InternalIndex first_out_of_object_descriptor(in_object_length);
+ InternalIndex number_of_descriptors(
+ descs.object()->number_of_descriptors());
+ for (InternalIndex i(in_object_length); i < number_of_descriptors; ++i) {
+ PropertyDetails details = descs.GetPropertyDetails(i);
+ // Skip over non-field properties.
+ if (details.location() != PropertyLocation::kField) {
+ continue;
+ }
+ // Skip over in-object fields.
+ // TODO(leszeks): We could make this smarter, like a binary search.
+ if (details.field_index() < in_object_length) {
+ continue;
+ }
+ first_out_of_object_descriptor = i;
+ break;
+ }
+
// Allocate new PropertyArray.
int new_length = old_length + JSObject::kFieldsAdded;
Uninitialized<PropertyArray> new_property_array =
@@ -335,18 +360,28 @@ class TurbolevEarlyLoweringReducer : public Next {
__ HeapConstant(factory_->property_array_map()));
// Copy existing properties over.
- for (int i = 0; i < old_length; i++) {
+ InternalIndex descriptor = first_out_of_object_descriptor;
+ for (int i = 0; i < old_length; ++i, ++descriptor) {
+ PropertyDetails details = descs.GetPropertyDetails(descriptor);
+ while (details.location() != PropertyLocation::kField) {
+ ++descriptor;
+ details = descs.GetPropertyDetails(descriptor);
+ }
+ DCHECK_EQ(i, details.field_index() - in_object_length);
+ Representation r = details.representation();
+
V<Object> old_value = __ template LoadField<Object>(
- old_property_array, AccessBuilder::ForPropertyArraySlot(i));
+ old_property_array, AccessBuilder::ForPropertyArraySlot(i, r));
__ InitializeField(new_property_array,
- AccessBuilder::ForPropertyArraySlot(i), old_value);
+ AccessBuilder::ForPropertyArraySlot(i, r), old_value);
}
// Initialize new properties to undefined.
V<Undefined> undefined = __ HeapConstant(factory_->undefined_value());
for (int i = 0; i < JSObject::kFieldsAdded; ++i) {
__ InitializeField(new_property_array,
- AccessBuilder::ForPropertyArraySlot(old_length + i),
+ AccessBuilder::ForPropertyArraySlot(
+ old_length + i, Representation::Tagged()),
undefined);
}
diff --git a/src/compiler/turboshaft/turbolev-graph-builder.cc b/src/compiler/turboshaft/turbolev-graph-builder.cc
index bcb461488899bcbf3a130845f39f0005bd34131e..d80362036da4c80e192ed489e3c66e8bfed271ba 100644
--- a/src/compiler/turboshaft/turbolev-graph-builder.cc
+++ b/src/compiler/turboshaft/turbolev-graph-builder.cc
@@ -2760,10 +2760,11 @@ class GraphBuildingNodeProcessor {
maglev::ProcessResult Process(maglev::ExtendPropertiesBackingStore* node,
const maglev::ProcessingState& state) {
GET_FRAME_STATE_MAYBE_ABORT(frame_state, node->eager_deopt_info());
- SetMap(node, __ ExtendPropertiesBackingStore(
- Map(node->property_array_input()),
- Map(node->object_input()), node->old_length(), frame_state,
- node->eager_deopt_info()->feedback_to_update()));
+ SetMap(node,
+ __ ExtendPropertiesBackingStore(
+ Map(node->property_array_input()), Map(node->object_input()),
+ node->old_map(), node->old_length(), frame_state,
+ node->eager_deopt_info()->feedback_to_update()));
return maglev::ProcessResult::kContinue;
}
diff --git a/src/maglev/maglev-graph-builder.cc b/src/maglev/maglev-graph-builder.cc
index a2cac0c5ded6806292d98218dcb6da0f7430f16b..4664ca78b4413da6a3d9d6cafa705d33f5c02ee2 100644
--- a/src/maglev/maglev-graph-builder.cc
+++ b/src/maglev/maglev-graph-builder.cc
@@ -5253,7 +5253,7 @@ ReduceResult MaglevGraphBuilder::BuildExtendPropertiesBackingStore(
// potentially causing a sandbox violation. This CHECK defends against that.
SBXCHECK_GE(length, 0);
return AddNewNode<ExtendPropertiesBackingStore>({property_array, receiver},
- length);
+ map, length);
}
MaybeReduceResult MaglevGraphBuilder::TryBuildStoreField(
diff --git a/src/maglev/maglev-ir.h b/src/maglev/maglev-ir.h
index 9d5cf70429ed5513aa7cd23baf1a044acfc25feb..707d8fd56a367856a648dbe152e3d945416c70af 100644
--- a/src/maglev/maglev-ir.h
+++ b/src/maglev/maglev-ir.h
@@ -9084,8 +9084,10 @@ class ExtendPropertiesBackingStore
using Base = FixedInputValueNodeT<2, ExtendPropertiesBackingStore>;
public:
- explicit ExtendPropertiesBackingStore(uint64_t bitfield, int old_length)
- : Base(bitfield), old_length_(old_length) {}
+ explicit ExtendPropertiesBackingStore(uint64_t bitfield,
+ const compiler::MapRef& old_map,
+ int old_length)
+ : Base(bitfield), old_map_(old_map), old_length_(old_length) {}
static constexpr OpProperties kProperties =
OpProperties::CanAllocate() | OpProperties::CanRead() |
@@ -9105,9 +9107,11 @@ class ExtendPropertiesBackingStore
void GenerateCode(MaglevAssembler*, const ProcessingState&);
void PrintParams(std::ostream&) const;
+ const compiler::MapRef& old_map() const { return old_map_; }
int old_length() const { return old_length_; }
private:
+ const compiler::MapRef old_map_;
const int old_length_;
};

View File

@@ -0,0 +1,475 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Darius Mercadier <dmercadier@chromium.org>
Date: Wed, 5 Nov 2025 14:06:54 +0100
Subject: [turboshaft] Avoid introducing too many Variables
.... if we have very large merges.
Cf https://crbug.com/418027512#comment5 for explanations of why this
is necessary (and the following comment for why I don't see a good
alternative to this CL).
I've locally confirmed that this fixes the OOM from
https://crbug.com/457625181, and it reduces memory consumption on
binaries/crbug-40219016-zelda/zelda.wasm (from
https://crbug.com/418027512) by 20+%.
Bug: 418027512, 457625181
Change-Id: If55af659667723ce85ff71bcac66a43aff863e05
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7119378
Commit-Queue: Darius Mercadier <dmercadier@chromium.org>
Auto-Submit: Darius Mercadier <dmercadier@chromium.org>
Reviewed-by: Matthias Liedtke <mliedtke@chromium.org>
Cr-Commit-Position: refs/heads/main@{#103534}
diff --git a/src/compiler/turboshaft/branch-elimination-reducer.h b/src/compiler/turboshaft/branch-elimination-reducer.h
index f115c86894f0cf739d6381f7844e5589831cc209..d917d27bd3964ba07b41efa49b86435ae7720064 100644
--- a/src/compiler/turboshaft/branch-elimination-reducer.h
+++ b/src/compiler/turboshaft/branch-elimination-reducer.h
@@ -323,6 +323,10 @@ class BranchEliminationReducer : public Next {
goto no_change;
}
+ if (!__ CanCreateNVariables(destination_origin->OpCountUpperBound())) {
+ goto no_change;
+ }
+
if (const BranchOp* branch = last_op.template TryCast<BranchOp>()) {
V<Word32> condition =
__ template MapToNewGraph<true>(branch->condition());
diff --git a/src/compiler/turboshaft/copying-phase.h b/src/compiler/turboshaft/copying-phase.h
index 875861d005435b1c2a1591886c053ca360c3e2f2..b43958499d5b6d6e72b81d965d0729bb213c7ae6 100644
--- a/src/compiler/turboshaft/copying-phase.h
+++ b/src/compiler/turboshaft/copying-phase.h
@@ -714,9 +714,23 @@ class GraphVisitor : public OutputGraphAssembler<GraphVisitor<AfterNext>,
if (Asm().CanAutoInlineBlocksWithSinglePredecessor() &&
terminator.Is<GotoOp>()) {
Block* destination = terminator.Cast<GotoOp>().destination;
- if (destination->PredecessorCount() == 1) {
- block_to_inline_now_ = destination;
- return;
+ // Inlining the destination will require setting it in needs_variables_
+ // mode; we thus check that we can actually create enough variables to do
+ // this.
+ // TODO(dmercadier): in practice, the only reason we need variables for
+ // the destination is because we could be currently in a phase that cloned
+ // the current block, which could lead to {destination} being cloned as
+ // well. No all phases can do this, so we could check that we're not in
+ // such a phase, and if so, not use variables for the destination. One way
+ // to do this would be to have a DisallowCloningReducer which would
+ // static_assert that LoopUnrolling/LoopPeeling/BranchElimination aren't
+ // on the stack and would also prevent using CloneSubGraph,
+ // CloneAndInlineBlock and CloneBlockAndGoto.
+ if (Asm().CanCreateNVariables(destination->OpCountUpperBound())) {
+ if (destination->PredecessorCount() == 1) {
+ block_to_inline_now_ = destination;
+ return;
+ }
}
}
// Just going through the regular VisitOp function.
diff --git a/src/compiler/turboshaft/graph.h b/src/compiler/turboshaft/graph.h
index 936c8b0269a9b87a4ffa20c40bbd908fb8c69010..a3c1c40e4e7097f518e107d85786c7cc5466e595 100644
--- a/src/compiler/turboshaft/graph.h
+++ b/src/compiler/turboshaft/graph.h
@@ -608,6 +608,7 @@ class Graph {
operation_origins_.Reset();
operation_types_.Reset();
dominator_tree_depth_ = 0;
+ max_merge_pred_count_ = 0;
#ifdef DEBUG
block_type_refinement_.Reset();
// Do not reset of graph_created_from_turbofan_ as it is propagated along
@@ -791,6 +792,8 @@ class Graph {
bound_blocks_.push_back(block);
uint32_t depth = block->ComputeDominator();
dominator_tree_depth_ = std::max<uint32_t>(dominator_tree_depth_, depth);
+ max_merge_pred_count_ =
+ std::max<uint32_t>(max_merge_pred_count_, block->PredecessorCount());
#ifdef DEBUG
if (v8_flags.turboshaft_trace_emitted) {
@@ -1016,6 +1019,8 @@ class Graph {
uint32_t DominatorTreeDepth() const { return dominator_tree_depth_; }
+ uint32_t max_merge_pred_count() const { return max_merge_pred_count_; }
+
const GrowingOpIndexSidetable<Type>& operation_types() const {
return operation_types_;
}
@@ -1068,6 +1073,7 @@ class Graph {
std::swap(next_block_, companion.next_block_);
std::swap(block_permutation_, companion.block_permutation_);
std::swap(graph_zone_, companion.graph_zone_);
+ std::swap(max_merge_pred_count_, companion.max_merge_pred_count_);
op_to_block_.SwapData(companion.op_to_block_);
source_positions_.SwapData(companion.source_positions_);
operation_origins_.SwapData(companion.operation_origins_);
@@ -1206,6 +1212,9 @@ class Graph {
GrowingOpIndexSidetable<SourcePosition> source_positions_;
GrowingOpIndexSidetable<OpIndex> operation_origins_;
uint32_t dominator_tree_depth_ = 0;
+ // {max_merge_pred_count_} stores the maximum number of predecessors that any
+ // Merge in the graph has.
+ uint32_t max_merge_pred_count_ = 0;
GrowingOpIndexSidetable<Type> operation_types_;
#ifdef DEBUG
GrowingBlockSidetable<TypeRefinements> block_type_refinement_;
diff --git a/src/compiler/turboshaft/loop-peeling-reducer.h b/src/compiler/turboshaft/loop-peeling-reducer.h
index a9b5eaaf4c88354164b3a5833d4bd6b2760b12a0..b7df7acb61d048669a2cacfbc4e2156df69788dc 100644
--- a/src/compiler/turboshaft/loop-peeling-reducer.h
+++ b/src/compiler/turboshaft/loop-peeling-reducer.h
@@ -57,8 +57,7 @@ class LoopPeelingReducer : public Next {
const Block* dst = gto.destination;
if (dst->IsLoop() && !gto.is_backedge && CanPeelLoop(dst)) {
if (ShouldSkipOptimizationStep()) goto no_change;
- PeelFirstIteration(dst);
- return {};
+ if (PeelFirstIteration(dst)) return {};
} else if (IsEmittingPeeledIteration() && dst == current_loop_header_) {
// We skip the backedge of the loop: PeelFirstIeration will instead emit a
// forward edge to the non-peeled header.
@@ -111,13 +110,21 @@ class LoopPeelingReducer : public Next {
kEmittingUnpeeledBody
};
- void PeelFirstIteration(const Block* header) {
+ bool PeelFirstIteration(const Block* header) {
TRACE("LoopPeeling: peeling loop at " << header->index());
DCHECK_EQ(peeling_, PeelingStatus::kNotPeeling);
ScopedModification<PeelingStatus> scope(&peeling_,
PeelingStatus::kEmittingPeeledLoop);
current_loop_header_ = header;
+ constexpr int kNumberOfLoopCopies = 2; // peeled + unpeeled
+ size_t op_count_upper_bound =
+ loop_finder_.GetLoopInfo(header).op_count * kNumberOfLoopCopies;
+ if (!__ CanCreateNVariables(op_count_upper_bound)) {
+ TRACE("> Too many variables, skipping peeling");
+ return false;
+ }
+
// Emitting the peeled iteration.
auto loop_body = loop_finder_.GetLoopBody(header);
// Note that this call to CloneSubGraph will not emit the backedge because
@@ -133,7 +140,7 @@ class LoopPeelingReducer : public Next {
// While peeling, we realized that the 2nd iteration of the loop is not
// reachable.
TRACE("> Second iteration is not reachable, stopping now");
- return;
+ return true;
}
// We now emit the regular unpeeled loop.
@@ -141,6 +148,7 @@ class LoopPeelingReducer : public Next {
TRACE("> Emitting unpeeled loop body");
__ CloneSubGraph(loop_body, /* keep_loop_kinds */ true,
/* is_loop_after_peeling */ true);
+ return true;
}
bool CanPeelLoop(const Block* header) {
diff --git a/src/compiler/turboshaft/loop-unrolling-reducer.h b/src/compiler/turboshaft/loop-unrolling-reducer.h
index 181d298bfa27d21f013016b34a586078d12f8a58..92d6f7b36d4c5c0a64723f7d18427a62347bad9f 100644
--- a/src/compiler/turboshaft/loop-unrolling-reducer.h
+++ b/src/compiler/turboshaft/loop-unrolling-reducer.h
@@ -211,6 +211,11 @@ class V8_EXPORT_PRIVATE LoopUnrollingAnalyzer {
info.op_count < kMaxLoopSizeForPartialUnrolling;
}
+ size_t GetLoopOpCount(const Block* loop_header) {
+ DCHECK(loop_header->IsLoop());
+ return loop_finder_.GetLoopInfo(loop_header).op_count;
+ }
+
// The returned unroll count is the total number of copies of the loop body
// in the resulting graph, i.e., an unroll count of N means N-1 copies of the
// body which were partially unrolled, and 1 for the original/remaining body.
@@ -383,14 +388,12 @@ class LoopUnrollingReducer : public Next {
// header (note that loop headers only have 2 predecessor, including the
// backedge), and that isn't the backedge.
if (ShouldSkipOptimizationStep()) goto no_change;
- if (analyzer_.ShouldRemoveLoop(dst)) {
- RemoveLoop(dst);
+ if (analyzer_.ShouldRemoveLoop(dst) && RemoveLoop(dst)) {
return {};
- } else if (analyzer_.ShouldFullyUnrollLoop(dst)) {
- FullyUnrollLoop(dst);
+ } else if (analyzer_.ShouldFullyUnrollLoop(dst) && FullyUnrollLoop(dst)) {
return {};
- } else if (analyzer_.ShouldPartiallyUnrollLoop(dst)) {
- PartiallyUnrollLoop(dst);
+ } else if (analyzer_.ShouldPartiallyUnrollLoop(dst) &&
+ PartiallyUnrollLoop(dst)) {
return {};
}
} else if ((unrolling_ == UnrollingStatus::kUnrolling) &&
@@ -467,9 +470,9 @@ class LoopUnrollingReducer : public Next {
// and would like to not emit the loop body that follows.
kRemoveLoop,
};
- void RemoveLoop(const Block* header);
- void FullyUnrollLoop(const Block* header);
- void PartiallyUnrollLoop(const Block* header);
+ bool RemoveLoop(const Block* header);
+ bool FullyUnrollLoop(const Block* header);
+ bool PartiallyUnrollLoop(const Block* header);
void FixLoopPhis(const Block* input_graph_loop, Block* output_graph_loop,
const Block* backedge_block);
bool IsRunningBuiltinPipeline() {
@@ -508,10 +511,16 @@ class LoopUnrollingReducer : public Next {
};
template <class Next>
-void LoopUnrollingReducer<Next>::PartiallyUnrollLoop(const Block* header) {
+bool LoopUnrollingReducer<Next>::PartiallyUnrollLoop(const Block* header) {
TRACE("LoopUnrolling: partially unrolling loop at " << header->index().id());
DCHECK_EQ(unrolling_, UnrollingStatus::kNotUnrolling);
DCHECK(!skip_next_stack_check_);
+
+ if (!__ CanCreateNVariables(analyzer_.GetLoopOpCount(header))) {
+ TRACE("> Too many variables, skipping unrolling");
+ return false;
+ }
+
unrolling_ = UnrollingStatus::kUnrolling;
auto loop_body = analyzer_.GetLoopBody(header);
@@ -533,7 +542,7 @@ void LoopUnrollingReducer<Next>::PartiallyUnrollLoop(const Block* header) {
__ CloneSubGraph(loop_body, /* keep_loop_kinds */ true);
if (StopUnrollingIfUnreachable(output_graph_header)) {
TRACE("> Next iteration is unreachable, stopping unrolling");
- return;
+ return true;
}
// Emitting the subsequent folded iterations. We set `unrolling_` to
@@ -549,7 +558,7 @@ void LoopUnrollingReducer<Next>::PartiallyUnrollLoop(const Block* header) {
__ CloneSubGraph(loop_body, /* keep_loop_kinds */ false);
if (StopUnrollingIfUnreachable(output_graph_header)) {
TRACE("> Next iteration is unreachable, stopping unrolling");
- return;
+ return true;
}
}
@@ -567,6 +576,7 @@ void LoopUnrollingReducer<Next>::PartiallyUnrollLoop(const Block* header) {
unrolling_ = UnrollingStatus::kNotUnrolling;
TRACE("> Finished partially unrolling loop " << header->index().id());
+ return true;
}
template <class Next>
@@ -622,10 +632,20 @@ void LoopUnrollingReducer<Next>::FixLoopPhis(const Block* input_graph_loop,
}
template <class Next>
-void LoopUnrollingReducer<Next>::RemoveLoop(const Block* header) {
+bool LoopUnrollingReducer<Next>::RemoveLoop(const Block* header) {
TRACE("LoopUnrolling: removing loop at " << header->index().id());
DCHECK_EQ(unrolling_, UnrollingStatus::kNotUnrolling);
DCHECK(!skip_next_stack_check_);
+
+ if (!__ CanCreateNVariables(analyzer_.GetLoopOpCount(header))) {
+ TRACE("> Too many variables, skipping removal");
+ // TODO(dmercadier): in theory, RemoveLoop shouldn't need Variables, since
+ // it cannot be called while unrolling an outer loop, since we only unroll
+ // innermost loops. We should teach CloneAndInlineBlock that it doesn't
+ // always need to introduce Variables, and then remove this bailout.
+ return false;
+ }
+
// When removing a loop, we still need to emit the header (since it has to
// always be executed before the 1st iteration anyways), but by setting
// {unrolling_} to `kRemoveLoop`, the final Branch of the loop will become a
@@ -633,15 +653,21 @@ void LoopUnrollingReducer<Next>::RemoveLoop(const Block* header) {
unrolling_ = UnrollingStatus::kRemoveLoop;
__ CloneAndInlineBlock(header);
unrolling_ = UnrollingStatus::kNotUnrolling;
+ return true;
}
template <class Next>
-void LoopUnrollingReducer<Next>::FullyUnrollLoop(const Block* header) {
+bool LoopUnrollingReducer<Next>::FullyUnrollLoop(const Block* header) {
TRACE("LoopUnrolling: fully unrolling loop at " << header->index().id());
DCHECK_EQ(unrolling_, UnrollingStatus::kNotUnrolling);
DCHECK(!skip_next_stack_check_);
ScopedModification<bool> skip_stack_checks(&skip_next_stack_check_, true);
+ if (!__ CanCreateNVariables(analyzer_.GetLoopOpCount(header))) {
+ TRACE("> Too many variables, skipping unrolling");
+ return false;
+ }
+
size_t iter_count = analyzer_.GetIterationCount(header).exact_count();
TRACE("> iter_count: " << iter_count);
@@ -654,7 +680,7 @@ void LoopUnrollingReducer<Next>::FullyUnrollLoop(const Block* header) {
__ CloneSubGraph(loop_body, /* keep_loop_kinds */ false);
if (StopUnrollingIfUnreachable()) {
TRACE("> Next iteration is unreachable, stopping unrolling");
- return;
+ return true;
}
}
@@ -667,6 +693,7 @@ void LoopUnrollingReducer<Next>::FullyUnrollLoop(const Block* header) {
unrolling_ = UnrollingStatus::kNotUnrolling;
TRACE("> Finished fully unrolling loop " << header->index().id());
+ return true;
}
#undef TRACE
diff --git a/src/compiler/turboshaft/turbolev-graph-builder.cc b/src/compiler/turboshaft/turbolev-graph-builder.cc
index 9d1dafe2b0b733c88283b21eddb36c9827912eca..bcb461488899bcbf3a130845f39f0005bd34131e 100644
--- a/src/compiler/turboshaft/turbolev-graph-builder.cc
+++ b/src/compiler/turboshaft/turbolev-graph-builder.cc
@@ -118,12 +118,7 @@ class BlockOriginTrackingReducer : public Next {
}
void Bind(Block* block) {
Next::Bind(block);
- // The 1st block we bind doesn't exist in Maglev and is meant to hold
- // Constants (which in Maglev are not in any block), and thus
- // {maglev_input_block_} should still be nullptr. In all other cases,
- // {maglev_input_block_} should not be nullptr.
- DCHECK_EQ(maglev_input_block_ == nullptr,
- block == &__ output_graph().StartBlock());
+ DCHECK_NOT_NULL(maglev_input_block_);
turboshaft_block_origins_[block->index()] = maglev_input_block_;
}
@@ -519,9 +514,11 @@ class GraphBuildingNodeProcessor {
block_mapping_[block] =
block->is_loop() ? __ NewLoopHeader() : __ NewBlock();
}
- // Constants are not in a block in Maglev but are in Turboshaft. We bind a
- // block now, so that Constants can then be emitted.
- __ Bind(__ NewBlock());
+ // Constants are not in a block in Maglev but are in Turboshaft. We bind the
+ // 1st block now, so that Constants can then be emitted.
+ const maglev::BasicBlock* first_maglev_block = graph->blocks().front();
+ __ SetMaglevInputBlock(first_maglev_block);
+ __ Bind(block_mapping_[first_maglev_block]);
// Initializing undefined constant so that we don't need to recreate it too
// often.
@@ -607,9 +604,20 @@ class GraphBuildingNodeProcessor {
Block* turboshaft_block = Map(maglev_block);
if (__ current_block() != nullptr) {
- // The first block for Constants doesn't end with a Jump, so we add one
- // now.
- __ Goto(turboshaft_block);
+ // We must be in the first block of the graph, inserted by Turboshaft in
+ // PreProcessGraph so that constants can be bound in a block. No need to
+ // do anything else: we don't emit a Goto so that the actual 1st block of
+ // the Maglev graph gets inlined into this first block of the Turboshaft
+ // graph, which, in addition to saving a Goto, saves the need to clone the
+ // destination into the current block later, and also ensures that
+ // Parameters are always in the 1st block.
+ DCHECK_EQ(__ output_graph().block_count(), 1);
+ DCHECK_EQ(maglev_block->id(), 0);
+ DCHECK_EQ(__ current_block(), block_mapping_[maglev_block]);
+ // maglev_input_block should have been set by calling SetMaglevInputBlock
+ // in PreProcessGraph.
+ DCHECK_EQ(__ maglev_input_block(), maglev_block);
+ return maglev::BlockProcessResult::kContinue;
}
#ifdef DEBUG
diff --git a/src/compiler/turboshaft/variable-reducer.h b/src/compiler/turboshaft/variable-reducer.h
index b11338bdf6e928cd09a0bdbad42fd835c8210c36..03cc2fa77f0d4a194893a8be5747d6de887e5ee9 100644
--- a/src/compiler/turboshaft/variable-reducer.h
+++ b/src/compiler/turboshaft/variable-reducer.h
@@ -9,6 +9,7 @@
#include <optional>
#include "src/base/logging.h"
+#include "src/base/macros.h"
#include "src/codegen/machine-type.h"
#include "src/compiler/turboshaft/assembler.h"
#include "src/compiler/turboshaft/graph.h"
@@ -91,6 +92,15 @@ class VariableReducer : public RequiredOptimizationReducer<AfterNext> {
public:
TURBOSHAFT_REDUCER_BOILERPLATE(VariableReducer)
+ ~VariableReducer() {
+ if (too_many_variables_bailouts_count_ != 0 &&
+ V8_UNLIKELY(v8_flags.trace_turbo_bailouts)) {
+ std::cout << "Bailing out from block cloning "
+ << too_many_variables_bailouts_count_ << " time"
+ << (too_many_variables_bailouts_count_ > 1 ? "s" : "") << "\n";
+ }
+ }
+
void Bind(Block* new_block) {
Next::Bind(new_block);
@@ -190,6 +200,26 @@ class VariableReducer : public RequiredOptimizationReducer<AfterNext> {
return table_.GetPredecessorValue(var, predecessor_index);
}
+ bool CanCreateNVariables(size_t n) {
+ // Merges with many predecessors combined with many variables can quickly
+ // blow up memory since the SnapshotTable needs to create a state whose
+ // size can be up to number_of_predecessor*variable_count (note: in
+ // practice, it's often not quite variable_count but less since only
+ // variables that are live in at least one predecessor are counted). To
+ // avoid OOM or otherwise huge memory consumption, we thus stop creating
+ // variables (and bail out on optimizations that need variables) when this
+ // number becomes too large. I somewhat arbitrarily selected 100K here,
+ // which sounds high, but in terms of memory, it's just 100K*8=800KB, which
+ // is less than 1MB, which isn't going to amount for much in a function
+ // that is probably very large if it managed to reach this limit.
+ constexpr uint32_t kMaxAllowedMergeStateSize = 100'000;
+ bool can_create =
+ __ input_graph().max_merge_pred_count() * (variable_count_ + n) <
+ kMaxAllowedMergeStateSize;
+ if (!can_create) too_many_variables_bailouts_count_++;
+ return can_create;
+ }
+
void SetVariable(Variable var, OpIndex new_index) {
DCHECK(!is_temporary_);
if (V8_UNLIKELY(__ generating_unreachable_operations())) return;
@@ -206,10 +236,12 @@ class VariableReducer : public RequiredOptimizationReducer<AfterNext> {
Variable NewLoopInvariantVariable(MaybeRegisterRepresentation rep) {
DCHECK(!is_temporary_);
+ variable_count_++;
return table_.NewKey(VariableData{rep, true}, OpIndex::Invalid());
}
Variable NewVariable(MaybeRegisterRepresentation rep) {
DCHECK(!is_temporary_);
+ variable_count_++;
return table_.NewKey(VariableData{rep, false}, OpIndex::Invalid());
}
@@ -314,6 +346,10 @@ class VariableReducer : public RequiredOptimizationReducer<AfterNext> {
__ input_graph().block_count(), std::nullopt, __ phase_zone()};
bool is_temporary_ = false;
+ // Tracks the number of variables that have been created.
+ uint32_t variable_count_ = 0;
+ uint32_t too_many_variables_bailouts_count_ = 0;
+
// {predecessors_} is used during merging, but we use an instance variable for
// it, in order to save memory and not reallocate it for each merge.
ZoneVector<Snapshot> predecessors_{__ phase_zone()};
diff --git a/test/unittests/compiler/turboshaft/control-flow-unittest.cc b/test/unittests/compiler/turboshaft/control-flow-unittest.cc
index 49e1c8c2561bd010d12e5229c4d6594b9846b40b..b39b073a2ea899550fe0df6a81dcebc2d75efa49 100644
--- a/test/unittests/compiler/turboshaft/control-flow-unittest.cc
+++ b/test/unittests/compiler/turboshaft/control-flow-unittest.cc
@@ -55,7 +55,7 @@ TEST_F(ControlFlowTest, DefaultBlockInlining) {
// BranchElimination should remove such branches by cloning the block with the
// branch. In the end, the graph should contain (almost) no branches anymore.
TEST_F(ControlFlowTest, BranchElimination) {
- static constexpr int kSize = 10000;
+ static constexpr int kSize = 200;
auto test = CreateFromGraph(1, [](auto& Asm) {
V<Word32> cond =

View File

@@ -52,7 +52,8 @@ def get_repo_root(path):
def am(repo, patch_data, threeway=False, directory=None, exclude=None,
committer_name=None, committer_email=None, keep_cr=True):
args = []
# --keep-non-patch prevents stripping leading bracketed strings on the subject line
args = ['--keep-non-patch']
if threeway:
args += ['--3way']
if directory is not None:

View File

@@ -1136,6 +1136,10 @@ void App::DisableHardwareAcceleration(gin_helper::ErrorThrower thrower) {
"before app is ready");
return;
}
// If the GpuDataManager is already initialized, disable hardware
// acceleration immediately. Otherwise, set a flag to disable it in
// OnPreCreateThreads().
if (content::GpuDataManager::Initialized()) {
content::GpuDataManager::GetInstance()->DisableHardwareAcceleration();
} else {
@@ -1143,6 +1147,13 @@ void App::DisableHardwareAcceleration(gin_helper::ErrorThrower thrower) {
}
}
bool App::IsHardwareAccelerationEnabled() {
if (content::GpuDataManager::Initialized())
return content::GpuDataManager::GetInstance()
->HardwareAccelerationEnabled();
return !disable_hw_acceleration_;
}
void App::DisableDomainBlockingFor3DAPIs(gin_helper::ErrorThrower thrower) {
if (Browser::Get()->is_ready()) {
thrower.ThrowError(
@@ -1923,6 +1934,8 @@ gin::ObjectTemplateBuilder App::GetObjectTemplateBuilder(v8::Isolate* isolate) {
&App::SetAccessibilitySupportEnabled)
.SetMethod("disableHardwareAcceleration",
&App::DisableHardwareAcceleration)
.SetMethod("isHardwareAccelerationEnabled",
&App::IsHardwareAccelerationEnabled)
.SetMethod("disableDomainBlockingFor3DAPIs",
&App::DisableDomainBlockingFor3DAPIs)
.SetMethod("getFileIcon", &App::GetFileIcon)

View File

@@ -212,6 +212,7 @@ class App final : public gin::Wrappable<App>,
void ReleaseSingleInstanceLock();
bool Relaunch(gin::Arguments* args);
void DisableHardwareAcceleration(gin_helper::ErrorThrower thrower);
bool IsHardwareAccelerationEnabled();
void DisableDomainBlockingFor3DAPIs(gin_helper::ErrorThrower thrower);
bool IsAccessibilitySupportEnabled();
v8::Local<v8::Value> GetAccessibilitySupportFeatures();

View File

@@ -40,6 +40,7 @@
#endif
#if BUILDFLAG(IS_WIN)
#include <variant>
#include "shell/browser/ui/views/win_frame_view.h"
#include "shell/browser/ui/win/taskbar_host.h"
#include "ui/base/win/shell.h"
@@ -1096,7 +1097,11 @@ bool BaseWindow::IsSnapped() const {
void BaseWindow::SetAccentColor(gin_helper::Arguments* args) {
bool accent_color = false;
std::string accent_color_string;
if (args->GetNext(&accent_color_string)) {
if (!args->PeekNext().IsEmpty() && args->PeekNext()->IsNull()) {
window_->SetAccentColor(std::monostate{});
window_->UpdateWindowAccentColor(window_->IsActive());
} else if (args->GetNext(&accent_color_string) &&
!accent_color_string.empty()) {
std::optional<SkColor> maybe_color = ParseCSSColor(accent_color_string);
if (maybe_color.has_value()) {
window_->SetAccentColor(maybe_color.value());
@@ -1107,7 +1112,7 @@ void BaseWindow::SetAccentColor(gin_helper::Arguments* args) {
window_->UpdateWindowAccentColor(window_->IsActive());
} else {
args->ThrowError(
"Invalid accent color value - must be a string or boolean");
"Invalid accent color value - must be null, hex string, or boolean");
}
}

View File

@@ -586,6 +586,7 @@ Session::Session(v8::Isolate* isolate, ElectronBrowserContext* browser_context)
if (auto* service =
SpellcheckServiceFactory::GetForContext(browser_context)) {
service->SetHunspellObserver(this);
service->InitializeDictionaries(base::DoNothing());
}
#endif
}

View File

@@ -546,6 +546,9 @@ v8::Local<v8::Promise> Browser::DockShow(v8::Isolate* isolate) {
gin_helper::Promise<void> promise(isolate);
v8::Local<v8::Promise> handle = promise.GetHandle();
for (auto* const& window : WindowList::GetWindows())
[window->GetNativeWindow().GetNativeNSWindow() setCanHide:YES];
BOOL active = [[NSRunningApplication currentApplication] isActive];
ProcessSerialNumber psn = {0, kCurrentProcess};
if (active) {

View File

@@ -64,10 +64,6 @@ void InitializeFeatureList() {
std::string(",") + network::features::kLocalNetworkAccessChecks.name;
#if BUILDFLAG(IS_WIN)
disable_features +=
// Delayed spellcheck initialization is causing the
// 'custom dictionary word list API' spec to crash.
std::string(",") + spellcheck::kWinDelaySpellcheckServiceInit.name;
// Refs https://issues.chromium.org/issues/401996981
// TODO(deepak1556): Remove this once test added in
// https://github.com/electron/electron/pull/12904

View File

@@ -104,7 +104,8 @@ NativeWindow::NativeWindow(const gin_helper::Dictionary& options,
transparent_{options.ValueOrDefault(options::kTransparent, false)},
enable_larger_than_screen_{
options.ValueOrDefault(options::kEnableLargerThanScreen, false)},
is_modal_{parent != nullptr && options.ValueOrDefault("modal", false)},
is_modal_{parent != nullptr &&
options.ValueOrDefault(options::kModal, false)},
has_frame_{options.ValueOrDefault(options::kFrame, true) &&
title_bar_style_ == TitleBarStyle::kNormal},
parent_{parent} {

View File

@@ -554,7 +554,7 @@ bool NativeWindowViews::IsFocused() const {
}
void NativeWindowViews::Show() {
if (is_modal() && NativeWindow::parent() && !widget()->IsVisible())
if (is_modal() && NativeWindow::parent())
static_cast<NativeWindowViews*>(parent())->IncrementChildModals();
widget()->native_widget_private()->Show(GetRestoredState(), gfx::Rect());
@@ -1364,7 +1364,8 @@ void NativeWindowViews::SetFocusable(bool focusable) {
ex_style |= WS_EX_NOACTIVATE;
::SetWindowLong(GetAcceleratedWidget(), GWL_EXSTYLE, ex_style);
SetSkipTaskbar(!focusable);
Focus(false);
if (!focusable)
Focus(false);
#endif
}

View File

@@ -468,6 +468,12 @@ NSArray* ConvertSharingItemToNS(const SharingItem& item) {
if (!represented)
return;
if (![represented
isKindOfClass:[WeakPtrToElectronMenuModelAsNSObject class]]) {
NSLog(@"representedObject is not a WeakPtrToElectronMenuModelAsNSObject");
return;
}
electron::ElectronMenuModel* model =
[WeakPtrToElectronMenuModelAsNSObject getFrom:represented];
if (!model)

View File

@@ -100,13 +100,19 @@ void ElectronDesktopWindowTreeHostLinux::OnWindowStateChanged(
void ElectronDesktopWindowTreeHostLinux::OnWindowTiledStateChanged(
ui::WindowTiledEdges new_tiled_edges) {
if (auto* const view = native_window_view_->GetClientFrameViewLinux()) {
bool maximized = new_tiled_edges.top && new_tiled_edges.left &&
new_tiled_edges.bottom && new_tiled_edges.right;
// GNOME on Ubuntu reports all edges as tiled
// even if the window is only half-tiled so do not trust individual edge
// values.
bool maximized = native_window_view_->IsMaximized();
bool tiled = new_tiled_edges.top || new_tiled_edges.left ||
new_tiled_edges.bottom || new_tiled_edges.right;
view->set_tiled(tiled && !maximized);
}
UpdateFrameHints();
ScheduleRelayout();
if (GetWidget()->non_client_view()) {
GetWidget()->non_client_view()->SchedulePaint();
}
}
void ElectronDesktopWindowTreeHostLinux::UpdateWindowState(

View File

@@ -520,6 +520,12 @@ void InspectableWebContents::UpdateDevToolsZoomLevel(double level) {
}
void InspectableWebContents::ActivateWindow() {
if (embedder_message_dispatcher_) {
if (managed_devtools_web_contents_ && view_) {
view_->ActivateDevTools();
}
}
// Set the zoom level.
SetZoomLevelForWebContents(GetDevToolsWebContents(), GetDevToolsZoomLevel());
}

View File

@@ -132,6 +132,23 @@ void InspectableWebContentsView::ShowDevTools(bool activate) {
}
}
void InspectableWebContentsView::ActivateDevTools() {
if (!devtools_visible_) {
return;
}
if (devtools_window_) {
if (!devtools_window_->IsActive()) {
devtools_window_->Activate();
}
return;
}
if (devtools_web_view_) {
if (!devtools_web_view_->HasFocus()) {
devtools_web_view_->RequestFocus();
}
}
}
void InspectableWebContentsView::CloseDevTools() {
if (!devtools_visible_)
return;

View File

@@ -49,6 +49,7 @@ class InspectableWebContentsView : public views::View {
void SetCornerRadii(const gfx::RoundedCornersF& corner_radii);
void ShowDevTools(bool activate);
void ActivateDevTools();
void CloseDevTools();
bool IsDevToolsViewShowing();
bool IsDevToolsViewFocused();

View File

@@ -40,7 +40,6 @@ namespace {
// These values should be the same as Chromium uses.
constexpr int kResizeBorder = 10;
constexpr int kResizeInsideBoundsSize = 5;
ui::NavButtonProvider::ButtonState ButtonStateToNavButtonProviderState(
views::Button::ButtonState state) {
@@ -151,8 +150,19 @@ gfx::Insets ClientFrameViewLinux::RestoredMirroredFrameBorderInsets() const {
gfx::Insets ClientFrameViewLinux::RestoredFrameBorderInsets() const {
gfx::Insets insets = GetFrameProvider()->GetFrameThicknessDip();
insets.SetToMax(GetInputInsets());
return insets;
const gfx::Insets input = GetInputInsets();
auto expand_if_visible = [](int side_thickness, int min_band) {
return side_thickness > 0 ? std::max(side_thickness, min_band) : 0;
};
gfx::Insets merged;
merged.set_top(expand_if_visible(insets.top(), input.top()));
merged.set_left(expand_if_visible(insets.left(), input.left()));
merged.set_bottom(expand_if_visible(insets.bottom(), input.bottom()));
merged.set_right(expand_if_visible(insets.right(), input.right()));
return merged;
}
gfx::Insets ClientFrameViewLinux::GetInputInsets() const {
@@ -197,9 +207,7 @@ void ClientFrameViewLinux::OnWindowButtonOrderingChange() {
}
int ClientFrameViewLinux::ResizingBorderHitTest(const gfx::Point& point) {
return ResizingBorderHitTestImpl(point,
RestoredMirroredFrameBorderInsets() +
gfx::Insets(kResizeInsideBoundsSize));
return ResizingBorderHitTestImpl(point, RestoredMirroredFrameBorderInsets());
}
gfx::Rect ClientFrameViewLinux::GetBoundsForClientView() const {

View File

@@ -120,9 +120,18 @@ gin_helper::Handle<NativeImage> NativeImage::CreateFromNamedImage(
name.erase(pos, to_remove.length());
}
NSImage* image = [NSImage imageNamed:base::SysUTF8ToNSString(name)];
NSImage* image = nil;
NSString* ns_name = base::SysUTF8ToNSString(name);
if (!image.valid) {
// Treat non-Cocoa-prefixed names as SF Symbols first.
if (!base::StartsWith(name, "NS") && !base::StartsWith(name, "NX")) {
image = [NSImage imageWithSystemSymbolName:ns_name
accessibilityDescription:nil];
} else {
image = [NSImage imageNamed:ns_name];
}
if (!image || !image.valid) {
return CreateEmpty(args->isolate());
}

View File

@@ -222,6 +222,8 @@ inline constexpr std::string_view kSpellcheck = "spellcheck";
inline constexpr std::string_view kEnableDeprecatedPaste =
"enableDeprecatedPaste";
inline constexpr std::string_view kModal = "modal";
} // namespace options
// Following are actually command line switches, should be moved to other files.

View File

@@ -302,8 +302,8 @@ SkPath DrawSmoothRoundRect(float x,
bottom_right_smoothness, SkPoint::Make(x + width, y + height), 2);
// Bottom left corner
DrawCorner(path, bottom_left_radius, left_bottom_smoothness,
bottom_left_smoothness, SkPoint::Make(x, y + height), 3);
DrawCorner(path, bottom_left_radius, bottom_left_smoothness,
left_bottom_smoothness, SkPoint::Make(x, y + height), 3);
path.close();
return path;

View File

@@ -149,6 +149,12 @@ describe('app module', () => {
});
});
describe('app.isHardwareAccelerationEnabled()', () => {
it('should be a boolean', () => {
expect(app.isHardwareAccelerationEnabled()).to.be.a('boolean');
});
});
describe('app.isPackaged', () => {
it('should be false during tests', () => {
expect(app.isPackaged).to.equal(false);

View File

@@ -2562,7 +2562,23 @@ describe('BrowserWindow module', () => {
expect(() => {
// @ts-ignore this is wrong on purpose.
w.setAccentColor([1, 2, 3]);
}).to.throw('Invalid accent color value - must be a string or boolean');
}).to.throw('Invalid accent color value - must be null, hex string, or boolean');
});
it('throws if called with an invalid parameter', () => {
const w = new BrowserWindow({ show: false });
expect(() => {
// @ts-ignore this is wrong on purpose.
w.setAccentColor(new Date());
}).to.throw('Invalid accent color value - must be null, hex string, or boolean');
});
it('can be reset with null', () => {
const w = new BrowserWindow({ show: false });
w.setAccentColor('#FF0000');
expect(w.getAccentColor()).to.equal('#FF0000');
w.setAccentColor(null);
expect(w.getAccentColor()).to.not.equal('#FF0000');
});
it('returns the accent color after setting it to a string', () => {

View File

@@ -348,6 +348,11 @@ describe('nativeImage module', () => {
expect(image.isEmpty()).to.be.false();
});
ifit(process.platform === 'darwin')('returns a valid named symbol on darwin', function () {
const image = nativeImage.createFromNamedImage('atom');
expect(image.isEmpty()).to.be.false();
});
ifit(process.platform === 'darwin')('returns allows an HSL shift for a valid image on darwin', function () {
const image = nativeImage.createFromNamedImage('NSActionTemplate', [0.5, 0.2, 0.8]);
expect(image.isEmpty()).to.be.false();

View File

@@ -15,6 +15,7 @@ import * as webStream from 'node:stream/web';
import { setTimeout } from 'node:timers/promises';
import * as url from 'node:url';
import { collectStreamBody, getResponse } from './lib/net-helpers';
import { listen, defer, ifit } from './lib/spec-helpers';
import { WebmGenerator } from './lib/video-helpers';
import { closeAllWindows, closeWindow } from './lib/window-helpers';
@@ -1578,6 +1579,22 @@ describe('protocol module', () => {
expect(await net.fetch(url, { bypassCustomProtocolHandlers: true }).then(r => r.text())).to.equal('default');
});
it('can bypass intercepted protocol handlers with net.request', async () => {
protocol.handle('http', () => new Response('custom'));
defer(() => { protocol.unhandle('http'); });
const server = http.createServer((req, res) => {
res.end('default');
});
defer(() => server.close());
const { url } = await listen(server);
// Make a request using net.request with bypassCustomProtocolHandlers: true
const request = net.request({ method: 'GET', url, bypassCustomProtocolHandlers: true });
const response = await getResponse(request);
const body = await collectStreamBody(response);
expect(response.statusCode).to.equal(200);
expect(body).to.equal('default');
});
it('bypassing custom protocol handlers also bypasses new protocols', async () => {
protocol.handle('app', () => new Response('custom'));
defer(() => { protocol.unhandle('app'); });

View File

@@ -92,4 +92,46 @@ describe('View', () => {
expect(v.getVisible()).to.be.false();
});
});
describe('view.getBounds|setBounds', () => {
it('defaults to 0,0,0,0', () => {
const v = new View();
expect(v.getBounds()).to.deep.equal({ x: 0, y: 0, width: 0, height: 0 });
});
it('can be set and retrieved', () => {
const v = new View();
v.setBounds({ x: 10, y: 20, width: 300, height: 400 });
expect(v.getBounds()).to.deep.equal({ x: 10, y: 20, width: 300, height: 400 });
});
it('emits bounds-changed when bounds mutate', () => {
const v = new View();
let called = 0;
v.once('bounds-changed', () => { called++; });
v.setBounds({ x: 5, y: 6, width: 7, height: 8 });
expect(called).to.equal(1);
});
it('allows zero-size bounds', () => {
const v = new View();
v.setBounds({ x: 1, y: 2, width: 0, height: 0 });
expect(v.getBounds()).to.deep.equal({ x: 1, y: 2, width: 0, height: 0 });
});
it('allows negative coordinates', () => {
const v = new View();
v.setBounds({ x: -10, y: -20, width: 100, height: 50 });
expect(v.getBounds()).to.deep.equal({ x: -10, y: -20, width: 100, height: 50 });
});
it('child bounds remain relative after parent moves', () => {
const parent = new View();
const child = new View();
parent.addChildView(child);
child.setBounds({ x: 10, y: 15, width: 25, height: 30 });
parent.setBounds({ x: 50, y: 60, width: 500, height: 600 });
expect(child.getBounds()).to.deep.equal({ x: 10, y: 15, width: 25, height: 30 });
});
});
});

View File

@@ -1,6 +1,6 @@
import { BrowserWindow, ipcMain, webContents, session, app, BrowserView, WebContents, BaseWindow, WebContentsView } from 'electron/main';
import { expect } from 'chai';
import { assert, expect } from 'chai';
import * as cp from 'node:child_process';
import { once } from 'node:events';
@@ -1013,6 +1013,41 @@ describe('webContents module', () => {
await devToolsClosed;
expect(() => { webContents.getFocusedWebContents(); }).to.not.throw();
});
it('Inspect activates detached devtools window', async () => {
const window = new BrowserWindow({ show: true });
await window.loadURL('about:blank');
const webContentsBeforeOpenedDevtools = webContents.getAllWebContents();
const windowWasBlurred = once(window, 'blur');
window.webContents.openDevTools({ mode: 'detach' });
await windowWasBlurred;
let devToolsWebContents = null;
for (const newWebContents of webContents.getAllWebContents()) {
const oldWebContents = webContentsBeforeOpenedDevtools.find(
oldWebContents => {
return newWebContents.id === oldWebContents.id;
});
if (oldWebContents !== null) {
devToolsWebContents = newWebContents;
break;
}
}
assert(devToolsWebContents !== null);
const windowFocused = once(window, 'focus');
window.focus();
await windowFocused;
expect(devToolsWebContents.isFocused()).to.be.false();
const devToolsWebContentsFocused = once(devToolsWebContents, 'focus');
window.webContents.inspectElement(100, 100);
await devToolsWebContentsFocused;
expect(devToolsWebContents.isFocused()).to.be.true();
expect(window.isFocused()).to.be.false();
});
});
describe('setDevToolsWebContents() API', () => {

View File

@@ -1,7 +1,8 @@
import * as pdfjs from 'pdfjs-dist';
import { app } from 'electron';
async function getPDFDoc () {
try {
const pdfjs = await import('pdfjs-dist');
const doc = await pdfjs.getDocument(process.argv[2]).promise;
const page = await doc.getPage(1);
const { items } = await page.getTextContent();
@@ -20,4 +21,4 @@ async function getPDFDoc () {
}
}
getPDFDoc();
app.whenReady().then(() => getPDFDoc());