Repository: electron/electron
Pull request: feat: `WebContents.getOrCreateDevToolsTargetId()`

Backport of #49733

See that PR for details.


Notes: Added `webContents.getOrCreateDevToolsTargetId()`.


Changed files:
filename: docs/api/web-contents.md
patch: @@ -2235,6 +2235,16 @@ Returns `string` - The identifier of a WebContents stream. This identifier can b
 with `navigator.mediaDevices.getUserMedia` using a `chromeMediaSource` of `tab`.
 The identifier is restricted to the web contents that it is registered to and is only valid for 10 seconds.
 
+#### `contents.getOrCreateDevToolsTargetId()`
+
+Returns `string` - The Chrome DevTools Protocol
+[TargetID](https://chromedevtools.github.io/devtools-protocol/tot/Target/#type-TargetID)
+associated with this WebContents. This is the reverse of
+[`webContents.fromDevToolsTargetId()`](#webcontentsfromdevtoolstargetidtargetid).
+
+> [!NOTE]
+> This method creates a new DevTools agent for this WebContents if one does not already exist.
+
 #### `contents.getOSProcessId()`
 
 Returns `Integer` - The operating system `pid` of the associated renderer

filename: shell/browser/api/electron_api_web_contents.cc
patch: @@ -50,6 +50,7 @@
 #include "content/public/browser/context_menu_params.h"
 #include "content/public/browser/desktop_media_id.h"
 #include "content/public/browser/desktop_streams_registry.h"
+#include "content/public/browser/devtools_agent_host.h"
 #include "content/public/browser/download_request_utils.h"
 #include "content/public/browser/favicon_status.h"
 #include "content/public/browser/file_select_listener.h"
@@ -2801,6 +2802,11 @@ std::string WebContents::GetMediaSourceID(
   return id;
 }
 
+std::string WebContents::GetOrCreateDevToolsTargetId() {
+  auto agent_host = content::DevToolsAgentHost::GetOrCreateFor(web_contents());
+  return agent_host->GetId();
+}
+
 bool WebContents::IsCrashed() const {
   return web_contents()->IsCrashed();
 }
@@ -4667,6 +4673,8 @@ void WebContents::FillObjectTemplate(v8::Isolate* isolate,
                  &WebContents::SetWebRTCIPHandlingPolicy)
       .SetMethod("setWebRTCUDPPortRange", &WebContents::SetWebRTCUDPPortRange)
       .SetMethod("getMediaSourceId", &WebContents::GetMediaSourceID)
+      .SetMethod("getOrCreateDevToolsTargetId",
+                 &WebContents::GetOrCreateDevToolsTargetId)
       .SetMethod("getWebRTCIPHandlingPolicy",
                  &WebContents::GetWebRTCIPHandlingPolicy)
       .SetMethod("getWebRTCUDPPortRange", &WebContents::GetWebRTCUDPPortRange)

filename: shell/browser/api/electron_api_web_contents.h
patch: @@ -230,6 +230,7 @@ class WebContents final : public ExclusiveAccessContext,
   v8::Local<v8::Value> GetWebRTCUDPPortRange(v8::Isolate* isolate) const;
   void SetWebRTCUDPPortRange(gin::Arguments* args);
   std::string GetMediaSourceID(content::WebContents* request_web_contents);
+  std::string GetOrCreateDevToolsTargetId();
   bool IsCrashed() const;
   void ForcefullyCrashRenderer();
   void SetUserAgent(const std::string& user_agent);

filename: spec/api-web-contents-spec.ts
patch: @@ -1666,6 +1666,18 @@ describe('webContents module', () => {
     });
   });
 
+  describe('getOrCreateDevToolsTargetId()', () => {
+    afterEach(closeAllWindows);
+    it('returns the devtools target id', async () => {
+      const w = new BrowserWindow({ show: false });
+      await w.loadURL('about:blank');
+      const devToolsId = w.webContents.getOrCreateDevToolsTargetId();
+      expect(devToolsId).to.be.a('string').that.is.not.empty();
+      // Verify it's the inverse of fromDevToolsTargetId
+      expect(webContents.fromDevToolsTargetId(devToolsId)).to.equal(w.webContents);
+    });
+  });
+
   describe('userAgent APIs', () => {
     afterEach(closeAllWindows);
     it('is not empty by default', () => {