Skip to content

Commit aae969d

Browse files
authored
Merge pull request sveltejs#2415 from sveltejs/sveltejsgh-2356
Prevent infinite loops with chained bindings
2 parents 49679cc + 016078d commit aae969d

File tree

15 files changed

+214
-27
lines changed

15 files changed

+214
-27
lines changed

src/compile/render-dom/wrappers/InlineComponent/index.ts

+7-13
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,6 @@ export default class InlineComponentWrapper extends Wrapper {
112112

113113
const statements: string[] = [];
114114
const updates: string[] = [];
115-
const postupdates: string[] = [];
116115

117116
let props;
118117
const name_changes = block.get_unique_name(`${name}_changes`);
@@ -311,8 +310,6 @@ export default class InlineComponentWrapper extends Wrapper {
311310
}
312311
`);
313312

314-
postupdates.push(updating);
315-
316313
const contextual_dependencies = Array.from(binding.expression.contextual_dependencies);
317314
const dependencies = Array.from(binding.expression.dependencies);
318315

@@ -334,27 +331,27 @@ export default class InlineComponentWrapper extends Wrapper {
334331

335332
block.builders.init.add_block(deindent`
336333
function ${name}(${value}) {
337-
if (ctx.${name}.call(null, ${value}, ctx)) {
338-
${updating} = true;
339-
}
334+
ctx.${name}.call(null, ${value}, ctx);
335+
${updating} = true;
336+
@add_flush_callback(() => ${updating} = false);
340337
}
341338
`);
342339

343340
block.maintain_context = true; // TODO put this somewhere more logical
344341
} else {
345342
block.builders.init.add_block(deindent`
346343
function ${name}(${value}) {
347-
if (ctx.${name}.call(null, ${value})) {
348-
${updating} = true;
349-
}
344+
ctx.${name}.call(null, ${value});
345+
${updating} = true;
346+
@add_flush_callback(() => ${updating} = false);
350347
}
351348
`);
352349
}
353350

354351
const body = deindent`
355352
function ${name}(${args.join(', ')}) {
356353
${lhs} = ${value};
357-
return ${component.invalidate(dependencies[0])}
354+
${component.invalidate(dependencies[0])};
358355
}
359356
`;
360357

@@ -453,8 +450,6 @@ export default class InlineComponentWrapper extends Wrapper {
453450
else if (${switch_value}) {
454451
${name}.$set(${name_changes});
455452
}
456-
457-
${postupdates.length > 0 && `${postupdates.join(' = ')} = false;`}
458453
`);
459454
}
460455

@@ -498,7 +493,6 @@ export default class InlineComponentWrapper extends Wrapper {
498493
block.builders.update.add_block(deindent`
499494
${updates}
500495
${name}.$set(${name_changes});
501-
${postupdates.length > 0 && `${postupdates.join(' = ')} = false;`}
502496
`);
503497
}
504498

src/internal/Component.js

