diff --git a/packages/injected/src/bindingsController.ts b/packages/injected/src/bindingsController.ts index 7f58b482a5f00..cc5f393ae2b14 100644 --- a/packages/injected/src/bindingsController.ts +++ b/packages/injected/src/bindingsController.ts @@ -59,11 +59,29 @@ export class BindingsController { }); } const payload: BindingPayload = { name: bindingName, seq, serializedArgs }; - (this._global as any)[this._globalBindingName](JSON.stringify(payload)); + (this._global as any)[this._globalBindingName](this._stringifyPayload(payload)); return promise; }; } + private _stringifyPayload(payload: BindingPayload): string { + // The page may define Array.prototype.toJSON/Object.prototype.toJSON (e.g. Prototype.js), + // which would corrupt JSON.stringify(payload) into something other than an object with + // a serializedArgs array. Temporarily remove them so our own payload serializes correctly. + const arrayToJSON = (Array.prototype as any).toJSON; + const objectToJSON = (Object.prototype as any).toJSON; + try { + delete (Array.prototype as any).toJSON; + delete (Object.prototype as any).toJSON; + return JSON.stringify(payload); + } finally { + if (arrayToJSON !== undefined) + (Array.prototype as any).toJSON = arrayToJSON; + if (objectToJSON !== undefined) + (Object.prototype as any).toJSON = objectToJSON; + } + } + removeBinding(bindingName: string) { const data = this._bindings.get(bindingName); if (data) diff --git a/tests/page/page-expose-function.spec.ts b/tests/page/page-expose-function.spec.ts index 79c354791c638..ad6f1ef72d5a3 100644 --- a/tests/page/page-expose-function.spec.ts +++ b/tests/page/page-expose-function.spec.ts @@ -235,12 +235,13 @@ it('should work with busted Array.prototype.map/push', async ({ page, server }) expect(await page.evaluate('add(5, 6)')).toBe(11); }); -it('should fail with busted Array.prototype.toJSON', async ({ page }) => { +it('should work with busted Array.prototype.toJSON', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/41359' } }, async ({ page }) => { await page.evaluateHandle(() => (Array.prototype as any).toJSON = () => '"[]"'); await page.exposeFunction('add', (a, b) => a + b); - await expect(() => page.evaluate(`add(5, 6)`)).rejects.toThrowError('serializedArgs is not an array. This can happen when Array.prototype.toJSON is defined incorrectly'); + expect(await page.evaluate(`add(5, 6)`)).toBe(11); + // The page's own override must still be intact after the binding call. expect.soft(await page.evaluate(() => ([] as any).toJSON())).toBe('"[]"'); });