diff --git a/devtools/server/devtools-server.js b/devtools/server/devtools-server.js index 2ba4b9f35687..e1dd7994d16d 100644 --- a/devtools/server/devtools-server.js +++ b/devtools/server/devtools-server.js @@ -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; } diff --git a/devtools/server/tests/browser/browser_debugger_server.js b/devtools/server/tests/browser/browser_debugger_server.js index 0597af46012d..d54a4a8788ef 100644 --- a/devtools/server/tests/browser/browser_debugger_server.js +++ b/devtools/server/tests/browser/browser_debugger_server.js @@ -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() { - const { require } = ChromeUtils.importESModule( - "resource://devtools/shared/loader/Loader.sys.mjs" - ); - const { - DevToolsServer, - } = require("resource://devtools/server/devtools-server.js"); - return DevToolsServer.initialized; - } - ); - is(isInitialized, expected, message); + 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"); + is(DevToolsServer.initialized, _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) { diff --git a/dom/base/ChromeUtils.cpp b/dom/base/ChromeUtils.cpp index 14c8d5d123ac..2eb1bd8eb731 100644 --- a/dom/base/ChromeUtils.cpp +++ b/dom/base/ChromeUtils.cpp @@ -1858,4 +1858,28 @@ void ChromeUtils::GetAllPossibleUtilityActorNames(GlobalObject& aGlobal, aNames.AppendElement(WebIDLUtilityActorNameValues::GetString(idlName)); } } + +std::atomic 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 diff --git a/dom/base/ChromeUtils.h b/dom/base/ChromeUtils.h index 1e00fd344cba..60674a43e295 100644 --- a/dom/base/ChromeUtils.h +++ b/dom/base/ChromeUtils.h @@ -65,6 +65,11 @@ class ChromeUtils { static already_AddRefed 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 aMap, JS::MutableHandle aRetval, ErrorResult& aRv); @@ -296,6 +301,10 @@ class ChromeUtils { static void GetAllPossibleUtilityActorNames(GlobalObject& aGlobal, nsTArray& aNames); + + private: + // Number of DevTools session debugging the current process + static std::atomic sDevToolsOpenedCount; }; } // namespace dom diff --git a/dom/chrome-webidl/ChromeUtils.webidl b/dom/chrome-webidl/ChromeUtils.webidl index 472ad49852f4..4c0cf5985599 100644 --- a/dom/chrome-webidl/ChromeUtils.webidl +++ b/dom/chrome-webidl/ChromeUtils.webidl @@ -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