gecko-dev/dom/resourcestats/ResourceStatsService.jsm

335 lines
10 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";
this.EXPORTED_SYMBOLS = ["ResourceStatsService"];
const DEBUG = false;
function debug(s) { dump("-*- ResourceStatsService: " + s + "\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");
// Load ResourceStatsDB.
Cu.import("resource://gre/modules/ResourceStatsDB.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gIDBManager",
"@mozilla.org/dom/indexeddb/manager;1",
"nsIIndexedDatabaseManager");
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
"@mozilla.org/parentprocessmessagemanager;1",
"nsIMessageListenerManager");
XPCOMUtils.defineLazyServiceGetter(this, "appsService",
"@mozilla.org/AppsService;1",
"nsIAppsService");
this.ResourceStatsService = {
init: function() {
if (DEBUG) {
debug("Service started");
}
// Set notification to observe.
Services.obs.addObserver(this, "xpcom-shutdown", false);
// Add message listener.
this.messages = ["ResourceStats:GetStats",
"ResourceStats:ClearStats",
"ResourceStats:ClearAllStats",
"ResourceStats:AddAlarm",
"ResourceStats:GetAlarms",
"ResourceStats:RemoveAlarm",
"ResourceStats:RemoveAllAlarms",
"ResourceStats:GetComponents",
"ResourceStats:SampleRate",
"ResourceStats:MaxStorageAge"];
this.messages.forEach(function(aMessageName){
ppmm.addMessageListener(aMessageName, this);
}, this);
// Create indexedDB.
this._db = new ResourceStatsDB();
},
receiveMessage: function(aMessage) {
if (DEBUG) {
debug("receiveMessage(): " + aMessage.name);
}
let mm = aMessage.target;
let data = aMessage.data;
if (DEBUG) {
debug("received aMessage.data = " + JSON.stringify(data));
}
// To prevent the hacked child process from sending commands to parent,
// we need to check its permission and manifest URL.
if (!aMessage.target.assertPermission("resourcestats-manage")) {
return;
}
if (!aMessage.target.assertContainApp(data.manifestURL)) {
if (DEBUG) {
debug("Got msg from a child process containing illegal manifestURL.");
}
return;
}
switch (aMessage.name) {
case "ResourceStats:GetStats":
this.getStats(mm, data);
break;
case "ResourceStats:ClearStats":
this.clearStats(mm, data);
break;
case "ResourceStats:ClearAllStats":
this.clearAllStats(mm, data);
break;
case "ResourceStats:AddAlarm":
this.addAlarm(mm, data);
break;
case "ResourceStats:GetAlarms":
this.getAlarms(mm, data);
break;
case "ResourceStats:RemoveAlarm":
this.removeAlarm(mm, data);
break;
case "ResourceStats:RemoveAllAlarms":
this.removeAllAlarms(mm, data);
break;
case "ResourceStats:GetComponents":
this.getComponents(mm, data);
break;
case "ResourceStats:SampleRate":
// This message is sync.
return this._db.sampleRate;
case "ResourceStats:MaxStorageAge":
// This message is sync.
return this._db.maxStorageAge;
}
},
observe: function(aSubject, aTopic, aData) {
switch (aTopic) {
case "xpcom-shutdown":
if (DEBUG) {
debug("Service shutdown " + aData);
}
this.messages.forEach(function(aMessageName) {
ppmm.removeMessageListener(aMessageName, this);
}, this);
Services.obs.removeObserver(this, "xpcom-shutdown");
break;
default:
return;
}
},
// Closure generates callback function for DB request.
_createDbCallback: function(aMm, aId, aMessage) {
let resolveMsg = aMessage + ":Resolved";
let rejectMsg = aMessage + ":Rejected";
return function(aError, aResult) {
if (aError) {
aMm.sendAsyncMessage(rejectMsg, { resolverId: aId, reason: aError });
return;
}
aMm.sendAsyncMessage(resolveMsg, { resolverId: aId, value: aResult });
};
},
getStats: function(aMm, aData) {
if (DEBUG) {
debug("getStats(): " + JSON.stringify(aData));
}
// Note: we validate the manifestURL in _db.getStats().
let manifestURL = aData.statsOptions.manifestURL || "";
let serviceType = aData.statsOptions.serviceType || "";
let component = aData.statsOptions.component || "";
// Execute DB operation.
let onStatsGot = this._createDbCallback(aMm, aData.resolverId,
"ResourceStats:GetStats");
this._db.getStats(aData.type, manifestURL, serviceType, component,
aData.start, aData.end, onStatsGot);
},
clearStats: function(aMm, aData) {
if (DEBUG) {
debug("clearStats(): " + JSON.stringify(aData));
}
// Get appId and check whether manifestURL is a valid app.
let appId = 0;
let manifestURL = aData.statsOptions.manifestURL || "";
if (manifestURL) {
appId = appsService.getAppLocalIdByManifestURL(manifestURL);
if (!appId) {
aMm.sendAsyncMessage("ResourceStats:GetStats:Rejected", {
resolverId: aData.resolverId,
reason: "Invalid manifestURL"
});
return;
}
}
let serviceType = aData.statsOptions.serviceType || "";
let component = aData.statsOptions.component || "";
// Execute DB operation.
let onStatsCleared = this._createDbCallback(aMm, aData.resolverId,
"ResourceStats:ClearStats");
this._db.clearStats(aData.type, appId, serviceType, component,
aData.start, aData.end, onStatsCleared);
},
clearAllStats: function(aMm, aData) {
if (DEBUG) {
debug("clearAllStats(): " + JSON.stringify(aData));
}
// Execute DB operation.
let onAllStatsCleared = this._createDbCallback(aMm, aData.resolverId,
"ResourceStats:ClearAllStats");
this._db.clearAllStats(aData.type, onAllStatsCleared);
},
addAlarm: function(aMm, aData) {
if (DEBUG) {
debug("addAlarm(): " + JSON.stringify(aData));
}
// Get appId and check whether manifestURL is a valid app.
let manifestURL = aData.statsOptions.manifestURL;
if (manifestURL) {
let appId = appsService.getAppLocalIdByManifestURL(manifestURL);
if (!appId) {
aMm.sendAsyncMessage("ResourceStats:GetStats:Rejected", {
resolverId: aData.resolverId,
reason: "Invalid manifestURL"
});
return;
}
}
// Create an alarm object.
let newAlarm = {
type: aData.type,
component: aData.statsOptions.component || "",
serviceType: aData.statsOptions.serviceType || "",
manifestURL: manifestURL || "",
threshold: aData.threshold,
startTime: aData.startTime,
data: aData.data
};
// Execute DB operation.
let onAlarmAdded = this._createDbCallback(aMm, aData.resolverId,
"ResourceStats:AddAlarm");
this._db.addAlarm(newAlarm, onAlarmAdded);
},
getAlarms: function(aMm, aData) {
if (DEBUG) {
debug("getAlarms(): " + JSON.stringify(aData));
}
let options = null;
let statsOptions = aData.statsOptions;
// If all keys in statsOptions are set to null, treat this call as quering
// all alarms; otherwise, resolve the statsOptions and perform DB query
// according to the resolved result.
if (statsOptions.manifestURL || statsOptions.serviceType ||
statsOptions.component) {
// Validate manifestURL.
let manifestURL = statsOptions.manifestURL || "";
if (manifestURL) {
let appId = appsService.getAppLocalIdByManifestURL(manifestURL);
if (!appId) {
aMm.sendAsyncMessage("ResourceStats:GetStats:Rejected", {
resolverId: aData.resolverId,
reason: "Invalid manifestURL"
});
return;
}
}
options = {
manifestURL: manifestURL,
serviceType: statsOptions.serviceType || "",
component: statsOptions.component || ""
};
}
// Execute DB operation.
let onAlarmsGot = this._createDbCallback(aMm, aData.resolverId,
"ResourceStats:GetAlarms");
this._db.getAlarms(aData.type, options, onAlarmsGot);
},
removeAlarm: function(aMm, aData) {
if (DEBUG) {
debug("removeAlarm(): " + JSON.stringify(aData));
}
// Execute DB operation.
let onAlarmRemoved = function(aError, aResult) {
if (aError) {
aMm.sendAsyncMessage("ResourceStats:RemoveAlarm:Rejected",
{ resolverId: aData.resolverId, reason: aError });
}
if (!aResult) {
aMm.sendAsyncMessage("ResourceStats:RemoveAlarm:Rejected",
{ resolverId: aData.resolverId,
reason: "alarm not existed" });
}
aMm.sendAsyncMessage("ResourceStats:RemoveAlarm:Resolved",
{ resolverId: aData.resolverId, value: aResult });
};
this._db.removeAlarm(aData.type, aData.alarmId, onAlarmRemoved);
},
removeAllAlarms: function(aMm, aData) {
if (DEBUG) {
debug("removeAllAlarms(): " + JSON.stringify(aData));
}
// Execute DB operation.
let onAllAlarmsRemoved = this._createDbCallback(aMm, aData.resolverId,
"ResourceStats:RemoveAllAlarms");
this._db.removeAllAlarms(aData.type, onAllAlarmsRemoved);
},
getComponents: function(aMm, aData) {
if (DEBUG) {
debug("getComponents(): " + JSON.stringify(aData));
}
// Execute DB operation.
let onComponentsGot = this._createDbCallback(aMm, aData.resolverId,
"ResourceStats:GetComponents");
this._db.getComponents(aData.type, onComponentsGot);
},
};
this.ResourceStatsService.init();