// Always log packets when running tests. runxpcshelltests.py will throw // the output away anyway, unless you give it the --verbose flag.
Services.prefs.setBoolPref("devtools.debugger.log", false); // Enable remote debugging for the relevant tests.
Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
var { loadSubScript, loadSubScriptWithOptions } = Services.scriptloader;
/** * The logic here must resemble the logic of --start-debugger-server as closely * as possible. DevToolsStartup.sys.mjs uses a distinct loader that results in * the existence of two isolated module namespaces. In practice, this can cause * bugs such as bug 1837185.
*/ function getDistinctDevToolsServer() { const {
useDistinctSystemPrincipalLoader,
releaseDistinctSystemPrincipalLoader,
} = ChromeUtils.importESModule( "resource://devtools/shared/loader/DistinctSystemPrincipalLoader.sys.mjs",
{ global: "shared" }
); const requester = {}; const distinctLoader = useDistinctSystemPrincipalLoader(requester);
registerCleanupFunction(() => {
releaseDistinctSystemPrincipalLoader(requester);
});
/** * Initializes any test that needs to work with add-ons. * * Should be called once per test script that needs to use AddonTestUtils (and * not once per test task!).
*/
async function startupAddonsManager() { // Create a directory for extensions. const profileDir = do_get_profile().clone();
profileDir.append("extensions");
// These xpcshell tests use mocked actors (xpcshell-test/testactors) // which still don't support watcher actor. // Because of that we still can't enable server side targets and target swiching.
tabDescriptor.disableTargetSwitching();
/** * Create a MemoryFront for a fake test tab.
*/
async function createTabMemoryFront() { const target = await createTargetForFakeTab("test_memory");
// MemoryFront requires the HeadSnapshotActor actor to be available // as a global actor. This isn't registered by startTestDevToolsServer which // only register the target actors and not the browser ones.
DevToolsServer.registerActors({ browser: true });
// On XPCShell, the target isn't for a local tab and so target.destroy // won't close the client. So do it so here. It will automatically destroy the target.
await target.client.close();
});
return { target, memoryFront };
}
/** * Same as createTabMemoryFront but attaches the MemoryFront to the MemoryActor * scoped to the full runtime rather than to a tab.
*/
async function createMainProcessMemoryFront() { const target = await createTargetForMainProcess();
registerCleanupFunction(async () => {
await memoryFront.detach(); // For XPCShell, the main process target actor is ContentProcessTargetActor // which doesn't expose any `detach` method. So that the target actor isn't // destroyed when calling target.destroy. // Close the client to cleanup everything.
await target.client.close();
});
return { client: target.client, memoryFront };
}
function createLongStringFront(conn, form) { // CAUTION -- do not replicate in the codebase. Instead, use marshalling // This code is simulating how the LongStringFront would be created by protocol.js // We should not use it like this in the codebase, this is done only for testing // purposes until we can return a proper LongStringFront from the server. const front = new LongStringFront(conn, form);
front.actorID = form.actor;
front.manage(front); return front;
}
function createTestGlobal(name, options) { const principal = Cc["@mozilla.org/systemprincipal;1"].createInstance(
Ci.nsIPrincipal
); // NOTE: The Sandbox constructor behaves differently based on the argument // length. const sandbox = options
? Cu.Sandbox(principal, options)
: Cu.Sandbox(principal);
sandbox.__name = name; // Expose a few mocks to better represent a Window object. // These attributes will be used by DOCUMENT_EVENT resource listener.
sandbox.performance = { timing: {} };
sandbox.document = {
readyState: "complete",
defaultView: sandbox,
}; return sandbox;
}
function connect(client) {
dump("Connecting client.\n"); return client.connect();
}
function close(client) {
dump("Closing client.\n"); return client.close();
}
function listTabs(client) {
dump("Listing tabs.\n"); return client.mainRoot.listTabs();
}
function findTab(tabs, title) {
dump("Finding tab with title '" + title + "'.\n"); for (const tab of tabs) { if (tab.title === title) { return tab;
}
} returnnull;
}
function waitForNewSource(threadFront, url) {
dump("Waiting for new source with url '" + url + "'.\n"); return waitForEvent(threadFront, "newSource", function (packet) { return packet.source.url === url;
});
}
function resume(threadFront) {
dump("Resuming thread.\n"); return threadFront.resume();
}
function setBreakpoint(threadFront, location) {
dump("Setting breakpoint.\n"); return threadFront.setBreakpoint(location, {});
}
function getPrototypeAndProperties(objClient) {
dump("getting prototype and properties.\n");
return objClient.getPrototypeAndProperties();
}
function dumpn(msg) {
dump("DBG-TEST: " + msg + "\n");
}
function testExceptionHook(ex) { try {
do_report_unexpected_exception(ex);
} catch (e) { return { throw: e };
} return undefined;
}
// Convert an nsIScriptError 'logLevel' value into an appropriate string. function scriptErrorLogLevel(message) { switch (message.logLevel) { case Ci.nsIConsoleMessage.info: return"info"; case Ci.nsIConsoleMessage.warn: return"warning"; default: Assert.equal(message.logLevel, Ci.nsIConsoleMessage.error); return"error";
}
}
// Register a console listener, so console messages don't just disappear // into the ether. var errorCount = 0; var listener = {
observe(message) { try {
let string;
errorCount++; try { // If we've been given an nsIScriptError, then we can print out // something nicely formatted, for tools like Emacs to pick up.
message.QueryInterface(Ci.nsIScriptError);
dumpn(
message.sourceName + ":" +
message.lineNumber + ": " +
scriptErrorLogLevel(message) + ": " +
message.errorMessage
);
string = message.errorMessage;
} catch (e1) { // Be a little paranoid with message, as the whole goal here is to lose // no information. try {
string = "" + message.message;
} catch (e2) {
string = "";
}
}
// Make sure we exit all nested event loops so that the test can finish. while (
DevToolsServer &&
DevToolsServer.xpcInspector &&
DevToolsServer.xpcInspector.eventLoopNestLevel > 0
) {
DevToolsServer.xpcInspector.exitNestedEventLoop();
}
// In the world before bug 997440, exceptions were getting lost because of // the arbitrary JSContext being used in nsXPCWrappedJS::CallMethod. // In the new world, the wanderers have returned. However, because of the, // currently very-broken, exception reporting machinery in // nsXPCWrappedJS these get reported as errors to the console, even if // there's actually JS on the stack above that will catch them. If we // throw an error here because of them our tests start failing. So, we'll // just dump the message to the logs instead, to make sure the information // isn't lost.
dumpn("head_dbg.js observed a console message: " + string);
} catch (_) { // Swallow everything to avoid console reentrancy errors. We did our best // to log above, but apparently that didn't cut it.
}
},
};
Services.console.registerListener(listener);
function addTestGlobal(name, server = DevToolsServer) { const global = createTestGlobal(name);
server.addTestGlobal(global); return global;
}
// List the DevToolsClient |client|'s tabs, look for one whose title is // |title|.
async function getTestTab(client, title) { const tabs = await client.mainRoot.listTabs(); for (const tab of tabs) { if (tab.title === title) { return tab;
}
} returnnull;
} /** * Attach to the client's tab whose title is specified * @param {Object} client * @param {Object} title * @returns commands
*/
async function attachTestTab(client, title) { const descriptorFront = await getTestTab(client, title);
// These xpcshell tests use mocked actors (xpcshell-test/testactors) // which still don't support watcher actor. // Because of that we still can't enable server side targets and target swiching.
descriptorFront.disableTargetSwitching();
/** * Attach to the client's tab whose title is specified, and then attach to * that tab's thread. * @param {Object} client * @param {Object} title * @returns {Object} * targetFront * threadFront * commands
*/
async function attachTestThread(client, title) { const commands = await attachTestTab(client, title); const targetFront = commands.targetCommand.targetFront;
// Pass any configuration, in order to ensure starting all the thread actors // and have them to handle debugger statements.
await commands.threadConfigurationCommand.updateConfiguration({
skipBreakpoints: false,
});
const threadFront = await targetFront.getFront("thread"); Assert.equal(threadFront.state, "attached", "Thread front is attached"); return { targetFront, threadFront, commands };
}
/** * Initialize the testing devtools server with a tab whose title is |title|.
*/
async function startTestDevToolsServer(title, server = DevToolsServer) {
initTestDevToolsServer(server);
addTestGlobal(title);
DevToolsServer.registerActors({ target: true });
const transport = DevToolsServer.connectPipe(); const client = new DevToolsClient(transport);
await connect(client); return client;
}
async function finishClient(client) {
await client.close();
DevToolsServer.destroy();
do_test_finished();
}
/** * Takes a relative file path and returns the absolute file url for it.
*/ function getFileUrl(name, allowMissing = false) { const file = do_get_file(name, allowMissing); return Services.io.newFileURI(file).spec;
}
/** * Returns the full path of the file with the specified name in a * platform-independent and URL-like form.
*/ function getFilePath(
name,
allowMissing = false,
usePlatformPathSeparator = false
) { const file = do_get_file(name, allowMissing);
let path = Services.io.newFileURI(file).spec;
let filePrePath = "file://"; if ("nsILocalFileWin" in Ci && file instanceof Ci.nsILocalFileWin) {
filePrePath += "/";
}
path = path.slice(filePrePath.length);
if (usePlatformPathSeparator && path.match(/^\w:/)) {
path = path.replace(/\//g, "\\");
}
return path;
}
/** * Returns the full text contents of the given file.
*/ function readFile(fileName) { const f = do_get_file(fileName); const s = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
Ci.nsIFileInputStream
);
s.init(f, -1, -1, false); try { return NetUtil.readInputStreamToString(s, s.available());
} finally {
s.close();
}
}
function StubTransport() {}
StubTransport.prototype.ready = function () {};
StubTransport.prototype.send = function () {};
StubTransport.prototype.close = function () {};
// Create async version of the object where calling each method // is equivalent of calling it with asyncall. Mainly useful for // destructuring objects with methods that take callbacks. const Async = target => new Proxy(target, Async);
Async.get = (target, name) => typeof target[name] === "function"
? asyncall.bind(null, target[name], target)
: target[name];
// Calls async function that takes callback and errorback and returns // returns promise representing result. const asyncall = (fn, self, ...args) => new Promise((...etc) => fn.call(self, ...args, ...etc));
/** * Create a promise that is resolved on the next occurence of the given event. * * @param ThreadFront threadFront * @param String event * @param Function predicate * @returns Promise
*/ function waitForEvent(front, type, predicate) { if (!predicate) { return front.once(type);
}
returnnew Promise(function (resolve) { function listener(packet) { if (!predicate(packet)) { return;
}
front.off(type, listener);
resolve(packet);
}
front.on(type, listener);
});
}
/** * Execute the action on the next tick and return a promise that is resolved on * the next pause. * * When using promises and Task.jsm, we often want to do an action that causes a * pause and continue the task once the pause has ocurred. Unfortunately, if we * do the action that causes the pause within the task's current tick we will * pause before we have a chance to yield the promise that waits for the pause * and we enter a dead lock. The solution is to create the promise that waits * for the pause, schedule the action to run on the next tick of the event loop, * and finally yield the promise. * * @param Function action * @param ThreadFront threadFront * @returns Promise
*/ function executeOnNextTickAndWaitForPause(action, threadFront) { const paused = waitForPause(threadFront);
executeSoon(action); return paused;
}
/** * Interrupt JS execution for the specified thread. * * @param ThreadFront threadFront * @returns Promise
*/ function interrupt(threadFront) {
dumpn("Interrupting."); return threadFront.interrupt();
}
/** * Resume JS execution for the specified thread and then wait for the next pause * event. * * @param DevToolsClient client * @param ThreadFront threadFront * @returns Promise
*/
async function resumeAndWaitForPause(threadFront) { const paused = waitForPause(threadFront);
await resume(threadFront); return paused;
}
/** * Resume JS execution for a single step and wait for the pause after the step * has been taken. * * @param ThreadFront threadFront * @returns Promise
*/ function stepIn(threadFront) {
dumpn("Stepping in."); const paused = waitForPause(threadFront); return threadFront.stepIn().then(() => paused);
}
/** * Resume JS execution for a step over and wait for the pause after the step * has been taken. * * @param ThreadFront threadFront * @returns Promise
*/
async function stepOver(threadFront, frameActor) {
dumpn("Stepping over.");
await threadFront.stepOver(frameActor); return waitForPause(threadFront);
}
/** * Resume JS execution for a step out and wait for the pause after the step * has been taken. * * @param DevToolsClient client * @param ThreadFront threadFront * @returns Promise
*/
async function stepOut(threadFront, frameActor) {
dumpn("Stepping out.");
await threadFront.stepOut(frameActor); return waitForPause(threadFront);
}
/** * Restart specific frame and wait for the pause after the restart * has been taken. * * @param DevToolsClient client * @param ThreadFront threadFront * @returns Promise
*/
async function restartFrame(threadFront, frameActor) {
dumpn("Restarting frame.");
await threadFront.restart(frameActor); return waitForPause(threadFront);
}
/** * Get the list of `count` frames currently on stack, starting at the index * `first` for the specified thread. * * @param ThreadFront threadFront * @param Number first * @param Number count * @returns Promise
*/ function getFrames(threadFront, first, count) {
dumpn("Getting frames."); return threadFront.getFrames(first, count);
}
async function checkFramesLength(threadFront, expectedFrames) { const frameResponse = await threadFront.getFrames(0, null); Assert.equal(
frameResponse.frames.length,
expectedFrames, "Thread front has the expected number of frames"
);
}
/** * Do a reload which clears the thread debugger * * @param TabFront tabFront * @returns Promise<response>
*/ function reload(tabFront) { return tabFront.reload({});
}
/** * Returns an array of stack location strings given a thread and a sample. * * @param object thread * @param object sample * @returns object
*/ function getInflatedStackLocations(thread, sample) { const stackTable = thread.stackTable; const frameTable = thread.frameTable; const stringTable = thread.stringTable; const SAMPLE_STACK_SLOT = thread.samples.schema.stack; const STACK_PREFIX_SLOT = stackTable.schema.prefix; const STACK_FRAME_SLOT = stackTable.schema.frame; const FRAME_LOCATION_SLOT = frameTable.schema.location;
// Build the stack from the raw data and accumulate the locations in // an array.
let stackIndex = sample[SAMPLE_STACK_SLOT]; const locations = []; while (stackIndex !== null) { const stackEntry = stackTable.data[stackIndex]; const frame = frameTable.data[stackEntry[STACK_FRAME_SLOT]];
locations.push(stringTable[frame[FRAME_LOCATION_SLOT]]);
stackIndex = stackEntry[STACK_PREFIX_SLOT];
}
// The profiler tree is inverted, so reverse the array. return locations.reverse();
}
async function setupTestFromUrl(url) {
do_test_pending();
// These xpcshell tests use mocked actors (xpcshell-test/testactors) // which still don't support watcher actor. // Because of that we still can't enable server side targets and target swiching.
descriptorFront.disableTargetSwitching();
// Pass any configuration, in order to ensure starting all the thread actor // and have it to notify about all sources
await commands.threadConfigurationCommand.updateConfiguration({
skipBreakpoints: false,
});
/** * Run the given test function twice, one with a regular DevToolsServer, * testing against a fake tab. And another one against a WorkerDevToolsServer, * testing the worker codepath. * * @param Function test * Test function to run twice. * This test function is called with a dictionary: * - Sandbox debuggee * The custom JS debuggee created for this test. This is a Sandbox using system * principals by default. * - ThreadFront threadFront * A reference to a ThreadFront instance that is attached to the debuggee. * - DevToolsClient client * A reference to the DevToolsClient used to communicated with the RDP server. * @param Object options * Optional arguments to tweak test environment * - JSPrincipal principal * Principal to use for the debuggee. Defaults to systemPrincipal. * - boolean doNotRunWorker * If true, do not run this tests in worker debugger context. Defaults to false. * - bool wantXrays * Whether the debuggee wants Xray vision with respect to same-origin objects * outside the sandbox. Defaults to true. * - bool waitForFinish * Whether to wait for a call to threadFrontTestFinished after the test * function finishes.
*/ function threadFrontTest(test, options = {}) { const {
principal = systemPrincipal,
doNotRunWorker = false,
wantXrays = true,
waitForFinish = false,
} = options;
async function runThreadFrontTestWithServer(server, test) { // Setup a server and connect a client to it.
initTestDevToolsServer(server);
// Create a custom debuggee and register it to the server. // We are using a custom Sandbox as debuggee. Create a new zone because // debugger and debuggee must be in different compartments. const debuggee = Cu.Sandbox(principal, { freshZone: true, wantXrays }); const scriptName = "debuggee.js";
debuggee.__name = scriptName;
server.addTestGlobal(debuggee);
const client = new DevToolsClient(server.connectPipe());
await client.connect();
// Attach to the fake tab target and retrieve the ThreadFront instance. // Automatically resume as the thread is paused by default after attach. const { targetFront, threadFront, commands } = await attachTestThread(
client,
scriptName
);
// Cross the client/server boundary to retrieve the target actor & thread // actor instances, used by some tests. const rootActor = client.transport._serverConnection.rootActor; const targetActor =
rootActor._parameters.tabList.getTargetActorForTab("debuggee.js"); const { threadActor } = targetActor;
// Run the test function const args = {
threadActor,
threadFront,
debuggee,
client,
server,
targetFront,
commands,
isWorkerServer: server === WorkerDevToolsServer,
}; if (waitForFinish) { // Use dispatchToMainThread so that the test function does not have to // finish executing before the test itself finishes. const promise = new Promise(
resolve => (threadFrontTestFinished = resolve)
);
Services.tm.dispatchToMainThread(() => test(args));
await promise;
} else {
await test(args);
}
// Cleanup the client after the test ran
await client.close();
server.removeTestGlobal(debuggee);
// Also cleanup the created server
server.destroy();
}
return async () => {
dump(">>> Run thread front test against a regular DevToolsServer\n");
await runThreadFrontTestWithServer(DevToolsServer, test);
// Skip tests that fail in the worker context if (!doNotRunWorker) {
dump(">>> Run thread front test against a worker DevToolsServer\n");
await runThreadFrontTestWithServer(WorkerDevToolsServer, test);
}
};
}
// This callback is used in tandem with the waitForFinish option of // threadFrontTest to support thread front tests that use promises to // asynchronously finish the tests, instead of using async/await. // Newly written tests should avoid using this. See bug 1596114 for migrating // existing tests to async/await and removing this functionality.
let threadFrontTestFinished;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.