browser_target_parents.js (5438B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 // Test a given Target's parentFront attribute returns the correct parent front. 7 8 const { 9 DevToolsClient, 10 } = require("resource://devtools/client/devtools-client.js"); 11 const { 12 DevToolsServer, 13 } = require("resource://devtools/server/devtools-server.js"); 14 const { 15 createCommandsDictionary, 16 } = require("resource://devtools/shared/commands/index.js"); 17 18 const TEST_URL = `data:text/html;charset=utf-8,<div id="test"></div>`; 19 20 // Test against Tab targets 21 add_task(async function () { 22 const tab = await addTab(TEST_URL); 23 24 const client = await setupDebuggerClient(); 25 const mainRoot = client.mainRoot; 26 27 const tabDescriptors = await mainRoot.listTabs(); 28 29 const concurrentCommands = []; 30 for (const descriptor of tabDescriptors) { 31 concurrentCommands.push( 32 (async () => { 33 const commands = await createCommandsDictionary(descriptor); 34 // Descriptor's getTarget will only work if the TargetCommand watches for the first top target 35 await commands.targetCommand.startListening(); 36 })() 37 ); 38 } 39 info("Instantiate all tab's commands and initialize their TargetCommand"); 40 await Promise.all(concurrentCommands); 41 42 await testGetTargetWithConcurrentCalls(tabDescriptors, tabTarget => { 43 // We only call BrowsingContextTargetFront.attach and not TargetMixin.attachAndInitThread. 44 // So very few things are done. 45 return !!tabTarget.targetForm?.traits; 46 }); 47 48 await client.close(); 49 await removeTab(tab); 50 }); 51 52 // Test against Process targets 53 add_task(async function () { 54 const client = await setupDebuggerClient(); 55 const mainRoot = client.mainRoot; 56 57 const processes = await mainRoot.listProcesses(); 58 59 // Assert that concurrent calls to getTarget resolves the same target and that it is already attached 60 // With that, we were chasing a precise race, where a second call to ProcessDescriptor.getTarget() 61 // happens between the instantiation of ContentProcessTarget and its call to attach() from getTarget 62 // function. 63 await testGetTargetWithConcurrentCalls(processes, () => { 64 // We only call ContentProcessTargetFront.attach and not TargetMixin.attachAndInitThread. 65 // So nothing is done for content process targets. 66 return true; 67 }); 68 69 await client.close(); 70 }); 71 72 // Test against worker targets on parent process 73 add_task(async function () { 74 const client = await setupDebuggerClient(); 75 76 const mainRoot = client.mainRoot; 77 78 const { workers } = await mainRoot.listWorkers(); 79 80 ok(!!workers.length, "list workers returned a non-empty list of workers"); 81 82 for (const workerDescriptorFront of workers) { 83 let targetFront; 84 try { 85 targetFront = await workerDescriptorFront.getTarget(); 86 } catch (e) { 87 // Ignore race condition where we are trying to connect to a worker 88 // related to a previous test which is being destroyed. 89 if ( 90 e.message.includes("nsIWorkerDebugger.initialize") || 91 workerDescriptorFront.isDestroyed() || 92 !workerDescriptorFront.name 93 ) { 94 info("Failed to connect to " + workerDescriptorFront.url); 95 continue; 96 } 97 throw e; 98 } 99 // Bug 1767760: name might be null on some worker which are probably initializing or destroying. 100 if (!workerDescriptorFront.name) { 101 info("Failed to connect to " + workerDescriptorFront.url); 102 continue; 103 } 104 105 is( 106 workerDescriptorFront, 107 targetFront, 108 "For now, worker descriptors and targets are the same object (see bug 1667404)" 109 ); 110 // Check that accessing descriptor#name getter doesn't throw (See Bug 1714974). 111 ok( 112 workerDescriptorFront.name.includes(".js") || 113 workerDescriptorFront.name.includes(".mjs"), 114 `worker descriptor front holds the worker file name (${workerDescriptorFront.name})` 115 ); 116 is( 117 workerDescriptorFront.isWorkerDescriptor, 118 true, 119 "isWorkerDescriptor is true" 120 ); 121 } 122 123 await client.close(); 124 }); 125 126 async function setupDebuggerClient() { 127 // Instantiate a minimal server 128 DevToolsServer.init(); 129 DevToolsServer.allowChromeProcess = true; 130 if (!DevToolsServer.createRootActor) { 131 DevToolsServer.registerAllActors(); 132 } 133 const transport = DevToolsServer.connectPipe(); 134 const client = new DevToolsClient(transport); 135 await client.connect(); 136 return client; 137 } 138 139 async function testGetTargetWithConcurrentCalls(descriptors, isTargetAttached) { 140 // Assert that concurrent calls to getTarget resolves the same target and that it is already attached 141 await Promise.all( 142 descriptors.map(async descriptor => { 143 const promises = []; 144 const concurrentCalls = 10; 145 for (let i = 0; i < concurrentCalls; i++) { 146 const targetPromise = descriptor.getTarget(); 147 // Every odd runs, wait for a tick to introduce some more randomness 148 if (i % 2 == 0) { 149 await wait(0); 150 } 151 promises.push( 152 targetPromise.then(target => { 153 ok(isTargetAttached(target), "The target is attached"); 154 return target; 155 }) 156 ); 157 } 158 const targets = await Promise.all(promises); 159 for (let i = 1; i < concurrentCalls; i++) { 160 is( 161 targets[0], 162 targets[i], 163 "All the targets returned by concurrent calls to getTarget are the same" 164 ); 165 } 166 }) 167 ); 168 }