Merge pull request #2415 from sveltejs/gh-2356

Prevent infinite loops with chained bindings
pull/2418/head
Rich Harris 6 years ago committed by GitHub
commit aae969d6e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -112,7 +112,6 @@ export default class InlineComponentWrapper extends Wrapper {
const statements: string[] = []; const statements: string[] = [];
const updates: string[] = []; const updates: string[] = [];
const postupdates: string[] = [];
let props; let props;
const name_changes = block.get_unique_name(`${name}_changes`); const name_changes = block.get_unique_name(`${name}_changes`);
@ -311,8 +310,6 @@ export default class InlineComponentWrapper extends Wrapper {
} }
`); `);
postupdates.push(updating);
const contextual_dependencies = Array.from(binding.expression.contextual_dependencies); const contextual_dependencies = Array.from(binding.expression.contextual_dependencies);
const dependencies = Array.from(binding.expression.dependencies); const dependencies = Array.from(binding.expression.dependencies);
@ -334,9 +331,9 @@ export default class InlineComponentWrapper extends Wrapper {
block.builders.init.add_block(deindent` block.builders.init.add_block(deindent`
function ${name}(${value}) { function ${name}(${value}) {
if (ctx.${name}.call(null, ${value}, ctx)) { ctx.${name}.call(null, ${value}, ctx);
${updating} = true; ${updating} = true;
} @add_flush_callback(() => ${updating} = false);
} }
`); `);
@ -344,9 +341,9 @@ export default class InlineComponentWrapper extends Wrapper {
} else { } else {
block.builders.init.add_block(deindent` block.builders.init.add_block(deindent`
function ${name}(${value}) { function ${name}(${value}) {
if (ctx.${name}.call(null, ${value})) { ctx.${name}.call(null, ${value});
${updating} = true; ${updating} = true;
} @add_flush_callback(() => ${updating} = false);
} }
`); `);
} }
@ -354,7 +351,7 @@ export default class InlineComponentWrapper extends Wrapper {
const body = deindent` const body = deindent`
function ${name}(${args.join(', ')}) { function ${name}(${args.join(', ')}) {
${lhs} = ${value}; ${lhs} = ${value};
return ${component.invalidate(dependencies[0])} ${component.invalidate(dependencies[0])};
} }
`; `;
@ -453,8 +450,6 @@ export default class InlineComponentWrapper extends Wrapper {
else if (${switch_value}) { else if (${switch_value}) {
${name}.$set(${name_changes}); ${name}.$set(${name_changes});
} }
${postupdates.length > 0 && `${postupdates.join(' = ')} = false;`}
`); `);
} }
@ -498,7 +493,6 @@ export default class InlineComponentWrapper extends Wrapper {
block.builders.update.add_block(deindent` block.builders.update.add_block(deindent`
${updates} ${updates}
${name}.$set(${name_changes}); ${name}.$set(${name_changes});
${postupdates.length > 0 && `${postupdates.join(' = ')} = false;`}
`); `);
} }

@ -85,16 +85,9 @@ export function init(component, options, instance, create_fragment, not_equal, p
$$.ctx = instance $$.ctx = instance
? instance(component, props, (key, value) => { ? instance(component, props, (key, value) => {
if ($$.bound[key]) $$.bound[key](value); if ($$.ctx && not_equal($$.ctx[key], $$.ctx[key] = value)) {
if ($$.bound[key]) $$.bound[key](value);
if ($$.ctx) { if (ready) make_dirty(component, key);
const changed = not_equal(value, $$.ctx[key]);
if (ready && changed) {
make_dirty(component, key);
}
$$.ctx[key] = value;
return changed;
} }
}) })
: props; : props;

@ -7,6 +7,7 @@ export const intros = { enabled: false };
let update_promise; let update_promise;
const binding_callbacks = []; const binding_callbacks = [];
const render_callbacks = []; const render_callbacks = [];
const flush_callbacks = [];
export function schedule_update() { export function schedule_update() {
if (!update_promise) { if (!update_promise) {
@ -15,10 +16,6 @@ export function schedule_update() {
} }
} }
export function add_render_callback(fn) {
render_callbacks.push(fn);
}
export function tick() { export function tick() {
schedule_update(); schedule_update();
return update_promise; return update_promise;
@ -28,6 +25,14 @@ export function add_binding_callback(fn) {
binding_callbacks.push(fn); binding_callbacks.push(fn);
} }
export function add_render_callback(fn) {
render_callbacks.push(fn);
}
export function add_flush_callback(fn) {
flush_callbacks.push(fn);
}
export function flush() { export function flush() {
const seen_callbacks = new Set(); const seen_callbacks = new Set();
@ -56,6 +61,10 @@ export function flush() {
} }
} while (dirty_components.length); } while (dirty_components.length);
while (flush_callbacks.length) {
flush_callbacks.pop()();
}
update_promise = null; update_promise = null;
} }

