Files
electron/shell/common/node_bindings_linux.cc
T
Shelley Vohr ec147dedfd fix: illegal access errors when a same-process child window or subframe closes (#52964)
* fix: give each renderer Node.js environment its own uv loop

Same-process child windows and node-integrated subframes each get a
Node.js environment, and until now all of them shared uv_default_loop()
on the renderer main thread. Freeing one (FreeEnvironment runs the loop
until that environment's handles close, with JavaScript disallowed on
the isolate) therefore dispatched the other environments' pending
immediates, timers, fs and threadpool callbacks in that window, which
threw "Uncaught illegal access" in the surviving page or aborted the
renderer (InternalCallbackScope / ToLocalChecked checks).

NodeBindings can now integrate a given loop or own a fresh one (as the
worker path already did), and the renderer keeps one NodeBindings +
ElectronBindings pair per frame environment: the first borrows the
process-lifetime uv_default_loop() integration so native modules that
use the default loop keep working, later ones own their loop. Tearing an
environment down parks that loop's embed thread and frees it without
touching anyone else's handles. This also replaces the uv_env hand-off
between main-frame environments and the injected-frames bookkeeping with
a RenderFrame-keyed map, and gives each new environment its first loop
run from a fresh task since child contexts can be created from inside a
script.

Fixes https://github.com/electron/electron/issues/36858
Fixes https://github.com/electron/electron/issues/40607

* fix: don't stack an error message listener per renderer environment

CreateEnvironment adds Electron's V8 message listener for renderer and
worker environments, and V8 keeps every listener it is given, so each
navigation or child frame added another copy and uncaught errors ran the
async-hook cleanup once per environment ever created on the isolate.
Remove the previous registration before adding.

Also restate why about:blank subframes are skipped for Node.js
integration: freeing their environment is no longer a problem, but they
commit synchronously in the renderer and so never receive the preload
startup data the sandboxed bundle needs.

* test: cover child-window loops, opener reloads and napi work across renderer environments

Adds an async export to the echo fixture (a libuv threadpool round trip
that calls back into JS, the napi path that aborted in
InternalCallbackScope when it completed under another environment's
context) and uses it to check that same-process child windows, including
a synchronously created about:blank one, run immediates, fs and napi work
on their own loop, that a child keeps working while its opener reloads,
and that the survivor's napi callbacks still arrive when a sibling
environment is freed.

* test: cover napi thread-safe functions in child window environments

A thread-safe function called from another thread reaches JS through a
uv_async_t on its environment's loop and an AsyncResource::CallbackScope,
so in a same-process child window it used to run under the opener's
context and abort in InternalCallbackScope. Give the echo fixture a
thread-safe variant and exercise it alongside the async work.

Refs https://github.com/electron/electron/issues/33868

* refactor: set the isolate up for Node.js once per isolate

Pull the IsolateSettings / SetIsolateUpForNode / host-import-callback block
out of NodeBindings::CreateEnvironment into NodeBindings::SetUpIsolate, and
call it once from each owner: the renderer client's one-time init, the
WebWorkerObserver for its thread's isolate, and the browser and utility
processes right after their single CreateEnvironment (node's snapshot path
resets the isolate error handlers inside CreateEnvironment, so it has to
follow it there). Renderer frames no longer re-run isolate setup or stack a
message listener per environment.

* fix: only lend uv_default_loop() to main frame environments

Subframe environments always get a loop of their own; a main frame borrows
the process's uv_default_loop() integration while no other environment holds
it, so native modules that use the default loop see a consistent main-frame
environment.

* fix: free a frame's environment under the explicit microtasks policy

Restore the explicit microtasks policy around freeing a frame's Node.js
environment (its loop runs during teardown, see #33280), and only tear down
when the released context is the environment's own. Teardown can now be
reached from inside script (an iframe removed with element.remove()), so
ExplicitMicrotasksScope no longer asserts a zero microtasks scope depth in
the renderer; explicit checkpoints are no-ops there until the enclosing
scope unwinds.
2026-08-20 10:18:59 +02:00

43 lines
1.1 KiB
C++

// Copyright (c) 2014 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/common/node_bindings_linux.h"
#include <sys/epoll.h>
namespace electron {
NodeBindingsLinux::NodeBindingsLinux(BrowserEnvironment browser_env,
uv_loop_t* loop)
: NodeBindings(browser_env, loop), epoll_(epoll_create(1)) {
auto* const event_loop = uv_loop();
int backend_fd = uv_backend_fd(event_loop);
struct epoll_event ev = {0};
ev.events = EPOLLIN;
ev.data.fd = backend_fd;
epoll_ctl(epoll_, EPOLL_CTL_ADD, backend_fd, &ev);
}
void NodeBindingsLinux::PollEvents() {
auto* const event_loop = uv_loop();
int timeout = uv_backend_timeout(event_loop);
// Wait for new libuv events.
int r;
do {
struct epoll_event ev;
r = epoll_wait(epoll_, &ev, 1, timeout);
} while (r == -1 && errno == EINTR);
}
// static
std::unique_ptr<NodeBindings> NodeBindings::Create(BrowserEnvironment env,
uv_loop_t* loop) {
return std::make_unique<NodeBindingsLinux>(env, loop);
}
} // namespace electron