diff --git a/.changeset/five-birds-check.md b/.changeset/five-birds-check.md new file mode 100644 index 0000000000..edf46bff53 --- /dev/null +++ b/.changeset/five-birds-check.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: widen ownership upon property access if necessary diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js index 48d6df4d30..13367ec2fd 100644 --- a/packages/svelte/src/internal/client/proxy.js +++ b/packages/svelte/src/internal/client/proxy.js @@ -131,6 +131,22 @@ export function proxy(value, parent = null, prev) { if (s !== undefined) { var v = get(s); + + // In case of something like `foo = bar.map(...)`, foo would have ownership + // of the array itself, while the individual items would have ownership + // of the component that created the proxy. That means if we later do + // `foo[0].baz = 42`, we could get a false-positive ownership violation, + // since the two proxies are not connected to each other via the parent + // relationship. For this reason, we need to widen the ownership of the + // children upon access when we detect they are not connected. + if (DEV) { + /** @type {ProxyMetadata | undefined} */ + var prop_metadata = v?.[STATE_SYMBOL_METADATA]; + if (prop_metadata && prop_metadata?.parent !== metadata) { + widen_ownership(metadata, prop_metadata); + } + } + return v === UNINITIALIZED ? undefined : v; } diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-7/Component1.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-7/Component1.svelte new file mode 100644 index 0000000000..1db07ddbd0 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-7/Component1.svelte @@ -0,0 +1,13 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-7/Component2.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-7/Component2.svelte new file mode 100644 index 0000000000..8d63b9e9a9 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-7/Component2.svelte @@ -0,0 +1,7 @@ + + +{#if rows.length} + +{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-7/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-7/_config.js new file mode 100644 index 0000000000..e766a946d0 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-7/_config.js @@ -0,0 +1,22 @@ +import { flushSync } from 'svelte'; +import { ok, test } from '../../test'; + +// Tests that proxies widen ownership correctly even if not directly connected to each other +export default test({ + compileOptions: { + dev: true + }, + + test({ assert, target, warnings }) { + const input = target.querySelector('input'); + ok(input); + + input.checked = true; + input.dispatchEvent(new Event('input', { bubbles: true })); + flushSync(); + + assert.deepEqual(warnings, []); + }, + + warnings: [] +}); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-7/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-7/main.svelte new file mode 100644 index 0000000000..60a9b19a05 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-7/main.svelte @@ -0,0 +1,7 @@ + + +