toolkit/mozapps/update/nsUpdateTimerManager.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/mozapps/update/nsUpdateTimerManager.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,316 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
     1.9 +Components.utils.import("resource://gre/modules/Services.jsm");
    1.10 +
    1.11 +const Cc = Components.classes;
    1.12 +const Ci = Components.interfaces;
    1.13 +
    1.14 +const PREF_APP_UPDATE_LASTUPDATETIME_FMT  = "app.update.lastUpdateTime.%ID%";
    1.15 +const PREF_APP_UPDATE_TIMERMINIMUMDELAY   = "app.update.timerMinimumDelay";
    1.16 +const PREF_APP_UPDATE_TIMERFIRSTINTERVAL  = "app.update.timerFirstInterval";
    1.17 +const PREF_APP_UPDATE_LOG                 = "app.update.log";
    1.18 +
    1.19 +const CATEGORY_UPDATE_TIMER               = "update-timer";
    1.20 +
    1.21 +XPCOMUtils.defineLazyGetter(this, "gLogEnabled", function tm_gLogEnabled() {
    1.22 +  return getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);
    1.23 +});
    1.24 +
    1.25 +/**
    1.26 + *  Gets a preference value, handling the case where there is no default.
    1.27 + *  @param   func
    1.28 + *           The name of the preference function to call, on nsIPrefBranch
    1.29 + *  @param   preference
    1.30 + *           The name of the preference
    1.31 + *  @param   defaultValue
    1.32 + *           The default value to return in the event the preference has
    1.33 + *           no setting
    1.34 + *  @returns The value of the preference, or undefined if there was no
    1.35 + *           user or default value.
    1.36 + */
    1.37 +function getPref(func, preference, defaultValue) {
    1.38 +  try {
    1.39 +    return Services.prefs[func](preference);
    1.40 +  }
    1.41 +  catch (e) {
    1.42 +  }
    1.43 +  return defaultValue;
    1.44 +}
    1.45 +
    1.46 +/**
    1.47 + *  Logs a string to the error console.
    1.48 + *  @param   string
    1.49 + *           The string to write to the error console.
    1.50 + */
    1.51 +function LOG(string) {
    1.52 +  if (gLogEnabled) {
    1.53 +    dump("*** UTM:SVC " + string + "\n");
    1.54 +    Services.console.logStringMessage("UTM:SVC " + string);
    1.55 +  }
    1.56 +}
    1.57 +
    1.58 +/**
    1.59 + *  A manager for timers. Manages timers that fire over long periods of time
    1.60 + *  (e.g. days, weeks, months).
    1.61 + *  @constructor
    1.62 + */
    1.63 +function TimerManager() {
    1.64 +  Services.obs.addObserver(this, "xpcom-shutdown", false);
    1.65 +}
    1.66 +TimerManager.prototype = {
    1.67 +  /**
    1.68 +   * The Checker Timer
    1.69 +   */
    1.70 +  _timer: null,
    1.71 +
    1.72 +  /**
    1.73 + *    The Checker Timer minimum delay interval as specified by the
    1.74 + *    app.update.timerMinimumDelay pref. If the app.update.timerMinimumDelay
    1.75 + *    pref doesn't exist this will default to 120000.
    1.76 +   */
    1.77 +   _timerMinimumDelay: null,
    1.78 +
    1.79 +  /**
    1.80 +   * The set of registered timers.
    1.81 +   */
    1.82 +  _timers: { },
    1.83 +
    1.84 +  /**
    1.85 +   * See nsIObserver.idl
    1.86 +   */
    1.87 +  observe: function TM_observe(aSubject, aTopic, aData) {
    1.88 +    // Prevent setting the timer interval to a value of less than 30 seconds.
    1.89 +    var minInterval = 30000;
    1.90 +    // Prevent setting the first timer interval to a value of less than 10
    1.91 +    // seconds.
    1.92 +    var minFirstInterval = 10000;
    1.93 +    switch (aTopic) {
    1.94 +    case "utm-test-init":
    1.95 +      // Enforce a minimum timer interval of 500 ms for tests and fall through
    1.96 +      // to profile-after-change to initialize the timer.
    1.97 +      minInterval = 500;
    1.98 +      minFirstInterval = 500;
    1.99 +    case "profile-after-change":
   1.100 +      // Cancel the timer if it has already been initialized. This is primarily
   1.101 +      // for tests.
   1.102 +      this._timerMinimumDelay = Math.max(1000 * getPref("getIntPref", PREF_APP_UPDATE_TIMERMINIMUMDELAY, 120),
   1.103 +                                         minInterval);
   1.104 +      let firstInterval = Math.max(getPref("getIntPref", PREF_APP_UPDATE_TIMERFIRSTINTERVAL,
   1.105 +                                           this._timerMinimumDelay), minFirstInterval);
   1.106 +      this._canEnsureTimer = true;
   1.107 +      this._ensureTimer(firstInterval);
   1.108 +      break;
   1.109 +    case "xpcom-shutdown":
   1.110 +      Services.obs.removeObserver(this, "xpcom-shutdown");
   1.111 +
   1.112 +      // Release everything we hold onto.
   1.113 +      this._cancelTimer();
   1.114 +      for (var timerID in this._timers)
   1.115 +        delete this._timers[timerID];
   1.116 +      this._timers = null;
   1.117 +      break;
   1.118 +    }
   1.119 +  },
   1.120 +
   1.121 +  /**
   1.122 +   * Called when the checking timer fires.
   1.123 +   *
   1.124 +   * We only fire one notification each time, so that the operations are
   1.125 +   * staggered. We don't want too many to happen at once, which could
   1.126 +   * negatively impact responsiveness.
   1.127 +   *
   1.128 +   * @param   timer
   1.129 +   *          The checking timer that fired.
   1.130 +   */
   1.131 +  notify: function TM_notify(timer) {
   1.132 +    var nextDelay = null;
   1.133 +    function updateNextDelay(delay) {
   1.134 +      if (nextDelay === null || delay < nextDelay)
   1.135 +        nextDelay = delay;
   1.136 +    }
   1.137 +
   1.138 +    // Each timer calls tryFire(), which figures out which is the one that
   1.139 +    // wanted to be called earliest. That one will be fired; the others are
   1.140 +    // skipped and will be done later.
   1.141 +    var now = Math.round(Date.now() / 1000);
   1.142 +
   1.143 +    var callbackToFire = null;
   1.144 +    var earliestIntendedTime = null;
   1.145 +    var skippedFirings = false;
   1.146 +    function tryFire(callback, intendedTime) {
   1.147 +      var selected = false;
   1.148 +      if (intendedTime <= now) {
   1.149 +        if (intendedTime < earliestIntendedTime ||
   1.150 +            earliestIntendedTime === null) {
   1.151 +          callbackToFire = callback;
   1.152 +          earliestIntendedTime = intendedTime;
   1.153 +          selected = true;
   1.154 +        }
   1.155 +        else if (earliestIntendedTime !== null)
   1.156 +          skippedFirings = true;
   1.157 +      }
   1.158 +      // We do not need to updateNextDelay for the timer that actually fires;
   1.159 +      // we'll update right after it fires, with the proper intended time.
   1.160 +      // Note that we might select one, then select another later (with an
   1.161 +      // earlier intended time); it is still ok that we did not update for
   1.162 +      // the first one, since if we have skipped firings, the next delay
   1.163 +      // will be the minimum delay anyhow.
   1.164 +      if (!selected)
   1.165 +        updateNextDelay(intendedTime - now);
   1.166 +    }
   1.167 +
   1.168 +    var catMan = Cc["@mozilla.org/categorymanager;1"].
   1.169 +                 getService(Ci.nsICategoryManager);
   1.170 +    var entries = catMan.enumerateCategory(CATEGORY_UPDATE_TIMER);
   1.171 +    while (entries.hasMoreElements()) {
   1.172 +      let entry = entries.getNext().QueryInterface(Ci.nsISupportsCString).data;
   1.173 +      let value = catMan.getCategoryEntry(CATEGORY_UPDATE_TIMER, entry);
   1.174 +      let [cid, method, timerID, prefInterval, defaultInterval] = value.split(",");
   1.175 +
   1.176 +      defaultInterval = parseInt(defaultInterval);
   1.177 +      // cid and method are validated below when calling notify.
   1.178 +      if (!timerID || !defaultInterval || isNaN(defaultInterval)) {
   1.179 +        LOG("TimerManager:notify - update-timer category registered" +
   1.180 +            (cid ? " for " + cid : "") + " without required parameters - " +
   1.181 +             "skipping");
   1.182 +        continue;
   1.183 +      }
   1.184 +
   1.185 +      let interval = getPref("getIntPref", prefInterval, defaultInterval);
   1.186 +      let prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/,
   1.187 +                                                                      timerID);
   1.188 +      // Initialize the last update time to 0 when the preference isn't set so
   1.189 +      // the timer will be notified soon after a new profile's first use.
   1.190 +      let lastUpdateTime = getPref("getIntPref", prefLastUpdate, 0);
   1.191 +
   1.192 +      // If the last update time is greater than the current time then reset
   1.193 +      // it to 0 and the timer manager will correct the value when it fires
   1.194 +      // next for this consumer.
   1.195 +      if (lastUpdateTime > now)
   1.196 +        lastUpdateTime = 0;
   1.197 +
   1.198 +      if (lastUpdateTime == 0)
   1.199 +        Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
   1.200 +
   1.201 +      tryFire(function() {
   1.202 +        try {
   1.203 +          Components.classes[cid][method](Ci.nsITimerCallback).notify(timer);
   1.204 +          LOG("TimerManager:notify - notified " + cid);
   1.205 +        }
   1.206 +        catch (e) {
   1.207 +          LOG("TimerManager:notify - error notifying component id: " +
   1.208 +              cid + " ,error: " + e);
   1.209 +        }
   1.210 +        lastUpdateTime = now;
   1.211 +        Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
   1.212 +        updateNextDelay(lastUpdateTime + interval - now);
   1.213 +      }, lastUpdateTime + interval);
   1.214 +    }
   1.215 +
   1.216 +    for (let _timerID in this._timers) {
   1.217 +      let timerID = _timerID; // necessary for the closure to work properly
   1.218 +      let timerData = this._timers[timerID];
   1.219 +      // If the last update time is greater than the current time then reset
   1.220 +      // it to 0 and the timer manager will correct the value when it fires
   1.221 +      // next for this consumer.
   1.222 +      if (timerData.lastUpdateTime > now) {
   1.223 +        let prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, timerID);
   1.224 +        timerData.lastUpdateTime = 0;
   1.225 +        Services.prefs.setIntPref(prefLastUpdate, timerData.lastUpdateTime);
   1.226 +      }
   1.227 +      tryFire(function() {
   1.228 +        if (timerData.callback && timerData.callback.notify) {
   1.229 +          try {
   1.230 +            timerData.callback.notify(timer);
   1.231 +            LOG("TimerManager:notify - notified timerID: " + timerID);
   1.232 +          }
   1.233 +          catch (e) {
   1.234 +            LOG("TimerManager:notify - error notifying timerID: " + timerID +
   1.235 +                ", error: " + e);
   1.236 +          }
   1.237 +        }
   1.238 +        else {
   1.239 +          LOG("TimerManager:notify - timerID: " + timerID + " doesn't " +
   1.240 +              "implement nsITimerCallback - skipping");
   1.241 +        }
   1.242 +        lastUpdateTime = now;
   1.243 +        timerData.lastUpdateTime = lastUpdateTime;
   1.244 +        let prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, timerID);
   1.245 +        Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
   1.246 +        updateNextDelay(timerData.lastUpdateTime + timerData.interval - now);
   1.247 +      }, timerData.lastUpdateTime + timerData.interval);
   1.248 +    }
   1.249 +
   1.250 +    if (callbackToFire)
   1.251 +      callbackToFire();
   1.252 +
   1.253 +    if (nextDelay !== null) {
   1.254 +      if (skippedFirings)
   1.255 +        timer.delay = this._timerMinimumDelay;
   1.256 +      else
   1.257 +        timer.delay = Math.max(nextDelay * 1000, this._timerMinimumDelay);  
   1.258 +      this.lastTimerReset = Date.now();
   1.259 +    } else {
   1.260 +      this._cancelTimer();
   1.261 +    }
   1.262 +  },
   1.263 +
   1.264 +  /**
   1.265 +   * Starts the timer, if necessary, and ensures that it will fire soon enough
   1.266 +   * to happen after time |interval| (in milliseconds).
   1.267 +   */
   1.268 +  _ensureTimer: function(interval) {
   1.269 +    if (!this._canEnsureTimer)
   1.270 +      return;
   1.271 +    if (!this._timer) {
   1.272 +      this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
   1.273 +      this._timer.initWithCallback(this, interval,
   1.274 +                                   Ci.nsITimer.TYPE_REPEATING_SLACK);
   1.275 +      this.lastTimerReset = Date.now();
   1.276 +    } else {
   1.277 +      if (Date.now() + interval < this.lastTimerReset + this._timer.delay) 
   1.278 +        this._timer.delay = Math.max(this.lastTimerReset + interval - Date.now(), 0);
   1.279 +    }
   1.280 +  },
   1.281 +
   1.282 +  /**
   1.283 +   * Stops the timer, if it is running.
   1.284 +   */
   1.285 +  _cancelTimer: function() {
   1.286 +    if (this._timer) {
   1.287 +      this._timer.cancel();
   1.288 +      this._timer = null;
   1.289 +    }
   1.290 +  },
   1.291 +
   1.292 +  /**
   1.293 +   * See nsIUpdateTimerManager.idl
   1.294 +   */
   1.295 +  registerTimer: function TM_registerTimer(id, callback, interval) {
   1.296 +    LOG("TimerManager:registerTimer - id: " + id);
   1.297 +    let prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, id);
   1.298 +    // Initialize the last update time to 0 when the preference isn't set so
   1.299 +    // the timer will be notified soon after a new profile's first use.
   1.300 +    let lastUpdateTime = getPref("getIntPref", prefLastUpdate, 0);
   1.301 +    let now = Math.round(Date.now() / 1000);
   1.302 +    if (lastUpdateTime > now)
   1.303 +      lastUpdateTime = 0;
   1.304 +    if (lastUpdateTime == 0)
   1.305 +      Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
   1.306 +    this._timers[id] = { callback       : callback,
   1.307 +                         interval       : interval,
   1.308 +                         lastUpdateTime : lastUpdateTime };
   1.309 +
   1.310 +    this._ensureTimer(interval * 1000);
   1.311 +  },
   1.312 +
   1.313 +  classID: Components.ID("{B322A5C0-A419-484E-96BA-D7182163899F}"),
   1.314 +  QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateTimerManager,
   1.315 +                                         Ci.nsITimerCallback,
   1.316 +                                         Ci.nsIObserver])
   1.317 +};
   1.318 +
   1.319 +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TimerManager]);

mercurial