forked from mirrors/gecko-dev
		
	 6adf9223ce
			
		
	
	
		6adf9223ce
		
	
	
	
	
		
			
			Differential Revision: https://phabricator.services.mozilla.com/D3730 --HG-- extra : rebase_source : 935f166ec2c6581ba6f3fffe912404e81c8dc3d6 extra : histedit_source : ba701801de5205dcce6cfdccabe7b26aa7c7859c
		
			
				
	
	
		
			336 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			336 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", this);
 | |
| ChromeUtils.import("resource://gre/modules/Services.jsm", this);
 | |
| 
 | |
| const PREF_APP_UPDATE_LASTUPDATETIME_FMT  = "app.update.lastUpdateTime.%ID%";
 | |
| const PREF_APP_UPDATE_TIMERMINIMUMDELAY   = "app.update.timerMinimumDelay";
 | |
| const PREF_APP_UPDATE_TIMERFIRSTINTERVAL  = "app.update.timerFirstInterval";
 | |
| const PREF_APP_UPDATE_LOG                 = "app.update.log";
 | |
| 
 | |
| const CATEGORY_UPDATE_TIMER               = "update-timer";
 | |
| 
 | |
| XPCOMUtils.defineLazyGetter(this, "gLogEnabled", function tm_gLogEnabled() {
 | |
|   return Services.prefs.getBoolPref(PREF_APP_UPDATE_LOG, false);
 | |
| });
 | |
| 
 | |
| /**
 | |
|  *  Logs a string to the error console.
 | |
|  *  @param   string
 | |
|  *           The string to write to the error console.
 | |
|  */
 | |
