detached 84b9d0c

fix(cef): guard delayed viewport resize pulses

commit
84b9d0cf28d2fd8e364f462378034f1b92663c6b
author
brice · · 2026-06-20 14:50 UTC
parent
e857952
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +48 -11
M internal/infrastructure/cef/webview_viewport.go +15 -9
@@ -152,11 +152,11 @@ func (wv *WebView) scheduleResizeRepaintPulse(ctx context.Context, reason string
152152 if wv == nil || wv.destroyed.Load() {
153153 return
154154 }
155- visible := true
156- if wv.viewBridge != nil {
157- if widget := wv.viewBridge.Widget(); widget != nil {
158- visible = widget.IsVisible()
159- }
155+ wv.mu.RLock()
156+ host := wv.host
157+ wv.mu.RUnlock()
158+ if host == nil {
159+ return
160160 }
161161 seq := wv.viewportResizePulseSeq.Add(1)
162162 for _, delayMs := range [...]int64{16, 48} {
@@ -165,16 +165,15 @@ func (wv *WebView) scheduleResizeRepaintPulse(ctx context.Context, reason string
165165 return
166166 }
167167 wv.mu.RLock()
168- host := wv.host
168+ currentHost := wv.host
169169 wv.mu.RUnlock()
170- if host == nil {
170+ if currentHost != host {
171171 return
172172 }
173- notifyBrowserViewportSync(host, visible)
173+ notifyBrowserViewportResize(host)
174174 logging.FromContext(ctx).Debug().
175175 Uint64("webview_id", uint64(wv.id)).
176176 Int64("delay_ms", delayMs).
177- Bool("visible", visible).
178177 Str("reason", reason).
179178 Msg("cef: delayed resize viewport sync")
180179 }))
@@ -533,6 +532,13 @@ func notifyBrowserViewportSync(host viewportSyncBrowserHost, visible bool) {
533532 if visible {
534533 host.WasHidden(0)
535534 }
535+ notifyBrowserViewportResize(host)
536+}
537+
538+func notifyBrowserViewportResize(host viewportSyncBrowserHost) {
539+ if host == nil {
540+ return
541+ }
536542 host.NotifyScreenInfoChanged()
537543 notifyBrowserResize(host)
538544 }
@@ -152,11 +152,11 @@ func (wv *WebView) scheduleResizeRepaintPulse(ctx context.Context, reason string
152 if wv == nil || wv.destroyed.Load() {152 if wv == nil || wv.destroyed.Load() {
153 return153 return
154 }154 }
155- visible := true155+ wv.mu.RLock()
156- if wv.viewBridge != nil {156+ host := wv.host
157- if widget := wv.viewBridge.Widget(); widget != nil {157+ wv.mu.RUnlock()
158- visible = widget.IsVisible()158+ if host == nil {
159- }159+ return
160 }160 }
161 seq := wv.viewportResizePulseSeq.Add(1)161 seq := wv.viewportResizePulseSeq.Add(1)
162 for _, delayMs := range [...]int64{16, 48} {162 for _, delayMs := range [...]int64{16, 48} {
@@ -165,16 +165,15 @@ func (wv *WebView) scheduleResizeRepaintPulse(ctx context.Context, reason string
165 return165 return
166 }166 }
167 wv.mu.RLock()167 wv.mu.RLock()
168- host := wv.host168+ currentHost := wv.host
169 wv.mu.RUnlock()169 wv.mu.RUnlock()
170- if host == nil {170+ if currentHost != host {
171 return171 return
172 }172 }
173- notifyBrowserViewportSync(host, visible)173+ notifyBrowserViewportResize(host)
174 logging.FromContext(ctx).Debug().174 logging.FromContext(ctx).Debug().
175 Uint64("webview_id", uint64(wv.id)).175 Uint64("webview_id", uint64(wv.id)).
176 Int64("delay_ms", delayMs).176 Int64("delay_ms", delayMs).
177- Bool("visible", visible).
178 Str("reason", reason).177 Str("reason", reason).
179 Msg("cef: delayed resize viewport sync")178 Msg("cef: delayed resize viewport sync")
180 }))179 }))
@@ -533,6 +532,13 @@ func notifyBrowserViewportSync(host viewportSyncBrowserHost, visible bool) {
533 if visible {532 if visible {
534 host.WasHidden(0)533 host.WasHidden(0)
535 }534 }
535+ notifyBrowserViewportResize(host)
536+}
537+
538+func notifyBrowserViewportResize(host viewportSyncBrowserHost) {
539+ if host == nil {
540+ return
541+ }
536 host.NotifyScreenInfoChanged()542 host.NotifyScreenInfoChanged()
537 notifyBrowserResize(host)543 notifyBrowserResize(host)
538 }544 }
M internal/infrastructure/cef/webview_viewport_test.go +33 -2
@@ -451,13 +451,44 @@ func TestScheduleResizeRepaintPulse_CoalescesToLatestSequence(t *testing.T) {
451451 scheduled[2].Execute()
452452 scheduled[3].Execute()
453453 require.Equal(t, []string{
454- "WasHidden",
455454 "NotifyScreenInfoChanged",
456455 "WasResized",
457456 "Invalidate",
458- "WasHidden",
459457 "NotifyScreenInfoChanged",
460458 "WasResized",
461459 "Invalidate",
462460 }, host.calls)
463461 }
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, delayMs 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+}
@@ -451,13 +451,44 @@ func TestScheduleResizeRepaintPulse_CoalescesToLatestSequence(t *testing.T) {
451 scheduled[2].Execute()451 scheduled[2].Execute()
452 scheduled[3].Execute()452 scheduled[3].Execute()
453 require.Equal(t, []string{453 require.Equal(t, []string{
454- "WasHidden",
455 "NotifyScreenInfoChanged",454 "NotifyScreenInfoChanged",
456 "WasResized",455 "WasResized",
457 "Invalidate",456 "Invalidate",
458- "WasHidden",
459 "NotifyScreenInfoChanged",457 "NotifyScreenInfoChanged",
460 "WasResized",458 "WasResized",
461 "Invalidate",459 "Invalidate",
462 }, host.calls)460 }, host.calls)
463 }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, delayMs 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+}

Keyboard shortcuts

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

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

Branches 8

Tags 42

↑↓ moveenter openesc close