summaryrefslogtreecommitdiffstats
path: root/devtools/client/framework/browser-toolbox/window.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/framework/browser-toolbox/window.js')
-rw-r--r--devtools/client/framework/browser-toolbox/window.js336
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);
+ }
+}