detached 69151be

refactor: consolidate reveal state

commit
69151be1c94e5b3550cd440949404f92ebf90a23
author
brice · · 2026-07-15 09:07 UTC
parent
c811fd0
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +46 -45
M internal/ui/coordinator/content/content_reveal_test.go +16 -0
@@ -54,6 +54,22 @@ func TestRevealState_IsBoundToCurrentWebViewAcrossReplacementPaths(t *testing.T)
5454 assert.False(t, c.WebViewRevealed(paneID), "popup registration must not inherit a prior WebView reveal")
5555 }
5656
57+func TestRevealState_StoresIdentityAsTheOnlyStateValue(t *testing.T) {
58+ paneID := entity.PaneID("pane-1")
59+ wv := revealTestWebView(t, 101, 1)
60+ c := newRevealTestCoordinator()
61+ c.setWebViewLocked(paneID, wv)
62+ identity, _ := identityForWebView(wv)
63+
64+ c.markPendingReveal(paneID, identity)
65+ c.markWebViewRevealed(paneID, identity)
66+
67+ c.revealMu.Lock()
68+ defer c.revealMu.Unlock()
69+ assert.Equal(t, identity, c.pendingReveal[paneID])
70+ assert.Equal(t, identity, c.revealedWebViews[paneID])
71+}
72+
5773 func TestRevealState_ReleaseClearsStateWithoutWebViewMapping(t *testing.T) {
5874 paneID := entity.PaneID("pane-1")
5975 wv := revealTestWebView(t, 101, 1)
@@ -54,6 +54,22 @@ func TestRevealState_IsBoundToCurrentWebViewAcrossReplacementPaths(t *testing.T)
54 assert.False(t, c.WebViewRevealed(paneID), "popup registration must not inherit a prior WebView reveal")54 assert.False(t, c.WebViewRevealed(paneID), "popup registration must not inherit a prior WebView reveal")
55 }55 }
56 56
57+func TestRevealState_StoresIdentityAsTheOnlyStateValue(t *testing.T) {
58+ paneID := entity.PaneID("pane-1")
59+ wv := revealTestWebView(t, 101, 1)
60+ c := newRevealTestCoordinator()
61+ c.setWebViewLocked(paneID, wv)
62+ identity, _ := identityForWebView(wv)
63+
64+ c.markPendingReveal(paneID, identity)
65+ c.markWebViewRevealed(paneID, identity)
66+
67+ c.revealMu.Lock()
68+ defer c.revealMu.Unlock()
69+ assert.Equal(t, identity, c.pendingReveal[paneID])
70+ assert.Equal(t, identity, c.revealedWebViews[paneID])
71+}
72+
57 func TestRevealState_ReleaseClearsStateWithoutWebViewMapping(t *testing.T) {73 func TestRevealState_ReleaseClearsStateWithoutWebViewMapping(t *testing.T) {
58 paneID := entity.PaneID("pane-1")74 paneID := entity.PaneID("pane-1")
59 wv := revealTestWebView(t, 101, 1)75 wv := revealTestWebView(t, 101, 1)
M internal/ui/coordinator/content/coordinator.go +22 -36
@@ -80,11 +80,9 @@ type Coordinator struct {
8080
8181 // revealMu is always acquired after webViewsMu. Reveal state is keyed by the
8282 // WebView identity (ID plus pool-reuse generation), never by pane alone.
83- revealMu sync.Mutex
84- pendingReveal map[entity.PaneID]bool
85- revealedWebViews map[entity.PaneID]bool
86- revealedWebViewIdentity map[entity.PaneID]webViewIdentity
87- pendingRevealIdentity map[entity.PaneID]webViewIdentity
83+ revealMu sync.Mutex
84+ pendingReveal map[entity.PaneID]webViewIdentity
85+ revealedWebViews map[entity.PaneID]webViewIdentity
8886
8987 appearanceMu sync.Mutex
9088 pendingScriptRefresh map[entity.PaneID]bool
@@ -149,25 +147,23 @@ func NewCoordinator(
149147 log.Debug().Msg("creating content coordinator")
150148
151149 return &Coordinator{
152- logger: log.With().Str("component", "content-coordinator").Logger(),
153- pool: pool,
154- injector: injector,
155- widgetFactory: widgetFactory,
156- faviconAdapter: faviconAdapter,
157- zoomUC: zoomUC,
158- permissionUC: permissionUC,
159- webViews: make(map[entity.PaneID]port.WebView),
160- webViewPaneIDs: make(map[port.WebViewID]entity.PaneID),
161- paneTitles: make(map[entity.PaneID]string),
162- navOrigins: make(map[entity.PaneID]string),
163- pendingReveal: make(map[entity.PaneID]bool),
164- revealedWebViews: make(map[entity.PaneID]bool),
165- revealedWebViewIdentity: make(map[entity.PaneID]webViewIdentity),
166- pendingRevealIdentity: make(map[entity.PaneID]webViewIdentity),
167- pendingScriptRefresh: make(map[entity.PaneID]bool),
168- pendingThemePanes: make(map[entity.PaneID]bool),
169- getActiveWS: getActiveWS,
170- popups: newPopupManager(),
150+ logger: log.With().Str("component", "content-coordinator").Logger(),
151+ pool: pool,
152+ injector: injector,
153+ widgetFactory: widgetFactory,
154+ faviconAdapter: faviconAdapter,
155+ zoomUC: zoomUC,
156+ permissionUC: permissionUC,
157+ webViews: make(map[entity.PaneID]port.WebView),
158+ webViewPaneIDs: make(map[port.WebViewID]entity.PaneID),
159+ paneTitles: make(map[entity.PaneID]string),
160+ navOrigins: make(map[entity.PaneID]string),
161+ pendingReveal: make(map[entity.PaneID]webViewIdentity),
162+ revealedWebViews: make(map[entity.PaneID]webViewIdentity),
163+ pendingScriptRefresh: make(map[entity.PaneID]bool),
164+ pendingThemePanes: make(map[entity.PaneID]bool),
165+ getActiveWS: getActiveWS,
166+ popups: newPopupManager(),
171167 }
172168 }
173169
@@ -334,16 +330,10 @@ func identityForWebView(wv port.WebView) (webViewIdentity, bool) {
334330
335331 func (c *Coordinator) ensureRevealMapsLocked() {
336332 if c.pendingReveal == nil {
337- c.pendingReveal = make(map[entity.PaneID]bool)
333+ c.pendingReveal = make(map[entity.PaneID]webViewIdentity)
338334 }
339335 if c.revealedWebViews == nil {
340- c.revealedWebViews = make(map[entity.PaneID]bool)
341- }
342- if c.revealedWebViewIdentity == nil {
343- c.revealedWebViewIdentity = make(map[entity.PaneID]webViewIdentity)
344- }
345- if c.pendingRevealIdentity == nil {
346- c.pendingRevealIdentity = make(map[entity.PaneID]webViewIdentity)
336+ c.revealedWebViews = make(map[entity.PaneID]webViewIdentity)
347337 }
348338 }
349339
@@ -368,9 +358,7 @@ func (c *Coordinator) setWebViewLocked(paneID entity.PaneID, wv port.WebView) {
368358 }
369359 c.ensureRevealMapsLocked()
370360 delete(c.pendingReveal, paneID)
371- delete(c.pendingRevealIdentity, paneID)
372361 delete(c.revealedWebViews, paneID)
373- delete(c.revealedWebViewIdentity, paneID)
374362 }
375363
376364 // deleteWebViewLocked removes both the mapping and any presentation state,
@@ -389,9 +377,7 @@ func (c *Coordinator) deleteWebViewLocked(paneID entity.PaneID) port.WebView {
389377 }
390378 c.ensureRevealMapsLocked()
391379 delete(c.pendingReveal, paneID)
392- delete(c.pendingRevealIdentity, paneID)
393380 delete(c.revealedWebViews, paneID)
394- delete(c.revealedWebViewIdentity, paneID)
395381 return wv
396382 }
397383
@@ -80,11 +80,9 @@ type Coordinator struct {
80 80
81 // revealMu is always acquired after webViewsMu. Reveal state is keyed by the81 // revealMu is always acquired after webViewsMu. Reveal state is keyed by the
82 // WebView identity (ID plus pool-reuse generation), never by pane alone.82 // WebView identity (ID plus pool-reuse generation), never by pane alone.
83- revealMu sync.Mutex83+ revealMu sync.Mutex
84- pendingReveal map[entity.PaneID]bool84+ pendingReveal map[entity.PaneID]webViewIdentity
85- revealedWebViews map[entity.PaneID]bool85+ revealedWebViews map[entity.PaneID]webViewIdentity
86- revealedWebViewIdentity map[entity.PaneID]webViewIdentity
87- pendingRevealIdentity map[entity.PaneID]webViewIdentity
88 86
89 appearanceMu sync.Mutex87 appearanceMu sync.Mutex
90 pendingScriptRefresh map[entity.PaneID]bool88 pendingScriptRefresh map[entity.PaneID]bool
@@ -149,25 +147,23 @@ func NewCoordinator(
149 log.Debug().Msg("creating content coordinator")147 log.Debug().Msg("creating content coordinator")
150 148
151 return &Coordinator{149 return &Coordinator{
152- logger: log.With().Str("component", "content-coordinator").Logger(),150+ logger: log.With().Str("component", "content-coordinator").Logger(),
153- pool: pool,151+ pool: pool,
154- injector: injector,152+ injector: injector,
155- widgetFactory: widgetFactory,153+ widgetFactory: widgetFactory,
156- faviconAdapter: faviconAdapter,154+ faviconAdapter: faviconAdapter,
157- zoomUC: zoomUC,155+ zoomUC: zoomUC,
158- permissionUC: permissionUC,156+ permissionUC: permissionUC,
159- webViews: make(map[entity.PaneID]port.WebView),157+ webViews: make(map[entity.PaneID]port.WebView),
160- webViewPaneIDs: make(map[port.WebViewID]entity.PaneID),158+ webViewPaneIDs: make(map[port.WebViewID]entity.PaneID),
161- paneTitles: make(map[entity.PaneID]string),159+ paneTitles: make(map[entity.PaneID]string),
162- navOrigins: make(map[entity.PaneID]string),160+ navOrigins: make(map[entity.PaneID]string),
163- pendingReveal: make(map[entity.PaneID]bool),161+ pendingReveal: make(map[entity.PaneID]webViewIdentity),
164- revealedWebViews: make(map[entity.PaneID]bool),162+ revealedWebViews: make(map[entity.PaneID]webViewIdentity),
165- revealedWebViewIdentity: make(map[entity.PaneID]webViewIdentity),163+ pendingScriptRefresh: make(map[entity.PaneID]bool),
166- pendingRevealIdentity: make(map[entity.PaneID]webViewIdentity),164+ pendingThemePanes: make(map[entity.PaneID]bool),
167- pendingScriptRefresh: make(map[entity.PaneID]bool),165+ getActiveWS: getActiveWS,
168- pendingThemePanes: make(map[entity.PaneID]bool),166+ popups: newPopupManager(),
169- getActiveWS: getActiveWS,
170- popups: newPopupManager(),
171 }167 }
172 }168 }
173 169
@@ -334,16 +330,10 @@ func identityForWebView(wv port.WebView) (webViewIdentity, bool) {
334 330
335 func (c *Coordinator) ensureRevealMapsLocked() {331 func (c *Coordinator) ensureRevealMapsLocked() {
336 if c.pendingReveal == nil {332 if c.pendingReveal == nil {
337- c.pendingReveal = make(map[entity.PaneID]bool)333+ c.pendingReveal = make(map[entity.PaneID]webViewIdentity)
338 }334 }
339 if c.revealedWebViews == nil {335 if c.revealedWebViews == nil {
340- c.revealedWebViews = make(map[entity.PaneID]bool)336+ c.revealedWebViews = make(map[entity.PaneID]webViewIdentity)
341- }
342- if c.revealedWebViewIdentity == nil {
343- c.revealedWebViewIdentity = make(map[entity.PaneID]webViewIdentity)
344- }
345- if c.pendingRevealIdentity == nil {
346- c.pendingRevealIdentity = make(map[entity.PaneID]webViewIdentity)
347 }337 }
348 }338 }
349 339
@@ -368,9 +358,7 @@ func (c *Coordinator) setWebViewLocked(paneID entity.PaneID, wv port.WebView) {
368 }358 }
369 c.ensureRevealMapsLocked()359 c.ensureRevealMapsLocked()
370 delete(c.pendingReveal, paneID)360 delete(c.pendingReveal, paneID)
371- delete(c.pendingRevealIdentity, paneID)
372 delete(c.revealedWebViews, paneID)361 delete(c.revealedWebViews, paneID)
373- delete(c.revealedWebViewIdentity, paneID)
374 }362 }
375 363
376 // deleteWebViewLocked removes both the mapping and any presentation state,364 // deleteWebViewLocked removes both the mapping and any presentation state,
@@ -389,9 +377,7 @@ func (c *Coordinator) deleteWebViewLocked(paneID entity.PaneID) port.WebView {
389 }377 }
390 c.ensureRevealMapsLocked()378 c.ensureRevealMapsLocked()
391 delete(c.pendingReveal, paneID)379 delete(c.pendingReveal, paneID)
392- delete(c.pendingRevealIdentity, paneID)
393 delete(c.revealedWebViews, paneID)380 delete(c.revealedWebViews, paneID)
394- delete(c.revealedWebViewIdentity, paneID)
395 return wv381 return wv
396 }382 }
397 383
M internal/ui/coordinator/content/navigation.go +8 -9
@@ -255,8 +255,7 @@ func (c *Coordinator) markPendingReveal(paneID entity.PaneID, identity webViewId
255255 return
256256 }
257257 c.ensureRevealMapsLocked()
258- c.pendingReveal[paneID] = true
259- c.pendingRevealIdentity[paneID] = identity
258+ c.pendingReveal[paneID] = identity
260259 }
261260
262261 func (c *Coordinator) clearPendingReveal(paneID entity.PaneID, identity webViewIdentity) {
@@ -264,11 +263,11 @@ func (c *Coordinator) clearPendingReveal(paneID entity.PaneID, identity webViewI
264263 c.revealMu.Lock()
265264 defer c.revealMu.Unlock()
266265 defer c.webViewsMu.RUnlock()
267- if c.pendingRevealIdentity[paneID] != identity {
266+ pendingIdentity, pending := c.pendingReveal[paneID]
267+ if !pending || pendingIdentity != identity {
268268 return
269269 }
270270 delete(c.pendingReveal, paneID)
271- delete(c.pendingRevealIdentity, paneID)
272271 }
273272
274273 // WebViewRevealed reports whether this pane's current WebView has completed a
@@ -293,7 +292,8 @@ func (c *Coordinator) webViewRevealed(paneID entity.PaneID, expected port.WebVie
293292 return false
294293 }
295294 }
296- return c.revealedWebViews[paneID] && c.revealedWebViewIdentity[paneID] == identity
295+ revealedIdentity, revealed := c.revealedWebViews[paneID]
296+ return revealed && revealedIdentity == identity
297297 }
298298
299299 func (c *Coordinator) markWebViewRevealed(paneID entity.PaneID, identity webViewIdentity) bool {
@@ -306,8 +306,7 @@ func (c *Coordinator) markWebViewRevealed(paneID entity.PaneID, identity webView
306306 return false
307307 }
308308 c.ensureRevealMapsLocked()
309- c.revealedWebViews[paneID] = true
310- c.revealedWebViewIdentity[paneID] = identity
309+ c.revealedWebViews[paneID] = identity
311310 return true
312311 }
313312
@@ -321,7 +320,8 @@ func (c *Coordinator) revealIfPending(
321320
322321 c.webViewsMu.RLock()
323322 c.revealMu.Lock()
324- if c.pendingRevealIdentity[paneID] != identity || !c.pendingReveal[paneID] {
323+ pendingIdentity, pending := c.pendingReveal[paneID]
324+ if !pending || pendingIdentity != identity {
325325 c.revealMu.Unlock()
326326 c.webViewsMu.RUnlock()
327327 c.revealMutationMu.Unlock()
@@ -335,7 +335,6 @@ func (c *Coordinator) revealIfPending(
335335 return
336336 }
337337 delete(c.pendingReveal, paneID)
338- delete(c.pendingRevealIdentity, paneID)
339338 c.revealMu.Unlock()
340339 c.webViewsMu.RUnlock()
341340
@@ -255,8 +255,7 @@ func (c *Coordinator) markPendingReveal(paneID entity.PaneID, identity webViewId
255 return255 return
256 }256 }
257 c.ensureRevealMapsLocked()257 c.ensureRevealMapsLocked()
258- c.pendingReveal[paneID] = true258+ c.pendingReveal[paneID] = identity
259- c.pendingRevealIdentity[paneID] = identity
260 }259 }
261 260
262 func (c *Coordinator) clearPendingReveal(paneID entity.PaneID, identity webViewIdentity) {261 func (c *Coordinator) clearPendingReveal(paneID entity.PaneID, identity webViewIdentity) {
@@ -264,11 +263,11 @@ func (c *Coordinator) clearPendingReveal(paneID entity.PaneID, identity webViewI
264 c.revealMu.Lock()263 c.revealMu.Lock()
265 defer c.revealMu.Unlock()264 defer c.revealMu.Unlock()
266 defer c.webViewsMu.RUnlock()265 defer c.webViewsMu.RUnlock()
267- if c.pendingRevealIdentity[paneID] != identity {266+ pendingIdentity, pending := c.pendingReveal[paneID]
267+ if !pending || pendingIdentity != identity {
268 return268 return
269 }269 }
270 delete(c.pendingReveal, paneID)270 delete(c.pendingReveal, paneID)
271- delete(c.pendingRevealIdentity, paneID)
272 }271 }
273 272
274 // WebViewRevealed reports whether this pane's current WebView has completed a273 // WebViewRevealed reports whether this pane's current WebView has completed a
@@ -293,7 +292,8 @@ func (c *Coordinator) webViewRevealed(paneID entity.PaneID, expected port.WebVie
293 return false292 return false
294 }293 }
295 }294 }
296- return c.revealedWebViews[paneID] && c.revealedWebViewIdentity[paneID] == identity295+ revealedIdentity, revealed := c.revealedWebViews[paneID]
296+ return revealed && revealedIdentity == identity
297 }297 }
298 298
299 func (c *Coordinator) markWebViewRevealed(paneID entity.PaneID, identity webViewIdentity) bool {299 func (c *Coordinator) markWebViewRevealed(paneID entity.PaneID, identity webViewIdentity) bool {
@@ -306,8 +306,7 @@ func (c *Coordinator) markWebViewRevealed(paneID entity.PaneID, identity webView
306 return false306 return false
307 }307 }
308 c.ensureRevealMapsLocked()308 c.ensureRevealMapsLocked()
309- c.revealedWebViews[paneID] = true309+ c.revealedWebViews[paneID] = identity
310- c.revealedWebViewIdentity[paneID] = identity
311 return true310 return true
312 }311 }
313 312
@@ -321,7 +320,8 @@ func (c *Coordinator) revealIfPending(
321 320
322 c.webViewsMu.RLock()321 c.webViewsMu.RLock()
323 c.revealMu.Lock()322 c.revealMu.Lock()
324- if c.pendingRevealIdentity[paneID] != identity || !c.pendingReveal[paneID] {323+ pendingIdentity, pending := c.pendingReveal[paneID]
324+ if !pending || pendingIdentity != identity {
325 c.revealMu.Unlock()325 c.revealMu.Unlock()
326 c.webViewsMu.RUnlock()326 c.webViewsMu.RUnlock()
327 c.revealMutationMu.Unlock()327 c.revealMutationMu.Unlock()
@@ -335,7 +335,6 @@ func (c *Coordinator) revealIfPending(
335 return335 return
336 }336 }
337 delete(c.pendingReveal, paneID)337 delete(c.pendingReveal, paneID)
338- delete(c.pendingRevealIdentity, paneID)
339 c.revealMu.Unlock()338 c.revealMu.Unlock()
340 c.webViewsMu.RUnlock()339 c.webViewsMu.RUnlock()
341 340

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