|
@@ -18,6 +18,76 @@ const tests = {
|
|
|
? 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;
|
|
|
}
|
|
|
};
|
|
|
|
|
@@ -29,6 +99,9 @@ ipcRenderer.on('test', async (_event, uuid, name, ...args) => {
|
|
|
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 });
|
|
|
+ ipcRenderer.send(`test-result-${uuid}`, {
|
|
|
+ error: true,
|
|
|
+ result: error.message
|
|
|
+ });
|
|
|
}
|
|
|
});
|