toolkit/mozapps/update/nsUpdateTimerManager.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

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]);

mercurial