browser_console.js (9662B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 // Test the basic features of the Browser Console. 5 6 "use strict"; 7 8 requestLongerTimeout(2); 9 10 const TEST_URI = 11 "http://example.com/browser/devtools/client/webconsole/" + 12 "test/browser/test-console.html?" + 13 Date.now(); 14 15 const TEST_XHR_ERROR_URI = `http://example.com/404.html?${Date.now()}`; 16 17 const TEST_IMAGE = 18 "http://example.com/browser/devtools/client/webconsole/" + 19 "test/test-image.png"; 20 21 add_task(async function () { 22 // Needed for the execute() call in `testMessages`. 23 await pushPref("security.allow_parent_unrestricted_js_loads", true); 24 await pushPref("devtools.browserconsole.enableNetworkMonitoring", true); 25 await pushPref("devtools.browsertoolbox.scope", "everything"); 26 27 // Open a parent process tab to check it doesn't have impact 28 const aboutRobotsTab = await addTab("about:robots"); 29 // And open the "actual" test tab 30 const tab = await addTab(TEST_URI); 31 32 await testMessages(); 33 34 info("Close tab"); 35 await removeTab(tab); 36 await removeTab(aboutRobotsTab); 37 }); 38 39 async function testMessages() { 40 const opened = waitForBrowserConsole(); 41 let hud = BrowserConsoleManager.getBrowserConsole(); 42 ok(!hud, "browser console is not open"); 43 44 // The test harness does override the global's console property to replace it with 45 // a Console.sys.mjs instance (https://searchfox.org/mozilla-central/rev/c5c002f81f08a73e04868e0c2bf0eb113f200b03/testing/mochitest/api.js#75-78) 46 // So here we reset the console property with the native console (which is luckily 47 // stored in `nativeConsole`). 48 const overriddenConsole = globalThis.console; 49 globalThis.console = globalThis.nativeConsole; 50 51 info("wait for the browser console to open with ctrl-shift-j"); 52 EventUtils.synthesizeKey("j", { accelKey: true, shiftKey: true }, window); 53 54 hud = await opened; 55 ok(hud, "browser console opened"); 56 57 info("Check that we don't display the non-native console API warning"); 58 // Wait a bit to let room for the message to be displayed 59 await wait(1000); 60 is( 61 await findMessageVirtualizedByType({ 62 hud, 63 text: "The Web Console logging API", 64 typeSelector: ".warn", 65 }), 66 undefined, 67 "The message about disabled console API is not displayed" 68 ); 69 // Set the overidden console back. 70 globalThis.console = overriddenConsole; 71 72 await clearOutput(hud); 73 74 await setFilterState(hud, { 75 netxhr: true, 76 css: true, 77 }); 78 79 executeSoon(() => { 80 expectUncaughtException(); 81 // eslint-disable-next-line no-undef 82 foobarException(); 83 }); 84 85 // Add a message from a chrome window. 86 hud.iframeWindow.console.log("message from chrome window"); 87 88 // Spawn worker from a chrome window and log a message and an error 89 const chromeSpawnedWorker = new hud.iframeWindow.Worker( 90 getRootDirectory(gTestPath) + "test-parent-worker.js" 91 ); 92 93 // Spawn Chrome worker from a chrome window and log a message 94 // It's important to use the browser console global so the message gets assigned 95 // a non-numeric innerID in Console.cpp 96 const browserConsoleGlobal = Cu.getGlobalForObject(hud); 97 const chromeWorker = new browserConsoleGlobal.ChromeWorker( 98 URL.createObjectURL( 99 new browserConsoleGlobal.Blob( 100 [`console.log("message in chrome worker")`], 101 { 102 type: "application/javascript", 103 } 104 ) 105 ) 106 ); 107 108 const sandbox = new Cu.Sandbox(null, { 109 wantComponents: false, 110 wantGlobalProperties: ["URL", "URLSearchParams"], 111 }); 112 const error = Cu.evalInSandbox( 113 `new Error("error from nuked globals");`, 114 sandbox 115 ); 116 console.error(error); 117 Cu.nukeSandbox(sandbox); 118 119 const componentsException = new Components.Exception("Components.Exception"); 120 console.error(componentsException); 121 122 // Check privileged error message from a content process 123 await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { 124 (async function () { 125 throw new Error("privileged content process error message"); 126 })(); 127 }); 128 129 // Add a message from a content window. 130 await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { 131 content.wrappedJSObject.console.log("message from content window"); 132 content.wrappedJSObject.throwError("error from content window"); 133 134 content.testWorker = new content.Worker("./test-worker.js"); 135 content.testWorker.postMessage({ 136 type: "log", 137 message: "message in content worker", 138 }); 139 content.testWorker.postMessage({ 140 type: "error", 141 message: "error in content worker", 142 }); 143 }); 144 145 // Test eval. 146 execute(hud, "`Parent Process Location: ${document.location.href}`"); 147 148 // Test eval frame script 149 gBrowser.selectedBrowser.messageManager.loadFrameScript( 150 `data:application/javascript,console.log("framescript-message")`, 151 false 152 ); 153 154 // Check for network requests. 155 const xhr = new XMLHttpRequest(); 156 xhr.onload = () => console.log("xhr loaded, status is: " + xhr.status); 157 xhr.open("get", TEST_URI, true); 158 xhr.send(); 159 160 // Check for xhr error. 161 const xhrErr = new XMLHttpRequest(); 162 xhrErr.onload = () => { 163 console.log("xhr error loaded, status is: " + xhrErr.status); 164 }; 165 xhrErr.open("get", TEST_XHR_ERROR_URI, true); 166 xhrErr.send(); 167 168 // Check that Fetch requests are categorized as "XHR". 169 await fetch(TEST_IMAGE); 170 console.log("fetch loaded"); 171 172 // Check messages logged with Services.console.logMessage 173 const scriptErrorMessage = Cc["@mozilla.org/scripterror;1"].createInstance( 174 Ci.nsIScriptError 175 ); 176 scriptErrorMessage.initWithWindowID( 177 "Error from Services.console.logMessage", 178 gBrowser.currentURI.prePath, 179 0, 180 0, 181 Ci.nsIScriptError.warningFlag, 182 // platform-specific category to test case for Bug 1770160 183 "chrome javascript", 184 gBrowser.selectedBrowser.innerWindowID 185 ); 186 Services.console.logMessage(scriptErrorMessage); 187 188 // Check messages logged in content with Log.sys.mjs 189 await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { 190 const { Log } = ChromeUtils.importESModule( 191 "resource://gre/modules/Log.sys.mjs" 192 ); 193 const logger = Log.repository.getLogger("TEST_LOGGER_" + Date.now()); 194 logger.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter())); 195 logger.level = Log.Level.Info; 196 logger.info("Log.sys.mjs content process messsage"); 197 }); 198 199 // Check CSS warnings in parent process 200 await execute(hud, `document.body.style.backgroundColor = "rainbow"`); 201 202 // Wait enough so any duplicated message would have the time to be rendered 203 await wait(1000); 204 205 await checkUniqueMessageExists( 206 hud, 207 "message from chrome window", 208 ".console-api" 209 ); 210 await checkUniqueMessageExists(hud, "error from nuked globals", ".error"); 211 await checkUniqueMessageExists( 212 hud, 213 "privileged content process error message", 214 ".error" 215 ); 216 await checkUniqueMessageExists( 217 hud, 218 "message from content window", 219 ".console-api" 220 ); 221 await checkUniqueMessageExists(hud, "error from content window", ".error"); 222 await checkUniqueMessageExists( 223 hud, 224 `"Parent Process Location: chrome://browser/content/browser.xhtml"`, 225 ".result" 226 ); 227 await checkUniqueMessageExists(hud, "framescript-message", ".console-api"); 228 await checkUniqueMessageExists( 229 hud, 230 "Error from Services.console.logMessage", 231 ".warn" 232 ); 233 await checkUniqueMessageExists(hud, "foobarException", ".error"); 234 await checkUniqueMessageExists(hud, "test-console.html", ".network"); 235 await checkUniqueMessageExists(hud, "404.html", ".network"); 236 await checkUniqueMessageExists(hud, "test-image.png", ".network"); 237 await checkUniqueMessageExists( 238 hud, 239 "Log.sys.mjs content process messsage", 240 ".console-api" 241 ); 242 await checkUniqueMessageExists( 243 hud, 244 "message in content worker", 245 ".console-api" 246 ); 247 await checkUniqueMessageExists(hud, "error in content worker", ".error"); 248 await checkUniqueMessageExists( 249 hud, 250 "message in parent worker", 251 ".console-api" 252 ); 253 await checkUniqueMessageExists(hud, "error in parent worker", ".error"); 254 await checkUniqueMessageExists( 255 hud, 256 "message in chrome worker", 257 ".console-api" 258 ); 259 await checkUniqueMessageExists( 260 hud, 261 "Expected color but found ‘rainbow’", 262 ".warn" 263 ); 264 await checkUniqueMessageExists( 265 hud, 266 "Expected color but found ‘bled’", 267 ".warn" 268 ); 269 270 await checkComponentExceptionMessage(hud, componentsException); 271 272 await resetFilters(hud); 273 274 await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { 275 content.testWorker.terminate(); 276 delete content.testWorker; 277 }); 278 chromeSpawnedWorker.terminate(); 279 chromeWorker.terminate(); 280 info("Close the Browser Console"); 281 await safeCloseBrowserConsole(); 282 } 283 284 async function checkComponentExceptionMessage(hud, exception) { 285 const msgNode = await checkUniqueMessageExists( 286 hud, 287 "Components.Exception", 288 ".error" 289 ); 290 const framesNode = await waitFor(() => msgNode.querySelector(".pane.frames")); 291 ok(framesNode, "The Components.Exception stack is displayed right away"); 292 293 const frameNodes = framesNode.querySelectorAll(".frame"); 294 Assert.greater(frameNodes.length, 1, "Got at least one frame in the stack"); 295 is( 296 frameNodes[0].querySelector(".line").textContent, 297 String(exception.lineNumber), 298 "The stack displayed by default refers to Components.Exception passed as argument" 299 ); 300 301 const [, line] = msgNode 302 .querySelector(".frame-link-line") 303 .textContent.split(":"); 304 is( 305 line, 306 String(exception.lineNumber + 1), 307 "The link on the top right refers to the console.error callsite" 308 ); 309 }