Bug 1819767 - [devtools] Expose ChromeUtils.isDevToolsOpened to know if DevTools are debugging something in the current process. r=smaug,devtools-reviewers,jdescottes

This doesn't help know what particular resource DevTools is currently inspecting,
but at least it helps know if it debugs something:
* one or many BrowsingContext(s) for regular DevTools (you can use BrowsingContext.watchedByDevTools instead)
* the whole process for the Browser Console/Toolbox

Differential Revision: https://phabricator.services.mozilla.com/D173295
This commit is contained in:
Alexandre Poirot 2023-04-03 12:35:53 +00:00
parent d577cf10cc
commit 4f2561f730
5 changed files with 120 additions and 15 deletions

View File

@ -394,6 +394,12 @@ var DevToolsServer = {
connID = "server" + loader.id + ".conn" + this._nextConnID++ + ".";
}
// Notify the platform code that DevTools is running in the current process
// when we are wiring the very first connection
if (!this.hasConnection()) {
ChromeUtils.notifyDevToolsOpened();
}
const conn = new DevToolsServerConnection(
connID,
transport,
@ -425,13 +431,20 @@ var DevToolsServer = {
delete this._connections[connection.prefix];
this.emit("connectionchange", "closed", connection);
const hasConnection = this.hasConnection();
// Notify the platform code that we stopped running DevTools code in the current process
if (!hasConnection) {
ChromeUtils.notifyDevToolsClosed();
}
// If keepAlive isn't explicitely set to true, destroy the server once its
// last connection closes. Multiple JSWindowActor may use the same DevToolsServer
// and in this case, let the server destroy itself once the last connection closes.
// Otherwise we set keepAlive to true when starting a listening server, receiving
// client connections. Typically when running server on phones, or on desktop
// via `--start-debugger-server`.
if (this.hasConnection() || this.keepAlive) {
if (hasConnection || this.keepAlive) {
return;
}

View File

@ -28,6 +28,11 @@ async function testDevToolsServerInitialized() {
false,
"By default, the DevToolsServer isn't initialized not in content process"
);
await assertDevToolsOpened(
tab,
false,
"By default, the DevTools are reported as closed"
);
const commands = await CommandsFactory.forTab(tab);
@ -40,6 +45,11 @@ async function testDevToolsServerInitialized() {
false,
"Creating the commands isn't enough to initialize the DevToolsServer in content process"
);
await assertDevToolsOpened(
tab,
false,
"DevTools are still reported as closed after having created the commands"
);
await commands.targetCommand.startListening();
@ -48,6 +58,11 @@ async function testDevToolsServerInitialized() {
true,
"Initializing the TargetCommand will initialize the DevToolsServer in content process"
);
await assertDevToolsOpened(
tab,
true,
"Initializing the TargetCommand will start reporting the DevTools as opened"
);
await commands.destroy();
@ -61,6 +76,11 @@ async function testDevToolsServerInitialized() {
false,
"But destroying the commands ends up destroying the DevToolsServer in the content process"
);
await assertDevToolsOpened(
tab,
false,
"Destroying the commands will report DevTools as being closed"
);
gBrowser.removeCurrentTab();
DevToolsServer.destroy();
@ -74,11 +94,13 @@ async function testDevToolsServerKeepAlive() {
false,
"Server not started in content process"
);
await assertDevToolsOpened(tab, false, "DevTools are reported as closed");
const commands = await CommandsFactory.forTab(tab);
await commands.targetCommand.startListening();
await assertServerInitialized(tab, true, "Server started in content process");
await assertDevToolsOpened(tab, true, "DevTools are reported as opened");
info("Set DevToolsServer.keepAlive to true in the content process");
DevToolsServer.keepAlive = true;
@ -92,6 +114,11 @@ async function testDevToolsServerKeepAlive() {
true,
"Server still running in content process"
);
await assertDevToolsOpened(
tab,
false,
"DevTools are reported as close, even if the server is still running because there is no more client connected"
);
ok(
DevToolsServer.initialized,
@ -113,6 +140,11 @@ async function testDevToolsServerKeepAlive() {
false,
"Server stopped in content process"
);
await assertDevToolsOpened(
tab,
false,
"DevTools are reported as closed after destroying the second commands"
);
ok(
!DevToolsServer.initialized,
@ -124,20 +156,27 @@ async function testDevToolsServerKeepAlive() {
}
async function assertServerInitialized(tab, expected, message) {
const isInitialized = await SpecialPowers.spawn(
tab.linkedBrowser,
[],
function() {
await SpecialPowers.spawn(tab.linkedBrowser, [expected, message], function(
_expected,
_message
) {
const { require } = ChromeUtils.importESModule(
"resource://devtools/shared/loader/Loader.sys.mjs"
);
const {
DevToolsServer,
} = require("resource://devtools/server/devtools-server.js");
return DevToolsServer.initialized;
is(DevToolsServer.initialized, _expected, _message);
});
}
);
is(isInitialized, expected, message);
async function assertDevToolsOpened(tab, expected, message) {
await SpecialPowers.spawn(tab.linkedBrowser, [expected, message], function(
_expected,
_message
) {
is(ChromeUtils.isDevToolsOpened(), _expected, _message);
});
}
async function setContentServerKeepAlive(tab, keepAlive, message) {

View File

@ -1858,4 +1858,28 @@ void ChromeUtils::GetAllPossibleUtilityActorNames(GlobalObject& aGlobal,
aNames.AppendElement(WebIDLUtilityActorNameValues::GetString(idlName));
}
}
std::atomic<uint32_t> ChromeUtils::sDevToolsOpenedCount = 0;
/* static */
bool ChromeUtils::IsDevToolsOpened() {
return ChromeUtils::sDevToolsOpenedCount > 0;
}
/* static */
bool ChromeUtils::IsDevToolsOpened(GlobalObject& aGlobal) {
return ChromeUtils::IsDevToolsOpened();
}
/* static */
void ChromeUtils::NotifyDevToolsOpened(GlobalObject& aGlobal) {
ChromeUtils::sDevToolsOpenedCount++;
}
/* static */
void ChromeUtils::NotifyDevToolsClosed(GlobalObject& aGlobal) {
MOZ_ASSERT(ChromeUtils::sDevToolsOpenedCount >= 1);
ChromeUtils::sDevToolsOpenedCount--;
}
} // namespace mozilla::dom

View File

@ -65,6 +65,11 @@ class ChromeUtils {
static already_AddRefed<devtools::HeapSnapshot> ReadHeapSnapshot(
GlobalObject& global, const nsAString& filePath, ErrorResult& rv);
static bool IsDevToolsOpened();
static bool IsDevToolsOpened(GlobalObject& aGlobal);
static void NotifyDevToolsOpened(GlobalObject& aGlobal);
static void NotifyDevToolsClosed(GlobalObject& aGlobal);
static void NondeterministicGetWeakMapKeys(
GlobalObject& aGlobal, JS::Handle<JS::Value> aMap,
JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv);
@ -296,6 +301,10 @@ class ChromeUtils {
static void GetAllPossibleUtilityActorNames(GlobalObject& aGlobal,
nsTArray<nsCString>& aNames);
private:
// Number of DevTools session debugging the current process
static std::atomic<uint32_t> sDevToolsOpenedCount;
};
} // namespace dom

View File

@ -115,6 +115,26 @@ namespace ChromeUtils {
[Throws, NewObject]
HeapSnapshot readHeapSnapshot(DOMString filePath);
/**
* Efficient way to know if DevTools are active in the current process.
*
* This doesn't help know what particular context is being debugged,
* but can help strip off code entirely when DevTools aren't used at all.
*
* BrowsingContext.isWatchedByDevTools is a more precise way to know
* when one precise tab is being debugged.
*/
boolean isDevToolsOpened();
/**
* API exposed to DevTools JS code in order to know when devtools are being active in the current process.
*
* This API counts the number of calls to these methods, allowing to track many DevTools instances.
*/
undefined notifyDevToolsOpened();
undefined notifyDevToolsClosed();
/**
* Return the keys in a weak map. This operation is
* non-deterministic because it is affected by the scheduling of the