From e47f60444968d334c5dd9771b0252e8c75300a37 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Fri, 8 Nov 2019 09:16:04 +0100 Subject: [PATCH] [FIX] component: concurrent rendering issue Resolved rendering with cancelled fiber for (not yet) destroyed component -> Cannot read property 'sel' of null Closes #421 --- src/component/component.ts | 3 + src/component/directive.ts | 2 +- tests/__snapshots__/animations.test.ts.snap | 4 +- .../__snapshots__/component.test.ts.snap | 56 ++++++++--------- .../props_validation.test.ts.snap | 2 +- tests/component/component.test.ts | 62 +++++++++++++++++++ .../route_component.test.ts.snap | 2 +- 7 files changed, 98 insertions(+), 33 deletions(-) diff --git a/src/component/component.ts b/src/component/component.ts index 087f2ee6..39cbef25 100644 --- a/src/component/component.ts +++ b/src/component/component.ts @@ -438,6 +438,9 @@ export class Component { } __owl__.isDestroyed = true; delete __owl__.vnode; + if (__owl__.currentFiber) { + __owl__.currentFiber.isCancelled = true; + } } __callMounted() { diff --git a/src/component/directive.ts b/src/component/directive.ts index d43c953c..130e37d7 100644 --- a/src/component/directive.ts +++ b/src/component/directive.ts @@ -455,7 +455,7 @@ QWeb.addDirective({ ); ctx.addLine(`const fiber = w${componentID}.__owl__.currentFiber;`); ctx.addLine( - `def${defID}.then(function () {if (w${componentID}.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; ${createHook}});` + `def${defID}.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; ${createHook}});` ); if (registerCode) { ctx.addLine(registerCode); diff --git a/tests/__snapshots__/animations.test.ts.snap b/tests/__snapshots__/animations.test.ts.snap index 29883063..a60216e8 100644 --- a/tests/__snapshots__/animations.test.ts.snap +++ b/tests/__snapshots__/animations.test.ts.snap @@ -35,7 +35,7 @@ exports[`animations t-transition combined with component 1`] = ` }; utils.transitionRemove(vn, 'chimay', finalize);}}}); const fiber = w3.__owl__.currentFiber; - def2.then(function () {if (w3.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); + def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); c1.push(pvnode); w3.__owl__.pvnode = pvnode; } @@ -80,7 +80,7 @@ exports[`animations t-transition combined with t-component and t-if 1`] = ` }; utils.transitionRemove(vn, 'chimay', finalize);}}}); const fiber = w3.__owl__.currentFiber; - def2.then(function () {if (w3.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); + def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); c1.push(pvnode); w3.__owl__.pvnode = pvnode; } diff --git a/tests/component/__snapshots__/component.test.ts.snap b/tests/component/__snapshots__/component.test.ts.snap index 2702de41..ffe6d669 100644 --- a/tests/component/__snapshots__/component.test.ts.snap +++ b/tests/component/__snapshots__/component.test.ts.snap @@ -61,7 +61,7 @@ exports[`basic widget properties reconciliation alg works for t-foreach in t-for let def8 = w9.__prepare(extra.fiber, undefined, undefined, sibling); let pvnode = h('dummy', {key: templateId9, hook: {insert(vn) { let nvn=w9.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w9.destroy();}}}); const fiber = w9.__owl__.currentFiber; - def8.then(function () {if (w9.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); + def8.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); c1.push(pvnode); w9.__owl__.pvnode = pvnode; } @@ -105,7 +105,7 @@ exports[`class and style attributes with t-component dynamic t-att-style is prop let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); const fiber = w3.__owl__.currentFiber; - def2.then(function () {if (w3.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.style = _5;}};}); + def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.style = _5;}};}); c1.push(pvnode); w3.__owl__.pvnode = pvnode; } @@ -150,7 +150,7 @@ exports[`class and style attributes with t-component t-att-class is properly add let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;context.__owl__.refs[ref5] = w3;},remove() {},destroy(vn) {w3.destroy();delete context.__owl__.refs[ref5];}}}); const fiber = w3.__owl__.currentFiber; - def2.then(function () {if (w3.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){}};}); + def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){}};}); c1.push(pvnode); w3.__owl__.pvnode = pvnode; } @@ -210,7 +210,7 @@ exports[`class and style attributes with t-component t-att-class is properly add let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;context.__owl__.refs[ref5] = w3;},remove() {},destroy(vn) {w3.destroy();delete context.__owl__.refs[ref5];}}}); const fiber = w3.__owl__.currentFiber; - def2.then(function () {if (w3.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){}};}); + def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){}};}); c1.push(pvnode); w3.__owl__.pvnode = pvnode; } @@ -282,7 +282,7 @@ exports[`composition sub components with some state rendered in a loop 1`] = ` let def6 = w7.__prepare(extra.fiber, undefined, undefined, sibling); let pvnode = h('dummy', {key: templateId7, hook: {insert(vn) { let nvn=w7.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w7.destroy();}}}); const fiber = w7.__owl__.currentFiber; - def6.then(function () {if (w7.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); + def6.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); c1.push(pvnode); w7.__owl__.pvnode = pvnode; } @@ -324,7 +324,7 @@ exports[`composition t-component with dynamic value 1`] = ` let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); const fiber = w3.__owl__.currentFiber; - def2.then(function () {if (w3.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); + def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); c1.push(pvnode); w3.__owl__.pvnode = pvnode; } @@ -365,7 +365,7 @@ exports[`composition t-component with dynamic value 2 1`] = ` let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); const fiber = w3.__owl__.currentFiber; - def2.then(function () {if (w3.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); + def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); c1.push(pvnode); w3.__owl__.pvnode = pvnode; } @@ -406,7 +406,7 @@ exports[`dynamic t-props basic use 1`] = ` let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); const fiber = w3.__owl__.currentFiber; - def2.then(function () {if (w3.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); + def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); c1.push(pvnode); w3.__owl__.pvnode = pvnode; } @@ -451,7 +451,7 @@ exports[`other directives with t-component t-on with getter as handler 1`] = ` let def3 = w4.__prepare(extra.fiber, undefined, undefined, sibling); let pvnode = h('dummy', {key: templateId4, hook: {insert(vn) { let nvn=w4.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}}); const fiber = w4.__owl__.currentFiber; - def3.then(function () {if (w4.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['handler'];if (fn) { fn.call(owner, e); } else { owner.handler; }});}};}); + def3.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['handler'];if (fn) { fn.call(owner, e); } else { owner.handler; }});}};}); c1.push(pvnode); w4.__owl__.pvnode = pvnode; } @@ -492,7 +492,7 @@ exports[`other directives with t-component t-on with handler bound to argument 1 let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); const fiber = w3.__owl__.currentFiber; - def2.then(function () {if (w3.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['onEv'];if (fn) { fn.call(owner, 3, e); } else { owner.onEv; }});}};}); + def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['onEv'];if (fn) { fn.call(owner, 3, e); } else { owner.onEv; }});}};}); c1.push(pvnode); w3.__owl__.pvnode = pvnode; } @@ -533,7 +533,7 @@ exports[`other directives with t-component t-on with handler bound to empty obje let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); const fiber = w3.__owl__.currentFiber; - def2.then(function () {if (w3.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['onEv'];if (fn) { fn.call(owner, {}, e); } else { owner.onEv; }});}};}); + def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['onEv'];if (fn) { fn.call(owner, {}, e); } else { owner.onEv; }});}};}); c1.push(pvnode); w3.__owl__.pvnode = pvnode; } @@ -574,7 +574,7 @@ exports[`other directives with t-component t-on with handler bound to empty obje let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); const fiber = w3.__owl__.currentFiber; - def2.then(function () {if (w3.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['onEv'];if (fn) { fn.call(owner, {}, e); } else { owner.onEv; }});}};}); + def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['onEv'];if (fn) { fn.call(owner, {}, e); } else { owner.onEv; }});}};}); c1.push(pvnode); w3.__owl__.pvnode = pvnode; } @@ -615,7 +615,7 @@ exports[`other directives with t-component t-on with handler bound to object 1`] let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); const fiber = w3.__owl__.currentFiber; - def2.then(function () {if (w3.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['onEv'];if (fn) { fn.call(owner, {val:3}, e); } else { owner.onEv; }});}};}); + def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['onEv'];if (fn) { fn.call(owner, {val:3}, e); } else { owner.onEv; }});}};}); c1.push(pvnode); w3.__owl__.pvnode = pvnode; } @@ -660,7 +660,7 @@ exports[`other directives with t-component t-on with inline statement 1`] = ` let def3 = w4.__prepare(extra.fiber, undefined, undefined, sibling); let pvnode = h('dummy', {key: templateId4, hook: {insert(vn) { let nvn=w4.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}}); const fiber = w4.__owl__.currentFiber; - def3.then(function () {if (w4.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['state.counter++'];if (fn) { fn.call(owner, e); } else { owner.state.counter++; }});}};}); + def3.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['state.counter++'];if (fn) { fn.call(owner, e); } else { owner.state.counter++; }});}};}); c1.push(pvnode); w4.__owl__.pvnode = pvnode; } @@ -701,7 +701,7 @@ exports[`other directives with t-component t-on with no handler (only modifiers) let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); const fiber = w3.__owl__.currentFiber; - def2.then(function () {if (w3.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['onEv'];if (fn) { fn.call(owner, e); } else { owner.onEv; }});}};}); + def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['onEv'];if (fn) { fn.call(owner, e); } else { owner.onEv; }});}};}); c1.push(pvnode); w3.__owl__.pvnode = pvnode; } @@ -742,7 +742,7 @@ exports[`other directives with t-component t-on with prevent and self modifiers let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); const fiber = w3.__owl__.currentFiber; - def2.then(function () {if (w3.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {e.preventDefault();if (e.target !== vn.elm) {return}const fn = owner['onEv'];if (fn) { fn.call(owner, e); } else { owner.onEv; }});}};}); + def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {e.preventDefault();if (e.target !== vn.elm) {return}const fn = owner['onEv'];if (fn) { fn.call(owner, e); } else { owner.onEv; }});}};}); c1.push(pvnode); w3.__owl__.pvnode = pvnode; } @@ -783,7 +783,7 @@ exports[`other directives with t-component t-on with self and prevent modifiers let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); const fiber = w3.__owl__.currentFiber; - def2.then(function () {if (w3.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (e.target !== vn.elm) {return}e.preventDefault();const fn = owner['onEv'];if (fn) { fn.call(owner, e); } else { owner.onEv; }});}};}); + def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (e.target !== vn.elm) {return}e.preventDefault();const fn = owner['onEv'];if (fn) { fn.call(owner, e); } else { owner.onEv; }});}};}); c1.push(pvnode); w3.__owl__.pvnode = pvnode; } @@ -824,7 +824,7 @@ exports[`other directives with t-component t-on with self modifier 1`] = ` let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); const fiber = w3.__owl__.currentFiber; - def2.then(function () {if (w3.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev-1', function (e) {const fn = owner['onEv1'];if (fn) { fn.call(owner, e); } else { owner.onEv1; }});vn.elm.addEventListener('ev-2', function (e) {if (e.target !== vn.elm) {return}const fn = owner['onEv2'];if (fn) { fn.call(owner, e); } else { owner.onEv2; }});}};}); + def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev-1', function (e) {const fn = owner['onEv1'];if (fn) { fn.call(owner, e); } else { owner.onEv1; }});vn.elm.addEventListener('ev-2', function (e) {if (e.target !== vn.elm) {return}const fn = owner['onEv2'];if (fn) { fn.call(owner, e); } else { owner.onEv2; }});}};}); c1.push(pvnode); w3.__owl__.pvnode = pvnode; } @@ -865,7 +865,7 @@ exports[`other directives with t-component t-on with stop and/or prevent modifie let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); const fiber = w3.__owl__.currentFiber; - def2.then(function () {if (w3.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev-1', function (e) {e.stopPropagation();const fn = owner['onEv1'];if (fn) { fn.call(owner, e); } else { owner.onEv1; }});vn.elm.addEventListener('ev-2', function (e) {e.preventDefault();const fn = owner['onEv2'];if (fn) { fn.call(owner, e); } else { owner.onEv2; }});vn.elm.addEventListener('ev-3', function (e) {e.stopPropagation();e.preventDefault();const fn = owner['onEv3'];if (fn) { fn.call(owner, e); } else { owner.onEv3; }});}};}); + def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev-1', function (e) {e.stopPropagation();const fn = owner['onEv1'];if (fn) { fn.call(owner, e); } else { owner.onEv1; }});vn.elm.addEventListener('ev-2', function (e) {e.preventDefault();const fn = owner['onEv2'];if (fn) { fn.call(owner, e); } else { owner.onEv2; }});vn.elm.addEventListener('ev-3', function (e) {e.stopPropagation();e.preventDefault();const fn = owner['onEv3'];if (fn) { fn.call(owner, e); } else { owner.onEv3; }});}};}); c1.push(pvnode); w3.__owl__.pvnode = pvnode; } @@ -925,7 +925,7 @@ exports[`random stuff/miscellaneous snapshotting compiled code 1`] = ` let def3 = w4.__prepare(extra.fiber, undefined, undefined, sibling); let pvnode = h('dummy', {key: templateId4, hook: {insert(vn) { let nvn=w4.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}}); const fiber = w4.__owl__.currentFiber; - def3.then(function () {if (w4.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); + def3.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); c1.push(pvnode); w4.__owl__.pvnode = pvnode; } @@ -983,7 +983,7 @@ exports[`random stuff/miscellaneous t-on with handler bound to dynamic argument let def6 = w7.__prepare(extra.fiber, undefined, undefined, sibling); let pvnode = h('dummy', {key: templateId7, hook: {insert(vn) { let nvn=w7.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w7.destroy();}}}); const fiber = w7.__owl__.currentFiber; - def6.then(function () {if (w7.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['onEv'];if (fn) { fn.call(owner, arg9, e); } else { owner.onEv; }});}};}); + def6.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['onEv'];if (fn) { fn.call(owner, arg9, e); } else { owner.onEv; }});}};}); c1.push(pvnode); w7.__owl__.pvnode = pvnode; } @@ -1231,7 +1231,7 @@ exports[`t-slot directive can define and call slots 1`] = ` let def2 = w3.__prepare(extra.fiber, {}, undefined, sibling); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); const fiber = w3.__owl__.currentFiber; - def2.then(function () {if (w3.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); + def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); c1.push(pvnode); w3.__owl__.pvnode = pvnode; } @@ -1477,7 +1477,7 @@ exports[`t-slot directive slots are rendered with proper context, part 2 2`] = ` let def8 = w9.__prepare(extra.fiber, Object.assign({}, scope), undefined, sibling); let pvnode = h('dummy', {key: templateId9, hook: {insert(vn) { let nvn=w9.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w9.destroy();}}}); const fiber = w9.__owl__.currentFiber; - def8.then(function () {if (w9.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); + def8.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); c7.push(pvnode); w9.__owl__.pvnode = pvnode; } @@ -1581,7 +1581,7 @@ exports[`t-slot directive slots are rendered with proper context, part 3 2`] = ` let def9 = w10.__prepare(extra.fiber, Object.assign({}, scope), {_8}, sibling); let pvnode = h('dummy', {key: templateId10, hook: {insert(vn) { let nvn=w10.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w10.destroy();}}}); const fiber = w10.__owl__.currentFiber; - def9.then(function () {if (w10.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); + def9.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); c7.push(pvnode); w10.__owl__.pvnode = pvnode; } @@ -1639,7 +1639,7 @@ exports[`t-slot directive slots are rendered with proper context, part 4 1`] = ` let def3 = w4.__prepare(extra.fiber, {}, {_2}, sibling); let pvnode = h('dummy', {key: templateId4, hook: {insert(vn) { let nvn=w4.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}}); const fiber = w4.__owl__.currentFiber; - def3.then(function () {if (w4.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); + def3.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); c1.push(pvnode); w4.__owl__.pvnode = pvnode; } @@ -1714,7 +1714,7 @@ exports[`top level sub widgets basic use 1`] = ` let def1 = w2.__prepare(extra.fiber, undefined, undefined, sibling); let pvnode = h('dummy', {key: templateId2, hook: {insert(vn) { let nvn=w2.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w2.destroy();}}}); const fiber = w2.__owl__.currentFiber; - def1.then(function () {if (w2.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); + def1.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); utils.defineProxy(vn4, pvnode); w2.__owl__.pvnode = pvnode; } @@ -1757,7 +1757,7 @@ exports[`top level sub widgets can select a sub widget 1`] = ` let def1 = w2.__prepare(extra.fiber, undefined, undefined, sibling); let pvnode = h('dummy', {key: templateId2, hook: {insert(vn) { let nvn=w2.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w2.destroy();}}}); const fiber = w2.__owl__.currentFiber; - def1.then(function () {if (w2.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); + def1.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); utils.defineProxy(vn4, pvnode); w2.__owl__.pvnode = pvnode; } @@ -1787,7 +1787,7 @@ exports[`top level sub widgets can select a sub widget 1`] = ` let def5 = w6.__prepare(extra.fiber, undefined, undefined, sibling); let pvnode = h('dummy', {key: templateId6, hook: {insert(vn) { let nvn=w6.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w6.destroy();}}}); const fiber = w6.__owl__.currentFiber; - def5.then(function () {if (w6.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); + def5.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); utils.defineProxy(vn8, pvnode); w6.__owl__.pvnode = pvnode; } diff --git a/tests/component/__snapshots__/props_validation.test.ts.snap b/tests/component/__snapshots__/props_validation.test.ts.snap index 4fdc9598..28e6f749 100644 --- a/tests/component/__snapshots__/props_validation.test.ts.snap +++ b/tests/component/__snapshots__/props_validation.test.ts.snap @@ -32,7 +32,7 @@ exports[`props validation props are validated in dev mode (code snapshot) 1`] = let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); const fiber = w3.__owl__.currentFiber; - def2.then(function () {if (w3.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); + def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); c1.push(pvnode); w3.__owl__.pvnode = pvnode; } diff --git a/tests/component/component.test.ts b/tests/component/component.test.ts index c7acf00e..28c4f34b 100644 --- a/tests/component/component.test.ts +++ b/tests/component/component.test.ts @@ -3301,6 +3301,68 @@ describe("async rendering", () => { await nextTick(); expect(fixture.innerHTML).toBe("
a2a2

