Skip to content

fix: only set Node's WASM streaming callback when fetch is enabled#51953

Merged
MarshallOfSound merged 2 commits into
mainfrom
fix/wasm-streaming-renderer-crash
Jun 10, 2026
Merged

fix: only set Node's WASM streaming callback when fetch is enabled#51953
MarshallOfSound merged 2 commits into
mainfrom
fix/wasm-streaming-renderer-crash

Conversation

@MarshallOfSound

Copy link
Copy Markdown
Member

Fixes #51950

Description of Change

Node.js v24.16.0 removed the experimental_fetch gate around isolate->SetWasmStreamingCallback() (nodejs/node#62759). When fix_allow_disabling_fetch_in_renderer_and_worker_processes.patch was rebased for the Node bump, the flag was restored but that gate was not.

As a result, Node.js unconditionally overrode Blink's WebAssembly streaming callback in renderer and worker processes — where we pass --no-experimental-fetch and never register Node's JS-side streaming implementation. Any WebAssembly.compileStreaming() / instantiateStreaming() call in a renderer with nodeIntegration: true then failed the CHECK(!impl.IsEmpty()) assertion in node_wasm_web_api.cc and crashed the renderer.

This restores the gate (matching the v24.15.0 behavior) so Blink's streaming callback stays installed in processes where Node's fetch is disabled, and adds a regression test. Browser, utility, and ELECTRON_RUN_AS_NODE processes are unaffected — fetch is enabled there, so Node's streaming implementation is still installed.

Checklist

  • PR description included and stakeholders cc'd
  • npm test passes
  • tests are added/changed

Release Notes

Notes: Fixed a renderer crash when calling WebAssembly.compileStreaming() or WebAssembly.instantiateStreaming() with nodeIntegration enabled.

Node.js v24.16.0 removed the experimental_fetch gate around
isolate->SetWasmStreamingCallback() (nodejs/node#62759). When
fix_allow_disabling_fetch_in_renderer_and_worker_processes.patch was
rebased for the version bump, the flag was restored but the gate was
not.

As a result, Node.js unconditionally overrode Blink's WebAssembly
streaming callback in renderer and worker processes, where Electron
passes --no-experimental-fetch and skips the bootstrap file that
registers Node's JS-side streaming implementation. Any call to
WebAssembly.compileStreaming() or instantiateStreaming() in a renderer
with nodeIntegration then failed the CHECK(\!impl.IsEmpty()) assertion
in node_wasm_web_api.cc and crashed the renderer.

Restore the gate so Blink's streaming callback stays in place in
processes where Node's fetch is disabled.

Fixes #51950
@MarshallOfSound MarshallOfSound requested a review from a team as a code owner June 10, 2026 15:39
@MarshallOfSound MarshallOfSound added semver/patch backwards-compatible bug fixes target/41-x-y PR should also be added to the "41-x-y" branch. target/42-x-y PR should also be added to the "42-x-y" branch. target/43-x-y PR should also be added to the "43-x-y" branch. labels Jun 10, 2026
@electron-cation electron-cation Bot added the new-pr 🌱 PR opened recently label Jun 10, 2026
@deepak1556

Copy link
Copy Markdown
Member

Thoughts on #51923, that would also address the broken code path. I am fine with either just want to get consensus on which change to land to address the issue.

@deepak1556

Copy link
Copy Markdown
Member

This is addressing the callback registered on the isolate, which #51923 doesn't cover yet. Approving as an immediate regression fix.

@dsanders11 dsanders11 added target/41-x-y PR should also be added to the "41-x-y" branch. and removed target/41-x-y PR should also be added to the "41-x-y" branch. labels Jun 10, 2026
@MarshallOfSound MarshallOfSound merged commit 82a5b7b into main Jun 10, 2026
141 of 143 checks passed
@MarshallOfSound MarshallOfSound deleted the fix/wasm-streaming-renderer-crash branch June 10, 2026 19:25
@release-clerk

release-clerk Bot commented Jun 10, 2026

Copy link
Copy Markdown

Release Notes Persisted

Fixed a renderer crash when calling WebAssembly.compileStreaming() or WebAssembly.instantiateStreaming() with nodeIntegration enabled.

@trop

trop Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

I was unable to backport this PR to "41-x-y" cleanly;
you will need to perform this backport manually.

@trop trop Bot added needs-manual-bp/41-x-y and removed target/41-x-y PR should also be added to the "41-x-y" branch. labels Jun 10, 2026
@trop

trop Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

I have automatically backported this PR to "43-x-y", please check out #51955

@trop trop Bot added in-flight/43-x-y and removed target/43-x-y PR should also be added to the "43-x-y" branch. labels Jun 10, 2026
@trop

trop Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

I have automatically backported this PR to "42-x-y", please check out #51956

@trop trop Bot added in-flight/42-x-y and removed target/42-x-y PR should also be added to the "42-x-y" branch. in-flight/43-x-y labels Jun 10, 2026
@trop trop Bot added merged/43-x-y PR was merged to the "43-x-y" branch. merged/42-x-y PR was merged to the "42-x-y" branch. and removed in-flight/42-x-y labels Jun 10, 2026
KanishkRanjan pushed a commit to KanishkRanjan/electron that referenced this pull request Jun 11, 2026
…lectron#51953)

* fix: only set Node's WASM streaming callback when fetch is enabled

Node.js v24.16.0 removed the experimental_fetch gate around
isolate->SetWasmStreamingCallback() (nodejs/node#62759). When
fix_allow_disabling_fetch_in_renderer_and_worker_processes.patch was
rebased for the version bump, the flag was restored but the gate was
not.

As a result, Node.js unconditionally overrode Blink's WebAssembly
streaming callback in renderer and worker processes, where Electron
passes --no-experimental-fetch and skips the bootstrap file that
registers Node's JS-side streaming implementation. Any call to
WebAssembly.compileStreaming() or instantiateStreaming() in a renderer
with nodeIntegration then failed the CHECK(\!impl.IsEmpty()) assertion
in node_wasm_web_api.cc and crashed the renderer.

Restore the gate so Blink's streaming callback stays in place in
processes where Node's fetch is disabled.

Fixes electron#51950

* test: cover WebAssembly streaming compilation in renderers with nodeIntegration
felixrieseberg added a commit to felixrieseberg/electron that referenced this pull request Jun 14, 2026
- Resolve modify/delete on the fetch patch (delete wins; the
  experimental_fetch gate it added to environment.cc is no longer
  needed since WasmResponseExtensions::Initialize handles the
  callback restoration in Electron's renderer code).
- Revert the environment.cc index hash in
  api_delete_deprecated_fields_on_v8_isolate.patch now that the
  preceding patch is gone (verified with `e patches node`).
- Preserve Blink's EventSource around LoadEnvironment in renderer
  and worker: Node's setupEventsource() in pre_execution.js deletes
  globalThis.EventSource whenever --experimental-eventsource is not
  set, and unlike setupNavigator/setupWebStorage it does not check
  noBrowserGlobals first. The previous blink* stash covered this as
  a side effect; restore just EventSource explicitly in C++.
- Extend the [native code] spec assertion to cover EventSource.
jkleinsc pushed a commit that referenced this pull request Jun 16, 2026
* chore: remove EnvironmentOptions::experimental_fetch dependency

Node.js removed the experimental_fetch option in nodejs/node#62759
since fetch is now unconditionally enabled. We had been patching the
field back in so that node_main.cc could read it and node_bindings.cc
could pass --no-experimental-fetch to renderer/worker processes.

Neither is necessary:

- In ELECTRON_RUN_AS_NODE mode, fetch is always available, so WASM
  streaming (which depends on Response) should always be set up.
- In renderer/worker processes, kNoBrowserGlobals already causes
  Node's PrincipalRealm::BootstrapRealm to skip
  internal/bootstrap/web/exposed-window-or-worker.js, which is where
  fetch/Request/Response/Headers/FormData are defined. Node's fetch
  globals were never being installed; Blink's win by default.

Drop the patch and the two call sites that depended on it.

Closes #51912

* fix: restore Blink's WASM streaming callback in renderer/worker

SetIsolateUpForNode now unconditionally registers Node's WASM streaming
callback (nodejs/node#62759 dropped the experimental_fetch gate). With
kNoBrowserGlobals the JS half of that callback is never installed, so
WebAssembly.compileStreaming/instantiateStreaming would crash on
CHECK(\!impl.IsEmpty()) in node_wasm_web_api.cc.

Re-register Blink's handler via WasmResponseExtensions::Initialize after
CreateEnvironment in renderer and worker so Blink's implementation wins.

Also drop the now-redundant blink* global stash/restore: with
kNoBrowserGlobals Node neither installs nor deletes fetch/Response/etc,
so Blink's bindings are never disturbed and the round-trip through
blinkfetch was a no-op.

Add a spec asserting fetch/Headers/Request/Response/FormData stringify
to [native code] in a nodeIntegration renderer.

* fixup: rebase on #51953 and preserve Blink's EventSource

- Resolve modify/delete on the fetch patch (delete wins; the
  experimental_fetch gate it added to environment.cc is no longer
  needed since WasmResponseExtensions::Initialize handles the
  callback restoration in Electron's renderer code).
- Revert the environment.cc index hash in
  api_delete_deprecated_fields_on_v8_isolate.patch now that the
  preceding patch is gone (verified with `e patches node`).
- Preserve Blink's EventSource around LoadEnvironment in renderer
  and worker: Node's setupEventsource() in pre_execution.js deletes
  globalThis.EventSource whenever --experimental-eventsource is not
  set, and unlike setupNavigator/setupWebStorage it does not check
  noBrowserGlobals first. The previous blink* stash covered this as
  a side effect; restore just EventSource explicitly in C++.
- Extend the [native code] spec assertion to cover EventSource.

* fixup: pass --experimental-eventsource instead of C++ save/restore

The C++ save/restore around LoadEnvironment was too late: preload
scripts run inside LoadEnvironment, after Node's setupEventsource()
has deleted globalThis.EventSource but before the C++ restore. This
broke 'preload has synchronous access to all eventual window APIs'.

Pass --experimental-eventsource for renderer/worker so Node's
setupEventsource() becomes a no-op and Blink's binding is never
deleted. Under kNoBrowserGlobals Node never installs its own undici
EventSource, so the flag's only effect is suppressing the delete.
jkleinsc pushed a commit that referenced this pull request Jun 18, 2026
* chore: remove EnvironmentOptions::experimental_fetch dependency

Node.js removed the experimental_fetch option in nodejs/node#62759
since fetch is now unconditionally enabled. We had been patching the
field back in so that node_main.cc could read it and node_bindings.cc
could pass --no-experimental-fetch to renderer/worker processes.

Neither is necessary:

- In ELECTRON_RUN_AS_NODE mode, fetch is always available, so WASM
  streaming (which depends on Response) should always be set up.
- In renderer/worker processes, kNoBrowserGlobals already causes
  Node's PrincipalRealm::BootstrapRealm to skip
  internal/bootstrap/web/exposed-window-or-worker.js, which is where
  fetch/Request/Response/Headers/FormData are defined. Node's fetch
  globals were never being installed; Blink's win by default.

Drop the patch and the two call sites that depended on it.

Closes #51912

Co-authored-by: Felix Rieseberg <f@anthropic.com>

* fix: restore Blink's WASM streaming callback in renderer/worker

SetIsolateUpForNode now unconditionally registers Node's WASM streaming
callback (nodejs/node#62759 dropped the experimental_fetch gate). With
kNoBrowserGlobals the JS half of that callback is never installed, so
WebAssembly.compileStreaming/instantiateStreaming would crash on
CHECK(\!impl.IsEmpty()) in node_wasm_web_api.cc.

Re-register Blink's handler via WasmResponseExtensions::Initialize after
CreateEnvironment in renderer and worker so Blink's implementation wins.

Also drop the now-redundant blink* global stash/restore: with
kNoBrowserGlobals Node neither installs nor deletes fetch/Response/etc,
so Blink's bindings are never disturbed and the round-trip through
blinkfetch was a no-op.

Add a spec asserting fetch/Headers/Request/Response/FormData stringify
to [native code] in a nodeIntegration renderer.

Co-authored-by: Felix Rieseberg <f@anthropic.com>

* fixup: rebase on #51953 and preserve Blink's EventSource

- Resolve modify/delete on the fetch patch (delete wins; the
  experimental_fetch gate it added to environment.cc is no longer
  needed since WasmResponseExtensions::Initialize handles the
  callback restoration in Electron's renderer code).
- Revert the environment.cc index hash in
  api_delete_deprecated_fields_on_v8_isolate.patch now that the
  preceding patch is gone (verified with `e patches node`).
- Preserve Blink's EventSource around LoadEnvironment in renderer
  and worker: Node's setupEventsource() in pre_execution.js deletes
  globalThis.EventSource whenever --experimental-eventsource is not
  set, and unlike setupNavigator/setupWebStorage it does not check
  noBrowserGlobals first. The previous blink* stash covered this as
  a side effect; restore just EventSource explicitly in C++.
- Extend the [native code] spec assertion to cover EventSource.

Co-authored-by: Felix Rieseberg <f@anthropic.com>

* fixup: pass --experimental-eventsource instead of C++ save/restore

The C++ save/restore around LoadEnvironment was too late: preload
scripts run inside LoadEnvironment, after Node's setupEventsource()
has deleted globalThis.EventSource but before the C++ restore. This
broke 'preload has synchronous access to all eventual window APIs'.

Pass --experimental-eventsource for renderer/worker so Node's
setupEventsource() becomes a no-op and Blink's binding is never
deleted. Under kNoBrowserGlobals Node never installs its own undici
EventSource, so the flag's only effect is suppressing the delete.

Co-authored-by: Felix Rieseberg <f@anthropic.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Felix Rieseberg <f@anthropic.com>
mitchchn pushed a commit to mitchchn/electron that referenced this pull request Jun 19, 2026
…tron#51923)

* chore: remove EnvironmentOptions::experimental_fetch dependency

Node.js removed the experimental_fetch option in nodejs/node#62759
since fetch is now unconditionally enabled. We had been patching the
field back in so that node_main.cc could read it and node_bindings.cc
could pass --no-experimental-fetch to renderer/worker processes.

Neither is necessary:

- In ELECTRON_RUN_AS_NODE mode, fetch is always available, so WASM
  streaming (which depends on Response) should always be set up.
- In renderer/worker processes, kNoBrowserGlobals already causes
  Node's PrincipalRealm::BootstrapRealm to skip
  internal/bootstrap/web/exposed-window-or-worker.js, which is where
  fetch/Request/Response/Headers/FormData are defined. Node's fetch
  globals were never being installed; Blink's win by default.

Drop the patch and the two call sites that depended on it.

Closes electron#51912

* fix: restore Blink's WASM streaming callback in renderer/worker

SetIsolateUpForNode now unconditionally registers Node's WASM streaming
callback (nodejs/node#62759 dropped the experimental_fetch gate). With
kNoBrowserGlobals the JS half of that callback is never installed, so
WebAssembly.compileStreaming/instantiateStreaming would crash on
CHECK(\!impl.IsEmpty()) in node_wasm_web_api.cc.

Re-register Blink's handler via WasmResponseExtensions::Initialize after
CreateEnvironment in renderer and worker so Blink's implementation wins.

Also drop the now-redundant blink* global stash/restore: with
kNoBrowserGlobals Node neither installs nor deletes fetch/Response/etc,
so Blink's bindings are never disturbed and the round-trip through
blinkfetch was a no-op.

Add a spec asserting fetch/Headers/Request/Response/FormData stringify
to [native code] in a nodeIntegration renderer.

* fixup: rebase on electron#51953 and preserve Blink's EventSource

- Resolve modify/delete on the fetch patch (delete wins; the
  experimental_fetch gate it added to environment.cc is no longer
  needed since WasmResponseExtensions::Initialize handles the
  callback restoration in Electron's renderer code).
- Revert the environment.cc index hash in
  api_delete_deprecated_fields_on_v8_isolate.patch now that the
  preceding patch is gone (verified with `e patches node`).
- Preserve Blink's EventSource around LoadEnvironment in renderer
  and worker: Node's setupEventsource() in pre_execution.js deletes
  globalThis.EventSource whenever --experimental-eventsource is not
  set, and unlike setupNavigator/setupWebStorage it does not check
  noBrowserGlobals first. The previous blink* stash covered this as
  a side effect; restore just EventSource explicitly in C++.
- Extend the [native code] spec assertion to cover EventSource.

* fixup: pass --experimental-eventsource instead of C++ save/restore

The C++ save/restore around LoadEnvironment was too late: preload
scripts run inside LoadEnvironment, after Node's setupEventsource()
has deleted globalThis.EventSource but before the C++ restore. This
broke 'preload has synchronous access to all eventual window APIs'.

Pass --experimental-eventsource for renderer/worker so Node's
setupEventsource() becomes a no-op and Blink's binding is never
deleted. Under kNoBrowserGlobals Node never installs its own undici
EventSource, so the flag's only effect is suppressing the delete.
@trop

trop Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

@mlaurencin has manually backported this PR to "41-x-y", please check out #52240

mlaurencin pushed a commit that referenced this pull request Jul 2, 2026
…51953)

* fix: only set Node's WASM streaming callback when fetch is enabled

Node.js v24.16.0 removed the experimental_fetch gate around
isolate->SetWasmStreamingCallback() (nodejs/node#62759). When
fix_allow_disabling_fetch_in_renderer_and_worker_processes.patch was
rebased for the version bump, the flag was restored but the gate was
not.

As a result, Node.js unconditionally overrode Blink's WebAssembly
streaming callback in renderer and worker processes, where Electron
passes --no-experimental-fetch and skips the bootstrap file that
registers Node's JS-side streaming implementation. Any call to
WebAssembly.compileStreaming() or instantiateStreaming() in a renderer
with nodeIntegration then failed the CHECK(\!impl.IsEmpty()) assertion
in node_wasm_web_api.cc and crashed the renderer.

Restore the gate so Blink's streaming callback stays in place in
processes where Node's fetch is disabled.

Fixes #51950

* test: cover WebAssembly streaming compilation in renderers with nodeIntegration
jkleinsc pushed a commit that referenced this pull request Jul 13, 2026
#52240)

* fix: only set Node's WASM streaming callback when fetch is enabled (#51953)

* fix: only set Node's WASM streaming callback when fetch is enabled

Node.js v24.16.0 removed the experimental_fetch gate around
isolate->SetWasmStreamingCallback() (nodejs/node#62759). When
fix_allow_disabling_fetch_in_renderer_and_worker_processes.patch was
rebased for the version bump, the flag was restored but the gate was
not.

As a result, Node.js unconditionally overrode Blink's WebAssembly
streaming callback in renderer and worker processes, where Electron
passes --no-experimental-fetch and skips the bootstrap file that
registers Node's JS-side streaming implementation. Any call to
WebAssembly.compileStreaming() or instantiateStreaming() in a renderer
with nodeIntegration then failed the CHECK(\!impl.IsEmpty()) assertion
in node_wasm_web_api.cc and crashed the renderer.

Restore the gate so Blink's streaming callback stays in place in
processes where Node's fetch is disabled.

Fixes #51950

* test: cover WebAssembly streaming compilation in renderers with nodeIntegration

* re-export node patches

* update patches

---------

Co-authored-by: Samuel Attard <sam@electronjs.org>
@trop trop Bot added merged/41-x-y PR was merged to the "41-x-y" branch. and removed in-flight/41-x-y labels Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

44-x-y merged/41-x-y PR was merged to the "41-x-y" branch. merged/42-x-y PR was merged to the "42-x-y" branch. merged/43-x-y PR was merged to the "43-x-y" branch. new-pr 🌱 PR opened recently semver/patch backwards-compatible bug fixes

Projects

Status: No status

3 participants