/* 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"; /* global content, addEventListener, addMessageListener, removeMessageListener, sendAsyncMessage */ /* * Frame script that listens for requests to start a `DevToolsServer` for a frame in a * content process. Loaded into content process frames by the main process during * frame-connector.js' connectToFrame. */ try { var chromeGlobal = this; // Encapsulate in its own scope to allows loading this frame script more than once. (function() { // In most cases, we are debugging a tab in content process, without chrome // privileges. But in some tests, we are attaching to privileged document. // Because the debugger can't be running in the same compartment than its debuggee, // we have to load the server in a dedicated Loader, flagged with // invisibleToDebugger, which will force it to be loaded in another compartment. let loader, customLoader = false; if (content.document.nodePrincipal.isSystemPrincipal) { const { DevToolsLoader } = ChromeUtils.import( "resource://devtools/shared/Loader.jsm" ); loader = new DevToolsLoader({ invisibleToDebugger: true, }); customLoader = true; } else { // Otherwise, use the shared loader. loader = ChromeUtils.import("resource://devtools/shared/Loader.jsm"); } const { require } = loader; const DevToolsUtils = require("devtools/shared/DevToolsUtils"); const { DevToolsServer } = require("devtools/server/devtools-server"); DevToolsServer.init(); // We want a special server without any root actor and only target-scoped actors. // We are going to spawn a FrameTargetActor instance in the next few lines, // it is going to act like a root actor without being one. DevToolsServer.registerActors({ target: true }); const connections = new Map(); const onConnect = DevToolsUtils.makeInfallible(function(msg) { removeMessageListener("debug:connect", onConnect); const mm = msg.target; const prefix = msg.data.prefix; const addonId = msg.data.addonId; const conn = DevToolsServer.connectToParent(prefix, mm); conn.parentMessageManager = mm; connections.set(prefix, conn); let actor; if (addonId) { const { WebExtensionTargetActor, } = require("devtools/server/actors/targets/webextension"); actor = new WebExtensionTargetActor( conn, chromeGlobal, prefix, addonId ); } else { const { FrameTargetActor, } = require("devtools/server/actors/targets/frame"); const { docShell } = chromeGlobal; // For a script loaded via loadFrameScript, the global is the content // message manager. actor = new FrameTargetActor(conn, docShell); } actor.manage(actor); sendAsyncMessage("debug:actor", { actor: actor.form(), prefix: prefix }); }); addMessageListener("debug:connect", onConnect); const onDisconnect = DevToolsUtils.makeInfallible(function(msg) { const prefix = msg.data.prefix; const conn = connections.get(prefix); if (!conn) { // Several copies of this frame script can be running for a single frame since it // is loaded once for each DevTools connection to the frame. If this disconnect // request doesn't match a connection known here, ignore it. return; } removeMessageListener("debug:disconnect", onDisconnect); // Call DevToolsServerConnection.close to destroy all child actors. It should end up // calling DevToolsServerConnection.onClosed that would actually cleanup all actor // pools. conn.close(); connections.delete(prefix); }); addMessageListener("debug:disconnect", onDisconnect); // In non-e10s mode, the "debug:disconnect" message isn't always received before the // messageManager connection goes away. Watching for "unload" here ensures we close // any connections when the frame is unloaded. addEventListener("unload", () => { for (const conn of connections.values()) { conn.close(); } connections.clear(); }); // Destroy the server once its last connection closes. Note that multiple frame // scripts may be running in parallel and reuse the same server. function destroyServer() { // Only destroy the server if there is no more connections to it. It may be used // to debug another tab running in the same process. if (DevToolsServer.hasConnection() || DevToolsServer.keepAlive) { return; } DevToolsServer.off("connectionchange", destroyServer); DevToolsServer.destroy(); // When debugging chrome pages, we initialized a dedicated loader, also destroy it if (customLoader) { loader.destroy(); } } DevToolsServer.on("connectionchange", destroyServer); })(); } catch (e) { dump(`Exception in DevTools frame startup: ${e}\n`); }