/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; // Test a given Target's parentFront attribute returns the correct parent front. const { DevToolsClient, } = require("resource://devtools/client/devtools-client.js"); const { DevToolsServer, } = require("resource://devtools/server/devtools-server.js"); const { createCommandsDictionary, } = require("resource://devtools/shared/commands/index.js"); const TEST_URL = `data:text/html;charset=utf-8,
`; // Test against Tab targets add_task(async function () { const tab = await addTab(TEST_URL); const client = await setupDebuggerClient(); const mainRoot = client.mainRoot; const tabDescriptors = await mainRoot.listTabs(); const concurrentCommands = []; for (const descriptor of tabDescriptors) { concurrentCommands.push( (async () => { const commands = await createCommandsDictionary(descriptor); // Descriptor's getTarget will only work if the TargetCommand watches for the first top target await commands.targetCommand.startListening(); })() ); } info("Instantiate all tab's commands and initialize their TargetCommand"); await Promise.all(concurrentCommands); await testGetTargetWithConcurrentCalls(tabDescriptors, tabTarget => { // We only call BrowsingContextTargetFront.attach and not TargetMixin.attachAndInitThread. // So very few things are done. return !!tabTarget.targetForm?.traits; }); await client.close(); await removeTab(tab); }); // Test against Process targets add_task(async function () { const client = await setupDebuggerClient(); const mainRoot = client.mainRoot; const processes = await mainRoot.listProcesses(); // Assert that concurrent calls to getTarget resolves the same target and that it is already attached // With that, we were chasing a precise race, where a second call to ProcessDescriptor.getTarget() // happens between the instantiation of ContentProcessTarget and its call to attach() from getTarget // function. await testGetTargetWithConcurrentCalls(processes, () => { // We only call ContentProcessTargetFront.attach and not TargetMixin.attachAndInitThread. // So nothing is done for content process targets. return true; }); await client.close(); }); // Test against Webextension targets add_task(async function () { const client = await setupDebuggerClient(); const mainRoot = client.mainRoot; const addons = await mainRoot.listAddons(); await Promise.all( // some extensions, such as themes, are not debuggable. Filter those out // before trying to connect. addons .filter(a => a.debuggable) .map(async addonDescriptorFront => { const addonFront = await addonDescriptorFront.getTarget(); ok(addonFront, "Got the addon target"); }) ); await client.close(); }); // Test against worker targets on parent process add_task(async function () { const client = await setupDebuggerClient(); const mainRoot = client.mainRoot; const { workers } = await mainRoot.listWorkers(); ok(!!workers.length, "list workers returned a non-empty list of workers"); for (const workerDescriptorFront of workers) { let targetFront; try { targetFront = await workerDescriptorFront.getTarget(); } catch (e) { // Ignore race condition where we are trying to connect to a worker // related to a previous test which is being destroyed. if ( e.message.includes("nsIWorkerDebugger.initialize") || workerDescriptorFront.isDestroyed() || !workerDescriptorFront.name ) { info("Failed to connect to " + workerDescriptorFront.url); continue; } throw e; } // Bug 1767760: name might be null on some worker which are probably initializing or destroying. if (!workerDescriptorFront.name) { info("Failed to connect to " + workerDescriptorFront.url); continue; } is( workerDescriptorFront, targetFront, "For now, worker descriptors and targets are the same object (see bug 1667404)" ); // Check that accessing descriptor#name getter doesn't throw (See Bug 1714974). ok( workerDescriptorFront.name.includes(".js") || workerDescriptorFront.name.includes(".mjs"), `worker descriptor front holds the worker file name (${workerDescriptorFront.name})` ); is( workerDescriptorFront.isWorkerDescriptor, true, "isWorkerDescriptor is true" ); } await client.close(); }); async function setupDebuggerClient() { // Instantiate a minimal server DevToolsServer.init(); DevToolsServer.allowChromeProcess = true; if (!DevToolsServer.createRootActor) { DevToolsServer.registerAllActors(); } const transport = DevToolsServer.connectPipe(); const client = new DevToolsClient(transport); await client.connect(); return client; } async function testGetTargetWithConcurrentCalls(descriptors, isTargetAttached) { // Assert that concurrent calls to getTarget resolves the same target and that it is already attached await Promise.all( descriptors.map(async descriptor => { const promises = []; const concurrentCalls = 10; for (let i = 0; i < concurrentCalls; i++) { const targetPromise = descriptor.getTarget(); // Every odd runs, wait for a tick to introduce some more randomness if (i % 2 == 0) { await wait(0); } promises.push( targetPromise.then(target => { ok(isTargetAttached(target), "The target is attached"); return target; }) ); } const targets = await Promise.all(promises); for (let i = 1; i < concurrentCalls; i++) { is( targets[0], targets[i], "All the targets returned by concurrent calls to getTarget are the same" ); } }) ); }