+3-10
Original file line numberDiff line numberDiff line change
@@ -85,16 +85,9 @@ export function init(component, options, instance, create_fragment, not_equal, p
8585

8686
$$.ctx = instance
8787
? instance(component, props, (key, value) => {
88-
if ($$.bound[key]) $$.bound[key](value);
89-
90-
if ($$.ctx) {
91-
const changed = not_equal(value, $$.ctx[key]);
92-
if (ready && changed) {
93-
make_dirty(component, key);
94-
}
95-
96-
$$.ctx[key] = value;
97-
return changed;
88+
if ($$.ctx && not_equal($$.ctx[key], $$.ctx[key] = value)) {
89+
if ($$.bound[key]) $$.bound[key](value);
90+
if (ready) make_dirty(component, key);
9891
}
9992
})
10093
: props;

src/internal/scheduler.js

+13-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export const intros = { enabled: false };
77
let update_promise;
88
const binding_callbacks = [];
99
const render_callbacks = [];
10+
const flush_callbacks = [];
1011

1112
export function schedule_update() {
1213
if (!update_promise) {
@@ -15,10 +16,6 @@ export function schedule_update() {
1516
}
1617
}
1718

18-
export function add_render_callback(fn) {
19-
render_callbacks.push(fn);
20-
}
21-
2219
export function tick() {
2320
schedule_update();
2421
return update_promise;
@@ -28,6 +25,14 @@ export function add_binding_callback(fn) {
2825
binding_callbacks.push(fn);
2926
}
3027

28+
export function add_render_callback(fn) {
29+
render_callbacks.push(fn);
30+
}
31+
32+
export function add_flush_callback(fn) {
33+
flush_callbacks.push(fn);
34+
}
35+
3136
export function flush() {
3237
const seen_callbacks = new Set();
3338

@@ -56,6 +61,10 @@ export function flush() {
5661
}
5762
} while (dirty_components.length);
5863

64+
while (flush_callbacks.length) {
65+
flush_callbacks.pop()();
66+
}
67+
5968
update_promise = null;
6069
}
6170

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script>
2+
import Two from './Two.svelte';
3+
4+
export let list;
5+
export let i;
6+
7+
function handle_click() {
8+
list = [...list, {}];
9+
}
10+
</script>
11+
12+
{#each list as item, j}
13+
<Two bind:value={item.value} {i} {j}/>
14+
{/each}
15+
16+
<button on:click={handle_click}>
17+
click me
18+
</button>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<script>
2+
export let i, j;
3+
export let value = `${i}:${j}`;
4+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export default {
2+
html: `
3+
<button>click me</button>
4+
<button>click me</button>
5+
6+
<p>{"value":"0:0"}</p>
7+
<p></p>
8+
`,
9+
10+
async test({ assert, target, window }) {
11+
const button = target.querySelectorAll('button')[1];
12+
13+
await button.dispatchEvent(new window.Event('click'));
14+
15+
assert.htmlEqual(target.innerHTML, `
16+
<button>click me</button>
17+
<button>click me</button>
18+
19+
<p>{"value":"0:0"}</p>
20+
<p>{"value":"1:0"}</p>
21+
`);
22+
}
23+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<script>
2+
import One from "./One.svelte";
3+
4+
const obj = {
5+
a: [{}],
6+
b: []
7+
};
8+
</script>
9+
10+
<One bind:list={obj.a} i={0}/>
11+
<One bind:list={obj.b} i={1}/>
12+
13+
<p>{obj.a.map(JSON.stringify)}</p>
14+
<p>{obj.b.map(JSON.stringify)}</p>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script>
2+
import Two from './Two.svelte';
3+
4+
export let list;
5+
export let i;
6+
7+
function handle_click() {
8+
list = [...list, {}];
9+
}
10+
</script>
11+
12+
{#each list as item, j}
13+
<Two bind:value={item.value} {i} {j}/>
14+
{/each}
15+
16+
<button on:click={handle_click}>
17+
click me
18+
</button>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<script>
2+
export let i, j;
3+
export let value = { i, j };
4+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export default {
2+
html: `
3+
<button>click me</button>
4+
<button>click me</button>
5+
6+
<p>{"value":{"i":0,"j":0}}</p>
7+
<p></p>
8+
`,
9+
10+
async test({ assert, target, window }) {
11+
const button = target.querySelectorAll('button')[1];
12+
13+
await button.dispatchEvent(new window.Event('click'));
14+
15+
assert.htmlEqual(target.innerHTML, `
16+
<button>click me</button>
17+
<button>click me</button>
18+
19+
<p>{"value":{"i":0,"j":0}}</p>
20+
<p>{"value":{"i":1,"j":0}}</p>
21+
`);
22+
}
23+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<script>
2+
import One from "./One.svelte";
3+
4+
const obj = {
5+
a: [{}],
6+
b: []
7+
};
8+
</script>
9+
10+
<One bind:list={obj.a} i={0}/>
11+
<One bind:list={obj.b} i={1}/>
12+
13+
<p>{obj.a.map(JSON.stringify)}</p>
14+
<p>{obj.b.map(JSON.stringify)}</p>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script>
2+
import Two from './Two.svelte';
3+
4+
export let list;
5+
export let i;
6+
7+
function handle_click() {
8+
list = [...list, {}];
9+
}
10+
</script>
11+
12+
{#each list as item, j}
13+
<Two bind:value={item.value} {i} {j}/>
14+
{/each}
15+
16+
<button on:click={handle_click}>
17+
click me
18+
</button>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<script>
2+
import { onMount } from 'svelte';
3+
4+
export let i, j;
5+
export let value;
6+
7+
onMount(() => {
8+
value = { i, j };
9+
});
10+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
export default {
2+
html: `
3+
<button>click me</button>
4+
<button>click me</button>
5+
6+
<p>{"value":{"i":0,"j":0}}</p>
7+
<p></p>
8+
`,
9+
10+
ssrHtml: `
11+
<button>click me</button>
12+
<button>click me</button>
13+
14+
<p>{}</p>
15+
<p></p>
16+
`,
17+
18+
async test({ assert, target, window }) {
19+
const button = target.querySelectorAll('button')[1];
20+
21+
await button.dispatchEvent(new window.Event('click'));
22+
23+
assert.htmlEqual(target.innerHTML, `
24+
<button>click me</button>
25+
<button>click me</button>
26+
27+
<p>{"value":{"i":0,"j":0}}</p>
28+
<p>{"value":{"i":1,"j":0}}</p>
29+
`);
30+
}
31+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<script>
2+
import One from "./One.svelte";
3+
4+
const obj = {
5+
a: [{}],
6+
b: []
7+
};
8+
</script>
9+
10+
<One bind:list={obj.a} i={0}/>
11+
<One bind:list={obj.b} i={1}/>
12+
13+
<p>{obj.a.map(JSON.stringify)}</p>
14+
<p>{obj.b.map(JSON.stringify)}</p>

0 commit comments

Comments
 (0)