Merge pull request #318 from bnema/fix/cef-viewport-refit
fix(cef): keep webview viewport synced during resize
4 files · +120 -9
M go.mod +1 -1
| @@ -7,7 +7,7 @@ require ( | |||
| 7 | 7 | github.com/andybalholm/brotli v1.2.1 | |
| 8 | 8 | github.com/bnema/purego v0.11.0-bnema.4 | |
| 9 | 9 | github.com/bnema/purego-cef v0.13.3 | |
| 10 | - | github.com/bnema/purego-cef2gtk v0.8.1 | |
| 10 | + | github.com/bnema/purego-cef2gtk v0.8.2 | |
| 11 | 11 | github.com/bnema/purego-pipewire v0.1.4 | |
| 12 | 12 | github.com/bnema/purego-sqlite v0.1.3 | |
| 13 | 13 | github.com/bnema/puregotk v0.7.0 | |
| @@ -7,7 +7,7 @@ require ( | |||||
| 7 | github.com/andybalholm/brotli v1.2.1 | 7 | github.com/andybalholm/brotli v1.2.1 | ||
| 8 | github.com/bnema/purego v0.11.0-bnema.4 | 8 | github.com/bnema/purego v0.11.0-bnema.4 | ||
| 9 | github.com/bnema/purego-cef v0.13.3 | 9 | github.com/bnema/purego-cef v0.13.3 | ||
| 10 | - | github.com/bnema/purego-cef2gtk v0.8.1 | 10 | + | github.com/bnema/purego-cef2gtk v0.8.2 |
| 11 | github.com/bnema/purego-pipewire v0.1.4 | 11 | github.com/bnema/purego-pipewire v0.1.4 | ||
| 12 | github.com/bnema/purego-sqlite v0.1.3 | 12 | github.com/bnema/purego-sqlite v0.1.3 | ||
| 13 | github.com/bnema/puregotk v0.7.0 | 13 | github.com/bnema/puregotk v0.7.0 | ||
M go.sum +2 -2 generated file: collapsed
M internal/infrastructure/cef/webview_viewport.go +22 -5
| @@ -94,7 +94,11 @@ func (wv *WebView) syncViewportOnGTK(ctx context.Context) { | |||
| 94 | 94 | if wv == nil { | |
| 95 | 95 | return | |
| 96 | 96 | } | |
| 97 | - | wv.syncViewportNowOnGTK(ctx, wv.takeViewportSyncReason()) | |
| 97 | + | reason := wv.takeViewportSyncReason() | |
| 98 | + | ctx = wv.viewportSyncContext(ctx) | |
| 99 | + | if wv.syncViewportNowOnGTK(ctx, reason) { | |
| 100 | + | wv.scheduleResizeRepaintPulse(ctx, reason) | |
| 101 | + | } | |
| 98 | 102 | } | |
| 99 | 103 | ||
| 100 | 104 | func (wv *WebView) syncViewportNowOnGTK(ctx context.Context, reason string) bool { | |
| @@ -148,6 +152,12 @@ func (wv *WebView) scheduleResizeRepaintPulse(ctx context.Context, reason string | |||
| 148 | 152 | if wv == nil || wv.destroyed.Load() { | |
| 149 | 153 | return | |
| 150 | 154 | } | |
| 155 | + | wv.mu.RLock() | |
| 156 | + | host := wv.host | |
| 157 | + | wv.mu.RUnlock() | |
| 158 | + | if host == nil { | |
| 159 | + | return | |
| 160 | + | } | |
| 151 | 161 | seq := wv.viewportResizePulseSeq.Add(1) | |
| 152 | 162 | for _, delayMs := range [...]int64{16, 48} { | |
| 153 | 163 | task := cefNewTask(cefTaskFunc(func() { | |
| @@ -155,17 +165,17 @@ func (wv *WebView) scheduleResizeRepaintPulse(ctx context.Context, reason string | |||
| 155 | 165 | return | |
| 156 | 166 | } | |
| 157 | 167 | wv.mu.RLock() | |
| 158 | - | host := wv.host | |
| 168 | + | currentHost := wv.host | |
| 159 | 169 | wv.mu.RUnlock() | |
| 160 | - | if host == nil { | |
| 170 | + | if currentHost != host { | |
| 161 | 171 | return | |
| 162 | 172 | } | |
| 163 | - | host.Invalidate(purecef.PaintElementTypePetView) | |
| 173 | + | notifyBrowserViewportResize(host) | |
| 164 | 174 | logging.FromContext(ctx).Debug(). | |
| 165 | 175 | Uint64("webview_id", uint64(wv.id)). | |
| 166 | 176 | Int64("delay_ms", delayMs). | |
| 167 | 177 | Str("reason", reason). | |
| 168 | - | Msg("cef: delayed resize repaint pulse") | |
| 178 | + | Msg("cef: delayed resize viewport sync") | |
| 169 | 179 | })) | |
| 170 | 180 | if task == nil { | |
| 171 | 181 | continue | |
| @@ -522,6 +532,13 @@ func notifyBrowserViewportSync(host viewportSyncBrowserHost, visible bool) { | |||
| 522 | 532 | if visible { | |
| 523 | 533 | host.WasHidden(0) | |
| 524 | 534 | } | |
| 535 | + | notifyBrowserViewportResize(host) | |
| 536 | + | } | |
| 537 | + | ||
| 538 | + | func notifyBrowserViewportResize(host viewportSyncBrowserHost) { | |
| 539 | + | if host == nil { | |
| 540 | + | return | |
| 541 | + | } | |
| 525 | 542 | host.NotifyScreenInfoChanged() | |
| 526 | 543 | notifyBrowserResize(host) | |
| 527 | 544 | } | |
| @@ -94,7 +94,11 @@ func (wv *WebView) syncViewportOnGTK(ctx context.Context) { | |||||
| 94 | if wv == nil { | 94 | if wv == nil { | ||
| 95 | return | 95 | return | ||
| 96 | } | 96 | } | ||
| 97 | - | wv.syncViewportNowOnGTK(ctx, wv.takeViewportSyncReason()) | 97 | + | reason := wv.takeViewportSyncReason() |
| 98 | + | ctx = wv.viewportSyncContext(ctx) | |||
| 99 | + | if wv.syncViewportNowOnGTK(ctx, reason) { | |||
| 100 | + | wv.scheduleResizeRepaintPulse(ctx, reason) | |||
| 101 | + | } | |||
| 98 | } | 102 | } | ||
| 99 | 103 | ||||
| 100 | func (wv *WebView) syncViewportNowOnGTK(ctx context.Context, reason string) bool { | 104 | func (wv *WebView) syncViewportNowOnGTK(ctx context.Context, reason string) bool { | ||
| @@ -148,6 +152,12 @@ func (wv *WebView) scheduleResizeRepaintPulse(ctx context.Context, reason string | |||||
| 148 | if wv == nil || wv.destroyed.Load() { | 152 | if wv == nil || wv.destroyed.Load() { | ||
| 149 | return | 153 | return | ||
| 150 | } | 154 | } | ||
| 155 | + | wv.mu.RLock() | |||
| 156 | + | host := wv.host | |||
| 157 | + | wv.mu.RUnlock() | |||
| 158 | + | if host == nil { | |||
| 159 | + | return | |||
| 160 | + | } | |||
| 151 | seq := wv.viewportResizePulseSeq.Add(1) | 161 | seq := wv.viewportResizePulseSeq.Add(1) | ||
| 152 | for _, delayMs := range [...]int64{16, 48} { | 162 | for _, delayMs := range [...]int64{16, 48} { | ||
| 153 | task := cefNewTask(cefTaskFunc(func() { | 163 | task := cefNewTask(cefTaskFunc(func() { | ||
| @@ -155,17 +165,17 @@ func (wv *WebView) scheduleResizeRepaintPulse(ctx context.Context, reason string | |||||
| 155 | return | 165 | return | ||
| 156 | } | 166 | } | ||
| 157 | wv.mu.RLock() | 167 | wv.mu.RLock() | ||
| 158 | - | host := wv.host | 168 | + | currentHost := wv.host |
| 159 | wv.mu.RUnlock() | 169 | wv.mu.RUnlock() | ||
| 160 | - | if host == nil { | 170 | + | if currentHost != host { |
| 161 | return | 171 | return | ||
| 162 | } | 172 | } | ||
| 163 | - | host.Invalidate(purecef.PaintElementTypePetView) | 173 | + | notifyBrowserViewportResize(host) |
| 164 | logging.FromContext(ctx).Debug(). | 174 | logging.FromContext(ctx).Debug(). | ||
| 165 | Uint64("webview_id", uint64(wv.id)). | 175 | Uint64("webview_id", uint64(wv.id)). | ||
| 166 | Int64("delay_ms", delayMs). | 176 | Int64("delay_ms", delayMs). | ||
| 167 | Str("reason", reason). | 177 | Str("reason", reason). | ||
| 168 | - | Msg("cef: delayed resize repaint pulse") | 178 | + | Msg("cef: delayed resize viewport sync") |
| 169 | })) | 179 | })) | ||
| 170 | if task == nil { | 180 | if task == nil { | ||
| 171 | continue | 181 | continue | ||
| @@ -522,6 +532,13 @@ func notifyBrowserViewportSync(host viewportSyncBrowserHost, visible bool) { | |||||
| 522 | if visible { | 532 | if visible { | ||
| 523 | host.WasHidden(0) | 533 | host.WasHidden(0) | ||
| 524 | } | 534 | } | ||
| 535 | + | notifyBrowserViewportResize(host) | |||
| 536 | + | } | |||
| 537 | + | ||||
| 538 | + | func notifyBrowserViewportResize(host viewportSyncBrowserHost) { | |||
| 539 | + | if host == nil { | |||
| 540 | + | return | |||
| 541 | + | } | |||
| 525 | host.NotifyScreenInfoChanged() | 542 | host.NotifyScreenInfoChanged() | ||
| 526 | notifyBrowserResize(host) | 543 | notifyBrowserResize(host) | ||
| 527 | } | 544 | } | ||
M internal/infrastructure/cef/webview_viewport_test.go +95 -1
| @@ -450,5 +450,99 @@ func TestScheduleResizeRepaintPulse_CoalescesToLatestSequence(t *testing.T) { | |||
| 450 | 450 | ||
| 451 | 451 | scheduled[2].Execute() | |
| 452 | 452 | scheduled[3].Execute() | |
| 453 | - | require.Equal(t, []string{"Invalidate", "Invalidate"}, host.calls) | |
| 453 | + | require.Equal(t, []string{ | |
| 454 | + | "NotifyScreenInfoChanged", | |
| 455 | + | "WasResized", | |
| 456 | + | "Invalidate", | |
| 457 | + | "NotifyScreenInfoChanged", | |
| 458 | + | "WasResized", | |
| 459 | + | "Invalidate", | |
| 460 | + | }, host.calls) | |
| 461 | + | } | |
| 462 | + | ||
| 463 | + | func TestScheduleResizeRepaintPulse_SkipsStaleHost(t *testing.T) { | |
| 464 | + | oldNewTask := cefNewTask | |
| 465 | + | oldPostDelayedTask := cefPostDelayedTask | |
| 466 | + | defer func() { | |
| 467 | + | cefNewTask = oldNewTask | |
| 468 | + | cefPostDelayedTask = oldPostDelayedTask | |
| 469 | + | }() | |
| 470 | + | ||
| 471 | + | cefNewTask = func(task purecef.Task) purecef.Task { return task } | |
| 472 | + | ||
| 473 | + | var scheduled []purecef.Task | |
| 474 | + | cefPostDelayedTask = func(threadID purecef.ThreadID, task purecef.Task, _ int64) int32 { | |
| 475 | + | require.Equal(t, purecef.ThreadIDTidUi, threadID) | |
| 476 | + | require.NotNil(t, task) | |
| 477 | + | scheduled = append(scheduled, task) | |
| 478 | + | return 1 | |
| 479 | + | } | |
| 480 | + | ||
| 481 | + | capturedHost := &viewportSyncOrderHost{} | |
| 482 | + | currentHost := &viewportSyncOrderHost{} | |
| 483 | + | wv := &WebView{ctx: context.Background(), host: capturedHost} | |
| 484 | + | ||
| 485 | + | wv.scheduleResizeRepaintPulse(context.Background(), "stale-host") | |
| 486 | + | wv.host = currentHost | |
| 487 | + | ||
| 488 | + | require.Len(t, scheduled, 2) | |
| 489 | + | scheduled[0].Execute() | |
| 490 | + | scheduled[1].Execute() | |
| 491 | + | ||
| 492 | + | require.Empty(t, capturedHost.calls) | |
| 493 | + | require.Empty(t, currentHost.calls) | |
| 494 | + | } | |
| 495 | + | ||
| 496 | + | func TestScheduleResizeRepaintPulse_SkipsWhenHostNilAtScheduleTime(t *testing.T) { | |
| 497 | + | oldNewTask := cefNewTask | |
| 498 | + | oldPostDelayedTask := cefPostDelayedTask | |
| 499 | + | defer func() { | |
| 500 | + | cefNewTask = oldNewTask | |
| 501 | + | cefPostDelayedTask = oldPostDelayedTask | |
| 502 | + | }() | |
| 503 | + | ||
| 504 | + | cefNewTask = func(task purecef.Task) purecef.Task { return task } | |
| 505 | + | ||
| 506 | + | var scheduled []purecef.Task | |
| 507 | + | cefPostDelayedTask = func(_ purecef.ThreadID, task purecef.Task, _ int64) int32 { | |
| 508 | + | scheduled = append(scheduled, task) | |
| 509 | + | return 1 | |
| 510 | + | } | |
| 511 | + | ||
| 512 | + | wv := &WebView{ctx: context.Background()} | |
| 513 | + | ||
| 514 | + | wv.scheduleResizeRepaintPulse(context.Background(), "nil-host") | |
| 515 | + | ||
| 516 | + | require.Empty(t, scheduled) | |
| 517 | + | } | |
| 518 | + | ||
| 519 | + | func TestScheduleResizeRepaintPulse_SkipsWhenHostClearedBeforeExecution(t *testing.T) { | |
| 520 | + | oldNewTask := cefNewTask | |
| 521 | + | oldPostDelayedTask := cefPostDelayedTask | |
| 522 | + | defer func() { | |
| 523 | + | cefNewTask = oldNewTask | |
| 524 | + | cefPostDelayedTask = oldPostDelayedTask | |
| 525 | + | }() | |
| 526 | + | ||
| 527 | + | cefNewTask = func(task purecef.Task) purecef.Task { return task } | |
| 528 | + | ||
| 529 | + | var scheduled []purecef.Task | |
| 530 | + | cefPostDelayedTask = func(threadID purecef.ThreadID, task purecef.Task, _ int64) int32 { | |
| 531 | + | require.Equal(t, purecef.ThreadIDTidUi, threadID) | |
| 532 | + | require.NotNil(t, task) | |
| 533 | + | scheduled = append(scheduled, task) | |
| 534 | + | return 1 | |
| 535 | + | } | |
| 536 | + | ||
| 537 | + | host := &viewportSyncOrderHost{} | |
| 538 | + | wv := &WebView{ctx: context.Background(), host: host} | |
| 539 | + | ||
| 540 | + | wv.scheduleResizeRepaintPulse(context.Background(), "nil-host-before-execute") | |
| 541 | + | wv.host = nil | |
| 542 | + | ||
| 543 | + | require.Len(t, scheduled, 2) | |
| 544 | + | scheduled[0].Execute() | |
| 545 | + | scheduled[1].Execute() | |
| 546 | + | ||
| 547 | + | require.Empty(t, host.calls) | |
| 454 | 548 | } | |
| @@ -450,5 +450,99 @@ func TestScheduleResizeRepaintPulse_CoalescesToLatestSequence(t *testing.T) { | |||||
| 450 | 450 | ||||
| 451 | scheduled[2].Execute() | 451 | scheduled[2].Execute() | ||
| 452 | scheduled[3].Execute() | 452 | scheduled[3].Execute() | ||
| 453 | - | require.Equal(t, []string{"Invalidate", "Invalidate"}, host.calls) | 453 | + | require.Equal(t, []string{ |
| 454 | + | "NotifyScreenInfoChanged", | |||
| 455 | + | "WasResized", | |||
| 456 | + | "Invalidate", | |||
| 457 | + | "NotifyScreenInfoChanged", | |||
| 458 | + | "WasResized", | |||
| 459 | + | "Invalidate", | |||
| 460 | + | }, host.calls) | |||
| 461 | + | } | |||
| 462 | + | ||||
| 463 | + | func TestScheduleResizeRepaintPulse_SkipsStaleHost(t *testing.T) { | |||
| 464 | + | oldNewTask := cefNewTask | |||
| 465 | + | oldPostDelayedTask := cefPostDelayedTask | |||
| 466 | + | defer func() { | |||
| 467 | + | cefNewTask = oldNewTask | |||
| 468 | + | cefPostDelayedTask = oldPostDelayedTask | |||
| 469 | + | }() | |||
| 470 | + | ||||
| 471 | + | cefNewTask = func(task purecef.Task) purecef.Task { return task } | |||
| 472 | + | ||||
| 473 | + | var scheduled []purecef.Task | |||
| 474 | + | cefPostDelayedTask = func(threadID purecef.ThreadID, task purecef.Task, _ int64) int32 { | |||
| 475 | + | require.Equal(t, purecef.ThreadIDTidUi, threadID) | |||
| 476 | + | require.NotNil(t, task) | |||
| 477 | + | scheduled = append(scheduled, task) | |||
| 478 | + | return 1 | |||
| 479 | + | } | |||
| 480 | + | ||||
| 481 | + | capturedHost := &viewportSyncOrderHost{} | |||
| 482 | + | currentHost := &viewportSyncOrderHost{} | |||
| 483 | + | wv := &WebView{ctx: context.Background(), host: capturedHost} | |||
| 484 | + | ||||
| 485 | + | wv.scheduleResizeRepaintPulse(context.Background(), "stale-host") | |||
| 486 | + | wv.host = currentHost | |||
| 487 | + | ||||
| 488 | + | require.Len(t, scheduled, 2) | |||
| 489 | + | scheduled[0].Execute() | |||
| 490 | + | scheduled[1].Execute() | |||
| 491 | + | ||||
| 492 | + | require.Empty(t, capturedHost.calls) | |||
| 493 | + | require.Empty(t, currentHost.calls) | |||
| 494 | + | } | |||
| 495 | + | ||||
| 496 | + | func TestScheduleResizeRepaintPulse_SkipsWhenHostNilAtScheduleTime(t *testing.T) { | |||
| 497 | + | oldNewTask := cefNewTask | |||
| 498 | + | oldPostDelayedTask := cefPostDelayedTask | |||
| 499 | + | defer func() { | |||
| 500 | + | cefNewTask = oldNewTask | |||
| 501 | + | cefPostDelayedTask = oldPostDelayedTask | |||
| 502 | + | }() | |||
| 503 | + | ||||
| 504 | + | cefNewTask = func(task purecef.Task) purecef.Task { return task } | |||
| 505 | + | ||||
| 506 | + | var scheduled []purecef.Task | |||
| 507 | + | cefPostDelayedTask = func(_ purecef.ThreadID, task purecef.Task, _ int64) int32 { | |||
| 508 | + | scheduled = append(scheduled, task) | |||
| 509 | + | return 1 | |||
| 510 | + | } | |||
| 511 | + | ||||
| 512 | + | wv := &WebView{ctx: context.Background()} | |||
| 513 | + | ||||
| 514 | + | wv.scheduleResizeRepaintPulse(context.Background(), "nil-host") | |||
| 515 | + | ||||
| 516 | + | require.Empty(t, scheduled) | |||
| 517 | + | } | |||
| 518 | + | ||||
| 519 | + | func TestScheduleResizeRepaintPulse_SkipsWhenHostClearedBeforeExecution(t *testing.T) { | |||
| 520 | + | oldNewTask := cefNewTask | |||
| 521 | + | oldPostDelayedTask := cefPostDelayedTask | |||
| 522 | + | defer func() { | |||
| 523 | + | cefNewTask = oldNewTask | |||
| 524 | + | cefPostDelayedTask = oldPostDelayedTask | |||
| 525 | + | }() | |||
| 526 | + | ||||
| 527 | + | cefNewTask = func(task purecef.Task) purecef.Task { return task } | |||
| 528 | + | ||||
| 529 | + | var scheduled []purecef.Task | |||
| 530 | + | cefPostDelayedTask = func(threadID purecef.ThreadID, task purecef.Task, _ int64) int32 { | |||
| 531 | + | require.Equal(t, purecef.ThreadIDTidUi, threadID) | |||
| 532 | + | require.NotNil(t, task) | |||
| 533 | + | scheduled = append(scheduled, task) | |||
| 534 | + | return 1 | |||
| 535 | + | } | |||
| 536 | + | ||||
| 537 | + | host := &viewportSyncOrderHost{} | |||
| 538 | + | wv := &WebView{ctx: context.Background(), host: host} | |||
| 539 | + | ||||
| 540 | + | wv.scheduleResizeRepaintPulse(context.Background(), "nil-host-before-execute") | |||
| 541 | + | wv.host = nil | |||
| 542 | + | ||||
| 543 | + | require.Len(t, scheduled, 2) | |||
| 544 | + | scheduled[0].Execute() | |||
| 545 | + | scheduled[1].Execute() | |||
| 546 | + | ||||
| 547 | + | require.Empty(t, host.calls) | |||
| 454 | } | 548 | } | ||