fix(cef): stabilize local navigation behavior
Restore the legacy CEF profile path and avoid premature load completion so existing sessions survive and cross-site navigations keep visible progress. Disable transcoding by default for local testing and switch PipeWire to the released module version.
- commit
- 622e14a2acb97cefacd65a947932a42c89cf1ca6
- author
- brice · · 2026-04-11 10:03 UTC
- parent
- 02f4a19
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
20 files · +224 -73
M go.mod +1 -3
| @@ -5,7 +5,7 @@ go 1.26.1 | |||
| 5 | 5 | require ( | |
| 6 | 6 | github.com/bnema/purego-cef v0.8.0 | |
| 7 | 7 | github.com/bnema/purego-ffmpeg v0.3.0 | |
| 8 | - | github.com/bnema/purego-pipewire v0.0.0-00010101000000-000000000000 | |
| 8 | + | github.com/bnema/purego-pipewire v0.1.1 | |
| 9 | 9 | github.com/bnema/purego-sqlite v0.1.1 | |
| 10 | 10 | github.com/bnema/puregotk v0.3.0 | |
| 11 | 11 | github.com/bnema/puregotk-webkit v0.0.16 | |
| @@ -31,8 +31,6 @@ replace github.com/bnema/purego-cef => ../purego-cef | |||
| 31 | 31 | ||
| 32 | 32 | replace github.com/bnema/purego-ffmpeg => ../purego-ffmpeg | |
| 33 | 33 | ||
| 34 | - | replace github.com/bnema/purego-pipewire => ../../../purego-pipewire/.worktrees/dumber-cef-audio | |
| 35 | - | ||
| 36 | 34 | require ( | |
| 37 | 35 | github.com/atotto/clipboard v0.1.4 // indirect | |
| 38 | 36 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect | |
| @@ -5,7 +5,7 @@ go 1.26.1 | |||||
| 5 | require ( | 5 | require ( | ||
| 6 | github.com/bnema/purego-cef v0.8.0 | 6 | github.com/bnema/purego-cef v0.8.0 | ||
| 7 | github.com/bnema/purego-ffmpeg v0.3.0 | 7 | github.com/bnema/purego-ffmpeg v0.3.0 | ||
| 8 | - | github.com/bnema/purego-pipewire v0.0.0-00010101000000-000000000000 | 8 | + | github.com/bnema/purego-pipewire v0.1.1 |
| 9 | github.com/bnema/purego-sqlite v0.1.1 | 9 | github.com/bnema/purego-sqlite v0.1.1 | ||
| 10 | github.com/bnema/puregotk v0.3.0 | 10 | github.com/bnema/puregotk v0.3.0 | ||
| 11 | github.com/bnema/puregotk-webkit v0.0.16 | 11 | github.com/bnema/puregotk-webkit v0.0.16 | ||
| @@ -31,8 +31,6 @@ replace github.com/bnema/purego-cef => ../purego-cef | |||||
| 31 | 31 | ||||
| 32 | replace github.com/bnema/purego-ffmpeg => ../purego-ffmpeg | 32 | replace github.com/bnema/purego-ffmpeg => ../purego-ffmpeg | ||
| 33 | 33 | ||||
| 34 | - | replace github.com/bnema/purego-pipewire => ../../../purego-pipewire/.worktrees/dumber-cef-audio | |||
| 35 | - | ||||
| 36 | require ( | 34 | require ( | ||
| 37 | github.com/atotto/clipboard v0.1.4 // indirect | 35 | github.com/atotto/clipboard v0.1.4 // indirect | ||
| 38 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect | 36 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect | ||
M go.sum +2 -0 generated file: collapsed
M internal/infrastructure/cef/engine_init.go +4 -1
| @@ -105,7 +105,10 @@ func defaultCEFUserDataDir() string { | |||
| 105 | 105 | // Fallback to HOME-based path if XDG resolution fails | |
| 106 | 106 | return filepath.Clean(filepath.Join(os.Getenv("HOME"), ".config", "cef_user_data")) | |
| 107 | 107 | } | |
| 108 | - | return filepath.Join(dirs.ConfigHome, "cef_user_data") | |
| 108 | + | if os.Getenv("ENV") == "dev" { | |
| 109 | + | return filepath.Join(dirs.ConfigHome, "cef_user_data") | |
| 110 | + | } | |
| 111 | + | return filepath.Join(filepath.Dir(dirs.ConfigHome), "cef_user_data") | |
| 109 | 112 | } | |
| 110 | 113 | ||
| 111 | 114 | // initializeCEF calls cef_initialize with the App to register custom schemes | |
| @@ -105,7 +105,10 @@ func defaultCEFUserDataDir() string { | |||||
| 105 | // Fallback to HOME-based path if XDG resolution fails | 105 | // Fallback to HOME-based path if XDG resolution fails | ||
| 106 | return filepath.Clean(filepath.Join(os.Getenv("HOME"), ".config", "cef_user_data")) | 106 | return filepath.Clean(filepath.Join(os.Getenv("HOME"), ".config", "cef_user_data")) | ||
| 107 | } | 107 | } | ||
| 108 | - | return filepath.Join(dirs.ConfigHome, "cef_user_data") | 108 | + | if os.Getenv("ENV") == "dev" { |
| 109 | + | return filepath.Join(dirs.ConfigHome, "cef_user_data") | |||
| 110 | + | } | |||
| 111 | + | return filepath.Join(filepath.Dir(dirs.ConfigHome), "cef_user_data") | |||
| 109 | } | 112 | } | ||
| 110 | 113 | ||||
| 111 | // initializeCEF calls cef_initialize with the App to register custom schemes | 114 | // initializeCEF calls cef_initialize with the App to register custom schemes | ||
M internal/infrastructure/cef/engine_init_test.go +2 -4
| @@ -31,8 +31,7 @@ func TestPrepareCEFInitTraceFile_EnabledViaEnv(t *testing.T) { | |||
| 31 | 31 | require.FileExists(t, path) | |
| 32 | 32 | } | |
| 33 | 33 | ||
| 34 | - | func TestPrepareCEFSettings_SetsRootCachePath(t *testing.T) { | |
| 35 | - | // Test production mode: should use XDG config home | |
| 34 | + | func TestPrepareCEFSettings_RootCachePath_UsesLegacyCEFUserDataDir(t *testing.T) { | |
| 36 | 35 | homeDir := filepath.Join(t.TempDir(), "home") | |
| 37 | 36 | t.Setenv("HOME", homeDir) | |
| 38 | 37 | t.Setenv("ENV", "") | |
| @@ -40,8 +39,7 @@ func TestPrepareCEFSettings_SetsRootCachePath(t *testing.T) { | |||
| 40 | 39 | logger := zerolog.Nop() | |
| 41 | 40 | settings := prepareCEFSettings(config.CEFEngineConfig{}, &logger) | |
| 42 | 41 | ||
| 43 | - | // Should use XDG config home (~/.config/dumber/cef_user_data in prod) | |
| 44 | - | require.Equal(t, filepath.Join(homeDir, ".config", "dumber", "cef_user_data"), settings.RootCachePath) | |
| 42 | + | require.Equal(t, filepath.Join(homeDir, ".config", "cef_user_data"), settings.RootCachePath) | |
| 45 | 43 | } | |
| 46 | 44 | ||
| 47 | 45 | func TestPrepareCEFSettings_RootCachePath_DevMode(t *testing.T) { | |
| @@ -31,8 +31,7 @@ func TestPrepareCEFInitTraceFile_EnabledViaEnv(t *testing.T) { | |||||
| 31 | require.FileExists(t, path) | 31 | require.FileExists(t, path) | ||
| 32 | } | 32 | } | ||
| 33 | 33 | ||||
| 34 | - | func TestPrepareCEFSettings_SetsRootCachePath(t *testing.T) { | 34 | + | func TestPrepareCEFSettings_RootCachePath_UsesLegacyCEFUserDataDir(t *testing.T) { |
| 35 | - | // Test production mode: should use XDG config home | |||
| 36 | homeDir := filepath.Join(t.TempDir(), "home") | 35 | homeDir := filepath.Join(t.TempDir(), "home") | ||
| 37 | t.Setenv("HOME", homeDir) | 36 | t.Setenv("HOME", homeDir) | ||
| 38 | t.Setenv("ENV", "") | 37 | t.Setenv("ENV", "") | ||
| @@ -40,8 +39,7 @@ func TestPrepareCEFSettings_SetsRootCachePath(t *testing.T) { | |||||
| 40 | logger := zerolog.Nop() | 39 | logger := zerolog.Nop() | ||
| 41 | settings := prepareCEFSettings(config.CEFEngineConfig{}, &logger) | 40 | settings := prepareCEFSettings(config.CEFEngineConfig{}, &logger) | ||
| 42 | 41 | ||||
| 43 | - | // Should use XDG config home (~/.config/dumber/cef_user_data in prod) | 42 | + | require.Equal(t, filepath.Join(homeDir, ".config", "cef_user_data"), settings.RootCachePath) |
| 44 | - | require.Equal(t, filepath.Join(homeDir, ".config", "dumber", "cef_user_data"), settings.RootCachePath) | |||
| 45 | } | 43 | } | ||
| 46 | 44 | ||||
| 47 | func TestPrepareCEFSettings_RootCachePath_DevMode(t *testing.T) { | 45 | func TestPrepareCEFSettings_RootCachePath_DevMode(t *testing.T) { | ||
M internal/infrastructure/cef/webview_handlers.go +10 -16
| @@ -411,7 +411,7 @@ func (h *handlerSet) OnLoadStart(_ purecef.Browser, frame purecef.Frame, _ purec | |||
| 411 | 411 | } | |
| 412 | 412 | } | |
| 413 | 413 | ||
| 414 | - | // OnLoadEnd fires LoadFinished and sets progress to 1.0 for the main frame. | |
| 414 | + | // OnLoadEnd resets crash count and injects content scripts for the main frame. | |
| 415 | 415 | func (h *handlerSet) OnLoadEnd(_ purecef.Browser, frame purecef.Frame, httpStatusCode int32) { | |
| 416 | 416 | log := logging.FromContext(h.wv.ctx) | |
| 417 | 417 | log.Debug(). | |
| @@ -434,21 +434,15 @@ func (h *handlerSet) OnLoadEnd(_ purecef.Browser, frame purecef.Frame, httpStatu | |||
| 434 | 434 | }) | |
| 435 | 435 | } | |
| 436 | 436 | ||
| 437 | - | h.wv.mu.RLock() | |
| 438 | - | cb := h.wv.callbacks | |
| 439 | - | h.wv.mu.RUnlock() | |
| 440 | - | if cb != nil { | |
| 441 | - | if cb.OnLoadChanged != nil { | |
| 442 | - | h.wv.runOnGTK(func() { | |
| 443 | - | cb.OnLoadChanged(port.LoadFinished) | |
| 444 | - | }) | |
| 445 | - | } | |
| 446 | - | if cb.OnProgressChanged != nil { | |
| 447 | - | h.wv.runOnGTK(func() { | |
| 448 | - | cb.OnProgressChanged(1.0) | |
| 449 | - | }) | |
| 450 | - | } | |
| 451 | - | } | |
| 437 | + | // NOTE: We intentionally do NOT dispatch LoadFinished or ProgressChanged(1.0) | |
| 438 | + | // here. OnLoadEnd is a per-frame event, and during cross-site navigations | |
| 439 | + | // (process swap), CEF fires OnLoadEnd for the OLD main frame before the new | |
| 440 | + | // page finishes loading. Dispatching LoadFinished here caused the progress | |
| 441 | + | // bar to hide prematurely while the new page was still loading. | |
| 442 | + | // | |
| 443 | + | // OnLoadingStateChange(isLoading=false) is the correct browser-level signal | |
| 444 | + | // for load completion — CEF guarantees it fires AFTER all OnLoadEnd calls. | |
| 445 | + | // CEF's OnLoadingProgressChange also provides progress=1.0 at completion. | |
| 452 | 446 | } | |
| 453 | 447 | ||
| 454 | 448 | // OnLoadError is a no-op. | |
| @@ -411,7 +411,7 @@ func (h *handlerSet) OnLoadStart(_ purecef.Browser, frame purecef.Frame, _ purec | |||||
| 411 | } | 411 | } | ||
| 412 | } | 412 | } | ||
| 413 | 413 | ||||
| 414 | - | // OnLoadEnd fires LoadFinished and sets progress to 1.0 for the main frame. | 414 | + | // OnLoadEnd resets crash count and injects content scripts for the main frame. |
| 415 | func (h *handlerSet) OnLoadEnd(_ purecef.Browser, frame purecef.Frame, httpStatusCode int32) { | 415 | func (h *handlerSet) OnLoadEnd(_ purecef.Browser, frame purecef.Frame, httpStatusCode int32) { | ||
| 416 | log := logging.FromContext(h.wv.ctx) | 416 | log := logging.FromContext(h.wv.ctx) | ||
| 417 | log.Debug(). | 417 | log.Debug(). | ||
| @@ -434,21 +434,15 @@ func (h *handlerSet) OnLoadEnd(_ purecef.Browser, frame purecef.Frame, httpStatu | |||||
| 434 | }) | 434 | }) | ||
| 435 | } | 435 | } | ||
| 436 | 436 | ||||
| 437 | - | h.wv.mu.RLock() | 437 | + | // NOTE: We intentionally do NOT dispatch LoadFinished or ProgressChanged(1.0) |
| 438 | - | cb := h.wv.callbacks | 438 | + | // here. OnLoadEnd is a per-frame event, and during cross-site navigations |
| 439 | - | h.wv.mu.RUnlock() | 439 | + | // (process swap), CEF fires OnLoadEnd for the OLD main frame before the new |
| 440 | - | if cb != nil { | 440 | + | // page finishes loading. Dispatching LoadFinished here caused the progress |
| 441 | - | if cb.OnLoadChanged != nil { | 441 | + | // bar to hide prematurely while the new page was still loading. |
| 442 | - | h.wv.runOnGTK(func() { | 442 | + | // |
| 443 | - | cb.OnLoadChanged(port.LoadFinished) | 443 | + | // OnLoadingStateChange(isLoading=false) is the correct browser-level signal |
| 444 | - | }) | 444 | + | // for load completion — CEF guarantees it fires AFTER all OnLoadEnd calls. |
| 445 | - | } | 445 | + | // CEF's OnLoadingProgressChange also provides progress=1.0 at completion. |
| 446 | - | if cb.OnProgressChanged != nil { | |||
| 447 | - | h.wv.runOnGTK(func() { | |||
| 448 | - | cb.OnProgressChanged(1.0) | |||
| 449 | - | }) | |||
| 450 | - | } | |||
| 451 | - | } | |||
| 452 | } | 446 | } | ||
| 453 | 447 | ||||
| 454 | // OnLoadError is a no-op. | 448 | // OnLoadError is a no-op. | ||
M internal/infrastructure/cef/webview_handlers_test.go +21 -0
| @@ -5,6 +5,7 @@ import ( | |||
| 5 | 5 | "testing" | |
| 6 | 6 | ||
| 7 | 7 | purecef "github.com/bnema/purego-cef/cef" | |
| 8 | + | "github.com/stretchr/testify/assert" | |
| 8 | 9 | "github.com/stretchr/testify/require" | |
| 9 | 10 | ||
| 10 | 11 | "github.com/bnema/dumber/internal/application/port" | |
| @@ -68,6 +69,26 @@ func TestOnLoadStartFiresCommittedAndUpdatesURI(t *testing.T) { | |||
| 68 | 69 | require.Equal(t, "https://google.com", wv.URI()) | |
| 69 | 70 | } | |
| 70 | 71 | ||
| 72 | + | func TestOnLoadEndDoesNotDispatchBrowserLevelCompletion(t *testing.T) { | |
| 73 | + | wv := &WebView{ctx: context.Background()} | |
| 74 | + | var gotEvents []port.LoadEvent | |
| 75 | + | var gotProgress []float64 | |
| 76 | + | wv.SetCallbacks(&port.WebViewCallbacks{ | |
| 77 | + | OnLoadChanged: func(event port.LoadEvent) { | |
| 78 | + | gotEvents = append(gotEvents, event) | |
| 79 | + | }, | |
| 80 | + | OnProgressChanged: func(progress float64) { | |
| 81 | + | gotProgress = append(gotProgress, progress) | |
| 82 | + | }, | |
| 83 | + | }) | |
| 84 | + | ||
| 85 | + | h := &handlerSet{wv: wv} | |
| 86 | + | h.OnLoadEnd(nil, stubFrame{main: true, url: "https://reddit.com"}, 200) | |
| 87 | + | ||
| 88 | + | assert.Empty(t, gotEvents) | |
| 89 | + | assert.Empty(t, gotProgress) | |
| 90 | + | } | |
| 91 | + | ||
| 71 | 92 | func TestGetViewRectUsesDIPCoordinates(t *testing.T) { | |
| 72 | 93 | rect := &purecef.Rect{} | |
| 73 | 94 | h := &handlerSet{ | |
| @@ -5,6 +5,7 @@ import ( | |||||
| 5 | "testing" | 5 | "testing" | ||
| 6 | 6 | ||||
| 7 | purecef "github.com/bnema/purego-cef/cef" | 7 | purecef "github.com/bnema/purego-cef/cef" | ||
| 8 | + | "github.com/stretchr/testify/assert" | |||
| 8 | "github.com/stretchr/testify/require" | 9 | "github.com/stretchr/testify/require" | ||
| 9 | 10 | ||||
| 10 | "github.com/bnema/dumber/internal/application/port" | 11 | "github.com/bnema/dumber/internal/application/port" | ||
| @@ -68,6 +69,26 @@ func TestOnLoadStartFiresCommittedAndUpdatesURI(t *testing.T) { | |||||
| 68 | require.Equal(t, "https://google.com", wv.URI()) | 69 | require.Equal(t, "https://google.com", wv.URI()) | ||
| 69 | } | 70 | } | ||
| 70 | 71 | ||||
| 72 | + | func TestOnLoadEndDoesNotDispatchBrowserLevelCompletion(t *testing.T) { | |||
| 73 | + | wv := &WebView{ctx: context.Background()} | |||
| 74 | + | var gotEvents []port.LoadEvent | |||
| 75 | + | var gotProgress []float64 | |||
| 76 | + | wv.SetCallbacks(&port.WebViewCallbacks{ | |||
| 77 | + | OnLoadChanged: func(event port.LoadEvent) { | |||
| 78 | + | gotEvents = append(gotEvents, event) | |||
| 79 | + | }, | |||
| 80 | + | OnProgressChanged: func(progress float64) { | |||
| 81 | + | gotProgress = append(gotProgress, progress) | |||
| 82 | + | }, | |||
| 83 | + | }) | |||
| 84 | + | ||||
| 85 | + | h := &handlerSet{wv: wv} | |||
| 86 | + | h.OnLoadEnd(nil, stubFrame{main: true, url: "https://reddit.com"}, 200) | |||
| 87 | + | ||||
| 88 | + | assert.Empty(t, gotEvents) | |||
| 89 | + | assert.Empty(t, gotProgress) | |||
| 90 | + | } | |||
| 91 | + | ||||
| 71 | func TestGetViewRectUsesDIPCoordinates(t *testing.T) { | 92 | func TestGetViewRectUsesDIPCoordinates(t *testing.T) { | ||
| 72 | rect := &purecef.Rect{} | 93 | rect := &purecef.Rect{} | ||
| 73 | h := &handlerSet{ | 94 | h := &handlerSet{ | ||
M internal/infrastructure/config/defaults.go +1 -1
| @@ -70,7 +70,7 @@ const ( | |||
| 70 | 70 | defaultCEFWindowlessFrameRate = 60 // OSR frame rate for CEF | |
| 71 | 71 | ||
| 72 | 72 | // Transcoding defaults | |
| 73 | - | defaultTranscodingEnabled = true | |
| 73 | + | defaultTranscodingEnabled = false | |
| 74 | 74 | defaultTranscodingHWAccel = "auto" | |
| 75 | 75 | defaultTranscodingMaxConcurrent = 3 | |
| 76 | 76 | defaultTranscodingQuality = "medium" | |
| @@ -70,7 +70,7 @@ const ( | |||||
| 70 | defaultCEFWindowlessFrameRate = 60 // OSR frame rate for CEF | 70 | defaultCEFWindowlessFrameRate = 60 // OSR frame rate for CEF | ||
| 71 | 71 | ||||
| 72 | // Transcoding defaults | 72 | // Transcoding defaults | ||
| 73 | - | defaultTranscodingEnabled = true | 73 | + | defaultTranscodingEnabled = false |
| 74 | defaultTranscodingHWAccel = "auto" | 74 | defaultTranscodingHWAccel = "auto" | ||
| 75 | defaultTranscodingMaxConcurrent = 3 | 75 | defaultTranscodingMaxConcurrent = 3 | ||
| 76 | defaultTranscodingQuality = "medium" | 76 | defaultTranscodingQuality = "medium" | ||
M internal/infrastructure/config/defaults_test.go +1 -1
| @@ -11,7 +11,7 @@ func TestDefaultConfig_CoreDefaults(t *testing.T) { | |||
| 11 | 11 | assert.Equal(t, "info", cfg.Logging.Level) | |
| 12 | 12 | assert.False(t, cfg.Logging.CaptureGTKLogs) | |
| 13 | 13 | assert.False(t, cfg.Media.ShowDiagnosticsOnStartup) | |
| 14 | - | assert.True(t, cfg.Transcoding.Enabled) | |
| 14 | + | assert.False(t, cfg.Transcoding.Enabled) | |
| 15 | 15 | assert.Equal(t, "auto", cfg.Transcoding.HWAccel) | |
| 16 | 16 | assert.Equal(t, 3, cfg.Transcoding.MaxConcurrent) | |
| 17 | 17 | assert.Equal(t, "medium", cfg.Transcoding.Quality) | |
| @@ -11,7 +11,7 @@ func TestDefaultConfig_CoreDefaults(t *testing.T) { | |||||
| 11 | assert.Equal(t, "info", cfg.Logging.Level) | 11 | assert.Equal(t, "info", cfg.Logging.Level) | ||
| 12 | assert.False(t, cfg.Logging.CaptureGTKLogs) | 12 | assert.False(t, cfg.Logging.CaptureGTKLogs) | ||
| 13 | assert.False(t, cfg.Media.ShowDiagnosticsOnStartup) | 13 | assert.False(t, cfg.Media.ShowDiagnosticsOnStartup) | ||
| 14 | - | assert.True(t, cfg.Transcoding.Enabled) | 14 | + | assert.False(t, cfg.Transcoding.Enabled) |
| 15 | assert.Equal(t, "auto", cfg.Transcoding.HWAccel) | 15 | assert.Equal(t, "auto", cfg.Transcoding.HWAccel) | ||
| 16 | assert.Equal(t, 3, cfg.Transcoding.MaxConcurrent) | 16 | assert.Equal(t, 3, cfg.Transcoding.MaxConcurrent) | ||
| 17 | assert.Equal(t, "medium", cfg.Transcoding.Quality) | 17 | assert.Equal(t, "medium", cfg.Transcoding.Quality) | ||
M internal/infrastructure/config/loader_test.go +2 -2
| @@ -28,7 +28,7 @@ func TestSetEngineDefaults(t *testing.T) { | |||
| 28 | 28 | assert.False(t, mgr.viper.GetBool("engine.webkit.disable_dmabuf_renderer")) | |
| 29 | 29 | ||
| 30 | 30 | // Transcoding defaults | |
| 31 | - | assert.True(t, mgr.viper.GetBool("transcoding.enabled")) | |
| 31 | + | assert.False(t, mgr.viper.GetBool("transcoding.enabled")) | |
| 32 | 32 | assert.Equal(t, "auto", mgr.viper.GetString("transcoding.hwaccel")) | |
| 33 | 33 | assert.Equal(t, 3, mgr.viper.GetInt("transcoding.max_concurrent")) | |
| 34 | 34 | assert.Equal(t, "medium", mgr.viper.GetString("transcoding.quality")) | |
| @@ -74,7 +74,7 @@ type = "cef" | |||
| 74 | 74 | ||
| 75 | 75 | cfg := mgr.Get() | |
| 76 | 76 | require.NotNil(t, cfg) | |
| 77 | - | assert.True(t, cfg.Transcoding.Enabled) | |
| 77 | + | assert.False(t, cfg.Transcoding.Enabled) | |
| 78 | 78 | assert.Equal(t, "auto", cfg.Transcoding.HWAccel) | |
| 79 | 79 | assert.Equal(t, 3, cfg.Transcoding.MaxConcurrent) | |
| 80 | 80 | assert.Equal(t, "medium", cfg.Transcoding.Quality) | |
| @@ -28,7 +28,7 @@ func TestSetEngineDefaults(t *testing.T) { | |||||
| 28 | assert.False(t, mgr.viper.GetBool("engine.webkit.disable_dmabuf_renderer")) | 28 | assert.False(t, mgr.viper.GetBool("engine.webkit.disable_dmabuf_renderer")) | ||
| 29 | 29 | ||||
| 30 | // Transcoding defaults | 30 | // Transcoding defaults | ||
| 31 | - | assert.True(t, mgr.viper.GetBool("transcoding.enabled")) | 31 | + | assert.False(t, mgr.viper.GetBool("transcoding.enabled")) |
| 32 | assert.Equal(t, "auto", mgr.viper.GetString("transcoding.hwaccel")) | 32 | assert.Equal(t, "auto", mgr.viper.GetString("transcoding.hwaccel")) | ||
| 33 | assert.Equal(t, 3, mgr.viper.GetInt("transcoding.max_concurrent")) | 33 | assert.Equal(t, 3, mgr.viper.GetInt("transcoding.max_concurrent")) | ||
| 34 | assert.Equal(t, "medium", mgr.viper.GetString("transcoding.quality")) | 34 | assert.Equal(t, "medium", mgr.viper.GetString("transcoding.quality")) | ||
| @@ -74,7 +74,7 @@ type = "cef" | |||||
| 74 | 74 | ||||
| 75 | cfg := mgr.Get() | 75 | cfg := mgr.Get() | ||
| 76 | require.NotNil(t, cfg) | 76 | require.NotNil(t, cfg) | ||
| 77 | - | assert.True(t, cfg.Transcoding.Enabled) | 77 | + | assert.False(t, cfg.Transcoding.Enabled) |
| 78 | assert.Equal(t, "auto", cfg.Transcoding.HWAccel) | 78 | assert.Equal(t, "auto", cfg.Transcoding.HWAccel) | ||
| 79 | assert.Equal(t, 3, cfg.Transcoding.MaxConcurrent) | 79 | assert.Equal(t, 3, cfg.Transcoding.MaxConcurrent) | ||
| 80 | assert.Equal(t, "medium", cfg.Transcoding.Quality) | 80 | assert.Equal(t, "medium", cfg.Transcoding.Quality) | ||
M internal/infrastructure/config/schema.go +1 -1
| @@ -369,7 +369,7 @@ type DownloadsConfig struct { | |||
| 369 | 369 | // When enabled, proprietary video formats (H.264, HEVC) are transcoded | |
| 370 | 370 | // to open codecs (VP9, AV1) that CEF can decode natively. | |
| 371 | 371 | type TranscodingConfig struct { | |
| 372 | - | // Enabled controls whether transcoding is active (default: true). | |
| 372 | + | // Enabled controls whether transcoding is active (default: false). | |
| 373 | 373 | Enabled bool `mapstructure:"enabled" yaml:"enabled" toml:"enabled"` | |
| 374 | 374 | // HWAccel selects the hardware acceleration API: "auto", "vaapi", or "nvenc". | |
| 375 | 375 | // Default: "auto" (tries VAAPI first, then CUDA). | |
| @@ -369,7 +369,7 @@ type DownloadsConfig struct { | |||||
| 369 | // When enabled, proprietary video formats (H.264, HEVC) are transcoded | 369 | // When enabled, proprietary video formats (H.264, HEVC) are transcoded | ||
| 370 | // to open codecs (VP9, AV1) that CEF can decode natively. | 370 | // to open codecs (VP9, AV1) that CEF can decode natively. | ||
| 371 | type TranscodingConfig struct { | 371 | type TranscodingConfig struct { | ||
| 372 | - | // Enabled controls whether transcoding is active (default: true). | 372 | + | // Enabled controls whether transcoding is active (default: false). |
| 373 | Enabled bool `mapstructure:"enabled" yaml:"enabled" toml:"enabled"` | 373 | Enabled bool `mapstructure:"enabled" yaml:"enabled" toml:"enabled"` | ||
| 374 | // HWAccel selects the hardware acceleration API: "auto", "vaapi", or "nvenc". | 374 | // HWAccel selects the hardware acceleration API: "auto", "vaapi", or "nvenc". | ||
| 375 | // Default: "auto" (tries VAAPI first, then CUDA). | 375 | // Default: "auto" (tries VAAPI first, then CUDA). | ||
M internal/infrastructure/config/writer_test.go +3 -3
| @@ -47,7 +47,7 @@ func TestWriteConfigOrdered(t *testing.T) { | |||
| 47 | 47 | if !strings.Contains(string(content), "[transcoding]") { | |
| 48 | 48 | t.Fatalf("expected transcoding section in generated config, got:\n%s", string(content)) | |
| 49 | 49 | } | |
| 50 | - | // Match "enabled = true" specifically within the [transcoding] section to | |
| 50 | + | // Match "enabled = false" specifically within the [transcoding] section to | |
| 51 | 51 | // avoid false positives from other sections that may also have an enabled key. | |
| 52 | 52 | transcodingIdx := bytes.Index(content, []byte("[transcoding]")) | |
| 53 | 53 | if transcodingIdx < 0 { | |
| @@ -61,8 +61,8 @@ func TestWriteConfigOrdered(t *testing.T) { | |||
| 61 | 61 | } else { | |
| 62 | 62 | transcodingBlock = afterTranscoding | |
| 63 | 63 | } | |
| 64 | - | if !bytes.Contains(transcodingBlock, []byte("enabled = true")) { | |
| 65 | - | t.Fatalf("expected enabled = true in [transcoding] section, got:\n%s", string(transcodingBlock)) | |
| 64 | + | if !bytes.Contains(transcodingBlock, []byte("enabled = false")) { | |
| 65 | + | t.Fatalf("expected enabled = false in [transcoding] section, got:\n%s", string(transcodingBlock)) | |
| 66 | 66 | } | |
| 67 | 67 | ||
| 68 | 68 | t.Logf("Found %d sections, all sorted", len(sections)) | |
| @@ -47,7 +47,7 @@ func TestWriteConfigOrdered(t *testing.T) { | |||||
| 47 | if !strings.Contains(string(content), "[transcoding]") { | 47 | if !strings.Contains(string(content), "[transcoding]") { | ||
| 48 | t.Fatalf("expected transcoding section in generated config, got:\n%s", string(content)) | 48 | t.Fatalf("expected transcoding section in generated config, got:\n%s", string(content)) | ||
| 49 | } | 49 | } | ||
| 50 | - | // Match "enabled = true" specifically within the [transcoding] section to | 50 | + | // Match "enabled = false" specifically within the [transcoding] section to |
| 51 | // avoid false positives from other sections that may also have an enabled key. | 51 | // avoid false positives from other sections that may also have an enabled key. | ||
| 52 | transcodingIdx := bytes.Index(content, []byte("[transcoding]")) | 52 | transcodingIdx := bytes.Index(content, []byte("[transcoding]")) | ||
| 53 | if transcodingIdx < 0 { | 53 | if transcodingIdx < 0 { | ||
| @@ -61,8 +61,8 @@ func TestWriteConfigOrdered(t *testing.T) { | |||||
| 61 | } else { | 61 | } else { | ||
| 62 | transcodingBlock = afterTranscoding | 62 | transcodingBlock = afterTranscoding | ||
| 63 | } | 63 | } | ||
| 64 | - | if !bytes.Contains(transcodingBlock, []byte("enabled = true")) { | 64 | + | if !bytes.Contains(transcodingBlock, []byte("enabled = false")) { |
| 65 | - | t.Fatalf("expected enabled = true in [transcoding] section, got:\n%s", string(transcodingBlock)) | 65 | + | t.Fatalf("expected enabled = false in [transcoding] section, got:\n%s", string(transcodingBlock)) |
| 66 | } | 66 | } | ||
| 67 | 67 | ||||
| 68 | t.Logf("Found %d sections, all sorted", len(sections)) | 68 | t.Logf("Found %d sections, all sorted", len(sections)) | ||
M internal/ui/app.go +1 -1
| @@ -2579,7 +2579,7 @@ func (a *App) ensureFloatingSession( | |||
| 2579 | 2579 | // regular workspace panes. This allows the content coordinator's | |
| 2580 | 2580 | // existing callbacks (onLoadStarted, onProgressChanged, etc.) to find | |
| 2581 | 2581 | // and update the floating pane automatically. | |
| 2582 | - | pv := component.NewPaneView(a.widgetFactory, paneID, webViewWidget) | |
| 2582 | + | pv := component.NewPaneView(ctx, a.widgetFactory, paneID, webViewWidget) | |
| 2583 | 2583 | pv.SetActive(true) | |
| 2584 | 2584 | // Hide loading skeleton — floating panes have their own themed container. | |
| 2585 | 2585 | pv.HideLoadingSkeleton() | |
| @@ -2579,7 +2579,7 @@ func (a *App) ensureFloatingSession( | |||||
| 2579 | // regular workspace panes. This allows the content coordinator's | 2579 | // regular workspace panes. This allows the content coordinator's | ||
| 2580 | // existing callbacks (onLoadStarted, onProgressChanged, etc.) to find | 2580 | // existing callbacks (onLoadStarted, onProgressChanged, etc.) to find | ||
| 2581 | // and update the floating pane automatically. | 2581 | // and update the floating pane automatically. | ||
| 2582 | - | pv := component.NewPaneView(a.widgetFactory, paneID, webViewWidget) | 2582 | + | pv := component.NewPaneView(ctx, a.widgetFactory, paneID, webViewWidget) |
| 2583 | pv.SetActive(true) | 2583 | pv.SetActive(true) | ||
| 2584 | // Hide loading skeleton — floating panes have their own themed container. | 2584 | // Hide loading skeleton — floating panes have their own themed container. | ||
| 2585 | pv.HideLoadingSkeleton() | 2585 | pv.HideLoadingSkeleton() | ||
M internal/ui/component/pane_view.go +34 -2
| @@ -6,6 +6,7 @@ import ( | |||
| 6 | 6 | "sync" | |
| 7 | 7 | ||
| 8 | 8 | "github.com/bnema/dumber/internal/domain/entity" | |
| 9 | + | "github.com/bnema/dumber/internal/logging" | |
| 9 | 10 | "github.com/bnema/dumber/internal/ui/input" | |
| 10 | 11 | "github.com/bnema/dumber/internal/ui/layout" | |
| 11 | 12 | ) | |
| @@ -18,6 +19,7 @@ const ( | |||
| 18 | 19 | // PaneView is a container for a single WebView with active state indication. | |
| 19 | 20 | // It uses an overlay to display a border around the active pane. | |
| 20 | 21 | type PaneView struct { | |
| 22 | + | ctx context.Context | |
| 21 | 23 | factory layout.WidgetFactory | |
| 22 | 24 | overlay layout.OverlayWidget | |
| 23 | 25 | webViewWidget layout.Widget // The actual WebView widget | |
| @@ -40,7 +42,7 @@ type PaneView struct { | |||
| 40 | 42 | } | |
| 41 | 43 | ||
| 42 | 44 | // NewPaneView creates a new pane view container for a WebView widget. | |
| 43 | - | func NewPaneView(factory layout.WidgetFactory, paneID entity.PaneID, webViewWidget layout.Widget) *PaneView { | |
| 45 | + | func NewPaneView(ctx context.Context, factory layout.WidgetFactory, paneID entity.PaneID, webViewWidget layout.Widget) *PaneView { | |
| 44 | 46 | overlay := factory.NewOverlay() | |
| 45 | 47 | overlay.SetHexpand(true) | |
| 46 | 48 | overlay.SetVexpand(true) | |
| @@ -81,6 +83,7 @@ func NewPaneView(factory layout.WidgetFactory, paneID entity.PaneID, webViewWidg | |||
| 81 | 83 | // issues with the internal progress gizmo before the overlay is realized | |
| 82 | 84 | ||
| 83 | 85 | return &PaneView{ | |
| 86 | + | ctx: ctx, | |
| 84 | 87 | factory: factory, | |
| 85 | 88 | overlay: overlay, | |
| 86 | 89 | webViewWidget: webViewWidget, | |
| @@ -344,7 +347,16 @@ func (pv *PaneView) ensureProgressBar() *ProgressBar { | |||
| 344 | 347 | return pv.progressBar | |
| 345 | 348 | } | |
| 346 | 349 | ||
| 347 | - | pb := NewProgressBar(pv.factory) | |
| 350 | + | ctx := pv.ctx | |
| 351 | + | if ctx == nil { | |
| 352 | + | ctx = context.Background() | |
| 353 | + | } | |
| 354 | + | logging.FromContext(ctx). | |
| 355 | + | Debug(). | |
| 356 | + | Str("pane_id", string(pv.paneID)). | |
| 357 | + | Msg("creating progress bar") | |
| 358 | + | ||
| 359 | + | pb := NewProgressBar(pv.ctx, pv.factory) | |
| 348 | 360 | pv.overlay.AddOverlay(pb.Widget()) | |
| 349 | 361 | pv.overlay.SetClipOverlay(pb.Widget(), false) | |
| 350 | 362 | pv.overlay.SetMeasureOverlay(pb.Widget(), false) | |
| @@ -357,6 +369,15 @@ func (pv *PaneView) ensureProgressBar() *ProgressBar { | |||
| 357 | 369 | func (pv *PaneView) SetLoadProgress(progress float64) { | |
| 358 | 370 | pv.mu.Lock() | |
| 359 | 371 | pb := pv.ensureProgressBar() | |
| 372 | + | ctx := pv.ctx | |
| 373 | + | if ctx == nil { | |
| 374 | + | ctx = context.Background() | |
| 375 | + | } | |
| 376 | + | logging.FromContext(ctx). | |
| 377 | + | Debug(). | |
| 378 | + | Str("pane_id", string(pv.paneID)). | |
| 379 | + | Float64("progress", progress). | |
| 380 | + | Msg("setting load progress") | |
| 360 | 381 | pv.mu.Unlock() | |
| 361 | 382 | ||
| 362 | 383 | pb.SetProgress(progress) | |
| @@ -365,7 +386,18 @@ func (pv *PaneView) SetLoadProgress(progress float64) { | |||
| 365 | 386 | // SetLoading shows or hides the progress bar. | |
| 366 | 387 | func (pv *PaneView) SetLoading(loading bool) { | |
| 367 | 388 | pv.mu.Lock() | |
| 389 | + | hadProgressBar := pv.progressBar != nil | |
| 368 | 390 | pb := pv.ensureProgressBar() | |
| 391 | + | ctx := pv.ctx | |
| 392 | + | if ctx == nil { | |
| 393 | + | ctx = context.Background() | |
| 394 | + | } | |
| 395 | + | logging.FromContext(ctx). | |
| 396 | + | Debug(). | |
| 397 | + | Str("pane_id", string(pv.paneID)). | |
| 398 | + | Bool("loading", loading). | |
| 399 | + | Bool("progress_bar_existed", hadProgressBar). | |
| 400 | + | Msg("setting loading state") | |
| 369 | 401 | pv.mu.Unlock() | |
| 370 | 402 | ||
| 371 | 403 | if loading { | |
| @@ -6,6 +6,7 @@ import ( | |||||
| 6 | "sync" | 6 | "sync" | ||
| 7 | 7 | ||||
| 8 | "github.com/bnema/dumber/internal/domain/entity" | 8 | "github.com/bnema/dumber/internal/domain/entity" | ||
| 9 | + | "github.com/bnema/dumber/internal/logging" | |||
| 9 | "github.com/bnema/dumber/internal/ui/input" | 10 | "github.com/bnema/dumber/internal/ui/input" | ||
| 10 | "github.com/bnema/dumber/internal/ui/layout" | 11 | "github.com/bnema/dumber/internal/ui/layout" | ||
| 11 | ) | 12 | ) | ||
| @@ -18,6 +19,7 @@ const ( | |||||
| 18 | // PaneView is a container for a single WebView with active state indication. | 19 | // PaneView is a container for a single WebView with active state indication. | ||
| 19 | // It uses an overlay to display a border around the active pane. | 20 | // It uses an overlay to display a border around the active pane. | ||
| 20 | type PaneView struct { | 21 | type PaneView struct { | ||
| 22 | + | ctx context.Context | |||
| 21 | factory layout.WidgetFactory | 23 | factory layout.WidgetFactory | ||
| 22 | overlay layout.OverlayWidget | 24 | overlay layout.OverlayWidget | ||
| 23 | webViewWidget layout.Widget // The actual WebView widget | 25 | webViewWidget layout.Widget // The actual WebView widget | ||
| @@ -40,7 +42,7 @@ type PaneView struct { | |||||
| 40 | } | 42 | } | ||
| 41 | 43 | ||||
| 42 | // NewPaneView creates a new pane view container for a WebView widget. | 44 | // NewPaneView creates a new pane view container for a WebView widget. | ||
| 43 | - | func NewPaneView(factory layout.WidgetFactory, paneID entity.PaneID, webViewWidget layout.Widget) *PaneView { | 45 | + | func NewPaneView(ctx context.Context, factory layout.WidgetFactory, paneID entity.PaneID, webViewWidget layout.Widget) *PaneView { |
| 44 | overlay := factory.NewOverlay() | 46 | overlay := factory.NewOverlay() | ||
| 45 | overlay.SetHexpand(true) | 47 | overlay.SetHexpand(true) | ||
| 46 | overlay.SetVexpand(true) | 48 | overlay.SetVexpand(true) | ||
| @@ -81,6 +83,7 @@ func NewPaneView(factory layout.WidgetFactory, paneID entity.PaneID, webViewWidg | |||||
| 81 | // issues with the internal progress gizmo before the overlay is realized | 83 | // issues with the internal progress gizmo before the overlay is realized | ||
| 82 | 84 | ||||
| 83 | return &PaneView{ | 85 | return &PaneView{ | ||
| 86 | + | ctx: ctx, | |||
| 84 | factory: factory, | 87 | factory: factory, | ||
| 85 | overlay: overlay, | 88 | overlay: overlay, | ||
| 86 | webViewWidget: webViewWidget, | 89 | webViewWidget: webViewWidget, | ||
| @@ -344,7 +347,16 @@ func (pv *PaneView) ensureProgressBar() *ProgressBar { | |||||
| 344 | return pv.progressBar | 347 | return pv.progressBar | ||
| 345 | } | 348 | } | ||
| 346 | 349 | ||||
| 347 | - | pb := NewProgressBar(pv.factory) | 350 | + | ctx := pv.ctx |
| 351 | + | if ctx == nil { | |||
| 352 | + | ctx = context.Background() | |||
| 353 | + | } | |||
| 354 | + | logging.FromContext(ctx). | |||
| 355 | + | Debug(). | |||
| 356 | + | Str("pane_id", string(pv.paneID)). | |||
| 357 | + | Msg("creating progress bar") | |||
| 358 | + | ||||
| 359 | + | pb := NewProgressBar(pv.ctx, pv.factory) | |||
| 348 | pv.overlay.AddOverlay(pb.Widget()) | 360 | pv.overlay.AddOverlay(pb.Widget()) | ||
| 349 | pv.overlay.SetClipOverlay(pb.Widget(), false) | 361 | pv.overlay.SetClipOverlay(pb.Widget(), false) | ||
| 350 | pv.overlay.SetMeasureOverlay(pb.Widget(), false) | 362 | pv.overlay.SetMeasureOverlay(pb.Widget(), false) | ||
| @@ -357,6 +369,15 @@ func (pv *PaneView) ensureProgressBar() *ProgressBar { | |||||
| 357 | func (pv *PaneView) SetLoadProgress(progress float64) { | 369 | func (pv *PaneView) SetLoadProgress(progress float64) { | ||
| 358 | pv.mu.Lock() | 370 | pv.mu.Lock() | ||
| 359 | pb := pv.ensureProgressBar() | 371 | pb := pv.ensureProgressBar() | ||
| 372 | + | ctx := pv.ctx | |||
| 373 | + | if ctx == nil { | |||
| 374 | + | ctx = context.Background() | |||
| 375 | + | } | |||
| 376 | + | logging.FromContext(ctx). | |||
| 377 | + | Debug(). | |||
| 378 | + | Str("pane_id", string(pv.paneID)). | |||
| 379 | + | Float64("progress", progress). | |||
| 380 | + | Msg("setting load progress") | |||
| 360 | pv.mu.Unlock() | 381 | pv.mu.Unlock() | ||
| 361 | 382 | ||||
| 362 | pb.SetProgress(progress) | 383 | pb.SetProgress(progress) | ||
| @@ -365,7 +386,18 @@ func (pv *PaneView) SetLoadProgress(progress float64) { | |||||
| 365 | // SetLoading shows or hides the progress bar. | 386 | // SetLoading shows or hides the progress bar. | ||
| 366 | func (pv *PaneView) SetLoading(loading bool) { | 387 | func (pv *PaneView) SetLoading(loading bool) { | ||
| 367 | pv.mu.Lock() | 388 | pv.mu.Lock() | ||
| 389 | + | hadProgressBar := pv.progressBar != nil | |||
| 368 | pb := pv.ensureProgressBar() | 390 | pb := pv.ensureProgressBar() | ||
| 391 | + | ctx := pv.ctx | |||
| 392 | + | if ctx == nil { | |||
| 393 | + | ctx = context.Background() | |||
| 394 | + | } | |||
| 395 | + | logging.FromContext(ctx). | |||
| 396 | + | Debug(). | |||
| 397 | + | Str("pane_id", string(pv.paneID)). | |||
| 398 | + | Bool("loading", loading). | |||
| 399 | + | Bool("progress_bar_existed", hadProgressBar). | |||
| 400 | + | Msg("setting loading state") | |||
| 369 | pv.mu.Unlock() | 401 | pv.mu.Unlock() | ||
| 370 | 402 | ||||
| 371 | if loading { | 403 | if loading { | ||
M internal/ui/component/pane_view_test.go +24 -23
| @@ -1,6 +1,7 @@ | |||
| 1 | 1 | package component_test | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "context" | |
| 4 | 5 | "testing" | |
| 5 | 6 | ||
| 6 | 7 | "github.com/stretchr/testify/assert" | |
| @@ -47,7 +48,7 @@ func TestNewPaneView_CreatesOverlay(t *testing.T) { | |||
| 47 | 48 | mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once() | |
| 48 | 49 | ||
| 49 | 50 | // Act | |
| 50 | - | pv := component.NewPaneView(mockFactory, paneID, mockWebView) | |
| 51 | + | pv := component.NewPaneView(context.Background(), mockFactory, paneID, mockWebView) | |
| 51 | 52 | ||
| 52 | 53 | // Assert | |
| 53 | 54 | require.NotNil(t, pv) | |
| @@ -88,7 +89,7 @@ func TestNewPaneView_NilWebView(t *testing.T) { | |||
| 88 | 89 | mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once() | |
| 89 | 90 | ||
| 90 | 91 | // Act | |
| 91 | - | pv := component.NewPaneView(mockFactory, paneID, nil) | |
| 92 | + | pv := component.NewPaneView(context.Background(), mockFactory, paneID, nil) | |
| 92 | 93 | ||
| 93 | 94 | // Assert | |
| 94 | 95 | require.NotNil(t, pv) | |
| @@ -104,7 +105,7 @@ func TestSetActive_True_AddsCSSClass(t *testing.T) { | |||
| 104 | 105 | ||
| 105 | 106 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | |
| 106 | 107 | ||
| 107 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 108 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 108 | 109 | ||
| 109 | 110 | // Expect CSS class to be added | |
| 110 | 111 | mockBorderBox.EXPECT().AddCssClass("pane-active").Once() | |
| @@ -125,7 +126,7 @@ func TestSetActive_False_RemovesCSSClass(t *testing.T) { | |||
| 125 | 126 | ||
| 126 | 127 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | |
| 127 | 128 | ||
| 128 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 129 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 129 | 130 | ||
| 130 | 131 | // First activate | |
| 131 | 132 | mockBorderBox.EXPECT().AddCssClass("pane-active").Once() | |
| @@ -150,7 +151,7 @@ func TestSetActive_NoChangeWhenSameState(t *testing.T) { | |||
| 150 | 151 | ||
| 151 | 152 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | |
| 152 | 153 | ||
| 153 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 154 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 154 | 155 | ||
| 155 | 156 | // Act - setting false when already false should not call RemoveCssClass | |
| 156 | 157 | pv.SetActive(false) | |
| @@ -169,7 +170,7 @@ func TestPaneID_ReturnsPaneID(t *testing.T) { | |||
| 169 | 170 | paneID := entity.PaneID("test-pane-123") | |
| 170 | 171 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | |
| 171 | 172 | ||
| 172 | - | pv := component.NewPaneView(mockFactory, paneID, mockWebView) | |
| 173 | + | pv := component.NewPaneView(context.Background(), mockFactory, paneID, mockWebView) | |
| 173 | 174 | ||
| 174 | 175 | // Act | |
| 175 | 176 | result := pv.PaneID() | |
| @@ -187,7 +188,7 @@ func TestWebViewWidget_ReturnsWebView(t *testing.T) { | |||
| 187 | 188 | ||
| 188 | 189 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | |
| 189 | 190 | ||
| 190 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 191 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 191 | 192 | ||
| 192 | 193 | // Act | |
| 193 | 194 | result := pv.WebViewWidget() | |
| @@ -206,7 +207,7 @@ func TestSetWebViewWidget_ReplacesWidget(t *testing.T) { | |||
| 206 | 207 | ||
| 207 | 208 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockOldWebView) | |
| 208 | 209 | ||
| 209 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockOldWebView) | |
| 210 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockOldWebView) | |
| 210 | 211 | ||
| 211 | 212 | // Expect removal of old widget and addition of new | |
| 212 | 213 | mockOverlay.EXPECT().SetChild(nil).Once() | |
| @@ -229,7 +230,7 @@ func TestSetWebViewWidget_FromNil(t *testing.T) { | |||
| 229 | 230 | ||
| 230 | 231 | setupPaneViewMocksNoWebView(t, mockFactory, mockOverlay, mockBorderBox) | |
| 231 | 232 | ||
| 232 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), nil) | |
| 233 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), nil) | |
| 233 | 234 | ||
| 234 | 235 | // Expect only setting new child (no removal since old was nil) | |
| 235 | 236 | mockNewWebView.EXPECT().GetParent().Return(nil).Once() | |
| @@ -251,7 +252,7 @@ func TestGrabFocus_DelegatesToWebView(t *testing.T) { | |||
| 251 | 252 | ||
| 252 | 253 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | |
| 253 | 254 | ||
| 254 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 255 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 255 | 256 | ||
| 256 | 257 | mockWebView.EXPECT().GrabFocus().Return(true).Once() | |
| 257 | 258 | ||
| @@ -270,7 +271,7 @@ func TestGrabFocus_NilWebView_ReturnsFalse(t *testing.T) { | |||
| 270 | 271 | ||
| 271 | 272 | setupPaneViewMocksNoWebView(t, mockFactory, mockOverlay, mockBorderBox) | |
| 272 | 273 | ||
| 273 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), nil) | |
| 274 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), nil) | |
| 274 | 275 | ||
| 275 | 276 | // Act | |
| 276 | 277 | result := pv.GrabFocus() | |
| @@ -288,7 +289,7 @@ func TestHasFocus_DelegatesToWebView(t *testing.T) { | |||
| 288 | 289 | ||
| 289 | 290 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | |
| 290 | 291 | ||
| 291 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 292 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 292 | 293 | ||
| 293 | 294 | mockWebView.EXPECT().HasFocus().Return(true).Once() | |
| 294 | 295 | ||
| @@ -307,7 +308,7 @@ func TestHasFocus_NilWebView_ReturnsFalse(t *testing.T) { | |||
| 307 | 308 | ||
| 308 | 309 | setupPaneViewMocksNoWebView(t, mockFactory, mockOverlay, mockBorderBox) | |
| 309 | 310 | ||
| 310 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), nil) | |
| 311 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), nil) | |
| 311 | 312 | ||
| 312 | 313 | // Act | |
| 313 | 314 | result := pv.HasFocus() | |
| @@ -325,7 +326,7 @@ func TestWidget_ReturnsOverlay(t *testing.T) { | |||
| 325 | 326 | ||
| 326 | 327 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | |
| 327 | 328 | ||
| 328 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 329 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 329 | 330 | ||
| 330 | 331 | // Act | |
| 331 | 332 | result := pv.Widget() | |
| @@ -343,7 +344,7 @@ func TestOverlay_ReturnsOverlayWidget(t *testing.T) { | |||
| 343 | 344 | ||
| 344 | 345 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | |
| 345 | 346 | ||
| 346 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 347 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 347 | 348 | ||
| 348 | 349 | // Act | |
| 349 | 350 | result := pv.Overlay() | |
| @@ -361,7 +362,7 @@ func TestShow_DelegatesToOverlay(t *testing.T) { | |||
| 361 | 362 | ||
| 362 | 363 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | |
| 363 | 364 | ||
| 364 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 365 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 365 | 366 | ||
| 366 | 367 | mockOverlay.EXPECT().Show().Once() | |
| 367 | 368 | ||
| @@ -380,7 +381,7 @@ func TestHide_DelegatesToOverlay(t *testing.T) { | |||
| 380 | 381 | ||
| 381 | 382 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | |
| 382 | 383 | ||
| 383 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 384 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 384 | 385 | ||
| 385 | 386 | mockOverlay.EXPECT().Hide().Once() | |
| 386 | 387 | ||
| @@ -399,7 +400,7 @@ func TestSetVisible_DelegatesToOverlay(t *testing.T) { | |||
| 399 | 400 | ||
| 400 | 401 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | |
| 401 | 402 | ||
| 402 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 403 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 403 | 404 | ||
| 404 | 405 | mockOverlay.EXPECT().SetVisible(true).Once() | |
| 405 | 406 | ||
| @@ -418,7 +419,7 @@ func TestIsVisible_DelegatesToOverlay(t *testing.T) { | |||
| 418 | 419 | ||
| 419 | 420 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | |
| 420 | 421 | ||
| 421 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 422 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 422 | 423 | ||
| 423 | 424 | mockOverlay.EXPECT().IsVisible().Return(true).Once() | |
| 424 | 425 | ||
| @@ -438,7 +439,7 @@ func TestAddCssClass_DelegatesToOverlay(t *testing.T) { | |||
| 438 | 439 | ||
| 439 | 440 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | |
| 440 | 441 | ||
| 441 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 442 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 442 | 443 | ||
| 443 | 444 | mockOverlay.EXPECT().AddCssClass("custom-class").Once() | |
| 444 | 445 | ||
| @@ -457,7 +458,7 @@ func TestRemoveCssClass_DelegatesToOverlay(t *testing.T) { | |||
| 457 | 458 | ||
| 458 | 459 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | |
| 459 | 460 | ||
| 460 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 461 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 461 | 462 | ||
| 462 | 463 | mockOverlay.EXPECT().RemoveCssClass("custom-class").Once() | |
| 463 | 464 | ||
| @@ -476,7 +477,7 @@ func TestSetOnFocusIn_SetsCallback(t *testing.T) { | |||
| 476 | 477 | ||
| 477 | 478 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | |
| 478 | 479 | ||
| 479 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 480 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 480 | 481 | ||
| 481 | 482 | callback := func(paneID entity.PaneID) {} | |
| 482 | 483 | ||
| @@ -496,7 +497,7 @@ func TestSetOnFocusOut_SetsCallback(t *testing.T) { | |||
| 496 | 497 | ||
| 497 | 498 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | |
| 498 | 499 | ||
| 499 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 500 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | |
| 500 | 501 | ||
| 501 | 502 | callback := func(paneID entity.PaneID) {} | |
| 502 | 503 | ||
| @@ -1,6 +1,7 @@ | |||||
| 1 | package component_test | 1 | package component_test | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "context" | |||
| 4 | "testing" | 5 | "testing" | ||
| 5 | 6 | ||||
| 6 | "github.com/stretchr/testify/assert" | 7 | "github.com/stretchr/testify/assert" | ||
| @@ -47,7 +48,7 @@ func TestNewPaneView_CreatesOverlay(t *testing.T) { | |||||
| 47 | mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once() | 48 | mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once() | ||
| 48 | 49 | ||||
| 49 | // Act | 50 | // Act | ||
| 50 | - | pv := component.NewPaneView(mockFactory, paneID, mockWebView) | 51 | + | pv := component.NewPaneView(context.Background(), mockFactory, paneID, mockWebView) |
| 51 | 52 | ||||
| 52 | // Assert | 53 | // Assert | ||
| 53 | require.NotNil(t, pv) | 54 | require.NotNil(t, pv) | ||
| @@ -88,7 +89,7 @@ func TestNewPaneView_NilWebView(t *testing.T) { | |||||
| 88 | mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once() | 89 | mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once() | ||
| 89 | 90 | ||||
| 90 | // Act | 91 | // Act | ||
| 91 | - | pv := component.NewPaneView(mockFactory, paneID, nil) | 92 | + | pv := component.NewPaneView(context.Background(), mockFactory, paneID, nil) |
| 92 | 93 | ||||
| 93 | // Assert | 94 | // Assert | ||
| 94 | require.NotNil(t, pv) | 95 | require.NotNil(t, pv) | ||
| @@ -104,7 +105,7 @@ func TestSetActive_True_AddsCSSClass(t *testing.T) { | |||||
| 104 | 105 | ||||
| 105 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | 106 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | ||
| 106 | 107 | ||||
| 107 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | 108 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) |
| 108 | 109 | ||||
| 109 | // Expect CSS class to be added | 110 | // Expect CSS class to be added | ||
| 110 | mockBorderBox.EXPECT().AddCssClass("pane-active").Once() | 111 | mockBorderBox.EXPECT().AddCssClass("pane-active").Once() | ||
| @@ -125,7 +126,7 @@ func TestSetActive_False_RemovesCSSClass(t *testing.T) { | |||||
| 125 | 126 | ||||
| 126 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | 127 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | ||
| 127 | 128 | ||||
| 128 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | 129 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) |
| 129 | 130 | ||||
| 130 | // First activate | 131 | // First activate | ||
| 131 | mockBorderBox.EXPECT().AddCssClass("pane-active").Once() | 132 | mockBorderBox.EXPECT().AddCssClass("pane-active").Once() | ||
| @@ -150,7 +151,7 @@ func TestSetActive_NoChangeWhenSameState(t *testing.T) { | |||||
| 150 | 151 | ||||
| 151 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | 152 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | ||
| 152 | 153 | ||||
| 153 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | 154 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) |
| 154 | 155 | ||||
| 155 | // Act - setting false when already false should not call RemoveCssClass | 156 | // Act - setting false when already false should not call RemoveCssClass | ||
| 156 | pv.SetActive(false) | 157 | pv.SetActive(false) | ||
| @@ -169,7 +170,7 @@ func TestPaneID_ReturnsPaneID(t *testing.T) { | |||||
| 169 | paneID := entity.PaneID("test-pane-123") | 170 | paneID := entity.PaneID("test-pane-123") | ||
| 170 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | 171 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | ||
| 171 | 172 | ||||
| 172 | - | pv := component.NewPaneView(mockFactory, paneID, mockWebView) | 173 | + | pv := component.NewPaneView(context.Background(), mockFactory, paneID, mockWebView) |
| 173 | 174 | ||||
| 174 | // Act | 175 | // Act | ||
| 175 | result := pv.PaneID() | 176 | result := pv.PaneID() | ||
| @@ -187,7 +188,7 @@ func TestWebViewWidget_ReturnsWebView(t *testing.T) { | |||||
| 187 | 188 | ||||
| 188 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | 189 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | ||
| 189 | 190 | ||||
| 190 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | 191 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) |
| 191 | 192 | ||||
| 192 | // Act | 193 | // Act | ||
| 193 | result := pv.WebViewWidget() | 194 | result := pv.WebViewWidget() | ||
| @@ -206,7 +207,7 @@ func TestSetWebViewWidget_ReplacesWidget(t *testing.T) { | |||||
| 206 | 207 | ||||
| 207 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockOldWebView) | 208 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockOldWebView) | ||
| 208 | 209 | ||||
| 209 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockOldWebView) | 210 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockOldWebView) |
| 210 | 211 | ||||
| 211 | // Expect removal of old widget and addition of new | 212 | // Expect removal of old widget and addition of new | ||
| 212 | mockOverlay.EXPECT().SetChild(nil).Once() | 213 | mockOverlay.EXPECT().SetChild(nil).Once() | ||
| @@ -229,7 +230,7 @@ func TestSetWebViewWidget_FromNil(t *testing.T) { | |||||
| 229 | 230 | ||||
| 230 | setupPaneViewMocksNoWebView(t, mockFactory, mockOverlay, mockBorderBox) | 231 | setupPaneViewMocksNoWebView(t, mockFactory, mockOverlay, mockBorderBox) | ||
| 231 | 232 | ||||
| 232 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), nil) | 233 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), nil) |
| 233 | 234 | ||||
| 234 | // Expect only setting new child (no removal since old was nil) | 235 | // Expect only setting new child (no removal since old was nil) | ||
| 235 | mockNewWebView.EXPECT().GetParent().Return(nil).Once() | 236 | mockNewWebView.EXPECT().GetParent().Return(nil).Once() | ||
| @@ -251,7 +252,7 @@ func TestGrabFocus_DelegatesToWebView(t *testing.T) { | |||||
| 251 | 252 | ||||
| 252 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | 253 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | ||
| 253 | 254 | ||||
| 254 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | 255 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) |
| 255 | 256 | ||||
| 256 | mockWebView.EXPECT().GrabFocus().Return(true).Once() | 257 | mockWebView.EXPECT().GrabFocus().Return(true).Once() | ||
| 257 | 258 | ||||
| @@ -270,7 +271,7 @@ func TestGrabFocus_NilWebView_ReturnsFalse(t *testing.T) { | |||||
| 270 | 271 | ||||
| 271 | setupPaneViewMocksNoWebView(t, mockFactory, mockOverlay, mockBorderBox) | 272 | setupPaneViewMocksNoWebView(t, mockFactory, mockOverlay, mockBorderBox) | ||
| 272 | 273 | ||||
| 273 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), nil) | 274 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), nil) |
| 274 | 275 | ||||
| 275 | // Act | 276 | // Act | ||
| 276 | result := pv.GrabFocus() | 277 | result := pv.GrabFocus() | ||
| @@ -288,7 +289,7 @@ func TestHasFocus_DelegatesToWebView(t *testing.T) { | |||||
| 288 | 289 | ||||
| 289 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | 290 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | ||
| 290 | 291 | ||||
| 291 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | 292 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) |
| 292 | 293 | ||||
| 293 | mockWebView.EXPECT().HasFocus().Return(true).Once() | 294 | mockWebView.EXPECT().HasFocus().Return(true).Once() | ||
| 294 | 295 | ||||
| @@ -307,7 +308,7 @@ func TestHasFocus_NilWebView_ReturnsFalse(t *testing.T) { | |||||
| 307 | 308 | ||||
| 308 | setupPaneViewMocksNoWebView(t, mockFactory, mockOverlay, mockBorderBox) | 309 | setupPaneViewMocksNoWebView(t, mockFactory, mockOverlay, mockBorderBox) | ||
| 309 | 310 | ||||
| 310 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), nil) | 311 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), nil) |
| 311 | 312 | ||||
| 312 | // Act | 313 | // Act | ||
| 313 | result := pv.HasFocus() | 314 | result := pv.HasFocus() | ||
| @@ -325,7 +326,7 @@ func TestWidget_ReturnsOverlay(t *testing.T) { | |||||
| 325 | 326 | ||||
| 326 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | 327 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | ||
| 327 | 328 | ||||
| 328 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | 329 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) |
| 329 | 330 | ||||
| 330 | // Act | 331 | // Act | ||
| 331 | result := pv.Widget() | 332 | result := pv.Widget() | ||
| @@ -343,7 +344,7 @@ func TestOverlay_ReturnsOverlayWidget(t *testing.T) { | |||||
| 343 | 344 | ||||
| 344 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | 345 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | ||
| 345 | 346 | ||||
| 346 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | 347 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) |
| 347 | 348 | ||||
| 348 | // Act | 349 | // Act | ||
| 349 | result := pv.Overlay() | 350 | result := pv.Overlay() | ||
| @@ -361,7 +362,7 @@ func TestShow_DelegatesToOverlay(t *testing.T) { | |||||
| 361 | 362 | ||||
| 362 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | 363 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | ||
| 363 | 364 | ||||
| 364 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | 365 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) |
| 365 | 366 | ||||
| 366 | mockOverlay.EXPECT().Show().Once() | 367 | mockOverlay.EXPECT().Show().Once() | ||
| 367 | 368 | ||||
| @@ -380,7 +381,7 @@ func TestHide_DelegatesToOverlay(t *testing.T) { | |||||
| 380 | 381 | ||||
| 381 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | 382 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | ||
| 382 | 383 | ||||
| 383 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | 384 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) |
| 384 | 385 | ||||
| 385 | mockOverlay.EXPECT().Hide().Once() | 386 | mockOverlay.EXPECT().Hide().Once() | ||
| 386 | 387 | ||||
| @@ -399,7 +400,7 @@ func TestSetVisible_DelegatesToOverlay(t *testing.T) { | |||||
| 399 | 400 | ||||
| 400 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | 401 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | ||
| 401 | 402 | ||||
| 402 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | 403 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) |
| 403 | 404 | ||||
| 404 | mockOverlay.EXPECT().SetVisible(true).Once() | 405 | mockOverlay.EXPECT().SetVisible(true).Once() | ||
| 405 | 406 | ||||
| @@ -418,7 +419,7 @@ func TestIsVisible_DelegatesToOverlay(t *testing.T) { | |||||
| 418 | 419 | ||||
| 419 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | 420 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | ||
| 420 | 421 | ||||
| 421 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | 422 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) |
| 422 | 423 | ||||
| 423 | mockOverlay.EXPECT().IsVisible().Return(true).Once() | 424 | mockOverlay.EXPECT().IsVisible().Return(true).Once() | ||
| 424 | 425 | ||||
| @@ -438,7 +439,7 @@ func TestAddCssClass_DelegatesToOverlay(t *testing.T) { | |||||
| 438 | 439 | ||||
| 439 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | 440 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | ||
| 440 | 441 | ||||
| 441 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | 442 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) |
| 442 | 443 | ||||
| 443 | mockOverlay.EXPECT().AddCssClass("custom-class").Once() | 444 | mockOverlay.EXPECT().AddCssClass("custom-class").Once() | ||
| 444 | 445 | ||||
| @@ -457,7 +458,7 @@ func TestRemoveCssClass_DelegatesToOverlay(t *testing.T) { | |||||
| 457 | 458 | ||||
| 458 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | 459 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | ||
| 459 | 460 | ||||
| 460 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | 461 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) |
| 461 | 462 | ||||
| 462 | mockOverlay.EXPECT().RemoveCssClass("custom-class").Once() | 463 | mockOverlay.EXPECT().RemoveCssClass("custom-class").Once() | ||
| 463 | 464 | ||||
| @@ -476,7 +477,7 @@ func TestSetOnFocusIn_SetsCallback(t *testing.T) { | |||||
| 476 | 477 | ||||
| 477 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | 478 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | ||
| 478 | 479 | ||||
| 479 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | 480 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) |
| 480 | 481 | ||||
| 481 | callback := func(paneID entity.PaneID) {} | 482 | callback := func(paneID entity.PaneID) {} | ||
| 482 | 483 | ||||
| @@ -496,7 +497,7 @@ func TestSetOnFocusOut_SetsCallback(t *testing.T) { | |||||
| 496 | 497 | ||||
| 497 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | 498 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | ||
| 498 | 499 | ||||
| 499 | - | pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView) | 500 | + | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) |
| 500 | 501 | ||||
| 501 | callback := func(paneID entity.PaneID) {} | 502 | callback := func(paneID entity.PaneID) {} | ||
| 502 | 503 | ||||
M internal/ui/component/progress_bar.go +58 -1
| @@ -2,8 +2,10 @@ | |||
| 2 | 2 | package component | |
| 3 | 3 | ||
| 4 | 4 | import ( | |
| 5 | + | "context" | |
| 5 | 6 | "sync" | |
| 6 | 7 | ||
| 8 | + | "github.com/bnema/dumber/internal/logging" | |
| 7 | 9 | "github.com/bnema/puregotk/v4/glib" | |
| 8 | 10 | "github.com/bnema/puregotk/v4/gtk" | |
| 9 | 11 | ||
| @@ -24,6 +26,7 @@ const ( | |||
| 24 | 26 | // Implements smooth animation by incrementing towards the target value. | |
| 25 | 27 | // Includes a 30-second timeout to auto-hide if the page load stalls. | |
| 26 | 28 | type ProgressBar struct { | |
| 29 | + | ctx context.Context | |
| 27 | 30 | progressBar layout.ProgressBarWidget | |
| 28 | 31 | ||
| 29 | 32 | visible bool | |
| @@ -36,7 +39,7 @@ type ProgressBar struct { | |||
| 36 | 39 | } | |
| 37 | 40 | ||
| 38 | 41 | // NewProgressBar creates a new progress bar component using the widget factory. | |
| 39 | - | func NewProgressBar(factory layout.WidgetFactory) *ProgressBar { | |
| 42 | + | func NewProgressBar(ctx context.Context, factory layout.WidgetFactory) *ProgressBar { | |
| 40 | 43 | progressBar := factory.NewProgressBar() | |
| 41 | 44 | ||
| 42 | 45 | // Add "osd" class for on-screen-display overlay styling (like Epiphany) | |
| @@ -63,6 +66,7 @@ func NewProgressBar(factory layout.WidgetFactory) *ProgressBar { | |||
| 63 | 66 | progressBar.SetVisible(false) | |
| 64 | 67 | ||
| 65 | 68 | return &ProgressBar{ | |
| 69 | + | ctx: ctx, | |
| 66 | 70 | progressBar: progressBar, | |
| 67 | 71 | visible: false, | |
| 68 | 72 | currentValue: 0, | |
| @@ -76,6 +80,19 @@ func (pb *ProgressBar) SetProgress(progress float64) { | |||
| 76 | 80 | pb.mu.Lock() | |
| 77 | 81 | defer pb.mu.Unlock() | |
| 78 | 82 | ||
| 83 | + | incomingProgress := progress | |
| 84 | + | ctx := pb.ctx | |
| 85 | + | if ctx == nil { | |
| 86 | + | ctx = context.Background() | |
| 87 | + | } | |
| 88 | + | logging.FromContext(ctx). | |
| 89 | + | Debug(). | |
| 90 | + | Float64("incoming_progress", incomingProgress). | |
| 91 | + | Float64("current_value", pb.currentValue). | |
| 92 | + | Float64("target_value", pb.targetValue). | |
| 93 | + | Bool("visible", pb.visible). | |
| 94 | + | Msg("setting progress") | |
| 95 | + | ||
| 79 | 96 | // Clamp progress to valid range | |
| 80 | 97 | if progress < 0 { | |
| 81 | 98 | progress = 0 | |
| @@ -131,18 +148,49 @@ func (pb *ProgressBar) startAnimation() { | |||
| 131 | 148 | pb.animationTimer = glib.TimeoutAdd(progressIntervalMs, &cb, 0) | |
| 132 | 149 | } | |
| 133 | 150 | ||
| 151 | + | // initialProgressFraction is the fraction set when the progress bar first | |
| 152 | + | // appears so the user gets immediate visual feedback that loading has begun. | |
| 153 | + | // CEF may not fire OnLoadingProgressChange for hundreds of milliseconds | |
| 154 | + | // during cross-site process swaps, leaving the bar at 0% (invisible). | |
| 155 | + | const initialProgressFraction = 0.1 | |
| 156 | + | ||
| 134 | 157 | // Show makes the progress bar visible and starts the auto-hide timeout. | |
| 135 | 158 | func (pb *ProgressBar) Show() { | |
| 136 | 159 | pb.mu.Lock() | |
| 137 | 160 | defer pb.mu.Unlock() | |
| 138 | 161 | ||
| 162 | + | ctx := pb.ctx | |
| 163 | + | if ctx == nil { | |
| 164 | + | ctx = context.Background() | |
| 165 | + | } | |
| 166 | + | logging.FromContext(ctx). | |
| 167 | + | Debug(). | |
| 168 | + | Bool("visible", pb.visible). | |
| 169 | + | Float64("current_value", pb.currentValue). | |
| 170 | + | Float64("target_value", pb.targetValue). | |
| 171 | + | Msg("progress bar show before") | |
| 172 | + | ||
| 139 | 173 | if !pb.visible { | |
| 140 | 174 | pb.visible = true | |
| 175 | + | // Set an initial non-zero fraction so the bar is visually noticeable | |
| 176 | + | // immediately. Without this, the bar is technically visible but | |
| 177 | + | // renders as empty (0% fill) until the first progress callback, | |
| 178 | + | // which in CEF can be delayed during cross-site process swaps. | |
| 179 | + | pb.currentValue = initialProgressFraction | |
| 180 | + | pb.targetValue = initialProgressFraction | |
| 181 | + | pb.progressBar.SetFraction(initialProgressFraction) | |
| 141 | 182 | pb.progressBar.SetVisible(true) | |
| 142 | 183 | } | |
| 143 | 184 | ||
| 144 | 185 | // Reset timeout timer on every Show call | |
| 145 | 186 | pb.resetTimeout() | |
| 187 | + | ||
| 188 | + | logging.FromContext(pb.ctx). | |
| 189 | + | Debug(). | |
| 190 | + | Bool("visible", pb.visible). | |
| 191 | + | Float64("current_value", pb.currentValue). | |
| 192 | + | Float64("target_value", pb.targetValue). | |
| 193 | + | Msg("progress bar show after") | |
| 146 | 194 | } | |
| 147 | 195 | ||
| 148 | 196 | // resetTimeout cancels any existing timeout and starts a new one. | |
| @@ -181,6 +229,15 @@ func (pb *ProgressBar) Hide() { | |||
| 181 | 229 | // hideInternal performs the actual hide operation. | |
| 182 | 230 | // Must be called with lock held. | |
| 183 | 231 | func (pb *ProgressBar) hideInternal() { | |
| 232 | + | ctx := pb.ctx | |
| 233 | + | if ctx == nil { | |
| 234 | + | ctx = context.Background() | |
| 235 | + | } | |
| 236 | + | logging.FromContext(ctx). | |
| 237 | + | Debug(). | |
| 238 | + | Bool("visible", pb.visible). | |
| 239 | + | Msg("progress bar hide before") | |
| 240 | + | ||
| 184 | 241 | if pb.visible { | |
| 185 | 242 | pb.visible = false | |
| 186 | 243 | pb.progressBar.SetVisible(false) | |
| @@ -2,8 +2,10 @@ | |||||
| 2 | package component | 2 | package component | ||
| 3 | 3 | ||||
| 4 | import ( | 4 | import ( | ||
| 5 | + | "context" | |||
| 5 | "sync" | 6 | "sync" | ||
| 6 | 7 | ||||
| 8 | + | "github.com/bnema/dumber/internal/logging" | |||
| 7 | "github.com/bnema/puregotk/v4/glib" | 9 | "github.com/bnema/puregotk/v4/glib" | ||
| 8 | "github.com/bnema/puregotk/v4/gtk" | 10 | "github.com/bnema/puregotk/v4/gtk" | ||
| 9 | 11 | ||||
| @@ -24,6 +26,7 @@ const ( | |||||
| 24 | // Implements smooth animation by incrementing towards the target value. | 26 | // Implements smooth animation by incrementing towards the target value. | ||
| 25 | // Includes a 30-second timeout to auto-hide if the page load stalls. | 27 | // Includes a 30-second timeout to auto-hide if the page load stalls. | ||
| 26 | type ProgressBar struct { | 28 | type ProgressBar struct { | ||
| 29 | + | ctx context.Context | |||
| 27 | progressBar layout.ProgressBarWidget | 30 | progressBar layout.ProgressBarWidget | ||
| 28 | 31 | ||||
| 29 | visible bool | 32 | visible bool | ||
| @@ -36,7 +39,7 @@ type ProgressBar struct { | |||||
| 36 | } | 39 | } | ||
| 37 | 40 | ||||
| 38 | // NewProgressBar creates a new progress bar component using the widget factory. | 41 | // NewProgressBar creates a new progress bar component using the widget factory. | ||
| 39 | - | func NewProgressBar(factory layout.WidgetFactory) *ProgressBar { | 42 | + | func NewProgressBar(ctx context.Context, factory layout.WidgetFactory) *ProgressBar { |
| 40 | progressBar := factory.NewProgressBar() | 43 | progressBar := factory.NewProgressBar() | ||
| 41 | 44 | ||||
| 42 | // Add "osd" class for on-screen-display overlay styling (like Epiphany) | 45 | // Add "osd" class for on-screen-display overlay styling (like Epiphany) | ||
| @@ -63,6 +66,7 @@ func NewProgressBar(factory layout.WidgetFactory) *ProgressBar { | |||||
| 63 | progressBar.SetVisible(false) | 66 | progressBar.SetVisible(false) | ||
| 64 | 67 | ||||
| 65 | return &ProgressBar{ | 68 | return &ProgressBar{ | ||
| 69 | + | ctx: ctx, | |||
| 66 | progressBar: progressBar, | 70 | progressBar: progressBar, | ||
| 67 | visible: false, | 71 | visible: false, | ||
| 68 | currentValue: 0, | 72 | currentValue: 0, | ||
| @@ -76,6 +80,19 @@ func (pb *ProgressBar) SetProgress(progress float64) { | |||||
| 76 | pb.mu.Lock() | 80 | pb.mu.Lock() | ||
| 77 | defer pb.mu.Unlock() | 81 | defer pb.mu.Unlock() | ||
| 78 | 82 | ||||
| 83 | + | incomingProgress := progress | |||
| 84 | + | ctx := pb.ctx | |||
| 85 | + | if ctx == nil { | |||
| 86 | + | ctx = context.Background() | |||
| 87 | + | } | |||
| 88 | + | logging.FromContext(ctx). | |||
| 89 | + | Debug(). | |||
| 90 | + | Float64("incoming_progress", incomingProgress). | |||
| 91 | + | Float64("current_value", pb.currentValue). | |||
| 92 | + | Float64("target_value", pb.targetValue). | |||
| 93 | + | Bool("visible", pb.visible). | |||
| 94 | + | Msg("setting progress") | |||
| 95 | + | ||||
| 79 | // Clamp progress to valid range | 96 | // Clamp progress to valid range | ||
| 80 | if progress < 0 { | 97 | if progress < 0 { | ||
| 81 | progress = 0 | 98 | progress = 0 | ||
| @@ -131,18 +148,49 @@ func (pb *ProgressBar) startAnimation() { | |||||
| 131 | pb.animationTimer = glib.TimeoutAdd(progressIntervalMs, &cb, 0) | 148 | pb.animationTimer = glib.TimeoutAdd(progressIntervalMs, &cb, 0) | ||
| 132 | } | 149 | } | ||
| 133 | 150 | ||||
| 151 | + | // initialProgressFraction is the fraction set when the progress bar first | |||
| 152 | + | // appears so the user gets immediate visual feedback that loading has begun. | |||
| 153 | + | // CEF may not fire OnLoadingProgressChange for hundreds of milliseconds | |||
| 154 | + | // during cross-site process swaps, leaving the bar at 0% (invisible). | |||
| 155 | + | const initialProgressFraction = 0.1 | |||
| 156 | + | ||||
| 134 | // Show makes the progress bar visible and starts the auto-hide timeout. | 157 | // Show makes the progress bar visible and starts the auto-hide timeout. | ||
| 135 | func (pb *ProgressBar) Show() { | 158 | func (pb *ProgressBar) Show() { | ||
| 136 | pb.mu.Lock() | 159 | pb.mu.Lock() | ||
| 137 | defer pb.mu.Unlock() | 160 | defer pb.mu.Unlock() | ||
| 138 | 161 | ||||
| 162 | + | ctx := pb.ctx | |||
| 163 | + | if ctx == nil { | |||
| 164 | + | ctx = context.Background() | |||
| 165 | + | } | |||
| 166 | + | logging.FromContext(ctx). | |||
| 167 | + | Debug(). | |||
| 168 | + | Bool("visible", pb.visible). | |||
| 169 | + | Float64("current_value", pb.currentValue). | |||
| 170 | + | Float64("target_value", pb.targetValue). | |||
| 171 | + | Msg("progress bar show before") | |||
| 172 | + | ||||
| 139 | if !pb.visible { | 173 | if !pb.visible { | ||
| 140 | pb.visible = true | 174 | pb.visible = true | ||
| 175 | + | // Set an initial non-zero fraction so the bar is visually noticeable | |||
| 176 | + | // immediately. Without this, the bar is technically visible but | |||
| 177 | + | // renders as empty (0% fill) until the first progress callback, | |||
| 178 | + | // which in CEF can be delayed during cross-site process swaps. | |||
| 179 | + | pb.currentValue = initialProgressFraction | |||
| 180 | + | pb.targetValue = initialProgressFraction | |||
| 181 | + | pb.progressBar.SetFraction(initialProgressFraction) | |||
| 141 | pb.progressBar.SetVisible(true) | 182 | pb.progressBar.SetVisible(true) | ||
| 142 | } | 183 | } | ||
| 143 | 184 | ||||
| 144 | // Reset timeout timer on every Show call | 185 | // Reset timeout timer on every Show call | ||
| 145 | pb.resetTimeout() | 186 | pb.resetTimeout() | ||
| 187 | + | ||||
| 188 | + | logging.FromContext(pb.ctx). | |||
| 189 | + | Debug(). | |||
| 190 | + | Bool("visible", pb.visible). | |||
| 191 | + | Float64("current_value", pb.currentValue). | |||
| 192 | + | Float64("target_value", pb.targetValue). | |||
| 193 | + | Msg("progress bar show after") | |||
| 146 | } | 194 | } | ||
| 147 | 195 | ||||
| 148 | // resetTimeout cancels any existing timeout and starts a new one. | 196 | // resetTimeout cancels any existing timeout and starts a new one. | ||
| @@ -181,6 +229,15 @@ func (pb *ProgressBar) Hide() { | |||||
| 181 | // hideInternal performs the actual hide operation. | 229 | // hideInternal performs the actual hide operation. | ||
| 182 | // Must be called with lock held. | 230 | // Must be called with lock held. | ||
| 183 | func (pb *ProgressBar) hideInternal() { | 231 | func (pb *ProgressBar) hideInternal() { | ||
| 232 | + | ctx := pb.ctx | |||
| 233 | + | if ctx == nil { | |||
| 234 | + | ctx = context.Background() | |||
| 235 | + | } | |||
| 236 | + | logging.FromContext(ctx). | |||
| 237 | + | Debug(). | |||
| 238 | + | Bool("visible", pb.visible). | |||
| 239 | + | Msg("progress bar hide before") | |||
| 240 | + | ||||
| 184 | if pb.visible { | 241 | if pb.visible { | ||
| 185 | pb.visible = false | 242 | pb.visible = false | ||
| 186 | pb.progressBar.SetVisible(false) | 243 | pb.progressBar.SetVisible(false) | ||
M internal/ui/component/workspace_view.go +1 -1
| @@ -86,7 +86,7 @@ func (a *paneViewFactoryAdapter) CreatePaneView(node *entity.PaneNode) layout.Wi | |||
| 86 | 86 | ||
| 87 | 87 | // Create PaneView without WebView widget for now | |
| 88 | 88 | // WebView will be attached later by the application layer | |
| 89 | - | pv := NewPaneView(a.wv.factory, node.Pane.ID, nil) | |
| 89 | + | pv := NewPaneView(a.ctx, a.wv.factory, node.Pane.ID, nil) | |
| 90 | 90 | ||
| 91 | 91 | // Store in map for later lookup | |
| 92 | 92 | // Note: Caller (SetWorkspace) already holds the lock, so we access directly | |
| @@ -86,7 +86,7 @@ func (a *paneViewFactoryAdapter) CreatePaneView(node *entity.PaneNode) layout.Wi | |||||
| 86 | 86 | ||||
| 87 | // Create PaneView without WebView widget for now | 87 | // Create PaneView without WebView widget for now | ||
| 88 | // WebView will be attached later by the application layer | 88 | // WebView will be attached later by the application layer | ||
| 89 | - | pv := NewPaneView(a.wv.factory, node.Pane.ID, nil) | 89 | + | pv := NewPaneView(a.ctx, a.wv.factory, node.Pane.ID, nil) |
| 90 | 90 | ||||
| 91 | // Store in map for later lookup | 91 | // Store in map for later lookup | ||
| 92 | // Note: Caller (SetWorkspace) already holds the lock, so we access directly | 92 | // Note: Caller (SetWorkspace) already holds the lock, so we access directly | ||
M internal/ui/coordinator/content/content_reveal_test.go +34 -0
| @@ -8,7 +8,9 @@ import ( | |||
| 8 | 8 | "github.com/stretchr/testify/assert" | |
| 9 | 9 | ||
| 10 | 10 | "github.com/bnema/dumber/internal/application/port" | |
| 11 | + | "github.com/bnema/dumber/internal/application/port/mocks" | |
| 11 | 12 | "github.com/bnema/dumber/internal/domain/entity" | |
| 13 | + | "github.com/bnema/dumber/internal/ui/component" | |
| 12 | 14 | ) | |
| 13 | 15 | ||
| 14 | 16 | // TestPendingReveal tests the pending reveal state management. | |
| @@ -108,6 +110,38 @@ func TestRevealIfPending_OnlyRevealsOnce(t *testing.T) { | |||
| 108 | 110 | assert.False(t, c.pendingReveal[paneID]) | |
| 109 | 111 | } | |
| 110 | 112 | ||
| 113 | + | func TestOnLoadCommitted_RevealsPendingWebViewForCommittedPage(t *testing.T) { | |
| 114 | + | paneID := entity.PaneID("pane-1") | |
| 115 | + | shownCount := 0 | |
| 116 | + | var shownPane entity.PaneID | |
| 117 | + | ||
| 118 | + | wv := mocks.NewMockWebView(t) | |
| 119 | + | wv.EXPECT().URI().Return("https://example.com") | |
| 120 | + | wv.EXPECT().ResetBackgroundToDefault() | |
| 121 | + | wv.EXPECT().Title().Return("") | |
| 122 | + | wv.EXPECT().IsDestroyed().Return(false).Maybe() | |
| 123 | + | ||
| 124 | + | c := &Coordinator{ | |
| 125 | + | pendingReveal: make(map[entity.PaneID]bool), | |
| 126 | + | webViews: map[entity.PaneID]port.WebView{ | |
| 127 | + | paneID: wv, | |
| 128 | + | }, | |
| 129 | + | paneTitles: make(map[entity.PaneID]string), | |
| 130 | + | navOrigins: make(map[entity.PaneID]string), | |
| 131 | + | getActiveWS: func() (*entity.Workspace, *component.WorkspaceView) { return nil, nil }, | |
| 132 | + | onWebViewShown: func(id entity.PaneID) { | |
| 133 | + | shownCount++ | |
| 134 | + | shownPane = id | |
| 135 | + | }, | |
| 136 | + | } | |
| 137 | + | c.pendingReveal[paneID] = true | |
| 138 | + | ||
| 139 | + | c.onLoadCommitted(context.Background(), paneID, wv) | |
| 140 | + | ||
| 141 | + | assert.Equal(t, 1, shownCount) | |
| 142 | + | assert.Equal(t, paneID, shownPane) | |
| 143 | + | } | |
| 144 | + | ||
| 111 | 145 | func TestPendingReveal_ConcurrentAccess(t *testing.T) { | |
| 112 | 146 | c := &Coordinator{ | |
| 113 | 147 | pendingReveal: make(map[entity.PaneID]bool), | |
| @@ -8,7 +8,9 @@ import ( | |||||
| 8 | "github.com/stretchr/testify/assert" | 8 | "github.com/stretchr/testify/assert" | ||
| 9 | 9 | ||||
| 10 | "github.com/bnema/dumber/internal/application/port" | 10 | "github.com/bnema/dumber/internal/application/port" | ||
| 11 | + | "github.com/bnema/dumber/internal/application/port/mocks" | |||
| 11 | "github.com/bnema/dumber/internal/domain/entity" | 12 | "github.com/bnema/dumber/internal/domain/entity" | ||
| 13 | + | "github.com/bnema/dumber/internal/ui/component" | |||
| 12 | ) | 14 | ) | ||
| 13 | 15 | ||||
| 14 | // TestPendingReveal tests the pending reveal state management. | 16 | // TestPendingReveal tests the pending reveal state management. | ||
| @@ -108,6 +110,38 @@ func TestRevealIfPending_OnlyRevealsOnce(t *testing.T) { | |||||
| 108 | assert.False(t, c.pendingReveal[paneID]) | 110 | assert.False(t, c.pendingReveal[paneID]) | ||
| 109 | } | 111 | } | ||
| 110 | 112 | ||||
| 113 | + | func TestOnLoadCommitted_RevealsPendingWebViewForCommittedPage(t *testing.T) { | |||
| 114 | + | paneID := entity.PaneID("pane-1") | |||
| 115 | + | shownCount := 0 | |||
| 116 | + | var shownPane entity.PaneID | |||
| 117 | + | ||||
| 118 | + | wv := mocks.NewMockWebView(t) | |||
| 119 | + | wv.EXPECT().URI().Return("https://example.com") | |||
| 120 | + | wv.EXPECT().ResetBackgroundToDefault() | |||
| 121 | + | wv.EXPECT().Title().Return("") | |||
| 122 | + | wv.EXPECT().IsDestroyed().Return(false).Maybe() | |||
| 123 | + | ||||
| 124 | + | c := &Coordinator{ | |||
| 125 | + | pendingReveal: make(map[entity.PaneID]bool), | |||
| 126 | + | webViews: map[entity.PaneID]port.WebView{ | |||
| 127 | + | paneID: wv, | |||
| 128 | + | }, | |||
| 129 | + | paneTitles: make(map[entity.PaneID]string), | |||
| 130 | + | navOrigins: make(map[entity.PaneID]string), | |||
| 131 | + | getActiveWS: func() (*entity.Workspace, *component.WorkspaceView) { return nil, nil }, | |||
| 132 | + | onWebViewShown: func(id entity.PaneID) { | |||
| 133 | + | shownCount++ | |||
| 134 | + | shownPane = id | |||
| 135 | + | }, | |||
| 136 | + | } | |||
| 137 | + | c.pendingReveal[paneID] = true | |||
| 138 | + | ||||
| 139 | + | c.onLoadCommitted(context.Background(), paneID, wv) | |||
| 140 | + | ||||
| 141 | + | assert.Equal(t, 1, shownCount) | |||
| 142 | + | assert.Equal(t, paneID, shownPane) | |||
| 143 | + | } | |||
| 144 | + | ||||
| 111 | func TestPendingReveal_ConcurrentAccess(t *testing.T) { | 145 | func TestPendingReveal_ConcurrentAccess(t *testing.T) { | ||
| 112 | c := &Coordinator{ | 146 | c := &Coordinator{ | ||
| 113 | pendingReveal: make(map[entity.PaneID]bool), | 147 | pendingReveal: make(map[entity.PaneID]bool), | ||
M internal/ui/coordinator/content/coordinator.go +3 -0
| @@ -14,10 +14,12 @@ import ( | |||
| 14 | 14 | "github.com/bnema/dumber/internal/ui/component" | |
| 15 | 15 | "github.com/bnema/dumber/internal/ui/input" | |
| 16 | 16 | "github.com/bnema/dumber/internal/ui/layout" | |
| 17 | + | "github.com/rs/zerolog" | |
| 17 | 18 | ) | |
| 18 | 19 | ||
| 19 | 20 | // Coordinator manages WebView lifecycle, title tracking, and content attachment. | |
| 20 | 21 | type Coordinator struct { | |
| 22 | + | logger zerolog.Logger | |
| 21 | 23 | pool port.WebViewPool | |
| 22 | 24 | widgetFactory layout.WidgetFactory | |
| 23 | 25 | faviconAdapter *adapter.FaviconAdapter | |
| @@ -151,6 +153,7 @@ func NewCoordinator( | |||
| 151 | 153 | log.Debug().Msg("creating content coordinator") | |
| 152 | 154 | ||
| 153 | 155 | return &Coordinator{ | |
| 156 | + | logger: log.With().Str("component", "content-coordinator").Logger(), | |
| 154 | 157 | pool: pool, | |
| 155 | 158 | injector: injector, | |
| 156 | 159 | widgetFactory: widgetFactory, | |
| @@ -14,10 +14,12 @@ import ( | |||||
| 14 | "github.com/bnema/dumber/internal/ui/component" | 14 | "github.com/bnema/dumber/internal/ui/component" | ||
| 15 | "github.com/bnema/dumber/internal/ui/input" | 15 | "github.com/bnema/dumber/internal/ui/input" | ||
| 16 | "github.com/bnema/dumber/internal/ui/layout" | 16 | "github.com/bnema/dumber/internal/ui/layout" | ||
| 17 | + | "github.com/rs/zerolog" | |||
| 17 | ) | 18 | ) | ||
| 18 | 19 | ||||
| 19 | // Coordinator manages WebView lifecycle, title tracking, and content attachment. | 20 | // Coordinator manages WebView lifecycle, title tracking, and content attachment. | ||
| 20 | type Coordinator struct { | 21 | type Coordinator struct { | ||
| 22 | + | logger zerolog.Logger | |||
| 21 | pool port.WebViewPool | 23 | pool port.WebViewPool | ||
| 22 | widgetFactory layout.WidgetFactory | 24 | widgetFactory layout.WidgetFactory | ||
| 23 | faviconAdapter *adapter.FaviconAdapter | 25 | faviconAdapter *adapter.FaviconAdapter | ||
| @@ -151,6 +153,7 @@ func NewCoordinator( | |||||
| 151 | log.Debug().Msg("creating content coordinator") | 153 | log.Debug().Msg("creating content coordinator") | ||
| 152 | 154 | ||||
| 153 | return &Coordinator{ | 155 | return &Coordinator{ | ||
| 156 | + | logger: log.With().Str("component", "content-coordinator").Logger(), | |||
| 154 | pool: pool, | 157 | pool: pool, | ||
| 155 | injector: injector, | 158 | injector: injector, | ||
| 156 | widgetFactory: widgetFactory, | 159 | widgetFactory: widgetFactory, | ||
M internal/ui/coordinator/content/navigation.go +17 -9
| @@ -9,6 +9,7 @@ import ( | |||
| 9 | 9 | "github.com/bnema/dumber/internal/domain/entity" | |
| 10 | 10 | urlutil "github.com/bnema/dumber/internal/domain/url" | |
| 11 | 11 | "github.com/bnema/dumber/internal/logging" | |
| 12 | + | "github.com/bnema/dumber/internal/ui/component" | |
| 12 | 13 | "github.com/bnema/puregotk/v4/gtk" | |
| 13 | 14 | ) | |
| 14 | 15 | ||
| @@ -77,9 +78,7 @@ func (c *Coordinator) onLoadCommitted(ctx context.Context, paneID entity.PaneID, | |||
| 77 | 78 | } | |
| 78 | 79 | ||
| 79 | 80 | c.markPendingReveal(paneID) | |
| 80 | - | if wv.EstimatedProgress() > 0 { | |
| 81 | - | c.revealIfPending(ctx, paneID, uri, "progress-after-commit") | |
| 82 | - | } | |
| 81 | + | c.revealIfPending(ctx, paneID, uri, "progress-after-commit") | |
| 83 | 82 | ||
| 84 | 83 | // Update domain model with current URI for session snapshots | |
| 85 | 84 | c.updatePaneURI(paneID, uri) | |
| @@ -188,11 +187,14 @@ func (c *Coordinator) onLoadStarted(paneID entity.PaneID) { | |||
| 188 | 187 | }) | |
| 189 | 188 | ||
| 190 | 189 | _, wsView := c.getActiveWS() | |
| 190 | + | var paneView *component.PaneView | |
| 191 | + | if wsView != nil { | |
| 192 | + | paneView = wsView.GetPaneView(paneID) | |
| 193 | + | } | |
| 194 | + | ||
| 191 | 195 | if wsView == nil { | |
| 192 | 196 | return | |
| 193 | 197 | } | |
| 194 | - | ||
| 195 | - | paneView := wsView.GetPaneView(paneID) | |
| 196 | 198 | if paneView != nil { | |
| 197 | 199 | paneView.SetLoading(true) | |
| 198 | 200 | } | |
| @@ -201,11 +203,14 @@ func (c *Coordinator) onLoadStarted(paneID entity.PaneID) { | |||
| 201 | 203 | // onLoadFinished hides the progress bar when page loading completes. | |
| 202 | 204 | func (c *Coordinator) onLoadFinished(ctx context.Context, paneID entity.PaneID, wv port.WebView) { | |
| 203 | 205 | _, wsView := c.getActiveWS() | |
| 206 | + | var paneView *component.PaneView | |
| 207 | + | if wsView != nil { | |
| 208 | + | paneView = wsView.GetPaneView(paneID) | |
| 209 | + | } | |
| 210 | + | ||
| 204 | 211 | if wsView == nil { | |
| 205 | 212 | return | |
| 206 | 213 | } | |
| 207 | - | ||
| 208 | - | paneView := wsView.GetPaneView(paneID) | |
| 209 | 214 | if paneView != nil { | |
| 210 | 215 | paneView.SetLoading(false) | |
| 211 | 216 | } | |
| @@ -225,11 +230,14 @@ func (c *Coordinator) onProgressChanged(paneID entity.PaneID, progress float64) | |||
| 225 | 230 | } | |
| 226 | 231 | ||
| 227 | 232 | _, wsView := c.getActiveWS() | |
| 233 | + | var paneView *component.PaneView | |
| 234 | + | if wsView != nil { | |
| 235 | + | paneView = wsView.GetPaneView(paneID) | |
| 236 | + | } | |
| 237 | + | ||
| 228 | 238 | if wsView == nil { | |
| 229 | 239 | return | |
| 230 | 240 | } | |
| 231 | - | ||
| 232 | - | paneView := wsView.GetPaneView(paneID) | |
| 233 | 241 | if paneView != nil { | |
| 234 | 242 | paneView.SetLoadProgress(progress) | |
| 235 | 243 | } | |
| @@ -9,6 +9,7 @@ import ( | |||||
| 9 | "github.com/bnema/dumber/internal/domain/entity" | 9 | "github.com/bnema/dumber/internal/domain/entity" | ||
| 10 | urlutil "github.com/bnema/dumber/internal/domain/url" | 10 | urlutil "github.com/bnema/dumber/internal/domain/url" | ||
| 11 | "github.com/bnema/dumber/internal/logging" | 11 | "github.com/bnema/dumber/internal/logging" | ||
| 12 | + | "github.com/bnema/dumber/internal/ui/component" | |||
| 12 | "github.com/bnema/puregotk/v4/gtk" | 13 | "github.com/bnema/puregotk/v4/gtk" | ||
| 13 | ) | 14 | ) | ||
| 14 | 15 | ||||
| @@ -77,9 +78,7 @@ func (c *Coordinator) onLoadCommitted(ctx context.Context, paneID entity.PaneID, | |||||
| 77 | } | 78 | } | ||
| 78 | 79 | ||||
| 79 | c.markPendingReveal(paneID) | 80 | c.markPendingReveal(paneID) | ||
| 80 | - | if wv.EstimatedProgress() > 0 { | 81 | + | c.revealIfPending(ctx, paneID, uri, "progress-after-commit") |
| 81 | - | c.revealIfPending(ctx, paneID, uri, "progress-after-commit") | |||
| 82 | - | } | |||
| 83 | 82 | ||||
| 84 | // Update domain model with current URI for session snapshots | 83 | // Update domain model with current URI for session snapshots | ||
| 85 | c.updatePaneURI(paneID, uri) | 84 | c.updatePaneURI(paneID, uri) | ||
| @@ -188,11 +187,14 @@ func (c *Coordinator) onLoadStarted(paneID entity.PaneID) { | |||||
| 188 | }) | 187 | }) | ||
| 189 | 188 | ||||
| 190 | _, wsView := c.getActiveWS() | 189 | _, wsView := c.getActiveWS() | ||
| 190 | + | var paneView *component.PaneView | |||
| 191 | + | if wsView != nil { | |||
| 192 | + | paneView = wsView.GetPaneView(paneID) | |||
| 193 | + | } | |||
| 194 | + | ||||
| 191 | if wsView == nil { | 195 | if wsView == nil { | ||
| 192 | return | 196 | return | ||
| 193 | } | 197 | } | ||
| 194 | - | ||||
| 195 | - | paneView := wsView.GetPaneView(paneID) | |||
| 196 | if paneView != nil { | 198 | if paneView != nil { | ||
| 197 | paneView.SetLoading(true) | 199 | paneView.SetLoading(true) | ||
| 198 | } | 200 | } | ||
| @@ -201,11 +203,14 @@ func (c *Coordinator) onLoadStarted(paneID entity.PaneID) { | |||||
| 201 | // onLoadFinished hides the progress bar when page loading completes. | 203 | // onLoadFinished hides the progress bar when page loading completes. | ||
| 202 | func (c *Coordinator) onLoadFinished(ctx context.Context, paneID entity.PaneID, wv port.WebView) { | 204 | func (c *Coordinator) onLoadFinished(ctx context.Context, paneID entity.PaneID, wv port.WebView) { | ||
| 203 | _, wsView := c.getActiveWS() | 205 | _, wsView := c.getActiveWS() | ||
| 206 | + | var paneView *component.PaneView | |||
| 207 | + | if wsView != nil { | |||
| 208 | + | paneView = wsView.GetPaneView(paneID) | |||
| 209 | + | } | |||
| 210 | + | ||||
| 204 | if wsView == nil { | 211 | if wsView == nil { | ||
| 205 | return | 212 | return | ||
| 206 | } | 213 | } | ||
| 207 | - | ||||
| 208 | - | paneView := wsView.GetPaneView(paneID) | |||
| 209 | if paneView != nil { | 214 | if paneView != nil { | ||
| 210 | paneView.SetLoading(false) | 215 | paneView.SetLoading(false) | ||
| 211 | } | 216 | } | ||
| @@ -225,11 +230,14 @@ func (c *Coordinator) onProgressChanged(paneID entity.PaneID, progress float64) | |||||
| 225 | } | 230 | } | ||
| 226 | 231 | ||||
| 227 | _, wsView := c.getActiveWS() | 232 | _, wsView := c.getActiveWS() | ||
| 233 | + | var paneView *component.PaneView | |||
| 234 | + | if wsView != nil { | |||
| 235 | + | paneView = wsView.GetPaneView(paneID) | |||
| 236 | + | } | |||
| 237 | + | ||||
| 228 | if wsView == nil { | 238 | if wsView == nil { | ||
| 229 | return | 239 | return | ||
| 230 | } | 240 | } | ||
| 231 | - | ||||
| 232 | - | paneView := wsView.GetPaneView(paneID) | |||
| 233 | if paneView != nil { | 241 | if paneView != nil { | ||
| 234 | paneView.SetLoadProgress(progress) | 242 | paneView.SetLoadProgress(progress) | ||
| 235 | } | 243 | } | ||
M internal/ui/coordinator/workspace.go +4 -4
| @@ -358,7 +358,7 @@ func (c *WorkspaceCoordinator) doIncrementalStackSplit( | |||
| 358 | 358 | Msg("stack split: determined split type") | |
| 359 | 359 | ||
| 360 | 360 | // 3. Create new PaneView for the new pane (without WebView - will attach later) | |
| 361 | - | newPaneView := component.NewPaneView(factory, output.NewPaneNode.Pane.ID, nil) | |
| 361 | + | newPaneView := component.NewPaneView(ctx, factory, output.NewPaneNode.Pane.ID, nil) | |
| 362 | 362 | setupPaneViewHover(ctx, newPaneView, wsView) | |
| 363 | 363 | ||
| 364 | 364 | // 4. Wrap the new PaneView in a StackedView | |
| @@ -534,7 +534,7 @@ func (c *WorkspaceCoordinator) doIncrementalSplit( | |||
| 534 | 534 | Msg("determined split type") | |
| 535 | 535 | ||
| 536 | 536 | // 4. Create new PaneView for the new pane | |
| 537 | - | newPaneView := component.NewPaneView(factory, output.NewPaneNode.Pane.ID, nil) | |
| 537 | + | newPaneView := component.NewPaneView(ctx, factory, output.NewPaneNode.Pane.ID, nil) | |
| 538 | 538 | setupPaneViewHover(ctx, newPaneView, wsView) | |
| 539 | 539 | ||
| 540 | 540 | // 5. Wrap the new PaneView in a StackedView | |
| @@ -1438,7 +1438,7 @@ func (c *WorkspaceCoordinator) StackPane(ctx context.Context) error { | |||
| 1438 | 1438 | } | |
| 1439 | 1439 | ||
| 1440 | 1440 | // Create PaneView for the new pane | |
| 1441 | - | newPaneView := component.NewPaneView(c.widgetFactory, newPaneID, nil) | |
| 1441 | + | newPaneView := component.NewPaneView(ctx, c.widgetFactory, newPaneID, nil) | |
| 1442 | 1442 | setupPaneViewHover(ctx, newPaneView, stackCtx.wsView) | |
| 1443 | 1443 | stackCtx.wsView.RegisterPaneView(newPaneID, newPaneView) | |
| 1444 | 1444 | ||
| @@ -2084,7 +2084,7 @@ func (c *WorkspaceCoordinator) attachPopupPaneView( | |||
| 2084 | 2084 | if wsView == nil { | |
| 2085 | 2085 | return nil | |
| 2086 | 2086 | } | |
| 2087 | - | newPaneView := component.NewPaneView(c.widgetFactory, input.PopupPane.ID, nil) | |
| 2087 | + | newPaneView := component.NewPaneView(ctx, c.widgetFactory, input.PopupPane.ID, nil) | |
| 2088 | 2088 | setupPaneViewHover(ctx, newPaneView, wsView) | |
| 2089 | 2089 | wsView.RegisterPaneView(input.PopupPane.ID, newPaneView) | |
| 2090 | 2090 | ||
| @@ -358,7 +358,7 @@ func (c *WorkspaceCoordinator) doIncrementalStackSplit( | |||||
| 358 | Msg("stack split: determined split type") | 358 | Msg("stack split: determined split type") | ||
| 359 | 359 | ||||
| 360 | // 3. Create new PaneView for the new pane (without WebView - will attach later) | 360 | // 3. Create new PaneView for the new pane (without WebView - will attach later) | ||
| 361 | - | newPaneView := component.NewPaneView(factory, output.NewPaneNode.Pane.ID, nil) | 361 | + | newPaneView := component.NewPaneView(ctx, factory, output.NewPaneNode.Pane.ID, nil) |
| 362 | setupPaneViewHover(ctx, newPaneView, wsView) | 362 | setupPaneViewHover(ctx, newPaneView, wsView) | ||
| 363 | 363 | ||||
| 364 | // 4. Wrap the new PaneView in a StackedView | 364 | // 4. Wrap the new PaneView in a StackedView | ||
| @@ -534,7 +534,7 @@ func (c *WorkspaceCoordinator) doIncrementalSplit( | |||||
| 534 | Msg("determined split type") | 534 | Msg("determined split type") | ||
| 535 | 535 | ||||
| 536 | // 4. Create new PaneView for the new pane | 536 | // 4. Create new PaneView for the new pane | ||
| 537 | - | newPaneView := component.NewPaneView(factory, output.NewPaneNode.Pane.ID, nil) | 537 | + | newPaneView := component.NewPaneView(ctx, factory, output.NewPaneNode.Pane.ID, nil) |
| 538 | setupPaneViewHover(ctx, newPaneView, wsView) | 538 | setupPaneViewHover(ctx, newPaneView, wsView) | ||
| 539 | 539 | ||||
| 540 | // 5. Wrap the new PaneView in a StackedView | 540 | // 5. Wrap the new PaneView in a StackedView | ||
| @@ -1438,7 +1438,7 @@ func (c *WorkspaceCoordinator) StackPane(ctx context.Context) error { | |||||
| 1438 | } | 1438 | } | ||
| 1439 | 1439 | ||||
| 1440 | // Create PaneView for the new pane | 1440 | // Create PaneView for the new pane | ||
| 1441 | - | newPaneView := component.NewPaneView(c.widgetFactory, newPaneID, nil) | 1441 | + | newPaneView := component.NewPaneView(ctx, c.widgetFactory, newPaneID, nil) |
| 1442 | setupPaneViewHover(ctx, newPaneView, stackCtx.wsView) | 1442 | setupPaneViewHover(ctx, newPaneView, stackCtx.wsView) | ||
| 1443 | stackCtx.wsView.RegisterPaneView(newPaneID, newPaneView) | 1443 | stackCtx.wsView.RegisterPaneView(newPaneID, newPaneView) | ||
| 1444 | 1444 | ||||
| @@ -2084,7 +2084,7 @@ func (c *WorkspaceCoordinator) attachPopupPaneView( | |||||
| 2084 | if wsView == nil { | 2084 | if wsView == nil { | ||
| 2085 | return nil | 2085 | return nil | ||
| 2086 | } | 2086 | } | ||
| 2087 | - | newPaneView := component.NewPaneView(c.widgetFactory, input.PopupPane.ID, nil) | 2087 | + | newPaneView := component.NewPaneView(ctx, c.widgetFactory, input.PopupPane.ID, nil) |
| 2088 | setupPaneViewHover(ctx, newPaneView, wsView) | 2088 | setupPaneViewHover(ctx, newPaneView, wsView) | ||
| 2089 | wsView.RegisterPaneView(input.PopupPane.ID, newPaneView) | 2089 | wsView.RegisterPaneView(input.PopupPane.ID, newPaneView) | ||
| 2090 | 2090 | ||||