Skip to content

Commit 6e5ab2e

Browse files
authored
fix: prevent ownership validation from infering with component context (#11438)
Ownership validation had a false positive when rendering a component as slotted content of another component. To fix this, #11401 did set the current component context to the context the snippet was declared in, not to the context it is rendered in. This was flawed because it means that component context was altered in a way that setContext/getContext failed because the parent chain was incorrect. This fixes that by introducing a separate global (dev time only) which tracks the component function the ownership needs. fixes #11429
1 parent dfb30aa commit 6e5ab2e

File tree

9 files changed

+73
-25
lines changed

9 files changed

+73
-25
lines changed

.changeset/two-candles-move.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
fix: prevent ownership validation from infering with component context

packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -746,12 +746,7 @@ function serialize_inline_component(node, component_name, context) {
746746

747747
if (context.state.options.dev) {
748748
binding_initializers.push(
749-
b.stmt(
750-
b.call(
751-
b.id('$.user_pre_effect'),
752-
b.thunk(b.call(b.id('$.add_owner'), expression, b.id(component_name)))
753-
)
754-
)
749+
b.stmt(b.call(b.id('$.add_owner_effect'), b.thunk(expression), b.id(component_name)))
755750
);
756751
}
757752

packages/svelte/src/internal/client/dev/ownership.js

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/** @typedef {{ file: string, line: number, column: number }} Location */
22

33
import { STATE_SYMBOL } from '../constants.js';
4-
import { render_effect } from '../reactivity/effects.js';
5-
import { current_component_context, untrack } from '../runtime.js';
4+
import { render_effect, user_pre_effect } from '../reactivity/effects.js';
5+
import { dev_current_component_function, set_dev_current_component_function } from '../runtime.js';
66
import { get_prototype_of } from '../utils.js';
77
import * as w from '../warnings.js';
88

@@ -109,8 +109,7 @@ export function mark_module_end(component) {
109109
*/
110110
export function add_owner(object, owner, global = false) {
111111
if (object && !global) {
112-
// @ts-expect-error
113-
const component = current_component_context.function;
112+
const component = dev_current_component_function;
114113
const metadata = object[STATE_SYMBOL];
115114
if (metadata && !has_owner(metadata, component)) {
116115
let original = get_owner(metadata);
@@ -124,6 +123,20 @@ export function add_owner(object, owner, global = false) {
124123
add_owner_to_object(object, owner, new Set());
125124
}
126125

126+
/**
127+
* @param {() => unknown} get_object
128+
* @param {any} Component
129+
*/
130+
export function add_owner_effect(get_object, Component) {
131+
var component = dev_current_component_function;
132+
user_pre_effect(() => {
133+
var prev = dev_current_component_function;
134+
set_dev_current_component_function(component);
135+
add_owner(get_object(), Component);
136+
set_dev_current_component_function(prev);
137+
});
138+
}
139+
127140
/**
128141
* @param {import('#client').ProxyMetadata<any> | null} from
129142
* @param {import('#client').ProxyMetadata<any>} to

packages/svelte/src/internal/client/dom/blocks/snippet.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import { add_snippet_symbol } from '../../../shared/validate.js';
22
import { EFFECT_TRANSPARENT } from '../../constants.js';
33
import { branch, block, destroy_effect } from '../../reactivity/effects.js';
4-
import { current_component_context, set_current_component_context } from '../../runtime.js';
4+
import {
5+
current_component_context,
6+
dev_current_component_function,
7+
set_dev_current_component_function
8+
} from '../../runtime.js';
59

610
/**
711
* @template {(node: import('#client').TemplateNode, ...args: any[]) => import('#client').Dom} SnippetFn
@@ -38,17 +42,17 @@ export function snippet(get_snippet, node, ...args) {
3842
* @returns
3943
*/
4044
export function wrap_snippet(fn) {
41-
let component = current_component_context;
45+
let component = /** @type {import('#client').ComponentContext} */ (current_component_context);
4246

4347
return add_snippet_symbol(
4448
(/** @type {import('#client').TemplateNode} */ node, /** @type {any[]} */ ...args) => {
45-
var previous_component_context = current_component_context;
46-
set_current_component_context(component);
49+
var previous_component_function = dev_current_component_function;
50+
set_dev_current_component_function(component.function);
4751

4852
try {
4953
return fn(node, ...args);
5054
} finally {
51-
set_current_component_context(previous_component_context);
55+
set_dev_current_component_function(previous_component_function);
5256
}
5357
}
5458
);

packages/svelte/src/internal/client/index.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
export { hmr } from './dev/hmr.js';
2-
export { ADD_OWNER, add_owner, mark_module_start, mark_module_end } from './dev/ownership.js';
2+
export {
3+
ADD_OWNER,
4+
add_owner,
5+
mark_module_start,
6+
mark_module_end,
7+
add_owner_effect
8+
} from './dev/ownership.js';
39
export { inspect } from './dev/inspect.js';
410
export { await_block as await } from './dom/blocks/await.js';
511
export { if_block as if } from './dom/blocks/if.js';

packages/svelte/src/internal/client/proxy.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,7 @@ export function proxy(value, immutable = true, parent = null) {
7575
value[STATE_SYMBOL].owners =
7676
parent === null
7777
? current_component_context !== null
78-
? // @ts-expect-error
79-
new Set([current_component_context.function])
78+
? new Set([current_component_context.function])
8079
: null
8180
: new Set();
8281
}

packages/svelte/src/internal/client/runtime.js

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,26 @@ export let current_component_context = null;
113113
/** @param {import('#client').ComponentContext | null} context */
114114
export function set_current_component_context(context) {
115115
current_component_context = context;
116+
if (DEV) {
117+
dev_current_component_function = context?.function;
118+
}
119+
}
120+
121+
/**
122+
* The current component function. Different from current component context:
123+
* ```html
124+
* <!-- App.svelte -->
125+
* <Foo>
126+
* <Bar /> <!-- context == Foo.svelte, function == App.svelte -->
127+
* </Foo>
128+
* ```
129+
* @type {import('#client').ComponentContext['function']}
130+
*/
131+
export let dev_current_component_function = null;
132+
133+
/** @param {import('#client').ComponentContext['function']} fn */
134+
export function set_dev_current_component_function(fn) {
135+
dev_current_component_function = fn;
116136
}
117137

118138
/** @returns {boolean} */
@@ -400,7 +420,7 @@ export function execute_effect(effect) {
400420
var previous_component_context = current_component_context;
401421

402422
current_effect = effect;
403-
current_component_context = component_context;
423+
set_current_component_context(component_context);
404424

405425
try {
406426
if ((flags & BLOCK_EFFECT) === 0) {
@@ -412,7 +432,7 @@ export function execute_effect(effect) {
412432
effect.teardown = typeof teardown === 'function' ? teardown : null;
413433
} finally {
414434
current_effect = previous_effect;
415-
current_component_context = previous_component_context;
435+
set_current_component_context(previous_component_context);
416436
}
417437
}
418438

@@ -885,8 +905,8 @@ export function getContext(key) {
885905
const result = /** @type {T} */ (context_map.get(key));
886906

887907
if (DEV) {
888-
// @ts-expect-error
889-
const fn = current_component_context.function;
908+
const fn = /** @type {import('#client').ComponentContext} */ (current_component_context)
909+
.function;
890910
if (fn) {
891911
add_owner(result, fn, true);
892912
}
@@ -940,7 +960,6 @@ export function getAllContexts() {
940960
const context_map = get_or_init_context_map('getAllContexts');
941961

942962
if (DEV) {
943-
// @ts-expect-error
944963
const fn = current_component_context?.function;
945964
if (fn) {
946965
for (const value of context_map.values()) {
@@ -1066,8 +1085,8 @@ export function push(props, runes = false, fn) {
10661085

10671086
if (DEV) {
10681087
// component function
1069-
// @ts-expect-error
10701088
current_component_context.function = fn;
1089+
dev_current_component_function = fn;
10711090
}
10721091
}
10731092

@@ -1089,7 +1108,7 @@ export function pop(component) {
10891108
effect(effects[i]);
10901109
}
10911110
}
1092-
current_component_context = context_stack_item.p;
1111+
set_current_component_context(context_stack_item.p);
10931112
context_stack_item.m = true;
10941113
}
10951114
// Micro-optimization: Don't set .a above to the empty object

packages/svelte/src/internal/client/types.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ export type ComponentContext = {
4949
/** This tracks whether `$:` statements have run in the current cycle, to ensure they only run once */
5050
r2: Source<boolean>;
5151
};
52+
/**
53+
* dev mode only: the component function
54+
*/
55+
function?: any;
5256
};
5357

5458
export type ComponentContextLegacy = ComponentContext & {

packages/svelte/tests/runtime-legacy/samples/context-api/_config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { test } from '../../test';
22

33
export default test({
4+
compileOptions: {
5+
dev: true // to ensure dev mode does not break context in some way
6+
},
47
html: `
58
<div class="tabs">
69
<div class="tab-list">

0 commit comments

Comments
 (0)