/* 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/. */ /* eslint-env mozilla/frame-script */ "use strict"; /* global addEventListener */ /* * 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 { useDistinctSystemPrincipalLoader } = ChromeUtils.importESModule( "resource://devtools/shared/loader/DistinctSystemPrincipalLoader.sys.mjs" ); loader = useDistinctSystemPrincipalLoader(chromeGlobal); customLoader = true; } else { // Otherwise, use the shared loader. loader = ChromeUtils.importESModule( "resource://devtools/shared/loader/Loader.sys.mjs" ); } const { require } = loader; const DevToolsUtils = require("resource://devtools/shared/DevToolsUtils.js"); const { DevToolsServer, } = require("resource://devtools/server/devtools-server.js"); DevToolsServer.init(); // We want a special server without any root actor and only target-scoped actors. // We are going to spawn a WindowGlobalTargetActor 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) { const mm = msg.target; const prefix = msg.data.prefix; const addonId = msg.data.addonId; const addonBrowsingContextGroupId = msg.data.addonBrowsingContextGroupId; // If we try to create several frame targets simultaneously, the frame script will be loaded several times. // In this case a single "debug:connect" message might be received by all the already loaded frame scripts. // Check if the DevToolsServer already knows the provided connection prefix, // because it means that another framescript instance already handled this message. // Another "debug:connect" message is guaranteed to be emitted for another prefix, // so we keep the message listener and wait for this next message. if (DevToolsServer.hasConnectionForPrefix(prefix)) { return; } removeMessageListener("debug:connect", onConnect); const conn = DevToolsServer.connectToParent(prefix, mm); connections.set(prefix, conn); let actor; if (addonId) { const { WebExtensionTargetActor, } = require("resource://devtools/server/actors/targets/webextension.js"); const { createWebExtensionSessionContext, } = require("resource://devtools/server/actors/watcher/session-context.js"); const { browsingContext } = docShell; actor = new WebExtensionTargetActor(conn, { addonId, addonBrowsingContextGroupId, chromeGlobal, isTopLevelTarget: true, prefix, sessionContext: createWebExtensionSessionContext( { addonId, browsingContextID: browsingContext.id, innerWindowId: browsingContext.currentWindowContext.innerWindowId, }, { isServerTargetSwitchingEnabled: msg.data.isServerTargetSwitchingEnabled, } ), }); } else { const { WindowGlobalTargetActor, } = require("resource://devtools/server/actors/targets/window-global.js"); const { createBrowserElementSessionContext, } = require("resource://devtools/server/actors/watcher/session-context.js"); const { docShell } = chromeGlobal; // For a script loaded via loadFrameScript, the global is the content // message manager. // All WindowGlobalTarget actors created via the framescript are top-level // targets. Non top-level WindowGlobalTarget actors are all created by the // DevToolsFrameChild actor. // // createBrowserElementSessionContext only reads browserId attribute const fakeBrowserElement = { browserId: docShell.browsingContext.browserId, }; actor = new WindowGlobalTargetActor(conn, { docShell, isTopLevelTarget: true, // This is only used when server target switching is off and we create // the target from TabDescriptor. So all config attributes are false. sessionContext: createBrowserElementSessionContext( fakeBrowserElement, {} ), }); } actor.manage(actor); sendAsyncMessage("debug:actor", { actor: actor.form(), 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.onTransportClosed 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 destroyLoader() { // 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", destroyLoader); // When debugging chrome pages, we initialized a dedicated loader, also destroy it if (customLoader) { const { releaseDistinctSystemPrincipalLoader } = ChromeUtils.importESModule( "resource://devtools/shared/loader/DistinctSystemPrincipalLoader.sys.mjs" ); releaseDistinctSystemPrincipalLoader(chromeGlobal); } } DevToolsServer.on("connectionchange", destroyLoader); })(); } catch (e) { dump(`Exception in DevTools frame startup: ${e}\n`); }