diff --git a/.changeset/snapshot-own-proto-key.md b/.changeset/snapshot-own-proto-key.md new file mode 100644 index 0000000000..9d99362c1e --- /dev/null +++ b/.changeset/snapshot-own-proto-key.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: keep an own `__proto__` key in `$state.snapshot` diff --git a/packages/svelte/src/internal/shared/clone.js b/packages/svelte/src/internal/shared/clone.js index ba0c731047..11f02d1a24 100644 --- a/packages/svelte/src/internal/shared/clone.js +++ b/packages/svelte/src/internal/shared/clone.js @@ -1,7 +1,7 @@ /** @import { Snapshot } from './types' */ import { DEV } from 'esm-env'; import * as w from './warnings.js'; -import { get_prototype_of, is_array, object_prototype } from './utils.js'; +import { define_property, get_prototype_of, is_array, object_prototype } from './utils.js'; /** * In dev, we keep track of which properties could not be cloned. In prod @@ -90,7 +90,7 @@ function clone(value, cloned, path, paths, original = null, no_tojson = false) { } for (var key of Object.keys(value)) { - copy[key] = clone( + var cloned_value = clone( // @ts-expect-error value[key], cloned, @@ -99,6 +99,22 @@ function clone(value, cloned, path, paths, original = null, no_tojson = false) { null, no_tojson ); + + if (key === '__proto__') { + // Assigning `__proto__` runs the setter inherited from `Object.prototype` + // rather than creating a property, so the key would be dropped and an + // object value would become the copy's prototype, leaving the snapshot + // inheriting fields that were data. `structuredClone`, which this + // function falls back to below, keeps it as an own property. + define_property(copy, key, { + value: cloned_value, + writable: true, + enumerable: true, + configurable: true + }); + } else { + copy[key] = cloned_value; + } } return copy; diff --git a/packages/svelte/tests/runtime-runes/samples/state-snapshot-proto-key/_config.js b/packages/svelte/tests/runtime-runes/samples/state-snapshot-proto-key/_config.js new file mode 100644 index 0000000000..d589156c07 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/state-snapshot-proto-key/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + html: `
["__proto__","b"]
true
` +}); diff --git a/packages/svelte/tests/runtime-runes/samples/state-snapshot-proto-key/main.svelte b/packages/svelte/tests/runtime-runes/samples/state-snapshot-proto-key/main.svelte new file mode 100644 index 0000000000..6e95cf2eca --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/state-snapshot-proto-key/main.svelte @@ -0,0 +1,11 @@ + + +
{JSON.stringify(Object.keys(snapshot))}
+
{Object.getPrototypeOf(snapshot) === Object.prototype}
+
{snapshot.admin}