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
Expand Up @@ -100,6 +100,39 @@ describe('structuredClone', () => {
expect(clone).toEqual(value);
});

it('clones __proto__ as an own data property', () => {
const propertyValue = {key: 'value'};
const objectValue = {['__proto__']: propertyValue};
const arrayValue: Array<unknown> = [];
// $FlowExpectedError[cannot-write] the property is defined, not the prototype.
Object.defineProperty(arrayValue, '__proto__', {
configurable: true,
enumerable: true,
value: propertyValue,
writable: true,
});

function expectClonedDataProperty(
value: interface {},
expectedPrototype: interface {},
) {
const clone = structuredClone(value);
const property = Object.getOwnPropertyDescriptor(clone, '__proto__');

expect(Object.getPrototypeOf(clone)).toBe(expectedPrototype);
expect(property).toEqual({
configurable: true,
enumerable: true,
value: propertyValue,
writable: true,
});
expect(property?.value).not.toBe(propertyValue);
}

expectClonedDataProperty(objectValue, Object.prototype);
expectClonedDataProperty(arrayValue, Array.prototype);
});

it('does NOT clone non-enumerable properties', () => {
const value = {foo: 'bar'};
// $FlowExpectedError[prop-missing]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,25 @@ const ObjectPrototype = Object.prototype;
// any given point we only have one memory object alive anyway.
const memory: Map<unknown, unknown> = new Map();

function setClonedProperty(
target: interface {},
key: string,
value: unknown,
): void {
if (key === '__proto__') {
// $FlowExpectedError[cannot-write] the property is defined, not the prototype.
Object.defineProperty(target, key, {
configurable: true,
enumerable: true,
value,
writable: true,
});
} else {
// $FlowExpectedError[prop-missing]
target[key] = value;
}
}

function structuredCloneInternal<T>(value: T): T {
// Handles `null` and `undefined`.
if (value == null) {
Expand Down Expand Up @@ -67,11 +86,11 @@ function structuredCloneInternal<T>(value: T): T {

// Handles arrays.
if (Array.isArray(value)) {
const result = [];
const result: Array<unknown> = [];
memory.set(value, result);

for (const key of Object.keys(value)) {
result[key] = structuredCloneInternal(value[key]);
setClonedProperty(result, key, structuredCloneInternal(value[key]));
}

// $FlowExpectedError[incompatible-type] we know result is T
Expand All @@ -85,8 +104,7 @@ function structuredCloneInternal<T>(value: T): T {
memory.set(value, result);

for (const key of Object.keys(value)) {
// $FlowExpectedError[prop-missing]
result[key] = structuredCloneInternal(value[key]);
setClonedProperty(result, key, structuredCloneInternal(value[key]));
}

// $FlowExpectedError[incompatible-type] we know result is T
Expand Down Expand Up @@ -181,8 +199,7 @@ function structuredCloneInternal<T>(value: T): T {
// 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]);
setClonedProperty(result, key, structuredCloneInternal(value[key]));
}

// $FlowExpectedError[incompatible-type] we know result is T
Expand Down
Loading