fix(cef): address CodeRabbit review findings across CEF engine
- Remove debug stderr prints from main() startup path - Use strings.HasPrefix for CEF subprocess detection - Consolidate duplicate resolveEngineType into config.ResolveEngineType() - Remove vestigial error return from UpdateHistoryTitle - Guard persistTitleUpdate against empty titles - Simplify factory adapter returns to direct delegation - Fix recordBrowserCreateRequest log bug (Msg method capture) - Make pumpWorkCount atomic to prevent potential races - Document os.Args mutation thread-safety in engine_init - Log warning in CreateRelated about unsupported session sharing - Add error/type logging in pool Prewarm - Use int64 intermediates in render buffer size calculation - Distinguish left/right modifier VK codes in input bridge - Close pendingURI race by holding lock through LoadURL - Simplify double lock/unlock in webview state update helpers
- commit
- 030c8ba724d89e5656aefaf4bf73c8310576d12c
- author
- brice · · 2026-03-20 15:12 UTC
- committer
- brice · · 2026-03-28 04:36 UTC
- parent
- a004a88
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
15 files · +67 -77
M cmd/dumber/main.go +3 -19
| @@ -7,6 +7,7 @@ import ( | |||
| 7 | 7 | "os" | |
| 8 | 8 | "os/signal" | |
| 9 | 9 | "runtime" | |
| 10 | + | "strings" | |
| 10 | 11 | "syscall" | |
| 11 | 12 | ||
| 12 | 13 | "github.com/bnema/dumber/internal/application/port" | |
| @@ -55,9 +56,6 @@ var restoreSessionID string | |||
| 55 | 56 | func main() { | |
| 56 | 57 | enableCrashForensics() | |
| 57 | 58 | ||
| 58 | - | // Debug: dump args at entry to identify subprocess leakage. | |
| 59 | - | fmt.Fprintf(os.Stderr, "cef-debug: main() entry, pid=%d args=%v\n", os.Getpid(), os.Args) | |
| 60 | - | ||
| 61 | 59 | // CEF subprocess handling: when CEF re-launches the binary with | |
| 62 | 60 | // --type=renderer/gpu/etc, we must call ExecuteProcess before anything | |
| 63 | 61 | // else (Cobra, config, arg stripping). We detect subprocesses by the | |
| @@ -65,9 +63,7 @@ func main() { | |||
| 65 | 63 | // var, because subprocesses may not inherit the environment and the | |
| 66 | 64 | // engine can also be selected via config. | |
| 67 | 65 | if isCEFSubprocess() { | |
| 68 | - | fmt.Fprintf(os.Stderr, "cef: detected subprocess, args=%v\n", os.Args) | |
| 69 | 66 | cef.MaybeExitSubprocess() | |
| 70 | - | fmt.Fprintf(os.Stderr, "cef: MaybeExitSubprocess returned (browser process)\n") | |
| 71 | 67 | } | |
| 72 | 68 | ||
| 73 | 69 | // Run GUI mode for browse command | |
| @@ -113,7 +109,7 @@ func runGUI() int { | |||
| 113 | 109 | } | |
| 114 | 110 | timer.MarkDuration("parallel_phase", initResult.Duration) | |
| 115 | 111 | ||
| 116 | - | if cfg.Engine.CEF.CEFMultiThreadedMessageLoop() && resolveEngineType(cfg) == "cef" { | |
| 112 | + | if cfg.Engine.CEF.CEFMultiThreadedMessageLoop() && cfg.Engine.ResolveEngineType() == "cef" { | |
| 117 | 113 | logging.FromContext(ctx).Info().Msg("pre-initializing libadwaita before CEF multi-threaded loop") | |
| 118 | 114 | ui.EnsureAdwaitaInitialized() | |
| 119 | 115 | if initResult.AdwaitaDetector != nil { | |
| @@ -252,18 +248,6 @@ func initStartupContext(cfg *config.Config) context.Context { | |||
| 252 | 248 | return ctx | |
| 253 | 249 | } | |
| 254 | 250 | ||
| 255 | - | // resolveEngineType returns the effective engine type from config + env override. | |
| 256 | - | func resolveEngineType(cfg *config.Config) string { | |
| 257 | - | engineType := cfg.Engine.Type | |
| 258 | - | if engineType == "" { | |
| 259 | - | engineType = "webkit" | |
| 260 | - | } | |
| 261 | - | if envEngine := os.Getenv("DUMBER_ENGINE"); envEngine != "" { | |
| 262 | - | engineType = envEngine | |
| 263 | - | } | |
| 264 | - | return engineType | |
| 265 | - | } | |
| 266 | - | ||
| 267 | 251 | func initStackAndRepos( | |
| 268 | 252 | ctx context.Context, | |
| 269 | 253 | cfg *config.Config, | |
| @@ -538,7 +522,7 @@ func createUseCases(repos *repositories, cfg *config.Config) *useCases { | |||
| 538 | 522 | // this process was spawned by CEF as a renderer, GPU, or utility subprocess. | |
| 539 | 523 | func isCEFSubprocess() bool { | |
| 540 | 524 | for _, arg := range os.Args { | |
| 541 | - | if len(arg) > 7 && arg[:7] == "--type=" { | |
| 525 | + | if strings.HasPrefix(arg, "--type=") { | |
| 542 | 526 | return true | |
| 543 | 527 | } | |
| 544 | 528 | } | |
| @@ -7,6 +7,7 @@ import ( | |||||
| 7 | "os" | 7 | "os" | ||
| 8 | "os/signal" | 8 | "os/signal" | ||
| 9 | "runtime" | 9 | "runtime" | ||
| 10 | + | "strings" | |||
| 10 | "syscall" | 11 | "syscall" | ||
| 11 | 12 | ||||
| 12 | "github.com/bnema/dumber/internal/application/port" | 13 | "github.com/bnema/dumber/internal/application/port" | ||
| @@ -55,9 +56,6 @@ var restoreSessionID string | |||||
| 55 | func main() { | 56 | func main() { | ||
| 56 | enableCrashForensics() | 57 | enableCrashForensics() | ||
| 57 | 58 | ||||
| 58 | - | // Debug: dump args at entry to identify subprocess leakage. | |||
| 59 | - | fmt.Fprintf(os.Stderr, "cef-debug: main() entry, pid=%d args=%v\n", os.Getpid(), os.Args) | |||
| 60 | - | ||||
| 61 | // CEF subprocess handling: when CEF re-launches the binary with | 59 | // CEF subprocess handling: when CEF re-launches the binary with | ||
| 62 | // --type=renderer/gpu/etc, we must call ExecuteProcess before anything | 60 | // --type=renderer/gpu/etc, we must call ExecuteProcess before anything | ||
| 63 | // else (Cobra, config, arg stripping). We detect subprocesses by the | 61 | // else (Cobra, config, arg stripping). We detect subprocesses by the | ||
| @@ -65,9 +63,7 @@ func main() { | |||||
| 65 | // var, because subprocesses may not inherit the environment and the | 63 | // var, because subprocesses may not inherit the environment and the | ||
| 66 | // engine can also be selected via config. | 64 | // engine can also be selected via config. | ||
| 67 | if isCEFSubprocess() { | 65 | if isCEFSubprocess() { | ||
| 68 | - | fmt.Fprintf(os.Stderr, "cef: detected subprocess, args=%v\n", os.Args) | |||
| 69 | cef.MaybeExitSubprocess() | 66 | cef.MaybeExitSubprocess() | ||
| 70 | - | fmt.Fprintf(os.Stderr, "cef: MaybeExitSubprocess returned (browser process)\n") | |||
| 71 | } | 67 | } | ||
| 72 | 68 | ||||
| 73 | // Run GUI mode for browse command | 69 | // Run GUI mode for browse command | ||
| @@ -113,7 +109,7 @@ func runGUI() int { | |||||
| 113 | } | 109 | } | ||
| 114 | timer.MarkDuration("parallel_phase", initResult.Duration) | 110 | timer.MarkDuration("parallel_phase", initResult.Duration) | ||
| 115 | 111 | ||||
| 116 | - | if cfg.Engine.CEF.CEFMultiThreadedMessageLoop() && resolveEngineType(cfg) == "cef" { | 112 | + | if cfg.Engine.CEF.CEFMultiThreadedMessageLoop() && cfg.Engine.ResolveEngineType() == "cef" { |
| 117 | logging.FromContext(ctx).Info().Msg("pre-initializing libadwaita before CEF multi-threaded loop") | 113 | logging.FromContext(ctx).Info().Msg("pre-initializing libadwaita before CEF multi-threaded loop") | ||
| 118 | ui.EnsureAdwaitaInitialized() | 114 | ui.EnsureAdwaitaInitialized() | ||
| 119 | if initResult.AdwaitaDetector != nil { | 115 | if initResult.AdwaitaDetector != nil { | ||
| @@ -252,18 +248,6 @@ func initStartupContext(cfg *config.Config) context.Context { | |||||
| 252 | return ctx | 248 | return ctx | ||
| 253 | } | 249 | } | ||
| 254 | 250 | ||||
| 255 | - | // resolveEngineType returns the effective engine type from config + env override. | |||
| 256 | - | func resolveEngineType(cfg *config.Config) string { | |||
| 257 | - | engineType := cfg.Engine.Type | |||
| 258 | - | if engineType == "" { | |||
| 259 | - | engineType = "webkit" | |||
| 260 | - | } | |||
| 261 | - | if envEngine := os.Getenv("DUMBER_ENGINE"); envEngine != "" { | |||
| 262 | - | engineType = envEngine | |||
| 263 | - | } | |||
| 264 | - | return engineType | |||
| 265 | - | } | |||
| 266 | - | ||||
| 267 | func initStackAndRepos( | 251 | func initStackAndRepos( | ||
| 268 | ctx context.Context, | 252 | ctx context.Context, | ||
| 269 | cfg *config.Config, | 253 | cfg *config.Config, | ||
| @@ -538,7 +522,7 @@ func createUseCases(repos *repositories, cfg *config.Config) *useCases { | |||||
| 538 | // this process was spawned by CEF as a renderer, GPU, or utility subprocess. | 522 | // this process was spawned by CEF as a renderer, GPU, or utility subprocess. | ||
| 539 | func isCEFSubprocess() bool { | 523 | func isCEFSubprocess() bool { | ||
| 540 | for _, arg := range os.Args { | 524 | for _, arg := range os.Args { | ||
| 541 | - | if len(arg) > 7 && arg[:7] == "--type=" { | 525 | + | if strings.HasPrefix(arg, "--type=") { |
| 542 | return true | 526 | return true | ||
| 543 | } | 527 | } | ||
| 544 | } | 528 | } | ||
M internal/application/usecase/navigate.go +6 -3
| @@ -330,6 +330,10 @@ func (uc *NavigateUseCase) persistHistory(ctx context.Context, record historyRec | |||
| 330 | 330 | func (uc *NavigateUseCase) persistTitleUpdate(ctx context.Context, historyURL, title string) { | |
| 331 | 331 | log := logging.FromContext(ctx) | |
| 332 | 332 | ||
| 333 | + | if title == "" { | |
| 334 | + | return | |
| 335 | + | } | |
| 336 | + | ||
| 333 | 337 | entry, err := uc.historyRepo.FindByURL(ctx, historyURL) | |
| 334 | 338 | if err != nil { | |
| 335 | 339 | log.Warn().Err(err).Str("url", historyURL).Msg("failed to find history entry for title update") | |
| @@ -354,12 +358,12 @@ func (uc *NavigateUseCase) persistTitleUpdate(ctx context.Context, historyURL, t | |||
| 354 | 358 | // UpdateHistoryTitle queues a title update for a history entry. The actual | |
| 355 | 359 | // DB write happens in the background historyWorker to avoid concurrent | |
| 356 | 360 | // repo access from the GTK main thread and the worker goroutine. | |
| 357 | - | func (uc *NavigateUseCase) UpdateHistoryTitle(_ context.Context, historyURL, title string) error { | |
| 361 | + | func (uc *NavigateUseCase) UpdateHistoryTitle(_ context.Context, historyURL, title string) { | |
| 358 | 362 | historyURL = canonicalizeURLForHistory(historyURL, historyCanonicalizationOptions{ | |
| 359 | 363 | StripTrackingParams: stripTrackingParamsForHistoryDedup, | |
| 360 | 364 | }) | |
| 361 | 365 | if historyURL == "" { | |
| 362 | - | return nil | |
| 366 | + | return | |
| 363 | 367 | } | |
| 364 | 368 | ||
| 365 | 369 | // Non-blocking send — same queue as visit records. | |
| @@ -368,7 +372,6 @@ func (uc *NavigateUseCase) UpdateHistoryTitle(_ context.Context, historyURL, tit | |||
| 368 | 372 | default: | |
| 369 | 373 | // Queue full — title update is best-effort. | |
| 370 | 374 | } | |
| 371 | - | return nil | |
| 372 | 375 | } | |
| 373 | 376 | ||
| 374 | 377 | // Reload reloads the current page. | |
| @@ -330,6 +330,10 @@ func (uc *NavigateUseCase) persistHistory(ctx context.Context, record historyRec | |||||
| 330 | func (uc *NavigateUseCase) persistTitleUpdate(ctx context.Context, historyURL, title string) { | 330 | func (uc *NavigateUseCase) persistTitleUpdate(ctx context.Context, historyURL, title string) { | ||
| 331 | log := logging.FromContext(ctx) | 331 | log := logging.FromContext(ctx) | ||
| 332 | 332 | ||||
| 333 | + | if title == "" { | |||
| 334 | + | return | |||
| 335 | + | } | |||
| 336 | + | ||||
| 333 | entry, err := uc.historyRepo.FindByURL(ctx, historyURL) | 337 | entry, err := uc.historyRepo.FindByURL(ctx, historyURL) | ||
| 334 | if err != nil { | 338 | if err != nil { | ||
| 335 | log.Warn().Err(err).Str("url", historyURL).Msg("failed to find history entry for title update") | 339 | log.Warn().Err(err).Str("url", historyURL).Msg("failed to find history entry for title update") | ||
| @@ -354,12 +358,12 @@ func (uc *NavigateUseCase) persistTitleUpdate(ctx context.Context, historyURL, t | |||||
| 354 | // UpdateHistoryTitle queues a title update for a history entry. The actual | 358 | // UpdateHistoryTitle queues a title update for a history entry. The actual | ||
| 355 | // DB write happens in the background historyWorker to avoid concurrent | 359 | // DB write happens in the background historyWorker to avoid concurrent | ||
| 356 | // repo access from the GTK main thread and the worker goroutine. | 360 | // repo access from the GTK main thread and the worker goroutine. | ||
| 357 | - | func (uc *NavigateUseCase) UpdateHistoryTitle(_ context.Context, historyURL, title string) error { | 361 | + | func (uc *NavigateUseCase) UpdateHistoryTitle(_ context.Context, historyURL, title string) { |
| 358 | historyURL = canonicalizeURLForHistory(historyURL, historyCanonicalizationOptions{ | 362 | historyURL = canonicalizeURLForHistory(historyURL, historyCanonicalizationOptions{ | ||
| 359 | StripTrackingParams: stripTrackingParamsForHistoryDedup, | 363 | StripTrackingParams: stripTrackingParamsForHistoryDedup, | ||
| 360 | }) | 364 | }) | ||
| 361 | if historyURL == "" { | 365 | if historyURL == "" { | ||
| 362 | - | return nil | 366 | + | return |
| 363 | } | 367 | } | ||
| 364 | 368 | ||||
| 365 | // Non-blocking send — same queue as visit records. | 369 | // Non-blocking send — same queue as visit records. | ||
| @@ -368,7 +372,6 @@ func (uc *NavigateUseCase) UpdateHistoryTitle(_ context.Context, historyURL, tit | |||||
| 368 | default: | 372 | default: | ||
| 369 | // Queue full — title update is best-effort. | 373 | // Queue full — title update is best-effort. | ||
| 370 | } | 374 | } | ||
| 371 | - | return nil | |||
| 372 | } | 375 | } | ||
| 373 | 376 | ||||
| 374 | // Reload reloads the current page. | 377 | // Reload reloads the current page. | ||
M internal/application/usecase/navigate_test.go +1 -2
| @@ -79,8 +79,7 @@ func TestUpdateHistoryTitle_UsesMetadataUpdateWithoutIncrementingVisits(t *testi | |||
| 79 | 79 | require.NotNil(t, before) | |
| 80 | 80 | ||
| 81 | 81 | uc := NewNavigateUseCase(repo, nil, entity.ZoomDefault) | |
| 82 | - | err = uc.UpdateHistoryTitle(ctx, "https://example.com/article", "New") | |
| 83 | - | require.NoError(t, err) | |
| 82 | + | uc.UpdateHistoryTitle(ctx, "https://example.com/article", "New") | |
| 84 | 83 | uc.Close() | |
| 85 | 84 | ||
| 86 | 85 | after, err := repo.FindByURL(ctx, "https://example.com/article") | |
| @@ -79,8 +79,7 @@ func TestUpdateHistoryTitle_UsesMetadataUpdateWithoutIncrementingVisits(t *testi | |||||
| 79 | require.NotNil(t, before) | 79 | require.NotNil(t, before) | ||
| 80 | 80 | ||||
| 81 | uc := NewNavigateUseCase(repo, nil, entity.ZoomDefault) | 81 | uc := NewNavigateUseCase(repo, nil, entity.ZoomDefault) | ||
| 82 | - | err = uc.UpdateHistoryTitle(ctx, "https://example.com/article", "New") | 82 | + | uc.UpdateHistoryTitle(ctx, "https://example.com/article", "New") |
| 83 | - | require.NoError(t, err) | |||
| 84 | uc.Close() | 83 | uc.Close() | ||
| 85 | 84 | ||||
| 86 | after, err := repo.FindByURL(ctx, "https://example.com/article") | 85 | after, err := repo.FindByURL(ctx, "https://example.com/article") | ||
M internal/bootstrap/engine.go +1 -9
| @@ -3,7 +3,6 @@ package bootstrap | |||
| 3 | 3 | import ( | |
| 4 | 4 | "context" | |
| 5 | 5 | "fmt" | |
| 6 | - | "os" | |
| 7 | 6 | ||
| 8 | 7 | "github.com/bnema/dumber/internal/application/port" | |
| 9 | 8 | "github.com/bnema/dumber/internal/application/usecase" | |
| @@ -29,14 +28,7 @@ type EngineInput struct { | |||
| 29 | 28 | // BuildEngine constructs a port.Engine for the engine type specified in cfg.Engine.Type. | |
| 30 | 29 | func BuildEngine(input EngineInput) (port.Engine, error) { | |
| 31 | 30 | cfg := input.Config | |
| 32 | - | engineType := cfg.Engine.Type | |
| 33 | - | if engineType == "" { | |
| 34 | - | engineType = "webkit" | |
| 35 | - | } | |
| 36 | - | // Allow env var override for smoke testing: DUMBER_ENGINE=cef | |
| 37 | - | if envEngine := os.Getenv("DUMBER_ENGINE"); envEngine != "" { | |
| 38 | - | engineType = envEngine | |
| 39 | - | } | |
| 31 | + | engineType := cfg.Engine.ResolveEngineType() | |
| 40 | 32 | switch engineType { | |
| 41 | 33 | case "webkit": | |
| 42 | 34 | opts := port.EngineOptions{ | |
| @@ -3,7 +3,6 @@ package bootstrap | |||||
| 3 | import ( | 3 | import ( | ||
| 4 | "context" | 4 | "context" | ||
| 5 | "fmt" | 5 | "fmt" | ||
| 6 | - | "os" | |||
| 7 | 6 | ||||
| 8 | "github.com/bnema/dumber/internal/application/port" | 7 | "github.com/bnema/dumber/internal/application/port" | ||
| 9 | "github.com/bnema/dumber/internal/application/usecase" | 8 | "github.com/bnema/dumber/internal/application/usecase" | ||
| @@ -29,14 +28,7 @@ type EngineInput struct { | |||||
| 29 | // BuildEngine constructs a port.Engine for the engine type specified in cfg.Engine.Type. | 28 | // BuildEngine constructs a port.Engine for the engine type specified in cfg.Engine.Type. | ||
| 30 | func BuildEngine(input EngineInput) (port.Engine, error) { | 29 | func BuildEngine(input EngineInput) (port.Engine, error) { | ||
| 31 | cfg := input.Config | 30 | cfg := input.Config | ||
| 32 | - | engineType := cfg.Engine.Type | 31 | + | engineType := cfg.Engine.ResolveEngineType() |
| 33 | - | if engineType == "" { | |||
| 34 | - | engineType = "webkit" | |||
| 35 | - | } | |||
| 36 | - | // Allow env var override for smoke testing: DUMBER_ENGINE=cef | |||
| 37 | - | if envEngine := os.Getenv("DUMBER_ENGINE"); envEngine != "" { | |||
| 38 | - | engineType = envEngine | |||
| 39 | - | } | |||
| 40 | switch engineType { | 32 | switch engineType { | ||
| 41 | case "webkit": | 33 | case "webkit": | ||
| 42 | opts := port.EngineOptions{ | 34 | opts := port.EngineOptions{ | ||
M internal/infrastructure/cef/engine.go +7 -9
| @@ -32,7 +32,7 @@ type Engine struct { | |||
| 32 | 32 | pumpTimerSource atomic.Uint64 | |
| 33 | 33 | ||
| 34 | 34 | // Diagnostic counters. | |
| 35 | - | pumpWorkCount uint64 | |
| 35 | + | pumpWorkCount atomic.Uint64 | |
| 36 | 36 | scheduleCount atomic.Uint64 // OnScheduleMessagePumpWork call count (currently always 0) | |
| 37 | 37 | ||
| 38 | 38 | contextInitializedCount atomic.Uint64 | |
| @@ -171,12 +171,12 @@ func (e *Engine) recordBrowserCreateRequest(width, height, result int32) { | |||
| 171 | 171 | e.browserCreateLastWidth.Store(width) | |
| 172 | 172 | e.browserCreateLastHeight.Store(height) | |
| 173 | 173 | ||
| 174 | - | log := logging.FromContext(e.ctx).Debug(). | |
| 174 | + | logging.FromContext(e.ctx).Debug(). | |
| 175 | 175 | Uint64("request_count", count). | |
| 176 | 176 | Int32("width", width). | |
| 177 | 177 | Int32("height", height). | |
| 178 | 178 | Int32("result", result). | |
| 179 | - | Msg | |
| 179 | + | Msg("cef: BrowserHostCreateBrowser returned") | |
| 180 | 180 | if result != 1 { | |
| 181 | 181 | logging.FromContext(e.ctx).Warn(). | |
| 182 | 182 | Uint64("request_count", count). | |
| @@ -184,9 +184,7 @@ func (e *Engine) recordBrowserCreateRequest(width, height, result int32) { | |||
| 184 | 184 | Int32("height", height). | |
| 185 | 185 | Int32("result", result). | |
| 186 | 186 | Msg("cef: BrowserHostCreateBrowser returned non-success") | |
| 187 | - | return | |
| 188 | 187 | } | |
| 189 | - | log("cef: BrowserHostCreateBrowser returned") | |
| 190 | 188 | } | |
| 191 | 189 | ||
| 192 | 190 | func (e *Engine) recordBrowserAfterCreated(browser purecef.Browser) { | |
| @@ -226,7 +224,7 @@ func (e *Engine) maybeLogBrowserCreateStall() { | |||
| 226 | 224 | Int32("last_create_result", e.browserCreateLastResult.Load()). | |
| 227 | 225 | Int32("last_create_width", e.browserCreateLastWidth.Load()). | |
| 228 | 226 | Int32("last_create_height", e.browserCreateLastHeight.Load()). | |
| 229 | - | Uint64("pump_work", e.pumpWorkCount). | |
| 227 | + | Uint64("pump_work", e.pumpWorkCount.Load()). | |
| 230 | 228 | Uint64("schedule_calls", e.scheduleCount.Load()). | |
| 231 | 229 | Uint64("context_initialized", e.contextInitializedCount.Load()). | |
| 232 | 230 | Uint64("renderer_launches", e.childLaunchRenderer.Load()). | |
| @@ -269,11 +267,11 @@ func (e *Engine) startManualMessagePump() { | |||
| 269 | 267 | return glib.SOURCE_REMOVE | |
| 270 | 268 | } | |
| 271 | 269 | ||
| 272 | - | e.pumpWorkCount++ | |
| 270 | + | count := e.pumpWorkCount.Add(1) | |
| 273 | 271 | e.maybeLogBrowserCreateStall() | |
| 274 | - | if e.pumpWorkCount <= 20 || e.pumpWorkCount%100 == 0 { | |
| 272 | + | if count <= 20 || count%100 == 0 { | |
| 275 | 273 | logging.FromContext(e.ctx).Debug(). | |
| 276 | - | Uint64("work", e.pumpWorkCount). | |
| 274 | + | Uint64("work", count). | |
| 277 | 275 | Int64("interval_ms", intervalMs). | |
| 278 | 276 | Msg("cef: manual pump doing work") | |
| 279 | 277 | } | |
| @@ -32,7 +32,7 @@ type Engine struct { | |||||
| 32 | pumpTimerSource atomic.Uint64 | 32 | pumpTimerSource atomic.Uint64 | ||
| 33 | 33 | ||||
| 34 | // Diagnostic counters. | 34 | // Diagnostic counters. | ||
| 35 | - | pumpWorkCount uint64 | 35 | + | pumpWorkCount atomic.Uint64 |
| 36 | scheduleCount atomic.Uint64 // OnScheduleMessagePumpWork call count (currently always 0) | 36 | scheduleCount atomic.Uint64 // OnScheduleMessagePumpWork call count (currently always 0) | ||
| 37 | 37 | ||||
| 38 | contextInitializedCount atomic.Uint64 | 38 | contextInitializedCount atomic.Uint64 | ||
| @@ -171,12 +171,12 @@ func (e *Engine) recordBrowserCreateRequest(width, height, result int32) { | |||||
| 171 | e.browserCreateLastWidth.Store(width) | 171 | e.browserCreateLastWidth.Store(width) | ||
| 172 | e.browserCreateLastHeight.Store(height) | 172 | e.browserCreateLastHeight.Store(height) | ||
| 173 | 173 | ||||
| 174 | - | log := logging.FromContext(e.ctx).Debug(). | 174 | + | logging.FromContext(e.ctx).Debug(). |
| 175 | Uint64("request_count", count). | 175 | Uint64("request_count", count). | ||
| 176 | Int32("width", width). | 176 | Int32("width", width). | ||
| 177 | Int32("height", height). | 177 | Int32("height", height). | ||
| 178 | Int32("result", result). | 178 | Int32("result", result). | ||
| 179 | - | Msg | 179 | + | Msg("cef: BrowserHostCreateBrowser returned") |
| 180 | if result != 1 { | 180 | if result != 1 { | ||
| 181 | logging.FromContext(e.ctx).Warn(). | 181 | logging.FromContext(e.ctx).Warn(). | ||
| 182 | Uint64("request_count", count). | 182 | Uint64("request_count", count). | ||
| @@ -184,9 +184,7 @@ func (e *Engine) recordBrowserCreateRequest(width, height, result int32) { | |||||
| 184 | Int32("height", height). | 184 | Int32("height", height). | ||
| 185 | Int32("result", result). | 185 | Int32("result", result). | ||
| 186 | Msg("cef: BrowserHostCreateBrowser returned non-success") | 186 | Msg("cef: BrowserHostCreateBrowser returned non-success") | ||
| 187 | - | return | |||
| 188 | } | 187 | } | ||
| 189 | - | log("cef: BrowserHostCreateBrowser returned") | |||
| 190 | } | 188 | } | ||
| 191 | 189 | ||||
| 192 | func (e *Engine) recordBrowserAfterCreated(browser purecef.Browser) { | 190 | func (e *Engine) recordBrowserAfterCreated(browser purecef.Browser) { | ||
| @@ -226,7 +224,7 @@ func (e *Engine) maybeLogBrowserCreateStall() { | |||||
| 226 | Int32("last_create_result", e.browserCreateLastResult.Load()). | 224 | Int32("last_create_result", e.browserCreateLastResult.Load()). | ||
| 227 | Int32("last_create_width", e.browserCreateLastWidth.Load()). | 225 | Int32("last_create_width", e.browserCreateLastWidth.Load()). | ||
| 228 | Int32("last_create_height", e.browserCreateLastHeight.Load()). | 226 | Int32("last_create_height", e.browserCreateLastHeight.Load()). | ||
| 229 | - | Uint64("pump_work", e.pumpWorkCount). | 227 | + | Uint64("pump_work", e.pumpWorkCount.Load()). |
| 230 | Uint64("schedule_calls", e.scheduleCount.Load()). | 228 | Uint64("schedule_calls", e.scheduleCount.Load()). | ||
| 231 | Uint64("context_initialized", e.contextInitializedCount.Load()). | 229 | Uint64("context_initialized", e.contextInitializedCount.Load()). | ||
| 232 | Uint64("renderer_launches", e.childLaunchRenderer.Load()). | 230 | Uint64("renderer_launches", e.childLaunchRenderer.Load()). | ||
| @@ -269,11 +267,11 @@ func (e *Engine) startManualMessagePump() { | |||||
| 269 | return glib.SOURCE_REMOVE | 267 | return glib.SOURCE_REMOVE | ||
| 270 | } | 268 | } | ||
| 271 | 269 | ||||
| 272 | - | e.pumpWorkCount++ | 270 | + | count := e.pumpWorkCount.Add(1) |
| 273 | e.maybeLogBrowserCreateStall() | 271 | e.maybeLogBrowserCreateStall() | ||
| 274 | - | if e.pumpWorkCount <= 20 || e.pumpWorkCount%100 == 0 { | 272 | + | if count <= 20 || count%100 == 0 { |
| 275 | logging.FromContext(e.ctx).Debug(). | 273 | logging.FromContext(e.ctx).Debug(). | ||
| 276 | - | Uint64("work", e.pumpWorkCount). | 274 | + | Uint64("work", count). |
| 277 | Int64("interval_ms", intervalMs). | 275 | Int64("interval_ms", intervalMs). | ||
| 278 | Msg("cef: manual pump doing work") | 276 | Msg("cef: manual pump doing work") | ||
| 279 | } | 277 | } | ||
M internal/infrastructure/cef/engine_adapters.go +2 -10
| @@ -18,19 +18,11 @@ type webViewFactoryAdapter struct { | |||
| 18 | 18 | } | |
| 19 | 19 | ||
| 20 | 20 | func (a *webViewFactoryAdapter) Create(ctx context.Context) (port.WebView, error) { | |
| 21 | - | wv, err := a.factory.Create(ctx) | |
| 22 | - | if err != nil { | |
| 23 | - | return nil, err | |
| 24 | - | } | |
| 25 | - | return wv, nil | |
| 21 | + | return a.factory.Create(ctx) | |
| 26 | 22 | } | |
| 27 | 23 | ||
| 28 | 24 | func (a *webViewFactoryAdapter) CreateRelated(ctx context.Context, parentID port.WebViewID) (port.WebView, error) { | |
| 29 | - | wv, err := a.factory.CreateRelated(ctx, parentID) | |
| 30 | - | if err != nil { | |
| 31 | - | return nil, err | |
| 32 | - | } | |
| 33 | - | return wv, nil | |
| 25 | + | return a.factory.CreateRelated(ctx, parentID) | |
| 34 | 26 | } | |
| 35 | 27 | ||
| 36 | 28 | // --- WebViewPool adapter --- | |
| @@ -18,19 +18,11 @@ type webViewFactoryAdapter struct { | |||||
| 18 | } | 18 | } | ||
| 19 | 19 | ||||
| 20 | func (a *webViewFactoryAdapter) Create(ctx context.Context) (port.WebView, error) { | 20 | func (a *webViewFactoryAdapter) Create(ctx context.Context) (port.WebView, error) { | ||
| 21 | - | wv, err := a.factory.Create(ctx) | 21 | + | return a.factory.Create(ctx) |
| 22 | - | if err != nil { | |||
| 23 | - | return nil, err | |||
| 24 | - | } | |||
| 25 | - | return wv, nil | |||
| 26 | } | 22 | } | ||
| 27 | 23 | ||||
| 28 | func (a *webViewFactoryAdapter) CreateRelated(ctx context.Context, parentID port.WebViewID) (port.WebView, error) { | 24 | func (a *webViewFactoryAdapter) CreateRelated(ctx context.Context, parentID port.WebViewID) (port.WebView, error) { | ||
| 29 | - | wv, err := a.factory.CreateRelated(ctx, parentID) | 25 | + | return a.factory.CreateRelated(ctx, parentID) |
| 30 | - | if err != nil { | |||
| 31 | - | return nil, err | |||
| 32 | - | } | |||
| 33 | - | return wv, nil | |||
| 34 | } | 26 | } | ||
| 35 | 27 | ||||
| 36 | // --- WebViewPool adapter --- | 28 | // --- WebViewPool adapter --- | ||
M internal/infrastructure/cef/engine_init.go +3 -0
| @@ -56,6 +56,9 @@ func NewEngine(ctx context.Context, cfg config.CEFEngineConfig) (*Engine, error) | |||
| 56 | 56 | // --no-zygote: Disable the zygote process which uses fork(). Go's runtime | |
| 57 | 57 | // does not survive fork cleanly, causing renderer crashes. Without zygote, | |
| 58 | 58 | // CEF launches each subprocess as a fresh exec of the helper binary. | |
| 59 | + | // | |
| 60 | + | // Thread-safety: safe because this runs during single-threaded startup | |
| 61 | + | // before any concurrent goroutines are spawned. | |
| 59 | 62 | savedArgs := os.Args | |
| 60 | 63 | os.Args = appendIfMissing(os.Args, "--no-zygote") | |
| 61 | 64 | ||
| @@ -56,6 +56,9 @@ func NewEngine(ctx context.Context, cfg config.CEFEngineConfig) (*Engine, error) | |||||
| 56 | // --no-zygote: Disable the zygote process which uses fork(). Go's runtime | 56 | // --no-zygote: Disable the zygote process which uses fork(). Go's runtime | ||
| 57 | // does not survive fork cleanly, causing renderer crashes. Without zygote, | 57 | // does not survive fork cleanly, causing renderer crashes. Without zygote, | ||
| 58 | // CEF launches each subprocess as a fresh exec of the helper binary. | 58 | // CEF launches each subprocess as a fresh exec of the helper binary. | ||
| 59 | + | // | |||
| 60 | + | // Thread-safety: safe because this runs during single-threaded startup | |||
| 61 | + | // before any concurrent goroutines are spawned. | |||
| 59 | savedArgs := os.Args | 62 | savedArgs := os.Args | ||
| 60 | os.Args = appendIfMissing(os.Args, "--no-zygote") | 63 | os.Args = appendIfMissing(os.Args, "--no-zygote") | ||
| 61 | 64 | ||||
M internal/infrastructure/cef/factory.go +1 -0
| @@ -124,5 +124,6 @@ func (f *WebViewFactory) Create(ctx context.Context) (port.WebView, error) { | |||
| 124 | 124 | // TODO(phase2): look up the parent browser by parentID and use the same | |
| 125 | 125 | // request context so cookies and session state are shared. | |
| 126 | 126 | func (f *WebViewFactory) CreateRelated(ctx context.Context, _ port.WebViewID) (port.WebView, error) { | |
| 127 | + | logging.FromContext(ctx).Warn().Msg("cef: CreateRelated does not yet support session sharing, creating independent WebView") | |
| 127 | 128 | return f.Create(ctx) | |
| 128 | 129 | } | |
| @@ -124,5 +124,6 @@ func (f *WebViewFactory) Create(ctx context.Context) (port.WebView, error) { | |||||
| 124 | // TODO(phase2): look up the parent browser by parentID and use the same | 124 | // TODO(phase2): look up the parent browser by parentID and use the same | ||
| 125 | // request context so cookies and session state are shared. | 125 | // request context so cookies and session state are shared. | ||
| 126 | func (f *WebViewFactory) CreateRelated(ctx context.Context, _ port.WebViewID) (port.WebView, error) { | 126 | func (f *WebViewFactory) CreateRelated(ctx context.Context, _ port.WebViewID) (port.WebView, error) { | ||
| 127 | + | logging.FromContext(ctx).Warn().Msg("cef: CreateRelated does not yet support session sharing, creating independent WebView") | |||
| 127 | return f.Create(ctx) | 128 | return f.Create(ctx) | ||
| 128 | } | 129 | } | ||
M internal/infrastructure/cef/input_bridge.go +12 -6
| @@ -409,12 +409,18 @@ func gdkKeyvalToVKSwitch(keyval uint) int32 { | |||
| 409 | 409 | return 0x21 | |
| 410 | 410 | case gdkKeyPageDown: | |
| 411 | 411 | return 0x22 | |
| 412 | - | case 0xffe1, 0xffe2: // Shift_L, Shift_R | |
| 413 | - | return 0xA0 | |
| 414 | - | case 0xffe3, 0xffe4: // Control_L, Control_R | |
| 415 | - | return 0xA2 | |
| 416 | - | case 0xffe9, 0xffea: // Alt_L, Alt_R | |
| 417 | - | return 0xA4 | |
| 412 | + | case 0xffe1: // Shift_L | |
| 413 | + | return 0xA0 // VK_LSHIFT | |
| 414 | + | case 0xffe2: // Shift_R | |
| 415 | + | return 0xA1 // VK_RSHIFT | |
| 416 | + | case 0xffe3: // Control_L | |
| 417 | + | return 0xA2 // VK_LCONTROL | |
| 418 | + | case 0xffe4: // Control_R | |
| 419 | + | return 0xA3 // VK_RCONTROL | |
| 420 | + | case 0xffe9: // Alt_L | |
| 421 | + | return 0xA4 // VK_LMENU | |
| 422 | + | case 0xffea: // Alt_R | |
| 423 | + | return 0xA5 // VK_RMENU | |
| 418 | 424 | default: | |
| 419 | 425 | // For unmapped keys, return the keyval if it fits in a reasonable range. | |
| 420 | 426 | if keyval < 0x100 { | |
| @@ -409,12 +409,18 @@ func gdkKeyvalToVKSwitch(keyval uint) int32 { | |||||
| 409 | return 0x21 | 409 | return 0x21 | ||
| 410 | case gdkKeyPageDown: | 410 | case gdkKeyPageDown: | ||
| 411 | return 0x22 | 411 | return 0x22 | ||
| 412 | - | case 0xffe1, 0xffe2: // Shift_L, Shift_R | 412 | + | case 0xffe1: // Shift_L |
| 413 | - | return 0xA0 | 413 | + | return 0xA0 // VK_LSHIFT |
| 414 | - | case 0xffe3, 0xffe4: // Control_L, Control_R | 414 | + | case 0xffe2: // Shift_R |
| 415 | - | return 0xA2 | 415 | + | return 0xA1 // VK_RSHIFT |
| 416 | - | case 0xffe9, 0xffea: // Alt_L, Alt_R | 416 | + | case 0xffe3: // Control_L |
| 417 | - | return 0xA4 | 417 | + | return 0xA2 // VK_LCONTROL |
| 418 | + | case 0xffe4: // Control_R | |||
| 419 | + | return 0xA3 // VK_RCONTROL | |||
| 420 | + | case 0xffe9: // Alt_L | |||
| 421 | + | return 0xA4 // VK_LMENU | |||
| 422 | + | case 0xffea: // Alt_R | |||
| 423 | + | return 0xA5 // VK_RMENU | |||
| 418 | default: | 424 | default: | ||
| 419 | // For unmapped keys, return the keyval if it fits in a reasonable range. | 425 | // For unmapped keys, return the keyval if it fits in a reasonable range. | ||
| 420 | if keyval < 0x100 { | 426 | if keyval < 0x100 { | ||
M internal/infrastructure/cef/pool.go +9 -2
| @@ -3,9 +3,11 @@ package cef | |||
| 3 | 3 | import ( | |
| 4 | 4 | "context" | |
| 5 | 5 | "errors" | |
| 6 | + | "fmt" | |
| 6 | 7 | "sync" | |
| 7 | 8 | ||
| 8 | 9 | "github.com/bnema/dumber/internal/application/port" | |
| 10 | + | "github.com/bnema/dumber/internal/logging" | |
| 9 | 11 | ) | |
| 10 | 12 | ||
| 11 | 13 | // Compile-time interface check. | |
| @@ -67,15 +69,20 @@ func (p *WebViewPool) Prewarm(count int) { | |||
| 67 | 69 | } | |
| 68 | 70 | ||
| 69 | 71 | ctx := context.Background() | |
| 72 | + | log := logging.FromContext(ctx) | |
| 70 | 73 | views := make([]*WebView, 0, count) | |
| 71 | 74 | for range count { | |
| 72 | 75 | wv, err := p.factory.Create(ctx) | |
| 73 | 76 | if err != nil { | |
| 77 | + | log.Warn().Err(err).Msg("cef: pool prewarm failed to create WebView") | |
| 74 | 78 | continue | |
| 75 | 79 | } | |
| 76 | - | if cefWV, ok := wv.(*WebView); ok { | |
| 77 | - | views = append(views, cefWV) | |
| 80 | + | cefWV, ok := wv.(*WebView) | |
| 81 | + | if !ok { | |
| 82 | + | log.Warn().Str("concrete_type", fmt.Sprintf("%T", wv)).Msg("cef: pool prewarm got unexpected WebView type") | |
| 83 | + | continue | |
| 78 | 84 | } | |
| 85 | + | views = append(views, cefWV) | |
| 79 | 86 | } | |
| 80 | 87 | ||
| 81 | 88 | p.mu.Lock() | |
| @@ -3,9 +3,11 @@ package cef | |||||
| 3 | import ( | 3 | import ( | ||
| 4 | "context" | 4 | "context" | ||
| 5 | "errors" | 5 | "errors" | ||
| 6 | + | "fmt" | |||
| 6 | "sync" | 7 | "sync" | ||
| 7 | 8 | ||||
| 8 | "github.com/bnema/dumber/internal/application/port" | 9 | "github.com/bnema/dumber/internal/application/port" | ||
| 10 | + | "github.com/bnema/dumber/internal/logging" | |||
| 9 | ) | 11 | ) | ||
| 10 | 12 | ||||
| 11 | // Compile-time interface check. | 13 | // Compile-time interface check. | ||
| @@ -67,15 +69,20 @@ func (p *WebViewPool) Prewarm(count int) { | |||||
| 67 | } | 69 | } | ||
| 68 | 70 | ||||
| 69 | ctx := context.Background() | 71 | ctx := context.Background() | ||
| 72 | + | log := logging.FromContext(ctx) | |||
| 70 | views := make([]*WebView, 0, count) | 73 | views := make([]*WebView, 0, count) | ||
| 71 | for range count { | 74 | for range count { | ||
| 72 | wv, err := p.factory.Create(ctx) | 75 | wv, err := p.factory.Create(ctx) | ||
| 73 | if err != nil { | 76 | if err != nil { | ||
| 77 | + | log.Warn().Err(err).Msg("cef: pool prewarm failed to create WebView") | |||
| 74 | continue | 78 | continue | ||
| 75 | } | 79 | } | ||
| 76 | - | if cefWV, ok := wv.(*WebView); ok { | 80 | + | cefWV, ok := wv.(*WebView) |
| 77 | - | views = append(views, cefWV) | 81 | + | if !ok { |
| 82 | + | log.Warn().Str("concrete_type", fmt.Sprintf("%T", wv)).Msg("cef: pool prewarm got unexpected WebView type") | |||
| 83 | + | continue | |||
| 78 | } | 84 | } | ||
| 85 | + | views = append(views, cefWV) | |||
| 79 | } | 86 | } | ||
| 80 | 87 | ||||
| 81 | p.mu.Lock() | 88 | p.mu.Lock() | ||
M internal/infrastructure/cef/render_pipeline.go +1 -1
| @@ -159,7 +159,7 @@ func (rp *renderPipeline) handlePaint(buffer unsafe.Pointer, width, height int32 | |||
| 159 | 159 | startedAt := time.Now() | |
| 160 | 160 | rp.mu.Lock() | |
| 161 | 161 | ||
| 162 | - | bufSize := int(width) * int(height) * 4 | |
| 162 | + | bufSize := int(int64(width) * int64(height) * 4) | |
| 163 | 163 | srcSlice := unsafe.Slice((*byte)(buffer), bufSize) | |
| 164 | 164 | copiedBytes := uint64(0) | |
| 165 | 165 | ||
| @@ -159,7 +159,7 @@ func (rp *renderPipeline) handlePaint(buffer unsafe.Pointer, width, height int32 | |||||
| 159 | startedAt := time.Now() | 159 | startedAt := time.Now() | ||
| 160 | rp.mu.Lock() | 160 | rp.mu.Lock() | ||
| 161 | 161 | ||||
| 162 | - | bufSize := int(width) * int(height) * 4 | 162 | + | bufSize := int(int64(width) * int64(height) * 4) |
| 163 | srcSlice := unsafe.Slice((*byte)(buffer), bufSize) | 163 | srcSlice := unsafe.Slice((*byte)(buffer), bufSize) | ||
| 164 | copiedBytes := uint64(0) | 164 | copiedBytes := uint64(0) | ||
| 165 | 165 | ||||
M internal/infrastructure/cef/webview.go +3 -9
| @@ -380,11 +380,9 @@ func (wv *WebView) NativeWidget() uintptr { | |||
| 380 | 380 | func (wv *WebView) updateURI(uri string) { | |
| 381 | 381 | wv.mu.Lock() | |
| 382 | 382 | wv.uri = uri | |
| 383 | + | cb := wv.callbacks | |
| 383 | 384 | wv.mu.Unlock() | |
| 384 | 385 | ||
| 385 | - | wv.mu.RLock() | |
| 386 | - | cb := wv.callbacks | |
| 387 | - | wv.mu.RUnlock() | |
| 388 | 386 | if cb != nil && cb.OnURIChanged != nil { | |
| 389 | 387 | wv.runOnGTK(func() { | |
| 390 | 388 | cb.OnURIChanged(uri) | |
| @@ -395,11 +393,9 @@ func (wv *WebView) updateURI(uri string) { | |||
| 395 | 393 | func (wv *WebView) updateTitle(title string) { | |
| 396 | 394 | wv.mu.Lock() | |
| 397 | 395 | wv.title = title | |
| 396 | + | cb := wv.callbacks | |
| 398 | 397 | wv.mu.Unlock() | |
| 399 | 398 | ||
| 400 | - | wv.mu.RLock() | |
| 401 | - | cb := wv.callbacks | |
| 402 | - | wv.mu.RUnlock() | |
| 403 | 399 | if cb != nil && cb.OnTitleChanged != nil { | |
| 404 | 400 | wv.runOnGTK(func() { | |
| 405 | 401 | cb.OnTitleChanged(title) | |
| @@ -410,11 +406,9 @@ func (wv *WebView) updateTitle(title string) { | |||
| 410 | 406 | func (wv *WebView) updateProgress(progress float64) { | |
| 411 | 407 | wv.mu.Lock() | |
| 412 | 408 | wv.progress = progress | |
| 409 | + | cb := wv.callbacks | |
| 413 | 410 | wv.mu.Unlock() | |
| 414 | 411 | ||
| 415 | - | wv.mu.RLock() | |
| 416 | - | cb := wv.callbacks | |
| 417 | - | wv.mu.RUnlock() | |
| 418 | 412 | if cb != nil && cb.OnProgressChanged != nil { | |
| 419 | 413 | wv.runOnGTK(func() { | |
| 420 | 414 | cb.OnProgressChanged(progress) | |
| @@ -380,11 +380,9 @@ func (wv *WebView) NativeWidget() uintptr { | |||||
| 380 | func (wv *WebView) updateURI(uri string) { | 380 | func (wv *WebView) updateURI(uri string) { | ||
| 381 | wv.mu.Lock() | 381 | wv.mu.Lock() | ||
| 382 | wv.uri = uri | 382 | wv.uri = uri | ||
| 383 | + | cb := wv.callbacks | |||
| 383 | wv.mu.Unlock() | 384 | wv.mu.Unlock() | ||
| 384 | 385 | ||||
| 385 | - | wv.mu.RLock() | |||
| 386 | - | cb := wv.callbacks | |||
| 387 | - | wv.mu.RUnlock() | |||
| 388 | if cb != nil && cb.OnURIChanged != nil { | 386 | if cb != nil && cb.OnURIChanged != nil { | ||
| 389 | wv.runOnGTK(func() { | 387 | wv.runOnGTK(func() { | ||
| 390 | cb.OnURIChanged(uri) | 388 | cb.OnURIChanged(uri) | ||
| @@ -395,11 +393,9 @@ func (wv *WebView) updateURI(uri string) { | |||||
| 395 | func (wv *WebView) updateTitle(title string) { | 393 | func (wv *WebView) updateTitle(title string) { | ||
| 396 | wv.mu.Lock() | 394 | wv.mu.Lock() | ||
| 397 | wv.title = title | 395 | wv.title = title | ||
| 396 | + | cb := wv.callbacks | |||
| 398 | wv.mu.Unlock() | 397 | wv.mu.Unlock() | ||
| 399 | 398 | ||||
| 400 | - | wv.mu.RLock() | |||
| 401 | - | cb := wv.callbacks | |||
| 402 | - | wv.mu.RUnlock() | |||
| 403 | if cb != nil && cb.OnTitleChanged != nil { | 399 | if cb != nil && cb.OnTitleChanged != nil { | ||
| 404 | wv.runOnGTK(func() { | 400 | wv.runOnGTK(func() { | ||
| 405 | cb.OnTitleChanged(title) | 401 | cb.OnTitleChanged(title) | ||
| @@ -410,11 +406,9 @@ func (wv *WebView) updateTitle(title string) { | |||||
| 410 | func (wv *WebView) updateProgress(progress float64) { | 406 | func (wv *WebView) updateProgress(progress float64) { | ||
| 411 | wv.mu.Lock() | 407 | wv.mu.Lock() | ||
| 412 | wv.progress = progress | 408 | wv.progress = progress | ||
| 409 | + | cb := wv.callbacks | |||
| 413 | wv.mu.Unlock() | 410 | wv.mu.Unlock() | ||
| 414 | 411 | ||||
| 415 | - | wv.mu.RLock() | |||
| 416 | - | cb := wv.callbacks | |||
| 417 | - | wv.mu.RUnlock() | |||
| 418 | if cb != nil && cb.OnProgressChanged != nil { | 412 | if cb != nil && cb.OnProgressChanged != nil { | ||
| 419 | wv.runOnGTK(func() { | 413 | wv.runOnGTK(func() { | ||
| 420 | cb.OnProgressChanged(progress) | 414 | cb.OnProgressChanged(progress) | ||
M internal/infrastructure/cef/webview_handlers.go +1 -2
| @@ -357,14 +357,13 @@ func (h *handlerSet) OnAfterCreated(browser purecef.Browser) { | |||
| 357 | 357 | h.wv.host = host | |
| 358 | 358 | uri := h.wv.pendingURI | |
| 359 | 359 | h.wv.pendingURI = "" | |
| 360 | - | h.wv.mu.Unlock() | |
| 361 | - | ||
| 362 | 360 | h.wv.input.setHost(host) | |
| 363 | 361 | ||
| 364 | 362 | // Replay any navigation that was requested before the browser existed. | |
| 365 | 363 | if uri != "" { | |
| 366 | 364 | browser.GetMainFrame().LoadURL(uri) | |
| 367 | 365 | } | |
| 366 | + | h.wv.mu.Unlock() | |
| 368 | 367 | } | |
| 369 | 368 | ||
| 370 | 369 | // DoClose returns false to allow the default close behavior. | |
| @@ -357,14 +357,13 @@ func (h *handlerSet) OnAfterCreated(browser purecef.Browser) { | |||||
| 357 | h.wv.host = host | 357 | h.wv.host = host | ||
| 358 | uri := h.wv.pendingURI | 358 | uri := h.wv.pendingURI | ||
| 359 | h.wv.pendingURI = "" | 359 | h.wv.pendingURI = "" | ||
| 360 | - | h.wv.mu.Unlock() | |||
| 361 | - | ||||
| 362 | h.wv.input.setHost(host) | 360 | h.wv.input.setHost(host) | ||
| 363 | 361 | ||||
| 364 | // Replay any navigation that was requested before the browser existed. | 362 | // Replay any navigation that was requested before the browser existed. | ||
| 365 | if uri != "" { | 363 | if uri != "" { | ||
| 366 | browser.GetMainFrame().LoadURL(uri) | 364 | browser.GetMainFrame().LoadURL(uri) | ||
| 367 | } | 365 | } | ||
| 366 | + | h.wv.mu.Unlock() | |||
| 368 | } | 367 | } | ||
| 369 | 368 | ||||
| 370 | // DoClose returns false to allow the default close behavior. | 369 | // DoClose returns false to allow the default close behavior. | ||
M internal/infrastructure/config/engine.go +16 -0
| @@ -1,5 +1,21 @@ | |||
| 1 | 1 | package config | |
| 2 | 2 | ||
| 3 | + | import "os" | |
| 4 | + | ||
| 5 | + | // ResolveEngineType returns the effective engine type from config + env override. | |
| 6 | + | // It defaults to "webkit" when no type is configured, and allows DUMBER_ENGINE | |
| 7 | + | // to override the configured value for smoke testing. | |
| 8 | + | func (e *EngineConfig) ResolveEngineType() string { | |
| 9 | + | engineType := e.Type | |
| 10 | + | if engineType == "" { | |
| 11 | + | engineType = "webkit" | |
| 12 | + | } | |
| 13 | + | if envEngine := os.Getenv("DUMBER_ENGINE"); envEngine != "" { | |
| 14 | + | engineType = envEngine | |
| 15 | + | } | |
| 16 | + | return engineType | |
| 17 | + | } | |
| 18 | + | ||
| 3 | 19 | // EngineConfig holds engine selection and universal engine options. | |
| 4 | 20 | type EngineConfig struct { | |
| 5 | 21 | Type string `mapstructure:"type" toml:"type" yaml:"type"` | |
| @@ -1,5 +1,21 @@ | |||||
| 1 | package config | 1 | package config | ||
| 2 | 2 | ||||
| 3 | + | import "os" | |||
| 4 | + | ||||
| 5 | + | // ResolveEngineType returns the effective engine type from config + env override. | |||
| 6 | + | // It defaults to "webkit" when no type is configured, and allows DUMBER_ENGINE | |||
| 7 | + | // to override the configured value for smoke testing. | |||
| 8 | + | func (e *EngineConfig) ResolveEngineType() string { | |||
| 9 | + | engineType := e.Type | |||
| 10 | + | if engineType == "" { | |||
| 11 | + | engineType = "webkit" | |||
| 12 | + | } | |||
| 13 | + | if envEngine := os.Getenv("DUMBER_ENGINE"); envEngine != "" { | |||
| 14 | + | engineType = envEngine | |||
| 15 | + | } | |||
| 16 | + | return engineType | |||
| 17 | + | } | |||
| 18 | + | ||||
| 3 | // EngineConfig holds engine selection and universal engine options. | 19 | // EngineConfig holds engine selection and universal engine options. | ||
| 4 | type EngineConfig struct { | 20 | type EngineConfig struct { | ||
| 5 | Type string `mapstructure:"type" toml:"type" yaml:"type"` | 21 | Type string `mapstructure:"type" toml:"type" yaml:"type"` | ||
M internal/ui/coordinator/navigation.go +1 -5
| @@ -241,15 +241,11 @@ func (c *NavigationCoordinator) PrintPage(ctx context.Context) error { | |||
| 241 | 241 | ||
| 242 | 242 | // UpdateHistoryTitle updates the title of a history entry after page load. | |
| 243 | 243 | func (c *NavigationCoordinator) UpdateHistoryTitle(ctx context.Context, paneID entity.PaneID, url, title string) { | |
| 244 | - | log := logging.FromContext(ctx) | |
| 245 | - | ||
| 246 | 244 | if c.navigateUC == nil { | |
| 247 | 245 | return | |
| 248 | 246 | } | |
| 249 | 247 | ||
| 250 | - | if err := c.navigateUC.UpdateHistoryTitle(ctx, url, title); err != nil { | |
| 251 | - | log.Warn().Err(err).Str("url", url).Msg("failed to update history title") | |
| 252 | - | } | |
| 248 | + | c.navigateUC.UpdateHistoryTitle(ctx, url, title) | |
| 253 | 249 | } | |
| 254 | 250 | ||
| 255 | 251 | // RecordHistory records a URL in history on page commit. | |
| @@ -241,15 +241,11 @@ func (c *NavigationCoordinator) PrintPage(ctx context.Context) error { | |||||
| 241 | 241 | ||||
| 242 | // UpdateHistoryTitle updates the title of a history entry after page load. | 242 | // UpdateHistoryTitle updates the title of a history entry after page load. | ||
| 243 | func (c *NavigationCoordinator) UpdateHistoryTitle(ctx context.Context, paneID entity.PaneID, url, title string) { | 243 | func (c *NavigationCoordinator) UpdateHistoryTitle(ctx context.Context, paneID entity.PaneID, url, title string) { | ||
| 244 | - | log := logging.FromContext(ctx) | |||
| 245 | - | ||||
| 246 | if c.navigateUC == nil { | 244 | if c.navigateUC == nil { | ||
| 247 | return | 245 | return | ||
| 248 | } | 246 | } | ||
| 249 | 247 | ||||
| 250 | - | if err := c.navigateUC.UpdateHistoryTitle(ctx, url, title); err != nil { | 248 | + | c.navigateUC.UpdateHistoryTitle(ctx, url, title) |
| 251 | - | log.Warn().Err(err).Str("url", url).Msg("failed to update history title") | |||
| 252 | - | } | |||
| 253 | } | 249 | } | ||
| 254 | 250 | ||||
| 255 | // RecordHistory records a URL in history on page commit. | 251 | // RecordHistory records a URL in history on page commit. | ||