gecko-dev/devtools/client/framework/toolbox-process-window.js

264 строки
8.6 KiB
JavaScript

/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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.import("resource://devtools/shared/Loader.jsm", {});
// Require this module to setup core modules
loader.require("devtools/client/framework/devtools-browser");
var { gDevTools } = require("devtools/client/framework/devtools");
var { TargetFactory } = require("devtools/client/framework/target");
var { Toolbox } = require("devtools/client/framework/toolbox");
var Services = require("Services");
var { DebuggerClient } = require("devtools/shared/client/debugger-client");
var { PrefsHelper } = require("devtools/client/shared/prefs");
// 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 gToolbox, gClient;
function appendStatusMessage(msg) {
let statusMessage = document.getElementById("status-message");
statusMessage.value += msg + "\n";
if (msg.stack) {
statusMessage.value += msg.stack + "\n";
}
}
function toggleStatusMessage(visible = true) {
let statusMessageContainer = document.getElementById("status-message-container");
statusMessageContainer.hidden = !visible;
}
function revealStatusMessage() {
toggleStatusMessage(true);
}
function hideStatusMessage() {
toggleStatusMessage(false);
}
var connect = async function() {
// Initiate the connection
let env = Cc["@mozilla.org/process/environment;1"]
.getService(Ci.nsIEnvironment);
let port = env.get("MOZ_BROWSER_TOOLBOX_PORT");
let addonID = env.get("MOZ_BROWSER_TOOLBOX_ADDONID");
// A port needs to be passed in from the environment, for instance:
// MOZ_BROWSER_TOOLBOX_PORT=6080 ./mach run -chrome \
// chrome://devtools/content/framework/toolbox-process-window.xul
if (!port) {
throw new Error("Must pass a port in an env variable with MOZ_BROWSER_TOOLBOX_PORT");
}
let host = Prefs.chromeDebuggingHost;
let webSocket = Prefs.chromeDebuggingWebSocket;
appendStatusMessage(`Connecting to ${host}:${port}, ws: ${webSocket}`);
let transport = await DebuggerClient.socketConnect({
host,
port,
webSocket,
});
gClient = new DebuggerClient(transport);
appendStatusMessage("Start protocol client for connection");
await gClient.connect();
appendStatusMessage("Get root form for toolbox");
if (addonID) {
let { addons } = await gClient.listAddons();
let addonActor = addons.filter(addon => addon.id === addonID).pop();
let isTabActor = addonActor.isWebExtension;
await openToolbox({form: addonActor, chrome: true, isTabActor});
} else {
let response = await gClient.getProcess();
await openToolbox({form: response.form, chrome: true});
}
};
// 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.performance.ui.show-platform-data", true);
Services.prefs.setBoolPref("devtools.inspector.showAllAnonymousContent", true);
Services.prefs.setBoolPref("browser.dom.window.dump.enabled", true);
Services.prefs.setBoolPref("devtools.command-button-noautohide.enabled", true);
Services.prefs.setBoolPref("devtools.scratchpad.enabled", true);
// Bug 1225160 - Using source maps with browser debugging can lead to a crash
Services.prefs.setBoolPref("devtools.debugger.source-maps-enabled", false);
Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", true);
Services.prefs.setBoolPref("devtools.preference.new-panel-enabled", false);
Services.prefs.setBoolPref("layout.css.emulate-moz-box-with-flex", false);
}
window.addEventListener("load", async function() {
let cmdClose = document.getElementById("toolbox-cmd-close");
cmdClose.addEventListener("command", onCloseCommand);
setPrefDefaults();
// Reveal status message if connecting is slow or if an error occurs.
let 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();
}
async function openToolbox({ form, chrome, isTabActor }) {
let options = {
form: form,
client: gClient,
chrome: chrome,
isTabActor: isTabActor
};
appendStatusMessage(`Create toolbox target: ${JSON.stringify(arguments, null, 2)}`);
let target = await TargetFactory.forRemoteTab(options);
let frame = document.getElementById("toolbox-iframe");
// Remember the last panel that was used inside of this profile.
// But if we are testing, then it should always open the debugger panel.
let selectedTool =
Services.prefs.getCharPref("devtools.browsertoolbox.panel",
Services.prefs.getCharPref("devtools.toolbox.selectedTool",
"jsdebugger"));
options = { customIframe: frame };
appendStatusMessage(`Show toolbox with ${selectedTool} selected`);
let toolbox = await gDevTools.showToolbox(
target,
selectedTool,
Toolbox.HostType.CUSTOM,
options
);
onNewToolbox(toolbox);
}
function onNewToolbox(toolbox) {
gToolbox = toolbox;
bindToolboxHandlers();
raise();
let env = Cc["@mozilla.org/process/environment;1"]
.getService(Ci.nsIEnvironment);
let testScript = env.get("MOZ_TOOLBOX_TEST_SCRIPT");
if (testScript) {
// Only allow executing random chrome scripts when a special
// test-only pref is set
let prefName = "devtools.browser-toolbox.allow-unsafe-script";
if (Services.prefs.getPrefType(prefName) == Services.prefs.PREF_BOOL &&
Services.prefs.getBoolPref(prefName) === true) {
evaluateTestScript(testScript, toolbox);
}
}
}
function evaluateTestScript(script, toolbox) {
let sandbox = Cu.Sandbox(window);
sandbox.window = window;
sandbox.toolbox = toolbox;
sandbox.ChromeUtils = ChromeUtils;
Cu.evalInSandbox(script, sandbox);
}
async function bindToolboxHandlers() {
gToolbox.once("destroyed", quitApp);
window.addEventListener("unload", onUnload);
if (Services.appinfo.OS == "Darwin") {
// Badge the dock icon to differentiate this process from the main application
// process.
updateBadgeText(false);
// Once the debugger panel opens listen for thread pause / resume.
let panel = await gToolbox.getPanelWhenReady("jsdebugger");
setupThreadListeners(panel);
}
}
function setupThreadListeners(panel) {
updateBadgeText(panel.isPaused());
let onPaused = updateBadgeText.bind(null, true);
let onResumed = updateBadgeText.bind(null, false);
gToolbox.target.on("thread-paused", onPaused);
gToolbox.target.on("thread-resumed", onResumed);
panel.once("destroyed", () => {
gToolbox.target.off("thread-paused", onPaused);
gToolbox.target.off("thread-resumed", onResumed);
});
}
function updateBadgeText(paused) {
let dockSupport = Cc["@mozilla.org/widget/macdocksupport;1"]
.getService(Ci.nsIMacDockSupport);
dockSupport.badgeText = paused ? "▐▐ " : " ▶";
}
function onUnload() {
window.removeEventListener("unload", onUnload);
window.removeEventListener("message", onMessage);
let cmdClose = document.getElementById("toolbox-cmd-close");
cmdClose.removeEventListener("command", onCloseCommand);
gToolbox.destroy();
}
function onMessage(event) {
try {
let json = JSON.parse(event.data);
switch (json.name) {
case "toolbox-raise":
raise();
break;
case "toolbox-title":
setTitle(json.data.value);
break;
}
} catch (e) {
console.error(e);
}
}
window.addEventListener("message", onMessage);
function raise() {
window.focus();
}
function setTitle(title) {
document.title = title;
}
function quitApp() {
let quit = Cc["@mozilla.org/supports-PRBool;1"]
.createInstance(Ci.nsISupportsPRBool);
Services.obs.notifyObservers(quit, "quit-application-requested");
let shouldProceed = !quit.data;
if (shouldProceed) {
Services.startup.quit(Ci.nsIAppStartup.eForceQuit);
}
}