From e03f8d65904173020e92f55d19b5b7a24af8cfd7 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 21 Nov 2018 23:30:00 -0500 Subject: [PATCH] handle nightmarish infinite loop situation --- .../render-dom/wrappers/Element/index.ts | 4 +- src/internal/Component.js | 10 +++-- src/internal/scheduler.js | 37 +++++++++++++------ 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/compile/render-dom/wrappers/Element/index.ts b/src/compile/render-dom/wrappers/Element/index.ts index bae2d00dc9..e6b3a06d72 100644 --- a/src/compile/render-dom/wrappers/Element/index.ts +++ b/src/compile/render-dom/wrappers/Element/index.ts @@ -757,7 +757,7 @@ export default class ElementWrapper extends Wrapper { block.builders.intro.addConditional(`@intro.enabled`, deindent` if (${name}) ${name}.invalidate(); - @after_update(() => { + @after_render(() => { if (!${name}) ${name} = @wrapTransition(#component, ${this.var}, ${fn}, ${snippet}, true); ${name}.run(1); }); @@ -792,7 +792,7 @@ export default class ElementWrapper extends Wrapper { } block.builders.intro.addConditional(`@intro.enabled`, deindent` - @after_update(() => { + @after_render(() => { ${introName} = @wrapTransition(#component, ${this.var}, ${fn}, ${snippet}, true); ${introName}.run(1); }); diff --git a/src/internal/Component.js b/src/internal/Component.js index 0c856e70f6..56e5ac1ad7 100644 --- a/src/internal/Component.js +++ b/src/internal/Component.js @@ -1,4 +1,4 @@ -import { schedule_update, flush, intro } from './scheduler.js'; +import { after_render, flush, intro, schedule_update } from './scheduler.js'; import { set_current_component } from './lifecycle.js' import { is_function, run, run_all, noop } from './utils.js'; import { blankObject } from './utils.js'; @@ -33,11 +33,13 @@ export class $$Component { this.$$.inject_props(options.props); } + run_all(this.$$beforeRender); this.$$fragment = this.$$create_fragment(this, this.$$.get_state()); if (options.target) { intro.enabled = !!options.intro; this.$$mount(options.target); + flush(); intro.enabled = true; } @@ -90,7 +92,6 @@ export class $$Component { } $$mount(target, anchor) { - run_all(this.$$beforeRender); this.$$fragment.c(); this.$$fragment[this.$$fragment.i ? 'i' : 'm'](target, anchor); this.$$.inject_refs(this.$$refs); @@ -99,15 +100,16 @@ export class $$Component { this.$$onDestroy.push(...onDestroy); this.$$onMount = []; - run_all(this.$$afterRender); + this.$$afterRender.forEach(after_render); } $$update() { run_all(this.$$beforeRender); this.$$fragment.p(this.$$dirty, this.$$.get_state()); this.$$.inject_refs(this.$$refs); - run_all(this.$$afterRender); this.$$dirty = null; + + this.$$afterRender.forEach(after_render); } } diff --git a/src/internal/scheduler.js b/src/internal/scheduler.js index aaebcb53f9..fcfcdc2b76 100644 --- a/src/internal/scheduler.js +++ b/src/internal/scheduler.js @@ -1,7 +1,7 @@ let update_scheduled = false; -const dirty_components = []; -const after_update_callbacks = []; +let dirty_components = []; +const after_render_callbacks = []; export const intro = { enabled: false }; @@ -13,18 +13,33 @@ export function schedule_update(component) { } } -export function after_update(fn) { - after_update_callbacks.push(fn); +export function after_render(fn) { + after_render_callbacks.push(fn); } export function flush() { - while (dirty_components.length) { - dirty_components.pop().$$update(); - } - - while (after_update_callbacks.length) { - after_update_callbacks.shift()(); - } + const seen_callbacks = new Set(); + + do { + // first, call beforeRender functions + // and update components + while (dirty_components.length) { + dirty_components.shift().$$update(); + } + + // then, once components are updated, call + // afterRender functions. This may cause + // subsequent updates... + while (after_render_callbacks.length) { + const callback = after_render_callbacks.pop(); + if (!seen_callbacks.has(callback)) { + callback(); + + // ...so guard against infinite loops + seen_callbacks.add(callback); + } + } + } while (dirty_components.length); update_scheduled = false; }