window.js (10771B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 var { loader, require } = ChromeUtils.importESModule( 8 "resource://devtools/shared/loader/Loader.sys.mjs" 9 ); 10 11 var { useDistinctSystemPrincipalLoader, releaseDistinctSystemPrincipalLoader } = 12 ChromeUtils.importESModule( 13 "resource://devtools/shared/loader/DistinctSystemPrincipalLoader.sys.mjs", 14 { global: "shared" } 15 ); 16 17 // Require this module to setup core modules 18 loader.require("resource://devtools/client/framework/devtools-browser.js"); 19 20 var { gDevTools } = require("resource://devtools/client/framework/devtools.js"); 21 var { Toolbox } = require("resource://devtools/client/framework/toolbox.js"); 22 var { 23 DevToolsClient, 24 } = require("resource://devtools/client/devtools-client.js"); 25 var { PrefsHelper } = require("resource://devtools/client/shared/prefs.js"); 26 const KeyShortcuts = require("resource://devtools/client/shared/key-shortcuts.js"); 27 const { LocalizationHelper } = require("resource://devtools/shared/l10n.js"); 28 const L10N = new LocalizationHelper( 29 "devtools/client/locales/toolbox.properties" 30 ); 31 32 const lazy = {}; 33 ChromeUtils.defineESModuleGetters(lazy, { 34 BrowserToolboxLauncher: 35 "resource://devtools/client/framework/browser-toolbox/Launcher.sys.mjs", 36 }); 37 38 const { 39 CommandsFactory, 40 } = require("resource://devtools/shared/commands/commands-factory.js"); 41 42 // Timeout to wait before we assume that a connect() timed out without an error. 43 // In milliseconds. (With the Debugger pane open, this has been reported to last 44 // more than 10 seconds!) 45 const STATUS_REVEAL_TIME = 15000; 46 47 /** 48 * Shortcuts for accessing various debugger preferences. 49 */ 50 var Prefs = new PrefsHelper("devtools.debugger", { 51 chromeDebuggingHost: ["Char", "chrome-debugging-host"], 52 chromeDebuggingWebSocket: ["Bool", "chrome-debugging-websocket"], 53 }); 54 55 var gCommands, gToolbox, gShortcuts; 56 57 function appendStatusMessage(msg) { 58 const statusMessage = document.getElementById("status-message"); 59 statusMessage.textContent += msg + "\n"; 60 if (msg.stack) { 61 statusMessage.textContent += msg.stack + "\n"; 62 } 63 } 64 65 function toggleStatusMessage(visible = true) { 66 document.getElementById("status-message-container").hidden = !visible; 67 } 68 69 function revealStatusMessage() { 70 toggleStatusMessage(true); 71 } 72 73 function hideStatusMessage() { 74 toggleStatusMessage(false); 75 } 76 77 var connect = async function () { 78 // Initiate the connection 79 80 // MOZ_BROWSER_TOOLBOX_FORCE_MULTIPROCESS is set by the target Firefox instance 81 // before opening the Browser Toolbox, it's set to "1" if multiprocess mode should 82 // be forced (for example, when running mochitest with `--jsdebugger`). 83 if (Services.env.get("MOZ_BROWSER_TOOLBOX_FORCE_MULTIPROCESS") === "1") { 84 Services.prefs.setCharPref("devtools.browsertoolbox.scope", "everything"); 85 } 86 87 const port = Services.env.get("MOZ_BROWSER_TOOLBOX_PORT"); 88 89 // A port needs to be passed in from the environment, for instance: 90 // MOZ_BROWSER_TOOLBOX_PORT=6080 ./mach run -chrome \ 91 // chrome://devtools/content/framework/browser-toolbox/window.html 92 if (!port) { 93 throw new Error( 94 "Must pass a port in an env variable with MOZ_BROWSER_TOOLBOX_PORT" 95 ); 96 } 97 98 const host = Prefs.chromeDebuggingHost; 99 const webSocket = Prefs.chromeDebuggingWebSocket; 100 appendStatusMessage(`Connecting to ${host}:${port}, ws: ${webSocket}`); 101 const transport = await DevToolsClient.socketConnect({ 102 host, 103 port, 104 webSocket, 105 }); 106 const client = new DevToolsClient(transport); 107 appendStatusMessage("Start protocol client for connection"); 108 await client.connect(); 109 110 appendStatusMessage("Get root form for toolbox"); 111 gCommands = await CommandsFactory.forMainProcess({ client }); 112 113 // Bug 1794607: for some unexpected reason, closing the DevToolsClient 114 // when the commands is destroyed by the toolbox would introduce leaks 115 // when running the browser-toolbox mochitests. 116 gCommands.shouldCloseClient = false; 117 118 await openToolbox(gCommands); 119 }; 120 121 // Certain options should be toggled since we can assume chrome debugging here 122 function setPrefDefaults() { 123 Services.prefs.setBoolPref("devtools.inspector.showUserAgentStyles", true); 124 Services.prefs.setBoolPref( 125 "devtools.inspector.showAllAnonymousContent", 126 true 127 ); 128 Services.prefs.setBoolPref("browser.dom.window.dump.enabled", true); 129 Services.prefs.setBoolPref("devtools.console.stdout.chrome", true); 130 Services.prefs.setBoolPref( 131 "devtools.command-button-noautohide.enabled", 132 true 133 ); 134 135 // Bug 1773226: Try to avoid session restore to reopen a transient browser window 136 // if we ever opened a URL from the browser toolbox. (but it doesn't seem to be enough) 137 Services.prefs.setBoolPref("browser.sessionstore.resume_from_crash", false); 138 139 // Disable Safe mode as the browser toolbox is often closed brutaly by subprocess 140 // and the safe mode kicks in when reopening it 141 Services.prefs.setIntPref("toolkit.startup.max_resumed_crashes", -1); 142 } 143 144 window.addEventListener( 145 "load", 146 async function () { 147 gShortcuts = new KeyShortcuts({ window }); 148 gShortcuts.on("CmdOrCtrl+W", onCloseCommand); 149 gShortcuts.on("CmdOrCtrl+Alt+Shift+I", onDebugBrowserToolbox); 150 gShortcuts.on("CmdOrCtrl+Alt+R", onReloadBrowser); 151 152 const statusMessageContainer = document.getElementById( 153 "status-message-title" 154 ); 155 statusMessageContainer.textContent = L10N.getStr( 156 "browserToolbox.statusMessage" 157 ); 158 159 setPrefDefaults(); 160 161 // Reveal status message if connecting is slow or if an error occurs. 162 const delayedStatusReveal = setTimeout( 163 revealStatusMessage, 164 STATUS_REVEAL_TIME 165 ); 166 try { 167 await connect(); 168 clearTimeout(delayedStatusReveal); 169 hideStatusMessage(); 170 } catch (e) { 171 clearTimeout(delayedStatusReveal); 172 appendStatusMessage(e); 173 revealStatusMessage(); 174 console.error(e); 175 } 176 }, 177 { once: true } 178 ); 179 180 function onCloseCommand() { 181 window.close(); 182 } 183 184 /** 185 * Open a Browser toolbox debugging the current browser toolbox 186 * 187 * This helps debugging the browser toolbox code, especially the code 188 * running in the parent process. i.e. frontend code. 189 */ 190 function onDebugBrowserToolbox() { 191 lazy.BrowserToolboxLauncher.init(); 192 } 193 194 /** 195 * Replicate the local-build-only key shortcut to reload the browser 196 */ 197 function onReloadBrowser() { 198 gToolbox.commands.targetCommand.reloadTopLevelTarget(); 199 } 200 201 async function openToolbox(commands) { 202 const form = commands.descriptorFront._form; 203 appendStatusMessage( 204 `Create toolbox for target descriptor: ${JSON.stringify({ form }, null, 2)}` 205 ); 206 207 // Remember the last panel that was used inside of this profile. 208 // But if we are testing, then it should always open the debugger panel. 209 const selectedTool = Services.prefs.getCharPref( 210 "devtools.browsertoolbox.panel", 211 Services.prefs.getCharPref("devtools.toolbox.selectedTool", "jsdebugger") 212 ); 213 214 const toolboxOptions = { doc: document }; 215 appendStatusMessage(`Show toolbox with ${selectedTool} selected`); 216 217 gToolbox = await gDevTools.showToolbox(commands, { 218 toolId: selectedTool, 219 hostType: Toolbox.HostType.BROWSERTOOLBOX, 220 hostOptions: toolboxOptions, 221 }); 222 223 bindToolboxHandlers(); 224 225 // Enable some testing features if the browser toolbox test pref is set. 226 if ( 227 Services.prefs.getBoolPref( 228 "devtools.browsertoolbox.enable-test-server", 229 false 230 ) 231 ) { 232 // setup a server so that the test can evaluate messages in this process. 233 installTestingServer(); 234 } 235 236 await gToolbox.raise(); 237 238 // Warn the user if we started recording this browser toolbox via MOZ_BROWSER_TOOLBOX_PROFILER_STARTUP=1 239 if (Services.env.get("MOZ_PROFILER_STARTUP") === "1") { 240 const notificationBox = gToolbox.getNotificationBox(); 241 const text = 242 "The profiler started recording this toolbox, open another browser toolbox to open the profile via the performance panel"; 243 notificationBox.appendNotification( 244 text, 245 null, 246 null, 247 notificationBox.PRIORITY_INFO_HIGH 248 ); 249 } 250 } 251 252 let releaseTestLoader = null; 253 function installTestingServer() { 254 // Install a DevToolsServer in this process and inform the server of its 255 // location. Tests operating on the browser toolbox run in the server 256 // (the firefox parent process) and can connect to this new server using 257 // initBrowserToolboxTask(), allowing them to evaluate scripts here. 258 259 const requester = {}; 260 const testLoader = useDistinctSystemPrincipalLoader(requester); 261 releaseTestLoader = () => releaseDistinctSystemPrincipalLoader(requester); 262 const { DevToolsServer } = testLoader.require( 263 "resource://devtools/server/devtools-server.js" 264 ); 265 const { SocketListener } = testLoader.require( 266 "resource://devtools/shared/security/socket.js" 267 ); 268 269 DevToolsServer.init(); 270 DevToolsServer.registerAllActors(); 271 DevToolsServer.allowChromeProcess = true; 272 273 // Force this server to be kept alive until the browser toolbox process is closed. 274 // For some reason intermittents appears on Windows when destroying the server 275 // once the last connection drops. 276 DevToolsServer.keepAlive = true; 277 278 // Use a fixed port which initBrowserToolboxTask can look for. 279 const socketOptions = { portOrPath: 6001 }; 280 const listener = new SocketListener(DevToolsServer, socketOptions); 281 listener.open(); 282 } 283 284 async function bindToolboxHandlers() { 285 gToolbox.once("destroyed", quitApp); 286 window.addEventListener("unload", onUnload); 287 288 // If the remote connection drops, firefox was closed 289 // In such case, force closing the browser toolbox 290 gCommands.client.once("closed", quitApp); 291 292 if (Services.appinfo.OS == "Darwin") { 293 // Badge the dock icon to differentiate this process from the main application 294 // process. 295 updateBadgeText(false); 296 297 gToolbox.on("toolbox-paused", () => updateBadgeText(true)); 298 gToolbox.on("toolbox-resumed", () => updateBadgeText(false)); 299 } 300 } 301 302 function updateBadgeText(paused) { 303 const dockSupport = Cc["@mozilla.org/widget/macdocksupport;1"].getService( 304 Ci.nsIMacDockSupport 305 ); 306 dockSupport.badgeText = paused ? "▐▐ " : " ▶"; 307 } 308 309 function onUnload() { 310 window.removeEventListener("unload", onUnload); 311 gToolbox.destroy(); 312 if (releaseTestLoader) { 313 releaseTestLoader(); 314 releaseTestLoader = null; 315 } 316 } 317 318 function quitApp() { 319 const quit = Cc["@mozilla.org/supports-PRBool;1"].createInstance( 320 Ci.nsISupportsPRBool 321 ); 322 Services.obs.notifyObservers(quit, "quit-application-requested"); 323 324 const shouldProceed = !quit.data; 325 if (shouldProceed) { 326 Services.startup.quit(Ci.nsIAppStartup.eForceQuit); 327 } 328 }