browser_startup_syncIPC.js (13388B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 /* This test sync IPC done on the main thread during startup. */ 5 6 "use strict"; 7 8 // Shortcuts for conditions. 9 const LINUX = AppConstants.platform == "linux"; 10 const WIN = AppConstants.platform == "win"; 11 const MAC = AppConstants.platform == "macosx"; 12 const WEBRENDER = window.windowUtils.layerManagerType.startsWith("WebRender"); 13 const SKELETONUI = Services.prefs.getBoolPref( 14 "browser.startup.preXulSkeletonUI", 15 false 16 ); 17 // GPUPROCESS is approximate; any check for this should have "ignoreIfUnused: true". 18 const GPUPROCESS = 19 ((WIN || LINUX || MAC) && 20 Services.prefs.getBoolPref("layers.gpu-process.enabled")) || 21 Services.prefs.getBoolPref("layers.gpu-process.force-enabled"); 22 23 /* 24 * Specifying 'ignoreIfUnused: true' will make the test ignore unused entries; 25 * without this the test is strict and will fail if a list entry isn't used. 26 */ 27 const startupPhases = { 28 // Anything done before or during app-startup must have a compelling reason 29 // to run before we have even selected the user profile. 30 "before profile selection": [], 31 32 "before opening first browser window": [], 33 34 // We reach this phase right after showing the first browser window. 35 // This means that any I/O at this point delayed first paint. 36 "before first paint": [ 37 { 38 name: "PLayerTransaction::Msg_GetTextureFactoryIdentifier", 39 condition: (MAC || LINUX) && !WEBRENDER, 40 maxCount: 1, 41 }, 42 { 43 name: "PLayerTransaction::Msg_GetTextureFactoryIdentifier", 44 condition: WIN && !WEBRENDER, 45 maxCount: 3, 46 }, 47 { 48 name: "PWebRenderBridge::Msg_EnsureConnected", 49 condition: WIN && WEBRENDER, 50 maxCount: 3, 51 }, 52 { 53 name: "PWebRenderBridge::Msg_EnsureConnected", 54 condition: (MAC || LINUX) && WEBRENDER, 55 maxCount: 1, 56 }, 57 { 58 // bug 1373773 59 name: "PCompositorBridge::Msg_NotifyChildCreated", 60 condition: !GPUPROCESS, 61 ignoreIfUnused: true, 62 maxCount: 1, 63 }, 64 { 65 // bug 1373773 66 name: "PCompositorBridge::Msg_NotifyChildCreated", 67 condition: GPUPROCESS, 68 ignoreIfUnused: true, 69 maxCount: 2, 70 }, 71 { 72 name: "PCompositorBridge::Msg_MapAndNotifyChildCreated", 73 condition: GPUPROCESS, 74 ignoreIfUnused: true, 75 maxCount: 2, 76 }, 77 { 78 name: "PCompositorBridge::Msg_FlushRendering", 79 condition: MAC, 80 maxCount: 1, 81 }, 82 { 83 name: "PCompositorBridge::Msg_FlushRendering", 84 condition: WIN, 85 ignoreIfUnused: true, // Only on Win7 32 86 maxCount: 1, 87 }, 88 { 89 name: "PCompositorBridge::Msg_Initialize", 90 condition: GPUPROCESS, 91 ignoreIfUnused: true, 92 maxCount: 3, 93 }, 94 { 95 name: "PCompositorWidget::Msg_Initialize", 96 condition: WIN, 97 ignoreIfUnused: true, // Only on Win10 64 98 maxCount: 3, 99 }, 100 { 101 name: "PGPU::Msg_AddLayerTreeIdMapping", 102 condition: GPUPROCESS, 103 ignoreIfUnused: true, 104 maxCount: 5, 105 }, 106 { 107 name: "PCompositorBridge::Msg_MakeSnapshot", 108 condition: WIN && !WEBRENDER, 109 ignoreIfUnused: true, // Only on Win10 64 110 maxCount: 1, 111 }, 112 { 113 name: "PWebRenderBridge::Msg_GetSnapshot", 114 condition: WIN && WEBRENDER, 115 ignoreIfUnused: true, // Sometimes in the next phase on Windows10 QR 116 maxCount: 1, 117 }, 118 { 119 name: "PCompositorBridge::Msg_WillClose", 120 condition: WIN, 121 ignoreIfUnused: true, // Only on Win10 64 122 maxCount: 2, 123 }, 124 { 125 name: "PAPZInputBridge::Msg_ProcessUnhandledEvent", 126 condition: WIN, 127 ignoreIfUnused: true, // Only on Win10 64 128 maxCount: 2, 129 }, 130 { 131 name: "PGPU::Msg_GetDeviceStatus", 132 // bug 1553740 might want to drop the WEBRENDER clause here. 133 // Additionally, the skeleton UI causes us to attach "before first paint" to a 134 // later event, which lets this sneak in. 135 condition: WIN && (WEBRENDER || SKELETONUI), 136 // If Init() completes before we call EnsureGPUReady we won't send GetDeviceStatus 137 // so we can safely ignore if unused. 138 ignoreIfUnused: true, 139 maxCount: 1, 140 }, 141 { 142 // bug 1784869 143 // We use Resume signal to propagate correct XWindow/wl_surface 144 // to EGL compositor. 145 name: "PCompositorBridge::Msg_Resume", 146 condition: LINUX, 147 ignoreIfUnused: true, // intermittently occurs in "before handling user events" 148 maxCount: 1, 149 }, 150 ], 151 152 // We are at this phase once we are ready to handle user events. 153 // Any IO at this phase or before gets in the way of the user 154 // interacting with the first browser window. 155 "before handling user events": [ 156 { 157 name: "PCompositorBridge::Msg_FlushRendering", 158 condition: MAC, 159 ignoreIfUnused: true, 160 maxCount: 1, 161 }, 162 { 163 name: "PCompositorBridge::Msg_FlushRendering", 164 condition: LINUX, 165 ignoreIfUnused: true, // intermittently occurs in "before becoming idle" 166 maxCount: 2, 167 }, 168 { 169 name: "PLayerTransaction::Msg_GetTextureFactoryIdentifier", 170 condition: (!MAC && !WEBRENDER) || (WIN && WEBRENDER), 171 ignoreIfUnused: true, // intermittently occurs in "before becoming idle" 172 maxCount: 1, 173 }, 174 { 175 name: "PCompositorBridge::Msg_Initialize", 176 condition: WIN, 177 ignoreIfUnused: true, // Only on Win10 64 178 maxCount: 1, 179 }, 180 { 181 name: "PCompositorWidget::Msg_Initialize", 182 condition: WIN, 183 ignoreIfUnused: true, // Only on Win10 64 184 maxCount: 1, 185 }, 186 { 187 name: "PCompositorBridge::Msg_WillClose", 188 condition: WIN, 189 ignoreIfUnused: true, // Only on Win7 32 190 maxCount: 2, 191 }, 192 { 193 name: "PCompositorBridge::Msg_MakeSnapshot", 194 condition: WIN, 195 ignoreIfUnused: true, // Only on Win7 32 196 maxCount: 1, 197 }, 198 { 199 name: "PWebRenderBridge::Msg_GetSnapshot", 200 condition: WIN && WEBRENDER, 201 ignoreIfUnused: true, // Sometimes in the next phase on Windows10 QR 202 maxCount: 1, 203 }, 204 { 205 name: "PAPZInputBridge::Msg_ProcessUnhandledEvent", 206 condition: WIN, 207 ignoreIfUnused: true, // intermittently occurs in "before becoming idle" 208 maxCount: 1, 209 }, 210 { 211 name: "PAPZInputBridge::Msg_ReceiveMouseInputEvent", 212 condition: WIN, 213 ignoreIfUnused: true, // intermittently occurs in "before becoming idle" 214 maxCount: 1, 215 }, 216 { 217 name: "PWebRenderBridge::Msg_EnsureConnected", 218 condition: WIN && WEBRENDER, 219 ignoreIfUnused: true, 220 maxCount: 1, 221 }, 222 { 223 name: "PContent::Reply_BeginDriverCrashGuard", 224 condition: WIN, 225 ignoreIfUnused: true, // Bug 1660590 - found while running test on windows hardware 226 maxCount: 1, 227 }, 228 { 229 name: "PContent::Reply_EndDriverCrashGuard", 230 condition: WIN, 231 ignoreIfUnused: true, // Bug 1660590 - found while running test on windows hardware 232 maxCount: 1, 233 }, 234 { 235 // bug 1784869 236 // We use Resume signal to propagate correct XWindow/wl_surface 237 // to EGL compositor. 238 name: "PCompositorBridge::Msg_Resume", 239 condition: LINUX, 240 ignoreIfUnused: true, // intermittently occurs in "before first paint" 241 maxCount: 1, 242 }, 243 ], 244 245 // Things that are expected to be completely out of the startup path 246 // and loaded lazily when used for the first time by the user should 247 // be listed here. 248 "before becoming idle": [ 249 { 250 // bug 1373773 251 name: "PCompositorBridge::Msg_NotifyChildCreated", 252 ignoreIfUnused: true, 253 maxCount: 1, 254 }, 255 { 256 name: "PAPZInputBridge::Msg_ProcessUnhandledEvent", 257 condition: WIN, 258 ignoreIfUnused: true, // Only on Win10 64 259 maxCount: 1, 260 }, 261 { 262 name: "PAPZInputBridge::Msg_ReceiveMouseInputEvent", 263 condition: WIN, 264 ignoreIfUnused: true, // Only on Win10 64 265 maxCount: 1, 266 }, 267 { 268 // bug 1554234 269 name: "PLayerTransaction::Msg_GetTextureFactoryIdentifier", 270 condition: WIN || LINUX, 271 ignoreIfUnused: true, // intermittently occurs in "before handling user events" 272 maxCount: 1, 273 }, 274 { 275 name: "PWebRenderBridge::Msg_EnsureConnected", 276 condition: (WIN || LINUX) && WEBRENDER, 277 ignoreIfUnused: true, 278 maxCount: 1, 279 }, 280 { 281 name: "PCompositorBridge::Msg_Initialize", 282 condition: WIN, 283 ignoreIfUnused: true, // Intermittently occurs in "before handling user events" 284 maxCount: 1, 285 }, 286 { 287 name: "PCompositorWidget::Msg_Initialize", 288 condition: WIN, 289 ignoreIfUnused: true, // Intermittently occurs in "before handling user events" 290 maxCount: 1, 291 }, 292 { 293 name: "PCompositorBridge::Msg_MapAndNotifyChildCreated", 294 condition: WIN, 295 ignoreIfUnused: true, 296 maxCount: 1, 297 }, 298 { 299 name: "PCompositorBridge::Msg_FlushRendering", 300 condition: MAC || SKELETONUI, 301 ignoreIfUnused: true, 302 maxCount: 1, 303 }, 304 { 305 name: "PCompositorBridge::Msg_FlushRendering", 306 condition: LINUX, 307 ignoreIfUnused: true, // intermittently occurs in "before handling user events" 308 maxCount: 1, 309 }, 310 { 311 name: "PWebRenderBridge::Msg_GetSnapshot", 312 condition: WIN && WEBRENDER, 313 ignoreIfUnused: true, 314 maxCount: 1, 315 }, 316 { 317 name: "PCompositorBridge::Msg_MakeSnapshot", 318 condition: WIN, 319 ignoreIfUnused: true, 320 maxCount: 1, 321 }, 322 { 323 name: "PCompositorBridge::Msg_WillClose", 324 condition: WIN, 325 ignoreIfUnused: true, 326 maxCount: 2, 327 }, 328 // Added for the search-detection built-in add-on. 329 { 330 name: "PGPU::Msg_AddLayerTreeIdMapping", 331 condition: GPUPROCESS, 332 ignoreIfUnused: true, 333 maxCount: 1, 334 }, 335 ], 336 }; 337 338 add_task(async function () { 339 if ( 340 !AppConstants.NIGHTLY_BUILD && 341 !AppConstants.MOZ_DEV_EDITION && 342 !AppConstants.DEBUG 343 ) { 344 ok( 345 !("@mozilla.org/test/startuprecorder;1" in Cc), 346 "the startup recorder component shouldn't exist in this non-nightly/non-devedition/" + 347 "non-debug build." 348 ); 349 return; 350 } 351 352 let startupRecorder = 353 Cc["@mozilla.org/test/startuprecorder;1"].getService().wrappedJSObject; 354 await startupRecorder.done; 355 356 // Check for sync IPC markers in the startup profile. 357 let profile = startupRecorder.data.profile.threads[0]; 358 359 let phases = {}; 360 { 361 const nameCol = profile.markers.schema.name; 362 const dataCol = profile.markers.schema.data; 363 const startTimeCol = profile.markers.schema.startTime; 364 365 let markersForCurrentPhase = []; 366 for (let m of profile.markers.data) { 367 let markerName = profile.stringTable[m[nameCol]]; 368 if (markerName.startsWith("startupRecorder:")) { 369 phases[markerName.split("startupRecorder:")[1]] = 370 markersForCurrentPhase; 371 markersForCurrentPhase = []; 372 continue; 373 } 374 375 let markerData = m[dataCol]; 376 if ( 377 !markerData || 378 markerData.category != "Sync IPC" || 379 !m[startTimeCol] 380 ) { 381 continue; 382 } 383 384 markersForCurrentPhase.push(markerName); 385 } 386 } 387 388 for (let phase in startupPhases) { 389 startupPhases[phase] = startupPhases[phase].filter( 390 entry => !("condition" in entry) || entry.condition 391 ); 392 } 393 394 let shouldPass = true; 395 for (let phase in phases) { 396 let knownIPCList = startupPhases[phase]; 397 if (knownIPCList.length) { 398 info( 399 `known sync IPC ${phase}:\n` + 400 knownIPCList 401 .map(e => ` ${e.name} - at most ${e.maxCount} times`) 402 .join("\n") 403 ); 404 } 405 406 let markers = phases[phase]; 407 for (let marker of markers) { 408 let expected = false; 409 for (let entry of knownIPCList) { 410 if (marker == entry.name) { 411 entry.useCount = (entry.useCount || 0) + 1; 412 expected = true; 413 break; 414 } 415 } 416 if (!expected) { 417 ok(false, `unexpected ${marker} sync IPC ${phase}`); 418 shouldPass = false; 419 } 420 } 421 422 for (let entry of knownIPCList) { 423 // Make sure useCount has been defined. 424 entry.useCount = entry.useCount || 0; 425 let message = `sync IPC ${entry.name} `; 426 if (entry.useCount == entry.maxCount) { 427 message += "happened as many times as expected"; 428 } else if (entry.useCount < entry.maxCount) { 429 message += `allowed ${entry.maxCount} but only happened ${entry.useCount} times`; 430 } else { 431 message += `happened ${entry.useCount} but max is ${entry.maxCount}`; 432 shouldPass = false; 433 } 434 Assert.lessOrEqual(entry.useCount, entry.maxCount, `${message} ${phase}`); 435 436 if (entry.useCount == 0 && !entry.ignoreIfUnused) { 437 ok(false, `unused known IPC entry ${phase}: ${entry.name}`); 438 shouldPass = false; 439 } 440 } 441 } 442 443 if (shouldPass) { 444 ok(shouldPass, "No unexpected sync IPC during startup"); 445 } else { 446 const filename = "profile_startup_syncIPC.json"; 447 let path = Services.env.get("MOZ_UPLOAD_DIR"); 448 let profilePath = PathUtils.join(path, filename); 449 await IOUtils.writeJSON(profilePath, startupRecorder.data.profile); 450 ok( 451 false, 452 `Unexpected sync IPC behavior during startup; open the ${filename} ` + 453 "artifact in the Firefox Profiler to see what happened" 454 ); 455 } 456 });