Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/react-native/__typetests__/globals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
57 changes: 57 additions & 0 deletions packages/react-native/flow/bom.js.flow
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,63 @@ declare class FormData {
values(): Iterator<FormDataEntryValue>;
}

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<T>(value: T): T;

declare class DOMRectReadOnly {
readonly bottom: number;
constructor(x: number, y: number, width: number, height: number): void;
Expand Down
2 changes: 2 additions & 0 deletions packages/react-native/flow/global.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
10 changes: 10 additions & 0 deletions packages/react-native/src/private/setup/setUpDOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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});
Expand Down Expand Up @@ -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: {
Expand Down
Loading
Loading