feat: Focus DevTools when breakpoint is triggered (#48701)

`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

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Michał Pichliński <michal.pichlinski@here.io>
This commit is contained in:
trop[bot]
2025-11-10 16:41:46 -05:00
committed by GitHub
parent 6140359cd3
commit 074cedd561
4 changed files with 60 additions and 1 deletions

View File

@@ -519,6 +519,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());
}

View File

@@ -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;

View File

@@ -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();

View File

@@ -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';
@@ -995,6 +995,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', () => {