detached 8e7fa59

fix: harden ghost clear race and aggressive webview reuse safety

commit
8e7fa596047db9da67f3b4b154296baf72762d99
author
brice · · 2026-02-07 08:41 UTC
parent
162a4b9
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +14 -2
M internal/infrastructure/webkit/pool.go +3 -1
@@ -519,7 +519,9 @@ func (p *WebViewPool) shouldReuseOnRelease(wv *WebView) bool {
519519 case reusePolicySafe:
520520 return !wv.HasNavigationActivity()
521521 case reusePolicyAggressive:
522- return true
522+ // Even in aggressive mode, avoid reusing views that already navigated.
523+ // Reusing navigated views can leak back/forward/page context across tabs.
524+ return !wv.HasNavigationActivity()
523525 default:
524526 return false
525527 }
@@ -519,7 +519,9 @@ func (p *WebViewPool) shouldReuseOnRelease(wv *WebView) bool {
519 case reusePolicySafe:519 case reusePolicySafe:
520 return !wv.HasNavigationActivity()520 return !wv.HasNavigationActivity()
521 case reusePolicyAggressive:521 case reusePolicyAggressive:
522- return true522+ // Even in aggressive mode, avoid reusing views that already navigated.
523+ // Reusing navigated views can leak back/forward/page context across tabs.
524+ return !wv.HasNavigationActivity()
523 default:525 default:
524 return false526 return false
525 }527 }
M internal/ui/component/omnibox.go +11 -1
@@ -914,8 +914,10 @@ func (o *Omnibox) setGhostText(originalInput, suffix, fullText string) {
914914 func (o *Omnibox) clearGhostText() {
915915 o.mu.Lock()
916916 realInput := o.realInput
917+ ghostFullText := o.ghostFullText
917918 hadGhost := o.hasGhostText || o.ghostSuffix != "" || o.ghostFullText != ""
918919 o.ghostToken++
920+ capturedToken := o.ghostToken
919921 o.ghostSuffix = ""
920922 o.ghostFullText = ""
921923 o.hasGhostText = false
@@ -929,10 +931,18 @@ func (o *Omnibox) clearGhostText() {
929931 if o.entry == nil {
930932 return false
931933 }
934+ o.mu.RLock()
935+ currentToken := o.ghostToken
936+ o.mu.RUnlock()
937+ if capturedToken != currentToken {
938+ return false
939+ }
932940 o.mu.Lock()
933941 o.isAcceptingGhost = true
934942 o.mu.Unlock()
935- if o.entry.GetText() != realInput {
943+ // Only revert text when the entry still shows the previous ghost completion.
944+ // If the user typed since scheduling this idle callback, never overwrite it.
945+ if o.entry.GetText() == ghostFullText && o.entry.GetText() != realInput {
936946 o.entry.SetText(realInput)
937947 }
938948 o.entry.SelectRegion(-1, -1)
@@ -914,8 +914,10 @@ func (o *Omnibox) setGhostText(originalInput, suffix, fullText string) {
914 func (o *Omnibox) clearGhostText() {914 func (o *Omnibox) clearGhostText() {
915 o.mu.Lock()915 o.mu.Lock()
916 realInput := o.realInput916 realInput := o.realInput
917+ ghostFullText := o.ghostFullText
917 hadGhost := o.hasGhostText || o.ghostSuffix != "" || o.ghostFullText != ""918 hadGhost := o.hasGhostText || o.ghostSuffix != "" || o.ghostFullText != ""
918 o.ghostToken++919 o.ghostToken++
920+ capturedToken := o.ghostToken
919 o.ghostSuffix = ""921 o.ghostSuffix = ""
920 o.ghostFullText = ""922 o.ghostFullText = ""
921 o.hasGhostText = false923 o.hasGhostText = false
@@ -929,10 +931,18 @@ func (o *Omnibox) clearGhostText() {
929 if o.entry == nil {931 if o.entry == nil {
930 return false932 return false
931 }933 }
934+ o.mu.RLock()
935+ currentToken := o.ghostToken
936+ o.mu.RUnlock()
937+ if capturedToken != currentToken {
938+ return false
939+ }
932 o.mu.Lock()940 o.mu.Lock()
933 o.isAcceptingGhost = true941 o.isAcceptingGhost = true
934 o.mu.Unlock()942 o.mu.Unlock()
935- if o.entry.GetText() != realInput {943+ // Only revert text when the entry still shows the previous ghost completion.
944+ // If the user typed since scheduling this idle callback, never overwrite it.
945+ if o.entry.GetText() == ghostFullText && o.entry.GetText() != realInput {
936 o.entry.SetText(realInput)946 o.entry.SetText(realInput)
937 }947 }
938 o.entry.SelectRegion(-1, -1)948 o.entry.SelectRegion(-1, -1)

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