fix: widen ownership upon property access if necessary (#13175)

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.

Fixes #13137
pull/13198/head
Simon H 2 months ago committed by GitHub
parent fd7e8b70cf
commit 3808935f51
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: widen ownership upon property access if necessary

@ -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 bar. 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 metadata 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;
}

@ -0,0 +1,13 @@
<script>
import Component2 from './Component2.svelte';
let { rows = $bindable([]) } = $props();
let rows2 = $state([]);
$effect(() => {
rows2 = rows.slice();
});
</script>
<Component2 bind:rows={rows2} />

@ -0,0 +1,7 @@
<script>
let { rows = $bindable([]) } = $props();
</script>
{#if rows.length}
<input type="checkbox" bind:checked={rows[0].check} />
{/if}

@ -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: []
});

@ -0,0 +1,7 @@
<script>
import Component1 from './Component1.svelte';
let rows = $state([{}]);
</script>
<Component1 bind:rows />
Loading…
Cancel
Save