a2b2

"); }); + + test("concurrent renderings scenario 10", async () => { + // Here is the global idea of this scenario: + // A + // | + // B <- async willUpdateProps + // ----- <- conditional (initialy false) + // | + // C <- async willStart + // Render A and B normally + // Change the condition on B to trigger a re-rendering with C (async willStart) + // Change the state on A to trigger a global re-rendering, which is blocked + // in B (async willUpdateProps) + // Resolve the willStart of C: the first re-rendering has been cancelled by + // the global re-rendering, but handlers waiting for the rendering promise to + // resolve might execute and we don't want them to crash/do anything + const defB = makeDeferred(); + const defC = makeDeferred(); + let stateB; + class ComponentC extends Component { + static template = xml``; + willStart() { + return defC; + } + } + ComponentC.prototype.__render = jest.fn(ComponentC.prototype.__render); + class ComponentB extends Component { + static template = xml`

`; + state = useState({ hasChild: false }); + static components = { ComponentC }; + constructor(parent, props) { + super(parent, props); + stateB = this.state; + } + willUpdateProps() { + return defB; + } + } + class ComponentA extends Component { + static template = xml`
`; + static components = { ComponentB }; + state = useState({ value: 1 }); + } + + const componentA = new ComponentA(); + await componentA.mount(fixture); + expect(fixture.innerHTML).toBe("

"); + + stateB.hasChild = true; + await nextTick(); + expect(fixture.innerHTML).toBe("

"); + + componentA.state.value = 2; + defC.resolve(); + await nextTick(); + expect(fixture.innerHTML).toBe("

"); + + defB.resolve(); + await nextTick(); + expect(fixture.innerHTML).toBe("

2

"); + expect(ComponentC.prototype.__render).toHaveBeenCalledTimes(1); + }); }); describe("widget and observable state", () => { diff --git a/tests/router/__snapshots__/route_component.test.ts.snap b/tests/router/__snapshots__/route_component.test.ts.snap index 3c38bd1f..3e6ac6e3 100644 --- a/tests/router/__snapshots__/route_component.test.ts.snap +++ b/tests/router/__snapshots__/route_component.test.ts.snap @@ -35,7 +35,7 @@ exports[`RouteComponent can render simple cases 1`] = ` let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); const fiber = w3.__owl__.currentFiber; - def2.then(function () {if (w3.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); + def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); utils.defineProxy(vn5, pvnode); w3.__owl__.pvnode = pvnode; }