This repository has been archived by the owner on Dec 16, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 522
/
eventloop.coffee
160 lines (148 loc) · 5.43 KB
/
eventloop.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
URL = require("url")
# Handles the Window event loop, timers and pending requests.
class EventLoop
constructor: (browser)->
timers = {}
lastHandle = 0
# ### window.setTimeout(fn, delay) => Number
#
# Implements window.setTimeout using event queue
this.setTimeout = (fn, delay)->
timer =
when: browser.clock + delay
timeout: true
fire: =>
try
if typeof fn == "function"
fn.apply this
else
browser.evaluate fn
finally
delete timers[handle]
handle = ++lastHandle
timers[handle] = timer
handle
# ### window.setInterval(fn, delay) => Number
#
# Implements window.setInterval using event queue
this.setInterval = (fn, delay)->
timer =
when: browser.clock + delay
interval: true
fire: =>
try
if typeof fn == "function"
fn.apply this
else
browser.evaluate fn
finally
timer.when = browser.clock + delay
handle = ++lastHandle
timers[handle] = timer
handle
# ### window.clearTimeout(timeout)
#
# Implements window.clearTimeout using event queue
this.clearTimeout = (handle)-> delete timers[handle] if timers[handle]?.timeout
# ### window.clearInterval(interval)
#
# Implements window.clearInterval using event queue
this.clearInterval = (handle)-> delete timers[handle] if timers[handle]?.interval
# Size of processing queue (number of ongoing tasks).
processing = 0
# Requests on wait that cannot be handled yet: there's no event in the
# queue, but we anticipate one (in-progress XHR request).
waiting = []
# Called when done processing a request, and if we're done processing all
# requests, wake up any waiting callbacks.
wakeUp = ->
if --processing == 0
process.nextTick waiter while waiter = waiting.pop()
# ### perform(fn)
#
# Run the function as part of the event queue (calls to `wait` will wait for
# this function to complete). Function can be anything and is called
# synchronous with a `done` function; when it's done processing, it lets the
# event loop know by calling the done function.
this.perform = (fn)->
++processing
fn wakeUp
return
# ### wait(window, terminate, callback, intervals)
#
# Process all events from the queue. This method returns immediately, events
# are processed in the background. When all events are exhausted, it calls
# the callback with null, window; if any event fails, it calls the callback
# with the exception.
#
# Events include timeout, interval and XHR onreadystatechange. DOM events
# are handled synchronously.
this.wait = (window, terminate, callback, intervals)->
process.nextTick =>
earliest = null
for handle, timer of timers
continue if timer.interval && intervals == false
earliest = timer if !earliest || timer.when < earliest.when
if earliest
intervals = false
event = ->
browser.clock = earliest.when if browser.clock < earliest.when
earliest.fire()
if event
try
event()
done = false
if typeof terminate is "number"
--terminate
done = true if terminate <= 0
else if typeof terminate is "function"
done = true if terminate.call(window) == false
if done
process.nextTick ->
browser.emit "done", browser
callback null, window if callback
else
@wait window, terminate, callback, intervals
catch err
browser.emit "error", err
callback err, window if callback
else if processing > 0
waiting.push => @wait window, terminate, callback, intervals
else
browser.emit "done", browser
callback null, window if callback
# Used internally for the duration of an internal request (loading
# resource, XHR). Also collects request/response for debugging.
#
# Function is called with request object and the function to be called
# next. After storing the request, that function is called with a single
# argument, a done callback. It must call the done callback when it
# completes processing, passing error and response arguments.
#
# See also `processing`.
this.request = (request, fn)->
url = request.url.toString()
browser.log -> "#{request.method} #{url}"
pending = browser.record request
++processing
fn (err, response)->
if err
browser.log -> "Error loading #{url}: #{err}"
pending.error = err
else
browser.log -> "#{request.method} #{url} => #{response.status}"
pending.response = response
wakeUp()
this.extend = (window)=>
for fn in ["setTimeout", "setInterval", "clearTimeout", "clearInterval"]
window[fn] = this[fn]
window.perform = this.perform
window.wait = (terminate, callback)=> this.wait(window, terminate, callback)
window.request = this.request
this.dump = ()->
[ "The time: #{browser.clock}",
"Timers: #{timers.length}",
"Processing: #{processing}",
"Waiting: #{waiting.length}" ]
exports.use = (browser)->
return new EventLoop(browser)