dom/alarm/AlarmService.jsm

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/dom/alarm/AlarmService.jsm	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,563 @@
     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 file,
     1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +"use strict";
     1.9 +
    1.10 +/* static functions */
    1.11 +const DEBUG = false;
    1.12 +
    1.13 +function debug(aStr) {
    1.14 +  if (DEBUG)
    1.15 +    dump("AlarmService: " + aStr + "\n");
    1.16 +}
    1.17 +
    1.18 +const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
    1.19 +
    1.20 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.21 +Cu.import("resource://gre/modules/Services.jsm");
    1.22 +Cu.import("resource://gre/modules/AlarmDB.jsm");
    1.23 +
    1.24 +this.EXPORTED_SYMBOLS = ["AlarmService"];
    1.25 +
    1.26 +XPCOMUtils.defineLazyGetter(this, "appsService", function() {
    1.27 +  return Cc["@mozilla.org/AppsService;1"].getService(Ci.nsIAppsService);
    1.28 +});
    1.29 +
    1.30 +XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
    1.31 +                                   "@mozilla.org/parentprocessmessagemanager;1",
    1.32 +                                   "nsIMessageListenerManager");
    1.33 +
    1.34 +XPCOMUtils.defineLazyGetter(this, "messenger", function() {
    1.35 +  return Cc["@mozilla.org/system-message-internal;1"].getService(Ci.nsISystemMessagesInternal);
    1.36 +});
    1.37 +
    1.38 +XPCOMUtils.defineLazyGetter(this, "powerManagerService", function() {
    1.39 +  return Cc["@mozilla.org/power/powermanagerservice;1"].getService(Ci.nsIPowerManagerService);
    1.40 +});
    1.41 +
    1.42 +/**
    1.43 + * AlarmService provides an API to schedule alarms using the device's RTC.
    1.44 + *
    1.45 + * AlarmService is primarily used by the mozAlarms API (navigator.mozAlarms)
    1.46 + * which uses IPC to communicate with the service.
    1.47 + *
    1.48 + * AlarmService can also be used by Gecko code by importing the module and then
    1.49 + * using AlarmService.add() and AlarmService.remove(). Only Gecko code running
    1.50 + * in the parent process should do this.
    1.51 + */
    1.52 +
    1.53 +this.AlarmService = {
    1.54 +  init: function init() {
    1.55 +    debug("init()");
    1.56 +    Services.obs.addObserver(this, "profile-change-teardown", false);
    1.57 +    Services.obs.addObserver(this, "webapps-clear-data",false);
    1.58 +
    1.59 +    this._currentTimezoneOffset = (new Date()).getTimezoneOffset();
    1.60 +
    1.61 +    let alarmHalService =
    1.62 +      this._alarmHalService = Cc["@mozilla.org/alarmHalService;1"]
    1.63 +                              .getService(Ci.nsIAlarmHalService);
    1.64 +
    1.65 +    alarmHalService.setAlarmFiredCb(this._onAlarmFired.bind(this));
    1.66 +    alarmHalService.setTimezoneChangedCb(this._onTimezoneChanged.bind(this));
    1.67 +
    1.68 +    // Add the messages to be listened to.
    1.69 +    this._messages = ["AlarmsManager:GetAll",
    1.70 +                      "AlarmsManager:Add",
    1.71 +                      "AlarmsManager:Remove"];
    1.72 +    this._messages.forEach(function addMessage(msgName) {
    1.73 +      ppmm.addMessageListener(msgName, this);
    1.74 +    }.bind(this));
    1.75 +
    1.76 +    // Set the indexeddb database.
    1.77 +    this._db = new AlarmDB();
    1.78 +    this._db.init();
    1.79 +
    1.80 +    // Variable to save alarms waiting to be set.
    1.81 +    this._alarmQueue = [];
    1.82 +
    1.83 +    this._restoreAlarmsFromDb();
    1.84 +  },
    1.85 +
    1.86 +  // Getter/setter to access the current alarm set in system.
    1.87 +  _alarm: null,
    1.88 +  get _currentAlarm() {
    1.89 +    return this._alarm;
    1.90 +  },
    1.91 +  set _currentAlarm(aAlarm) {
    1.92 +    this._alarm = aAlarm;
    1.93 +    if (!aAlarm) {
    1.94 +      return;
    1.95 +    }
    1.96 +
    1.97 +    let alarmTimeInMs = this._getAlarmTime(aAlarm);
    1.98 +    let ns = (alarmTimeInMs % 1000) * 1000000;
    1.99 +    if (!this._alarmHalService.setAlarm(alarmTimeInMs / 1000, ns)) {
   1.100 +      throw Components.results.NS_ERROR_FAILURE;
   1.101 +    }
   1.102 +  },
   1.103 +
   1.104 +  receiveMessage: function receiveMessage(aMessage) {
   1.105 +    debug("receiveMessage(): " + aMessage.name);
   1.106 +    let json = aMessage.json;
   1.107 +
   1.108 +    // To prevent the hacked child process from sending commands to parent
   1.109 +    // to schedule alarms, we need to check its permission and manifest URL.
   1.110 +    if (this._messages.indexOf(aMessage.name) != -1) {
   1.111 +      if (!aMessage.target.assertPermission("alarms")) {
   1.112 +        debug("Got message from a child process with no 'alarms' permission.");
   1.113 +        return null;
   1.114 +      }
   1.115 +      if (!aMessage.target.assertContainApp(json.manifestURL)) {
   1.116 +        debug("Got message from a child process containing illegal manifest URL.");
   1.117 +        return null;
   1.118 +      }
   1.119 +    }
   1.120 +
   1.121 +    let mm = aMessage.target.QueryInterface(Ci.nsIMessageSender);
   1.122 +    switch (aMessage.name) {
   1.123 +      case "AlarmsManager:GetAll":
   1.124 +        this._db.getAll(
   1.125 +          json.manifestURL,
   1.126 +          function getAllSuccessCb(aAlarms) {
   1.127 +            debug("Callback after getting alarms from database: " +
   1.128 +                  JSON.stringify(aAlarms));
   1.129 +            this._sendAsyncMessage(mm, "GetAll", true, json.requestId, aAlarms);
   1.130 +          }.bind(this),
   1.131 +          function getAllErrorCb(aErrorMsg) {
   1.132 +            this._sendAsyncMessage(mm, "GetAll", false, json.requestId, aErrorMsg);
   1.133 +          }.bind(this)
   1.134 +        );
   1.135 +        break;
   1.136 +
   1.137 +      case "AlarmsManager:Add":
   1.138 +        // Prepare a record for the new alarm to be added.
   1.139 +        let newAlarm = {
   1.140 +          date: json.date,
   1.141 +          ignoreTimezone: json.ignoreTimezone,
   1.142 +          data: json.data,
   1.143 +          pageURL: json.pageURL,
   1.144 +          manifestURL: json.manifestURL
   1.145 +        };
   1.146 +
   1.147 +        this.add(newAlarm, null,
   1.148 +          // Receives the alarm ID as the last argument.
   1.149 +          this._sendAsyncMessage.bind(this, mm, "Add", true, json.requestId),
   1.150 +          // Receives the error message as the last argument.
   1.151 +          this._sendAsyncMessage.bind(this, mm, "Add", false, json.requestId)
   1.152 +        );
   1.153 +        break;
   1.154 +
   1.155 +      case "AlarmsManager:Remove":
   1.156 +        this.remove(json.id, json.manifestURL);
   1.157 +        break;
   1.158 +
   1.159 +      default:
   1.160 +        throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
   1.161 +        break;
   1.162 +    }
   1.163 +  },
   1.164 +
   1.165 +  _sendAsyncMessage: function _sendAsyncMessage(aMessageManager, aMessageName,
   1.166 +                                                aSuccess, aRequestId, aData) {
   1.167 +    debug("_sendAsyncMessage()");
   1.168 +
   1.169 +    if (!aMessageManager) {
   1.170 +      debug("Invalid message manager: null");
   1.171 +      throw Components.results.NS_ERROR_FAILURE;
   1.172 +    }
   1.173 +
   1.174 +    let json = null;
   1.175 +    switch (aMessageName)
   1.176 +    {
   1.177 +      case "Add":
   1.178 +        json = aSuccess ?
   1.179 +          { requestId: aRequestId, id: aData } :
   1.180 +          { requestId: aRequestId, errorMsg: aData };
   1.181 +        break;
   1.182 +
   1.183 +      case "GetAll":
   1.184 +        json = aSuccess ?
   1.185 +          { requestId: aRequestId, alarms: aData } :
   1.186 +          { requestId: aRequestId, errorMsg: aData };
   1.187 +        break;
   1.188 +
   1.189 +      default:
   1.190 +        throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
   1.191 +        break;
   1.192 +    }
   1.193 +
   1.194 +    aMessageManager.sendAsyncMessage("AlarmsManager:" + aMessageName +
   1.195 +                                     ":Return:" + (aSuccess ? "OK" : "KO"), json);
   1.196 +  },
   1.197 +
   1.198 +  _removeAlarmFromDb: function _removeAlarmFromDb(aId, aManifestURL,
   1.199 +                                                  aRemoveSuccessCb) {
   1.200 +    debug("_removeAlarmFromDb()");
   1.201 +
   1.202 +    // If the aRemoveSuccessCb is undefined or null, set a dummy callback for
   1.203 +    // it which is needed for _db.remove().
   1.204 +    if (!aRemoveSuccessCb) {
   1.205 +      aRemoveSuccessCb = function removeSuccessCb() {
   1.206 +        debug("Remove alarm from DB successfully.");
   1.207 +      };
   1.208 +    }
   1.209 +
   1.210 +    this._db.remove(
   1.211 +      aId,
   1.212 +      aManifestURL,
   1.213 +      aRemoveSuccessCb,
   1.214 +      function removeErrorCb(aErrorMsg) {
   1.215 +        throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
   1.216 +      }
   1.217 +    );
   1.218 +  },
   1.219 +
   1.220 +  /**
   1.221 +   * Create a copy of the alarm that does not expose internal fields to
   1.222 +   * receivers and sticks to the public |respectTimezone| API rather than the
   1.223 +   * boolean |ignoreTimezone| field.
   1.224 +   */
   1.225 +  _publicAlarm: function _publicAlarm(aAlarm) {
   1.226 +    let alarm = {
   1.227 +      "id":              aAlarm.id,
   1.228 +      "date":            aAlarm.date,
   1.229 +      "respectTimezone": aAlarm.ignoreTimezone ?
   1.230 +                           "ignoreTimezone" : "honorTimezone",
   1.231 +      "data":            aAlarm.data
   1.232 +    };
   1.233 +
   1.234 +    return alarm;
   1.235 +  },
   1.236 +
   1.237 +  _fireSystemMessage: function _fireSystemMessage(aAlarm) {
   1.238 +    debug("Fire system message: " + JSON.stringify(aAlarm));
   1.239 +
   1.240 +    let manifestURI = Services.io.newURI(aAlarm.manifestURL, null, null);
   1.241 +    let pageURI = Services.io.newURI(aAlarm.pageURL, null, null);
   1.242 +
   1.243 +    messenger.sendMessage("alarm", this._publicAlarm(aAlarm),
   1.244 +                          pageURI, manifestURI);
   1.245 +  },
   1.246 +
   1.247 +  _notifyAlarmObserver: function _notifyAlarmObserver(aAlarm) {
   1.248 +    debug("_notifyAlarmObserver()");
   1.249 +
   1.250 +    if (aAlarm.manifestURL) {
   1.251 +      this._fireSystemMessage(aAlarm);
   1.252 +    } else if (typeof aAlarm.alarmFiredCb === "function") {
   1.253 +      aAlarm.alarmFiredCb(this._publicAlarm(aAlarm));
   1.254 +    }
   1.255 +  },
   1.256 +
   1.257 +  _onAlarmFired: function _onAlarmFired() {
   1.258 +    debug("_onAlarmFired()");
   1.259 +
   1.260 +    if (this._currentAlarm) {
   1.261 +      this._removeAlarmFromDb(this._currentAlarm.id, null);
   1.262 +      this._notifyAlarmObserver(this._currentAlarm);
   1.263 +      this._currentAlarm = null;
   1.264 +    }
   1.265 +
   1.266 +    // Reset the next alarm from the queue.
   1.267 +    let alarmQueue = this._alarmQueue;
   1.268 +    while (alarmQueue.length > 0) {
   1.269 +      let nextAlarm = alarmQueue.shift();
   1.270 +      let nextAlarmTime = this._getAlarmTime(nextAlarm);
   1.271 +
   1.272 +      // If the next alarm has been expired, directly notify the observer.
   1.273 +      // it instead of setting it.
   1.274 +      if (nextAlarmTime <= Date.now()) {
   1.275 +        this._removeAlarmFromDb(nextAlarm.id, null);
   1.276 +        this._notifyAlarmObserver(nextAlarm);
   1.277 +      } else {
   1.278 +        this._currentAlarm = nextAlarm;
   1.279 +        break;
   1.280 +      }
   1.281 +    }
   1.282 +    this._debugCurrentAlarm();
   1.283 +  },
   1.284 +
   1.285 +  _onTimezoneChanged: function _onTimezoneChanged(aTimezoneOffset) {
   1.286 +    debug("_onTimezoneChanged()");
   1.287 +
   1.288 +    this._currentTimezoneOffset = aTimezoneOffset;
   1.289 +    this._restoreAlarmsFromDb();
   1.290 +  },
   1.291 +
   1.292 +  _restoreAlarmsFromDb: function _restoreAlarmsFromDb() {
   1.293 +    debug("_restoreAlarmsFromDb()");
   1.294 +
   1.295 +    this._db.getAll(
   1.296 +      null,
   1.297 +      function getAllSuccessCb(aAlarms) {
   1.298 +        debug("Callback after getting alarms from database: " +
   1.299 +              JSON.stringify(aAlarms));
   1.300 +
   1.301 +        // Clear any alarms set or queued in the cache.
   1.302 +        let alarmQueue = this._alarmQueue;
   1.303 +        alarmQueue.length = 0;
   1.304 +        this._currentAlarm = null;
   1.305 +
   1.306 +        // Only restore the alarm that's not yet expired; otherwise, remove it
   1.307 +        // from the database and notify the observer.
   1.308 +        aAlarms.forEach(function addAlarm(aAlarm) {
   1.309 +          if (this._getAlarmTime(aAlarm) > Date.now()) {
   1.310 +            alarmQueue.push(aAlarm);
   1.311 +          } else {
   1.312 +            this._removeAlarmFromDb(aAlarm.id, null);
   1.313 +            this._notifyAlarmObserver(aAlarm);
   1.314 +          }
   1.315 +        }.bind(this));
   1.316 +
   1.317 +        // Set the next alarm from queue.
   1.318 +        if (alarmQueue.length) {
   1.319 +          alarmQueue.sort(this._sortAlarmByTimeStamps.bind(this));
   1.320 +          this._currentAlarm = alarmQueue.shift();
   1.321 +        }
   1.322 +
   1.323 +        this._debugCurrentAlarm();
   1.324 +      }.bind(this),
   1.325 +      function getAllErrorCb(aErrorMsg) {
   1.326 +        throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
   1.327 +      }
   1.328 +    );
   1.329 +  },
   1.330 +
   1.331 +  _getAlarmTime: function _getAlarmTime(aAlarm) {
   1.332 +    // Avoid casting a Date object to a Date again to
   1.333 +    // preserve milliseconds. See bug 810973.
   1.334 +    let alarmTime;
   1.335 +    if (aAlarm.date instanceof Date) {
   1.336 +      alarmTime = aAlarm.date.getTime();
   1.337 +    } else {
   1.338 +      alarmTime = (new Date(aAlarm.date)).getTime();
   1.339 +    }
   1.340 +
   1.341 +    // For an alarm specified with "ignoreTimezone", it must be fired respect
   1.342 +    // to the user's timezone.  Supposing an alarm was set at 7:00pm at Tokyo,
   1.343 +    // it must be gone off at 7:00pm respect to Paris' local time when the user
   1.344 +    // is located at Paris.  We can adjust the alarm UTC time by calculating
   1.345 +    // the difference of the orginal timezone and the current timezone.
   1.346 +    if (aAlarm.ignoreTimezone) {
   1.347 +       alarmTime += (this._currentTimezoneOffset - aAlarm.timezoneOffset) * 60000;
   1.348 +    }
   1.349 +    return alarmTime;
   1.350 +  },
   1.351 +
   1.352 +  _sortAlarmByTimeStamps: function _sortAlarmByTimeStamps(aAlarm1, aAlarm2) {
   1.353 +    return this._getAlarmTime(aAlarm1) - this._getAlarmTime(aAlarm2);
   1.354 +  },
   1.355 +
   1.356 +  _debugCurrentAlarm: function _debugCurrentAlarm() {
   1.357 +    debug("Current alarm: " + JSON.stringify(this._currentAlarm));
   1.358 +    debug("Alarm queue: " + JSON.stringify(this._alarmQueue));
   1.359 +  },
   1.360 +
   1.361 +  /**
   1.362 +   *
   1.363 +   * Add a new alarm. This will set the RTC to fire at the selected date and
   1.364 +   * notify the caller. Notifications are delivered via System Messages if the
   1.365 +   * alarm is added on behalf of a app. Otherwise aAlarmFiredCb is called.
   1.366 +   *
   1.367 +   * @param object aNewAlarm
   1.368 +   *        Should contain the following literal properties:
   1.369 +   *          - |date| date: when the alarm should timeout.
   1.370 +   *          - |ignoreTimezone| boolean: See [1] for the details.
   1.371 +   *          - |manifestURL| string: Manifest of app on whose behalf the alarm
   1.372 +   *                                  is added.
   1.373 +   *          - |pageURL| string: The page in the app that receives the system
   1.374 +   *                              message.
   1.375 +   *          - |data| object [optional]: Data that can be stored in DB.
   1.376 +   * @param function aAlarmFiredCb
   1.377 +   *        Callback function invoked when the alarm is fired.
   1.378 +   *        It receives a single argument, the alarm object.
   1.379 +   *        May be null.
   1.380 +   * @param function aSuccessCb
   1.381 +   *        Callback function to receive an alarm ID (number).
   1.382 +   * @param function aErrorCb
   1.383 +   *        Callback function to receive an error message (string).
   1.384 +   * @returns void
   1.385 +   *
   1.386 +   * Notes:
   1.387 +   * [1] https://wiki.mozilla.org/WebAPI/AlarmAPI#Proposed_API
   1.388 +   */
   1.389 +
   1.390 +  add: function(aNewAlarm, aAlarmFiredCb, aSuccessCb, aErrorCb) {
   1.391 +    debug("add(" + aNewAlarm.date + ")");
   1.392 +
   1.393 +    aSuccessCb = aSuccessCb || function() {};
   1.394 +    aErrorCb = aErrorCb || function() {};
   1.395 +
   1.396 +    if (!aNewAlarm) {
   1.397 +      aErrorCb("alarm is null");
   1.398 +      return;
   1.399 +    }
   1.400 +
   1.401 +    if (!aNewAlarm.date) {
   1.402 +      aErrorCb("alarm.date is null");
   1.403 +      return;
   1.404 +    }
   1.405 +
   1.406 +    aNewAlarm['timezoneOffset'] = this._currentTimezoneOffset;
   1.407 +
   1.408 +    this._db.add(
   1.409 +      aNewAlarm,
   1.410 +      function addSuccessCb(aNewId) {
   1.411 +        debug("Callback after adding alarm in database.");
   1.412 +
   1.413 +        aNewAlarm['id'] = aNewId;
   1.414 +
   1.415 +        // Now that the alarm has been added to the database, we can tack on
   1.416 +        // the non-serializable callback to the in-memory object.
   1.417 +        aNewAlarm['alarmFiredCb'] = aAlarmFiredCb;
   1.418 +
   1.419 +        // If there is no alarm being set in system, set the new alarm.
   1.420 +        if (this._currentAlarm == null) {
   1.421 +          this._currentAlarm = aNewAlarm;
   1.422 +          this._debugCurrentAlarm();
   1.423 +          aSuccessCb(aNewId);
   1.424 +          return;
   1.425 +        }
   1.426 +
   1.427 +        // If the new alarm is earlier than the current alarm, swap them and
   1.428 +        // push the previous alarm back to queue.
   1.429 +        let alarmQueue = this._alarmQueue;
   1.430 +        let aNewAlarmTime = this._getAlarmTime(aNewAlarm);
   1.431 +        let currentAlarmTime = this._getAlarmTime(this._currentAlarm);
   1.432 +        if (aNewAlarmTime < currentAlarmTime) {
   1.433 +          alarmQueue.unshift(this._currentAlarm);
   1.434 +          this._currentAlarm = aNewAlarm;
   1.435 +          this._debugCurrentAlarm();
   1.436 +          aSuccessCb(aNewId);
   1.437 +          return;
   1.438 +        }
   1.439 +
   1.440 +        // Push the new alarm in the queue.
   1.441 +        alarmQueue.push(aNewAlarm);
   1.442 +        alarmQueue.sort(this._sortAlarmByTimeStamps.bind(this));
   1.443 +        this._debugCurrentAlarm();
   1.444 +        aSuccessCb(aNewId);
   1.445 +      }.bind(this),
   1.446 +      function addErrorCb(aErrorMsg) {
   1.447 +        aErrorCb(aErrorMsg);
   1.448 +      }.bind(this)
   1.449 +    );
   1.450 +  },
   1.451 +
   1.452 +  /*
   1.453 +   * Remove the alarm associated with an ID.
   1.454 +   *
   1.455 +   * @param number aAlarmId
   1.456 +   *        The ID of the alarm to be removed.
   1.457 +   * @param string aManifestURL
   1.458 +   *        Manifest URL for application which added the alarm. (Optional)
   1.459 +   * @returns void
   1.460 +   */
   1.461 +  remove: function(aAlarmId, aManifestURL) {
   1.462 +    debug("remove(" + aAlarmId + ", " + aManifestURL + ")");
   1.463 +    this._removeAlarmFromDb(
   1.464 +      aAlarmId,
   1.465 +      aManifestURL,
   1.466 +      function removeSuccessCb() {
   1.467 +        debug("Callback after removing alarm from database.");
   1.468 +
   1.469 +        // If there are no alarms set, nothing to do.
   1.470 +        if (!this._currentAlarm) {
   1.471 +          debug("No alarms set.");
   1.472 +          return;
   1.473 +        }
   1.474 +
   1.475 +        // Check if the alarm to be removed is in the queue and whether it
   1.476 +        // belongs to the requesting app.
   1.477 +        let alarmQueue = this._alarmQueue;
   1.478 +        if (this._currentAlarm.id != aAlarmId ||
   1.479 +            this._currentAlarm.manifestURL != aManifestURL) {
   1.480 +
   1.481 +          for (let i = 0; i < alarmQueue.length; i++) {
   1.482 +            if (alarmQueue[i].id == aAlarmId &&
   1.483 +                alarmQueue[i].manifestURL == aManifestURL) {
   1.484 +
   1.485 +              alarmQueue.splice(i, 1);
   1.486 +              break;
   1.487 +            }
   1.488 +          }
   1.489 +          this._debugCurrentAlarm();
   1.490 +          return;
   1.491 +        }
   1.492 +
   1.493 +        // The alarm to be removed is the current alarm reset the next alarm
   1.494 +        // from queue if any.
   1.495 +        if (alarmQueue.length) {
   1.496 +          this._currentAlarm = alarmQueue.shift();
   1.497 +          this._debugCurrentAlarm();
   1.498 +          return;
   1.499 +        }
   1.500 +
   1.501 +        // No alarm waiting to be set in the queue.
   1.502 +        this._currentAlarm = null;
   1.503 +        this._debugCurrentAlarm();
   1.504 +      }.bind(this)
   1.505 +    );
   1.506 +  },
   1.507 +
   1.508 +  observe: function(aSubject, aTopic, aData) {
   1.509 +    switch (aTopic) {
   1.510 +      case "profile-change-teardown":
   1.511 +        this.uninit();
   1.512 +        break;
   1.513 +      case "webapps-clear-data":
   1.514 +        let params =
   1.515 +          aSubject.QueryInterface(Ci.mozIApplicationClearPrivateDataParams);
   1.516 +        if (!params) {
   1.517 +          debug("Error! Fail to remove alarms for an uninstalled app.");
   1.518 +          return;
   1.519 +        }
   1.520 +
   1.521 +        // Only remove alarms for apps.
   1.522 +        if (params.browserOnly) {
   1.523 +          return;
   1.524 +        }
   1.525 +
   1.526 +        let manifestURL = appsService.getManifestURLByLocalId(params.appId);
   1.527 +        if (!manifestURL) {
   1.528 +          debug("Error! Fail to remove alarms for an uninstalled app.");
   1.529 +          return;
   1.530 +        }
   1.531 +
   1.532 +        this._db.getAll(
   1.533 +          manifestURL,
   1.534 +          function getAllSuccessCb(aAlarms) {
   1.535 +            aAlarms.forEach(function removeAlarm(aAlarm) {
   1.536 +              this.remove(aAlarm.id, manifestURL);
   1.537 +            }, this);
   1.538 +          }.bind(this),
   1.539 +          function getAllErrorCb(aErrorMsg) {
   1.540 +            throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
   1.541 +          }
   1.542 +        );
   1.543 +        break;
   1.544 +    }
   1.545 +  },
   1.546 +
   1.547 +  uninit: function uninit() {
   1.548 +    debug("uninit()");
   1.549 +    Services.obs.removeObserver(this, "profile-change-teardown");
   1.550 +    Services.obs.removeObserver(this, "webapps-clear-data");
   1.551 +
   1.552 +    this._messages.forEach(function(aMsgName) {
   1.553 +      ppmm.removeMessageListener(aMsgName, this);
   1.554 +    }.bind(this));
   1.555 +    ppmm = null;
   1.556 +
   1.557 +    if (this._db) {
   1.558 +      this._db.close();
   1.559 +    }
   1.560 +    this._db = null;
   1.561 +
   1.562 +    this._alarmHalService = null;
   1.563 +  }
   1.564 +}
   1.565 +
   1.566 +AlarmService.init();

mercurial