From ccde633e92f677ab817327c882ef33d9a8024458 Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Thu, 10 Jul 2014 08:23:42 -0400 Subject: [PATCH] fix double redraw when events fire simultaneously --- docs/change-log.md | 9 +++ docs/getting-started.md | 2 - mithril.js | 25 +++---- tests/mithril-tests.js | 159 ++++++++++++++++++++-------------------- tests/mock.js | 6 -- 5 files changed, 99 insertions(+), 102 deletions(-) diff --git a/docs/change-log.md b/docs/change-log.md index b4f5e5be..394764b1 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -1,5 +1,14 @@ ## Change Log +[v0.1.18](/mithril/archive/v0.1.19) - maintenance + +### Bug Fixes: + +- fix double redraw when events fire simultaneously +- prevent routes from reverting to original route in some cases + +--- + [v0.1.18](/mithril/archive/v0.1.18) - maintenance ### Bug Fixes: diff --git a/docs/getting-started.md b/docs/getting-started.md index 5fb5cc9e..574e2a70 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -376,8 +376,6 @@ m.module(document, todo); Mithril's auto-redrawing system keeps track of controller stability, and only redraws the view once it detects that the controller has finished running all of its code, including asynchronous AJAX payloads. -Also note that this mechanism itself is not asynchronous if it doesn't need to be: Mithril does not need to wait for the next browser repaint frame to redraw - it doesn't even need to wait for the document ready event on the first redraw - it will redraw immediately upon script completion, if able to. - --- ### Summary diff --git a/mithril.js b/mithril.js index 0c063d2b..0a9b2f8f 100644 --- a/mithril.js +++ b/mithril.js @@ -1,7 +1,7 @@ Mithril = m = new function app(window) { var type = {}.toString var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/ - + function m() { var args = arguments var hasAttrs = type.call(args[1]) == "[object Object]" && !("tag" in args[1]) && !("subtree" in args[1]) @@ -197,6 +197,7 @@ Mithril = m = new function app(window) { return cached } function setAttributes(node, tag, dataAttrs, cachedAttrs, namespace) { + var groups = {} for (var attrName in dataAttrs) { var dataAttr = dataAttrs[attrName] var cachedAttr = cachedAttrs[attrName] @@ -267,7 +268,7 @@ Mithril = m = new function app(window) { } return nodes } - function autoredraw(callback, object) { + function autoredraw(callback, object, group) { return function(e) { e = e || event m.startComputation() @@ -316,7 +317,7 @@ Mithril = m = new function app(window) { return value } - var roots = [], modules = [], controllers = [], now = 0, lastRedraw = 0, lastRedrawId = 0, computePostRedrawHook = null + var roots = [], modules = [], controllers = [], lastRedrawId = 0, computePostRedrawHook = null m.module = function(root, module) { var index = roots.indexOf(root) if (index < 0) index = roots.length @@ -336,14 +337,10 @@ Mithril = m = new function app(window) { } } m.redraw = function() { - now = window.performance && window.performance.now ? window.performance.now() : new window.Date().getTime() - if (now - lastRedraw > 16) redraw() - else { - var cancel = window.cancelAnimationFrame || window.clearTimeout - var defer = window.requestAnimationFrame || window.setTimeout - cancel(lastRedrawId) - lastRedrawId = defer(redraw, 0) - } + var cancel = window.cancelAnimationFrame || window.clearTimeout + var defer = window.requestAnimationFrame || window.setTimeout + cancel(lastRedrawId) + lastRedrawId = defer(redraw, 0) } function redraw() { for (var i = 0; i < roots.length; i++) { @@ -353,7 +350,6 @@ Mithril = m = new function app(window) { computePostRedrawHook() computePostRedrawHook = null } - lastRedraw = now } var pendingRequests = 0 @@ -391,7 +387,6 @@ Mithril = m = new function app(window) { } computePostRedrawHook = setScroll window[listener]() - currentRoute = normalizeRoute(window.location[m.route.mode]) } else if (arguments[0].addEventListener) { var element = arguments[0] @@ -410,7 +405,7 @@ Mithril = m = new function app(window) { if (querystring) currentRoute += (currentRoute.indexOf("?") === -1 ? "?" : "&") + querystring var shouldReplaceHistoryEntry = (arguments.length == 3 ? arguments[2] : arguments[1]) === true - + if (window.history.pushState) { computePostRedrawHook = function() { window.history[shouldReplaceHistoryEntry ? "replaceState" : "pushState"](null, window.document.title, modes[m.route.mode] + currentRoute) @@ -585,7 +580,7 @@ Mithril = m = new function app(window) { var maybeXhr = options.config(xhr, options) if (maybeXhr !== undefined) xhr = maybeXhr } - xhr.send(options.data) + xhr.send(options.method == "GET" ? "" : options.data) return xhr } function bindData(xhrOptions, data, serialize) { diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index c2b00dad..316b0c03 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -28,7 +28,7 @@ function testMithril(mock) { //m.module test(function() { - mock.performance.$elapse(50) + mock.requestAnimationFrame.$resolve() var root1 = mock.document.createElement("div") m.module(root1, { @@ -671,23 +671,22 @@ function testMithril(mock) { //m.redraw test(function() { - mock.performance.$elapse(50) //setup + mock.requestAnimationFrame.$resolve() //setup var controller var root = mock.document.createElement("div") m.module(root, { controller: function() {controller = this}, view: function(ctrl) {return ctrl.value} }) + mock.requestAnimationFrame.$resolve() + var valueBefore = root.childNodes[0].nodeValue controller.value = "foo" m.redraw() - var valueBefore = root.childNodes[0].nodeValue - mock.performance.$elapse(50) - m.redraw() - mock.performance.$elapse(50) //teardown + mock.requestAnimationFrame.$resolve() return valueBefore === "" && root.childNodes[0].nodeValue === "foo" }) test(function() { - mock.performance.$elapse(50) //setup + mock.requestAnimationFrame.$resolve() //setup var count = 0 var root = mock.document.createElement("div") m.module(root, { @@ -696,18 +695,18 @@ function testMithril(mock) { count++ } }) + mock.requestAnimationFrame.$resolve() //teardown m.redraw() m.redraw() m.redraw() - mock.performance.$elapse(50) m.redraw() - mock.performance.$elapse(50) //teardown + mock.requestAnimationFrame.$resolve() //teardown return count === 2 }) //m.route test(function() { - mock.performance.$elapse(50) //setup + mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") @@ -715,11 +714,11 @@ function testMithril(mock) { m.route(root, "/test1", { "/test1": {controller: function() {}, view: function() {return "foo"}} }) - mock.performance.$elapse(50) //teardown + mock.requestAnimationFrame.$resolve() //teardown return mock.location.search == "?/test1" && root.childNodes[0].nodeValue === "foo" }) test(function() { - mock.performance.$elapse(50) //setup + mock.requestAnimationFrame.$resolve() //setup mock.location.pathname = "/" var root = mock.document.createElement("div") @@ -727,11 +726,11 @@ function testMithril(mock) { m.route(root, "/test2", { "/test2": {controller: function() {}, view: function() {return "foo"}} }) - mock.performance.$elapse(50) //teardown + mock.requestAnimationFrame.$resolve() //teardown return mock.location.pathname == "/test2" && root.childNodes[0].nodeValue === "foo" }) test(function() { - mock.performance.$elapse(50) //setup + mock.requestAnimationFrame.$resolve() //setup mock.location.hash = "#" var root = mock.document.createElement("div") @@ -739,11 +738,11 @@ function testMithril(mock) { m.route(root, "/test3", { "/test3": {controller: function() {}, view: function() {return "foo"}} }) - mock.performance.$elapse(50) //teardown + mock.requestAnimationFrame.$resolve() //teardown return mock.location.hash == "#/test3" && root.childNodes[0].nodeValue === "foo" }) test(function() { - mock.performance.$elapse(50) //setup + mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") @@ -751,11 +750,11 @@ function testMithril(mock) { m.route(root, "/test4/foo", { "/test4/:test": {controller: function() {}, view: function() {return m.route.param("test")}} }) - mock.performance.$elapse(50) //teardown + mock.requestAnimationFrame.$resolve() //teardown return mock.location.search == "?/test4/foo" && root.childNodes[0].nodeValue === "foo" }) test(function() { - mock.performance.$elapse(50) //setup + mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var module = {controller: function() {}, view: function() {return m.route.param("test")}} @@ -767,14 +766,14 @@ function testMithril(mock) { "/test5/:test": module }) var paramValueBefore = m.route.param("test") - mock.performance.$elapse(50) + mock.requestAnimationFrame.$resolve() m.route("/") var paramValueAfter = m.route.param("test") - mock.performance.$elapse(50) //teardown + mock.requestAnimationFrame.$resolve() //teardown return mock.location.search == "?/" && paramValueBefore === "foo" && paramValueAfter === undefined }) test(function() { - mock.performance.$elapse(50) //setup + mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var module = {controller: function() {}, view: function() {return m.route.param("a1")}} @@ -786,15 +785,15 @@ function testMithril(mock) { "/test6/:a1": module }) var paramValueBefore = m.route.param("a1") - mock.performance.$elapse(50) + mock.requestAnimationFrame.$resolve() m.route("/") var paramValueAfter = m.route.param("a1") - mock.performance.$elapse(50) //teardown + mock.requestAnimationFrame.$resolve() //teardown return mock.location.search == "?/" && paramValueBefore === "foo" && paramValueAfter === undefined }) test(function() { //https://github.com/lhorie/mithril.js/issues/61 - mock.performance.$elapse(50) //setup + mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var module = {controller: function() {}, view: function() {return m.route.param("a1")}} @@ -806,14 +805,14 @@ function testMithril(mock) { "/test7/:a1": module }) var routeValueBefore = m.route() - mock.performance.$elapse(50) + mock.requestAnimationFrame.$resolve() m.route("/") var routeValueAfter = m.route() - mock.performance.$elapse(50) //teardown + mock.requestAnimationFrame.$resolve() //teardown return routeValueBefore === "/test7/foo" && routeValueAfter === "/" }) test(function() { - mock.performance.$elapse(50) //setup + mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") @@ -826,11 +825,11 @@ function testMithril(mock) { } } }) - mock.performance.$elapse(50) //teardown + mock.requestAnimationFrame.$resolve() //teardown return mock.location.search == "?/test8/foo/SEP/bar/baz" && root.childNodes[0].nodeValue === "foo_bar/baz" }) test(function() { - mock.performance.$elapse(50) //setup + mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") @@ -843,11 +842,11 @@ function testMithril(mock) { } } }) - mock.performance.$elapse(50) //teardown + mock.requestAnimationFrame.$resolve() //teardown return mock.location.search == "?/test9/foo/bar/SEP/baz" && root.childNodes[0].nodeValue === "foo/bar_baz" }) test(function() { - mock.performance.$elapse(50) //setup + mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") @@ -860,11 +859,11 @@ function testMithril(mock) { } } }) - mock.performance.$elapse(50) //teardown + mock.requestAnimationFrame.$resolve() //teardown return root.childNodes[0].nodeValue === "foo bar" }) test(function() { - mock.performance.$elapse(50) //setup + mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") @@ -873,13 +872,13 @@ function testMithril(mock) { "/": {controller: function() {}, view: function() {return "foo"}}, "/test11": {controller: function() {}, view: function() {return "bar"}} }) - mock.performance.$elapse(50) + mock.requestAnimationFrame.$resolve() m.route("/test11/") - mock.performance.$elapse(50) //teardown + mock.requestAnimationFrame.$resolve() //teardown return mock.location.search == "?/test11/" && root.childNodes[0].nodeValue === "bar" }) test(function() { - mock.performance.$elapse(50) //setup + mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") @@ -888,13 +887,13 @@ function testMithril(mock) { "/": {controller: function() {}, view: function() {}}, "/test12": {controller: function() {}, view: function() {}} }) - mock.performance.$elapse(50) + mock.requestAnimationFrame.$resolve() m.route("/test12?a=foo&b=bar") - mock.performance.$elapse(50) //teardown + mock.requestAnimationFrame.$resolve() //teardown return mock.location.search == "?/test12?a=foo&b=bar" && m.route.param("a") == "foo" && m.route.param("b") == "bar" }) test(function() { - mock.performance.$elapse(50) //setup + mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") @@ -903,13 +902,13 @@ function testMithril(mock) { "/": {controller: function() {}, view: function() {return "bar"}}, "/test13/:test": {controller: function() {}, view: function() {return m.route.param("test")}} }) - mock.performance.$elapse(50) + mock.requestAnimationFrame.$resolve() m.route("/test13/foo?test=bar") - mock.performance.$elapse(50) //teardown + mock.requestAnimationFrame.$resolve() //teardown return mock.location.search == "?/test13/foo?test=bar" && root.childNodes[0].nodeValue === "foo" }) test(function() { - mock.performance.$elapse(50) //setup + mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") @@ -918,13 +917,13 @@ function testMithril(mock) { "/": {controller: function() {}, view: function() {return "bar"}}, "/test14": {controller: function() {}, view: function() {return "foo"}} }) - mock.performance.$elapse(50) + mock.requestAnimationFrame.$resolve() m.route("/test14?test&test2=") - mock.performance.$elapse(50) //teardown + mock.requestAnimationFrame.$resolve() //teardown return mock.location.search == "?/test14?test&test2=" && m.route.param("test") === true && m.route.param("test2") === "" }) test(function() { - mock.performance.$elapse(50) //setup + mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") @@ -933,13 +932,13 @@ function testMithril(mock) { "/": {controller: function() {}, view: function() {}}, "/test12": {controller: function() {}, view: function() {}} }) - mock.performance.$elapse(50) + mock.requestAnimationFrame.$resolve() m.route("/test12", {a: "foo", b: "bar"}) - mock.performance.$elapse(50) //teardown + mock.requestAnimationFrame.$resolve() //teardown return mock.location.search == "?/test12?a=foo&b=bar" && m.route.param("a") == "foo" && m.route.param("b") == "bar" }) test(function() { - mock.performance.$elapse(50) //setup + mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") @@ -949,13 +948,13 @@ function testMithril(mock) { "/": {controller: function() {route1 = m.route()}, view: function() {}}, "/test13": {controller: function() {route2 = m.route()}, view: function() {}} }) - mock.performance.$elapse(50) + mock.requestAnimationFrame.$resolve() m.route("/test13") - mock.performance.$elapse(50) //teardown + mock.requestAnimationFrame.$resolve() //teardown return route1 == "/" && route2 == "/test13" }) test(function() { - mock.performance.$elapse(50) //setup + mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") @@ -976,13 +975,13 @@ function testMithril(mock) { }, "/test14": {controller: function() {}, view: function() {}} }) - mock.performance.$elapse(50) + mock.requestAnimationFrame.$resolve() m.route("/test14") - mock.performance.$elapse(50) //teardown + mock.requestAnimationFrame.$resolve() //teardown return unloaded == 1 }) test(function() { - mock.performance.$elapse(50) //setup + mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") @@ -1011,13 +1010,13 @@ function testMithril(mock) { } } }) - mock.performance.$elapse(50) + mock.requestAnimationFrame.$resolve() m.route("/test15") - mock.performance.$elapse(50) //teardown + mock.requestAnimationFrame.$resolve() //teardown return unloaded == 1 }) test(function() { - mock.performance.$elapse(50) //setup + mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") @@ -1043,13 +1042,13 @@ function testMithril(mock) { } } }) - mock.performance.$elapse(50) + mock.requestAnimationFrame.$resolve() m.route("/test16") - mock.performance.$elapse(50) //teardown + mock.requestAnimationFrame.$resolve() //teardown return unloaded == 1 }) test(function() { - mock.performance.$elapse(50) //setup + mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") @@ -1077,13 +1076,13 @@ function testMithril(mock) { } } }) - mock.performance.$elapse(50) + mock.requestAnimationFrame.$resolve() m.route("/test17") - mock.performance.$elapse(50) //teardown + mock.requestAnimationFrame.$resolve() //teardown return unloaded == 1 }) test(function() { - mock.performance.$elapse(50) //setup + mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") @@ -1109,13 +1108,13 @@ function testMithril(mock) { } } }) - mock.performance.$elapse(50) + mock.requestAnimationFrame.$resolve() m.route("/test18") - mock.performance.$elapse(50) //teardown + mock.requestAnimationFrame.$resolve() //teardown return unloaded == 1 }) test(function() { - mock.performance.$elapse(50) //setup + mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") @@ -1153,13 +1152,13 @@ function testMithril(mock) { } } }) - mock.performance.$elapse(50) + mock.requestAnimationFrame.$resolve() m.route("/test20") - mock.performance.$elapse(50) //teardown + mock.requestAnimationFrame.$resolve() //teardown return unloaded == 1 }) test(function() { - mock.performance.$elapse(50) //setup + mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") @@ -1196,13 +1195,13 @@ function testMithril(mock) { } } }) - mock.performance.$elapse(50) + mock.requestAnimationFrame.$resolve() m.route("/test21") - mock.performance.$elapse(50) //teardown + mock.requestAnimationFrame.$resolve() //teardown return unloaded == 1 }) test(function() { - mock.performance.$elapse(50) //setup + mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") @@ -1221,15 +1220,15 @@ function testMithril(mock) { } }, }) - mock.performance.$elapse(50) + mock.requestAnimationFrame.$resolve() var foo = root.childNodes[0].childNodes[0].nodeValue; m.route("/bar") - mock.performance.$elapse(50) //teardown + mock.requestAnimationFrame.$resolve() //teardown var bar = root.childNodes[0].childNodes[0].nodeValue; return (foo === "foo" && bar === "bar") }) test(function() { - mock.performance.$elapse(50) //setup + mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") @@ -1254,9 +1253,9 @@ function testMithril(mock) { } }, }) - mock.performance.$elapse(50) + mock.requestAnimationFrame.$resolve() m.route("/bar1") - mock.performance.$elapse(50) //teardown + mock.requestAnimationFrame.$resolve() //teardown return unloaded == 1 }) //end m.route @@ -1502,7 +1501,7 @@ function testMithril(mock) { //m.startComputation/m.endComputation test(function() { - mock.performance.$elapse(50) + mock.requestAnimationFrame.$resolve() var controller var root = mock.document.createElement("div") @@ -1511,11 +1510,13 @@ function testMithril(mock) { view: function(ctrl) {return ctrl.value} }) - mock.performance.$elapse(50) + mock.requestAnimationFrame.$resolve() m.startComputation() controller.value = "foo" m.endComputation() + mock.requestAnimationFrame.$resolve() + return root.childNodes[0].nodeValue === "foo" }) diff --git a/tests/mock.js b/tests/mock.js index 68797241..811208df 100644 --- a/tests/mock.js +++ b/tests/mock.js @@ -71,17 +71,11 @@ mock.window = new function() { child.parentNode = null } window.scrollTo = function() {} - window.performance = new function () { - var timestamp = 50 - this.$elapse = function(amount) {timestamp += amount} - this.now = function() {return timestamp} - } window.cancelAnimationFrame = function() {} window.requestAnimationFrame = function(callback) {window.requestAnimationFrame.$callback = callback} window.requestAnimationFrame.$resolve = function() { if (window.requestAnimationFrame.$callback) window.requestAnimationFrame.$callback() window.requestAnimationFrame.$callback = null - window.performance.$elapse(20) } window.XMLHttpRequest = new function() { var request = function() {