| function LOG(string) {
 | |
|   if (gLogEnabled) {
 | |
|     dump("*** UTM:SVC " + string + "\n");
 | |
|     Services.console.logStringMessage("UTM:SVC " + string);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  *  A manager for timers. Manages timers that fire over long periods of time
 | |
|  *  (e.g. days, weeks, months).
 | |
|  *  @constructor
 | |
|  */
 | |
| function TimerManager() {
 | |
|   Services.obs.addObserver(this, "profile-before-change");
 | |
| }
 | |
| TimerManager.prototype = {
 | |
|   /**
 | |
|    * The Checker Timer
 | |
|    */
 | |
|   _timer: null,
 | |
| 
 | |
|   /**
 | |
|    * The Checker Timer minimum delay interval as specified by the
 | |
|    * app.update.timerMinimumDelay pref. If the app.update.timerMinimumDelay
 | |
|    * pref doesn't exist this will default to 120000.
 | |
|    */
 | |
|   _timerMinimumDelay: null,
 | |
| 
 | |
|   /**
 | |
|    * The set of registered timers.
 | |
|    */
 | |
|   _timers: { },
 | |
| 
 | |
|   /**
 | |
|    * See nsIObserver.idl
 | |
|    */
 | |
|   observe: function TM_observe(aSubject, aTopic, aData) {
 | |
|     // Prevent setting the timer interval to a value of less than 30 seconds.
 | |
|     var minInterval = 30000;
 | |
|     // Prevent setting the first timer interval to a value of less than 10
 | |
|     // seconds.
 | |
|     var minFirstInterval = 10000;
 | |
|     switch (aTopic) {
 | |
|       case "utm-test-init":
 | |
|         // Enforce a minimum timer interval of 500 ms for tests and fall through
 | |
|         // to profile-after-change to initialize the timer.
 | |
|         minInterval = 500;
 | |
|         minFirstInterval = 500;
 | |
|       case "profile-after-change":
 | |
|         this._timerMinimumDelay =
 | |
|           Math.max(1000 * Services.prefs.getIntPref(PREF_APP_UPDATE_TIMERMINIMUMDELAY, 120),
 | |
|                    minInterval);
 | |
|         // Prevent the timer delay between notifications to other consumers from
 | |
|         // being greater than 5 minutes which is 300000 milliseconds.
 | |
|         this._timerMinimumDelay = Math.min(this._timerMinimumDelay, 300000);
 | |
|         // Prevent the first interval from being less than the value of minFirstInterval
 | |
|         let firstInterval =
 | |
|           Math.max(Services.prefs.getIntPref(PREF_APP_UPDATE_TIMERFIRSTINTERVAL,
 | |
|                                              30000), minFirstInterval);
 | |
|         // Prevent the first interval from being greater than 2 minutes which is
 | |
|         // 120000 milliseconds.
 | |
|         firstInterval = Math.min(firstInterval, 120000);
 | |
|         // Cancel the timer if it has already been initialized. This is primarily
 | |
|         // for tests.
 | |
|         this._canEnsureTimer = true;
 | |
|         this._ensureTimer(firstInterval);
 | |
|         break;
 | |
|       case "profile-before-change":
 | |
|         Services.obs.removeObserver(this, "profile-before-change");
 | |
| 
 | |
|         // Release everything we hold onto.
 | |
|         this._cancelTimer();
 | |
|         for (var timerID in this._timers) {
 | |
|           delete this._timers[timerID];
 | |
|         }
 | |
|         this._timers = null;
 | |
|         break;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Called when the checking timer fires.
 | |
|    *
 | |
|    * We only fire one notification each time, so that the operations are
 | |
|    * staggered. We don't want too many to happen at once, which could
 | |
|    * negatively impact responsiveness.
 | |
|    *
 | |
|    * @param   timer
 | |
|    *          The checking timer that fired.
 | |
|    */
 | |
|   notify: function TM_notify(timer) {
 | |
|     var nextDelay = null;
 | |
|     function updateNextDelay(delay) {
 | |
|       if (nextDelay === null || delay < nextDelay) {
 | |
|         nextDelay = delay;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Each timer calls tryFire(), which figures out which is the one that
 | |
|     // wanted to be called earliest. That one will be fired; the others are
 | |
|     // skipped and will be done later.
 | |
|     var now = Math.round(Date.now() / 1000);
 | |
| 
 | |
|     var callbackToFire = null;
 | |
|     var earliestIntendedTime = null;
 | |
|     var skippedFirings = false;
 | |
|     var lastUpdateTime = null;
 | |
|     function tryFire(callback, intendedTime) {
 | |
|       var selected = false;
 | |
|       if (intendedTime <= now) {
 | |
|         if (intendedTime < earliestIntendedTime ||
 | |
|             earliestIntendedTime === null) {
 | |
|           callbackToFire = callback;
 | |
|           earliestIntendedTime = intendedTime;
 | |
|           selected = true;
 | |
|         } else if (earliestIntendedTime !== null) {
 | |
|           skippedFirings = true;
 | |
|         }
 | |
|       }
 | |
|       // We do not need to updateNextDelay for the timer that actually fires;
 | |
|       // we'll update right after it fires, with the proper intended time.
 | |
|       // Note that we might select one, then select another later (with an
 | |
|       // earlier intended time); it is still ok that we did not update for
 | |
|       // the first one, since if we have skipped firings, the next delay
 | |
|       // will be the minimum delay anyhow.
 | |
|       if (!selected) {
 | |
|         updateNextDelay(intendedTime - now);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     var catMan = Cc["@mozilla.org/categorymanager;1"].
 | |
|                  getService(Ci.nsICategoryManager);
 | |
|     for (let {data: entry} of catMan.enumerateCategory(CATEGORY_UPDATE_TIMER)) {
 | |
|       let value = catMan.getCategoryEntry(CATEGORY_UPDATE_TIMER, entry);
 | |
|       let [cid, method, timerID, prefInterval, defaultInterval, maxInterval] = value.split(",");
 | |
| 
 | |
|       defaultInterval = parseInt(defaultInterval);
 | |
|       // cid and method are validated below when calling notify.
 | |
|       if (!timerID || !defaultInterval || isNaN(defaultInterval)) {
 | |
|         LOG("TimerManager:notify - update-timer category registered" +
 | |
|             (cid ? " for " + cid : "") + " without required parameters - " +
 | |
|              "skipping");
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       let interval = Services.prefs.getIntPref(prefInterval, defaultInterval);
 | |
|       // Allow the update-timer category to specify a maximum value to prevent
 | |
|       // values larger than desired.
 | |
|       maxInterval = parseInt(maxInterval);
 | |
|       if (maxInterval && !isNaN(maxInterval)) {
 | |
|         interval = Math.min(interval, maxInterval);
 | |
|       }
 | |
|       let prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/,
 | |
|                                                                       timerID);
 | |
|       // Initialize the last update time to 0 when the preference isn't set so
 | |
|       // the timer will be notified soon after a new profile's first use.
 | |
|       lastUpdateTime = Services.prefs.getIntPref(prefLastUpdate, 0);
 | |
| 
 | |
|       // If the last update time is greater than the current time then reset
 | |
|       // it to 0 and the timer manager will correct the value when it fires
 | |
|       // next for this consumer.
 | |
|       if (lastUpdateTime > now) {
 | |
|         lastUpdateTime = 0;
 | |
|       }
 | |
| 
 | |
|       if (lastUpdateTime == 0) {
 | |
|         Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
 | |
|       }
 | |
| 
 | |
|       tryFire(function() {
 | |
|         try {
 | |
|           Cc[cid][method](Ci.nsITimerCallback).notify(timer);
 | |
|           LOG("TimerManager:notify - notified " + cid);
 | |
|         } catch (e) {
 | |
|           LOG("TimerManager:notify - error notifying component id: " +
 | |
|               cid + " ,error: " + e);
 | |
|         }
 | |
|         lastUpdateTime = now;
 | |
|         Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
 | |
|         updateNextDelay(lastUpdateTime + interval - now);
 | |
|       }, lastUpdateTime + interval);
 | |
|     }
 | |
| 
 | |
|     for (let _timerID in this._timers) {
 | |
|       let timerID = _timerID; // necessary for the closure to work properly
 | |
|       let timerData = this._timers[timerID];
 | |
|       // If the last update time is greater than the current time then reset
 | |
|       // it to 0 and the timer manager will correct the value when it fires
 | |
|       // next for this consumer.
 | |
|       if (timerData.lastUpdateTime > now) {
 | |
|         let prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, timerID);
 | |
|         timerData.lastUpdateTime = 0;
 | |
|         Services.prefs.setIntPref(prefLastUpdate, timerData.lastUpdateTime);
 | |
|       }
 | |
|       tryFire(function() {
 | |
|         if (timerData.callback && timerData.callback.notify) {
 | |
|           ChromeUtils.idleDispatch(() => {
 | |
|             try {
 | |
|               timerData.callback.notify(timer);
 | |
|               LOG("TimerManager:notify - notified timerID: " + timerID);
 | |
|             } catch (e) {
 | |
|               LOG("TimerManager:notify - error notifying timerID: " + timerID +
 | |
|                   ", error: " + e);
 | |
|             }
 | |
|           });
 | |
|         } else {
 | |
|           LOG("TimerManager:notify - timerID: " + timerID + " doesn't " +
 | |
|               "implement nsITimerCallback - skipping");
 | |
|         }
 | |
|         lastUpdateTime = now;
 | |
|         timerData.lastUpdateTime = lastUpdateTime;
 | |
|         let prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, timerID);
 | |
|         Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
 | |
|         updateNextDelay(timerData.lastUpdateTime + timerData.interval - now);
 | |
|       }, timerData.lastUpdateTime + timerData.interval);
 | |
|     }
 | |
| 
 | |
|     if (callbackToFire) {
 | |
|       callbackToFire();
 | |
|     }
 | |
| 
 | |
|     if (nextDelay !== null) {
 | |
|       if (skippedFirings) {
 | |
|         timer.delay = this._timerMinimumDelay;
 | |
|       } else {
 | |
|         timer.delay = Math.max(nextDelay * 1000, this._timerMinimumDelay);
 | |
|       }
 | |
|       this.lastTimerReset = Date.now();
 | |
|     } else {
 | |
|       this._cancelTimer();
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Starts the timer, if necessary, and ensures that it will fire soon enough
 | |
|    * to happen after time |interval| (in milliseconds).
 | |
|    */
 | |
|   _ensureTimer(interval) {
 | |
|     if (!this._canEnsureTimer) {
 | |
|       return;
 | |
|     }
 | |
|     if (!this._timer) {
 | |
|       this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
 | |
|       this._timer.initWithCallback(this, interval,
 | |
|                                    Ci.nsITimer.TYPE_REPEATING_SLACK);
 | |
|       this.lastTimerReset = Date.now();
 | |
|     } else if (Date.now() + interval < this.lastTimerReset + this._timer.delay) {
 | |
|       this._timer.delay = Math.max(this.lastTimerReset + interval - Date.now(), 0);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Stops the timer, if it is running.
 | |
|    */
 | |
|   _cancelTimer() {
 | |
|     if (this._timer) {
 | |
|       this._timer.cancel();
 | |
|       this._timer = null;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * See nsIUpdateTimerManager.idl
 | |
|    */
 | |
|   registerTimer: function TM_registerTimer(id, callback, interval) {
 | |
|     LOG("TimerManager:registerTimer - id: " + id);
 | |
|     if (this._timers === null) {
 | |
|       // Use normal logging since reportError is not available while shutting
 | |
|       // down.
 | |
|       gLogEnabled = true;
 | |
|       LOG("TimerManager:registerTimer called after profile-before-change " +
 | |
|           "notification. Ignoring timer registration for id: " + id);
 | |
|       return;
 | |
|     }
 | |
|     if (id in this._timers && callback != this._timers[id].callback) {
 | |
|       LOG("TimerManager:registerTimer - Ignoring second registration for " + id);
 | |
|       return;
 | |
|     }
 | |
|     let prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, id);
 | |
|     // Initialize the last update time to 0 when the preference isn't set so
 | |
|     // the timer will be notified soon after a new profile's first use.
 | |
|     let lastUpdateTime = Services.prefs.getIntPref(prefLastUpdate, 0);
 | |
|     let now = Math.round(Date.now() / 1000);
 | |
|     if (lastUpdateTime > now) {
 | |
|       lastUpdateTime = 0;
 | |
|     }
 | |
|     if (lastUpdateTime == 0) {
 | |
|       Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
 | |
|     }
 | |
|     this._timers[id] = {callback,
 | |
|                         interval,
 | |
|                         lastUpdateTime};
 | |
| 
 | |
|     this._ensureTimer(interval * 1000);
 | |
|   },
 | |
| 
 | |
|   unregisterTimer: function TM_unregisterTimer(id) {
 | |
|     LOG("TimerManager:unregisterTimer - id: " + id);
 | |
|     if (id in this._timers) {
 | |
|       delete this._timers[id];
 | |
|     } else {
 | |
|       LOG("TimerManager:unregisterTimer - Ignoring unregistration request for " +
 | |
|           "unknown id: " + id);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   classID: Components.ID("{B322A5C0-A419-484E-96BA-D7182163899F}"),
 | |
|   QueryInterface: ChromeUtils.generateQI([Ci.nsIUpdateTimerManager,
 | |
|                                           Ci.nsITimerCallback,
 | |
|                                           Ci.nsIObserver])
 | |
| };
 | |
| 
 | |
| this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TimerManager]);
 |