detached dbdc6c0

feat(webkit): add native popup lifecycle support

Add WebKit create/ready-to-show signal handlers for proper popup
lifecycle management:
- RegisterPopupCreateHandler: handles WebKit's create signal
- RegisterReadyToShowHandler: handles ready-to-show signal
- NewBareRelatedWebView: creates related WebView with CGO
- WrapBareWebView/InitializeFromBare: deferred initialization

This aligns with WebKitGTK's expected popup lifecycle instead of
bypassing it with manual WebView creation.
commit
dbdc6c0b9319c6d8f878a184175cdfa8e47d2427
author
bamen · · 2025-10-12 07:05 UTC
parent
ce336d0
signature
unsigned
2 files · +262 -1
M pkg/webkit/webview.go +144 -1
@@ -37,7 +37,9 @@ type WebView struct {
3737 onFaviconChanged func([]byte)
3838 onFaviconURIChanged func(pageURI, faviconURI string)
3939 onZoomChanged func(float64)
40- onPopup func(string) *WebView
40+ onPopup func(string) *WebView // Deprecated: Use onPopupCreate instead
41+ onPopupCreate func(*webkit.NavigationAction) *WebView // New WebKit create signal handler
42+ onReadyToShow func() // WebKit ready-to-show signal handler
4143 onClose func()
4244 onNavigationPolicy func(url string, isUserGesture bool) bool
4345 onWindowTypeDetected func(WindowType, *WindowFeatures)
@@ -222,6 +224,34 @@ func (w *WebView) setupEventHandlers() {
222224 }
223225 })
224226
227+ // Create signal - for popup lifecycle management
228+ w.view.ConnectCreate(func(navigationAction *webkit.NavigationAction) gtk.Widgetter {
229+ log.Printf("[webkit] ConnectCreate callback fired for parent WebView ID=%d", w.id)
230+ if w.onPopupCreate != nil {
231+ log.Printf("[webkit] Calling onPopupCreate handler")
232+ newWebView := w.onPopupCreate(navigationAction)
233+ if newWebView != nil {
234+ log.Printf("[webkit] onPopupCreate returned WebView wrapper ID=%d", newWebView.id)
235+ log.Printf("[webkit] Extracting underlying gotk4 WebView from wrapper")
236+ underlyingView := newWebView.view
237+ log.Printf("[webkit] Extracted gotk4 WebView=%p, about to return to WebKit", underlyingView)
238+ // Return the underlying WebView widget to WebKit
239+ return underlyingView
240+ }
241+ log.Printf("[webkit] onPopupCreate returned nil, blocking popup")
242+ }
243+ // Return nil to cancel popup creation
244+ log.Printf("[webkit] No onPopupCreate handler, blocking popup")
245+ return nil
246+ })
247+
248+ // Ready-to-show signal - emitted when popup is ready to be displayed
249+ w.view.ConnectReadyToShow(func() {
250+ if w.onReadyToShow != nil {
251+ w.onReadyToShow()
252+ }
253+ })
254+
225255 // Script message received - connect to UserContentManager's script-message-received signal
226256 // This receives messages from JavaScript via webkit.messageHandlers.dumber.postMessage()
227257 // The signal name includes the handler name as a detail: "script-message-received::dumber"
@@ -480,6 +510,25 @@ func (w *WebView) RegisterCloseHandler(handler func()) {
480510 w.onClose = handler
481511 }
482512
513+// RegisterPopupCreateHandler registers a handler for WebKit's create signal
514+// This is called when JavaScript calls window.open() or a link with target="_blank" is clicked
515+// The handler should create and return a new WebView for the popup, or return nil to block it
516+// The returned WebView should NOT be shown yet - WebKit will emit ready-to-show when it's ready
517+func (w *WebView) RegisterPopupCreateHandler(handler func(*webkit.NavigationAction) *WebView) {
518+ w.mu.Lock()
519+ defer w.mu.Unlock()
520+ w.onPopupCreate = handler
521+}
522+
523+// RegisterReadyToShowHandler registers a handler for WebKit's ready-to-show signal
524+// This is called when a popup WebView is ready to be displayed
525+// At this point it's safe to insert the popup into the UI (workspace, window, etc.)
526+func (w *WebView) RegisterReadyToShowHandler(handler func()) {
527+ w.mu.Lock()
528+ defer w.mu.Unlock()
529+ w.onReadyToShow = handler
530+}
531+
483532 // RegisterNavigationPolicyHandler registers a handler for navigation policy decisions
484533 func (w *WebView) RegisterNavigationPolicyHandler(handler func(url string, isUserGesture bool) bool) {
485534 w.mu.Lock()
@@ -712,3 +761,97 @@ func (w *WebView) RegisterWorkspaceNavigationHandler(handler func(direction stri
712761 defer w.mu.Unlock()
713762 w.onWorkspaceNavigation = handler
714763 }
764+
765+// GtkWebView returns the underlying gotk4 WebView for advanced operations
766+// This is used when creating related views (popups) that need to share the same session
767+func (w *WebView) GtkWebView() *webkit.WebView {
768+ w.mu.RLock()
769+ defer w.mu.RUnlock()
770+ return w.view
771+}
772+
773+// WrapBareWebView creates a minimal WebView wrapper around a bare gotk4 WebView
774+// This is used during popup creation to return a WebView to WebKit before initialization
775+// Full initialization (container, scripts, event handlers) should be done later
776+func WrapBareWebView(bareView *webkit.WebView) *WebView {
777+ if bareView == nil {
778+ return nil
779+ }
780+
781+ // Generate unique ID
782+ id := atomic.AddUint64(&viewIDCounter, 1)
783+ log.Printf("[webkit] Created minimal wrapper for bare WebView (ID: %d)", id)
784+
785+ // Create minimal wrapper - NO initialization yet
786+ wv := &WebView{
787+ view: bareView,
788+ container: nil, // Will be created during full initialization
789+ window: nil,
790+ id: id,
791+ config: nil, // Will be set during full initialization
792+ }
793+
794+ // Register in global registry
795+ viewMu.Lock()
796+ viewRegistry[id] = wv
797+ viewMu.Unlock()
798+
799+ return wv
800+}
801+
802+// InitializeFromBare completes initialization of a bare WebView wrapper
803+// This should be called in ready-to-show after WebKit has configured the view
804+func (w *WebView) InitializeFromBare(cfg *Config) error {
805+ w.mu.Lock()
806+ defer w.mu.Unlock()
807+
808+ if w.config != nil {
809+ return fmt.Errorf("WebView already initialized")
810+ }
811+
812+ if cfg == nil {
813+ cfg = GetDefaultConfig()
814+ }
815+
816+ w.config = cfg
817+
818+ // Verify persistent session
819+ viewSession := w.view.NetworkSession()
820+ if viewSession == nil {
821+ return fmt.Errorf("WebView has no network session")
822+ }
823+
824+ // Setup UserContentManager and inject GUI scripts
825+ if err := SetupUserContentManager(w.view, cfg.AppearanceConfigJSON, w.id); err != nil {
826+ return fmt.Errorf("failed to setup user content manager: %w", err)
827+ }
828+
829+ // Create container (GtkBox) to hold the WebView
830+ container := gtk.NewBox(gtk.OrientationVertical, 0)
831+ container.SetHExpand(true)
832+ container.SetVExpand(true)
833+
834+ // Configure WebView widget for expansion
835+ w.view.SetHExpand(true)
836+ w.view.SetVExpand(true)
837+
838+ // Add WebView to container
839+ container.Append(w.view)
840+
841+ w.container = container
842+
843+ // Apply configuration
844+ if err := w.applyConfig(); err != nil {
845+ return err
846+ }
847+
848+ // Setup event handlers
849+ w.setupEventHandlers()
850+
851+ // Attach keyboard bridge
852+ w.AttachKeyboardBridge()
853+
854+ log.Printf("[webkit] Completed initialization for WebView ID %d", w.id)
855+
856+ return nil
857+}
@@ -37,7 +37,9 @@ type WebView struct {
37 onFaviconChanged func([]byte)37 onFaviconChanged func([]byte)
38 onFaviconURIChanged func(pageURI, faviconURI string)38 onFaviconURIChanged func(pageURI, faviconURI string)
39 onZoomChanged func(float64)39 onZoomChanged func(float64)
40- onPopup func(string) *WebView40+ onPopup func(string) *WebView // Deprecated: Use onPopupCreate instead
41+ onPopupCreate func(*webkit.NavigationAction) *WebView // New WebKit create signal handler
42+ onReadyToShow func() // WebKit ready-to-show signal handler
41 onClose func()43 onClose func()
42 onNavigationPolicy func(url string, isUserGesture bool) bool44 onNavigationPolicy func(url string, isUserGesture bool) bool
43 onWindowTypeDetected func(WindowType, *WindowFeatures)45 onWindowTypeDetected func(WindowType, *WindowFeatures)
@@ -222,6 +224,34 @@ func (w *WebView) setupEventHandlers() {
222 }224 }
223 })225 })
224 226
227+ // Create signal - for popup lifecycle management
228+ w.view.ConnectCreate(func(navigationAction *webkit.NavigationAction) gtk.Widgetter {
229+ log.Printf("[webkit] ConnectCreate callback fired for parent WebView ID=%d", w.id)
230+ if w.onPopupCreate != nil {
231+ log.Printf("[webkit] Calling onPopupCreate handler")
232+ newWebView := w.onPopupCreate(navigationAction)
233+ if newWebView != nil {
234+ log.Printf("[webkit] onPopupCreate returned WebView wrapper ID=%d", newWebView.id)
235+ log.Printf("[webkit] Extracting underlying gotk4 WebView from wrapper")
236+ underlyingView := newWebView.view
237+ log.Printf("[webkit] Extracted gotk4 WebView=%p, about to return to WebKit", underlyingView)
238+ // Return the underlying WebView widget to WebKit
239+ return underlyingView
240+ }
241+ log.Printf("[webkit] onPopupCreate returned nil, blocking popup")
242+ }
243+ // Return nil to cancel popup creation
244+ log.Printf("[webkit] No onPopupCreate handler, blocking popup")
245+ return nil
246+ })
247+
248+ // Ready-to-show signal - emitted when popup is ready to be displayed
249+ w.view.ConnectReadyToShow(func() {
250+ if w.onReadyToShow != nil {
251+ w.onReadyToShow()
252+ }
253+ })
254+
225 // Script message received - connect to UserContentManager's script-message-received signal255 // Script message received - connect to UserContentManager's script-message-received signal
226 // This receives messages from JavaScript via webkit.messageHandlers.dumber.postMessage()256 // This receives messages from JavaScript via webkit.messageHandlers.dumber.postMessage()
227 // The signal name includes the handler name as a detail: "script-message-received::dumber"257 // The signal name includes the handler name as a detail: "script-message-received::dumber"
@@ -480,6 +510,25 @@ func (w *WebView) RegisterCloseHandler(handler func()) {
480 w.onClose = handler510 w.onClose = handler
481 }511 }
482 512
513+// RegisterPopupCreateHandler registers a handler for WebKit's create signal
514+// This is called when JavaScript calls window.open() or a link with target="_blank" is clicked
515+// The handler should create and return a new WebView for the popup, or return nil to block it
516+// The returned WebView should NOT be shown yet - WebKit will emit ready-to-show when it's ready
517+func (w *WebView) RegisterPopupCreateHandler(handler func(*webkit.NavigationAction) *WebView) {
518+ w.mu.Lock()
519+ defer w.mu.Unlock()
520+ w.onPopupCreate = handler
521+}
522+
523+// RegisterReadyToShowHandler registers a handler for WebKit's ready-to-show signal
524+// This is called when a popup WebView is ready to be displayed
525+// At this point it's safe to insert the popup into the UI (workspace, window, etc.)
526+func (w *WebView) RegisterReadyToShowHandler(handler func()) {
527+ w.mu.Lock()
528+ defer w.mu.Unlock()
529+ w.onReadyToShow = handler
530+}
531+
483 // RegisterNavigationPolicyHandler registers a handler for navigation policy decisions532 // RegisterNavigationPolicyHandler registers a handler for navigation policy decisions
484 func (w *WebView) RegisterNavigationPolicyHandler(handler func(url string, isUserGesture bool) bool) {533 func (w *WebView) RegisterNavigationPolicyHandler(handler func(url string, isUserGesture bool) bool) {
485 w.mu.Lock()534 w.mu.Lock()
@@ -712,3 +761,97 @@ func (w *WebView) RegisterWorkspaceNavigationHandler(handler func(direction stri
712 defer w.mu.Unlock()761 defer w.mu.Unlock()
713 w.onWorkspaceNavigation = handler762 w.onWorkspaceNavigation = handler
714 }763 }
764+
765+// GtkWebView returns the underlying gotk4 WebView for advanced operations
766+// This is used when creating related views (popups) that need to share the same session
767+func (w *WebView) GtkWebView() *webkit.WebView {
768+ w.mu.RLock()
769+ defer w.mu.RUnlock()
770+ return w.view
771+}
772+
773+// WrapBareWebView creates a minimal WebView wrapper around a bare gotk4 WebView
774+// This is used during popup creation to return a WebView to WebKit before initialization
775+// Full initialization (container, scripts, event handlers) should be done later
776+func WrapBareWebView(bareView *webkit.WebView) *WebView {
777+ if bareView == nil {
778+ return nil
779+ }
780+
781+ // Generate unique ID
782+ id := atomic.AddUint64(&viewIDCounter, 1)
783+ log.Printf("[webkit] Created minimal wrapper for bare WebView (ID: %d)", id)
784+
785+ // Create minimal wrapper - NO initialization yet
786+ wv := &WebView{
787+ view: bareView,
788+ container: nil, // Will be created during full initialization
789+ window: nil,
790+ id: id,
791+ config: nil, // Will be set during full initialization
792+ }
793+
794+ // Register in global registry
795+ viewMu.Lock()
796+ viewRegistry[id] = wv
797+ viewMu.Unlock()
798+
799+ return wv
800+}
801+
802+// InitializeFromBare completes initialization of a bare WebView wrapper
803+// This should be called in ready-to-show after WebKit has configured the view
804+func (w *WebView) InitializeFromBare(cfg *Config) error {
805+ w.mu.Lock()
806+ defer w.mu.Unlock()
807+
808+ if w.config != nil {
809+ return fmt.Errorf("WebView already initialized")
810+ }
811+
812+ if cfg == nil {
813+ cfg = GetDefaultConfig()
814+ }
815+
816+ w.config = cfg
817+
818+ // Verify persistent session
819+ viewSession := w.view.NetworkSession()
820+ if viewSession == nil {
821+ return fmt.Errorf("WebView has no network session")
822+ }
823+
824+ // Setup UserContentManager and inject GUI scripts
825+ if err := SetupUserContentManager(w.view, cfg.AppearanceConfigJSON, w.id); err != nil {
826+ return fmt.Errorf("failed to setup user content manager: %w", err)
827+ }
828+
829+ // Create container (GtkBox) to hold the WebView
830+ container := gtk.NewBox(gtk.OrientationVertical, 0)
831+ container.SetHExpand(true)
832+ container.SetVExpand(true)
833+
834+ // Configure WebView widget for expansion
835+ w.view.SetHExpand(true)
836+ w.view.SetVExpand(true)
837+
838+ // Add WebView to container
839+ container.Append(w.view)
840+
841+ w.container = container
842+
843+ // Apply configuration
844+ if err := w.applyConfig(); err != nil {
845+ return err
846+ }
847+
848+ // Setup event handlers
849+ w.setupEventHandlers()
850+
851+ // Attach keyboard bridge
852+ w.AttachKeyboardBridge()
853+
854+ log.Printf("[webkit] Completed initialization for WebView ID %d", w.id)
855+
856+ return nil
857+}
A pkg/webkit/webview_related.go +118 -0
@@ -0,0 +1,118 @@
1+package webkit
2+
3+/*
4+#cgo pkg-config: webkitgtk-6.0 gtk4
5+#include <webkit/webkit.h>
6+#include <gtk/gtk.h>
7+#include <stdlib.h>
8+
9+// create_related_web_view creates a WebView with the related-view property set
10+// This is the WebKitGTK 6.0 way to share session/cookies between views (for popups)
11+// The related-view property is construct-only, so it must be set during g_object_new
12+static inline WebKitWebView* create_related_web_view(WebKitWebView* parent) {
13+ return WEBKIT_WEB_VIEW(g_object_new(
14+ WEBKIT_TYPE_WEB_VIEW,
15+ "related-view", parent,
16+ NULL
17+ ));
18+}
19+*/
20+import "C"
21+import (
22+ "log"
23+ "runtime"
24+ "unsafe"
25+
26+ webkit "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6"
27+ coreglib "github.com/diamondburned/gotk4/pkg/core/glib"
28+ "github.com/diamondburned/gotk4/pkg/gtk/v4"
29+)
30+
31+// NewBareRelatedWebView creates a bare gotk4 WebView with the related-view property set
32+// This ensures the popup shares the same session/process context as the parent
33+// The returned WebView is bare (no wrapper, no initialization) and must be initialized later
34+//
35+// Since gotk4-webkitgtk's wrapWebView() is internal and cannot be accessed,
36+// we manually construct the WebView struct hierarchy using CGO.
37+func NewBareRelatedWebView(parentView *webkit.WebView) *webkit.WebView {
38+ if parentView == nil {
39+ log.Printf("[webkit] NewBareRelatedWebView: parent is nil, creating unrelated WebView")
40+ return webkit.NewWebView()
41+ }
42+
43+ // Get the native C pointer from the gotk4 WebView
44+ parentObj := coreglib.InternObject(parentView)
45+ if parentObj == nil {
46+ log.Printf("[webkit] NewBareRelatedWebView: failed to get parent object, creating unrelated WebView")
47+ return webkit.NewWebView()
48+ }
49+ parentNative := (*C.WebKitWebView)(unsafe.Pointer(parentObj.Native()))
50+ if parentNative == nil {
51+ log.Printf("[webkit] NewBareRelatedWebView: parent native pointer is nil, creating unrelated WebView")
52+ return webkit.NewWebView()
53+ }
54+
55+ log.Printf("[webkit] Creating bare WebView with related-view property (parent=%p)", parentNative)
56+
57+ // Create related WebView using CGO helper with g_object_new
58+ log.Printf("[webkit] Step 3: About to call C.create_related_web_view")
59+ webViewNative := C.create_related_web_view(parentNative)
60+ log.Printf("[webkit] Step 4: C.create_related_web_view returned: %p", webViewNative)
61+
62+ if webViewNative == nil {
63+ log.Printf("[webkit] NewBareRelatedWebView: C.create_related_web_view returned nil")
64+ return nil
65+ }
66+
67+ // Wrap the C object in a coreglib.Object
68+ // This takes ownership of the floating reference
69+ log.Printf("[webkit] Step 5: About to call coreglib.Take")
70+ obj := coreglib.Take(unsafe.Pointer(webViewNative))
71+ log.Printf("[webkit] Step 6: coreglib.Take succeeded, obj=%p", obj)
72+
73+ // Manually construct the WebView struct hierarchy
74+ // WebView -> WebViewBase -> gtk.Widget -> coreglib.InitiallyUnowned -> coreglib.Object
75+ //
76+ // Since we cannot call the internal wrapWebView() function, we must
77+ // manually construct the full struct hierarchy using the same pattern
78+ // that gotk4 uses internally.
79+
80+ log.Printf("[webkit] Step 7: About to create gtk.Widget base")
81+ // Create the gtk.Widget base
82+ widget := gtk.Widget{
83+ InitiallyUnowned: coreglib.InitiallyUnowned{
84+ Object: obj,
85+ },
86+ Object: obj,
87+ Accessible: gtk.Accessible{
88+ Object: obj,
89+ },
90+ Buildable: gtk.Buildable{
91+ Object: obj,
92+ },
93+ ConstraintTarget: gtk.ConstraintTarget{
94+ Object: obj,
95+ },
96+ }
97+ log.Printf("[webkit] Step 8: Created gtk.Widget base")
98+
99+ // Create the WebViewBase
100+ log.Printf("[webkit] Step 9: About to create WebViewBase")
101+ webViewBase := webkit.WebViewBase{
102+ Widget: widget,
103+ }
104+ log.Printf("[webkit] Step 10: Created WebViewBase")
105+
106+ // Create the final WebView
107+ log.Printf("[webkit] Step 11: About to create final WebView")
108+ relatedView := &webkit.WebView{
109+ WebViewBase: webViewBase,
110+ }
111+ log.Printf("[webkit] Step 12: Created final WebView: %p", relatedView)
112+
113+ // Ensure parentView is kept alive until after the CGO call completes
114+ runtime.KeepAlive(parentView)
115+
116+ log.Printf("[webkit] Created bare related WebView (parent=%p)", parentNative)
117+ return relatedView
118+}
@@ -0,0 +1,118 @@
1+package webkit
2+
3+/*
4+#cgo pkg-config: webkitgtk-6.0 gtk4
5+#include <webkit/webkit.h>
6+#include <gtk/gtk.h>
7+#include <stdlib.h>
8+
9+// create_related_web_view creates a WebView with the related-view property set
10+// This is the WebKitGTK 6.0 way to share session/cookies between views (for popups)
11+// The related-view property is construct-only, so it must be set during g_object_new
12+static inline WebKitWebView* create_related_web_view(WebKitWebView* parent) {
13+ return WEBKIT_WEB_VIEW(g_object_new(
14+ WEBKIT_TYPE_WEB_VIEW,
15+ "related-view", parent,
16+ NULL
17+ ));
18+}
19+*/
20+import "C"
21+import (
22+ "log"
23+ "runtime"
24+ "unsafe"
25+
26+ webkit "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6"
27+ coreglib "github.com/diamondburned/gotk4/pkg/core/glib"
28+ "github.com/diamondburned/gotk4/pkg/gtk/v4"
29+)
30+
31+// NewBareRelatedWebView creates a bare gotk4 WebView with the related-view property set
32+// This ensures the popup shares the same session/process context as the parent
33+// The returned WebView is bare (no wrapper, no initialization) and must be initialized later
34+//
35+// Since gotk4-webkitgtk's wrapWebView() is internal and cannot be accessed,
36+// we manually construct the WebView struct hierarchy using CGO.
37+func NewBareRelatedWebView(parentView *webkit.WebView) *webkit.WebView {
38+ if parentView == nil {
39+ log.Printf("[webkit] NewBareRelatedWebView: parent is nil, creating unrelated WebView")
40+ return webkit.NewWebView()
41+ }
42+
43+ // Get the native C pointer from the gotk4 WebView
44+ parentObj := coreglib.InternObject(parentView)
45+ if parentObj == nil {
46+ log.Printf("[webkit] NewBareRelatedWebView: failed to get parent object, creating unrelated WebView")
47+ return webkit.NewWebView()
48+ }
49+ parentNative := (*C.WebKitWebView)(unsafe.Pointer(parentObj.Native()))
50+ if parentNative == nil {
51+ log.Printf("[webkit] NewBareRelatedWebView: parent native pointer is nil, creating unrelated WebView")
52+ return webkit.NewWebView()
53+ }
54+
55+ log.Printf("[webkit] Creating bare WebView with related-view property (parent=%p)", parentNative)
56+
57+ // Create related WebView using CGO helper with g_object_new
58+ log.Printf("[webkit] Step 3: About to call C.create_related_web_view")
59+ webViewNative := C.create_related_web_view(parentNative)
60+ log.Printf("[webkit] Step 4: C.create_related_web_view returned: %p", webViewNative)
61+
62+ if webViewNative == nil {
63+ log.Printf("[webkit] NewBareRelatedWebView: C.create_related_web_view returned nil")
64+ return nil
65+ }
66+
67+ // Wrap the C object in a coreglib.Object
68+ // This takes ownership of the floating reference
69+ log.Printf("[webkit] Step 5: About to call coreglib.Take")
70+ obj := coreglib.Take(unsafe.Pointer(webViewNative))
71+ log.Printf("[webkit] Step 6: coreglib.Take succeeded, obj=%p", obj)
72+
73+ // Manually construct the WebView struct hierarchy
74+ // WebView -> WebViewBase -> gtk.Widget -> coreglib.InitiallyUnowned -> coreglib.Object
75+ //
76+ // Since we cannot call the internal wrapWebView() function, we must
77+ // manually construct the full struct hierarchy using the same pattern
78+ // that gotk4 uses internally.
79+
80+ log.Printf("[webkit] Step 7: About to create gtk.Widget base")
81+ // Create the gtk.Widget base
82+ widget := gtk.Widget{
83+ InitiallyUnowned: coreglib.InitiallyUnowned{
84+ Object: obj,
85+ },
86+ Object: obj,
87+ Accessible: gtk.Accessible{
88+ Object: obj,
89+ },
90+ Buildable: gtk.Buildable{
91+ Object: obj,
92+ },
93+ ConstraintTarget: gtk.ConstraintTarget{
94+ Object: obj,
95+ },
96+ }
97+ log.Printf("[webkit] Step 8: Created gtk.Widget base")
98+
99+ // Create the WebViewBase
100+ log.Printf("[webkit] Step 9: About to create WebViewBase")
101+ webViewBase := webkit.WebViewBase{
102+ Widget: widget,
103+ }
104+ log.Printf("[webkit] Step 10: Created WebViewBase")
105+
106+ // Create the final WebView
107+ log.Printf("[webkit] Step 11: About to create final WebView")
108+ relatedView := &webkit.WebView{
109+ WebViewBase: webViewBase,
110+ }
111+ log.Printf("[webkit] Step 12: Created final WebView: %p", relatedView)
112+
113+ // Ensure parentView is kept alive until after the CGO call completes
114+ runtime.KeepAlive(parentView)
115+
116+ log.Printf("[webkit] Created bare related WebView (parent=%p)", parentNative)
117+ return relatedView
118+}

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