mirror of https://github.com/electron/electron
108 lines
3.4 KiB
JavaScript
108 lines
3.4 KiB
JavaScript
const { contextBridge, ipcRenderer } = require('electron');
|
|
|
|
const evalTests = {
|
|
evalConstructorName: () => globalThis.constructor.name
|
|
};
|
|
|
|
const tests = {
|
|
testSend: (name, ...args) => {
|
|
ipcRenderer.send(name, ...args);
|
|
},
|
|
testInvoke: async (name, ...args) => {
|
|
const result = await ipcRenderer.invoke(name, ...args);
|
|
return result;
|
|
},
|
|
testEvaluate: (testName, args) => {
|
|
const func = evalTests[testName];
|
|
const result = args
|
|
? contextBridge.executeInMainWorld({ func, args })
|
|
: contextBridge.executeInMainWorld({ func });
|
|
return result;
|
|
},
|
|
testPrototypeLeak: () => {
|
|
const checkPrototypes = (value) => {
|
|
// Get prototype in preload world
|
|
const prototype = Object.getPrototypeOf(value);
|
|
const constructorName = prototype.constructor.name;
|
|
|
|
const result = contextBridge.executeInMainWorld({
|
|
func: (value) => {
|
|
// Deeply check that value prototypes exist in the local world
|
|
const check = (v) => {
|
|
if (typeof v === 'undefined' || v === null) return true;
|
|
const prototype = Object.getPrototypeOf(v);
|
|
const constructorName = prototype.constructor.name;
|
|
const localPrototype = globalThis[constructorName].prototype;
|
|
if (prototype !== localPrototype) return false;
|
|
if (Array.isArray(v)) return v.every(check);
|
|
if (typeof v === 'object') return Object.values(v).every(check);
|
|
if (typeof v === 'function') return check(v());
|
|
return true;
|
|
};
|
|
return { protoMatches: check(value), value };
|
|
},
|
|
args: [value, constructorName]
|
|
});
|
|
|
|
// Deeply check that value prototypes exist in the local world
|
|
const check = (v) => {
|
|
if (typeof v === 'undefined' || v === null) return true;
|
|
const prototype = Object.getPrototypeOf(v);
|
|
const constructorName = prototype.constructor.name;
|
|
const localPrototype = globalThis[constructorName].prototype;
|
|
if (prototype !== localPrototype) return false;
|
|
if (Array.isArray(v)) return v.every(check);
|
|
if (typeof v === 'object') return Object.values(v).every(check);
|
|
if (typeof v === 'function') return check(v());
|
|
return true;
|
|
};
|
|
|
|
return (
|
|
// Prototype matched in main world
|
|
result.protoMatches &&
|
|
// Returned value matches prototype
|
|
check(result.value)
|
|
);
|
|
};
|
|
|
|
const values = [
|
|
123,
|
|
'string',
|
|
true,
|
|
[],
|
|
[123, 'string', true, ['foo']],
|
|
Symbol('foo'),
|
|
10n,
|
|
{},
|
|
Promise.resolve(),
|
|
() => {},
|
|
() => () => null,
|
|
{ [Symbol('foo')]: 123 }
|
|
];
|
|
|
|
for (const value of values) {
|
|
if (!checkPrototypes(value)) {
|
|
const constructorName = Object.getPrototypeOf(value).constructor.name;
|
|
return `${constructorName} (${value}) leaked in service worker preload`;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
ipcRenderer.on('test', async (_event, uuid, name, ...args) => {
|
|
console.debug(`running test ${name} for ${uuid}`);
|
|
try {
|
|
const result = await tests[name]?.(...args);
|
|
console.debug(`responding test ${name} for ${uuid}`);
|
|
ipcRenderer.send(`test-result-${uuid}`, { error: false, result });
|
|
} catch (error) {
|
|
console.debug(`erroring test ${name} for ${uuid}`);
|
|
ipcRenderer.send(`test-result-${uuid}`, {
|
|
error: true,
|
|
result: error.message
|
|
});
|
|
}
|
|
});
|