|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 'use strict'; |
|
5 |
|
6 module.metadata = { |
|
7 "stability": "stable" |
|
8 }; |
|
9 |
|
10 const { CC, Cc, Ci } = require("chrome"); |
|
11 const { when: unload } = require("./system/unload"); |
|
12 |
|
13 const { TYPE_ONE_SHOT, TYPE_REPEATING_SLACK } = Ci.nsITimer; |
|
14 const Timer = CC("@mozilla.org/timer;1", "nsITimer"); |
|
15 const timers = Object.create(null); |
|
16 const threadManager = Cc["@mozilla.org/thread-manager;1"]. |
|
17 getService(Ci.nsIThreadManager); |
|
18 const prefBranch = Cc["@mozilla.org/preferences-service;1"]. |
|
19 getService(Ci.nsIPrefService). |
|
20 QueryInterface(Ci.nsIPrefBranch); |
|
21 |
|
22 let MIN_DELAY = 4; |
|
23 // Try to get min timeout delay used by browser. |
|
24 try { MIN_DELAY = prefBranch.getIntPref("dom.min_timeout_value"); } finally {} |
|
25 |
|
26 |
|
27 // Last timer id. |
|
28 let lastID = 0; |
|
29 |
|
30 // Sets typer either by timeout or by interval |
|
31 // depending on a given type. |
|
32 function setTimer(type, callback, delay, ...args) { |
|
33 let id = ++ lastID; |
|
34 let timer = timers[id] = Timer(); |
|
35 timer.initWithCallback({ |
|
36 notify: function notify() { |
|
37 try { |
|
38 if (type === TYPE_ONE_SHOT) |
|
39 delete timers[id]; |
|
40 callback.apply(null, args); |
|
41 } |
|
42 catch(error) { |
|
43 console.exception(error); |
|
44 } |
|
45 } |
|
46 }, Math.max(delay || MIN_DELAY), type); |
|
47 return id; |
|
48 } |
|
49 |
|
50 function unsetTimer(id) { |
|
51 let timer = timers[id]; |
|
52 delete timers[id]; |
|
53 if (timer) timer.cancel(); |
|
54 } |
|
55 |
|
56 let immediates = new Map(); |
|
57 |
|
58 let dispatcher = _ => { |
|
59 // Allow scheduling of a new dispatch loop. |
|
60 dispatcher.scheduled = false; |
|
61 // Take a snapshot of timer `id`'s that have being present before |
|
62 // starting a dispatch loop, in order to ignore timers registered |
|
63 // in side effect to dispatch while also skipping immediates that |
|
64 // were removed in side effect. |
|
65 let ids = [id for ([id] of immediates)]; |
|
66 for (let id of ids) { |
|
67 let immediate = immediates.get(id); |
|
68 if (immediate) { |
|
69 immediates.delete(id); |
|
70 try { immediate(); } |
|
71 catch (error) { console.exception(error); } |
|
72 } |
|
73 } |
|
74 } |
|
75 |
|
76 function setImmediate(callback, ...params) { |
|
77 let id = ++ lastID; |
|
78 // register new immediate timer with curried params. |
|
79 immediates.set(id, _ => callback.apply(callback, params)); |
|
80 // if dispatch loop is not scheduled schedule one. Own scheduler |
|
81 if (!dispatcher.scheduled) { |
|
82 dispatcher.scheduled = true; |
|
83 threadManager.currentThread.dispatch(dispatcher, |
|
84 Ci.nsIThread.DISPATCH_NORMAL); |
|
85 } |
|
86 return id; |
|
87 } |
|
88 |
|
89 function clearImmediate(id) { |
|
90 immediates.delete(id); |
|
91 } |
|
92 |
|
93 // Bind timers so that toString-ing them looks same as on native timers. |
|
94 exports.setImmediate = setImmediate.bind(null); |
|
95 exports.clearImmediate = clearImmediate.bind(null); |
|
96 exports.setTimeout = setTimer.bind(null, TYPE_ONE_SHOT); |
|
97 exports.setInterval = setTimer.bind(null, TYPE_REPEATING_SLACK); |
|
98 exports.clearTimeout = unsetTimer.bind(null); |
|
99 exports.clearInterval = unsetTimer.bind(null); |
|
100 |
|
101 // all timers are cleared out on unload. |
|
102 unload(function() { |
|
103 immediates.clear(); |
|
104 Object.keys(timers).forEach(unsetTimer) |
|
105 }); |