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 { | |||
| 37 | 37 | onFaviconChanged func([]byte) | |
| 38 | 38 | onFaviconURIChanged func(pageURI, faviconURI string) | |
| 39 | 39 | 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 | |
| 41 | 43 | onClose func() | |
| 42 | 44 | onNavigationPolicy func(url string, isUserGesture bool) bool | |
| 43 | 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 | 255 | // Script message received - connect to UserContentManager's script-message-received signal | |
| 226 | 256 | // This receives messages from JavaScript via webkit.messageHandlers.dumber.postMessage() | |
| 227 | 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 | 510 | 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 | 532 | // RegisterNavigationPolicyHandler registers a handler for navigation policy decisions | |
| 484 | 533 | func (w *WebView) RegisterNavigationPolicyHandler(handler func(url string, isUserGesture bool) bool) { | |
| 485 | 534 | w.mu.Lock() | |
| @@ -712,3 +761,97 @@ func (w *WebView) RegisterWorkspaceNavigationHandler(handler func(direction stri | |||
| 712 | 761 | defer w.mu.Unlock() | |
| 713 | 762 | 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 | + | } | |
| @@ -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) *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 | |||
| 41 | onClose func() | 43 | onClose func() | ||
| 42 | onNavigationPolicy func(url string, isUserGesture bool) bool | 44 | 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 signal | 255 | // 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 = handler | 510 | 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 decisions | 532 | // 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 = handler | 762 | 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 | + | } | |||