detached 27707ff

feat(webkit): implement popup window signal handling and related webview creation

Implements popup window support in WebKit adapter:
- Added PopupRequest type for popup metadata (target URI, frame name, user gesture, parent ID)
- Implemented NewWebViewWithRelated constructor for creating popups that share session/cookies with parent
- Connected 'create' signal to OnCreate callback for popup creation/blocking
- Connected 'ready-to-show' signal to OnReadyToShow callback for popup display lifecycle
- Added gtk import for Widget type in signal handling
commit
27707ff0d67e838757b048353c584a3796a5ea54
author
bnema · · 2025-12-09 22:58 UTC
parent
cc823ac
signature
unsigned
1 file · +91 -2
M internal/infrastructure/webkit/webview.go +91 -2
@@ -6,6 +6,7 @@ import (
66 "sync/atomic"
77
88 "github.com/bnema/puregotk-webkit/webkit"
9+ "github.com/jwijenbergh/puregotk/v4/gtk"
910 "github.com/rs/zerolog"
1011 )
1112
@@ -22,6 +23,14 @@ const (
2223 LoadFinished LoadEvent = LoadEvent(webkit.LoadFinishedValue)
2324 )
2425
26+// PopupRequest contains information about a popup window request from the create signal.
27+type PopupRequest struct {
28+ TargetURI string
29+ FrameName string // e.g., "_blank", custom name, or empty
30+ IsUserGesture bool
31+ ParentID WebViewID
32+}
33+
2534 // webViewRegistry tracks all active WebViews.
2635 type webViewRegistry struct {
2736 views map[WebViewID]*WebView
@@ -82,8 +91,8 @@ type WebView struct {
8291 OnURIChanged func(string)
8392 OnProgressChanged func(float64)
8493 OnClose func()
85- // TODO(puregotk-webkit): OnCreate for popup support
86- // See: /home/brice/sync/alpha/obsidian/projects/puregotk-webkit/issues/related-webview.md
94+ OnCreate func(PopupRequest) *WebView // Return new WebView or nil to block popup
95+ OnReadyToShow func() // Called when popup is ready to display
8796
8897 logger zerolog.Logger
8998 mu sync.RWMutex
@@ -122,6 +131,43 @@ func NewWebView(wkCtx *WebKitContext, settings *SettingsManager, logger zerolog.
122131 return wv, nil
123132 }
124133
134+// NewWebViewWithRelated creates a WebView that shares session/cookies with parent.
135+// This is required for popup windows to maintain authentication state.
136+func NewWebViewWithRelated(parent *WebView, settings *SettingsManager, logger zerolog.Logger) (*WebView, error) {
137+ if parent == nil {
138+ return nil, fmt.Errorf("parent webview is nil")
139+ }
140+ if parent.IsDestroyed() {
141+ return nil, fmt.Errorf("parent webview %d is destroyed", parent.id)
142+ }
143+
144+ inner := webkit.NewWebViewWithRelatedView(parent.inner)
145+ if inner == nil {
146+ return nil, fmt.Errorf("failed to create related webkit webview")
147+ }
148+
149+ wv := &WebView{
150+ inner: inner,
151+ logger: logger.With().Str("component", "webview-popup").Logger(),
152+ signalIDs: make([]uint32, 0, 6),
153+ }
154+
155+ wv.id = globalRegistry.register(wv)
156+
157+ if settings != nil {
158+ settings.ApplyToWebView(inner)
159+ }
160+
161+ wv.connectSignals()
162+
163+ wv.logger.Debug().
164+ Uint64("id", uint64(wv.id)).
165+ Uint64("parent_id", uint64(parent.id)).
166+ Msg("related webview created for popup")
167+
168+ return wv, nil
169+}
170+
125171 // connectSignals sets up signal handlers for the WebView.
126172 func (wv *WebView) connectSignals() {
127173 // load-changed signal
@@ -157,6 +203,49 @@ func (wv *WebView) connectSignals() {
157203 sigID = wv.inner.ConnectClose(&closeCb)
158204 wv.signalIDs = append(wv.signalIDs, sigID)
159205
206+ // create signal for popup window handling
207+ createCb := func(inner webkit.WebView, navActionPtr uintptr) gtk.Widget {
208+ if wv.OnCreate == nil {
209+ return gtk.Widget{} // Block popup
210+ }
211+
212+ navAction := webkit.NavigationActionFromPointer(navActionPtr)
213+ if navAction == nil {
214+ return gtk.Widget{} // Block popup
215+ }
216+ defer navAction.Free()
217+
218+ var targetURI string
219+ if req := navAction.GetRequest(); req != nil {
220+ targetURI = req.GetUri()
221+ }
222+
223+ popupReq := PopupRequest{
224+ TargetURI: targetURI,
225+ FrameName: navAction.GetFrameName(),
226+ IsUserGesture: navAction.IsUserGesture(),
227+ ParentID: wv.id,
228+ }
229+
230+ newWV := wv.OnCreate(popupReq)
231+ if newWV == nil {
232+ return gtk.Widget{} // Block popup
233+ }
234+
235+ return newWV.inner.Widget
236+ }
237+ sigID = wv.inner.ConnectCreate(&createCb)
238+ wv.signalIDs = append(wv.signalIDs, sigID)
239+
240+ // ready-to-show signal for popup display
241+ readyToShowCb := func(inner webkit.WebView) {
242+ if wv.OnReadyToShow != nil {
243+ wv.OnReadyToShow()
244+ }
245+ }
246+ sigID = wv.inner.ConnectReadyToShow(&readyToShowCb)
247+ wv.signalIDs = append(wv.signalIDs, sigID)
248+
160249 // Note: notify::title, notify::uri, notify::estimated-load-progress
161250 // would need GObject property change notifications which may require
162251 // different API patterns in puregotk. For now, we update these in
@@ -6,6 +6,7 @@ import (
6 "sync/atomic"6 "sync/atomic"
7 7
8 "github.com/bnema/puregotk-webkit/webkit"8 "github.com/bnema/puregotk-webkit/webkit"
9+ "github.com/jwijenbergh/puregotk/v4/gtk"
9 "github.com/rs/zerolog"10 "github.com/rs/zerolog"
10 )11 )
11 12
@@ -22,6 +23,14 @@ const (
22 LoadFinished LoadEvent = LoadEvent(webkit.LoadFinishedValue)23 LoadFinished LoadEvent = LoadEvent(webkit.LoadFinishedValue)
23 )24 )
24 25
26+// PopupRequest contains information about a popup window request from the create signal.
27+type PopupRequest struct {
28+ TargetURI string
29+ FrameName string // e.g., "_blank", custom name, or empty
30+ IsUserGesture bool
31+ ParentID WebViewID
32+}
33+
25 // webViewRegistry tracks all active WebViews.34 // webViewRegistry tracks all active WebViews.
26 type webViewRegistry struct {35 type webViewRegistry struct {
27 views map[WebViewID]*WebView36 views map[WebViewID]*WebView
@@ -82,8 +91,8 @@ type WebView struct {
82 OnURIChanged func(string)91 OnURIChanged func(string)
83 OnProgressChanged func(float64)92 OnProgressChanged func(float64)
84 OnClose func()93 OnClose func()
85- // TODO(puregotk-webkit): OnCreate for popup support94+ OnCreate func(PopupRequest) *WebView // Return new WebView or nil to block popup
86- // See: /home/brice/sync/alpha/obsidian/projects/puregotk-webkit/issues/related-webview.md95+ OnReadyToShow func() // Called when popup is ready to display
87 96
88 logger zerolog.Logger97 logger zerolog.Logger
89 mu sync.RWMutex98 mu sync.RWMutex
@@ -122,6 +131,43 @@ func NewWebView(wkCtx *WebKitContext, settings *SettingsManager, logger zerolog.
122 return wv, nil131 return wv, nil
123 }132 }
124 133
134+// NewWebViewWithRelated creates a WebView that shares session/cookies with parent.
135+// This is required for popup windows to maintain authentication state.
136+func NewWebViewWithRelated(parent *WebView, settings *SettingsManager, logger zerolog.Logger) (*WebView, error) {
137+ if parent == nil {
138+ return nil, fmt.Errorf("parent webview is nil")
139+ }
140+ if parent.IsDestroyed() {
141+ return nil, fmt.Errorf("parent webview %d is destroyed", parent.id)
142+ }
143+
144+ inner := webkit.NewWebViewWithRelatedView(parent.inner)
145+ if inner == nil {
146+ return nil, fmt.Errorf("failed to create related webkit webview")
147+ }
148+
149+ wv := &WebView{
150+ inner: inner,
151+ logger: logger.With().Str("component", "webview-popup").Logger(),
152+ signalIDs: make([]uint32, 0, 6),
153+ }
154+
155+ wv.id = globalRegistry.register(wv)
156+
157+ if settings != nil {
158+ settings.ApplyToWebView(inner)
159+ }
160+
161+ wv.connectSignals()
162+
163+ wv.logger.Debug().
164+ Uint64("id", uint64(wv.id)).
165+ Uint64("parent_id", uint64(parent.id)).
166+ Msg("related webview created for popup")
167+
168+ return wv, nil
169+}
170+
125 // connectSignals sets up signal handlers for the WebView.171 // connectSignals sets up signal handlers for the WebView.
126 func (wv *WebView) connectSignals() {172 func (wv *WebView) connectSignals() {
127 // load-changed signal173 // load-changed signal
@@ -157,6 +203,49 @@ func (wv *WebView) connectSignals() {
157 sigID = wv.inner.ConnectClose(&closeCb)203 sigID = wv.inner.ConnectClose(&closeCb)
158 wv.signalIDs = append(wv.signalIDs, sigID)204 wv.signalIDs = append(wv.signalIDs, sigID)
159 205
206+ // create signal for popup window handling
207+ createCb := func(inner webkit.WebView, navActionPtr uintptr) gtk.Widget {
208+ if wv.OnCreate == nil {
209+ return gtk.Widget{} // Block popup
210+ }
211+
212+ navAction := webkit.NavigationActionFromPointer(navActionPtr)
213+ if navAction == nil {
214+ return gtk.Widget{} // Block popup
215+ }
216+ defer navAction.Free()
217+
218+ var targetURI string
219+ if req := navAction.GetRequest(); req != nil {
220+ targetURI = req.GetUri()
221+ }
222+
223+ popupReq := PopupRequest{
224+ TargetURI: targetURI,
225+ FrameName: navAction.GetFrameName(),
226+ IsUserGesture: navAction.IsUserGesture(),
227+ ParentID: wv.id,
228+ }
229+
230+ newWV := wv.OnCreate(popupReq)
231+ if newWV == nil {
232+ return gtk.Widget{} // Block popup
233+ }
234+
235+ return newWV.inner.Widget
236+ }
237+ sigID = wv.inner.ConnectCreate(&createCb)
238+ wv.signalIDs = append(wv.signalIDs, sigID)
239+
240+ // ready-to-show signal for popup display
241+ readyToShowCb := func(inner webkit.WebView) {
242+ if wv.OnReadyToShow != nil {
243+ wv.OnReadyToShow()
244+ }
245+ }
246+ sigID = wv.inner.ConnectReadyToShow(&readyToShowCb)
247+ wv.signalIDs = append(wv.signalIDs, sigID)
248+
160 // Note: notify::title, notify::uri, notify::estimated-load-progress249 // Note: notify::title, notify::uri, notify::estimated-load-progress
161 // would need GObject property change notifications which may require250 // would need GObject property change notifications which may require
162 // different API patterns in puregotk. For now, we update these in251 // different API patterns in puregotk. For now, we update these in

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