mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-25 01:01:52 +00:00
Bug 862322 - Allow AlarmService to be used from Gecko code. r=gene
This commit is contained in:
parent
5a4ff80d3f
commit
541f47ab68
@ -34,17 +34,31 @@ XPCOMUtils.defineLazyGetter(this, "powerManagerService", function() {
|
||||
|
||||
let myGlobal = this;
|
||||
|
||||
/**
|
||||
* AlarmService provides an API to schedule alarms using the device's RTC.
|
||||
*
|
||||
* AlarmService is primarily used by the mozAlarms API (navigator.mozAlarms)
|
||||
* which uses IPC to communicate with the service.
|
||||
*
|
||||
* AlarmService can also be used by Gecko code by importing the module and then
|
||||
* using AlarmService.add() and AlarmService.remove(). Only Gecko code running
|
||||
* in the parent process should do this.
|
||||
*/
|
||||
|
||||
this.AlarmService = {
|
||||
init: function init() {
|
||||
debug("init()");
|
||||
|
||||
this._currentTimezoneOffset = (new Date()).getTimezoneOffset();
|
||||
|
||||
let alarmHalService = this._alarmHalService = Cc["@mozilla.org/alarmHalService;1"].getService(Ci.nsIAlarmHalService);
|
||||
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
|
||||
// Add the messages to be listened to.
|
||||
const messages = ["AlarmsManager:GetAll",
|
||||
"AlarmsManager:Add",
|
||||
"AlarmsManager:Remove"];
|
||||
@ -52,19 +66,20 @@ this.AlarmService = {
|
||||
ppmm.addMessageListener(msgName, this);
|
||||
}.bind(this));
|
||||
|
||||
// set the indexeddb database
|
||||
let idbManager = Cc["@mozilla.org/dom/indexeddb/manager;1"].getService(Ci.nsIIndexedDatabaseManager);
|
||||
// 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
|
||||
// Variable to save alarms waiting to be set.
|
||||
this._alarmQueue = [];
|
||||
|
||||
this._restoreAlarmsFromDb();
|
||||
},
|
||||
|
||||
// getter/setter to access the current alarm set in system
|
||||
// Getter/setter to access the current alarm set in system.
|
||||
_alarm: null,
|
||||
get _currentAlarm() {
|
||||
return this._alarm;
|
||||
@ -102,7 +117,8 @@ this.AlarmService = {
|
||||
this._db.getAll(
|
||||
json.manifestURL,
|
||||
function getAllSuccessCb(aAlarms) {
|
||||
debug("Callback after getting alarms from database: " + JSON.stringify(aAlarms));
|
||||
debug("Callback after getting alarms from database: " +
|
||||
JSON.stringify(aAlarms));
|
||||
this._sendAsyncMessage(mm, "GetAll", true, json.requestId, aAlarms);
|
||||
}.bind(this),
|
||||
function getAllErrorCb(aErrorMsg) {
|
||||
@ -112,105 +128,25 @@ this.AlarmService = {
|
||||
break;
|
||||
|
||||
case "AlarmsManager:Add":
|
||||
// prepare a record for the new alarm to be added
|
||||
// Prepare a record for the new alarm to be added.
|
||||
let newAlarm = {
|
||||
date: json.date,
|
||||
ignoreTimezone: json.ignoreTimezone,
|
||||
timezoneOffset: this._currentTimezoneOffset,
|
||||
date: json.date,
|
||||
ignoreTimezone: json.ignoreTimezone,
|
||||
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)
|
||||
this.add(newAlarm, null,
|
||||
// Receives the alarm ID as the last argument.
|
||||
this._sendAsyncMessage.bind(this, mm, "Add", true, json.requestId),
|
||||
// Receives the error message as the last argument.
|
||||
this._sendAsyncMessage.bind(this, mm, "Add", false, json.requestId)
|
||||
);
|
||||
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)
|
||||
);
|
||||
this.remove(json.id, json.manifestURL);
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -219,7 +155,8 @@ this.AlarmService = {
|
||||
}
|
||||
},
|
||||
|
||||
_sendAsyncMessage: function _sendAsyncMessage(aMessageManager, aMessageName, aSuccess, aRequestId, aData) {
|
||||
_sendAsyncMessage: function _sendAsyncMessage(aMessageManager, aMessageName,
|
||||
aSuccess, aRequestId, aData) {
|
||||
debug("_sendAsyncMessage()");
|
||||
|
||||
if (!aMessageManager) {
|
||||
@ -231,14 +168,14 @@ this.AlarmService = {
|
||||
switch (aMessageName)
|
||||
{
|
||||
case "Add":
|
||||
json = aSuccess ?
|
||||
{ requestId: aRequestId, id: aData } :
|
||||
json = aSuccess ?
|
||||
{ requestId: aRequestId, id: aData } :
|
||||
{ requestId: aRequestId, errorMsg: aData };
|
||||
break;
|
||||
|
||||
case "GetAll":
|
||||
json = aSuccess ?
|
||||
{ requestId: aRequestId, alarms: aData } :
|
||||
json = aSuccess ?
|
||||
{ requestId: aRequestId, alarms: aData } :
|
||||
{ requestId: aRequestId, errorMsg: aData };
|
||||
break;
|
||||
|
||||
@ -247,14 +184,16 @@ this.AlarmService = {
|
||||
break;
|
||||
}
|
||||
|
||||
aMessageManager.sendAsyncMessage("AlarmsManager:" + aMessageName + ":Return:" + (aSuccess ? "OK" : "KO"), json);
|
||||
aMessageManager.sendAsyncMessage("AlarmsManager:" + aMessageName +
|
||||
":Return:" + (aSuccess ? "OK" : "KO"), json);
|
||||
},
|
||||
|
||||
_removeAlarmFromDb: function _removeAlarmFromDb(aId, aManifestURL, aRemoveSuccessCb) {
|
||||
_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 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.");
|
||||
@ -271,28 +210,49 @@ this.AlarmService = {
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a copy of the alarm that does not expose internal fields to
|
||||
* receivers and sticks to the public |respectTimezone| API rather than the
|
||||
* boolean |ignoreTimezone| field.
|
||||
*/
|
||||
_publicAlarm: function _publicAlarm(aAlarm) {
|
||||
let alarm = {
|
||||
"id": aAlarm.id,
|
||||
"date": aAlarm.date,
|
||||
"respectTimezone": aAlarm.ignoreTimezone ?
|
||||
"ignoreTimezone" : "honorTimezone",
|
||||
"data": aAlarm.data
|
||||
};
|
||||
|
||||
return alarm;
|
||||
},
|
||||
|
||||
_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);
|
||||
|
||||
// We don't need to expose everything to the web content.
|
||||
let alarm = { "id": aAlarm.id,
|
||||
"date": aAlarm.date,
|
||||
"respectTimezone": aAlarm.ignoreTimezone ?
|
||||
"ignoreTimezone" : "honorTimezone",
|
||||
"data": aAlarm.data };
|
||||
messenger.sendMessage("alarm", this._publicAlarm(aAlarm),
|
||||
pageURI, manifestURI);
|
||||
},
|
||||
|
||||
messenger.sendMessage("alarm", alarm, pageURI, manifestURI);
|
||||
_notifyAlarmObserver: function _notifyAlarmObserver(aAlarm) {
|
||||
debug("_notifyAlarmObserver()");
|
||||
|
||||
if (aAlarm.manifestURL) {
|
||||
this._fireSystemMessage(aAlarm);
|
||||
} else if (typeof aAlarm.alarmFiredCb === "function") {
|
||||
aAlarm.alarmFiredCb(this._publicAlarm(aAlarm));
|
||||
}
|
||||
},
|
||||
|
||||
_onAlarmFired: function _onAlarmFired() {
|
||||
debug("_onAlarmFired()");
|
||||
|
||||
if (this._currentAlarm) {
|
||||
this._fireSystemMessage(this._currentAlarm);
|
||||
this._removeAlarmFromDb(this._currentAlarm.id, null);
|
||||
this._notifyAlarmObserver(this._currentAlarm);
|
||||
this._currentAlarm = null;
|
||||
}
|
||||
|
||||
@ -302,11 +262,11 @@ this.AlarmService = {
|
||||
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 the next alarm has been expired, directly notify the observer.
|
||||
// it instead of setting it.
|
||||
if (nextAlarmTime <= Date.now()) {
|
||||
this._fireSystemMessage(nextAlarm);
|
||||
this._removeAlarmFromDb(nextAlarm.id, null);
|
||||
this._notifyAlarmObserver(nextAlarm);
|
||||
} else {
|
||||
this._currentAlarm = nextAlarm;
|
||||
break;
|
||||
@ -328,25 +288,26 @@ this.AlarmService = {
|
||||
this._db.getAll(
|
||||
null,
|
||||
function getAllSuccessCb(aAlarms) {
|
||||
debug("Callback after getting alarms from database: " + JSON.stringify(aAlarms));
|
||||
debug("Callback after getting alarms from database: " +
|
||||
JSON.stringify(aAlarms));
|
||||
|
||||
// clear any alarms set or queued in the cache
|
||||
// Clear any alarms set or queued in the cache.
|
||||
let alarmQueue = this._alarmQueue;
|
||||
alarmQueue.length = 0;
|
||||
this._currentAlarm = null;
|
||||
|
||||
// Only restore the alarm that's not yet expired; otherwise,
|
||||
// fire a system message for it and remove it from database.
|
||||
// Only restore the alarm that's not yet expired; otherwise, remove it
|
||||
// from the database and notify the observer.
|
||||
aAlarms.forEach(function addAlarm(aAlarm) {
|
||||
if (this._getAlarmTime(aAlarm) > Date.now()) {
|
||||
alarmQueue.push(aAlarm);
|
||||
} else {
|
||||
this._fireSystemMessage(aAlarm);
|
||||
this._removeAlarmFromDb(aAlarm.id, null);
|
||||
this._notifyAlarmObserver(aAlarm);
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
// set the next alarm from queue
|
||||
// Set the next alarm from queue.
|
||||
if (alarmQueue.length) {
|
||||
alarmQueue.sort(this._sortAlarmByTimeStamps.bind(this));
|
||||
this._currentAlarm = alarmQueue.shift();
|
||||
@ -363,14 +324,11 @@ this.AlarmService = {
|
||||
_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.
|
||||
// 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;
|
||||
|
||||
@ -385,6 +343,154 @@ this.AlarmService = {
|
||||
debug("Current alarm: " + JSON.stringify(this._currentAlarm));
|
||||
debug("Alarm queue: " + JSON.stringify(this._alarmQueue));
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* Add a new alarm. This will set the RTC to fire at the selected date and
|
||||
* notify the caller. Notifications are delivered via System Messages if the
|
||||
* alarm is added on behalf of a app. Otherwise aAlarmFiredCb is called.
|
||||
*
|
||||
* @param object aNewAlarm
|
||||
* Should contain the following literal properties:
|
||||
* - |date| date: when the alarm should timeout.
|
||||
* - |ignoreTimezone| boolean: See [1] for the details.
|
||||
* - |manifestURL| string: Manifest of app on whose behalf the alarm
|
||||
* is added.
|
||||
* - |pageURL| string: The page in the app that receives the system
|
||||
* message.
|
||||
* - |data| object [optional]: Data that can be stored in DB.
|
||||
* @param function aAlarmFiredCb
|
||||
* Callback function invoked when the alarm is fired.
|
||||
* It receives a single argument, the alarm object.
|
||||
* May be null.
|
||||
* @param function aSuccessCb
|
||||
* Callback function to receive an alarm ID (number).
|
||||
* @param function aErrorCb
|
||||
* Callback function to receive an error message (string).
|
||||
* @returns void
|
||||
*
|
||||
* Notes:
|
||||
* [1] https://wiki.mozilla.org/WebAPI/AlarmAPI#Proposed_API
|
||||
*/
|
||||
|
||||
add: function(aNewAlarm, aAlarmFiredCb, aSuccessCb, aErrorCb) {
|
||||
debug("add(" + aNewAlarm.date + ")");
|
||||
|
||||
aSuccessCb = aSuccessCb || function() {};
|
||||
aErrorCb = aErrorCb || function() {};
|
||||
|
||||
if (!aNewAlarm) {
|
||||
aErrorCb("alarm is null");
|
||||
return;
|
||||
}
|
||||
|
||||
aNewAlarm['timezoneOffset'] = this._currentTimezoneOffset;
|
||||
let aNewAlarmTime = this._getAlarmTime(aNewAlarm);
|
||||
if (aNewAlarmTime <= Date.now()) {
|
||||
debug("Adding a alarm that has past time.");
|
||||
this._debugCurrentAlarm();
|
||||
aErrorCb("InvalidStateError");
|
||||
return;
|
||||
}
|
||||
|
||||
this._db.add(
|
||||
aNewAlarm,
|
||||
function addSuccessCb(aNewId) {
|
||||
debug("Callback after adding alarm in database.");
|
||||
|
||||
aNewAlarm['id'] = aNewId;
|
||||
|
||||
// Now that the alarm has been added to the database, we can tack on
|
||||
// the non-serializable callback to the in-memory object.
|
||||
aNewAlarm['alarmFiredCb'] = aAlarmFiredCb;
|
||||
|
||||
// If there is no alarm being set in system, set the new alarm.
|
||||
if (this._currentAlarm == null) {
|
||||
this._currentAlarm = aNewAlarm;
|
||||
this._debugCurrentAlarm();
|
||||
aSuccessCb(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 (aNewAlarmTime < currentAlarmTime) {
|
||||
alarmQueue.unshift(this._currentAlarm);
|
||||
this._currentAlarm = aNewAlarm;
|
||||
this._debugCurrentAlarm();
|
||||
aSuccessCb(aNewId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Push the new alarm in the queue.
|
||||
alarmQueue.push(aNewAlarm);
|
||||
alarmQueue.sort(this._sortAlarmByTimeStamps.bind(this));
|
||||
this._debugCurrentAlarm();
|
||||
aSuccessCb(aNewId);
|
||||
}.bind(this),
|
||||
function addErrorCb(aErrorMsg) {
|
||||
aErrorCb(aErrorMsg);
|
||||
}.bind(this)
|
||||
);
|
||||
},
|
||||
|
||||
/*
|
||||
* Remove the alarm associated with an ID.
|
||||
*
|
||||
* @param number aAlarmId
|
||||
* The ID of the alarm to be removed.
|
||||
* @param string aManifestURL
|
||||
* Manifest URL for application which added the alarm. (Optional)
|
||||
* @returns void
|
||||
*/
|
||||
remove: function(aAlarmId, aManifestURL) {
|
||||
debug("remove(" + aAlarmId + ", " + aManifestURL + ")");
|
||||
this._removeAlarmFromDb(
|
||||
aAlarmId,
|
||||
aManifestURL,
|
||||
function removeSuccessCb() {
|
||||
debug("Callback after removing alarm from database.");
|
||||
|
||||
// If there are no alarms set, nothing to do.
|
||||
if (!this._currentAlarm) {
|
||||
debug("No alarms set.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the alarm to be removed is in the queue and whether it
|
||||
// belongs to the requesting app.
|
||||
let alarmQueue = this._alarmQueue;
|
||||
if (this._currentAlarm.id != aAlarmId ||
|
||||
this._currentAlarm.manifestURL != aManifestURL) {
|
||||
|
||||
for (let i = 0; i < alarmQueue.length; i++) {
|
||||
if (alarmQueue[i].id == aAlarmId &&
|
||||
alarmQueue[i].manifestURL == aManifestURL) {
|
||||
|
||||
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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AlarmService.init();
|
||||
|
Loading…
Reference in New Issue
Block a user