@ -0,0 +1,18 @@
<script>
import Two from './Two.svelte';
export let list;
export let i;
function handle_click() {
list = [...list, {}];
}
</script>
{#each list as item, j}
<Two bind:value={item.value} {i} {j}/>
{/each}
<button on:click={handle_click}>
click me
</button>

@ -0,0 +1,4 @@
<script>
export let i, j;
export let value = `${i}:${j}`;
</script>

@ -0,0 +1,23 @@
export default {
html: `
<button>click me</button>
<button>click me</button>
<p>{"value":"0:0"}</p>
<p></p>
`,
async test({ assert, target, window }) {
const button = target.querySelectorAll('button')[1];
await button.dispatchEvent(new window.Event('click'));
assert.htmlEqual(target.innerHTML, `
<button>click me</button>
<button>click me</button>
<p>{"value":"0:0"}</p>
<p>{"value":"1:0"}</p>
`);
}
};

@ -0,0 +1,14 @@
<script>
import One from "./One.svelte";
const obj = {
a: [{}],
b: []
};
</script>
<One bind:list={obj.a} i={0}/>
<One bind:list={obj.b} i={1}/>
<p>{obj.a.map(JSON.stringify)}</p>
<p>{obj.b.map(JSON.stringify)}</p>

@ -0,0 +1,18 @@
<script>
import Two from './Two.svelte';
export let list;
export let i;
function handle_click() {
list = [...list, {}];
}
</script>
{#each list as item, j}
<Two bind:value={item.value} {i} {j}/>
{/each}
<button on:click={handle_click}>
click me
</button>

@ -0,0 +1,4 @@
<script>
export let i, j;
export let value = { i, j };
</script>

@ -0,0 +1,23 @@
export default {
html: `
<button>click me</button>
<button>click me</button>
<p>{"value":{"i":0,"j":0}}</p>
<p></p>
`,
async test({ assert, target, window }) {
const button = target.querySelectorAll('button')[1];
await button.dispatchEvent(new window.Event('click'));
assert.htmlEqual(target.innerHTML, `
<button>click me</button>
<button>click me</button>
<p>{"value":{"i":0,"j":0}}</p>
<p>{"value":{"i":1,"j":0}}</p>
`);
}
};

@ -0,0 +1,14 @@
<script>
import One from "./One.svelte";
const obj = {
a: [{}],
b: []
};
</script>
<One bind:list={obj.a} i={0}/>
<One bind:list={obj.b} i={1}/>
<p>{obj.a.map(JSON.stringify)}</p>
<p>{obj.b.map(JSON.stringify)}</p>

@ -0,0 +1,18 @@
<script>
import Two from './Two.svelte';
export let list;
export let i;
function handle_click() {
list = [...list, {}];
}
</script>
{#each list as item, j}
<Two bind:value={item.value} {i} {j}/>
{/each}
<button on:click={handle_click}>
click me
</button>

@ -0,0 +1,10 @@
<script>
import { onMount } from 'svelte';
export let i, j;
export let value;
onMount(() => {
value = { i, j };
});
</script>

@ -0,0 +1,31 @@
export default {
html: `
<button>click me</button>
<button>click me</button>
<p>{"value":{"i":0,"j":0}}</p>
<p></p>
`,
ssrHtml: `
<button>click me</button>
<button>click me</button>
<p>{}</p>
<p></p>
`,
async test({ assert, target, window }) {
const button = target.querySelectorAll('button')[1];
await button.dispatchEvent(new window.Event('click'));
assert.htmlEqual(target.innerHTML, `
<button>click me</button>
<button>click me</button>
<p>{"value":{"i":0,"j":0}}</p>
<p>{"value":{"i":1,"j":0}}</p>
`);
}
};

@ -0,0 +1,14 @@
<script>
import One from "./One.svelte";
const obj = {
a: [{}],
b: []
};
</script>
<One bind:list={obj.a} i={0}/>
<One bind:list={obj.b} i={1}/>
<p>{obj.a.map(JSON.stringify)}</p>
<p>{obj.b.map(JSON.stringify)}</p>
Loading…
Cancel
Save