detached e82fcb3

fix(cef): add DevTools, popup→new pane, and zoom debug logging

- OpenDevTools: implement port.DevToolsOpener via host.ShowDevTools()
- OnBeforePopup: fire OnCreate callback to open target="_blank" and
  window.open() links in a new stacked pane (matches WebKit behavior)
- SetZoomLevel: add debug log with factor and cef_level for diagnostics
commit
e82fcb325c90a9923f1d5aa01c561125642908af
author
brice · · 2026-03-21 06:59 UTC
committer
brice · · 2026-03-28 04:37 UTC
parent
60f4f26
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +54 -5
M internal/infrastructure/cef/webview.go +28 -1
@@ -13,12 +13,14 @@ import (
1313 "github.com/bnema/puregotk/v4/glib"
1414
1515 "github.com/bnema/dumber/internal/application/port"
16+ "github.com/bnema/dumber/internal/logging"
1617 )
1718
1819 // Compile-time interface checks.
1920 var (
2021 _ port.WebView = (*WebView)(nil)
2122 _ port.NativeWidgetProvider = (*WebView)(nil)
23+ _ port.DevToolsOpener = (*WebView)(nil)
2224 )
2325
2426 // errDestroyed is returned when an operation is attempted on a destroyed WebView.
@@ -324,11 +326,36 @@ func (wv *WebView) SetZoomLevel(_ context.Context, factor float64) error {
324326 if host == nil {
325327 return errNoBrowser
326328 }
327- host.SetZoomLevel(cefZoomFromFactor(factor))
329+ cefLevel := cefZoomFromFactor(factor)
330+ logging.FromContext(wv.ctx).Debug().
331+ Float64("factor", factor).
332+ Float64("cef_level", cefLevel).
333+ Msg("cef: SetZoomLevel")
334+ host.SetZoomLevel(cefLevel)
328335 wv.zoomFactor.Store(factor)
329336 return nil
330337 }
331338
339+// ---------------------------------------------------------------------------
340+// DevTools
341+// ---------------------------------------------------------------------------
342+
343+// OpenDevTools opens the Chromium DevTools in a separate window.
344+func (wv *WebView) OpenDevTools() {
345+ if wv.destroyed.Load() {
346+ return
347+ }
348+ wv.mu.RLock()
349+ host := wv.host
350+ wv.mu.RUnlock()
351+ if host == nil {
352+ return
353+ }
354+ windowInfo := purecef.DefaultWindowInfo()
355+ settings := purecef.DefaultBrowserSettings()
356+ host.ShowDevTools(&windowInfo, nil, &settings, nil)
357+}
358+
332359 // ---------------------------------------------------------------------------
333360 // Find
334361 // ---------------------------------------------------------------------------
@@ -13,12 +13,14 @@ import (
13 "github.com/bnema/puregotk/v4/glib"13 "github.com/bnema/puregotk/v4/glib"
14 14
15 "github.com/bnema/dumber/internal/application/port"15 "github.com/bnema/dumber/internal/application/port"
16+ "github.com/bnema/dumber/internal/logging"
16 )17 )
17 18
18 // Compile-time interface checks.19 // Compile-time interface checks.
19 var (20 var (
20 _ port.WebView = (*WebView)(nil)21 _ port.WebView = (*WebView)(nil)
21 _ port.NativeWidgetProvider = (*WebView)(nil)22 _ port.NativeWidgetProvider = (*WebView)(nil)
23+ _ port.DevToolsOpener = (*WebView)(nil)
22 )24 )
23 25
24 // errDestroyed is returned when an operation is attempted on a destroyed WebView.26 // errDestroyed is returned when an operation is attempted on a destroyed WebView.
@@ -324,11 +326,36 @@ func (wv *WebView) SetZoomLevel(_ context.Context, factor float64) error {
324 if host == nil {326 if host == nil {
325 return errNoBrowser327 return errNoBrowser
326 }328 }
327- host.SetZoomLevel(cefZoomFromFactor(factor))329+ cefLevel := cefZoomFromFactor(factor)
330+ logging.FromContext(wv.ctx).Debug().
331+ Float64("factor", factor).
332+ Float64("cef_level", cefLevel).
333+ Msg("cef: SetZoomLevel")
334+ host.SetZoomLevel(cefLevel)
328 wv.zoomFactor.Store(factor)335 wv.zoomFactor.Store(factor)
329 return nil336 return nil
330 }337 }
331 338
339+// ---------------------------------------------------------------------------
340+// DevTools
341+// ---------------------------------------------------------------------------
342+
343+// OpenDevTools opens the Chromium DevTools in a separate window.
344+func (wv *WebView) OpenDevTools() {
345+ if wv.destroyed.Load() {
346+ return
347+ }
348+ wv.mu.RLock()
349+ host := wv.host
350+ wv.mu.RUnlock()
351+ if host == nil {
352+ return
353+ }
354+ windowInfo := purecef.DefaultWindowInfo()
355+ settings := purecef.DefaultBrowserSettings()
356+ host.ShowDevTools(&windowInfo, nil, &settings, nil)
357+}
358+
332 // ---------------------------------------------------------------------------359 // ---------------------------------------------------------------------------
333 // Find360 // Find
334 // ---------------------------------------------------------------------------361 // ---------------------------------------------------------------------------
M internal/infrastructure/cef/webview_handlers.go +26 -4
@@ -331,16 +331,38 @@ func (h *handlerSet) OnLoadError(_ purecef.Browser, _ purecef.Frame, _ purecef.E
331331 // LifeSpanHandler (6 methods)
332332 // ===========================================================================
333333
334-// OnBeforePopup blocks all popups.
334+// OnBeforePopup intercepts popup requests (target="_blank", window.open).
335+// CEF OSR cannot create popup windows, so we fire the OnCreate callback
336+// to let the coordinator open the link in a new stacked pane.
335337 //
336338 //nolint:gocritic // signature imposed by purecef.LifeSpanHandler interface
337339 func (h *handlerSet) OnBeforePopup(
338- _ purecef.Browser, _ purecef.Frame, _ int32, _, _ string,
339- _ purecef.WindowOpenDisposition, _ int32, _ *purecef.PopupFeatures,
340+ _ purecef.Browser, _ purecef.Frame, _ int32, targetURL, targetFrameName string,
341+ _ purecef.WindowOpenDisposition, userGesture int32, _ *purecef.PopupFeatures,
340342 _ *purecef.WindowInfo, _ *purecef.Client, _ *purecef.BrowserSettings,
341343 _ *purecef.DictionaryValue, _ *bool,
342344 ) bool {
343- return true // block
345+ if targetURL == "" {
346+ return true
347+ }
348+
349+ h.wv.mu.RLock()
350+ cb := h.wv.callbacks
351+ h.wv.mu.RUnlock()
352+
353+ if cb != nil && cb.OnCreate != nil {
354+ req := port.PopupRequest{
355+ TargetURI: targetURL,
356+ FrameName: targetFrameName,
357+ IsUserGesture: userGesture != 0,
358+ ParentViewID: h.wv.id,
359+ }
360+ h.wv.runOnGTK(func() {
361+ cb.OnCreate(req)
362+ })
363+ }
364+
365+ return true // always block CEF popup; the coordinator handles the new pane
344366 }
345367
346368 func (h *handlerSet) OnBeforePopupAborted(_ purecef.Browser, _ int32) {}
@@ -331,16 +331,38 @@ func (h *handlerSet) OnLoadError(_ purecef.Browser, _ purecef.Frame, _ purecef.E
331 // LifeSpanHandler (6 methods)331 // LifeSpanHandler (6 methods)
332 // ===========================================================================332 // ===========================================================================
333 333
334-// OnBeforePopup blocks all popups.334+// OnBeforePopup intercepts popup requests (target="_blank", window.open).
335+// CEF OSR cannot create popup windows, so we fire the OnCreate callback
336+// to let the coordinator open the link in a new stacked pane.
335 //337 //
336 //nolint:gocritic // signature imposed by purecef.LifeSpanHandler interface338 //nolint:gocritic // signature imposed by purecef.LifeSpanHandler interface
337 func (h *handlerSet) OnBeforePopup(339 func (h *handlerSet) OnBeforePopup(
338- _ purecef.Browser, _ purecef.Frame, _ int32, _, _ string,340+ _ purecef.Browser, _ purecef.Frame, _ int32, targetURL, targetFrameName string,
339- _ purecef.WindowOpenDisposition, _ int32, _ *purecef.PopupFeatures,341+ _ purecef.WindowOpenDisposition, userGesture int32, _ *purecef.PopupFeatures,
340 _ *purecef.WindowInfo, _ *purecef.Client, _ *purecef.BrowserSettings,342 _ *purecef.WindowInfo, _ *purecef.Client, _ *purecef.BrowserSettings,
341 _ *purecef.DictionaryValue, _ *bool,343 _ *purecef.DictionaryValue, _ *bool,
342 ) bool {344 ) bool {
343- return true // block345+ if targetURL == "" {
346+ return true
347+ }
348+
349+ h.wv.mu.RLock()
350+ cb := h.wv.callbacks
351+ h.wv.mu.RUnlock()
352+
353+ if cb != nil && cb.OnCreate != nil {
354+ req := port.PopupRequest{
355+ TargetURI: targetURL,
356+ FrameName: targetFrameName,
357+ IsUserGesture: userGesture != 0,
358+ ParentViewID: h.wv.id,
359+ }
360+ h.wv.runOnGTK(func() {
361+ cb.OnCreate(req)
362+ })
363+ }
364+
365+ return true // always block CEF popup; the coordinator handles the new pane
344 }366 }
345 367
346 func (h *handlerSet) OnBeforePopupAborted(_ purecef.Browser, _ int32) {}368 func (h *handlerSet) OnBeforePopupAborted(_ purecef.Browser, _ int32) {}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close