From 762d0429b1fd2c3743fe7836108b3c1c557db750 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Mon, 2 Jan 2023 06:51:01 +0100 Subject: [PATCH] [fix] propagate bindings correctly (#8114) Fixes #8103 introduced through #7981 Keeps the infinite loop from happening but reopens #6298 and #5689 --- .../wrappers/InlineComponent/index.ts | 2 +- src/runtime/internal/Component.ts | 6 ++--- src/runtime/internal/scheduler.ts | 25 +++++++++++++++---- .../binding-indirect-value/Component.svelte | 6 +++++ .../_config.js | 3 ++- .../binding-indirect-value/main.svelte | 8 ++++++ .../Tab.svelte | 0 .../_config.js | 10 ++++++++ .../main.svelte | 0 9 files changed, 49 insertions(+), 11 deletions(-) create mode 100644 test/runtime/samples/binding-indirect-value/Component.svelte rename test/runtime/samples/{binding-no-unnecessary-invalidation => binding-indirect-value}/_config.js (62%) create mode 100644 test/runtime/samples/binding-indirect-value/main.svelte rename test/runtime/samples/{binding-no-unnecessary-invalidation => binding-no-unnecessary-invalidation.skip}/Tab.svelte (100%) create mode 100644 test/runtime/samples/binding-no-unnecessary-invalidation.skip/_config.js rename test/runtime/samples/{binding-no-unnecessary-invalidation => binding-no-unnecessary-invalidation.skip}/main.svelte (100%) diff --git a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts index f7875dc1b07d..bca01fdb4cd2 100644 --- a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts +++ b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts @@ -393,7 +393,7 @@ export default class InlineComponentWrapper extends Wrapper { component.partly_hoisted.push(body); - return b`@binding_callbacks.push(() => @bind(${this.var}, '${binding.name}', ${id}, ${snippet}));`; + return b`@binding_callbacks.push(() => @bind(${this.var}, '${binding.name}', ${id}));`; }); const munged_handlers = this.node.handlers.map(handler => { diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index eedf8dd1ada4..5aec24c651f7 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -5,13 +5,11 @@ import { children, detach, start_hydrating, end_hydrating } from './dom'; import { transition_in } from './transitions'; import { T$$ } from './types'; -export function bind(component, name, callback, value) { +export function bind(component, name, callback) { const index = component.$$.props[name]; if (index !== undefined) { component.$$.bound[index] = callback; - if (value === undefined) { - callback(component.$$.ctx[index]); - } + callback(component.$$.ctx[index]); } } diff --git a/src/runtime/internal/scheduler.ts b/src/runtime/internal/scheduler.ts index c0b8e57b08e8..f95ba446f550 100644 --- a/src/runtime/internal/scheduler.ts +++ b/src/runtime/internal/scheduler.ts @@ -52,17 +52,32 @@ export function add_flush_callback(fn) { const seen_callbacks = new Set(); let flushidx = 0; // Do *not* move this inside the flush() function export function flush() { + // Do not reenter flush while dirty components are updated, as this can + // result in an infinite loop. Instead, let the inner flush handle it. + // Reentrancy is ok afterwards for bindings etc. + if (flushidx !== 0) { + return; + } + const saved_component = current_component; do { // first, call beforeUpdate functions // and update components - while (flushidx < dirty_components.length) { - const component = dirty_components[flushidx]; - flushidx++; - set_current_component(component); - update(component.$$); + try { + while (flushidx < dirty_components.length) { + const component = dirty_components[flushidx]; + flushidx++; + set_current_component(component); + update(component.$$); + } + } catch (e) { + // reset dirty state to not end up in a deadlocked state and then rethrow + dirty_components.length = 0; + flushidx = 0; + throw e; } + set_current_component(null); dirty_components.length = 0; diff --git a/test/runtime/samples/binding-indirect-value/Component.svelte b/test/runtime/samples/binding-indirect-value/Component.svelte new file mode 100644 index 000000000000..25bf0d6758c6 --- /dev/null +++ b/test/runtime/samples/binding-indirect-value/Component.svelte @@ -0,0 +1,6 @@ + + +Child component "{value}"
diff --git a/test/runtime/samples/binding-no-unnecessary-invalidation/_config.js b/test/runtime/samples/binding-indirect-value/_config.js similarity index 62% rename from test/runtime/samples/binding-no-unnecessary-invalidation/_config.js rename to test/runtime/samples/binding-indirect-value/_config.js index a34cf0121f42..4eaf6839aa1e 100644 --- a/test/runtime/samples/binding-no-unnecessary-invalidation/_config.js +++ b/test/runtime/samples/binding-indirect-value/_config.js @@ -1,7 +1,8 @@ export default { async test({ assert, target }) { assert.htmlEqual(target.innerHTML, ` -

0

+ Parent component "bar"
+ Child component "bar"
`); } }; diff --git a/test/runtime/samples/binding-indirect-value/main.svelte b/test/runtime/samples/binding-indirect-value/main.svelte new file mode 100644 index 000000000000..db2734c89091 --- /dev/null +++ b/test/runtime/samples/binding-indirect-value/main.svelte @@ -0,0 +1,8 @@ + + +Parent component "{value}"
+ diff --git a/test/runtime/samples/binding-no-unnecessary-invalidation/Tab.svelte b/test/runtime/samples/binding-no-unnecessary-invalidation.skip/Tab.svelte similarity index 100% rename from test/runtime/samples/binding-no-unnecessary-invalidation/Tab.svelte rename to test/runtime/samples/binding-no-unnecessary-invalidation.skip/Tab.svelte diff --git a/test/runtime/samples/binding-no-unnecessary-invalidation.skip/_config.js b/test/runtime/samples/binding-no-unnecessary-invalidation.skip/_config.js new file mode 100644 index 000000000000..d7e553fd46d9 --- /dev/null +++ b/test/runtime/samples/binding-no-unnecessary-invalidation.skip/_config.js @@ -0,0 +1,10 @@ +// this test currently fails because the fix that made it pass broke other tests, +// see https://github.com/sveltejs/svelte/pull/8114 for more context. +export default { + skip: true, + async test({ assert, target }) { + assert.htmlEqual(target.innerHTML, ` +

0

+ `); + } +}; diff --git a/test/runtime/samples/binding-no-unnecessary-invalidation/main.svelte b/test/runtime/samples/binding-no-unnecessary-invalidation.skip/main.svelte similarity index 100% rename from test/runtime/samples/binding-no-unnecessary-invalidation/main.svelte rename to test/runtime/samples/binding-no-unnecessary-invalidation.skip/main.svelte