diff --git a/.changeset/tidy-starfishes-allow.md b/.changeset/tidy-starfishes-allow.md new file mode 100644 index 0000000000..64d64eac9a --- /dev/null +++ b/.changeset/tidy-starfishes-allow.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: only reuse state proxies that belong to the current value diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js index 8add490a10..07003a193d 100644 --- a/packages/svelte/src/internal/client/proxy.js +++ b/packages/svelte/src/internal/client/proxy.js @@ -34,7 +34,11 @@ export const READONLY_SYMBOL = Symbol('readonly'); export function proxy(value, immutable = true) { if (typeof value === 'object' && value != null && !is_frozen(value)) { if (STATE_SYMBOL in value) { - return /** @type {import('./types.js').ProxyMetadata} */ (value[STATE_SYMBOL]).p; + const metadata = /** @type {import('./types.js').ProxyMetadata} */ (value[STATE_SYMBOL]); + // Check that the incoming value is the same proxy that this state symbol was created for: + // If someone copies over the state symbol to a new object (using Reflect.ownKeys) the referenced + // proxy could be stale and we should not return it. + if (metadata.t === value || metadata.p === value) return metadata.p; } const prototype = get_prototype_of(value); @@ -51,7 +55,8 @@ export function proxy(value, immutable = true) { /** @type {import('./types.js').ProxyStateObject} */ (proxy), immutable ), - writable: false + writable: true, + enumerable: false }); return proxy; @@ -68,7 +73,7 @@ export function proxy(value, immutable = true) { * @returns {Record} */ function unwrap(value, already_unwrapped = new Map()) { - if (typeof value === 'object' && value != null && !is_frozen(value) && STATE_SYMBOL in value) { + if (typeof value === 'object' && value != null && STATE_SYMBOL in value) { const unwrapped = already_unwrapped.get(value); if (unwrapped !== undefined) { return unwrapped; @@ -100,6 +105,7 @@ function unwrap(value, already_unwrapped = new Map()) { return obj; } } + return value; } @@ -124,7 +130,8 @@ function init(value, proxy, immutable) { v: source(0), a: is_array(value), i: immutable, - p: proxy + p: proxy, + t: value }; } @@ -171,6 +178,10 @@ const state_proxy_handler = { if (DEV && prop === READONLY_SYMBOL) { return Reflect.get(target, READONLY_SYMBOL); } + if (prop === STATE_SYMBOL) { + return Reflect.get(target, STATE_SYMBOL); + } + const metadata = target[STATE_SYMBOL]; let s = metadata.s.get(prop); @@ -304,7 +315,13 @@ if (DEV) { */ export function readonly(value) { const proxy = value && value[READONLY_SYMBOL]; - if (proxy) return proxy; + if (proxy) { + const metadata = value[STATE_SYMBOL]; + // Check that the incoming value is the same proxy that this readonly symbol was created for: + // If someone copies over the readonly symbol to a new object (using Reflect.ownKeys) the referenced + // proxy could be stale and we should not return it. + if (metadata.p === value) return proxy; + } if ( typeof value === 'object' && diff --git a/packages/svelte/src/internal/client/types.d.ts b/packages/svelte/src/internal/client/types.d.ts index 38f76ed7c1..bcc0401381 100644 --- a/packages/svelte/src/internal/client/types.d.ts +++ b/packages/svelte/src/internal/client/types.d.ts @@ -410,6 +410,8 @@ export interface ProxyMetadata> { i: boolean; /** The associated proxy */ p: ProxyStateObject | ProxyReadonlyObject; + /** The original target this proxy was created for */ + t: T; } export type ProxyStateObject> = T & { diff --git a/packages/svelte/tests/runtime-runes/samples/state-reuse/_config.js b/packages/svelte/tests/runtime-runes/samples/state-reuse/_config.js new file mode 100644 index 0000000000..ebf98bd8d4 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/state-reuse/_config.js @@ -0,0 +1,16 @@ +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true + }, + + html: ``, + + async test({ assert, target }) { + const btn = target.querySelector('button'); + + await btn?.click(); + assert.htmlEqual(target.innerHTML, ``); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/state-reuse/main.svelte b/packages/svelte/tests/runtime-runes/samples/state-reuse/main.svelte new file mode 100644 index 0000000000..eda330f088 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/state-reuse/main.svelte @@ -0,0 +1,28 @@ + + + + +