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 Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +54 -5
M internal/infrastructure/cef/webview.go +28 -1
| @@ -13,12 +13,14 @@ import ( | |||
| 13 | 13 | "github.com/bnema/puregotk/v4/glib" | |
| 14 | 14 | ||
| 15 | 15 | "github.com/bnema/dumber/internal/application/port" | |
| 16 | + | "github.com/bnema/dumber/internal/logging" | |
| 16 | 17 | ) | |
| 17 | 18 | ||
| 18 | 19 | // Compile-time interface checks. | |
| 19 | 20 | var ( | |
| 20 | 21 | _ port.WebView = (*WebView)(nil) | |
| 21 | 22 | _ port.NativeWidgetProvider = (*WebView)(nil) | |
| 23 | + | _ port.DevToolsOpener = (*WebView)(nil) | |
| 22 | 24 | ) | |
| 23 | 25 | ||
| 24 | 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 | 326 | if host == nil { | |
| 325 | 327 | 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 | 335 | wv.zoomFactor.Store(factor) | |
| 329 | 336 | 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 | 360 | // Find | |
| 334 | 361 | // --------------------------------------------------------------------------- | |
| @@ -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 errNoBrowser | 327 | 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 nil | 336 | 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 | // Find | 360 | // 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 | |||
| 331 | 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 | 338 | //nolint:gocritic // signature imposed by purecef.LifeSpanHandler interface | |
| 337 | 339 | 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, | |
| 340 | 342 | _ *purecef.WindowInfo, _ *purecef.Client, _ *purecef.BrowserSettings, | |
| 341 | 343 | _ *purecef.DictionaryValue, _ *bool, | |
| 342 | 344 | ) 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 | |
| 344 | 366 | } | |
| 345 | 367 | ||
| 346 | 368 | 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 interface | 338 | //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 // 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 | |||
| 344 | } | 366 | } | ||
| 345 | 367 | ||||
| 346 | func (h *handlerSet) OnBeforePopupAborted(_ purecef.Browser, _ int32) {} | 368 | func (h *handlerSet) OnBeforePopupAborted(_ purecef.Browser, _ int32) {} | ||