diff --git a/packages/react-native/__typetests__/globals.tsx b/packages/react-native/__typetests__/globals.tsx index 66dc216176dc..1776431bedd1 100644 --- a/packages/react-native/__typetests__/globals.tsx +++ b/packages/react-native/__typetests__/globals.tsx @@ -7,6 +7,14 @@ const noop = () => { }; +const domException = new DOMException('message', 'DataCloneError'); +const domExceptionCode: number = domException.code; +const dataCloneErrorCode: 25 = DOMException.DATA_CLONE_ERR; +const clonedValue: {nested: {value: number}} = structuredClone({ + nested: {value: 1}, +}); +console.log(domExceptionCode, dataCloneErrorCode, clonedValue.nested.value); + function testInterval() { clearInterval(null); clearInterval(undefined); diff --git a/packages/react-native/flow/bom.js.flow b/packages/react-native/flow/bom.js.flow index 24cf0328eb97..4b75e0ce3634 100644 --- a/packages/react-native/flow/bom.js.flow +++ b/packages/react-native/flow/bom.js.flow @@ -268,6 +268,63 @@ declare class FormData { values(): Iterator; } +declare class DOMException extends Error { + constructor(message?: string, name?: string): void; + readonly code: number; + static readonly INDEX_SIZE_ERR: 1; + static readonly DOMSTRING_SIZE_ERR: 2; + static readonly HIERARCHY_REQUEST_ERR: 3; + static readonly WRONG_DOCUMENT_ERR: 4; + static readonly INVALID_CHARACTER_ERR: 5; + static readonly NO_DATA_ALLOWED_ERR: 6; + static readonly NO_MODIFICATION_ALLOWED_ERR: 7; + static readonly NOT_FOUND_ERR: 8; + static readonly NOT_SUPPORTED_ERR: 9; + static readonly INUSE_ATTRIBUTE_ERR: 10; + static readonly INVALID_STATE_ERR: 11; + static readonly SYNTAX_ERR: 12; + static readonly INVALID_MODIFICATION_ERR: 13; + static readonly NAMESPACE_ERR: 14; + static readonly INVALID_ACCESS_ERR: 15; + static readonly VALIDATION_ERR: 16; + static readonly TYPE_MISMATCH_ERR: 17; + static readonly SECURITY_ERR: 18; + static readonly NETWORK_ERR: 19; + static readonly ABORT_ERR: 20; + static readonly URL_MISMATCH_ERR: 21; + static readonly QUOTA_EXCEEDED_ERR: 22; + static readonly TIMEOUT_ERR: 23; + static readonly INVALID_NODE_TYPE_ERR: 24; + static readonly DATA_CLONE_ERR: 25; + readonly INDEX_SIZE_ERR: 1; + readonly DOMSTRING_SIZE_ERR: 2; + readonly HIERARCHY_REQUEST_ERR: 3; + readonly WRONG_DOCUMENT_ERR: 4; + readonly INVALID_CHARACTER_ERR: 5; + readonly NO_DATA_ALLOWED_ERR: 6; + readonly NO_MODIFICATION_ALLOWED_ERR: 7; + readonly NOT_FOUND_ERR: 8; + readonly NOT_SUPPORTED_ERR: 9; + readonly INUSE_ATTRIBUTE_ERR: 10; + readonly INVALID_STATE_ERR: 11; + readonly SYNTAX_ERR: 12; + readonly INVALID_MODIFICATION_ERR: 13; + readonly NAMESPACE_ERR: 14; + readonly INVALID_ACCESS_ERR: 15; + readonly VALIDATION_ERR: 16; + readonly TYPE_MISMATCH_ERR: 17; + readonly SECURITY_ERR: 18; + readonly NETWORK_ERR: 19; + readonly ABORT_ERR: 20; + readonly URL_MISMATCH_ERR: 21; + readonly QUOTA_EXCEEDED_ERR: 22; + readonly TIMEOUT_ERR: 23; + readonly INVALID_NODE_TYPE_ERR: 24; + readonly DATA_CLONE_ERR: 25; +} + +declare function structuredClone(value: T): T; + declare class DOMRectReadOnly { readonly bottom: number; constructor(x: number, y: number, width: number, height: number): void; diff --git a/packages/react-native/flow/global.js b/packages/react-native/flow/global.js index f7da9a277f85..0071b70e26e9 100644 --- a/packages/react-native/flow/global.js +++ b/packages/react-native/flow/global.js @@ -80,8 +80,10 @@ declare var global: { readonly RN$Bridgeless?: boolean, // setupDOM + readonly DOMException: typeof DOMException, readonly DOMRect: typeof DOMRect, readonly DOMRectReadOnly: typeof DOMRectReadOnly, + readonly structuredClone: typeof structuredClone, // Undeclared properties are implicitly `any`. [string | symbol]: any, diff --git a/packages/react-native/src/private/setup/__tests__/setUpDefaultReactNativeEnvironment-Globals-itest.js b/packages/react-native/src/private/setup/__tests__/setUpDefaultReactNativeEnvironment-Globals-itest.js index 2172cb72b6a4..97d88e555cd0 100644 --- a/packages/react-native/src/private/setup/__tests__/setUpDefaultReactNativeEnvironment-Globals-itest.js +++ b/packages/react-native/src/private/setup/__tests__/setUpDefaultReactNativeEnvironment-Globals-itest.js @@ -109,6 +109,32 @@ describe('setUpDefaultReactNativeEnvironment (globals)', () => { }); describe('Web APIs', () => { + describe('Structured data', () => { + it('should provide DOMException', () => { + const exception = new DOMException('message', 'DataCloneError'); + expect(exception).toBeInstanceOf(Error); + expect(exception.name).toBe('DataCloneError'); + expect(exception.code).toBe(DOMException.DATA_CLONE_ERR); + }); + + it('should provide structuredClone', () => { + const value = {nested: {value: 1}}; + const clone = structuredClone(value); + + expect(clone).toEqual(value); + expect(clone).not.toBe(value); + expect(clone.nested).not.toBe(value.nested); + + let error: unknown; + try { + structuredClone(Symbol()); + } catch (thrownError: unknown) { + error = thrownError; + } + expect(error).toBeInstanceOf(DOMException); + }); + }); + describe('DOM', () => { it('should provide Node', () => { expect(typeof Node).toBe('function'); diff --git a/packages/react-native/src/private/setup/setUpDOM.js b/packages/react-native/src/private/setup/setUpDOM.js index feca88f318c1..717260b1483d 100644 --- a/packages/react-native/src/private/setup/setUpDOM.js +++ b/packages/react-native/src/private/setup/setUpDOM.js @@ -19,6 +19,16 @@ export default function setUpDOM() { initialized = true; + polyfillGlobal( + 'DOMException', + () => require('../webapis/errors/DOMException').default, + ); + + polyfillGlobal( + 'structuredClone', + () => require('../webapis/structuredClone/structuredClone').default, + ); + polyfillGlobal( 'DOMRect', () => require('../webapis/geometry/DOMRect').default, 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 0bede29eb067..357ba6bfb55c 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 @@ -67,6 +67,7 @@ describe('structuredClone', () => { expect(structuredClone(0)).toBe(0); expect(structuredClone(1)).toBe(1); + expect(Object.is(structuredClone(-0), -0)).toBe(true); expect(structuredClone(0n)).toBe(0n); expect(structuredClone(1n)).toBe(1n); @@ -99,6 +100,12 @@ describe('structuredClone', () => { expect(booleanClone).not.toBe(booleanValue); expect(booleanClone).toBeInstanceOf(Boolean); expect(booleanClone.valueOf()).toBe(true); + + const bigintValue = Object(1n); + const bigintClone = structuredClone(bigintValue); + expect(bigintClone).not.toBe(bigintValue); + expect(bigintClone).toBeInstanceOf(BigInt); + expect(bigintClone.valueOf()).toBe(1n); }); it('throws with symbols, functions, WeakMap, WeakSet, Promise', () => { @@ -223,6 +230,58 @@ describe('structuredClone', () => { expect(clone).toEqual(value); }); + it('clones invalid dates', () => { + const value = new Date(NaN); + const clone = structuredClone(value); + expect(clone).not.toBe(value); + expect(clone).toBeInstanceOf(Date); + expect(Number.isNaN(clone.getTime())).toBe(true); + }); + + it('clones ArrayBuffer', () => { + const value = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]).buffer; + const clone = structuredClone(value); + expect(clone).not.toBe(value); + expect(clone).toBeInstanceOf(ArrayBuffer); + expect([...new Uint8Array(clone)]).toEqual([0, 1, 2, 3, 4, 5, 6, 7]); + }); + + it('clones DataView', () => { + const buffer = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]).buffer; + const value = new DataView(buffer); + const clone = structuredClone(value); + expect(clone).not.toBe(value); + expect(clone).toBeInstanceOf(DataView); + expect([...new Uint8Array(clone.buffer)]).toEqual([0, 1, 2, 3, 4, 5, 6, 7]); + }); + + it('clones typed arrays', () => { + const value = new Uint32Array([1, 2, 3]); + const clone = structuredClone(value); + expect(clone).not.toBe(value); + expect(clone).toBeInstanceOf(Uint32Array); + expect([...clone]).toEqual([1, 2, 3]); + }); + + it('preserves shared buffers between views', () => { + const buffer = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]).buffer; + const value = { + typedArray: new Uint16Array(buffer, 2, 2), + dataView: new DataView(buffer, 1, 6), + buffer, + }; + + const clone = structuredClone(value); + expect(clone.buffer).not.toBe(buffer); + expect(clone.dataView.buffer).toBe(clone.buffer); + expect(clone.typedArray.buffer).toBe(clone.buffer); + expect(clone.dataView.byteOffset).toBe(value.dataView.byteOffset); + expect(clone.dataView.byteLength).toBe(value.dataView.byteLength); + expect(clone.typedArray.byteOffset).toBe(value.typedArray.byteOffset); + expect(clone.typedArray.length).toBe(value.typedArray.length); + expect([...clone.typedArray]).toEqual([...value.typedArray]); + }); + it('clones errors', () => { const cause = new Error('cause message'); const value = new Error('error message', {cause}); @@ -261,6 +320,53 @@ describe('structuredClone', () => { expect(structuredClone(value).name).toBe('Error'); }); + it('preserves error subclasses', () => { + expect(structuredClone(new Error('boom'))).toBeInstanceOf(Error); + expect(structuredClone(new EvalError('boom'))).toBeInstanceOf(EvalError); + expect(structuredClone(new RangeError('boom'))).toBeInstanceOf(RangeError); + expect(structuredClone(new ReferenceError('boom'))).toBeInstanceOf( + ReferenceError, + ); + expect(structuredClone(new SyntaxError('boom'))).toBeInstanceOf( + SyntaxError, + ); + expect(structuredClone(new TypeError('boom'))).toBeInstanceOf(TypeError); + expect(structuredClone(new URIError('boom'))).toBeInstanceOf(URIError); + }); + + it('clones aggregate errors', () => { + const innerError = new TypeError('inner'); + const value = new AggregateError([innerError, {foo: 'bar'}], 'outer'); + const clone = structuredClone(value); + const clonedErrors = Array.from(clone.errors); + const clonedInnerError = ensureInstance(clonedErrors[0], TypeError); + + expect(clone).not.toBe(value); + expect(clone).toBeInstanceOf(AggregateError); + expect(clone.message).toBe(value.message); + expect(clone.stack).toBe(value.stack); + expect(clone.errors).not.toBe(value.errors); + expect(clonedInnerError).not.toBe(innerError); + expect(clonedInnerError.message).toBe(innerError.message); + expect(clonedErrors[1]).toEqual({foo: 'bar'}); + }); + + it('clones __proto__ as an own property', () => { + const value = JSON.parse('{"__proto__":{"foo":"bar"}}'); + const clone = structuredClone(value); + const clonedValue = nullthrows( + Object.getOwnPropertyDescriptor(clone, '__proto__'), + ).value; + const originalValue = nullthrows( + Object.getOwnPropertyDescriptor(value, '__proto__'), + ).value; + + expect(clone.foo).toBeUndefined(); + expect(Object.hasOwn(clone, '__proto__')).toBe(true); + expect(clonedValue).not.toBe(originalValue); + expect(clonedValue).toEqual({foo: 'bar'}); + }); + 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..636043dbe4ef 100644 --- a/packages/react-native/src/private/webapis/structuredClone/structuredClone.js +++ b/packages/react-native/src/private/webapis/structuredClone/structuredClone.js @@ -14,16 +14,6 @@ import { isPlatformObject, } from '../webidl/PlatformObjects'; -const VALID_ERROR_NAMES = new Set([ - 'Error', - 'EvalError', - 'RangeError', - 'ReferenceError', - 'SyntaxError', - 'TypeError', - 'URIError', -]); - const BASIC_CONSTRUCTORS = [Number, String, Boolean, Date]; const ObjectPrototype = Object.prototype; @@ -35,7 +25,7 @@ const ObjectPrototype = Object.prototype; // any given point we only have one memory object alive anyway. const memory: Map = new Map(); -function structuredCloneInternal(value: T): T { +function structuredCloneInternal(value: unknown): unknown { // Handles `null` and `undefined`. if (value == null) { return value; @@ -60,21 +50,33 @@ function structuredCloneInternal(value: T): T { } // Handles circular references. - if (memory.has(value)) { - // $FlowExpectedError[incompatible-type] we know memory.get(value) is T - return memory.get(value); + const existingClone = memory.get(value); + if (existingClone !== undefined) { + return existingClone; } // Handles arrays. if (Array.isArray(value)) { - const result = []; + const result: Array = []; memory.set(value, result); - - for (const key of Object.keys(value)) { - result[key] = structuredCloneInternal(value[key]); + const keys = Object.keys(value); + + for (let index = 0; index < keys.length; index++) { + const key = keys[index]; + const innerValue = value[key]; + switch (typeof innerValue) { + case 'undefined': + case 'boolean': + case 'number': + case 'string': + case 'bigint': + result[key] = innerValue; + break; + default: + result[key] = structuredCloneInternal(innerValue); + } } - // $FlowExpectedError[incompatible-type] we know result is T return result; } @@ -83,23 +85,73 @@ function structuredCloneInternal(value: T): T { if (Object.getPrototypeOf(value) === ObjectPrototype) { const result = {}; memory.set(value, result); - - for (const key of Object.keys(value)) { - // $FlowExpectedError[prop-missing] - result[key] = structuredCloneInternal(value[key]); + const keys = Object.keys(value); + + if (!Object.hasOwn(value, '__proto__')) { + for (let index = 0; index < keys.length; index++) { + const key = keys[index]; + const innerValue = value[key]; + switch (typeof innerValue) { + case 'undefined': + case 'boolean': + case 'number': + case 'string': + case 'bigint': + // $FlowExpectedError[prop-missing] + result[key] = innerValue; + break; + default: + // $FlowExpectedError[prop-missing] + result[key] = structuredCloneInternal(innerValue); + } + } + } else { + for (const key of keys) { + const clonedValue = structuredCloneInternal(value[key]); + if (key === '__proto__') { + defineEnumerableProperty(result, key, clonedValue); + } else { + // $FlowExpectedError[prop-missing] + result[key] = clonedValue; + } + } } - // $FlowExpectedError[incompatible-type] we know result is T return result; } // Handles complex types (typeof === 'object'). + if (value instanceof ArrayBuffer) { + return cloneArrayBuffer(value); + } + + if (value instanceof DataView) { + const result = new DataView( + cloneArrayBuffer(value.buffer), + value.byteOffset, + value.byteLength, + ); + memory.set(value, result); + return result; + } + + if (isTypedArray(value)) { + const result = cloneTypedArray(value); + memory.set(value, result); + return result; + } + + if (value instanceof BigInt) { + const result = Object(value.valueOf()); + memory.set(value, result); + return result; + } + 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; } } @@ -115,7 +167,6 @@ function structuredCloneInternal(value: T): T { ); } - // $FlowExpectedError[incompatible-type] we know result is T return result; } @@ -127,7 +178,6 @@ function structuredCloneInternal(value: T): T { result.add(structuredCloneInternal(innerValue)); } - // $FlowExpectedError[incompatible-type] we know result is T return result; } @@ -135,7 +185,6 @@ function structuredCloneInternal(value: T): T { const result = new RegExp(value.source, value.flags); memory.set(value, result); - // $FlowExpectedError[incompatible-type] we know result is T return result; } @@ -148,21 +197,25 @@ function structuredCloneInternal(value: T): T { return result; } - if (value instanceof Error) { - const result = value.cause - ? new Error(value.message, {cause: value.cause}) - : new Error(value.message); + if (value instanceof AggregateError) { + const result = new AggregateError([], value.message); memory.set(value, result); - if (VALID_ERROR_NAMES.has(value.name)) { - result.name = value.name; - } else { - result.name = 'Error'; + const errors = structuredCloneInternal(value.errors); + if (!Array.isArray(errors)) { + throw new TypeError('AggregateError errors must be an array'); } + result.errors = errors; + + cloneErrorProperties(value, result); + return result; + } - result.stack = value.stack; + if (value instanceof Error) { + const result = createErrorClone(value); + memory.set(value, result); - // $FlowExpectedError[incompatible-type] we know result is T + cloneErrorProperties(value, result); return result; } @@ -177,18 +230,43 @@ function structuredCloneInternal(value: T): T { // Arbitrary object slow path const result = {}; memory.set(value, result); + const keys = Object.keys(value); // We need to use Object.keys instead of iterating by indices because we // also need to copy arbitrary fields set in the array. - for (const key of Object.keys(value)) { - // $FlowExpectedError[prop-missing] - result[key] = structuredCloneInternal(value[key]); + if (Object.hasOwn(value, '__proto__')) { + for (const key of keys) { + const clonedValue = structuredCloneInternal(value[key]); + if (key === '__proto__') { + defineEnumerableProperty(result, key, clonedValue); + } else { + // $FlowExpectedError[prop-missing] + result[key] = clonedValue; + } + } + } else { + for (let index = 0; index < keys.length; index++) { + const key = keys[index]; + // $FlowExpectedError[prop-missing] + result[key] = structuredCloneInternal(value[key]); + } } - // $FlowExpectedError[incompatible-type] we know result is T return result; } +function cloneErrorProperties(value: Error, result: Error): void { + if (Object.hasOwn(value, 'cause')) { + Object.defineProperty(result, 'cause', { + configurable: true, + value: structuredCloneInternal(value.cause), + writable: true, + }); + } + + result.stack = value.stack; +} + /** * Basic implementation of `structuredClone`. * See: @@ -209,6 +287,7 @@ function structuredCloneInternal(value: T): T { */ export default function structuredClone(value: T): T { try { + // $FlowExpectedError[incompatible-type] structured cloning preserves the value's serializable type. return structuredCloneInternal(value); } finally { memory.clear(); @@ -217,6 +296,98 @@ export default function structuredClone(value: T): T { const NON_SERIALIZABLE_OBJECT_KEY = Symbol('nonSerializableObject'); +function cloneArrayBuffer(value: ArrayBuffer): ArrayBuffer { + const existingClone = memory.get(value); + if (existingClone instanceof ArrayBuffer) { + return existingClone; + } + + const result = value.slice(0); + memory.set(value, result); + return result; +} + +function cloneTypedArray(value: $TypedArray): $TypedArray { + const buffer = cloneArrayBuffer(value.buffer); + const {byteOffset, length} = value; + + if (value instanceof Int8Array) { + return new Int8Array(buffer, byteOffset, length); + } + if (value instanceof Uint8Array) { + return new Uint8Array(buffer, byteOffset, length); + } + if (value instanceof Uint8ClampedArray) { + return new Uint8ClampedArray(buffer, byteOffset, length); + } + if (value instanceof Int16Array) { + return new Int16Array(buffer, byteOffset, length); + } + if (value instanceof Uint16Array) { + return new Uint16Array(buffer, byteOffset, length); + } + if (value instanceof Int32Array) { + return new Int32Array(buffer, byteOffset, length); + } + if (value instanceof Uint32Array) { + return new Uint32Array(buffer, byteOffset, length); + } + if (typeof Float16Array !== 'undefined' && value instanceof Float16Array) { + return new Float16Array(buffer, byteOffset, length); + } + if (value instanceof Float32Array) { + return new Float32Array(buffer, byteOffset, length); + } + if (value instanceof Float64Array) { + return new Float64Array(buffer, byteOffset, length); + } + if (value instanceof BigInt64Array) { + return new BigInt64Array(buffer, byteOffset, length); + } + if (value instanceof BigUint64Array) { + return new BigUint64Array(buffer, byteOffset, length); + } + + throw new TypeError('Unsupported typed array'); +} + +function defineEnumerableProperty( + target: interface {}, + key: string, + value: unknown, +): void { + Object.defineProperty(target, key, { + configurable: true, + enumerable: true, + value, + writable: true, + }); +} + +declare function isTypedArray(value: unknown): implies value is $TypedArray; +function isTypedArray(value: unknown): boolean { + return ArrayBuffer.isView(value) && !(value instanceof DataView); +} + +function createErrorClone(value: Error): Error { + switch (value.name) { + case 'EvalError': + return new EvalError(value.message); + case 'RangeError': + return new RangeError(value.message); + case 'ReferenceError': + return new ReferenceError(value.message); + case 'SyntaxError': + return new SyntaxError(value.message); + case 'TypeError': + return new TypeError(value.message); + case 'URIError': + return new URIError(value.message); + default: + return new Error(value.message); + } +} + function isNonSerializableObject(obj: T): boolean { // $FlowExpectedError[invalid-in-lhs] return NON_SERIALIZABLE_OBJECT_KEY in obj; diff --git a/packages/react-native/src/types/globals.d.ts b/packages/react-native/src/types/globals.d.ts index f9820c6fc5b4..2e5bf77c5786 100644 --- a/packages/react-native/src/types/globals.d.ts +++ b/packages/react-native/src/types/globals.d.ts @@ -57,6 +57,69 @@ declare global { const HermesInternal: null | {}; + interface DOMException extends Error { + readonly code: number; + readonly message: string; + readonly name: string; + readonly INDEX_SIZE_ERR: 1; + readonly DOMSTRING_SIZE_ERR: 2; + readonly HIERARCHY_REQUEST_ERR: 3; + readonly WRONG_DOCUMENT_ERR: 4; + readonly INVALID_CHARACTER_ERR: 5; + readonly NO_DATA_ALLOWED_ERR: 6; + readonly NO_MODIFICATION_ALLOWED_ERR: 7; + readonly NOT_FOUND_ERR: 8; + readonly NOT_SUPPORTED_ERR: 9; + readonly INUSE_ATTRIBUTE_ERR: 10; + readonly INVALID_STATE_ERR: 11; + readonly SYNTAX_ERR: 12; + readonly INVALID_MODIFICATION_ERR: 13; + readonly NAMESPACE_ERR: 14; + readonly INVALID_ACCESS_ERR: 15; + readonly VALIDATION_ERR: 16; + readonly TYPE_MISMATCH_ERR: 17; + readonly SECURITY_ERR: 18; + readonly NETWORK_ERR: 19; + readonly ABORT_ERR: 20; + readonly URL_MISMATCH_ERR: 21; + readonly QUOTA_EXCEEDED_ERR: 22; + readonly TIMEOUT_ERR: 23; + readonly INVALID_NODE_TYPE_ERR: 24; + readonly DATA_CLONE_ERR: 25; + } + + var DOMException: { + prototype: DOMException; + new (message?: string, name?: string): DOMException; + readonly INDEX_SIZE_ERR: 1; + readonly DOMSTRING_SIZE_ERR: 2; + readonly HIERARCHY_REQUEST_ERR: 3; + readonly WRONG_DOCUMENT_ERR: 4; + readonly INVALID_CHARACTER_ERR: 5; + readonly NO_DATA_ALLOWED_ERR: 6; + readonly NO_MODIFICATION_ALLOWED_ERR: 7; + readonly NOT_FOUND_ERR: 8; + readonly NOT_SUPPORTED_ERR: 9; + readonly INUSE_ATTRIBUTE_ERR: 10; + readonly INVALID_STATE_ERR: 11; + readonly SYNTAX_ERR: 12; + readonly INVALID_MODIFICATION_ERR: 13; + readonly NAMESPACE_ERR: 14; + readonly INVALID_ACCESS_ERR: 15; + readonly VALIDATION_ERR: 16; + readonly TYPE_MISMATCH_ERR: 17; + readonly SECURITY_ERR: 18; + readonly NETWORK_ERR: 19; + readonly ABORT_ERR: 20; + readonly URL_MISMATCH_ERR: 21; + readonly QUOTA_EXCEEDED_ERR: 22; + readonly TIMEOUT_ERR: 23; + readonly INVALID_NODE_TYPE_ERR: 24; + readonly DATA_CLONE_ERR: 25; + }; + + function structuredClone(value: T): T; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMRectReadOnly) */ interface DOMRectReadOnly { /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMRectReadOnly/bottom) */