browser_target_command_processes.js (7362B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 // Test the TargetCommand API around processes 7 8 const TEST_URL = 9 "data:text/html;charset=utf-8," + encodeURIComponent(`<div id="test"></div>`); 10 11 add_task(async function () { 12 // Enabled fission's pref as the TargetCommand is almost disabled without it 13 await pushPref("devtools.browsertoolbox.scope", "everything"); 14 // Disable the preloaded process as it gets created lazily and may interfere 15 // with process count assertions 16 await pushPref("dom.ipc.processPrelaunch.enabled", false); 17 // This preference helps destroying the content process when we close the tab 18 await pushPref("dom.ipc.keepProcessesAlive.web", 1); 19 20 const commands = await CommandsFactory.forMainProcess(); 21 const targetCommand = commands.targetCommand; 22 await targetCommand.startListening(); 23 24 await testProcesses(targetCommand, targetCommand.targetFront); 25 26 targetCommand.destroy(); 27 // Wait for all the targets to be fully attached so we don't have pending requests. 28 await Promise.all( 29 targetCommand.getAllTargets(targetCommand.ALL_TYPES).map(t => t.initialized) 30 ); 31 32 await commands.destroy(); 33 }); 34 35 add_task(async function () { 36 const commands = await CommandsFactory.forMainProcess(); 37 const targetCommand = commands.targetCommand; 38 await targetCommand.startListening(); 39 40 const created = []; 41 const destroyed = []; 42 const onAvailable = ({ targetFront }) => { 43 created.push(targetFront); 44 }; 45 const onDestroyed = ({ targetFront }) => { 46 destroyed.push(targetFront); 47 }; 48 await targetCommand.watchTargets({ 49 types: [targetCommand.TYPES.PROCESS], 50 onAvailable, 51 onDestroyed, 52 }); 53 Assert.greater(created.length, 1, "We get many content process targets"); 54 55 targetCommand.stopListening(); 56 57 await waitFor( 58 () => created.length == destroyed.length, 59 "Wait for the destruction of all content process targets when calling stopListening" 60 ); 61 is( 62 created.length, 63 destroyed.length, 64 "Got notification of destruction for all previously reported targets" 65 ); 66 67 targetCommand.destroy(); 68 // Wait for all the targets to be fully attached so we don't have pending requests. 69 await Promise.all( 70 targetCommand.getAllTargets(targetCommand.ALL_TYPES).map(t => t.initialized) 71 ); 72 73 await commands.destroy(); 74 }); 75 76 async function testProcesses(targetCommand, target) { 77 info("Test TargetCommand against processes"); 78 const { TYPES } = targetCommand; 79 80 // Note that ppmm also includes the parent process, which is considered as a frame rather than a process 81 const originalProcessesCount = Services.ppmm.childCount - 1; 82 const processes = await targetCommand.getAllTargets([TYPES.PROCESS]); 83 is( 84 processes.length, 85 originalProcessesCount, 86 "Get a target for all content processes" 87 ); 88 89 const processes2 = await targetCommand.getAllTargets([TYPES.PROCESS]); 90 is( 91 processes2.length, 92 originalProcessesCount, 93 "retrieved the same number of processes" 94 ); 95 function sortFronts(f1, f2) { 96 return f1.actorID < f2.actorID; 97 } 98 processes.sort(sortFronts); 99 processes2.sort(sortFronts); 100 for (let i = 0; i < processes.length; i++) { 101 is(processes[i], processes2[i], `process ${i} targets are the same`); 102 } 103 104 // Assert that watchTargets will call the create callback for all existing frames 105 const targets = new Set(); 106 107 const pidRegExp = /^\d+$/; 108 109 const onAvailable = ({ targetFront }) => { 110 if (targets.has(targetFront)) { 111 ok(false, "The same target is notified multiple times via onAvailable"); 112 } 113 is( 114 targetFront.targetType, 115 TYPES.PROCESS, 116 "We are only notified about process targets" 117 ); 118 ok( 119 targetFront == target ? targetFront.isTopLevel : !targetFront.isTopLevel, 120 "isTopLevel property is correct" 121 ); 122 ok( 123 pidRegExp.test(targetFront.processID), 124 `Target has processID of expected shape (${targetFront.processID})` 125 ); 126 targets.add(targetFront); 127 }; 128 const onDestroyed = ({ targetFront }) => { 129 if (!targets.has(targetFront)) { 130 ok( 131 false, 132 "A target is declared destroyed via onDestroy without being notified via onAvailable" 133 ); 134 } 135 is( 136 targetFront.targetType, 137 TYPES.PROCESS, 138 "We are only notified about process targets" 139 ); 140 ok( 141 !targetFront.isTopLevel, 142 "We are never notified about the top level target destruction" 143 ); 144 targets.delete(targetFront); 145 }; 146 await targetCommand.watchTargets({ 147 types: [TYPES.PROCESS], 148 onAvailable, 149 onDestroyed, 150 }); 151 is( 152 targets.size, 153 originalProcessesCount, 154 "retrieved the same number of processes via watchTargets" 155 ); 156 for (let i = 0; i < processes.length; i++) { 157 ok( 158 targets.has(processes[i]), 159 `process ${i} targets are the same via watchTargets` 160 ); 161 } 162 163 const previousTargets = new Set(targets); 164 // Assert that onAvailable is called for processes created *after* the call to watchTargets 165 const onProcessCreated = new Promise(resolve => { 166 const onAvailable2 = ({ targetFront }) => { 167 if (previousTargets.has(targetFront)) { 168 return; 169 } 170 targetCommand.unwatchTargets({ 171 types: [TYPES.PROCESS], 172 onAvailable: onAvailable2, 173 }); 174 resolve(targetFront); 175 }; 176 targetCommand.watchTargets({ 177 types: [TYPES.PROCESS], 178 onAvailable: onAvailable2, 179 }); 180 }); 181 info("open new tab in new process"); 182 const tab1 = await BrowserTestUtils.openNewForegroundTab({ 183 gBrowser, 184 url: TEST_URL, 185 forceNewProcess: true, 186 }); 187 info("wait for process target to be created"); 188 const createdTarget = await onProcessCreated; 189 // For some reason, creating a new tab purges processes created from previous tests 190 // so it is not reasonable to assert the size of `targets` as it may be lower than expected. 191 ok(targets.has(createdTarget), "The new tab process is in the list"); 192 193 const processCountAfterTabOpen = targets.size; 194 195 // Assert that onDestroy is called for destroyed processes 196 const onProcessDestroyed = new Promise(resolve => { 197 const onAvailable3 = () => {}; 198 const onDestroyed3 = ({ targetFront }) => { 199 resolve(targetFront); 200 targetCommand.unwatchTargets({ 201 types: [TYPES.PROCESS], 202 onAvailable: onAvailable3, 203 onDestroyed: onDestroyed3, 204 }); 205 }; 206 targetCommand.watchTargets({ 207 types: [TYPES.PROCESS], 208 onAvailable: onAvailable3, 209 onDestroyed: onDestroyed3, 210 }); 211 }); 212 213 BrowserTestUtils.removeTab(tab1); 214 215 const destroyedTarget = await onProcessDestroyed; 216 is( 217 targets.size, 218 processCountAfterTabOpen - 1, 219 "The closed tab's process has been reported as destroyed" 220 ); 221 ok( 222 !targets.has(destroyedTarget), 223 "The destroyed target is no longer in the list" 224 ); 225 is( 226 destroyedTarget, 227 createdTarget, 228 "The destroyed target is the one that has been reported as created" 229 ); 230 231 targetCommand.unwatchTargets({ 232 types: [TYPES.PROCESS], 233 onAvailable, 234 onDestroyed, 235 }); 236 237 // Ensure that getAllTargets still works after the call to unwatchTargets 238 const processes3 = await targetCommand.getAllTargets([TYPES.PROCESS]); 239 is( 240 processes3.length, 241 processCountAfterTabOpen - 1, 242 "getAllTargets reports a new target" 243 ); 244 }