Skip to content

Commit 2871c1d

Browse files
fix: read nodeIntegrationInWorker from per-frame WebPreferences (#50122) (#50467)
Previously the renderer checked a process-wide command-line switch to decide whether to create a Node.js environment for dedicated workers. When a renderer process hosted multiple WebContents with different nodeIntegrationInWorker values (e.g. via window.open with overridden webPreferences in setWindowOpenHandler), all workers in the process used whichever value the first WebContents set on the command line. Instead, plumb the flag through blink's WorkerSettings at worker creation time, copying it from the initiating frame's WebPreferences. The check on the worker thread then reads the per-worker value. Nested workers inherit the flag from their parent worker via WorkerSettings::Copy. The --node-integration-in-worker command-line switch is removed as it is no longer consumed. Co-authored-by: Samuel Attard <sam@electronjs.org>
1 parent 0d3f57f commit 2871c1d

6 files changed

Lines changed: 192 additions & 30 deletions

File tree

patches/chromium/.patches

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,4 @@ cherry-pick-50b057660b4d.patch
156156
cherry-pick-45c5a70d984d.patch
157157
cherry-pick-05e4b544803c.patch
158158
cherry-pick-5efc7a0127a6.patch
159+
feat_plumb_node_integration_in_worker_through_workersettings.patch
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
2+
From: Samuel Attard <sattard@anthropic.com>
3+
Date: Sat, 7 Mar 2026 23:07:30 -0800
4+
Subject: feat: plumb node_integration_in_worker through WorkerSettings
5+
6+
Copy the node_integration_in_worker flag from the initiating frame's
7+
WebPreferences into WorkerSettings at dedicated worker creation time,
8+
so the value is readable per-worker on the worker thread rather than
9+
relying on a process-wide command line switch. The value is also
10+
propagated to nested workers via WorkerSettings::Copy.
11+
12+
diff --git a/third_party/blink/renderer/core/workers/dedicated_worker.cc b/third_party/blink/renderer/core/workers/dedicated_worker.cc
13+
index a0f78583334fdf4912b897e88d8ce518773dbfb1..300c5a3b806222e46388d2f0d906737cf282e52e 100644
14+
--- a/third_party/blink/renderer/core/workers/dedicated_worker.cc
15+
+++ b/third_party/blink/renderer/core/workers/dedicated_worker.cc
16+
@@ -37,6 +37,7 @@
17+
#include "third_party/blink/renderer/core/frame/local_frame_client.h"
18+
#include "third_party/blink/renderer/core/frame/web_frame_widget_impl.h"
19+
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
20+
+#include "third_party/blink/renderer/core/exported/web_view_impl.h"
21+
#include "third_party/blink/renderer/core/inspector/inspector_trace_events.h"
22+
#include "third_party/blink/renderer/core/inspector/main_thread_debugger.h"
23+
#include "third_party/blink/renderer/core/loader/document_loader.h"
24+
@@ -555,6 +556,12 @@ DedicatedWorker::CreateGlobalScopeCreationParams(
25+
auto* frame = window->GetFrame();
26+
parent_devtools_token = frame->GetDevToolsFrameToken();
27+
settings = std::make_unique<WorkerSettings>(frame->GetSettings());
28+
+ if (auto* web_local_frame = WebLocalFrameImpl::FromFrame(frame)) {
29+
+ if (auto* web_view = web_local_frame->ViewImpl()) {
30+
+ settings->SetNodeIntegrationInWorker(
31+
+ web_view->GetWebPreferences().node_integration_in_worker);
32+
+ }
33+
+ }
34+
agent_group_scheduler_compositor_task_runner =
35+
execution_context->GetScheduler()
36+
->ToFrameScheduler()
37+
diff --git a/third_party/blink/renderer/core/workers/worker_settings.cc b/third_party/blink/renderer/core/workers/worker_settings.cc
38+
index 45680c5f6ea0c7e89ccf43eb88f8a11e3318c02e..3fa3af62f4e7ba8186441c5e3184b1c04fe32d12 100644
39+
--- a/third_party/blink/renderer/core/workers/worker_settings.cc
40+
+++ b/third_party/blink/renderer/core/workers/worker_settings.cc
41+
@@ -40,6 +40,8 @@ std::unique_ptr<WorkerSettings> WorkerSettings::Copy(
42+
old_settings->strictly_block_blockable_mixed_content_;
43+
new_settings->generic_font_family_settings_ =
44+
old_settings->generic_font_family_settings_;
45+
+ new_settings->node_integration_in_worker_ =
46+
+ old_settings->node_integration_in_worker_;
47+
return new_settings;
48+
}
49+
50+
diff --git a/third_party/blink/renderer/core/workers/worker_settings.h b/third_party/blink/renderer/core/workers/worker_settings.h
51+
index 45c60dd2c44b05fdd279f759069383479823c7f2..33a2a0337efb9a46293e11d0d09b3fc182ab9618 100644
52+
--- a/third_party/blink/renderer/core/workers/worker_settings.h
53+
+++ b/third_party/blink/renderer/core/workers/worker_settings.h
54+
@@ -43,6 +43,11 @@ class CORE_EXPORT WorkerSettings {
55+
return generic_font_family_settings_;
56+
}
57+
58+
+ bool NodeIntegrationInWorker() const { return node_integration_in_worker_; }
59+
+ void SetNodeIntegrationInWorker(bool value) {
60+
+ node_integration_in_worker_ = value;
61+
+ }
62+
+
63+
private:
64+
void CopyFlagValuesFromSettings(Settings*);
65+
66+
@@ -54,6 +59,7 @@ class CORE_EXPORT WorkerSettings {
67+
bool strict_mixed_content_checking_ = false;
68+
bool allow_running_of_insecure_content_ = false;
69+
bool strictly_block_blockable_mixed_content_ = false;
70+
+ bool node_integration_in_worker_ = false;
71+
72+
GenericFontFamilySettings generic_font_family_settings_;
73+
};

shell/browser/web_contents_preferences.cc

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -343,9 +343,6 @@ void WebContentsPreferences::AppendCommandLineSwitches(
343343
command_line->AppendSwitchASCII(::switches::kDisableBlinkFeatures,
344344
*disable_blink_features_);
345345

346-
if (node_integration_in_worker_)
347-
command_line->AppendSwitch(switches::kNodeIntegrationInWorker);
348-
349346
// We are appending args to a webContents so let's save the current state
350347
// of our preferences object so that during the lifetime of the WebContents
351348
// we can fetch the options used to initially configure the WebContents

shell/common/options_switches.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -279,10 +279,6 @@ inline constexpr base::cstring_view kAppPath = "app-path";
279279
// The command line switch versions of the options.
280280
inline constexpr base::cstring_view kScrollBounce = "scroll-bounce";
281281

282-
// Command switch passed to renderer process to control nodeIntegration.
283-
inline constexpr base::cstring_view kNodeIntegrationInWorker =
284-
"node-integration-in-worker";
285-
286282
// Widevine options
287283
// Path to Widevine CDM binaries.
288284
inline constexpr base::cstring_view kWidevineCdmPath = "widevine-cdm-path";

shell/renderer/electron_renderer_client.cc

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,16 @@
1818
#include "shell/common/node_bindings.h"
1919
#include "shell/common/node_includes.h"
2020
#include "shell/common/node_util.h"
21-
#include "shell/common/options_switches.h"
21+
#include "shell/common/v8_util.h"
2222
#include "shell/renderer/electron_render_frame_observer.h"
2323
#include "shell/renderer/web_worker_observer.h"
2424
#include "third_party/blink/public/common/web_preferences/web_preferences.h"
2525
#include "third_party/blink/public/web/web_document.h"
2626
#include "third_party/blink/public/web/web_local_frame.h"
2727
#include "third_party/blink/renderer/core/execution_context/execution_context.h" // nogncheck
2828
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" // nogncheck
29+
#include "third_party/blink/renderer/core/workers/worker_global_scope.h" // nogncheck
30+
#include "third_party/blink/renderer/core/workers/worker_settings.h" // nogncheck
2931

3032
#if BUILDFLAG(IS_LINUX) && (defined(ARCH_CPU_X86_64) || defined(ARCH_CPU_ARM64))
3133
#define ENABLE_WEB_ASSEMBLY_TRAP_HANDLER_LINUX
@@ -207,44 +209,54 @@ void ElectronRendererClient::WillReleaseScriptContext(
207209
electron_bindings_->EnvironmentDestroyed(env);
208210
}
209211

210-
void ElectronRendererClient::WorkerScriptReadyForEvaluationOnWorkerThread(
211-
v8::Local<v8::Context> context) {
212+
namespace {
213+
214+
bool WorkerHasNodeIntegration(blink::ExecutionContext* ec) {
212215
// We do not create a Node.js environment in service or shared workers
213216
// owing to an inability to customize sandbox policies in these workers
214217
// given that they're run out-of-process.
215218
// Also avoid creating a Node.js environment for worklet global scope
216219
// created on the main thread.
217-
auto* ec = blink::ExecutionContext::From(context);
218220
if (ec->IsServiceWorkerGlobalScope() || ec->IsSharedWorkerGlobalScope() ||
219221
ec->IsMainThreadWorkletGlobalScope())
222+
return false;
223+
224+
auto* wgs = blink::DynamicTo<blink::WorkerGlobalScope>(ec);
225+
if (!wgs)
226+
return false;
227+
228+
// Read the nodeIntegrationInWorker preference from the worker's settings,
229+
// which were copied from the initiating frame's WebPreferences at worker
230+
// creation time. This ensures that in-process child windows with different
231+
// webPreferences get the correct per-frame value rather than a process-wide
232+
// value.
233+
auto* worker_settings = wgs->GetWorkerSettings();
234+
return worker_settings && worker_settings->NodeIntegrationInWorker();
235+
}
236+
237+
} // namespace
238+
239+
void ElectronRendererClient::WorkerScriptReadyForEvaluationOnWorkerThread(
240+
v8::Local<v8::Context> context) {
241+
auto* ec = blink::ExecutionContext::From(context);
242+
if (!WorkerHasNodeIntegration(ec))
220243
return;
221244

222-
// This won't be correct for in-process child windows with webPreferences
223-
// that have a different value for nodeIntegrationInWorker
224-
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
225-
switches::kNodeIntegrationInWorker)) {
226-
auto* current = WebWorkerObserver::GetCurrent();
227-
if (current)
228-
return;
229-
WebWorkerObserver::Create()->WorkerScriptReadyForEvaluation(context);
230-
}
245+
auto* current = WebWorkerObserver::GetCurrent();
246+
if (current)
247+
return;
248+
WebWorkerObserver::Create()->WorkerScriptReadyForEvaluation(context);
231249
}
232250

233251
void ElectronRendererClient::WillDestroyWorkerContextOnWorkerThread(
234252
v8::Local<v8::Context> context) {
235253
auto* ec = blink::ExecutionContext::From(context);
236-
if (ec->IsServiceWorkerGlobalScope() || ec->IsSharedWorkerGlobalScope() ||
237-
ec->IsMainThreadWorkletGlobalScope())
254+
if (!WorkerHasNodeIntegration(ec))
238255
return;
239256

240-
// TODO(loc): Note that this will not be correct for in-process child windows
241-
// with webPreferences that have a different value for nodeIntegrationInWorker
242-
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
243-
switches::kNodeIntegrationInWorker)) {
244-
auto* current = WebWorkerObserver::GetCurrent();
245-
if (current)
246-
current->ContextWillDestroy(context);
247-
}
257+
auto* current = WebWorkerObserver::GetCurrent();
258+
if (current)
259+
current->ContextWillDestroy(context);
248260
}
249261

