diff options
Diffstat (limited to 'devtools/client/framework/browser-toolbox/window.js')
-rw-r--r-- | devtools/client/framework/browser-toolbox/window.js | 336 |
1 files changed, 336 insertions, 0 deletions
diff --git a/devtools/client/framework/browser-toolbox/window.js b/devtools/client/framework/browser-toolbox/window.js new file mode 100644 index 0000000000..e84ef02829 --- /dev/null +++ b/devtools/client/framework/browser-toolbox/window.js @@ -0,0 +1,336 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var { loader, require } = ChromeUtils.importESModule( + "resource://devtools/shared/loader/Loader.sys.mjs" +); + +var { useDistinctSystemPrincipalLoader, releaseDistinctSystemPrincipalLoader } = + ChromeUtils.importESModule( + "resource://devtools/shared/loader/DistinctSystemPrincipalLoader.sys.mjs" + ); + +// Require this module to setup core modules +loader.require("resource://devtools/client/framework/devtools-browser.js"); + +var { gDevTools } = require("resource://devtools/client/framework/devtools.js"); +var { Toolbox } = require("resource://devtools/client/framework/toolbox.js"); +var { + DevToolsClient, +} = require("resource://devtools/client/devtools-client.js"); +var { PrefsHelper } = require("resource://devtools/client/shared/prefs.js"); +const KeyShortcuts = require("resource://devtools/client/shared/key-shortcuts.js"); +const { LocalizationHelper } = require("resource://devtools/shared/l10n.js"); +const L10N = new LocalizationHelper( + "devtools/client/locales/toolbox.properties" +); + +const lazy = {}; +ChromeUtils.defineESModuleGetters(lazy, { + BrowserToolboxLauncher: + "resource://devtools/client/framework/browser-toolbox/Launcher.sys.mjs", +}); + +const { + CommandsFactory, +} = require("resource://devtools/shared/commands/commands-factory.js"); + +// Timeout to wait before we assume that a connect() timed out without an error. +// In milliseconds. (With the Debugger pane open, this has been reported to last +// more than 10 seconds!) +const STATUS_REVEAL_TIME = 15000; + +/** + * Shortcuts for accessing various debugger preferences. + */ +var Prefs = new PrefsHelper("devtools.debugger", { + chromeDebuggingHost: ["Char", "chrome-debugging-host"], + chromeDebuggingWebSocket: ["Bool", "chrome-debugging-websocket"], +}); + +var gCommands, gToolbox, gShortcuts; + +function appendStatusMessage(msg) { + const statusMessage = document.getElementById("status-message"); + statusMessage.textContent += msg + "\n"; + if (msg.stack) { + statusMessage.textContent += msg.stack + "\n"; + } +} + +function toggleStatusMessage(visible = true) { + document.getElementById("status-message-container").hidden = !visible; +} + +function revealStatusMessage() { + toggleStatusMessage(true); +} + +function hideStatusMessage() { + toggleStatusMessage(false); +} + +var connect = async function () { + // Initiate the connection + + // MOZ_BROWSER_TOOLBOX_INPUT_CONTEXT is set by the target Firefox instance + // before opening the Browser Toolbox. + // If "devtools.webconsole.input.context" is true, the variable is set to "1", + // otherwise it is set to "0". + Services.prefs.setBoolPref( + "devtools.webconsole.input.context", + Services.env.get("MOZ_BROWSER_TOOLBOX_INPUT_CONTEXT") === "1" + ); + // Similar, but for the Browser Toolbox mode + if (Services.env.get("MOZ_BROWSER_TOOLBOX_FORCE_MULTIPROCESS") === "1") { + Services.prefs.setCharPref("devtools.browsertoolbox.scope", "everything"); + } + + const port = Services.env.get("MOZ_BROWSER_TOOLBOX_PORT"); + + // A port needs to be passed in from the environment, for instance: + // MOZ_BROWSER_TOOLBOX_PORT=6080 ./mach run -chrome \ + // chrome://devtools/content/framework/browser-toolbox/window.html + if (!port) { + throw new Error( + "Must pass a port in an env variable with MOZ_BROWSER_TOOLBOX_PORT" + ); + } + + const host = Prefs.chromeDebuggingHost; + const webSocket = Prefs.chromeDebuggingWebSocket; + appendStatusMessage(`Connecting to ${host}:${port}, ws: ${webSocket}`); + const transport = await DevToolsClient.socketConnect({ + host, + port, + webSocket, + }); + const client = new DevToolsClient(transport); + appendStatusMessage("Start protocol client for connection"); + await client.connect(); + + appendStatusMessage("Get root form for toolbox"); + gCommands = await CommandsFactory.forMainProcess({ client }); + + // Bug 1794607: for some unexpected reason, closing the DevToolsClient + // when the commands is destroyed by the toolbox would introduce leaks + // when running the browser-toolbox mochitests. + gCommands.shouldCloseClient = false; + + await openToolbox(gCommands); +}; + +// Certain options should be toggled since we can assume chrome debugging here +function setPrefDefaults() { + Services.prefs.setBoolPref("devtools.inspector.showUserAgentStyles", true); + Services.prefs.setBoolPref( + "devtools.inspector.showAllAnonymousContent", + true + ); + Services.prefs.setBoolPref("browser.dom.window.dump.enabled", true); + Services.prefs.setBoolPref("devtools.console.stdout.chrome", true); + Services.prefs.setBoolPref( + "devtools.command-button-noautohide.enabled", + true + ); + + // We force enabling the performance panel in the browser toolbox. + Services.prefs.setBoolPref("devtools.performance.enabled", true); + + // Bug 1773226: Try to avoid session restore to reopen a transient browser window + // if we ever opened a URL from the browser toolbox. (but it doesn't seem to be enough) + Services.prefs.setBoolPref("browser.sessionstore.resume_from_crash", false); + + // Disable Safe mode as the browser toolbox is often closed brutaly by subprocess + // and the safe mode kicks in when reopening it + Services.prefs.setIntPref("toolkit.startup.max_resumed_crashes", -1); +} + +window.addEventListener( + "load", + async function () { + gShortcuts = new KeyShortcuts({ window }); + gShortcuts.on("CmdOrCtrl+W", onCloseCommand); + gShortcuts.on("CmdOrCtrl+Alt+Shift+I", onDebugBrowserToolbox); + gShortcuts.on("CmdOrCtrl+Alt+R", onReloadBrowser); + + const statusMessageContainer = document.getElementById( + "status-message-title" + ); + statusMessageContainer.textContent = L10N.getStr( + "browserToolbox.statusMessage" + ); + + setPrefDefaults(); + + // Reveal status message if connecting is slow or if an error occurs. + const delayedStatusReveal = setTimeout( + revealStatusMessage, + STATUS_REVEAL_TIME + ); + try { + await connect(); + clearTimeout(delayedStatusReveal); + hideStatusMessage(); + } catch (e) { + clearTimeout(delayedStatusReveal); + appendStatusMessage(e); + revealStatusMessage(); + console.error(e); + } + }, + { once: true } +); + +function onCloseCommand(event) { + window.close(); +} + +/** + * Open a Browser toolbox debugging the current browser toolbox + * + * This helps debugging the browser toolbox code, especially the code + * running in the parent process. i.e. frontend code. + */ +function onDebugBrowserToolbox() { + lazy.BrowserToolboxLauncher.init(); +} + +/** + * Replicate the local-build-only key shortcut to reload the browser + */ +function onReloadBrowser() { + gToolbox.commands.targetCommand.reloadTopLevelTarget(); +} + +async function openToolbox(commands) { + const form = commands.descriptorFront._form; + appendStatusMessage( + `Create toolbox for target descriptor: ${JSON.stringify({ form }, null, 2)}` + ); + + // Remember the last panel that was used inside of this profile. + // But if we are testing, then it should always open the debugger panel. + const selectedTool = Services.prefs.getCharPref( + "devtools.browsertoolbox.panel", + Services.prefs.getCharPref("devtools.toolbox.selectedTool", "jsdebugger") + ); + + const toolboxOptions = { doc: document }; + appendStatusMessage(`Show toolbox with ${selectedTool} selected`); + + gToolbox = await gDevTools.showToolbox(commands, { + toolId: selectedTool, + hostType: Toolbox.HostType.BROWSERTOOLBOX, + hostOptions: toolboxOptions, + }); + + bindToolboxHandlers(); + + // Enable some testing features if the browser toolbox test pref is set. + if ( + Services.prefs.getBoolPref( + "devtools.browsertoolbox.enable-test-server", + false + ) + ) { + // setup a server so that the test can evaluate messages in this process. + installTestingServer(); + } + + await gToolbox.raise(); + + // Warn the user if we started recording this browser toolbox via MOZ_BROWSER_TOOLBOX_PROFILER_STARTUP=1 + if (Services.env.get("MOZ_PROFILER_STARTUP") === "1") { + const notificationBox = gToolbox.getNotificationBox(); + const text = + "The profiler started recording this toolbox, open another browser toolbox to open the profile via the performance panel"; + notificationBox.appendNotification( + text, + null, + null, + notificationBox.PRIORITY_INFO_HIGH + ); + } +} + +let releaseTestLoader = null; +function installTestingServer() { + // Install a DevToolsServer in this process and inform the server of its + // location. Tests operating on the browser toolbox run in the server + // (the firefox parent process) and can connect to this new server using + // initBrowserToolboxTask(), allowing them to evaluate scripts here. + + const requester = {}; + const testLoader = useDistinctSystemPrincipalLoader(requester); + releaseTestLoader = () => releaseDistinctSystemPrincipalLoader(requester); + const { DevToolsServer } = testLoader.require( + "resource://devtools/server/devtools-server.js" + ); + const { SocketListener } = testLoader.require( + "resource://devtools/shared/security/socket.js" + ); + + DevToolsServer.init(); + DevToolsServer.registerAllActors(); + DevToolsServer.allowChromeProcess = true; + + // Force this server to be kept alive until the browser toolbox process is closed. + // For some reason intermittents appears on Windows when destroying the server + // once the last connection drops. + DevToolsServer.keepAlive = true; + + // Use a fixed port which initBrowserToolboxTask can look for. + const socketOptions = { portOrPath: 6001 }; + const listener = new SocketListener(DevToolsServer, socketOptions); + listener.open(); +} + +async function bindToolboxHandlers() { + gToolbox.once("destroyed", quitApp); + window.addEventListener("unload", onUnload); + + // If the remote connection drops, firefox was closed + // In such case, force closing the browser toolbox + gCommands.client.once("closed", quitApp); + + if (Services.appinfo.OS == "Darwin") { + // Badge the dock icon to differentiate this process from the main application + // process. + updateBadgeText(false); + + gToolbox.on("toolbox-paused", () => updateBadgeText(true)); + gToolbox.on("toolbox-resumed", () => updateBadgeText(false)); + } +} + +function updateBadgeText(paused) { + const dockSupport = Cc["@mozilla.org/widget/macdocksupport;1"].getService( + Ci.nsIMacDockSupport + ); + dockSupport.badgeText = paused ? "▐▐ " : " ▶"; +} + +function onUnload() { + window.removeEventListener("unload", onUnload); + gToolbox.destroy(); + if (releaseTestLoader) { + releaseTestLoader(); + releaseTestLoader = null; + } +} + +function quitApp() { + const quit = Cc["@mozilla.org/supports-PRBool;1"].createInstance( + Ci.nsISupportsPRBool + ); + Services.obs.notifyObservers(quit, "quit-application-requested"); + + const shouldProceed = !quit.data; + if (shouldProceed) { + Services.startup.quit(Ci.nsIAppStartup.eForceQuit); + } +} |