Compare commits

...
Author SHA1 Message Date
Kaoru0822andClaude Opus 5.5 0c69564297 fix(electron): leave the preload unknown after a later dynamic override
A BrowserWindow webPreferences literal kept an earlier literal preload
after a later preload with a non-literal value, a spread, a computed key
or an accessor could replace it, so the graph associated the window with
the overwritten script. Derive the effective preload from the last
definition of the property, as Electron receives it.

Closes #1237

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 13:55:33 +00:00
2 changed files with 63 additions and 10 deletions
@@ -102,10 +102,6 @@ const collectWebPreferences = (
} => { } => {
const preferences: ElectronWebPreference[] = []; const preferences: ElectronWebPreference[] = [];
let unknown = 0; let unknown = 0;
let preload: ElectronBrowserWindowPreload = {
preload_path: null,
preload_resolution_context: null,
};
for (const property of object.properties) { for (const property of object.properties) {
if (t.isSpreadElement(property)) { if (t.isSpreadElement(property)) {
unknown += 1; unknown += 1;
@@ -133,11 +129,6 @@ const collectWebPreferences = (
continue; continue;
} }
const path = name === "preload" ? staticPath(property.value) : undefined; const path = name === "preload" ? staticPath(property.value) : undefined;
if (path !== undefined)
preload = {
preload_path: path,
preload_resolution_context: staticPathResolutionContext(property.value),
};
const value: ElectronStaticValue = const value: ElectronStaticValue =
path === undefined path === undefined
? electronStaticValue(source, property.value) ? electronStaticValue(source, property.value)
@@ -149,11 +140,34 @@ const collectWebPreferences = (
return { return {
status: "object-literal", status: "object-literal",
preferences, preferences,
preload, preload: effectivePreload(object),
unknown, unknown,
}; };
}; };
/**
* The preload path Electron receives is the last definition of the property.
* A later spread, computed key, accessor, or non-literal value can replace an
* earlier literal, so only a final static definition names the preload.
*/
const effectivePreload = (
object: t.ObjectExpression,
): ElectronBrowserWindowPreload => {
const lookup = objectProperty(object, "preload");
const path =
lookup.status === "explicit"
? staticPath(lookup.property.value)
: undefined;
return lookup.status !== "explicit" || path === undefined
? { preload_path: null, preload_resolution_context: null }
: {
preload_path: path,
preload_resolution_context: staticPathResolutionContext(
lookup.property.value,
),
};
};
const inspectContextBridge = ( const inspectContextBridge = (
node: t.CallExpression, node: t.CallExpression,
context: JavaScriptFindingContext, context: JavaScriptFindingContext,
@@ -65,3 +65,42 @@ describe("Electron option evidence", () => {
); );
}); });
}); });
describe("Electron effective preload", () => {
const windowPreload = (webPreferences: string) =>
analyzeJavaScriptStaticSource(
`new BrowserWindow({ webPreferences: { ${webPreferences} } });`,
).electron.browser_windows[0];
it.each([
'preload: "./first.js", preload: runtimePath',
'preload: "./first.js", ...runtimeOptions',
'preload: "./first.js", [runtimeKey]: runtimeValue',
'preload: "./first.js", get preload() { return runtimePath; }',
])("leaves the preload unknown after a later override in %s", (source) => {
expect(windowPreload(source)).toMatchObject({
preload_path: null,
preload_resolution_context: null,
web_preferences: expect.arrayContaining([
{
name: "preload",
value: { status: "literal", value: "./first.js", expression: null },
},
]),
});
});
it.each([
['preload: runtimePath, preload: "./first.js"', "./first.js"],
['...runtimeOptions, preload: "./first.js"', "./first.js"],
['[runtimeKey]: runtimeValue, preload: "./first.js"', "./first.js"],
['preload: "./first.js", preload: "./last.js"', "./last.js"],
['preload: "./first.js", ["preload"]: "./last.js"', "./last.js"],
['preload: "./first.js", sandbox: true', "./first.js"],
])("resolves the final static preload in %s", (source, expected) => {
expect(windowPreload(source)).toMatchObject({
preload_path: expected,
preload_resolution_context: "module-specifier",
});
});
});