From 28f1cf1f1115606bf1ac960ca8cb67f8babd12b4 Mon Sep 17 00:00:00 2001 From: michal-pichlinski-openfin <97029928+michal-pichlinski-openfin@users.noreply.github.com> Date: Tue, 28 Oct 2025 13:46:33 +0100 Subject: [PATCH] feat: Focus DevTools when breakpoint is triggered (#46386) `bringToFront` DevTools message is sent when breakpoint is triggered or inspect is called and Chromium upon this message activates DevTools via `DevToolsUIBindings::Delegate::ActivateWindow`: ``` void DevToolsWindow::ActivateWindow() { if (life_stage_ != kLoadCompleted) return; \#if BUILDFLAG(IS_ANDROID) NOTIMPLEMENTED(); \#else if (is_docked_ && GetInspectedBrowserWindow()) main_web_contents_->Focus(); else if (!is_docked_ && browser_ && !browser_->window()->IsActive()) browser_->window()->Activate(); \#endif } ``` which implements: `DevToolsUIBindings::Delegate::ActivateWindow`. Electron also implements this interface in: `electron::InspectableWebContents`. However it was only setting a zoom level, therefore this commit extends it with activation of the DevTools. Only supported for DevTools manged by `electron::InspectableWebContents`. Closes: #37388 --- shell/browser/ui/inspectable_web_contents.cc | 6 +++ .../ui/inspectable_web_contents_view.cc | 17 +++++++++ .../ui/inspectable_web_contents_view.h | 1 + spec/api-web-contents-spec.ts | 37 ++++++++++++++++++- 4 files changed, 60 insertions(+), 1 deletion(-) diff --git a/shell/browser/ui/inspectable_web_contents.cc b/shell/browser/ui/inspectable_web_contents.cc index 18ae7c3f63..57e07ed09c 100644 --- a/shell/browser/ui/inspectable_web_contents.cc +++ b/shell/browser/ui/inspectable_web_contents.cc @@ -520,6 +520,12 @@ void InspectableWebContents::UpdateDevToolsZoomLevel(double level) { } void InspectableWebContents::ActivateWindow() { + if (embedder_message_dispatcher_) { + if (managed_devtools_web_contents_ && view_) { + view_->ActivateDevTools(); + } + } + // Set the zoom level. SetZoomLevelForWebContents(GetDevToolsWebContents(), GetDevToolsZoomLevel()); } diff --git a/shell/browser/ui/inspectable_web_contents_view.cc b/shell/browser/ui/inspectable_web_contents_view.cc index 09155c1ced..00fff4a082 100644 --- a/shell/browser/ui/inspectable_web_contents_view.cc +++ b/shell/browser/ui/inspectable_web_contents_view.cc @@ -132,6 +132,23 @@ void InspectableWebContentsView::ShowDevTools(bool activate) { } } +void InspectableWebContentsView::ActivateDevTools() { + if (!devtools_visible_) { + return; + } + if (devtools_window_) { + if (!devtools_window_->IsActive()) { + devtools_window_->Activate(); + } + return; + } + if (devtools_web_view_) { + if (!devtools_web_view_->HasFocus()) { + devtools_web_view_->RequestFocus(); + } + } +} + void InspectableWebContentsView::CloseDevTools() { if (!devtools_visible_) return; diff --git a/shell/browser/ui/inspectable_web_contents_view.h b/shell/browser/ui/inspectable_web_contents_view.h index 30c2fef8e8..ff2ee86365 100644 --- a/shell/browser/ui/inspectable_web_contents_view.h +++ b/shell/browser/ui/inspectable_web_contents_view.h @@ -49,6 +49,7 @@ class InspectableWebContentsView : public views::View { void SetCornerRadii(const gfx::RoundedCornersF& corner_radii); void ShowDevTools(bool activate); + void ActivateDevTools(); void CloseDevTools(); bool IsDevToolsViewShowing(); bool IsDevToolsViewFocused(); diff --git a/spec/api-web-contents-spec.ts b/spec/api-web-contents-spec.ts index e701ff98e0..50cf544736 100644 --- a/spec/api-web-contents-spec.ts +++ b/spec/api-web-contents-spec.ts @@ -1,6 +1,6 @@ import { BrowserWindow, ipcMain, webContents, session, app, BrowserView, WebContents, BaseWindow, WebContentsView } from 'electron/main'; -import { expect } from 'chai'; +import { assert, expect } from 'chai'; import * as cp from 'node:child_process'; import { once } from 'node:events'; @@ -1013,6 +1013,41 @@ describe('webContents module', () => { await devToolsClosed; expect(() => { webContents.getFocusedWebContents(); }).to.not.throw(); }); + + it('Inspect activates detached devtools window', async () => { + const window = new BrowserWindow({ show: true }); + await window.loadURL('about:blank'); + const webContentsBeforeOpenedDevtools = webContents.getAllWebContents(); + + const windowWasBlurred = once(window, 'blur'); + window.webContents.openDevTools({ mode: 'detach' }); + await windowWasBlurred; + + let devToolsWebContents = null; + for (const newWebContents of webContents.getAllWebContents()) { + const oldWebContents = webContentsBeforeOpenedDevtools.find( + oldWebContents => { + return newWebContents.id === oldWebContents.id; + }); + if (oldWebContents !== null) { + devToolsWebContents = newWebContents; + break; + } + } + assert(devToolsWebContents !== null); + + const windowFocused = once(window, 'focus'); + window.focus(); + await windowFocused; + + expect(devToolsWebContents.isFocused()).to.be.false(); + const devToolsWebContentsFocused = once(devToolsWebContents, 'focus'); + window.webContents.inspectElement(100, 100); + await devToolsWebContentsFocused; + + expect(devToolsWebContents.isFocused()).to.be.true(); + expect(window.isFocused()).to.be.false(); + }); }); describe('setDevToolsWebContents() API', () => {