Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 3 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | "use strict"; |
michael@0 | 6 | |
michael@0 | 7 | /* static functions */ |
michael@0 | 8 | const DEBUG = false; |
michael@0 | 9 | |
michael@0 | 10 | function debug(aStr) { |
michael@0 | 11 | if (DEBUG) |
michael@0 | 12 | dump("AlarmService: " + aStr + "\n"); |
michael@0 | 13 | } |
michael@0 | 14 | |
michael@0 | 15 | const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; |
michael@0 | 16 | |
michael@0 | 17 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 18 | Cu.import("resource://gre/modules/Services.jsm"); |
michael@0 | 19 | Cu.import("resource://gre/modules/AlarmDB.jsm"); |
michael@0 | 20 | |
michael@0 | 21 | this.EXPORTED_SYMBOLS = ["AlarmService"]; |
michael@0 | 22 | |
michael@0 | 23 | XPCOMUtils.defineLazyGetter(this, "appsService", function() { |
michael@0 | 24 | return Cc["@mozilla.org/AppsService;1"].getService(Ci.nsIAppsService); |
michael@0 | 25 | }); |
michael@0 | 26 | |
michael@0 | 27 | XPCOMUtils.defineLazyServiceGetter(this, "ppmm", |
michael@0 | 28 | "@mozilla.org/parentprocessmessagemanager;1", |
michael@0 | 29 | "nsIMessageListenerManager"); |
michael@0 | 30 | |
michael@0 | 31 | XPCOMUtils.defineLazyGetter(this, "messenger", function() { |
michael@0 | 32 | return Cc["@mozilla.org/system-message-internal;1"].getService(Ci.nsISystemMessagesInternal); |
michael@0 | 33 | }); |
michael@0 | 34 | |
michael@0 | 35 | XPCOMUtils.defineLazyGetter(this, "powerManagerService", function() { |
michael@0 | 36 | return Cc["@mozilla.org/power/powermanagerservice;1"].getService(Ci.nsIPowerManagerService); |
michael@0 | 37 | }); |
michael@0 | 38 | |
michael@0 | 39 | /** |
michael@0 | 40 | * AlarmService provides an API to schedule alarms using the device's RTC. |
michael@0 | 41 | * |
michael@0 | 42 | * AlarmService is primarily used by the mozAlarms API (navigator.mozAlarms) |
michael@0 | 43 | * which uses IPC to communicate with the service. |
michael@0 | 44 | * |
michael@0 | 45 | * AlarmService can also be used by Gecko code by importing the module and then |
michael@0 | 46 | * using AlarmService.add() and AlarmService.remove(). Only Gecko code running |
michael@0 | 47 | * in the parent process should do this. |
michael@0 | 48 | */ |
michael@0 | 49 | |
michael@0 | 50 | this.AlarmService = { |
michael@0 | 51 | init: function init() { |
michael@0 | 52 | debug("init()"); |
michael@0 | 53 | Services.obs.addObserver(this, "profile-change-teardown", false); |
michael@0 | 54 | Services.obs.addObserver(this, "webapps-clear-data",false); |
michael@0 | 55 | |
michael@0 | 56 | this._currentTimezoneOffset = (new Date()).getTimezoneOffset(); |
michael@0 | 57 | |
michael@0 | 58 | let alarmHalService = |
michael@0 | 59 | this._alarmHalService = Cc["@mozilla.org/alarmHalService;1"] |
michael@0 | 60 | .getService(Ci.nsIAlarmHalService); |
michael@0 | 61 | |
michael@0 | 62 | alarmHalService.setAlarmFiredCb(this._onAlarmFired.bind(this)); |
michael@0 | 63 | alarmHalService.setTimezoneChangedCb(this._onTimezoneChanged.bind(this)); |
michael@0 | 64 | |
michael@0 | 65 | // Add the messages to be listened to. |
michael@0 | 66 | this._messages = ["AlarmsManager:GetAll", |
michael@0 | 67 | "AlarmsManager:Add", |
michael@0 | 68 | "AlarmsManager:Remove"]; |
michael@0 | 69 | this._messages.forEach(function addMessage(msgName) { |
michael@0 | 70 | ppmm.addMessageListener(msgName, this); |
michael@0 | 71 | }.bind(this)); |
michael@0 | 72 | |
michael@0 | 73 | // Set the indexeddb database. |
michael@0 | 74 | this._db = new AlarmDB(); |
michael@0 | 75 | this._db.init(); |
michael@0 | 76 | |
michael@0 | 77 | // Variable to save alarms waiting to be set. |
michael@0 | 78 | this._alarmQueue = []; |
michael@0 | 79 | |
michael@0 | 80 | this._restoreAlarmsFromDb(); |
michael@0 | 81 | }, |
michael@0 | 82 | |
michael@0 | 83 | // Getter/setter to access the current alarm set in system. |
michael@0 | 84 | _alarm: null, |
michael@0 | 85 | get _currentAlarm() { |
michael@0 | 86 | return this._alarm; |
michael@0 | 87 | }, |
michael@0 | 88 | set _currentAlarm(aAlarm) { |
michael@0 | 89 | this._alarm = aAlarm; |
michael@0 | 90 | if (!aAlarm) { |
michael@0 | 91 | return; |
michael@0 | 92 | } |
michael@0 | 93 | |
michael@0 | 94 | let alarmTimeInMs = this._getAlarmTime(aAlarm); |
michael@0 | 95 | let ns = (alarmTimeInMs % 1000) * 1000000; |
michael@0 | 96 | if (!this._alarmHalService.setAlarm(alarmTimeInMs / 1000, ns)) { |
michael@0 | 97 | throw Components.results.NS_ERROR_FAILURE; |
michael@0 | 98 | } |
michael@0 | 99 | }, |
michael@0 | 100 | |
michael@0 | 101 | receiveMessage: function receiveMessage(aMessage) { |
michael@0 | 102 | debug("receiveMessage(): " + aMessage.name); |
michael@0 | 103 | let json = aMessage.json; |
michael@0 | 104 | |
michael@0 | 105 | // To prevent the hacked child process from sending commands to parent |
michael@0 | 106 | // to schedule alarms, we need to check its permission and manifest URL. |
michael@0 | 107 | if (this._messages.indexOf(aMessage.name) != -1) { |
michael@0 | 108 | if (!aMessage.target.assertPermission("alarms")) { |
michael@0 | 109 | debug("Got message from a child process with no 'alarms' permission."); |
michael@0 | 110 | return null; |
michael@0 | 111 | } |
michael@0 | 112 | if (!aMessage.target.assertContainApp(json.manifestURL)) { |
michael@0 | 113 | debug("Got message from a child process containing illegal manifest URL."); |
michael@0 | 114 | return null; |
michael@0 | 115 | } |
michael@0 | 116 | } |
michael@0 | 117 | |
michael@0 | 118 | let mm = aMessage.target.QueryInterface(Ci.nsIMessageSender); |
michael@0 | 119 | switch (aMessage.name) { |
michael@0 | 120 | case "AlarmsManager:GetAll": |
michael@0 | 121 | this._db.getAll( |
michael@0 | 122 | json.manifestURL, |
michael@0 | 123 | function getAllSuccessCb(aAlarms) { |
michael@0 | 124 | debug("Callback after getting alarms from database: " + |
michael@0 | 125 | JSON.stringify(aAlarms)); |
michael@0 | 126 | this._sendAsyncMessage(mm, "GetAll", true, json.requestId, aAlarms); |
michael@0 | 127 | }.bind(this), |
michael@0 | 128 | function getAllErrorCb(aErrorMsg) { |
michael@0 | 129 | this._sendAsyncMessage(mm, "GetAll", false, json.requestId, aErrorMsg); |
michael@0 | 130 | }.bind(this) |
michael@0 | 131 | ); |
michael@0 | 132 | break; |
michael@0 | 133 | |
michael@0 | 134 | case "AlarmsManager:Add": |
michael@0 | 135 | // Prepare a record for the new alarm to be added. |
michael@0 | 136 | let newAlarm = { |
michael@0 | 137 | date: json.date, |
michael@0 | 138 | ignoreTimezone: json.ignoreTimezone, |
michael@0 | 139 | data: json.data, |
michael@0 | 140 | pageURL: json.pageURL, |
michael@0 | 141 | manifestURL: json.manifestURL |
michael@0 | 142 | }; |
michael@0 | 143 | |
michael@0 | 144 | this.add(newAlarm, null, |
michael@0 | 145 | // Receives the alarm ID as the last argument. |
michael@0 | 146 | this._sendAsyncMessage.bind(this, mm, "Add", true, json.requestId), |
michael@0 | 147 | // Receives the error message as the last argument. |
michael@0 | 148 | this._sendAsyncMessage.bind(this, mm, "Add", false, json.requestId) |
michael@0 | 149 | ); |
michael@0 | 150 | break; |
michael@0 | 151 | |
michael@0 | 152 | case "AlarmsManager:Remove": |
michael@0 | 153 | this.remove(json.id, json.manifestURL); |
michael@0 | 154 | break; |
michael@0 | 155 | |
michael@0 | 156 | default: |
michael@0 | 157 | throw Components.results.NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 158 | break; |
michael@0 | 159 | } |
michael@0 | 160 | }, |
michael@0 | 161 | |
michael@0 | 162 | _sendAsyncMessage: function _sendAsyncMessage(aMessageManager, aMessageName, |
michael@0 | 163 | aSuccess, aRequestId, aData) { |
michael@0 | 164 | debug("_sendAsyncMessage()"); |
michael@0 | 165 | |
michael@0 | 166 | if (!aMessageManager) { |
michael@0 | 167 | debug("Invalid message manager: null"); |
michael@0 | 168 | throw Components.results.NS_ERROR_FAILURE; |
michael@0 | 169 | } |
michael@0 | 170 | |
michael@0 | 171 | let json = null; |
michael@0 | 172 | switch (aMessageName) |
michael@0 | 173 | { |
michael@0 | 174 | case "Add": |
michael@0 | 175 | json = aSuccess ? |
michael@0 | 176 | { requestId: aRequestId, id: aData } : |
michael@0 | 177 | { requestId: aRequestId, errorMsg: aData }; |
michael@0 | 178 | break; |
michael@0 | 179 | |
michael@0 | 180 | case "GetAll": |
michael@0 | 181 | json = aSuccess ? |
michael@0 | 182 | { requestId: aRequestId, alarms: aData } : |
michael@0 | 183 | { requestId: aRequestId, errorMsg: aData }; |
michael@0 | 184 | break; |
michael@0 | 185 | |
michael@0 | 186 | default: |
michael@0 | 187 | throw Components.results.NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 188 | break; |
michael@0 | 189 | } |
michael@0 | 190 | |
michael@0 | 191 | aMessageManager.sendAsyncMessage("AlarmsManager:" + aMessageName + |
michael@0 | 192 | ":Return:" + (aSuccess ? "OK" : "KO"), json); |
michael@0 | 193 | }, |
michael@0 | 194 | |
michael@0 | 195 | _removeAlarmFromDb: function _removeAlarmFromDb(aId, aManifestURL, |
michael@0 | 196 | aRemoveSuccessCb) { |
michael@0 | 197 | debug("_removeAlarmFromDb()"); |
michael@0 | 198 | |
michael@0 | 199 | // If the aRemoveSuccessCb is undefined or null, set a dummy callback for |
michael@0 | 200 | // it which is needed for _db.remove(). |
michael@0 | 201 | if (!aRemoveSuccessCb) { |
michael@0 | 202 | aRemoveSuccessCb = function removeSuccessCb() { |
michael@0 | 203 | debug("Remove alarm from DB successfully."); |
michael@0 | 204 | }; |
michael@0 | 205 | } |
michael@0 | 206 | |
michael@0 | 207 | this._db.remove( |
michael@0 | 208 | aId, |
michael@0 | 209 | aManifestURL, |
michael@0 | 210 | aRemoveSuccessCb, |
michael@0 | 211 | function removeErrorCb(aErrorMsg) { |
michael@0 | 212 | throw Components.results.NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 213 | } |
michael@0 | 214 | ); |
michael@0 | 215 | }, |
michael@0 | 216 | |
michael@0 | 217 | /** |
michael@0 | 218 | * Create a copy of the alarm that does not expose internal fields to |
michael@0 | 219 | * receivers and sticks to the public |respectTimezone| API rather than the |
michael@0 | 220 | * boolean |ignoreTimezone| field. |
michael@0 | 221 | */ |
michael@0 | 222 | _publicAlarm: function _publicAlarm(aAlarm) { |
michael@0 | 223 | let alarm = { |
michael@0 | 224 | "id": aAlarm.id, |
michael@0 | 225 | "date": aAlarm.date, |
michael@0 | 226 | "respectTimezone": aAlarm.ignoreTimezone ? |
michael@0 | 227 | "ignoreTimezone" : "honorTimezone", |
michael@0 | 228 | "data": aAlarm.data |
michael@0 | 229 | }; |
michael@0 | 230 | |
michael@0 | 231 | return alarm; |
michael@0 | 232 | }, |
michael@0 | 233 | |
michael@0 | 234 | _fireSystemMessage: function _fireSystemMessage(aAlarm) { |
michael@0 | 235 | debug("Fire system message: " + JSON.stringify(aAlarm)); |
michael@0 | 236 | |
michael@0 | 237 | let manifestURI = Services.io.newURI(aAlarm.manifestURL, null, null); |
michael@0 | 238 | let pageURI = Services.io.newURI(aAlarm.pageURL, null, null); |
michael@0 | 239 | |
michael@0 | 240 | messenger.sendMessage("alarm", this._publicAlarm(aAlarm), |
michael@0 | 241 | pageURI, manifestURI); |
michael@0 | 242 | }, |
michael@0 | 243 | |
michael@0 | 244 | _notifyAlarmObserver: function _notifyAlarmObserver(aAlarm) { |
michael@0 | 245 | debug("_notifyAlarmObserver()"); |
michael@0 | 246 | |
michael@0 | 247 | if (aAlarm.manifestURL) { |
michael@0 | 248 | this._fireSystemMessage(aAlarm); |
michael@0 | 249 | } else if (typeof aAlarm.alarmFiredCb === "function") { |
michael@0 | 250 | aAlarm.alarmFiredCb(this._publicAlarm(aAlarm)); |
michael@0 | 251 | } |
michael@0 | 252 | }, |
michael@0 | 253 | |
michael@0 | 254 | _onAlarmFired: function _onAlarmFired() { |
michael@0 | 255 | debug("_onAlarmFired()"); |
michael@0 | 256 | |
michael@0 | 257 | if (this._currentAlarm) { |
michael@0 | 258 | this._removeAlarmFromDb(this._currentAlarm.id, null); |
michael@0 | 259 | this._notifyAlarmObserver(this._currentAlarm); |
michael@0 | 260 | this._currentAlarm = null; |
michael@0 | 261 | } |
michael@0 | 262 | |
michael@0 | 263 | // Reset the next alarm from the queue. |
michael@0 | 264 | let alarmQueue = this._alarmQueue; |
michael@0 | 265 | while (alarmQueue.length > 0) { |
michael@0 | 266 | let nextAlarm = alarmQueue.shift(); |
michael@0 | 267 | let nextAlarmTime = this._getAlarmTime(nextAlarm); |
michael@0 | 268 | |
michael@0 | 269 | // If the next alarm has been expired, directly notify the observer. |
michael@0 | 270 | // it instead of setting it. |
michael@0 | 271 | if (nextAlarmTime <= Date.now()) { |
michael@0 | 272 | this._removeAlarmFromDb(nextAlarm.id, null); |
michael@0 | 273 | this._notifyAlarmObserver(nextAlarm); |
michael@0 | 274 | } else { |
michael@0 | 275 | this._currentAlarm = nextAlarm; |
michael@0 | 276 | break; |
michael@0 | 277 | } |
michael@0 | 278 | } |
michael@0 | 279 | this._debugCurrentAlarm(); |
michael@0 | 280 | }, |
michael@0 | 281 | |
michael@0 | 282 | _onTimezoneChanged: function _onTimezoneChanged(aTimezoneOffset) { |
michael@0 | 283 | debug("_onTimezoneChanged()"); |
michael@0 | 284 | |
michael@0 | 285 | this._currentTimezoneOffset = aTimezoneOffset; |
michael@0 | 286 | this._restoreAlarmsFromDb(); |
michael@0 | 287 | }, |
michael@0 | 288 | |
michael@0 | 289 | _restoreAlarmsFromDb: function _restoreAlarmsFromDb() { |
michael@0 | 290 | debug("_restoreAlarmsFromDb()"); |
michael@0 | 291 | |
michael@0 | 292 | this._db.getAll( |
michael@0 | 293 | null, |
michael@0 | 294 | function getAllSuccessCb(aAlarms) { |
michael@0 | 295 | debug("Callback after getting alarms from database: " + |
michael@0 | 296 | JSON.stringify(aAlarms)); |
michael@0 | 297 | |
michael@0 | 298 | // Clear any alarms set or queued in the cache. |
michael@0 | 299 | let alarmQueue = this._alarmQueue; |
michael@0 | 300 | alarmQueue.length = 0; |
michael@0 | 301 | this._currentAlarm = null; |
michael@0 | 302 | |
michael@0 | 303 | // Only restore the alarm that's not yet expired; otherwise, remove it |
michael@0 | 304 | // from the database and notify the observer. |
michael@0 | 305 | aAlarms.forEach(function addAlarm(aAlarm) { |
michael@0 | 306 | if (this._getAlarmTime(aAlarm) > Date.now()) { |
michael@0 | 307 | alarmQueue.push(aAlarm); |
michael@0 | 308 | } else { |
michael@0 | 309 | this._removeAlarmFromDb(aAlarm.id, null); |
michael@0 | 310 | this._notifyAlarmObserver(aAlarm); |
michael@0 | 311 | } |
michael@0 | 312 | }.bind(this)); |
michael@0 | 313 | |
michael@0 | 314 | // Set the next alarm from queue. |
michael@0 | 315 | if (alarmQueue.length) { |
michael@0 | 316 | alarmQueue.sort(this._sortAlarmByTimeStamps.bind(this)); |
michael@0 | 317 | this._currentAlarm = alarmQueue.shift(); |
michael@0 | 318 | } |
michael@0 | 319 | |
michael@0 | 320 | this._debugCurrentAlarm(); |
michael@0 | 321 | }.bind(this), |
michael@0 | 322 | function getAllErrorCb(aErrorMsg) { |
michael@0 | 323 | throw Components.results.NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 324 | } |
michael@0 | 325 | ); |
michael@0 | 326 | }, |
michael@0 | 327 | |
michael@0 | 328 | _getAlarmTime: function _getAlarmTime(aAlarm) { |
michael@0 | 329 | // Avoid casting a Date object to a Date again to |
michael@0 | 330 | // preserve milliseconds. See bug 810973. |
michael@0 | 331 | let alarmTime; |
michael@0 | 332 | if (aAlarm.date instanceof Date) { |
michael@0 | 333 | alarmTime = aAlarm.date.getTime(); |
michael@0 | 334 | } else { |
michael@0 | 335 | alarmTime = (new Date(aAlarm.date)).getTime(); |
michael@0 | 336 | } |
michael@0 | 337 | |
michael@0 | 338 | // For an alarm specified with "ignoreTimezone", it must be fired respect |
michael@0 | 339 | // to the user's timezone. Supposing an alarm was set at 7:00pm at Tokyo, |
michael@0 | 340 | // it must be gone off at 7:00pm respect to Paris' local time when the user |
michael@0 | 341 | // is located at Paris. We can adjust the alarm UTC time by calculating |
michael@0 | 342 | // the difference of the orginal timezone and the current timezone. |
michael@0 | 343 | if (aAlarm.ignoreTimezone) { |
michael@0 | 344 | alarmTime += (this._currentTimezoneOffset - aAlarm.timezoneOffset) * 60000; |
michael@0 | 345 | } |
michael@0 | 346 | return alarmTime; |
michael@0 | 347 | }, |
michael@0 | 348 | |
michael@0 | 349 | _sortAlarmByTimeStamps: function _sortAlarmByTimeStamps(aAlarm1, aAlarm2) { |
michael@0 | 350 | return this._getAlarmTime(aAlarm1) - this._getAlarmTime(aAlarm2); |
michael@0 | 351 | }, |
michael@0 | 352 | |
michael@0 | 353 | _debugCurrentAlarm: function _debugCurrentAlarm() { |
michael@0 | 354 | debug("Current alarm: " + JSON.stringify(this._currentAlarm)); |
michael@0 | 355 | debug("Alarm queue: " + JSON.stringify(this._alarmQueue)); |
michael@0 | 356 | }, |
michael@0 | 357 | |
michael@0 | 358 | /** |
michael@0 | 359 | * |
michael@0 | 360 | * Add a new alarm. This will set the RTC to fire at the selected date and |
michael@0 | 361 | * notify the caller. Notifications are delivered via System Messages if the |
michael@0 | 362 | * alarm is added on behalf of a app. Otherwise aAlarmFiredCb is called. |
michael@0 | 363 | * |
michael@0 | 364 | * @param object aNewAlarm |
michael@0 | 365 | * Should contain the following literal properties: |
michael@0 | 366 | * - |date| date: when the alarm should timeout. |
michael@0 | 367 | * - |ignoreTimezone| boolean: See [1] for the details. |
michael@0 | 368 | * - |manifestURL| string: Manifest of app on whose behalf the alarm |
michael@0 | 369 | * is added. |
michael@0 | 370 | * - |pageURL| string: The page in the app that receives the system |
michael@0 | 371 | * message. |
michael@0 | 372 | * - |data| object [optional]: Data that can be stored in DB. |
michael@0 | 373 | * @param function aAlarmFiredCb |
michael@0 | 374 | * Callback function invoked when the alarm is fired. |
michael@0 | 375 | * It receives a single argument, the alarm object. |
michael@0 | 376 | * May be null. |
michael@0 | 377 | * @param function aSuccessCb |
michael@0 | 378 | * Callback function to receive an alarm ID (number). |
michael@0 | 379 | * @param function aErrorCb |
michael@0 | 380 | * Callback function to receive an error message (string). |
michael@0 | 381 | * @returns void |
michael@0 | 382 | * |
michael@0 | 383 | * Notes: |
michael@0 | 384 | * [1] https://wiki.mozilla.org/WebAPI/AlarmAPI#Proposed_API |
michael@0 | 385 | */ |
michael@0 | 386 | |
michael@0 | 387 | add: function(aNewAlarm, aAlarmFiredCb, aSuccessCb, aErrorCb) { |
michael@0 | 388 | debug("add(" + aNewAlarm.date + ")"); |
michael@0 | 389 | |
michael@0 | 390 | aSuccessCb = aSuccessCb || function() {}; |
michael@0 | 391 | aErrorCb = aErrorCb || function() {}; |
michael@0 | 392 | |
michael@0 | 393 | if (!aNewAlarm) { |
michael@0 | 394 | aErrorCb("alarm is null"); |
michael@0 | 395 | return; |
michael@0 | 396 | } |
michael@0 | 397 | |
michael@0 | 398 | if (!aNewAlarm.date) { |
michael@0 | 399 | aErrorCb("alarm.date is null"); |
michael@0 | 400 | return; |
michael@0 | 401 | } |
michael@0 | 402 | |
michael@0 | 403 | aNewAlarm['timezoneOffset'] = this._currentTimezoneOffset; |
michael@0 | 404 | |
michael@0 | 405 | this._db.add( |
michael@0 | 406 | aNewAlarm, |
michael@0 | 407 | function addSuccessCb(aNewId) { |
michael@0 | 408 | debug("Callback after adding alarm in database."); |
michael@0 | 409 | |
michael@0 | 410 | aNewAlarm['id'] = aNewId; |
michael@0 | 411 | |
michael@0 | 412 | // Now that the alarm has been added to the database, we can tack on |
michael@0 | 413 | // the non-serializable callback to the in-memory object. |
michael@0 | 414 | aNewAlarm['alarmFiredCb'] = aAlarmFiredCb; |
michael@0 | 415 | |
michael@0 | 416 | // If there is no alarm being set in system, set the new alarm. |
michael@0 | 417 | if (this._currentAlarm == null) { |
michael@0 | 418 | this._currentAlarm = aNewAlarm; |
michael@0 | 419 | this._debugCurrentAlarm(); |
michael@0 | 420 | aSuccessCb(aNewId); |
michael@0 | 421 | return; |
michael@0 | 422 | } |
michael@0 | 423 | |
michael@0 | 424 | // If the new alarm is earlier than the current alarm, swap them and |
michael@0 | 425 | // push the previous alarm back to queue. |
michael@0 | 426 | let alarmQueue = this._alarmQueue; |
michael@0 | 427 | let aNewAlarmTime = this._getAlarmTime(aNewAlarm); |
michael@0 | 428 | let currentAlarmTime = this._getAlarmTime(this._currentAlarm); |
michael@0 | 429 | if (aNewAlarmTime < currentAlarmTime) { |
michael@0 | 430 | alarmQueue.unshift(this._currentAlarm); |
michael@0 | 431 | this._currentAlarm = aNewAlarm; |
michael@0 | 432 | this._debugCurrentAlarm(); |
michael@0 | 433 | aSuccessCb(aNewId); |
michael@0 | 434 | return; |
michael@0 | 435 | } |
michael@0 | 436 | |
michael@0 | 437 | // Push the new alarm in the queue. |
michael@0 | 438 | alarmQueue.push(aNewAlarm); |
michael@0 | 439 | alarmQueue.sort(this._sortAlarmByTimeStamps.bind(this)); |
michael@0 | 440 | this._debugCurrentAlarm(); |
michael@0 | 441 | aSuccessCb(aNewId); |
michael@0 | 442 | }.bind(this), |
michael@0 | 443 | function addErrorCb(aErrorMsg) { |
michael@0 | 444 | aErrorCb(aErrorMsg); |
michael@0 | 445 | }.bind(this) |
michael@0 | 446 | ); |
michael@0 | 447 | }, |
michael@0 | 448 | |
michael@0 | 449 | /* |
michael@0 | 450 | * Remove the alarm associated with an ID. |
michael@0 | 451 | * |
michael@0 | 452 | * @param number aAlarmId |
michael@0 | 453 | * The ID of the alarm to be removed. |
michael@0 | 454 | * @param string aManifestURL |
michael@0 | 455 | * Manifest URL for application which added the alarm. (Optional) |
michael@0 | 456 | * @returns void |
michael@0 | 457 | */ |
michael@0 | 458 | remove: function(aAlarmId, aManifestURL) { |
michael@0 | 459 | debug("remove(" + aAlarmId + ", " + aManifestURL + ")"); |
michael@0 | 460 | this._removeAlarmFromDb( |
michael@0 | 461 | aAlarmId, |
michael@0 | 462 | aManifestURL, |
michael@0 | 463 | function removeSuccessCb() { |
michael@0 | 464 | debug("Callback after removing alarm from database."); |
michael@0 | 465 | |
michael@0 | 466 | // If there are no alarms set, nothing to do. |
michael@0 | 467 | if (!this._currentAlarm) { |
michael@0 | 468 | debug("No alarms set."); |
michael@0 | 469 | return; |
michael@0 | 470 | } |
michael@0 | 471 | |
michael@0 | 472 | // Check if the alarm to be removed is in the queue and whether it |
michael@0 | 473 | // belongs to the requesting app. |
michael@0 | 474 | let alarmQueue = this._alarmQueue; |
michael@0 | 475 | if (this._currentAlarm.id != aAlarmId || |
michael@0 | 476 | this._currentAlarm.manifestURL != aManifestURL) { |
michael@0 | 477 | |
michael@0 | 478 | for (let i = 0; i < alarmQueue.length; i++) { |
michael@0 | 479 | if (alarmQueue[i].id == aAlarmId && |
michael@0 | 480 | alarmQueue[i].manifestURL == aManifestURL) { |
michael@0 | 481 | |
michael@0 | 482 | alarmQueue.splice(i, 1); |
michael@0 | 483 | break; |
michael@0 | 484 | } |
michael@0 | 485 | } |
michael@0 | 486 | this._debugCurrentAlarm(); |
michael@0 | 487 | return; |
michael@0 | 488 | } |
michael@0 | 489 | |
michael@0 | 490 | // The alarm to be removed is the current alarm reset the next alarm |
michael@0 | 491 | // from queue if any. |
michael@0 | 492 | if (alarmQueue.length) { |
michael@0 | 493 | this._currentAlarm = alarmQueue.shift(); |
michael@0 | 494 | this._debugCurrentAlarm(); |
michael@0 | 495 | return; |
michael@0 | 496 | } |
michael@0 | 497 | |
michael@0 | 498 | // No alarm waiting to be set in the queue. |
michael@0 | 499 | this._currentAlarm = null; |
michael@0 | 500 | this._debugCurrentAlarm(); |
michael@0 | 501 | }.bind(this) |
michael@0 | 502 | ); |
michael@0 | 503 | }, |
michael@0 | 504 | |
michael@0 | 505 | observe: function(aSubject, aTopic, aData) { |
michael@0 | 506 | switch (aTopic) { |
michael@0 | 507 | case "profile-change-teardown": |
michael@0 | 508 | this.uninit(); |
michael@0 | 509 | break; |
michael@0 | 510 | case "webapps-clear-data": |
michael@0 | 511 | let params = |
michael@0 | 512 | aSubject.QueryInterface(Ci.mozIApplicationClearPrivateDataParams); |
michael@0 | 513 | if (!params) { |
michael@0 | 514 | debug("Error! Fail to remove alarms for an uninstalled app."); |
michael@0 | 515 | return; |
michael@0 | 516 | } |
michael@0 | 517 | |
michael@0 | 518 | // Only remove alarms for apps. |
michael@0 | 519 | if (params.browserOnly) { |
michael@0 | 520 | return; |
michael@0 | 521 | } |
michael@0 | 522 | |
michael@0 | 523 | let manifestURL = appsService.getManifestURLByLocalId(params.appId); |
michael@0 | 524 | if (!manifestURL) { |
michael@0 | 525 | debug("Error! Fail to remove alarms for an uninstalled app."); |
michael@0 | 526 | return; |
michael@0 | 527 | } |
michael@0 | 528 | |
michael@0 | 529 | this._db.getAll( |
michael@0 | 530 | manifestURL, |
michael@0 | 531 | function getAllSuccessCb(aAlarms) { |
michael@0 | 532 | aAlarms.forEach(function removeAlarm(aAlarm) { |
michael@0 | 533 | this.remove(aAlarm.id, manifestURL); |
michael@0 | 534 | }, this); |
michael@0 | 535 | }.bind(this), |
michael@0 | 536 | function getAllErrorCb(aErrorMsg) { |
michael@0 | 537 | throw Components.results.NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 538 | } |
michael@0 | 539 | ); |
michael@0 | 540 | break; |
michael@0 | 541 | } |
michael@0 | 542 | }, |
michael@0 | 543 | |
michael@0 | 544 | uninit: function uninit() { |
michael@0 | 545 | debug("uninit()"); |
michael@0 | 546 | Services.obs.removeObserver(this, "profile-change-teardown"); |
michael@0 | 547 | Services.obs.removeObserver(this, "webapps-clear-data"); |
michael@0 | 548 | |
michael@0 | 549 | this._messages.forEach(function(aMsgName) { |
michael@0 | 550 | ppmm.removeMessageListener(aMsgName, this); |
michael@0 | 551 | }.bind(this)); |
michael@0 | 552 | ppmm = null; |
michael@0 | 553 | |
michael@0 | 554 | if (this._db) { |
michael@0 | 555 | this._db.close(); |
michael@0 | 556 | } |
michael@0 | 557 | this._db = null; |
michael@0 | 558 | |
michael@0 | 559 | this._alarmHalService = null; |
michael@0 | 560 | } |
michael@0 | 561 | } |
michael@0 | 562 | |
michael@0 | 563 | AlarmService.init(); |