/toolkit/mozapps/update/nsUpdateTimerManager.js

http://github.com/zpao/v8monkey · JavaScript · 337 lines · 198 code · 27 blank · 112 comment · 30 complexity · b8e4df8dd8431306929875cf2c471bd5 MD5 · raw file

  1. /*
  2. # ***** BEGIN LICENSE BLOCK *****
  3. # Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4. #
  5. # The contents of this file are subject to the Mozilla Public License Version
  6. # 1.1 (the "License"); you may not use this file except in compliance with
  7. # the License. You may obtain a copy of the License at
  8. # http://www.mozilla.org/MPL/
  9. #
  10. # Software distributed under the License is distributed on an "AS IS" basis,
  11. # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12. # for the specific language governing rights and limitations under the
  13. # License.
  14. #
  15. # The Original Code is the Update timer Manager.
  16. #
  17. # The Initial Developer of the Original Code is Ben Goodger.
  18. # Portions created by the Initial Developer are Copyright (C) 2004
  19. # the Initial Developer. All Rights Reserved.
  20. #
  21. # Contributor(s):
  22. # Ben Goodger <ben@mozilla.org> (Original Author)
  23. # Robert Strong <robert.bugzilla@gmail.com>
  24. #
  25. # Alternatively, the contents of this file may be used under the terms of
  26. # either the GNU General Public License Version 2 or later (the "GPL"), or
  27. # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  28. # in which case the provisions of the GPL or the LGPL are applicable instead
  29. # of those above. If you wish to allow use of your version of this file only
  30. # under the terms of either the GPL or the LGPL, and not to allow others to
  31. # use your version of this file under the terms of the MPL, indicate your
  32. # decision by deleting the provisions above and replace them with the notice
  33. # and other provisions required by the GPL or the LGPL. If you do not delete
  34. # the provisions above, a recipient may use your version of this file under
  35. # the terms of any one of the MPL, the GPL or the LGPL.
  36. #
  37. # ***** END LICENSE BLOCK *****
  38. */
  39. Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
  40. Components.utils.import("resource://gre/modules/Services.jsm");
  41. const Cc = Components.classes;
  42. const Ci = Components.interfaces;
  43. const PREF_APP_UPDATE_LASTUPDATETIME_FMT = "app.update.lastUpdateTime.%ID%";
  44. const PREF_APP_UPDATE_TIMERMINIMUMDELAY = "app.update.timerMinimumDelay";
  45. const PREF_APP_UPDATE_TIMERFIRSTINTERVAL = "app.update.timerFirstInterval";
  46. const PREF_APP_UPDATE_LOG = "app.update.log";
  47. const CATEGORY_UPDATE_TIMER = "update-timer";
  48. XPCOMUtils.defineLazyGetter(this, "gLogEnabled", function tm_gLogEnabled() {
  49. return getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);
  50. });
  51. /**
  52. # Gets a preference value, handling the case where there is no default.
  53. # @param func
  54. # The name of the preference function to call, on nsIPrefBranch
  55. # @param preference
  56. # The name of the preference
  57. # @param defaultValue
  58. # The default value to return in the event the preference has
  59. # no setting
  60. # @returns The value of the preference, or undefined if there was no
  61. # user or default value.
  62. */
  63. function getPref(func, preference, defaultValue) {
  64. try {
  65. return Services.prefs[func](preference);
  66. }
  67. catch (e) {
  68. }
  69. return defaultValue;
  70. }
  71. /**
  72. # Logs a string to the error console.
  73. # @param string
  74. # The string to write to the error console.
  75. */
  76. function LOG(string) {
  77. if (gLogEnabled) {
  78. dump("*** UTM:SVC " + string + "\n");
  79. Services.console.logStringMessage("UTM:SVC " + string);
  80. }
  81. }
  82. /**
  83. # A manager for timers. Manages timers that fire over long periods of time
  84. # (e.g. days, weeks, months).
  85. # @constructor
  86. */
  87. function TimerManager() {
  88. Services.obs.addObserver(this, "xpcom-shutdown", false);
  89. }
  90. TimerManager.prototype = {
  91. /**
  92. * The Checker Timer
  93. */
  94. _timer: null,
  95. /**
  96. # The Checker Timer minimum delay interval as specified by the
  97. # app.update.timerMinimumDelay pref. If the app.update.timerMinimumDelay
  98. # pref doesn't exist this will default to 120000.
  99. */
  100. _timerMinimumDelay: null,
  101. /**
  102. * The set of registered timers.
  103. */
  104. _timers: { },
  105. /**
  106. * See nsIObserver.idl
  107. */
  108. observe: function TM_observe(aSubject, aTopic, aData) {
  109. // Prevent setting the timer interval to a value of less than 60 seconds.
  110. var minInterval = 60000;
  111. // Prevent setting the first timer interval to a value of less than 10
  112. // seconds.
  113. var minFirstInterval = 10000;
  114. switch (aTopic) {
  115. case "utm-test-init":
  116. // Enforce a minimum timer interval of 500 ms for tests and fall through
  117. // to profile-after-change to initialize the timer.
  118. minInterval = 500;
  119. minFirstInterval = 500;
  120. case "profile-after-change":
  121. // Cancel the timer if it has already been initialized. This is primarily
  122. // for tests.
  123. this._timerMinimumDelay = Math.max(1000 * getPref("getIntPref", PREF_APP_UPDATE_TIMERMINIMUMDELAY, 120),
  124. minInterval);
  125. let firstInterval = Math.max(getPref("getIntPref", PREF_APP_UPDATE_TIMERFIRSTINTERVAL,
  126. this._timerMinimumDelay), minFirstInterval);
  127. this._canEnsureTimer = true;
  128. this._ensureTimer(firstInterval);
  129. break;
  130. case "xpcom-shutdown":
  131. Services.obs.removeObserver(this, "xpcom-shutdown");
  132. // Release everything we hold onto.
  133. this._cancelTimer();
  134. for (var timerID in this._timers)
  135. delete this._timers[timerID];
  136. this._timers = null;
  137. break;
  138. }
  139. },
  140. /**
  141. # Called when the checking timer fires.
  142. #
  143. # We only fire one notification each time, so that the operations are
  144. # staggered. We don't want too many to happen at once, which could
  145. # negatively impact responsiveness.
  146. #
  147. # @param timer
  148. # The checking timer that fired.
  149. */
  150. notify: function TM_notify(timer) {
  151. var nextDelay = null;
  152. function updateNextDelay(delay) {
  153. if (nextDelay === null || delay < nextDelay)
  154. nextDelay = delay;
  155. }
  156. // Each timer calls tryFire(), which figures out which is the the one that
  157. // wanted to be called earliest. That one will be fired; the others are
  158. // skipped and will be done later.
  159. var now = Math.round(Date.now() / 1000);
  160. var callbackToFire = null;
  161. var earliestIntendedTime = null;
  162. var skippedFirings = false;
  163. function tryFire(callback, intendedTime) {
  164. var selected = false;
  165. if (intendedTime <= now) {
  166. if (intendedTime < earliestIntendedTime ||
  167. earliestIntendedTime === null) {
  168. callbackToFire = callback;
  169. earliestIntendedTime = intendedTime;
  170. selected = true;
  171. }
  172. else if (earliestIntendedTime !== null)
  173. skippedFirings = true;
  174. }
  175. // We do not need to updateNextDelay for the timer that actually fires;
  176. // we'll update right after it fires, with the proper intended time.
  177. // Note that we might select one, then select another later (with an
  178. // earlier intended time); it is still ok that we did not update for
  179. // the first one, since if we have skipped firings, the next delay
  180. // will be the minimum delay anyhow.
  181. if (!selected)
  182. updateNextDelay(intendedTime - now);
  183. }
  184. var catMan = Cc["@mozilla.org/categorymanager;1"].
  185. getService(Ci.nsICategoryManager);
  186. var entries = catMan.enumerateCategory(CATEGORY_UPDATE_TIMER);
  187. while (entries.hasMoreElements()) {
  188. let entry = entries.getNext().QueryInterface(Ci.nsISupportsCString).data;
  189. let value = catMan.getCategoryEntry(CATEGORY_UPDATE_TIMER, entry);
  190. let [cid, method, timerID, prefInterval, defaultInterval] = value.split(",");
  191. let lastUpdateTime;
  192. defaultInterval = parseInt(defaultInterval);
  193. // cid and method are validated below when calling notify.
  194. if (!timerID || !defaultInterval || isNaN(defaultInterval)) {
  195. LOG("TimerManager:notify - update-timer category registered" +
  196. (cid ? " for " + cid : "") + " without required parameters - " +
  197. "skipping");
  198. continue;
  199. }
  200. let interval = getPref("getIntPref", prefInterval, defaultInterval);
  201. let prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/,
  202. timerID);
  203. if (Services.prefs.prefHasUserValue(prefLastUpdate)) {
  204. lastUpdateTime = Services.prefs.getIntPref(prefLastUpdate);
  205. }
  206. else {
  207. lastUpdateTime = now;
  208. Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
  209. }
  210. tryFire(function() {
  211. try {
  212. Components.classes[cid][method](Ci.nsITimerCallback).notify(timer);
  213. LOG("TimerManager:notify - notified " + cid);
  214. }
  215. catch (e) {
  216. LOG("TimerManager:notify - error notifying component id: " +
  217. cid + " ,error: " + e);
  218. }
  219. lastUpdateTime = now;
  220. Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
  221. updateNextDelay(lastUpdateTime + interval - now);
  222. }, lastUpdateTime + interval);
  223. }
  224. for (let _timerID in this._timers) {
  225. let timerID = _timerID; // necessary for the closure to work properly
  226. let timerData = this._timers[timerID];
  227. tryFire(function() {
  228. if (timerData.callback instanceof Ci.nsITimerCallback) {
  229. try {
  230. timerData.callback.notify(timer);
  231. LOG("TimerManager:notify - notified timerID: " + timerID);
  232. }
  233. catch (e) {
  234. LOG("TimerManager:notify - error notifying timerID: " + timerID +
  235. ", error: " + e);
  236. }
  237. }
  238. else {
  239. LOG("TimerManager:notify - timerID: " + timerID + " doesn't " +
  240. "implement nsITimerCallback - skipping");
  241. }
  242. lastUpdateTime = now;
  243. timerData.lastUpdateTime = lastUpdateTime;
  244. var prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, timerID);
  245. Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
  246. updateNextDelay(timerData.lastUpdateTime + timerData.interval - now);
  247. }, timerData.lastUpdateTime + timerData.interval);
  248. }
  249. if (callbackToFire)
  250. callbackToFire();
  251. if (nextDelay !== null) {
  252. if (skippedFirings)
  253. timer.delay = this._timerMinimumDelay;
  254. else
  255. timer.delay = Math.max(nextDelay * 1000, this._timerMinimumDelay);
  256. this.lastTimerReset = Date.now();
  257. } else {
  258. this._cancelTimer();
  259. }
  260. },
  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 = this.lastTimerReset + interval - Date.now();
  276. }
  277. },
  278. /**
  279. * Stops the timer, if it is running.
  280. */
  281. _cancelTimer: function() {
  282. if (this._timer) {
  283. this._timer.cancel();
  284. this._timer = null;
  285. }
  286. },
  287. /**
  288. * See nsIUpdateTimerManager.idl
  289. */
  290. registerTimer: function TM_registerTimer(id, callback, interval) {
  291. LOG("TimerManager:registerTimer - id: " + id);
  292. var prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, id);
  293. var lastUpdateTime;
  294. if (Services.prefs.prefHasUserValue(prefLastUpdate)) {
  295. lastUpdateTime = Services.prefs.getIntPref(prefLastUpdate);
  296. } else {
  297. lastUpdateTime = Math.round(Date.now() / 1000);
  298. Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
  299. }
  300. this._timers[id] = { callback : callback,
  301. interval : interval,
  302. lastUpdateTime : lastUpdateTime };
  303. this._ensureTimer(interval * 1000);
  304. },
  305. classID: Components.ID("{B322A5C0-A419-484E-96BA-D7182163899F}"),
  306. QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateTimerManager,
  307. Ci.nsITimerCallback,
  308. Ci.nsIObserver])
  309. };
  310. var NSGetFactory = XPCOMUtils.generateNSGetFactory([TimerManager]);