detached 2ccf5f3

refactor(cef): simplify factory using NewClient returning Client

NewClient now returns cef.Client directly (with internal rawPointerHolder),
eliminating the clientAdapter workaround. BrowserHostCreateBrowser receives
the client cleanly without any raw pointer juggling.
commit
2ccf5f3b898e8f1814d0a19fd6d488c3ffdad182
author
brice · · 2026-03-19 19:23 UTC
committer
brice · · 2026-03-28 04:36 UTC
parent
144c362
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +10 -68
M internal/infrastructure/cef/factory.go +10 -68
@@ -54,11 +54,9 @@ func (f *WebViewFactory) Create(_ context.Context) (port.WebView, error) {
5454 input.attachTo(pipeline.glArea)
5555 wv.input = input
5656
57- // Build a CEF client backed by our handlerSet. NewClient allocates a raw
58- // CEF client struct with ref-counting and callback vtable wired up. We
59- // keep the pointer alive on the WebView so it is not garbage collected
60- // before the browser is destroyed.
61- clientPtr := purecef.NewClient(wv.handlers)
57+ // Build a CEF client backed by our handlerSet. NewClient returns a Client
58+ // that wraps the raw CEF struct with refcounting and callback vtable.
59+ client := purecef.NewClient(wv.handlers)
6260
6361 // Configure WindowInfo for off-screen rendering (OSR).
6462 var windowInfo purecef.WindowInfo
@@ -70,39 +68,18 @@ func (f *WebViewFactory) Create(_ context.Context) (port.WebView, error) {
7068 settings.Size = unsafe.Sizeof(settings)
7169 settings.WindowlessFrameRate = 60
7270
73- // Create the browser asynchronously. BrowserHostCreateBrowser posts to the
74- // CEF UI thread; the browser will be nil until OnAfterCreated fires.
75- //
76- // We call the raw creation function directly with the client pointer from
77- // NewClient, because the high-level wrapper's extractRawPointer requires a
78- // package-internal interface that external types cannot satisfy.
79- //
80- // NOTE: BrowserHostCreateBrowser is currently a stub in purego-cef (returns
81- // 0). That is expected — the skeleton is correct and will work once the
82- // stub is implemented.
83- createBrowser(clientPtr, &windowInfo, &settings)
84-
85- return wv, nil
86-}
87-
88-// createBrowser calls purecef.BrowserHostCreateBrowser with the raw client
89-// pointer. This is separated for testability and to document the impedance
90-// mismatch between NewClient (returns unsafe.Pointer) and
91-// BrowserHostCreateBrowser (expects Client interface with unexported
92-// rawPointer method).
93-func createBrowser(clientPtr unsafe.Pointer, windowInfo *purecef.WindowInfo, settings *purecef.BrowserSettings) {
94- // Wrap the raw pointer in a thin Client adapter so BrowserHostCreateBrowser
95- // can extract it. handlerSet already implements Client; the real vtable is
96- // in the raw struct that clientPtr points to.
97- adapter := &clientAdapter{ptr: clientPtr}
71+ // Create the browser asynchronously. The browser will be nil until
72+ // OnAfterCreated fires in the LifeSpanHandler.
9873 purecef.BrowserHostCreateBrowser(
99- windowInfo,
100- adapter,
74+ &windowInfo,
75+ client,
10176 "about:blank",
102- settings,
77+ &settings,
10378 nil, // extraInfo
10479 nil, // requestContext
10580 )
81+
82+ return wv, nil
10683 }
10784
10885 // CreateRelated creates a WebView that shares session/cookies with the parent.
@@ -111,38 +88,3 @@ func createBrowser(clientPtr unsafe.Pointer, windowInfo *purecef.WindowInfo, set
11188 func (f *WebViewFactory) CreateRelated(ctx context.Context, _ port.WebViewID) (port.WebView, error) {
11289 return f.Create(ctx)
11390 }
114-
115-// clientAdapter wraps an unsafe.Pointer from NewClient into something that
116-// satisfies the purecef.Client interface. The actual CEF callbacks are already
117-// wired inside the raw struct; these Go methods are never invoked by CEF.
118-//
119-// NOTE: purecef.extractRawPointer checks for an unexported rawPointer()
120-// method, which external packages cannot satisfy. Until purego-cef exports a
121-// proper WrapClient helper, the pointer extraction will return nil. This is
122-// acceptable because BrowserHostCreateBrowser is currently a stub.
123-type clientAdapter struct {
124- ptr unsafe.Pointer
125-}
126-
127-func (c *clientAdapter) GetAudioHandler() purecef.AudioHandler { return nil }
128-func (c *clientAdapter) GetCommandHandler() purecef.CommandHandler { return nil }
129-func (c *clientAdapter) GetContextMenuHandler() purecef.ContextMenuHandler { return nil }
130-func (c *clientAdapter) GetDialogHandler() purecef.DialogHandler { return nil }
131-func (c *clientAdapter) GetDisplayHandler() purecef.DisplayHandler { return nil }
132-func (c *clientAdapter) GetDownloadHandler() purecef.DownloadHandler { return nil }
133-func (c *clientAdapter) GetDragHandler() purecef.DragHandler { return nil }
134-func (c *clientAdapter) GetFindHandler() purecef.FindHandler { return nil }
135-func (c *clientAdapter) GetFocusHandler() purecef.FocusHandler { return nil }
136-func (c *clientAdapter) GetFrameHandler() purecef.FrameHandler { return nil }
137-func (c *clientAdapter) GetPermissionHandler() purecef.PermissionHandler { return nil }
138-func (c *clientAdapter) GetJsdialogHandler() purecef.JsdialogHandler { return nil }
139-func (c *clientAdapter) GetKeyboardHandler() purecef.KeyboardHandler { return nil }
140-func (c *clientAdapter) GetLifeSpanHandler() purecef.LifeSpanHandler { return nil }
141-func (c *clientAdapter) GetLoadHandler() purecef.LoadHandler { return nil }
142-func (c *clientAdapter) GetPrintHandler() purecef.PrintHandler { return nil }
143-func (c *clientAdapter) GetRenderHandler() purecef.RenderHandler { return nil }
144-func (c *clientAdapter) GetRequestHandler() purecef.RequestHandler { return nil }
145-
146-func (c *clientAdapter) OnProcessMessageReceived(_ purecef.Browser, _ purecef.Frame, _ purecef.ProcessID, _ purecef.ProcessMessage) int32 {
147- return 0
148-}
@@ -54,11 +54,9 @@ func (f *WebViewFactory) Create(_ context.Context) (port.WebView, error) {
54 input.attachTo(pipeline.glArea)54 input.attachTo(pipeline.glArea)
55 wv.input = input55 wv.input = input
56 56
57- // Build a CEF client backed by our handlerSet. NewClient allocates a raw57+ // Build a CEF client backed by our handlerSet. NewClient returns a Client
58- // CEF client struct with ref-counting and callback vtable wired up. We58+ // that wraps the raw CEF struct with refcounting and callback vtable.
59- // keep the pointer alive on the WebView so it is not garbage collected59+ client := purecef.NewClient(wv.handlers)
60- // before the browser is destroyed.
61- clientPtr := purecef.NewClient(wv.handlers)
62 60
63 // Configure WindowInfo for off-screen rendering (OSR).61 // Configure WindowInfo for off-screen rendering (OSR).
64 var windowInfo purecef.WindowInfo62 var windowInfo purecef.WindowInfo
@@ -70,39 +68,18 @@ func (f *WebViewFactory) Create(_ context.Context) (port.WebView, error) {
70 settings.Size = unsafe.Sizeof(settings)68 settings.Size = unsafe.Sizeof(settings)
71 settings.WindowlessFrameRate = 6069 settings.WindowlessFrameRate = 60
72 70
73- // Create the browser asynchronously. BrowserHostCreateBrowser posts to the71+ // Create the browser asynchronously. The browser will be nil until
74- // CEF UI thread; the browser will be nil until OnAfterCreated fires.72+ // OnAfterCreated fires in the LifeSpanHandler.
75- //
76- // We call the raw creation function directly with the client pointer from
77- // NewClient, because the high-level wrapper's extractRawPointer requires a
78- // package-internal interface that external types cannot satisfy.
79- //
80- // NOTE: BrowserHostCreateBrowser is currently a stub in purego-cef (returns
81- // 0). That is expected — the skeleton is correct and will work once the
82- // stub is implemented.
83- createBrowser(clientPtr, &windowInfo, &settings)
84-
85- return wv, nil
86-}
87-
88-// createBrowser calls purecef.BrowserHostCreateBrowser with the raw client
89-// pointer. This is separated for testability and to document the impedance
90-// mismatch between NewClient (returns unsafe.Pointer) and
91-// BrowserHostCreateBrowser (expects Client interface with unexported
92-// rawPointer method).
93-func createBrowser(clientPtr unsafe.Pointer, windowInfo *purecef.WindowInfo, settings *purecef.BrowserSettings) {
94- // Wrap the raw pointer in a thin Client adapter so BrowserHostCreateBrowser
95- // can extract it. handlerSet already implements Client; the real vtable is
96- // in the raw struct that clientPtr points to.
97- adapter := &clientAdapter{ptr: clientPtr}
98 purecef.BrowserHostCreateBrowser(73 purecef.BrowserHostCreateBrowser(
99- windowInfo,74+ &windowInfo,
100- adapter,75+ client,
101 "about:blank",76 "about:blank",
102- settings,77+ &settings,
103 nil, // extraInfo78 nil, // extraInfo
104 nil, // requestContext79 nil, // requestContext
105 )80 )
81+
82+ return wv, nil
106 }83 }
107 84
108 // CreateRelated creates a WebView that shares session/cookies with the parent.85 // CreateRelated creates a WebView that shares session/cookies with the parent.
@@ -111,38 +88,3 @@ func createBrowser(clientPtr unsafe.Pointer, windowInfo *purecef.WindowInfo, set
111 func (f *WebViewFactory) CreateRelated(ctx context.Context, _ port.WebViewID) (port.WebView, error) {88 func (f *WebViewFactory) CreateRelated(ctx context.Context, _ port.WebViewID) (port.WebView, error) {
112 return f.Create(ctx)89 return f.Create(ctx)
113 }90 }
114-
115-// clientAdapter wraps an unsafe.Pointer from NewClient into something that
116-// satisfies the purecef.Client interface. The actual CEF callbacks are already
117-// wired inside the raw struct; these Go methods are never invoked by CEF.
118-//
119-// NOTE: purecef.extractRawPointer checks for an unexported rawPointer()
120-// method, which external packages cannot satisfy. Until purego-cef exports a
121-// proper WrapClient helper, the pointer extraction will return nil. This is
122-// acceptable because BrowserHostCreateBrowser is currently a stub.
123-type clientAdapter struct {
124- ptr unsafe.Pointer
125-}
126-
127-func (c *clientAdapter) GetAudioHandler() purecef.AudioHandler { return nil }
128-func (c *clientAdapter) GetCommandHandler() purecef.CommandHandler { return nil }
129-func (c *clientAdapter) GetContextMenuHandler() purecef.ContextMenuHandler { return nil }
130-func (c *clientAdapter) GetDialogHandler() purecef.DialogHandler { return nil }
131-func (c *clientAdapter) GetDisplayHandler() purecef.DisplayHandler { return nil }
132-func (c *clientAdapter) GetDownloadHandler() purecef.DownloadHandler { return nil }
133-func (c *clientAdapter) GetDragHandler() purecef.DragHandler { return nil }
134-func (c *clientAdapter) GetFindHandler() purecef.FindHandler { return nil }
135-func (c *clientAdapter) GetFocusHandler() purecef.FocusHandler { return nil }
136-func (c *clientAdapter) GetFrameHandler() purecef.FrameHandler { return nil }
137-func (c *clientAdapter) GetPermissionHandler() purecef.PermissionHandler { return nil }
138-func (c *clientAdapter) GetJsdialogHandler() purecef.JsdialogHandler { return nil }
139-func (c *clientAdapter) GetKeyboardHandler() purecef.KeyboardHandler { return nil }
140-func (c *clientAdapter) GetLifeSpanHandler() purecef.LifeSpanHandler { return nil }
141-func (c *clientAdapter) GetLoadHandler() purecef.LoadHandler { return nil }
142-func (c *clientAdapter) GetPrintHandler() purecef.PrintHandler { return nil }
143-func (c *clientAdapter) GetRenderHandler() purecef.RenderHandler { return nil }
144-func (c *clientAdapter) GetRequestHandler() purecef.RequestHandler { return nil }
145-
146-func (c *clientAdapter) OnProcessMessageReceived(_ purecef.Browser, _ purecef.Frame, _ purecef.ProcessID, _ purecef.ProcessMessage) int32 {
147- return 0
148-}

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