diff --git a/docs/change-log.md b/docs/change-log.md index 8f9449cf..ba8196ba 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -64,6 +64,7 @@ - docs: tweaks: ([#2104](https://github.com/MithrilJS/mithril.js/pull/2104) [@mikeyb](https://github.com/mikeyb), [#2205](https://github.com/MithrilJS/mithril.js/pull/2205), [@cavemansspa](https://github.com/cavemansspa), [#2265](https://github.com/MithrilJS/mithril.js/pull/2265), [@isiahmeadows](https://github.com/isiahmeadows)) - render/core: avoid touching `Object.prototype.__proto__` setter with `key: "__proto__"` in certain situations ([#2251](https://github.com/MithrilJS/mithril.js/pull/2251)) - render/core: Vnodes stored in the dom node supplied to `m.render()` are now normalized [#2266](https://github.com/MithrilJS/mithril.js/pull/2266) +- render/core: `blur` handlers are now removed before removing the DOM vnodes, but after firing hooks ([#2286](https://github.com/MithrilJS/mithril.js/pull/2286)) --- diff --git a/render/render.js b/render/render.js index 44ce338e..90fe272d 100644 --- a/render/render.js +++ b/render/render.js @@ -673,6 +673,13 @@ module.exports = function($window) { if (child != null) onremove(child) } } + // Chrome emits a `blur` event on children when they are removed, + // but *before* they dereference their parent... + // https://stackoverflow.com/questions/21926083/failed-to-execute-removechild-on-node#22934552 + // https://github.com/MithrilJS/mithril.js/issues/1771 + if (vnode.events != null && vnode.events.onblur != null) { + vnode.dom.removeEventListener("blur", vnode.events, false) + } } } diff --git a/render/tests/test-event.js b/render/tests/test-event.js index 02246e18..9fd98c89 100644 --- a/render/tests/test-event.js +++ b/render/tests/test-event.js @@ -324,4 +324,16 @@ o.spec("event", function() { o(onevent.args[0].type).equals("transitionend") o(onevent.args[0].target).equals(div.dom) }) + + o("doesn't fire blur on removed nodes", function() { + var spy = o.spy() + var div = {tag: "div", attrs: {onblur: spy}} + + render(root, [div]) + div.dom.focus() + render(root, []) + + o(spy.callCount).equals(0) + o(onevent.callCount).equals(0) + }) }) diff --git a/test-utils/domMock.js b/test-utils/domMock.js index ce494683..2f6fd2b6 100644 --- a/test-utils/domMock.js +++ b/test-utils/domMock.js @@ -87,6 +87,13 @@ module.exports = function(options) { var index = this.childNodes.indexOf(child) if (index > -1) { this.childNodes.splice(index, 1) + // Yes, *this* is the behavior Chrome has and what FF is considering in + // https://bugzilla.mozilla.org/show_bug.cgi?id=559561 + if (activeElement === child) { + var blur = $window.document.createEvent() + blur.initEvent("blur") + child.dispatchEvent(blur) + } child.parentNode = null } else throw new TypeError("Failed to execute 'removeChild'") diff --git a/test-utils/tests/test-domMock.js b/test-utils/tests/test-domMock.js index 805f7b92..350091d1 100644 --- a/test-utils/tests/test-domMock.js +++ b/test-utils/tests/test-domMock.js @@ -208,6 +208,19 @@ o.spec("domMock", function() { try {parent.removeChild(child)} catch (e) {done()} }) + o("invokes blur on child if focused", function() { + var parent = $document.createElement("div") + var child = $document.createElement("a") + var spy = o.spy() + parent.appendChild(child) + child.addEventListener("blur", spy, false) + child.focus() + parent.removeChild(child) + + o(spy.callCount).equals(1) + o(spy.args[0].type).equals("blur") + o(spy.args[0].target).equals(child) + }) }) o.spec("insertBefore", function() {