Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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/. */
5 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
6 Components.utils.import("resource://gre/modules/Services.jsm");
8 const Cc = Components.classes;
9 const Ci = Components.interfaces;
11 const PREF_APP_UPDATE_LASTUPDATETIME_FMT = "app.update.lastUpdateTime.%ID%";
12 const PREF_APP_UPDATE_TIMERMINIMUMDELAY = "app.update.timerMinimumDelay";
13 const PREF_APP_UPDATE_TIMERFIRSTINTERVAL = "app.update.timerFirstInterval";
14 const PREF_APP_UPDATE_LOG = "app.update.log";
16 const CATEGORY_UPDATE_TIMER = "update-timer";
18 XPCOMUtils.defineLazyGetter(this, "gLogEnabled", function tm_gLogEnabled() {
19 return getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);
20 });
22 /**
23 * Gets a preference value, handling the case where there is no default.
24 * @param func
25 * The name of the preference function to call, on nsIPrefBranch
26 * @param preference
27 * The name of the preference
28 * @param defaultValue
29 * The default value to return in the event the preference has
30 * no setting
31 * @returns The value of the preference, or undefined if there was no
32 * user or default value.
33 */
34 function getPref(func, preference, defaultValue) {
35 try {
36 return Services.prefs[func](preference);
37 }
38 catch (e) {
39 }
40 return defaultValue;
41 }
43 /**
44 * Logs a string to the error console.
45 * @param string
46 * The string to write to the error console.
47 */
48 function LOG(string) {
49 if (gLogEnabled) {
50 dump("*** UTM:SVC " + string + "\n");
51 Services.console.logStringMessage("UTM:SVC " + string);
52 }
53 }
55 /**
56 * A manager for timers. Manages timers that fire over long periods of time
57 * (e.g. days, weeks, months).
58 * @constructor
59 */
60 function TimerManager() {
61 Services.obs.addObserver(this, "xpcom-shutdown", false);
62 }
63 TimerManager.prototype = {
64 /**
65 * The Checker Timer
66 */
67 _timer: null,
69 /**
70 * The Checker Timer minimum delay interval as specified by the
71 * app.update.timerMinimumDelay pref. If the app.update.timerMinimumDelay
72 * pref doesn't exist this will default to 120000.
73 */
74 _timerMinimumDelay: null,
76 /**
77 * The set of registered timers.
78 */
79 _timers: { },
81 /**
82 * See nsIObserver.idl
83 */
84 observe: function TM_observe(aSubject, aTopic, aData) {
85 // Prevent setting the timer interval to a value of less than 30 seconds.
86 var minInterval = 30000;
87 // Prevent setting the first timer interval to a value of less than 10
88 // seconds.
89 var minFirstInterval = 10000;
90 switch (aTopic) {
91 case "utm-test-init":
92 // Enforce a minimum timer interval of 500 ms for tests and fall through
93 // to profile-after-change to initialize the timer.
94 minInterval = 500;
95 minFirstInterval = 500;
96 case "profile-after-change":
97 // Cancel the timer if it has already been initialized. This is primarily
98 // for tests.
99 this._timerMinimumDelay = Math.max(1000 * getPref("getIntPref", PREF_APP_UPDATE_TIMERMINIMUMDELAY, 120),
100 minInterval);
101 let firstInterval = Math.max(getPref("getIntPref", PREF_APP_UPDATE_TIMERFIRSTINTERVAL,
102 this._timerMinimumDelay), minFirstInterval);
103 this._canEnsureTimer = true;
104 this._ensureTimer(firstInterval);
105 break;
106 case "xpcom-shutdown":
107 Services.obs.removeObserver(this, "xpcom-shutdown");
109 // Release everything we hold onto.
110 this._cancelTimer();
111 for (var timerID in this._timers)
112 delete this._timers[timerID];
113 this._timers = null;
114 break;
115 }
116 },
118 /**
119 * Called when the checking timer fires.
120 *
121 * We only fire one notification each time, so that the operations are
122 * staggered. We don't want too many to happen at once, which could
123 * negatively impact responsiveness.
124 *
125 * @param timer
126 * The checking timer that fired.
127 */
128 notify: function TM_notify(timer) {
129 var nextDelay = null;
130 function updateNextDelay(delay) {
131 if (nextDelay === null || delay < nextDelay)
132 nextDelay = delay;
133 }
135 // Each timer calls tryFire(), which figures out which is the one that
136 // wanted to be called earliest. That one will be fired; the others are
137 // skipped and will be done later.
138 var now = Math.round(Date.now() / 1000);
140 var callbackToFire = null;
141 var earliestIntendedTime = null;
142 var skippedFirings = false;
143 function tryFire(callback, intendedTime) {
144 var selected = false;
145 if (intendedTime <= now) {
146 if (intendedTime < earliestIntendedTime ||
147 earliestIntendedTime === null) {
148 callbackToFire = callback;
149 earliestIntendedTime = intendedTime;
150 selected = true;
151 }
152 else if (earliestIntendedTime !== null)
153 skippedFirings = true;
154 }
155 // We do not need to updateNextDelay for the timer that actually fires;
156 // we'll update right after it fires, with the proper intended time.
157 // Note that we might select one, then select another later (with an
158 // earlier intended time); it is still ok that we did not update for
159 // the first one, since if we have skipped firings, the next delay
160 // will be the minimum delay anyhow.
161 if (!selected)
162 updateNextDelay(intendedTime - now);
163 }
165 var catMan = Cc["@mozilla.org/categorymanager;1"].
166 getService(Ci.nsICategoryManager);
167 var entries = catMan.enumerateCategory(CATEGORY_UPDATE_TIMER);
168 while (entries.hasMoreElements()) {
169 let entry = entries.getNext().QueryInterface(Ci.nsISupportsCString).data;
170 let value = catMan.getCategoryEntry(CATEGORY_UPDATE_TIMER, entry);
171 let [cid, method, timerID, prefInterval, defaultInterval] = value.split(",");
173 defaultInterval = parseInt(defaultInterval);
174 // cid and method are validated below when calling notify.
175 if (!timerID || !defaultInterval || isNaN(defaultInterval)) {
176 LOG("TimerManager:notify - update-timer category registered" +
177 (cid ? " for " + cid : "") + " without required parameters - " +
178 "skipping");
179 continue;
180 }
182 let interval = getPref("getIntPref", prefInterval, defaultInterval);
183 let prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/,
184 timerID);
185 // Initialize the last update time to 0 when the preference isn't set so
186 // the timer will be notified soon after a new profile's first use.
187 let lastUpdateTime = getPref("getIntPref", prefLastUpdate, 0);
189 // If the last update time is greater than the current time then reset
190 // it to 0 and the timer manager will correct the value when it fires
191 // next for this consumer.
192 if (lastUpdateTime > now)
193 lastUpdateTime = 0;
195 if (lastUpdateTime == 0)
196 Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
198 tryFire(function() {
199 try {
200 Components.classes[cid][method](Ci.nsITimerCallback).notify(timer);
201 LOG("TimerManager:notify - notified " + cid);
202 }
203 catch (e) {
204 LOG("TimerManager:notify - error notifying component id: " +
205 cid + " ,error: " + e);
206 }
207 lastUpdateTime = now;
208 Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
209 updateNextDelay(lastUpdateTime + interval - now);
210 }, lastUpdateTime + interval);
211 }
213 for (let _timerID in this._timers) {
214 let timerID = _timerID; // necessary for the closure to work properly
215 let timerData = this._timers[timerID];
216 // If the last update time is greater than the current time then reset
217 // it to 0 and the timer manager will correct the value when it fires
218 // next for this consumer.
219 if (timerData.lastUpdateTime > now) {
220 let prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, timerID);
221 timerData.lastUpdateTime = 0;
222 Services.prefs.setIntPref(prefLastUpdate, timerData.lastUpdateTime);
223 }
224 tryFire(function() {
225 if (timerData.callback && timerData.callback.notify) {
226 try {
227 timerData.callback.notify(timer);
228 LOG("TimerManager:notify - notified timerID: " + timerID);
229 }
230 catch (e) {
231 LOG("TimerManager:notify - error notifying timerID: " + timerID +
232 ", error: " + e);
233 }
234 }
235 else {
236 LOG("TimerManager:notify - timerID: " + timerID + " doesn't " +
237 "implement nsITimerCallback - skipping");
238 }
239 lastUpdateTime = now;
240 timerData.lastUpdateTime = lastUpdateTime;
241 let prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, timerID);
242 Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
243 updateNextDelay(timerData.lastUpdateTime + timerData.interval - now);
244 }, timerData.lastUpdateTime + timerData.interval);
245 }
247 if (callbackToFire)
248 callbackToFire();
250 if (nextDelay !== null) {
251 if (skippedFirings)
252 timer.delay = this._timerMinimumDelay;
253 else
254 timer.delay = Math.max(nextDelay * 1000, this._timerMinimumDelay);
255 this.lastTimerReset = Date.now();
256 } else {
257 this._cancelTimer();
258 }
259 },
261 /**
262 * Starts the timer, if necessary, and ensures that it will fire soon enough
263 * to happen after time |interval| (in milliseconds).
264 */
265 _ensureTimer: function(interval) {
266 if (!this._canEnsureTimer)
267 return;
268 if (!this._timer) {
269 this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
270 this._timer.initWithCallback(this, interval,
271 Ci.nsITimer.TYPE_REPEATING_SLACK);
272 this.lastTimerReset = Date.now();
273 } else {
274 if (Date.now() + interval < this.lastTimerReset + this._timer.delay)
275 this._timer.delay = Math.max(this.lastTimerReset + interval - Date.now(), 0);
276 }
277 },
279 /**
280 * Stops the timer, if it is running.
281 */
282 _cancelTimer: function() {
283 if (this._timer) {
284 this._timer.cancel();
285 this._timer = null;
286 }
287 },
289 /**
290 * See nsIUpdateTimerManager.idl
291 */
292 registerTimer: function TM_registerTimer(id, callback, interval) {
293 LOG("TimerManager:registerTimer - id: " + id);
294 let prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, id);
295 // Initialize the last update time to 0 when the preference isn't set so
296 // the timer will be notified soon after a new profile's first use.
297 let lastUpdateTime = getPref("getIntPref", prefLastUpdate, 0);
298 let now = Math.round(Date.now() / 1000);
299 if (lastUpdateTime > now)
300 lastUpdateTime = 0;
301 if (lastUpdateTime == 0)
302 Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
303 this._timers[id] = { callback : callback,
304 interval : interval,
305 lastUpdateTime : lastUpdateTime };
307 this._ensureTimer(interval * 1000);
308 },
310 classID: Components.ID("{B322A5C0-A419-484E-96BA-D7182163899F}"),
311 QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateTimerManager,
312 Ci.nsITimerCallback,
313 Ci.nsIObserver])
314 };
316 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TimerManager]);