main 5041353

webview_scroll_cancel.go RawHistoryFolder

1package cef
2
3import (
4	"github.com/bnema/dumber/internal/shared/syncdispatch"
5)
6
7// This file owns inertial-scroll cancellation ordering for CEF WebViews.
8//
9// Threading contract: scrollMotionRetired is safe from any thread (it only
10// bumps the library epoch and never touches GTK). GTK cleanup runs either
11// inline inside runNavigationWithScrollCancel's ordered GTK operation or via
12// runOnGTK-queued epoch-scoped cleanup. Library imports stay inside this
13// infrastructure boundary; only the Cef2gtkAdapter seam is extended.
14
15// scrollCancelBridge is the narrow adapter seam for scroll cancellation.
16// *Cef2gtkAdapter satisfies it in production; tests inject a recording
17// fake. It covers only invalidation and epoch-scoped cleanup.
18type scrollCancelBridge interface {
19	InvalidateScroll() uint64
20	CancelScrollEpoch(epoch uint64) bool
21}
22
23// scrollCancelTarget resolves the live adapter for scroll cancellation.
24func (wv *WebView) scrollCancelTarget() scrollCancelBridge {
25	if wv == nil {
26		return nil
27	}
28	if wv.scrollCancelSeam != nil {
29		return wv.scrollCancelSeam
30	}
31	// Teardown clears viewBridge under wv.mu from the GTK thread while this
32	// runs from any thread (CEF UI, timers, GTK).
33	wv.mu.RLock()
34	bridge := wv.viewBridge
35	wv.mu.RUnlock()
36	if bridge == nil {
37		return nil
38	}
39	return bridge
40}
41
42// scrollMotionRetired immediately retires animated scroll motion and release
43// eligibility, returning the retired epoch for epoch-scoped GTK cleanup.
44// Safe to call from any thread, including during destruction: a destroyed
45// or absent bridge reports zero, so no destroyed check is needed here.
46func (wv *WebView) scrollMotionRetired() uint64 {
47	target := wv.scrollCancelTarget()
48	if wv == nil || target == nil {
49		return 0
50	}
51	return target.InvalidateScroll()
52}
53
54// cancelScrollEpochOnGTK performs GTK-only cleanup for a retired epoch. It
55// never clears a newer session. Call only on the GTK thread.
56func (wv *WebView) cancelScrollEpochOnGTK(epoch uint64) {
57	if wv == nil || epoch == 0 {
58		return
59	}
60	target := wv.scrollCancelTarget()
61	if target == nil {
62		return
63	}
64	target.CancelScrollEpoch(epoch)
65}
66
67// invalidateScrollMotion retires motion immediately and queues epoch-scoped
68// GTK cleanup through the owning dispatcher. Safe to call from any thread;
69// used by lifecycle callbacks (load handlers, visibility) where navigation
70// dispatch is not part of the call.
71func (wv *WebView) invalidateScrollMotion() uint64 {
72	epoch := wv.scrollMotionRetired()
73	if epoch == 0 {
74		return 0
75	}
76	target := wv.scrollCancelTarget()
77	wv.runOnGTK(func() {
78		if target == nil {
79			return
80		}
81		target.CancelScrollEpoch(epoch)
82	})
83	return epoch
84}
85
86// runNavigationWithScrollCancel invalidates scroll motion immediately, then
87// performs GTK cleanup followed by the existing navigation dispatch within
88// one ordered GTK operation. Calls already on the GTK thread execute inline;
89// off-thread calls use the existing bounded synchronous dispatch, which
90// skips abandoned queued work so a timed-out dispatch can never navigate
91// late. Dispatch errors preserve the command's existing error contract;
92// dispatch failure reports the existing GTK-sync error form.
93func (wv *WebView) runNavigationWithScrollCancel(label string, dispatch func() error) error {
94	if wv.destroyed.Load() {
95		return errDestroyed
96	}
97	epoch := wv.scrollMotionRetired()
98	var dispatchErr error
99	result := wv.runOnGTKSyncLabel(label, func() {
100		wv.cancelScrollEpochOnGTK(epoch)
101		dispatchErr = dispatch()
102	})
103	switch result.Status {
104	case syncdispatch.SyncDispatchInline,
105		syncdispatch.SyncDispatchCompleted,
106		syncdispatch.SyncDispatchCompletedAfterTimeout:
107		return dispatchErr
108	default:
109		return errGTKSyncDispatchIncomplete(label, result)
110	}
111}

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