mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-12 14:37:50 +00:00
335 lines
10 KiB
JavaScript
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();
|
|
|