зеркало из https://github.com/mozilla/gecko-dev.git
194 строки
7.6 KiB
JavaScript
194 строки
7.6 KiB
JavaScript
/* 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`);
|
|
}
|