summaryrefslogtreecommitdiffstats
path: root/devtools/server/startup/frame.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/startup/frame.js')
-rw-r--r--devtools/server/startup/frame.js193
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`);
+}