diff options
Diffstat (limited to 'devtools/server/startup/frame.js')
-rw-r--r-- | devtools/server/startup/frame.js | 193 |
1 files changed, 193 insertions, 0 deletions
diff --git a/devtools/server/startup/frame.js b/devtools/server/startup/frame.js new file mode 100644 index 0000000000..db14f03c15 --- /dev/null +++ b/devtools/server/startup/frame.js @@ -0,0 +1,193 @@ +/* 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`); +} |