Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/

import DOMException from '../../errors/DOMException';

// Snapshot the built-in prototypes before the first evaluation of the
// module under test. `structuredClone` is required (not imported) below so
// its top-level code runs after this snapshot, which is what lets the
// no-mutation regression test detect writes to the built-in prototypes.
const PROTOTYPE_SYMBOLS = {
WeakMap: Object.getOwnPropertySymbols(WeakMap.prototype),
WeakSet: Object.getOwnPropertySymbols(WeakSet.prototype),
Promise: Object.getOwnPropertySymbols(Promise.prototype),
};

function expectDataCloneError(fn: () => unknown) {
try {
fn();
} catch (error) {
expect(error).toBeInstanceOf(DOMException);
expect(error.name).toBe('DataCloneError');
return;
}

throw new Error('Expected function to throw DataCloneError, but it did not');
}

describe('structuredClone', () => {
it('does not add properties to built-in prototypes', () => {
require('../structuredClone');

expect(Object.getOwnPropertySymbols(WeakMap.prototype)).toEqual(
PROTOTYPE_SYMBOLS.WeakMap,
);
expect(Object.getOwnPropertySymbols(WeakSet.prototype)).toEqual(
PROTOTYPE_SYMBOLS.WeakSet,
);
expect(Object.getOwnPropertySymbols(Promise.prototype)).toEqual(
PROTOTYPE_SYMBOLS.Promise,
);
});

it('throws DataCloneError for non-serializable built-ins', () => {
const {default: structuredClone} = require('../structuredClone');

expectDataCloneError(() => structuredClone(new WeakMap()));
expectDataCloneError(() => structuredClone(new WeakSet()));
expectDataCloneError(() => structuredClone(new Promise(() => {})));
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -295,8 +295,6 @@ export default function structuredClone<T>(value: T): T {
}
}

const NON_SERIALIZABLE_OBJECT_KEY = Symbol('nonSerializableObject');

function cloneArrayBuffer(value: ArrayBuffer): ArrayBuffer {
const existingClone = memory.get(value);
if (existingClone instanceof ArrayBuffer) {
Expand Down Expand Up @@ -392,16 +390,11 @@ function createErrorClone(value: Error): Error {
}

function isNonSerializableObject<T extends interface {}>(obj: T): boolean {
// $FlowExpectedError[invalid-in-lhs]
return NON_SERIALIZABLE_OBJECT_KEY in obj;
}

function markClassAsNonSerializable<T>(cls: Class<T>): void {
// $FlowExpectedError[incompatible-use]
cls.prototype[NON_SERIALIZABLE_OBJECT_KEY] = true;
// Built-in prototypes must not be written to because Hardened JS
// environments (SES) freeze them, so the non-serializable
// built-ins are detected with `instanceof` instead of marking the
// prototypes with e.g. Symbol('nonSerializableObject').
return (
obj instanceof WeakMap || obj instanceof WeakSet || obj instanceof Promise
);
}

// Non-serializable built-ins.
markClassAsNonSerializable(WeakMap);
markClassAsNonSerializable(WeakSet);
markClassAsNonSerializable(Promise);
Loading