|
@@ -104,6 +104,27 @@ describe('contextBridge', () => {
|
|
|
};
|
|
|
|
|
|
it('should proxy numbers', async () => {
|
|
|
+ await makeBindingWindow(() => {
|
|
|
+ contextBridge.exposeInMainWorld('example', 123);
|
|
|
+ });
|
|
|
+ const result = await callWithBindings((root: any) => {
|
|
|
+ return root.example;
|
|
|
+ });
|
|
|
+ expect(result).to.equal(123);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should make global properties read-only', async () => {
|
|
|
+ await makeBindingWindow(() => {
|
|
|
+ contextBridge.exposeInMainWorld('example', 123);
|
|
|
+ });
|
|
|
+ const result = await callWithBindings((root: any) => {
|
|
|
+ root.example = 456;
|
|
|
+ return root.example;
|
|
|
+ });
|
|
|
+ expect(result).to.equal(123);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should proxy nested numbers', async () => {
|
|
|
await makeBindingWindow(() => {
|
|
|
contextBridge.exposeInMainWorld('example', {
|
|
|
myNumber: 123
|
|
@@ -129,6 +150,16 @@ describe('contextBridge', () => {
|
|
|
});
|
|
|
|
|
|
it('should proxy strings', async () => {
|
|
|
+ await makeBindingWindow(() => {
|
|
|
+ contextBridge.exposeInMainWorld('example', 'my-words');
|
|
|
+ });
|
|
|
+ const result = await callWithBindings((root: any) => {
|
|
|
+ return root.example;
|
|
|
+ });
|
|
|
+ expect(result).to.equal('my-words');
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should proxy nested strings', async () => {
|
|
|
await makeBindingWindow(() => {
|
|
|
contextBridge.exposeInMainWorld('example', {
|
|
|
myString: 'my-words'
|
|
@@ -141,6 +172,16 @@ describe('contextBridge', () => {
|
|
|
});
|
|
|
|
|
|
it('should proxy arrays', async () => {
|
|
|
+ await makeBindingWindow(() => {
|
|
|
+ contextBridge.exposeInMainWorld('example', [123, 'my-words']);
|
|
|
+ });
|
|
|
+ const result = await callWithBindings((root: any) => {
|
|
|
+ return [root.example, Array.isArray(root.example)];
|
|
|
+ });
|
|
|
+ expect(result).to.deep.equal([[123, 'my-words'], true]);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should proxy nested arrays', async () => {
|
|
|
await makeBindingWindow(() => {
|
|
|
contextBridge.exposeInMainWorld('example', {
|
|
|
myArr: [123, 'my-words']
|
|
@@ -153,6 +194,21 @@ describe('contextBridge', () => {
|
|
|
});
|
|
|
|
|
|
it('should make arrays immutable', async () => {
|
|
|
+ await makeBindingWindow(() => {
|
|
|
+ contextBridge.exposeInMainWorld('example', [123, 'my-words']);
|
|
|
+ });
|
|
|
+ const immutable = await callWithBindings((root: any) => {
|
|
|
+ try {
|
|
|
+ root.example.push(456);
|
|
|
+ return false;
|
|
|
+ } catch {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ expect(immutable).to.equal(true);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should make nested arrays immutable', async () => {
|
|
|
await makeBindingWindow(() => {
|
|
|
contextBridge.exposeInMainWorld('example', {
|
|
|
myArr: [123, 'my-words']
|
|
@@ -170,6 +226,16 @@ describe('contextBridge', () => {
|
|
|
});
|
|
|
|
|
|
it('should proxy booleans', async () => {
|
|
|
+ await makeBindingWindow(() => {
|
|
|
+ contextBridge.exposeInMainWorld('example', true);
|
|
|
+ });
|
|
|
+ const result = await callWithBindings((root: any) => {
|
|
|
+ return root.example;
|
|
|
+ });
|
|
|
+ expect(result).to.equal(true);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should proxy nested booleans', async () => {
|
|
|
await makeBindingWindow(() => {
|
|
|
contextBridge.exposeInMainWorld('example', {
|
|
|
myBool: true
|
|
@@ -182,6 +248,18 @@ describe('contextBridge', () => {
|
|
|
});
|
|
|
|
|
|
it('should proxy promises and resolve with the correct value', async () => {
|
|
|
+ await makeBindingWindow(() => {
|
|
|
+ contextBridge.exposeInMainWorld('example',
|
|
|
+ Promise.resolve('i-resolved')
|
|
|
+ );
|
|
|
+ });
|
|
|
+ const result = await callWithBindings((root: any) => {
|
|
|
+ return root.example;
|
|
|
+ });
|
|
|
+ expect(result).to.equal('i-resolved');
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should proxy nested promises and resolve with the correct value', async () => {
|
|
|
await makeBindingWindow(() => {
|
|
|
contextBridge.exposeInMainWorld('example', {
|
|
|
myPromise: Promise.resolve('i-resolved')
|
|
@@ -194,6 +272,21 @@ describe('contextBridge', () => {
|
|
|
});
|
|
|
|
|
|
it('should proxy promises and reject with the correct value', async () => {
|
|
|
+ await makeBindingWindow(() => {
|
|
|
+ contextBridge.exposeInMainWorld('example', Promise.reject(new Error('i-rejected')));
|
|
|
+ });
|
|
|
+ const result = await callWithBindings(async (root: any) => {
|
|
|
+ try {
|
|
|
+ await root.example;
|
|
|
+ return null;
|
|
|
+ } catch (err) {
|
|
|
+ return err;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ expect(result).to.be.an.instanceOf(Error).with.property('message', 'Uncaught Error: i-rejected');
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should proxy nested promises and reject with the correct value', async () => {
|
|
|
await makeBindingWindow(() => {
|
|
|
contextBridge.exposeInMainWorld('example', {
|
|
|
myPromise: Promise.reject(new Error('i-rejected'))
|
|
@@ -249,6 +342,16 @@ describe('contextBridge', () => {
|
|
|
expect(result).to.deep.equal([123, 'help', false, 'promise']);
|
|
|
});
|
|
|
|
|
|
+ it('should proxy functions', async () => {
|
|
|
+ await makeBindingWindow(() => {
|
|
|
+ contextBridge.exposeInMainWorld('example', () => 'return-value');
|
|
|
+ });
|
|
|
+ const result = await callWithBindings(async (root: any) => {
|
|
|
+ return root.example();
|
|
|
+ });
|
|
|
+ expect(result).equal('return-value');
|
|
|
+ });
|
|
|
+
|
|
|
it('should proxy methods that are callable multiple times', async () => {
|
|
|
await makeBindingWindow(() => {
|
|
|
contextBridge.exposeInMainWorld('example', {
|
|
@@ -299,7 +402,31 @@ describe('contextBridge', () => {
|
|
|
expect(result).to.deep.equal([123, 456, 789, false]);
|
|
|
});
|
|
|
|
|
|
- it('it should proxy null and undefined correctly', async () => {
|
|
|
+ it('it should proxy null', async () => {
|
|
|
+ await makeBindingWindow(() => {
|
|
|
+ contextBridge.exposeInMainWorld('example', null);
|
|
|
+ });
|
|
|
+ const result = await callWithBindings((root: any) => {
|
|
|
+ // Convert to strings as although the context bridge keeps the right value
|
|
|
+ // IPC does not
|
|
|
+ return `${root.example}`;
|
|
|
+ });
|
|
|
+ expect(result).to.deep.equal('null');
|
|
|
+ });
|
|
|
+
|
|
|
+ it('it should proxy undefined', async () => {
|
|
|
+ await makeBindingWindow(() => {
|
|
|
+ contextBridge.exposeInMainWorld('example', undefined);
|
|
|
+ });
|
|
|
+ const result = await callWithBindings((root: any) => {
|
|
|
+ // Convert to strings as although the context bridge keeps the right value
|
|
|
+ // IPC does not
|
|
|
+ return `${root.example}`;
|
|
|
+ });
|
|
|
+ expect(result).to.deep.equal('undefined');
|
|
|
+ });
|
|
|
+
|
|
|
+ it('it should proxy nested null and undefined correctly', async () => {
|
|
|
await makeBindingWindow(() => {
|
|
|
contextBridge.exposeInMainWorld('example', {
|
|
|
values: [null, undefined]
|
|
@@ -313,6 +440,19 @@ describe('contextBridge', () => {
|
|
|
expect(result).to.deep.equal(['null', 'undefined']);
|
|
|
});
|
|
|
|
|
|
+ it('should proxy symbols', async () => {
|
|
|
+ await makeBindingWindow(() => {
|
|
|
+ const mySymbol = Symbol('unique');
|
|
|
+ const isSymbol = (s: Symbol) => s === mySymbol;
|
|
|
+ contextBridge.exposeInMainWorld('symbol', mySymbol);
|
|
|
+ contextBridge.exposeInMainWorld('isSymbol', isSymbol);
|
|
|
+ });
|
|
|
+ const result = await callWithBindings((root: any) => {
|
|
|
+ return root.isSymbol(root.symbol);
|
|
|
+ });
|
|
|
+ expect(result).to.equal(true, 'symbols should be equal across contexts');
|
|
|
+ });
|
|
|
+
|
|
|
it('should proxy symbols such that symbol equality works', async () => {
|
|
|
await makeBindingWindow(() => {
|
|
|
const mySymbol = Symbol('unique');
|
|
@@ -341,6 +481,26 @@ describe('contextBridge', () => {
|
|
|
expect(result).to.equal(123, 'symbols key lookup should work across contexts');
|
|
|
});
|
|
|
|
|
|
+ it('should proxy typed arrays', async () => {
|
|
|
+ await makeBindingWindow(() => {
|
|
|
+ contextBridge.exposeInMainWorld('example', new Uint8Array(100));
|
|
|
+ });
|
|
|
+ const result = await callWithBindings((root: any) => {
|
|
|
+ return Object.getPrototypeOf(root.example) === Uint8Array.prototype;
|
|
|
+ });
|
|
|
+ expect(result).equal(true);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should proxy regexps', async () => {
|
|
|
+ await makeBindingWindow(() => {
|
|
|
+ contextBridge.exposeInMainWorld('example', /a/g);
|
|
|
+ });
|
|
|
+ const result = await callWithBindings((root: any) => {
|
|
|
+ return Object.getPrototypeOf(root.example) === RegExp.prototype;
|
|
|
+ });
|
|
|
+ expect(result).equal(true);
|
|
|
+ });
|
|
|
+
|
|
|
it('should proxy typed arrays and regexps through the serializer', async () => {
|
|
|
await makeBindingWindow(() => {
|
|
|
contextBridge.exposeInMainWorld('example', {
|
|
@@ -466,6 +626,22 @@ describe('contextBridge', () => {
|
|
|
expect(result).to.equal('final value');
|
|
|
});
|
|
|
|
|
|
+ it('should work with complex nested methods and promises attached directly to the global', async () => {
|
|
|
+ await makeBindingWindow(() => {
|
|
|
+ contextBridge.exposeInMainWorld('example',
|
|
|
+ (second: Function) => second((fourth: Function) => {
|
|
|
+ return fourth();
|
|
|
+ })
|
|
|
+ );
|
|
|
+ });
|
|
|
+ const result = await callWithBindings((root: any) => {
|
|
|
+ return root.example((third: Function) => {
|
|
|
+ return third(() => Promise.resolve('final value'));
|
|
|
+ });
|
|
|
+ });
|
|
|
+ expect(result).to.equal('final value');
|
|
|
+ });
|
|
|
+
|
|
|
it('should throw an error when recursion depth is exceeded', async () => {
|
|
|
await makeBindingWindow(() => {
|
|
|
contextBridge.exposeInMainWorld('example', {
|
|
@@ -641,6 +817,127 @@ describe('contextBridge', () => {
|
|
|
expect(result.protoMatches).to.deep.equal(result.protoMatches.map(() => true));
|
|
|
});
|
|
|
|
|
|
+ it('should not leak prototypes when attaching directly to the global', async () => {
|
|
|
+ await makeBindingWindow(() => {
|
|
|
+ const toExpose = {
|
|
|
+ number: 123,
|
|
|
+ string: 'string',
|
|
|
+ boolean: true,
|
|
|
+ arr: [123, 'string', true, ['foo']],
|
|
|
+ symbol: Symbol('foo'),
|
|
|
+ bigInt: 10n,
|
|
|
+ getObject: () => ({ thing: 123 }),
|
|
|
+ getNumber: () => 123,
|
|
|
+ getString: () => 'string',
|
|
|
+ getBoolean: () => true,
|
|
|
+ getArr: () => [123, 'string', true, ['foo']],
|
|
|
+ getPromise: async () => ({ number: 123, string: 'string', boolean: true, fn: () => 'string', arr: [123, 'string', true, ['foo']] }),
|
|
|
+ getFunctionFromFunction: async () => () => null,
|
|
|
+ object: {
|
|
|
+ number: 123,
|
|
|
+ string: 'string',
|
|
|
+ boolean: true,
|
|
|
+ arr: [123, 'string', true, ['foo']],
|
|
|
+ getPromise: async () => ({ number: 123, string: 'string', boolean: true, fn: () => 'string', arr: [123, 'string', true, ['foo']] })
|
|
|
+ },
|
|
|
+ receiveArguments: (fn: any) => fn({ key: 'value' }),
|
|
|
+ symbolKeyed: {
|
|
|
+ [Symbol('foo')]: 123
|
|
|
+ }
|
|
|
+ };
|
|
|
+ const keys: string[] = [];
|
|
|
+ Object.entries(toExpose).forEach(([key, value]) => {
|
|
|
+ keys.push(key);
|
|
|
+ contextBridge.exposeInMainWorld(key, value);
|
|
|
+ });
|
|
|
+ contextBridge.exposeInMainWorld('keys', keys);
|
|
|
+ });
|
|
|
+ const result = await callWithBindings(async (root: any) => {
|
|
|
+ const { keys } = root;
|
|
|
+ const cleanedRoot: any = {};
|
|
|
+ for (const [key, value] of Object.entries(root)) {
|
|
|
+ if (keys.includes(key)) {
|
|
|
+ cleanedRoot[key] = value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ let arg: any;
|
|
|
+ cleanedRoot.receiveArguments((o: any) => { arg = o; });
|
|
|
+ const protoChecks = [
|
|
|
+ ...Object.keys(cleanedRoot).map(key => [key, String]),
|
|
|
+ ...Object.getOwnPropertySymbols(cleanedRoot.symbolKeyed).map(key => [key, Symbol]),
|
|
|
+ [cleanedRoot, Object],
|
|
|
+ [cleanedRoot.number, Number],
|
|
|
+ [cleanedRoot.string, String],
|
|
|
+ [cleanedRoot.boolean, Boolean],
|
|
|
+ [cleanedRoot.arr, Array],
|
|
|
+ [cleanedRoot.arr[0], Number],
|
|
|
+ [cleanedRoot.arr[1], String],
|
|
|
+ [cleanedRoot.arr[2], Boolean],
|
|
|
+ [cleanedRoot.arr[3], Array],
|
|
|
+ [cleanedRoot.arr[3][0], String],
|
|
|
+ [cleanedRoot.symbol, Symbol],
|
|
|
+ [cleanedRoot.bigInt, BigInt],
|
|
|
+ [cleanedRoot.getNumber, Function],
|
|
|
+ [cleanedRoot.getNumber(), Number],
|
|
|
+ [cleanedRoot.getObject(), Object],
|
|
|
+ [cleanedRoot.getString(), String],
|
|
|
+ [cleanedRoot.getBoolean(), Boolean],
|
|
|
+ [cleanedRoot.getArr(), Array],
|
|
|
+ [cleanedRoot.getArr()[0], Number],
|
|
|
+ [cleanedRoot.getArr()[1], String],
|
|
|
+ [cleanedRoot.getArr()[2], Boolean],
|
|
|
+ [cleanedRoot.getArr()[3], Array],
|
|
|
+ [cleanedRoot.getArr()[3][0], String],
|
|
|
+ [cleanedRoot.getFunctionFromFunction, Function],
|
|
|
+ [cleanedRoot.getFunctionFromFunction(), Promise],
|
|
|
+ [await cleanedRoot.getFunctionFromFunction(), Function],
|
|
|
+ [cleanedRoot.getPromise(), Promise],
|
|
|
+ [await cleanedRoot.getPromise(), Object],
|
|
|
+ [(await cleanedRoot.getPromise()).number, Number],
|
|
|
+ [(await cleanedRoot.getPromise()).string, String],
|
|
|
+ [(await cleanedRoot.getPromise()).boolean, Boolean],
|
|
|
+ [(await cleanedRoot.getPromise()).fn, Function],
|
|
|
+ [(await cleanedRoot.getPromise()).fn(), String],
|
|
|
+ [(await cleanedRoot.getPromise()).arr, Array],
|
|
|
+ [(await cleanedRoot.getPromise()).arr[0], Number],
|
|
|
+ [(await cleanedRoot.getPromise()).arr[1], String],
|
|
|
+ [(await cleanedRoot.getPromise()).arr[2], Boolean],
|
|
|
+ [(await cleanedRoot.getPromise()).arr[3], Array],
|
|
|
+ [(await cleanedRoot.getPromise()).arr[3][0], String],
|
|
|
+ [cleanedRoot.object, Object],
|
|
|
+ [cleanedRoot.object.number, Number],
|
|
|
+ [cleanedRoot.object.string, String],
|
|
|
+ [cleanedRoot.object.boolean, Boolean],
|
|
|
+ [cleanedRoot.object.arr, Array],
|
|
|
+ [cleanedRoot.object.arr[0], Number],
|
|
|
+ [cleanedRoot.object.arr[1], String],
|
|
|
+ [cleanedRoot.object.arr[2], Boolean],
|
|
|
+ [cleanedRoot.object.arr[3], Array],
|
|
|
+ [cleanedRoot.object.arr[3][0], String],
|
|
|
+ [await cleanedRoot.object.getPromise(), Object],
|
|
|
+ [(await cleanedRoot.object.getPromise()).number, Number],
|
|
|
+ [(await cleanedRoot.object.getPromise()).string, String],
|
|
|
+ [(await cleanedRoot.object.getPromise()).boolean, Boolean],
|
|
|
+ [(await cleanedRoot.object.getPromise()).fn, Function],
|
|
|
+ [(await cleanedRoot.object.getPromise()).fn(), String],
|
|
|
+ [(await cleanedRoot.object.getPromise()).arr, Array],
|
|
|
+ [(await cleanedRoot.object.getPromise()).arr[0], Number],
|
|
|
+ [(await cleanedRoot.object.getPromise()).arr[1], String],
|
|
|
+ [(await cleanedRoot.object.getPromise()).arr[2], Boolean],
|
|
|
+ [(await cleanedRoot.object.getPromise()).arr[3], Array],
|
|
|
+ [(await cleanedRoot.object.getPromise()).arr[3][0], String],
|
|
|
+ [arg, Object],
|
|
|
+ [arg.key, String]
|
|
|
+ ];
|
|
|
+ return {
|
|
|
+ protoMatches: protoChecks.map(([a, Constructor]) => Object.getPrototypeOf(a) === Constructor.prototype)
|
|
|
+ };
|
|
|
+ });
|
|
|
+ // Every protomatch should be true
|
|
|
+ expect(result.protoMatches).to.deep.equal(result.protoMatches.map(() => true));
|
|
|
+ });
|
|
|
+
|
|
|
describe('internalContextBridge', () => {
|
|
|
describe('overrideGlobalValueFromIsolatedWorld', () => {
|
|
|
it('should override top level properties', async () => {
|