electron/spec/fixtures/api/preload-realm/preload-tests.js

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
});
}
});