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 402e055048da..00cab2253f6a 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 @@ -244,6 +244,31 @@ describe('structuredClone', () => { expect(structuredClone(value).name).toBe('Error'); }); + it('clones error causes deeply', () => { + const cause = {reason: 'cause'}; + const value = new Error('error message', {cause}); + const clone = structuredClone(value); + + const falsyCause = new Error('error message', {cause: false}); + const falsyCauseClone = structuredClone(falsyCause); + + const circularCause = new Error('error message'); + circularCause.cause = circularCause; + const circularCauseClone = structuredClone(circularCause); + + expect({ + causeIsCloned: clone.cause !== cause, + causeValue: clone.cause, + falsyCause: falsyCauseClone.cause, + circularCauseIsCloned: circularCauseClone.cause === circularCauseClone, + }).toEqual({ + causeIsCloned: true, + causeValue: cause, + falsyCause: false, + circularCauseIsCloned: true, + }); + }); + it('clones values deeply', () => { const value = { obj: { diff --git a/packages/react-native/src/private/webapis/structuredClone/structuredClone.js b/packages/react-native/src/private/webapis/structuredClone/structuredClone.js index 89a7f90725be..0afc10d42054 100644 --- a/packages/react-native/src/private/webapis/structuredClone/structuredClone.js +++ b/packages/react-native/src/private/webapis/structuredClone/structuredClone.js @@ -27,6 +27,8 @@ const VALID_ERROR_NAMES = new Set([ const BASIC_CONSTRUCTORS = [Number, String, Boolean, Date]; const ObjectPrototype = Object.prototype; +// $FlowFixMe[method-unbinding] this is always called with an explicit receiver. +const hasOwnProperty = Object.prototype.hasOwnProperty; // Technically the memory value should be a parameter in // `structuredCloneInternal` but as an optimization we can reuse the same map @@ -149,11 +151,18 @@ function structuredCloneInternal(value: T): T { } if (value instanceof Error) { - const result = value.cause - ? new Error(value.message, {cause: value.cause}) - : new Error(value.message); + const result = new Error(value.message); memory.set(value, result); + if (hasOwnProperty.call(value, 'cause')) { + Object.defineProperty(result, 'cause', { + configurable: true, + enumerable: false, + value: structuredCloneInternal(value.cause), + writable: true, + }); + } + if (VALID_ERROR_NAMES.has(value.name)) { result.name = value.name; } else {