detached d42aca3

stabilize cef integration tests and cursor lint

commit
d42aca36baf630cd3e7ce8faa3b220788cdf78bc
author
brice · · 2026-03-20 09:35 UTC
committer
brice · · 2026-03-28 04:36 UTC
parent
faa77cd
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
5 files · +44 -67
M internal/application/usecase/navigate_test.go +1 -1
@@ -81,11 +81,11 @@ func TestUpdateHistoryTitle_UsesMetadataUpdateWithoutIncrementingVisits(t *testi
8181 uc := NewNavigateUseCase(repo, nil, entity.ZoomDefault)
8282 err = uc.UpdateHistoryTitle(ctx, "https://example.com/article", "New")
8383 require.NoError(t, err)
84+ uc.Close()
8485
8586 after, err := repo.FindByURL(ctx, "https://example.com/article")
8687 require.NoError(t, err)
8788 require.NotNil(t, after)
8889 require.Equal(t, "New", after.Title)
8990 require.Equal(t, before.VisitCount, after.VisitCount)
90- uc.Close()
9191 }
@@ -81,11 +81,11 @@ func TestUpdateHistoryTitle_UsesMetadataUpdateWithoutIncrementingVisits(t *testi
81 uc := NewNavigateUseCase(repo, nil, entity.ZoomDefault)81 uc := NewNavigateUseCase(repo, nil, entity.ZoomDefault)
82 err = uc.UpdateHistoryTitle(ctx, "https://example.com/article", "New")82 err = uc.UpdateHistoryTitle(ctx, "https://example.com/article", "New")
83 require.NoError(t, err)83 require.NoError(t, err)
84+ uc.Close()
84 85
85 after, err := repo.FindByURL(ctx, "https://example.com/article")86 after, err := repo.FindByURL(ctx, "https://example.com/article")
86 require.NoError(t, err)87 require.NoError(t, err)
87 require.NotNil(t, after)88 require.NotNil(t, after)
88 require.Equal(t, "New", after.Title)89 require.Equal(t, "New", after.Title)
89 require.Equal(t, before.VisitCount, after.VisitCount)90 require.Equal(t, before.VisitCount, after.VisitCount)
90- uc.Close()
91 }91 }
M internal/bootstrap/engine.go +0 -1
@@ -27,7 +27,6 @@ type EngineInput struct {
2727 }
2828
2929 // BuildEngine constructs a port.Engine for the engine type specified in cfg.Engine.Type.
30-// Currently only "webkit" is supported; "cef" and other types return an error.
3130 func BuildEngine(input EngineInput) (port.Engine, error) {
3231 cfg := input.Config
3332 engineType := cfg.Engine.Type
@@ -27,7 +27,6 @@ type EngineInput struct {
27 }27 }
28 28
29 // BuildEngine constructs a port.Engine for the engine type specified in cfg.Engine.Type.29 // BuildEngine constructs a port.Engine for the engine type specified in cfg.Engine.Type.
30-// Currently only "webkit" is supported; "cef" and other types return an error.
31 func BuildEngine(input EngineInput) (port.Engine, error) {30 func BuildEngine(input EngineInput) (port.Engine, error) {
32 cfg := input.Config31 cfg := input.Config
33 engineType := cfg.Engine.Type32 engineType := cfg.Engine.Type
M internal/bootstrap/engine_test.go +3 -2
@@ -16,10 +16,11 @@ func TestBuildEngine_UnknownType(t *testing.T) {
1616 require.Contains(t, err.Error(), "unknown engine type")
1717 }
1818
19-func TestBuildEngine_CEF_NotImplemented(t *testing.T) {
19+func TestBuildEngine_CEF_ReturnsErrorWhenRuntimeUnavailable(t *testing.T) {
2020 cfg := config.DefaultConfig()
2121 cfg.Engine.Type = "cef"
22+ cfg.Engine.CEF.CEFDir = t.TempDir()
2223 _, err := BuildEngine(EngineInput{Config: cfg, Ctx: context.Background()})
2324 require.Error(t, err)
24- require.Contains(t, err.Error(), "not yet implemented")
25+ require.Contains(t, err.Error(), "cef: open library")
2526 }
@@ -16,10 +16,11 @@ func TestBuildEngine_UnknownType(t *testing.T) {
16 require.Contains(t, err.Error(), "unknown engine type")16 require.Contains(t, err.Error(), "unknown engine type")
17 }17 }
18 18
19-func TestBuildEngine_CEF_NotImplemented(t *testing.T) {19+func TestBuildEngine_CEF_ReturnsErrorWhenRuntimeUnavailable(t *testing.T) {
20 cfg := config.DefaultConfig()20 cfg := config.DefaultConfig()
21 cfg.Engine.Type = "cef"21 cfg.Engine.Type = "cef"
22+ cfg.Engine.CEF.CEFDir = t.TempDir()
22 _, err := BuildEngine(EngineInput{Config: cfg, Ctx: context.Background()})23 _, err := BuildEngine(EngineInput{Config: cfg, Ctx: context.Background()})
23 require.Error(t, err)24 require.Error(t, err)
24- require.Contains(t, err.Error(), "not yet implemented")25+ require.Contains(t, err.Error(), "cef: open library")
25 }26 }
M internal/infrastructure/cef/app.go +3 -1
@@ -15,7 +15,9 @@ type dumberApp struct {
1515 // adaptive message pump.
1616 func newDumberApp(engine *Engine) purecef.App {
1717 app := &dumberApp{engine: engine}
18- app.bph = purecef.NewBrowserProcessHandler(&dumberBPH{engine: engine})
18+ // Keep the raw Go implementation here. NewApp will wrap it exactly once
19+ // when GetBrowserProcessHandler is queried by CEF.
20+ app.bph = &dumberBPH{engine: engine}
1921 return purecef.NewApp(app)
2022 }
2123
@@ -15,7 +15,9 @@ type dumberApp struct {
15 // adaptive message pump.15 // adaptive message pump.
16 func newDumberApp(engine *Engine) purecef.App {16 func newDumberApp(engine *Engine) purecef.App {
17 app := &dumberApp{engine: engine}17 app := &dumberApp{engine: engine}
18- app.bph = purecef.NewBrowserProcessHandler(&dumberBPH{engine: engine})18+ // Keep the raw Go implementation here. NewApp will wrap it exactly once
19+ // when GetBrowserProcessHandler is queried by CEF.
20+ app.bph = &dumberBPH{engine: engine}
19 return purecef.NewApp(app)21 return purecef.NewApp(app)
20 }22 }
21 23
M internal/infrastructure/cef/webview_handlers.go +37 -62
@@ -256,6 +256,7 @@ func (h *handlerSet) OnLoadEnd(_ purecef.Browser, frame purecef.Frame, httpStatu
256256 }
257257 // Successful load — reset the consecutive crash counter.
258258 h.wv.crashCount = 0
259+
259260 h.wv.mu.RLock()
260261 cb := h.wv.callbacks
261262 h.wv.mu.RUnlock()
@@ -415,68 +416,42 @@ func (h *handlerSet) OnRenderProcessTerminated(_ purecef.Browser, status purecef
415416
416417 func (h *handlerSet) OnDocumentAvailableInMainFrame(_ purecef.Browser) {}
417418
418-// cefCursorToGDKName maps CEF cursor types to GDK/CSS cursor names.
419-//
420-//nolint:mnd,cyclop // cursor type lookup table
419+// cefCursorNames maps CEF cursor types to GDK/CSS cursor names.
420+var cefCursorNames = map[purecef.CursorType]string{
421+ purecef.CursorTypeCtPointer: "default",
422+ purecef.CursorTypeCtCross: "crosshair",
423+ purecef.CursorTypeCtHand: "pointer",
424+ purecef.CursorTypeCtIbeam: "text",
425+ purecef.CursorTypeCtWait: "wait",
426+ purecef.CursorTypeCtHelp: "help",
427+ purecef.CursorTypeCtEastresize: "e-resize",
428+ purecef.CursorTypeCtNorthresize: "n-resize",
429+ purecef.CursorTypeCtNortheastresize: "ne-resize",
430+ purecef.CursorTypeCtNorthwestresize: "nw-resize",
431+ purecef.CursorTypeCtSouthresize: "s-resize",
432+ purecef.CursorTypeCtSoutheastresize: "se-resize",
433+ purecef.CursorTypeCtSouthwestresize: "sw-resize",
434+ purecef.CursorTypeCtWestresize: "w-resize",
435+ purecef.CursorTypeCtNorthsouthresize: "ns-resize",
436+ purecef.CursorTypeCtEastwestresize: "ew-resize",
437+ purecef.CursorTypeCtNortheastsouthwestresize: "nesw-resize",
438+ purecef.CursorTypeCtNorthwestsoutheastresize: "nwse-resize",
439+ purecef.CursorTypeCtColumnresize: "col-resize",
440+ purecef.CursorTypeCtRowresize: "row-resize",
441+ purecef.CursorTypeCtMove: "move",
442+ purecef.CursorTypeCtProgress: "progress",
443+ purecef.CursorTypeCtNodrop: "no-drop",
444+ purecef.CursorTypeCtNotallowed: "not-allowed",
445+ purecef.CursorTypeCtGrab: "grab",
446+ purecef.CursorTypeCtGrabbing: "grabbing",
447+ purecef.CursorTypeCtZoomin: "zoom-in",
448+ purecef.CursorTypeCtZoomout: "zoom-out",
449+}
450+
451+// cefCursorToGDKName maps a CEF cursor type to a GDK/CSS cursor name.
421452 func cefCursorToGDKName(ct purecef.CursorType) string {
422- switch ct {
423- case purecef.CursorTypeCtPointer:
424- return "default"
425- case purecef.CursorTypeCtCross:
426- return "crosshair"
427- case purecef.CursorTypeCtHand:
428- return "pointer"
429- case purecef.CursorTypeCtIbeam:
430- return "text"
431- case purecef.CursorTypeCtWait:
432- return "wait"
433- case purecef.CursorTypeCtHelp:
434- return "help"
435- case purecef.CursorTypeCtEastresize:
436- return "e-resize"
437- case purecef.CursorTypeCtNorthresize:
438- return "n-resize"
439- case purecef.CursorTypeCtNortheastresize:
440- return "ne-resize"
441- case purecef.CursorTypeCtNorthwestresize:
442- return "nw-resize"
443- case purecef.CursorTypeCtSouthresize:
444- return "s-resize"
445- case purecef.CursorTypeCtSoutheastresize:
446- return "se-resize"
447- case purecef.CursorTypeCtSouthwestresize:
448- return "sw-resize"
449- case purecef.CursorTypeCtWestresize:
450- return "w-resize"
451- case purecef.CursorTypeCtNorthsouthresize:
452- return "ns-resize"
453- case purecef.CursorTypeCtEastwestresize:
454- return "ew-resize"
455- case purecef.CursorTypeCtNortheastsouthwestresize:
456- return "nesw-resize"
457- case purecef.CursorTypeCtNorthwestsoutheastresize:
458- return "nwse-resize"
459- case purecef.CursorTypeCtColumnresize:
460- return "col-resize"
461- case purecef.CursorTypeCtRowresize:
462- return "row-resize"
463- case purecef.CursorTypeCtMove:
464- return "move"
465- case purecef.CursorTypeCtProgress:
466- return "progress"
467- case purecef.CursorTypeCtNodrop:
468- return "no-drop"
469- case purecef.CursorTypeCtNotallowed:
470- return "not-allowed"
471- case purecef.CursorTypeCtGrab:
472- return "grab"
473- case purecef.CursorTypeCtGrabbing:
474- return "grabbing"
475- case purecef.CursorTypeCtZoomin:
476- return "zoom-in"
477- case purecef.CursorTypeCtZoomout:
478- return "zoom-out"
479- default:
480- return "default"
453+ if name, ok := cefCursorNames[ct]; ok {
454+ return name
481455 }
456+ return "default"
482457 }
@@ -256,6 +256,7 @@ func (h *handlerSet) OnLoadEnd(_ purecef.Browser, frame purecef.Frame, httpStatu
256 }256 }
257 // Successful load — reset the consecutive crash counter.257 // Successful load — reset the consecutive crash counter.
258 h.wv.crashCount = 0258 h.wv.crashCount = 0
259+
259 h.wv.mu.RLock()260 h.wv.mu.RLock()
260 cb := h.wv.callbacks261 cb := h.wv.callbacks
261 h.wv.mu.RUnlock()262 h.wv.mu.RUnlock()
@@ -415,68 +416,42 @@ func (h *handlerSet) OnRenderProcessTerminated(_ purecef.Browser, status purecef
415 416
416 func (h *handlerSet) OnDocumentAvailableInMainFrame(_ purecef.Browser) {}417 func (h *handlerSet) OnDocumentAvailableInMainFrame(_ purecef.Browser) {}
417 418
418-// cefCursorToGDKName maps CEF cursor types to GDK/CSS cursor names.419+// cefCursorNames maps CEF cursor types to GDK/CSS cursor names.
419-//420+var cefCursorNames = map[purecef.CursorType]string{
420-//nolint:mnd,cyclop // cursor type lookup table421+ purecef.CursorTypeCtPointer: "default",
422+ purecef.CursorTypeCtCross: "crosshair",
423+ purecef.CursorTypeCtHand: "pointer",
424+ purecef.CursorTypeCtIbeam: "text",
425+ purecef.CursorTypeCtWait: "wait",
426+ purecef.CursorTypeCtHelp: "help",
427+ purecef.CursorTypeCtEastresize: "e-resize",
428+ purecef.CursorTypeCtNorthresize: "n-resize",
429+ purecef.CursorTypeCtNortheastresize: "ne-resize",
430+ purecef.CursorTypeCtNorthwestresize: "nw-resize",
431+ purecef.CursorTypeCtSouthresize: "s-resize",
432+ purecef.CursorTypeCtSoutheastresize: "se-resize",
433+ purecef.CursorTypeCtSouthwestresize: "sw-resize",
434+ purecef.CursorTypeCtWestresize: "w-resize",
435+ purecef.CursorTypeCtNorthsouthresize: "ns-resize",
436+ purecef.CursorTypeCtEastwestresize: "ew-resize",
437+ purecef.CursorTypeCtNortheastsouthwestresize: "nesw-resize",
438+ purecef.CursorTypeCtNorthwestsoutheastresize: "nwse-resize",
439+ purecef.CursorTypeCtColumnresize: "col-resize",
440+ purecef.CursorTypeCtRowresize: "row-resize",
441+ purecef.CursorTypeCtMove: "move",
442+ purecef.CursorTypeCtProgress: "progress",
443+ purecef.CursorTypeCtNodrop: "no-drop",
444+ purecef.CursorTypeCtNotallowed: "not-allowed",
445+ purecef.CursorTypeCtGrab: "grab",
446+ purecef.CursorTypeCtGrabbing: "grabbing",
447+ purecef.CursorTypeCtZoomin: "zoom-in",
448+ purecef.CursorTypeCtZoomout: "zoom-out",
449+}
450+
451+// cefCursorToGDKName maps a CEF cursor type to a GDK/CSS cursor name.
421 func cefCursorToGDKName(ct purecef.CursorType) string {452 func cefCursorToGDKName(ct purecef.CursorType) string {
422- switch ct {453+ if name, ok := cefCursorNames[ct]; ok {
423- case purecef.CursorTypeCtPointer:454+ return name
424- return "default"
425- case purecef.CursorTypeCtCross:
426- return "crosshair"
427- case purecef.CursorTypeCtHand:
428- return "pointer"
429- case purecef.CursorTypeCtIbeam:
430- return "text"
431- case purecef.CursorTypeCtWait:
432- return "wait"
433- case purecef.CursorTypeCtHelp:
434- return "help"
435- case purecef.CursorTypeCtEastresize:
436- return "e-resize"
437- case purecef.CursorTypeCtNorthresize:
438- return "n-resize"
439- case purecef.CursorTypeCtNortheastresize:
440- return "ne-resize"
441- case purecef.CursorTypeCtNorthwestresize:
442- return "nw-resize"
443- case purecef.CursorTypeCtSouthresize:
444- return "s-resize"
445- case purecef.CursorTypeCtSoutheastresize:
446- return "se-resize"
447- case purecef.CursorTypeCtSouthwestresize:
448- return "sw-resize"
449- case purecef.CursorTypeCtWestresize:
450- return "w-resize"
451- case purecef.CursorTypeCtNorthsouthresize:
452- return "ns-resize"
453- case purecef.CursorTypeCtEastwestresize:
454- return "ew-resize"
455- case purecef.CursorTypeCtNortheastsouthwestresize:
456- return "nesw-resize"
457- case purecef.CursorTypeCtNorthwestsoutheastresize:
458- return "nwse-resize"
459- case purecef.CursorTypeCtColumnresize:
460- return "col-resize"
461- case purecef.CursorTypeCtRowresize:
462- return "row-resize"
463- case purecef.CursorTypeCtMove:
464- return "move"
465- case purecef.CursorTypeCtProgress:
466- return "progress"
467- case purecef.CursorTypeCtNodrop:
468- return "no-drop"
469- case purecef.CursorTypeCtNotallowed:
470- return "not-allowed"
471- case purecef.CursorTypeCtGrab:
472- return "grab"
473- case purecef.CursorTypeCtGrabbing:
474- return "grabbing"
475- case purecef.CursorTypeCtZoomin:
476- return "zoom-in"
477- case purecef.CursorTypeCtZoomout:
478- return "zoom-out"
479- default:
480- return "default"
481 }455 }
456+ return "default"
482 }457 }

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