detached 55dbfae

fix(cef): synchronize effective visibility transitions

commit
55dbfae6c0c68efd715ba4dccafb850175e5ed2d
author
brice · · 2026-07-14 06:18 UTC
committer
brice · · 2026-07-14 07:06 UTC
parent
d49b626
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +31 -4
M internal/infrastructure/cef/webview.go +4 -0
@@ -112,6 +112,10 @@ type WebView struct {
112112 viewportShowSignalID uint
113113 viewportRealizeSignalID uint
114114 viewportResizePulseSeq atomic.Uint64
115+ // effectiveVisibility records the last CEF WasHidden state dispatched. It
116+ // is guarded by mu because GTK and CEF UI work cross threads.
117+ effectiveVisibilityKnown bool
118+ effectiveVisible bool
115119
116120 adaptiveWindowlessFrameRate bool
117121 windowlessFrameRateMax int32
@@ -112,6 +112,10 @@ type WebView struct {
112 viewportShowSignalID uint112 viewportShowSignalID uint
113 viewportRealizeSignalID uint113 viewportRealizeSignalID uint
114 viewportResizePulseSeq atomic.Uint64114 viewportResizePulseSeq atomic.Uint64
115+ // effectiveVisibility records the last CEF WasHidden state dispatched. It
116+ // is guarded by mu because GTK and CEF UI work cross threads.
117+ effectiveVisibilityKnown bool
118+ effectiveVisible bool
115 119
116 adaptiveWindowlessFrameRate bool120 adaptiveWindowlessFrameRate bool
117 windowlessFrameRateMax int32121 windowlessFrameRateMax int32
M internal/infrastructure/cef/webview_handlers.go +3 -3
@@ -819,10 +819,10 @@ func (h *handlerSet) attachAfterCreatedBrowser(
819819 })
820820 }
821821
822- // Mark browser as visible — CEF OSR starts in hidden state and suppresses
823- // painting/caret updates until explicitly told the browser is shown.
824- host.WasHidden(0)
822+ // CEF OSR starts hidden; record the initial effective state before viewport
823+ // synchronization so the following visible sync is deduplicated.
825824 if h.wv != nil {
825+ h.wv.applyEffectiveVisibility(host, true)
826826 if h.wv.ctx != nil {
827827 logging.FromContext(h.wv.ctx).Info().
828828 Int32("windowless_frame_rate_configured", host.GetWindowlessFrameRate()).
@@ -819,10 +819,10 @@ func (h *handlerSet) attachAfterCreatedBrowser(
819 })819 })
820 }820 }
821 821
822- // Mark browser as visible — CEF OSR starts in hidden state and suppresses822+ // CEF OSR starts hidden; record the initial effective state before viewport
823- // painting/caret updates until explicitly told the browser is shown.823+ // synchronization so the following visible sync is deduplicated.
824- host.WasHidden(0)
825 if h.wv != nil {824 if h.wv != nil {
825+ h.wv.applyEffectiveVisibility(host, true)
826 if h.wv.ctx != nil {826 if h.wv.ctx != nil {
827 logging.FromContext(h.wv.ctx).Info().827 logging.FromContext(h.wv.ctx).Info().
828 Int32("windowless_frame_rate_configured", host.GetWindowlessFrameRate()).828 Int32("windowless_frame_rate_configured", host.GetWindowlessFrameRate()).
M internal/infrastructure/cef/webview_viewport.go +24 -1
@@ -488,7 +488,8 @@ func (wv *WebView) notifyViewportSyncOnCEFUIThread(host viewportSyncBrowserHost,
488488 if currentHost != host {
489489 return
490490 }
491- notifyBrowserViewportSync(host, visible)
491+ wv.applyEffectiveVisibility(host, visible)
492+ notifyBrowserViewportResize(host)
492493 }))
493494 if task == nil {
494495 return
@@ -526,6 +527,28 @@ func (wv *WebView) syncZoomForBackingScaleOnCEFUIThread(host purecef.BrowserHost
526527 cefPostDelayedTask(purecef.ThreadIDTidUi, task, 0)
527528 }
528529
530+// applyEffectiveVisibility reports the initial effective visibility and later
531+// transitions exactly once. CEF OSR begins hidden, so even an initially hidden
532+// GTK widget must be explicitly reported to keep both lifecycles aligned.
533+func (wv *WebView) applyEffectiveVisibility(host viewportSyncBrowserHost, visible bool) {
534+ if wv == nil || host == nil || wv.destroyed.Load() {
535+ return
536+ }
537+ wv.mu.Lock()
538+ if wv.effectiveVisibilityKnown && wv.effectiveVisible == visible {
539+ wv.mu.Unlock()
540+ return
541+ }
542+ wv.effectiveVisibilityKnown = true
543+ wv.effectiveVisible = visible
544+ wv.mu.Unlock()
545+ if visible {
546+ host.WasHidden(0)
547+ return
548+ }
549+ host.WasHidden(1)
550+}
551+
529552 func notifyBrowserViewportSync(host viewportSyncBrowserHost, visible bool) {
530553 if host == nil {
531554 return
@@ -488,7 +488,8 @@ func (wv *WebView) notifyViewportSyncOnCEFUIThread(host viewportSyncBrowserHost,
488 if currentHost != host {488 if currentHost != host {
489 return489 return
490 }490 }
491- notifyBrowserViewportSync(host, visible)491+ wv.applyEffectiveVisibility(host, visible)
492+ notifyBrowserViewportResize(host)
492 }))493 }))
493 if task == nil {494 if task == nil {
494 return495 return
@@ -526,6 +527,28 @@ func (wv *WebView) syncZoomForBackingScaleOnCEFUIThread(host purecef.BrowserHost
526 cefPostDelayedTask(purecef.ThreadIDTidUi, task, 0)527 cefPostDelayedTask(purecef.ThreadIDTidUi, task, 0)
527 }528 }
528 529
530+// applyEffectiveVisibility reports the initial effective visibility and later
531+// transitions exactly once. CEF OSR begins hidden, so even an initially hidden
532+// GTK widget must be explicitly reported to keep both lifecycles aligned.
533+func (wv *WebView) applyEffectiveVisibility(host viewportSyncBrowserHost, visible bool) {
534+ if wv == nil || host == nil || wv.destroyed.Load() {
535+ return
536+ }
537+ wv.mu.Lock()
538+ if wv.effectiveVisibilityKnown && wv.effectiveVisible == visible {
539+ wv.mu.Unlock()
540+ return
541+ }
542+ wv.effectiveVisibilityKnown = true
543+ wv.effectiveVisible = visible
544+ wv.mu.Unlock()
545+ if visible {
546+ host.WasHidden(0)
547+ return
548+ }
549+ host.WasHidden(1)
550+}
551+
529 func notifyBrowserViewportSync(host viewportSyncBrowserHost, visible bool) {552 func notifyBrowserViewportSync(host viewportSyncBrowserHost, visible bool) {
530 if host == nil {553 if host == nil {
531 return554 return

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