зеркало из https://github.com/mozilla/gecko-dev.git
358 строки
12 KiB
JavaScript
358 строки
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/. */
|
|
|
|
"use strict";
|
|
|
|
/* static functions */
|
|
const DEBUG = false;
|
|
|
|
function debug(aStr) {
|
|
if (DEBUG)
|
|
dump("AlarmService: " + aStr + "\n");
|
|
}
|
|
|
|
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
Cu.import("resource://gre/modules/AlarmDB.jsm");
|
|
|
|
let EXPORTED_SYMBOLS = ["AlarmService"];
|
|
|
|
XPCOMUtils.defineLazyGetter(this, "ppmm", function() {
|
|
return Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager);
|
|
});
|
|
|
|
XPCOMUtils.defineLazyGetter(this, "messenger", function() {
|
|
return Cc["@mozilla.org/system-message-internal;1"].getService(Ci.nsISystemMessagesInternal);
|
|
});
|
|
|
|
let myGlobal = this;
|
|
|
|
let AlarmService = {
|
|
init: function init() {
|
|
debug("init()");
|
|
|
|
this._currentTimezoneOffset = (new Date()).getTimezoneOffset();
|
|
|
|
let alarmHalService = this._alarmHalService = Cc["@mozilla.org/alarmHalService;1"].getService(Ci.nsIAlarmHalService);
|
|
alarmHalService.setAlarmFiredCb(this._onAlarmFired.bind(this));
|
|
alarmHalService.setTimezoneChangedCb(this._onTimezoneChanged.bind(this));
|
|
|
|
// add the messages to be listened
|
|
const messages = ["AlarmsManager:GetAll", "AlarmsManager:Add", "AlarmsManager:Remove"];
|
|
messages.forEach(function addMessage(msgName) {
|
|
ppmm.addMessageListener(msgName, this);
|
|
}.bind(this));
|
|
|
|
// set the indexeddb database
|
|
let idbManager = Cc["@mozilla.org/dom/indexeddb/manager;1"].getService(Ci.nsIIndexedDatabaseManager);
|
|
idbManager.initWindowless(myGlobal);
|
|
this._db = new AlarmDB(myGlobal);
|
|
this._db.init(myGlobal);
|
|
|
|
// variable to save alarms waiting to be set
|
|
this._alarmQueue = [];
|
|
|
|
this._restoreAlarmsFromDb();
|
|
},
|
|
|
|
// getter/setter to access the current alarm set in system
|
|
_alarm: null,
|
|
get _currentAlarm() {
|
|
return this._alarm;
|
|
},
|
|
set _currentAlarm(aAlarm) {
|
|
this._alarm = aAlarm;
|
|
if (!aAlarm)
|
|
return;
|
|
|
|
if (!this._alarmHalService.setAlarm(this._getAlarmTime(aAlarm) / 1000, 0))
|
|
throw Components.results.NS_ERROR_FAILURE;
|
|
},
|
|
|
|
receiveMessage: function receiveMessage(aMessage) {
|
|
debug("receiveMessage(): " + aMessage.name);
|
|
|
|
let mm = aMessage.target.QueryInterface(Ci.nsIFrameMessageManager);
|
|
let json = aMessage.json;
|
|
switch (aMessage.name) {
|
|
case "AlarmsManager:GetAll":
|
|
this._db.getAll(
|
|
json.manifestURL,
|
|
function getAllSuccessCb(aAlarms) {
|
|
debug("Callback after getting alarms from database: " + JSON.stringify(aAlarms));
|
|
this._sendAsyncMessage(mm, "GetAll", true, json.requestId, aAlarms);
|
|
}.bind(this),
|
|
function getAllErrorCb(aErrorMsg) {
|
|
this._sendAsyncMessage(mm, "GetAll", false, json.requestId, aErrorMsg);
|
|
}.bind(this)
|
|
);
|
|
break;
|
|
|
|
case "AlarmsManager:Add":
|
|
// prepare a record for the new alarm to be added
|
|
let newAlarm = {
|
|
date: json.date,
|
|
ignoreTimezone: json.ignoreTimezone,
|
|
timezoneOffset: this._currentTimezoneOffset,
|
|
data: json.data,
|
|
pageURL: json.pageURL,
|
|
manifestURL: json.manifestURL
|
|
};
|
|
|
|
let newAlarmTime = this._getAlarmTime(newAlarm);
|
|
if (newAlarmTime <= Date.now()) {
|
|
debug("Adding a alarm that has past time. Return DOMError.");
|
|
this._debugCurrentAlarm();
|
|
this._sendAsyncMessage(mm, "Add", false, json.requestId, "InvalidStateError");
|
|
break;
|
|
}
|
|
|
|
this._db.add(
|
|
newAlarm,
|
|
function addSuccessCb(aNewId) {
|
|
debug("Callback after adding alarm in database.");
|
|
|
|
newAlarm['id'] = aNewId;
|
|
|
|
// if there is no alarm being set in system, set the new alarm
|
|
if (this._currentAlarm == null) {
|
|
this._currentAlarm = newAlarm;
|
|
this._debugCurrentAlarm();
|
|
this._sendAsyncMessage(mm, "Add", true, json.requestId, aNewId);
|
|
return;
|
|
}
|
|
|
|
// if the new alarm is earlier than the current alarm
|
|
// swap them and push the previous alarm back to queue
|
|
let alarmQueue = this._alarmQueue;
|
|
let currentAlarmTime = this._getAlarmTime(this._currentAlarm);
|
|
if (newAlarmTime < currentAlarmTime) {
|
|
alarmQueue.unshift(this._currentAlarm);
|
|
this._currentAlarm = newAlarm;
|
|
this._debugCurrentAlarm();
|
|
this._sendAsyncMessage(mm, "Add", true, json.requestId, aNewId);
|
|
return;
|
|
}
|
|
|
|
//push the new alarm in the queue
|
|
alarmQueue.push(newAlarm);
|
|
alarmQueue.sort(this._sortAlarmByTimeStamps.bind(this));
|
|
this._debugCurrentAlarm();
|
|
this._sendAsyncMessage(mm, "Add", true, json.requestId, aNewId);
|
|
}.bind(this),
|
|
function addErrorCb(aErrorMsg) {
|
|
this._sendAsyncMessage(mm, "Add", false, json.requestId, aErrorMsg);
|
|
}.bind(this)
|
|
);
|
|
break;
|
|
|
|
case "AlarmsManager:Remove":
|
|
this._removeAlarmFromDb(
|
|
json.id,
|
|
json.manifestURL,
|
|
function removeSuccessCb() {
|
|
debug("Callback after removing alarm from database.");
|
|
|
|
// if there is no alarm being set
|
|
if (!this._currentAlarm) {
|
|
this._debugCurrentAlarm();
|
|
return;
|
|
}
|
|
|
|
// check if the alarm to be removed is in the queue
|
|
// by ID and whether it belongs to the requesting app
|
|
let alarmQueue = this._alarmQueue;
|
|
if (this._currentAlarm.id != json.id ||
|
|
this._currentAlarm.manifestURL != json.manifestURL) {
|
|
for (let i = 0; i < alarmQueue.length; i++) {
|
|
if (alarmQueue[i].id == json.id &&
|
|
alarmQueue[i].manifestURL == json.manifestURL) {
|
|
alarmQueue.splice(i, 1);
|
|
break;
|
|
}
|
|
}
|
|
this._debugCurrentAlarm();
|
|
return;
|
|
}
|
|
|
|
// the alarm to be removed is the current alarm
|
|
// reset the next alarm from queue if any
|
|
if (alarmQueue.length) {
|
|
this._currentAlarm = alarmQueue.shift();
|
|
this._debugCurrentAlarm();
|
|
return;
|
|
}
|
|
|
|
// no alarm waiting to be set in the queue
|
|
this._currentAlarm = null;
|
|
this._debugCurrentAlarm();
|
|
}.bind(this)
|
|
);
|
|
break;
|
|
|
|
default:
|
|
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
|
break;
|
|
}
|
|
},
|
|
|
|
_sendAsyncMessage: function _sendAsyncMessage(aMessageManager, aMessageName, aSuccess, aRequestId, aData) {
|
|
debug("_sendAsyncMessage()");
|
|
|
|
if (!aMessageManager) {
|
|
debug("Invalid message manager: null");
|
|
throw Components.results.NS_ERROR_FAILURE;
|
|
}
|
|
|
|
let json = null;
|
|
switch (aMessageName)
|
|
{
|
|
case "Add":
|
|
json = aSuccess ?
|
|
{ requestId: aRequestId, id: aData } :
|
|
{ requestId: aRequestId, errorMsg: aData };
|
|
break;
|
|
|
|
case "GetAll":
|
|
json = aSuccess ?
|
|
{ requestId: aRequestId, alarms: aData } :
|
|
{ requestId: aRequestId, errorMsg: aData };
|
|
break;
|
|
|
|
default:
|
|
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
|
break;
|
|
}
|
|
|
|
aMessageManager.sendAsyncMessage("AlarmsManager:" + aMessageName + ":Return:" + (aSuccess ? "OK" : "KO"), json);
|
|
},
|
|
|
|
_removeAlarmFromDb: function _removeAlarmFromDb(aId, aManifestURL, aRemoveSuccessCb) {
|
|
debug("_removeAlarmFromDb()");
|
|
|
|
// If the aRemoveSuccessCb is undefined or null, set a
|
|
// dummy callback for it which is needed for _db.remove()
|
|
if (!aRemoveSuccessCb) {
|
|
aRemoveSuccessCb = function removeSuccessCb() {
|
|
debug("Remove alarm from DB successfully.");
|
|
};
|
|
}
|
|
|
|
this._db.remove(
|
|
aId,
|
|
aManifestURL,
|
|
aRemoveSuccessCb,
|
|
function removeErrorCb(aErrorMsg) {
|
|
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
);
|
|
},
|
|
|
|
_fireSystemMessage: function _fireSystemMessage(aAlarm) {
|
|
debug("Fire system message: " + JSON.stringify(aAlarm));
|
|
let manifestURI = Services.io.newURI(aAlarm.manifestURL, null, null);
|
|
let pageURI = Services.io.newURI(aAlarm.pageURL, null, null);
|
|
messenger.sendMessage("alarm", aAlarm, pageURI, manifestURI);
|
|
},
|
|
|
|
_onAlarmFired: function _onAlarmFired() {
|
|
debug("_onAlarmFired()");
|
|
|
|
if (this._currentAlarm) {
|
|
this._fireSystemMessage(this._currentAlarm);
|
|
this._removeAlarmFromDb(this._currentAlarm.id, null);
|
|
this._currentAlarm = null;
|
|
}
|
|
|
|
// Reset the next alarm from the queue.
|
|
let alarmQueue = this._alarmQueue;
|
|
while (alarmQueue.length > 0) {
|
|
let nextAlarm = alarmQueue.shift();
|
|
let nextAlarmTime = this._getAlarmTime(nextAlarm);
|
|
|
|
// If the next alarm has been expired, directly
|
|
// fire system message for it instead of setting it.
|
|
if (nextAlarmTime <= Date.now()) {
|
|
this._fireSystemMessage(nextAlarm);
|
|
this._removeAlarmFromDb(nextAlarm.id, null);
|
|
} else {
|
|
this._currentAlarm = nextAlarm;
|
|
break;
|
|
}
|
|
}
|
|
this._debugCurrentAlarm();
|
|
},
|
|
|
|
_onTimezoneChanged: function _onTimezoneChanged(aTimezoneOffset) {
|
|
debug("_onTimezoneChanged()");
|
|
|
|
this._currentTimezoneOffset = aTimezoneOffset;
|
|
this._restoreAlarmsFromDb();
|
|
},
|
|
|
|
_restoreAlarmsFromDb: function _restoreAlarmsFromDb() {
|
|
debug("_restoreAlarmsFromDb()");
|
|
|
|
this._db.getAll(
|
|
null,
|
|
function getAllSuccessCb(aAlarms) {
|
|
debug("Callback after getting alarms from database: " + JSON.stringify(aAlarms));
|
|
|
|
// clear any alarms set or queued in the cache
|
|
let alarmQueue = this._alarmQueue;
|
|
alarmQueue.length = 0;
|
|
this._currentAlarm = null;
|
|
|
|
// only add the alarm that is valid
|
|
let nowTime = Date.now();
|
|
aAlarms.forEach(function addAlarm(aAlarm) {
|
|
if (this._getAlarmTime(aAlarm) > nowTime)
|
|
alarmQueue.push(aAlarm);
|
|
}.bind(this));
|
|
|
|
// set the next alarm from queue
|
|
if (alarmQueue.length) {
|
|
alarmQueue.sort(this._sortAlarmByTimeStamps.bind(this));
|
|
this._currentAlarm = alarmQueue.shift();
|
|
}
|
|
|
|
this._debugCurrentAlarm();
|
|
}.bind(this),
|
|
function getAllErrorCb(aErrorMsg) {
|
|
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
);
|
|
},
|
|
|
|
_getAlarmTime: function _getAlarmTime(aAlarm) {
|
|
let alarmTime = (new Date(aAlarm.date)).getTime();
|
|
|
|
// For an alarm specified with "ignoreTimezone",
|
|
// it must be fired respect to the user's timezone.
|
|
// Supposing an alarm was set at 7:00pm at Tokyo,
|
|
// it must be gone off at 7:00pm respect to Paris'
|
|
// local time when the user is located at Paris.
|
|
// We can adjust the alarm UTC time by calculating
|
|
// the difference of the orginal timezone and the
|
|
// current timezone.
|
|
if (aAlarm.ignoreTimezone)
|
|
alarmTime += (this._currentTimezoneOffset - aAlarm.timezoneOffset) * 60000;
|
|
|
|
return alarmTime;
|
|
},
|
|
|
|
_sortAlarmByTimeStamps: function _sortAlarmByTimeStamps(aAlarm1, aAlarm2) {
|
|
return this._getAlarmTime(aAlarm1) - this._getAlarmTime(aAlarm2);
|
|
},
|
|
|
|
_debugCurrentAlarm: function _debugCurrentAlarm() {
|
|
debug("Current alarm: " + JSON.stringify(this._currentAlarm));
|
|
debug("Alarm queue: " + JSON.stringify(this._alarmQueue));
|
|
},
|
|
}
|
|
|
|
AlarmService.init();
|