fix: race condition in `svelte:element` with transition #7948 (#7949)

fixes #7948
- The assignment of the variable "previous_tag" was incorrectly positioned and could cause race condition when used with transitions.
- We need another variable to detect when we are in a transition to remove a node

---------

Co-authored-by: Yuichiro Yamashita <xydybaseball@gmail.com>
pull/8335/head
adiGuba 2 years ago committed by GitHub
parent 60a205edb8
commit 69c199feac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -319,10 +319,16 @@ export default class ElementWrapper extends Wrapper {
const has_transitions = !!(this.node.intro || this.node.outro);
const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`;
const tag_will_be_removed = block.get_unique_name('tag_will_be_removed');
if (has_transitions) {
block.add_variable(tag_will_be_removed, x`false`);
}
block.chunks.update.push(b`
if (${tag}) {
if (!${previous_tag}) {
${this.var} = ${this.child_dynamic_element_block.name}(#ctx);
${previous_tag} = ${tag};
${this.var}.c();
${has_transitions && b`@transition_in(${this.var})`}
${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor});
@ -331,27 +337,39 @@ export default class ElementWrapper extends Wrapper {
${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`}
${this.renderer.options.dev && this.node.children.length > 0 && b`@validate_void_dynamic_element(${tag});`}
${this.var} = ${this.child_dynamic_element_block.name}(#ctx);
${previous_tag} = ${tag};
${this.var}.c();
${has_transitions && b`if (${tag_will_be_removed}) {
${tag_will_be_removed} = false;
@transition_in(${this.var})
}`}
${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor});
} else {
${has_transitions && b`if (${tag_will_be_removed}) {
${tag_will_be_removed} = false;
@transition_in(${this.var})
}`}
${this.var}.p(#ctx, #dirty);
}
} else if (${previous_tag}) {
${has_transitions
? b`
${tag_will_be_removed} = true;
@group_outros();
@transition_out(${this.var}, 1, 1, () => {
${this.var} = null;
${previous_tag} = ${tag};
${tag_will_be_removed} = false;
});
@check_outros();
`
: b`
${this.var}.d(1);
${this.var} = null;
${previous_tag} = ${tag};
`
}
}
${previous_tag} = ${tag};
`);
if (this.child_dynamic_element_block.has_intros) {

@ -112,11 +112,13 @@ function create_fragment(ctx) {
if (a) {
if (!previous_tag) {
svelte_element = create_dynamic_element(ctx);
previous_tag = a;
svelte_element.c();
svelte_element.m(svelte_element_anchor.parentNode, svelte_element_anchor);
} else if (safe_not_equal(previous_tag, a)) {
svelte_element.d(1);
svelte_element = create_dynamic_element(ctx);
previous_tag = a;
svelte_element.c();
svelte_element.m(svelte_element_anchor.parentNode, svelte_element_anchor);
} else {
@ -125,9 +127,8 @@ function create_fragment(ctx) {
} else if (previous_tag) {
svelte_element.d(1);
svelte_element = null;
previous_tag = a;
}
previous_tag = a;
},
i: noop,
o: noop,
@ -168,4 +169,4 @@ class Component extends SvelteComponent {
}
}
export default Component;
export default Component;

@ -72,11 +72,13 @@ function create_fragment(ctx) {
if (/*tag*/ ctx[0].svg) {
if (!previous_tag) {
svelte_element = create_dynamic_element(ctx);
previous_tag = /*tag*/ ctx[0].svg;
svelte_element.c();
svelte_element.m(svelte_element_anchor.parentNode, svelte_element_anchor);
} else if (safe_not_equal(previous_tag, /*tag*/ ctx[0].svg)) {
svelte_element.d(1);
svelte_element = create_dynamic_element(ctx);
previous_tag = /*tag*/ ctx[0].svg;
svelte_element.c();
svelte_element.m(svelte_element_anchor.parentNode, svelte_element_anchor);
} else {
@ -85,9 +87,8 @@ function create_fragment(ctx) {
} else if (previous_tag) {
svelte_element.d(1);
svelte_element = null;
previous_tag = /*tag*/ ctx[0].svg;
}
previous_tag = /*tag*/ ctx[0].svg;
},
i: noop,
o: noop,
@ -110,4 +111,4 @@ class Component extends SvelteComponent {
}
}
export default Component;
export default Component;

@ -0,0 +1,19 @@
export default {
async test({ assert, target, raf }) {
const button = target.querySelector('#button');
const container = target.querySelector('#container');
// Multiple click on button
await button.dispatchEvent(new window.MouseEvent('click'));
await button.dispatchEvent(new window.MouseEvent('click'));
await button.dispatchEvent(new window.MouseEvent('click'));
await button.dispatchEvent(new window.MouseEvent('click'));
await button.dispatchEvent(new window.MouseEvent('click'));
await button.dispatchEvent(new window.MouseEvent('click'));
await button.dispatchEvent(new window.MouseEvent('click'));
assert.equal(container.children.length, 1);
raf.tick(501);
assert.equal(container.children.length, 0);
}
};

@ -0,0 +1,13 @@
<script>
import { slide } from 'svelte/transition';
let tag = 'div';
function toggle() {
tag = (tag) ? null : 'div';
}
</script>
<button id="button" on:click={toggle}>toggle</button> TAG={tag}
<div id="container">
<svelte:element this={tag} transition:slide={{duration:500}}>CONTENT</svelte:element>
</div>
Loading…
Cancel
Save