fune/testing/talos/talos/talos-powers/components/TalosPowersService.js

317 lines
9.8 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/. */
const { interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm");
const FRAME_SCRIPT = "chrome://talos-powers/content/talos-powers-content.js";
function TalosPowersService() {
this.wrappedJSObject = this;
}
TalosPowersService.prototype = {
classDescription: "Talos Powers",
classID: Components.ID("{f5d53443-d58d-4a2f-8df0-98525d4f91ad}"),
contractID: "@mozilla.org/talos/talos-powers-service;1",
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
observe(subject, topic, data) {
switch (topic) {
case "profile-after-change":
// Note that this observation is registered in the chrome.manifest
// for this add-on.
this.init();
break;
case "xpcom-shutdown":
this.uninit();
break;
}
},
init() {
Services.mm.loadFrameScript(FRAME_SCRIPT, true);
Services.mm.addMessageListener("Talos:ForceQuit", this);
Services.mm.addMessageListener("TalosContentProfiler:Command", this);
Services.mm.addMessageListener("TalosPowersContent:ForceCCAndGC", this);
Services.mm.addMessageListener("TalosPowersContent:GetStartupInfo", this);
Services.mm.addMessageListener("TalosPowers:ParentExec:QueryMsg", this);
Services.obs.addObserver(this, "xpcom-shutdown");
},
uninit() {
Services.obs.removeObserver(this, "xpcom-shutdown");
},
receiveMessage(message) {
switch (message.name) {
case "Talos:ForceQuit": {
this.forceQuit(message.data);
break;
}
case "TalosContentProfiler:Command": {
this.receiveProfileCommand(message);
break;
}
case "TalosPowersContent:ForceCCAndGC": {
Cu.forceGC();
Cu.forceCC();
Cu.forceShrinkingGC();
break;
}
case "TalosPowersContent:GetStartupInfo": {
this.receiveGetStartupInfo(message);
break;
}
case "TalosPowers:ParentExec:QueryMsg": {
this.RecieveParentExecCommand(message);
break;
}
}
},
/**
* Enable the Gecko Profiler with some settings and then pause immediately.
*
* @param data (object)
* A JavaScript object with the following properties:
*
* entries (int):
* The sampling buffer size in bytes.
*
* interval (int):
* The sampling interval in milliseconds.
*
* threadsArray (array of strings):
* The thread names to sample.
*/
profilerBegin(data) {
Services.profiler
.StartProfiler(data.entries, data.interval,
["js", "leaf", "stackwalk", "threads"], 4,
data.threadsArray, data.threadsArray.length);
Services.profiler.PauseSampling();
},
/**
* Assuming the Profiler is running, dumps the Profile from all sampled
* processes and threads to the disk. The Profiler will be stopped once
* the profiles have been dumped. This method returns a Promise that
* will resolve once this has occurred.
*
* @param profileFile (string)
* The name of the file to write to.
*
* @returns Promise
*/
profilerFinish(profileFile) {
return new Promise((resolve, reject) => {
Services.profiler.getProfileDataAsync().then((profile) => {
let encoder = new TextEncoder();
let array = encoder.encode(JSON.stringify(profile));
OS.File.writeAtomic(profileFile, array, {
tmpPath: profileFile + ".tmp",
}).then(() => {
Services.profiler.StopProfiler();
resolve();
});
}, (error) => {
Cu.reportError("Failed to gather profile: " + error);
// FIXME: We should probably send a message down to the
// child which causes it to reject the waiting Promise.
reject();
});
});
},
/**
* Pauses the Profiler, optionally setting a parent process marker before
* doing so.
*
* @param marker (string, optional)
* A marker to set before pausing.
*/
profilerPause(marker = null) {
if (marker) {
Services.profiler.AddMarker(marker);
}
Services.profiler.PauseSampling();
},
/**
* Resumes a pausedProfiler, optionally setting a parent process marker
* after doing so.
*
* @param marker (string, optional)
* A marker to set after resuming.
*/
profilerResume(marker = null) {
Services.profiler.ResumeSampling();
if (marker) {
Services.profiler.AddMarker(marker);
}
},
/**
* Adds a marker to the Profile in the parent process.
*/
profilerMarker(marker) {
Services.profiler.AddMarker(marker);
},
receiveProfileCommand(message) {
const ACK_NAME = "TalosContentProfiler:Response";
let mm = message.target.messageManager;
let name = message.data.name;
let data = message.data.data;
switch (name) {
case "Profiler:Begin": {
this.profilerBegin(data);
// profilerBegin will cause the parent to send an async message to any
// child processes to start profiling. Because messages are serviced
// in order, we know that by the time that the child services the
// ACK message, that the profiler has started in its process.
mm.sendAsyncMessage(ACK_NAME, { name });
break;
}
case "Profiler:Finish": {
// The test is done. Dump the profile.
this.profilerFinish(data.profileFile).then(() => {
mm.sendAsyncMessage(ACK_NAME, { name });
});
break;
}
case "Profiler:Pause": {
this.profilerPause(data.marker);
mm.sendAsyncMessage(ACK_NAME, { name });
break;
}
case "Profiler:Resume": {
this.profilerResume(data.marker);
mm.sendAsyncMessage(ACK_NAME, { name });
break;
}
case "Profiler:Marker": {
this.profilerMarker(data.marker);
mm.sendAsyncMessage(ACK_NAME, { name });
}
}
},
forceQuit(messageData) {
if (messageData && messageData.waitForSafeBrowsing) {
let SafeBrowsing = Cu.import("resource://gre/modules/SafeBrowsing.jsm", {}).SafeBrowsing;
let whenDone = () => {
this.forceQuit();
};
SafeBrowsing.addMozEntriesFinishedPromise.then(whenDone, whenDone);
// Speed things up in case nobody else called this:
SafeBrowsing.init();
return;
}
let enumerator = Services.wm.getEnumerator(null);
while (enumerator.hasMoreElements()) {
let domWindow = enumerator.getNext();
domWindow.close();
}
try {
Services.startup.quit(Services.startup.eForceQuit);
} catch (e) {
dump("Force Quit failed: " + e);
}
},
receiveGetStartupInfo(message) {
let mm = message.target.messageManager;
let startupInfo = Services.startup.getStartupInfo();
if (!startupInfo["firstPaint"]) {
// It's possible that we were called early enough that
// the firstPaint measurement hasn't been set yet. In
// that case, we set up an observer for the next time
// a window is painted and re-retrieve the startup info.
let obs = function(subject, topic) {
Services.obs.removeObserver(this, topic);
startupInfo = Services.startup.getStartupInfo();
mm.sendAsyncMessage("TalosPowersContent:GetStartupInfo:Result",
startupInfo);
};
Services.obs.addObserver(obs, "widget-first-paint");
} else {
mm.sendAsyncMessage("TalosPowersContent:GetStartupInfo:Result",
startupInfo);
}
},
// These services are exposed to local unprivileged content.
// Each service is a function which accepts an argument, a callback for sending
// the reply (possibly async), and the parent window as a utility.
// arg/reply semantice are service-specific.
// To add a service: add a method at ParentExecServices here, then at the content:
// <script src="chrome://talos-powers-content/content/TalosPowersContent.js"></script>
// and then e.g. TalosPowersParent.exec("sampleParentService", myArg, myCallback)
// Sample service:
/*
// arg: anything. return: sample reply
sampleParentService: function(arg, callback, win) {
win.setTimeout(function() {
callback("sample reply for: " + arg);
}, 500);
},
*/
ParentExecServices: {
// arg: ignored. return: handle (number) for use with stopFrameTimeRecording
startFrameTimeRecording(arg, callback, win) {
var rv = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.startFrameTimeRecording();
callback(rv);
},
// arg: handle from startFrameTimeRecording. return: array with composition intervals
stopFrameTimeRecording(arg, callback, win) {
var rv = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.stopFrameTimeRecording(arg);
callback(rv);
},
},
RecieveParentExecCommand(msg) {
function sendResult(result) {
let mm = msg.target.messageManager;
mm.sendAsyncMessage("TalosPowers:ParentExec:ReplyMsg", {
id: msg.data.id,
result
});
}
let command = msg.data.command;
if (!this.ParentExecServices.hasOwnProperty(command.name))
throw new Error("TalosPowers:ParentExec: Invalid service '" + command.name + "'");
this.ParentExecServices[command.name](command.data, sendResult, msg.target.ownerGlobal);
},
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TalosPowersService]);