head.js (8785B)
1 "use strict"; 2 3 const { TelemetryTestUtils } = ChromeUtils.importESModule( 4 "resource://testing-common/TelemetryTestUtils.sys.mjs" 5 ); 6 7 /* eslint-disable mozilla/no-redeclare-with-import-autofix */ 8 const { ContentTaskUtils } = ChromeUtils.importESModule( 9 "resource://testing-common/ContentTaskUtils.sys.mjs" 10 ); 11 12 /** 13 * Returns a Promise that resolves once a crash report has 14 * been submitted. This function will also test the crash 15 * reports extra data to see if it matches expectedExtra. 16 * 17 * @param expectedExtra (object) 18 * An Object whose key-value pairs will be compared 19 * against the key-value pairs in the extra data of the 20 * crash report. A test failure will occur if there is 21 * a mismatch. 22 * 23 * If the value of the key-value pair is "null", this will 24 * be interpreted as "this key should not be included in the 25 * extra data", and will cause a test failure if it is detected 26 * in the crash report. 27 * 28 * Note that this will ignore any keys that are not included 29 * in expectedExtra. It's possible that the crash report 30 * will contain other extra information that is not 31 * compared against. 32 * @returns Promise 33 */ 34 function promiseCrashReport(expectedExtra = {}) { 35 return (async function () { 36 info("Starting wait on crash-report-status"); 37 let [subject] = await TestUtils.topicObserved( 38 "crash-report-status", 39 (unused, data) => { 40 return data == "success"; 41 } 42 ); 43 info("Topic observed!"); 44 45 if (!(subject instanceof Ci.nsIPropertyBag2)) { 46 throw new Error("Subject was not a Ci.nsIPropertyBag2"); 47 } 48 49 let remoteID = getPropertyBagValue(subject, "serverCrashID"); 50 if (!remoteID) { 51 throw new Error("Report should have a server ID"); 52 } 53 54 let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); 55 file.initWithPath(Services.crashmanager._submittedDumpsDir); 56 file.append(remoteID + ".txt"); 57 if (!file.exists()) { 58 throw new Error("Report should have been received by the server"); 59 } 60 61 file.remove(false); 62 63 let extra = getPropertyBagValue(subject, "extra"); 64 if (!(extra instanceof Ci.nsIPropertyBag2)) { 65 throw new Error("extra was not a Ci.nsIPropertyBag2"); 66 } 67 68 info("Iterating crash report extra keys"); 69 for (let { name: key } of extra.enumerator) { 70 let value = extra.getPropertyAsAString(key); 71 if (key in expectedExtra) { 72 if (expectedExtra[key] == null) { 73 ok(false, `Got unexpected key ${key} with value ${value}`); 74 } else { 75 is( 76 value, 77 expectedExtra[key], 78 `Crash report had the right extra value for ${key}` 79 ); 80 } 81 } 82 } 83 })(); 84 } 85 86 function promiseCrashReportFail() { 87 return (async function () { 88 info("Starting wait on crash-report-status"); 89 await TestUtils.topicObserved("crash-report-status", (unused, data) => { 90 return data == "failed"; 91 }); 92 info("Topic observed!"); 93 })(); 94 } 95 96 /** 97 * For an nsIPropertyBag, returns the value for a given 98 * key. 99 * 100 * @param bag 101 * The nsIPropertyBag to retrieve the value from 102 * @param key 103 * The key that we want to get the value for from the 104 * bag 105 * @returns The value corresponding to the key from the bag, 106 * or null if the value could not be retrieved (for 107 * example, if no value is set at that key). 108 */ 109 function getPropertyBagValue(bag, key) { 110 try { 111 let val = bag.getProperty(key); 112 return val; 113 } catch (e) { 114 if (e.result != Cr.NS_ERROR_FAILURE) { 115 throw e; 116 } 117 } 118 119 return null; 120 } 121 122 /** 123 * Sets up the browser to send crash reports to the local crash report 124 * testing server. 125 */ 126 async function setupLocalCrashReportServer() { 127 const SERVER_URL = 128 // eslint-disable-next-line @microsoft/sdl/no-insecure-url 129 "http://example.com/browser/toolkit/crashreporter/test/browser/crashreport.sjs"; 130 131 // The test harness sets MOZ_CRASHREPORTER_NO_REPORT, which disables crash 132 // reports. This test needs them enabled. The test also needs a mock 133 // report server, and fortunately one is already set up by toolkit/ 134 // crashreporter/test/Makefile.in. Assign its URL to MOZ_CRASHREPORTER_URL, 135 // which CrashSubmit.sys.mjs uses as a server override. 136 let noReport = Services.env.get("MOZ_CRASHREPORTER_NO_REPORT"); 137 let serverUrl = Services.env.get("MOZ_CRASHREPORTER_URL"); 138 Services.env.set("MOZ_CRASHREPORTER_NO_REPORT", ""); 139 Services.env.set("MOZ_CRASHREPORTER_URL", SERVER_URL); 140 141 registerCleanupFunction(function () { 142 Services.env.set("MOZ_CRASHREPORTER_NO_REPORT", noReport); 143 Services.env.set("MOZ_CRASHREPORTER_URL", serverUrl); 144 }); 145 } 146 147 /** 148 * Monkey patches TabCrashHandler.getDumpID to return null in order to test 149 * about:tabcrashed when a dump is not available. 150 */ 151 function prepareNoDump() { 152 let originalGetDumpID = TabCrashHandler.getDumpID; 153 TabCrashHandler.getDumpID = function () { 154 return null; 155 }; 156 registerCleanupFunction(() => { 157 TabCrashHandler.getDumpID = originalGetDumpID; 158 }); 159 } 160 161 const kBuildidMatchEnv = "MOZ_BUILDID_MATCH_DONTSEND"; 162 163 function setBuildidMatchDontSendEnv() { 164 info("Setting " + kBuildidMatchEnv + "=1"); 165 Services.env.set(kBuildidMatchEnv, "1"); 166 } 167 168 function unsetBuildidMatchDontSendEnv() { 169 info("Unsetting " + kBuildidMatchEnv); 170 Services.env.set(kBuildidMatchEnv, "0"); 171 } 172 173 const kBuildidMismatchEnv = "MOZ_FORCE_BUILDID_MISMATCH"; 174 175 function setBuildidMismatchEnv() { 176 info("Setting " + kBuildidMismatchEnv + "=1"); 177 Services.env.set(kBuildidMismatchEnv, "1"); 178 } 179 180 function unsetBuildidMismatchEnv() { 181 info("Unsetting " + kBuildidMismatchEnv); 182 Services.env.set(kBuildidMismatchEnv, "0"); 183 } 184 185 function getEventPromise(eventName, eventKind) { 186 return new Promise(function (resolve) { 187 info("Installing event listener (" + eventKind + ")"); 188 window.addEventListener( 189 eventName, 190 () => { 191 ok(true, "Received " + eventName + " (" + eventKind + ") event"); 192 info("Call resolve() for " + eventKind + " event"); 193 resolve(); 194 }, 195 { once: true } 196 ); 197 info("Installed event listener (" + eventKind + ")"); 198 }); 199 } 200 201 async function openNewTab(forceCrash) { 202 const PAGE = 203 "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page."; 204 205 let options = { 206 gBrowser, 207 PAGE, 208 waitForLoad: false, 209 waitForStateStop: false, 210 forceNewProcess: true, 211 }; 212 213 let tab = await BrowserTestUtils.openNewForegroundTab(options); 214 if (forceCrash === true) { 215 let browser = tab.linkedBrowser; 216 await BrowserTestUtils.crashFrame( 217 browser, 218 /* shouldShowTabCrashPage */ false, 219 /* shouldClearMinidumps */ true, 220 /* BrowsingContext */ null 221 ); 222 } 223 224 return tab; 225 } 226 227 async function closeTab(tab) { 228 await TestUtils.waitForTick(); 229 BrowserTestUtils.removeTab(tab); 230 } 231 232 const kInterval = 100; /* ms */ 233 const kRetries = 5; 234 235 /** 236 * This function waits until utility scalars are reported into the 237 * scalar snapshot. 238 */ 239 async function waitForProcessScalars(name) { 240 await ContentTaskUtils.waitForCondition( 241 () => { 242 const scalars = TelemetryTestUtils.getProcessScalars("parent"); 243 return Object.keys(scalars).includes(name); 244 }, 245 `Waiting for ${name} scalars to have been set`, 246 kInterval, 247 kRetries 248 ); 249 } 250 251 async function getTelemetry(name) { 252 try { 253 await waitForProcessScalars(name); 254 const scalars = TelemetryTestUtils.getProcessScalars("parent"); 255 return scalars[name]; 256 } catch (ex) { 257 const msg = `Waiting for ${name} scalars to have been set`; 258 if (ex.indexOf(msg) === 0) { 259 return undefined; 260 } 261 throw ex; 262 } 263 } 264 265 async function getFalsePositiveTelemetry() { 266 return await getTelemetry( 267 "dom.contentprocess.buildID_mismatch_false_positive" 268 ); 269 } 270 271 async function getTrueMismatchTelemetry() { 272 return await getTelemetry("dom.contentprocess.buildID_mismatch"); 273 } 274 275 // The logic bound to dom.ipc.processPrelaunch.enabled will react to value 276 // changes: https://searchfox.org/mozilla-central/rev/ecd91b104714a8b2584a4c03175be50ccb3a7c67/dom/ipc/PreallocatedProcessManager.cpp#171-195 277 // So we force flip to ensure we have no dangling process. 278 async function forceCleanProcesses() { 279 const origPrefValue = SpecialPowers.getBoolPref( 280 "dom.ipc.processPrelaunch.enabled" 281 ); 282 await SpecialPowers.setBoolPref( 283 "dom.ipc.processPrelaunch.enabled", 284 !origPrefValue 285 ); 286 await SpecialPowers.setBoolPref( 287 "dom.ipc.processPrelaunch.enabled", 288 origPrefValue 289 ); 290 const currPrefValue = SpecialPowers.getBoolPref( 291 "dom.ipc.processPrelaunch.enabled" 292 ); 293 Assert.strictEqual( 294 currPrefValue, 295 origPrefValue, 296 "processPrelaunch properly re-enabled" 297 ); 298 }