diff --git a/packages/react-native/src/private/webapis/structuredClone/__tests__/structuredClone-itest.js b/packages/react-native/src/private/webapis/structuredClone/__tests__/structuredClone-itest.js index 402e055048d..93fb418d58a 100644 --- a/packages/react-native/src/private/webapis/structuredClone/__tests__/structuredClone-itest.js +++ b/packages/react-native/src/private/webapis/structuredClone/__tests__/structuredClone-itest.js @@ -62,8 +62,18 @@ describe('structuredClone', () => { }); it('clones primitive value wrappers', () => { + const throwUnexpectedCoercion = () => { + throw new Error('Unexpected coercion'); + }; + const preventCoercion = (value: interface {}) => { + Object.defineProperty(value, Symbol.toPrimitive, { + value: throwUnexpectedCoercion, + }); + }; + // eslint-disable-next-line no-new-wrappers const numberValue = new Number(1); + preventCoercion(numberValue); const numberClone = structuredClone(numberValue); expect(numberClone).not.toBe(numberValue); expect(numberClone).toBeInstanceOf(Number); @@ -71,17 +81,18 @@ describe('structuredClone', () => { // eslint-disable-next-line no-new-wrappers const stringValue = new String('foo'); + preventCoercion(stringValue); const stringClone = structuredClone(stringValue); expect(stringClone).not.toBe(stringValue); expect(stringClone).toBeInstanceOf(String); expect(stringClone.valueOf()).toBe('foo'); // eslint-disable-next-line no-new-wrappers - const booleanValue = new Boolean(true); + const booleanValue = new Boolean(false); const booleanClone = structuredClone(booleanValue); expect(booleanClone).not.toBe(booleanValue); expect(booleanClone).toBeInstanceOf(Boolean); - expect(booleanClone.valueOf()).toBe(true); + expect(booleanClone.valueOf()).toBe(false); }); it('throws with symbols, functions, WeakMap, WeakSet, Promise', () => { @@ -200,6 +211,11 @@ describe('structuredClone', () => { it('clones dates', () => { const value = new Date('1993-06-11T14:30:45.123Z'); + Object.defineProperty(value, Symbol.toPrimitive, { + value() { + throw new Error('Unexpected coercion'); + }, + }); const clone = structuredClone(value); expect(clone).not.toBe(value); expect(clone).toBeInstanceOf(Date); diff --git a/packages/react-native/src/private/webapis/structuredClone/structuredClone.js b/packages/react-native/src/private/webapis/structuredClone/structuredClone.js index 89a7f90725b..dce16a83303 100644 --- a/packages/react-native/src/private/webapis/structuredClone/structuredClone.js +++ b/packages/react-native/src/private/webapis/structuredClone/structuredClone.js @@ -24,9 +24,15 @@ const VALID_ERROR_NAMES = new Set([ 'URIError', ]); -const BASIC_CONSTRUCTORS = [Number, String, Boolean, Date]; - const ObjectPrototype = Object.prototype; +// $FlowFixMe[method-unbinding] this is always called with an explicit receiver. +const numberValueOf = Number.prototype.valueOf; +// $FlowFixMe[method-unbinding] this is always called with an explicit receiver. +const stringValueOf = String.prototype.valueOf; +// $FlowFixMe[method-unbinding] this is always called with an explicit receiver. +const booleanValueOf = Boolean.prototype.valueOf; +// $FlowFixMe[method-unbinding] this is always called with an explicit receiver. +const dateValueOf = Date.prototype.valueOf; // Technically the memory value should be a parameter in // `structuredCloneInternal` but as an optimization we can reuse the same map @@ -95,13 +101,35 @@ function structuredCloneInternal(value: T): T { // Handles complex types (typeof === 'object'). - for (const Cls of BASIC_CONSTRUCTORS) { - if (value instanceof Cls) { - const result = new Cls(value); - memory.set(value, result); - // $FlowExpectedError[incompatible-type] we know result is T - return result; - } + if (value instanceof Number) { + // eslint-disable-next-line no-new-wrappers + const result = new Number(numberValueOf.call(value)); + memory.set(value, result); + // $FlowExpectedError[incompatible-type] we know result is T + return result; + } + + if (value instanceof String) { + // eslint-disable-next-line no-new-wrappers + const result = new String(stringValueOf.call(value)); + memory.set(value, result); + // $FlowExpectedError[incompatible-type] we know result is T + return result; + } + + if (value instanceof Boolean) { + // eslint-disable-next-line no-new-wrappers + const result = new Boolean(booleanValueOf.call(value)); + memory.set(value, result); + // $FlowExpectedError[incompatible-type] we know result is T + return result; + } + + if (value instanceof Date) { + const result = new Date(dateValueOf.call(value)); + memory.set(value, result); + // $FlowExpectedError[incompatible-type] we know result is T + return result; } if (value instanceof Map) {