Permalink
Browse files

Prevent interruption of ready callbacks

Note: This commit breaks assumption that ready callbacks called
syncronously and immediatelly when DOM already parsed at the moment of
adding the callback. I hope this assumption isn't very common,
considering that jQuery now doesn't provide such guarantee (see [3]).

HTML [spec][1] stated that one script can interrupt another one:

> Otherwise
> Immediately execute the script block, even if other scripts are already executing.

I'm not sure which set of attributes should have scripts tags to make
interruption possible. I guess if one script has `src` and `async`
attributes and another one has no `src` attribute (inline script), than
it is.

As I understand Zepto functions (as well as most of JS code at all)
assume that interpreter is single-thread and doesn't interrupt scripts
execution. That means that calling any Zepto functions is not safe in
general. Since most of Zepto-related code likely placed inside `ready`
callbacks, it's worth to protect the callbacks from interruption.

I guess it can be achived by piping callback execution over task/events
loop, i. e. wrapping callback call into `setTimeout` when it's not piped
already via event queue.

That's how jQuery [behaves][2]. By the way, they have one more
[argument][3] to call callbacks asynchronously.

[1]: https://html.spec.whatwg.org/multipage/scripting.html#script-processing-model
[2]: https://github.com/jquery/jquery/blob/7bb62bb3ae771fc57cc62ee14bd10d94680efb4f/src/core/ready-no-deferred.js#L94
[3]: jquery/jquery#1823

Build size
----------

Calculated for a build with the default modules set:

```
zepto.js      58726 -> 58755 (+28 bytes)
zepto.min.js  26427 -> 26453 (+26 bytes)
zepto.min.gz   9804 ->  9803 ( -1 bytes)
```
  • Loading branch information...
1 parent 24274f5 commit 474f94db724108341f91c2d43e98ada99d90f62c @Totktonada Totktonada committed Oct 31, 2016
Showing with 11 additions and 9 deletions.
  1. +1 −1 src/zepto.js
  2. +10 −8 test/zepto.html
View
@@ -439,7 +439,7 @@ var Zepto = (function() {
// don't use "interactive" on IE <= 10 (it can fired premature)
if (document.readyState === "complete" ||
(document.readyState !== "loading" && !document.documentElement.doScroll))
- callback($)
+ setTimeout(function(){ callback($) }, 0)
else
document.addEventListener("DOMContentLoaded", function(){ callback($) }, false)
return this
View
@@ -2649,8 +2649,10 @@
},
testDocumentReady: function (t) {
- // Check that if document is already loaded, ready() immediately executes callback
- var arg1, arg2, arg3, arg4, fired = false
+ // Check that ready() callback fired and get Zepto as an argument
+ var arg1, arg2, arg3, arg4
+
+ t.pause()
$(function (Z1) {
arg1 = Z1
$(document).ready(function (Z2) {
@@ -2659,16 +2661,16 @@
arg3 = Z3
$(document).on('foo ready bar', function (Z4) {
arg4 = Z4
- fired = true
+ t.resume(function(){
+ t.assertIdentical(Zepto, arg1)
+ t.assertIdentical(Zepto, arg2)
+ t.assertIdentical(Zepto, arg3)
+ t.assertIdentical(Zepto, arg4)
+ })
})
})
})
})
- t.assertTrue(fired)
- t.assertIdentical(Zepto, arg1)
- t.assertIdentical(Zepto, arg2)
- t.assertIdentical(Zepto, arg3)
- t.assertIdentical(Zepto, arg4)
},
testSlice: function (t) {

0 comments on commit 474f94d

Please sign in to comment.