250262
void ElectronRendererClient::SetUpWebAssemblyTrapHandler() {

spec/chromium-spec.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1372,6 +1372,89 @@ describe('chromium features', () => {
13721372
expect(data).to.equal('object function object function');
13731373
});
13741374

1375+
it('Worker does not have node integration when nodeIntegrationInWorker is disabled via setWindowOpenHandler', async () => {
1376+
const w = new BrowserWindow({
1377+
show: false,
1378+
webPreferences: {
1379+
nodeIntegration: true,
1380+
nodeIntegrationInWorker: true,
1381+
contextIsolation: false
1382+
}
1383+
});
1384+
1385+
w.webContents.setWindowOpenHandler(() => ({
1386+
action: 'allow',
1387+
overrideBrowserWindowOptions: {
1388+
show: false,
1389+
webPreferences: {
1390+
nodeIntegration: false,
1391+
nodeIntegrationInWorker: false,
1392+
contextIsolation: true
1393+
}
1394+
}
1395+
}));
1396+
1397+
await w.loadURL(`file://${fixturesPath}/pages/blank.html`);
1398+
const childCreated = once(app, 'browser-window-created') as Promise<[any, BrowserWindow]>;
1399+
w.webContents.executeJavaScript(`window.open(${JSON.stringify(`file://${fixturesPath}/pages/blank.html`)}); void 0;`);
1400+
const [, child] = await childCreated;
1401+
await once(child.webContents, 'did-finish-load');
1402+
1403+
const data = await child.webContents.executeJavaScript(`
1404+
const worker = new Worker('../workers/worker_node.js');
1405+
new Promise((resolve) => { worker.onmessage = e => resolve(e.data); })
1406+
`);
1407+
expect(data).to.equal('undefined undefined undefined undefined');
1408+
});
1409+
1410+
it('Worker has node integration when nodeIntegrationInWorker is enabled via setWindowOpenHandler', async () => {
1411+
const w = new BrowserWindow({
1412+
show: false,
1413+
webPreferences: {
1414+
nodeIntegration: true,
1415+
nodeIntegrationInWorker: false,
1416+
contextIsolation: false
1417+
}
1418+
});
1419+
1420+
w.webContents.setWindowOpenHandler(() => ({
1421+
action: 'allow',
1422+
overrideBrowserWindowOptions: {
1423+
show: false,
1424+
webPreferences: {
1425+
nodeIntegration: true,
1426+
nodeIntegrationInWorker: true,
1427+
contextIsolation: false
1428+
}
1429+
}
1430+
}));
1431+
1432+
await w.loadURL(`file://${fixturesPath}/pages/blank.html`);
1433+
1434+
// Parent's workers should NOT have node integration.
1435+
const parentData = await w.webContents.executeJavaScript(`
1436+
new Promise((resolve) => {
1437+
const worker = new Worker('../workers/worker_node.js');
1438+
worker.onmessage = e => resolve(e.data);
1439+
})
1440+
`);
1441+
expect(parentData).to.equal('undefined undefined undefined undefined');
1442+
1443+
const childCreated = once(app, 'browser-window-created') as Promise<[any, BrowserWindow]>;
1444+
w.webContents.executeJavaScript(`window.open(${JSON.stringify(`file://${fixturesPath}/pages/blank.html`)}); void 0;`);
1445+
const [, child] = await childCreated;
1446+
await once(child.webContents, 'did-finish-load');
1447+
1448+
// Child's workers should have node integration.
1449+
const childData = await child.webContents.executeJavaScript(`
1450+
new Promise((resolve) => {
1451+
const worker = new Worker('../workers/worker_node.js');
1452+
worker.onmessage = e => resolve(e.data);
1453+
})
1454+
`);
1455+
expect(childData).to.equal('object function object function');
1456+
});
1457+
13751458
it('Worker has access to fetch-dependent interfaces with nodeIntegrationInWorker', async () => {
13761459
const w = new BrowserWindow({
13771460
show: false,

0 commit comments

Comments
 (0)