From 7be2ff5feb2e4584be7d666a19e85a22e7ade956 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Gerardy Date: Sun, 26 Mar 2017 22:07:49 +0200 Subject: [PATCH] Strengthen the self-return prevention logic (for recycled nodes and updates) --- render/render.js | 3 ++- render/tests/test-component.js | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/render/render.js b/render/render.js index 835c2f5f..5e238d56 100644 --- a/render/render.js +++ b/render/render.js @@ -119,12 +119,12 @@ module.exports = function($window) { if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks) initLifecycle(vnode.state, vnode, hooks) vnode.instance = Vnode.normalize(vnode.state.view(vnode)) + if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as argument") sentinel.$$reentrantLock$$ = null } function createComponent(parent, vnode, hooks, ns, nextSibling) { initComponent(vnode, hooks) if (vnode.instance != null) { - if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as arguments") var element = createNode(parent, vnode.instance, hooks, ns, nextSibling) vnode.dom = vnode.instance.dom vnode.domSize = vnode.dom != null ? vnode.instance.domSize : 0 @@ -317,6 +317,7 @@ module.exports = function($window) { initComponent(vnode, hooks) } else { vnode.instance = Vnode.normalize(vnode.state.view(vnode)) + if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as argument") if (vnode.attrs != null) updateLifecycle(vnode.attrs, vnode, hooks) updateLifecycle(vnode.state, vnode, hooks) } diff --git a/render/tests/test-component.js b/render/tests/test-component.js index a3429cee..8fccb9c3 100644 --- a/render/tests/test-component.js +++ b/render/tests/test-component.js @@ -260,8 +260,9 @@ o.spec("component", function() { o(root.childNodes.length).equals(0) }) - o("throws a custom error if it returns itself", function() { + o("throws a custom error if it returns itself when created", function() { // A view that returns its vnode would otherwise trigger an infinite loop + var threw = false var component = createComponent({ view: function(vnode) { return vnode @@ -271,10 +272,41 @@ o.spec("component", function() { render(root, [{tag: component}]) } catch (e) { + threw = true o(e instanceof Error).equals(true) // Call stack exception is a RangeError o(e instanceof RangeError).equals(false) } + o(threw).equals(true) + }) + o("throws a custom error if it returns itself when updated", function() { + // A view that returns its vnode would otherwise trigger an infinite loop + var threw = false + var init = true + var oninit = o.spy() + var component = createComponent({ + oninit: oninit, + view: function(vnode) { + if (init) return init = false + else return vnode + } + }) + render(root, [{tag: component}]) + + o(root.firstChild.nodeType).equals(3) + o(root.firstChild.nodeValue).equals("") + + try { + render(root, [{tag: component}]) + } + catch (e) { + threw = true + o(e instanceof Error).equals(true) + // Call stack exception is a RangeError + o(e instanceof RangeError).equals(false) + } + o(threw).equals(true) + o(oninit.callCount).equals(1) }) o("can update when returning fragments", function() { var component = createComponent({