detached d9972d9

feat(webkit): implement popup handler with webkit create signal

commit
d9972d972d3525e59823783407a37f2062713dff
author
bamen · · 2025-09-20 05:21 UTC
parent
d0eaf06
signature
unsigned
4 files · +345 -62
M internal/app/browser/webview.go +9 -24
@@ -186,35 +186,20 @@ func (app *BrowserApp) attachPaneHandlers(pane *BrowserPane) {
186186 pane.messageHandler.SetNavigationController(pane.navigationController)
187187 }
188188
189- pane.webView.RegisterPopupHandler(func(uri string) bool {
190- handled := false
189+ pane.webView.RegisterPopupHandler(func(uri string) *webkit.WebView {
191190 if app.workspace != nil {
192- handled = app.workspace.HandlePopup(pane.webView, uri)
193- }
194- if handled {
195- return true
191+ return app.workspace.HandlePopup(pane.webView, uri)
196192 }
197193
198- if pane.navigationController != nil && uri != "" {
199- if err := pane.navigationController.NavigateToURL(uri); err != nil {
200- log.Printf("[workspace] popup fallback navigation failed: %v", err)
201- if pane.webView != nil {
202- if loadErr := pane.webView.LoadURL(uri); loadErr != nil {
203- log.Printf("[workspace] popup fallback load failed: %v", loadErr)
204- }
194+ // No workspace manager - just navigate in current pane
195+ if uri != "" && pane.navigationController != nil {
196+ go func() {
197+ if err := pane.navigationController.NavigateToURL(uri); err != nil {
198+ log.Printf("[webview] failed to navigate to URL %s: %v", uri, err)
205199 }
206- }
207- return true
208- }
209-
210- if pane.webView != nil && uri != "" {
211- if err := pane.webView.LoadURL(uri); err != nil {
212- log.Printf("[workspace] popup direct load failed: %v", err)
213- }
214- return true
200+ }()
215201 }
216-
217- return false
202+ return nil
218203 })
219204 }
220205
@@ -186,35 +186,20 @@ func (app *BrowserApp) attachPaneHandlers(pane *BrowserPane) {
186 pane.messageHandler.SetNavigationController(pane.navigationController)186 pane.messageHandler.SetNavigationController(pane.navigationController)
187 }187 }
188 188
189- pane.webView.RegisterPopupHandler(func(uri string) bool {189+ pane.webView.RegisterPopupHandler(func(uri string) *webkit.WebView {
190- handled := false
191 if app.workspace != nil {190 if app.workspace != nil {
192- handled = app.workspace.HandlePopup(pane.webView, uri)191+ return app.workspace.HandlePopup(pane.webView, uri)
193- }
194- if handled {
195- return true
196 }192 }
197 193
198- if pane.navigationController != nil && uri != "" {194+ // No workspace manager - just navigate in current pane
199- if err := pane.navigationController.NavigateToURL(uri); err != nil {195+ if uri != "" && pane.navigationController != nil {
200- log.Printf("[workspace] popup fallback navigation failed: %v", err)196+ go func() {
201- if pane.webView != nil {197+ if err := pane.navigationController.NavigateToURL(uri); err != nil {
202- if loadErr := pane.webView.LoadURL(uri); loadErr != nil {198+ log.Printf("[webview] failed to navigate to URL %s: %v", uri, err)
203- log.Printf("[workspace] popup fallback load failed: %v", loadErr)
204- }
205 }199 }
206- }200+ }()
207- return true
208- }
209-
210- if pane.webView != nil && uri != "" {
211- if err := pane.webView.LoadURL(uri); err != nil {
212- log.Printf("[workspace] popup direct load failed: %v", err)
213- }
214- return true
215 }201 }
216-202+ return nil
217- return false
218 })203 })
219 }204 }
220 205
M pkg/webkit/keyboard_cgo.go +95 -7
@@ -84,6 +84,7 @@ import (
8484 "sync"
8585 "sync/atomic"
8686 "time"
87+ "unsafe"
8788 )
8889
8990 // Registry of accelerators per view id.
@@ -436,23 +437,110 @@ func goOnURIChanged(id C.ulong, curi *C.char) {
436437 vw.dispatchURIChanged(uri)
437438 }
438439
439-//export goOnPopupRequest
440-func goOnPopupRequest(id C.ulong, curi *C.char) C.gboolean {
440+//export goHandleNewWindowPolicy
441+func goHandleNewWindowPolicy(id C.ulong, curi *C.char, navType C.int) C.gboolean {
441442 uid := uintptr(id)
442443 regMu.RLock()
443444 vw := viewByID[uid]
444445 regMu.RUnlock()
446+
445447 if vw == nil {
446- return C.gboolean(0)
448+ log.Printf("[webkit] Policy decision: no WebView found for ID %d", uid)
449+ return 0 // FALSE
447450 }
448- var uri string
451+
452+ uri := ""
449453 if curi != nil {
450454 uri = C.GoString(curi)
451455 }
452- if vw.dispatchPopupRequest(uri) {
453- return C.gboolean(1)
456+
457+ // Navigation type constants from WebKit headers
458+ navTypeNames := map[int]string{
459+ 0: "LINK_CLICKED",
460+ 1: "FORM_SUBMITTED",
461+ 2: "BACK_FORWARD",
462+ 3: "RELOAD",
463+ 4: "FORM_RESUBMITTED",
464+ 5: "OTHER",
454465 }
455- return C.gboolean(0)
466+
467+ navTypeName := navTypeNames[int(navType)]
468+ if navTypeName == "" {
469+ navTypeName = fmt.Sprintf("UNKNOWN(%d)", int(navType))
470+ }
471+
472+ log.Printf("[webkit] Policy decision for new window: URI=%s, NavType=%s", uri, navTypeName)
473+
474+ // Check if we have a popup handler and if popups are enabled
475+ if vw.popupHandler != nil {
476+ log.Printf("[webkit] Calling popup handler for URI: %s", uri)
477+ // Call popup handler synchronously to get immediate result
478+ result := vw.popupHandler(uri)
479+ if result != nil {
480+ log.Printf("[webkit] Popup handler created new WebView for: %s - blocking default popup", uri)
481+ return 1 // TRUE - we handled it, block the default popup
482+ } else {
483+ log.Printf("[webkit] Popup handler declined to create WebView for: %s - allowing native popup", uri)
484+ return 0 // FALSE - we didn't handle it, allow default popup
485+ }
486+ } else {
487+ log.Printf("[webkit] No popup handler registered - allowing native popup for: %s", uri)
488+ return 0 // FALSE - no handler, allow default popup
489+ }
490+}
491+
492+//export goHandleCreateWebView
493+func goHandleCreateWebView(id C.ulong, curi *C.char) *C.GtkWidget {
494+ uid := uintptr(id)
495+ regMu.RLock()
496+ vw := viewByID[uid]
497+ regMu.RUnlock()
498+
499+ if vw == nil {
500+ log.Printf("[webkit] Create signal: no WebView found for ID %d", uid)
501+ return nil // Fallback to native popup
502+ }
503+
504+ uri := ""
505+ if curi != nil {
506+ uri = C.GoString(curi)
507+ }
508+
509+ log.Printf("[webkit] Create signal: creating new pane for URI=%s", uri)
510+
511+ // Check if we have a popup handler
512+ if vw.popupHandler == nil {
513+ log.Printf("[webkit] Create signal: no popup handler registered")
514+ return nil // Fallback to native popup
515+ }
516+
517+ // Always use the popup handler to create workspace pane (tiling WM design)
518+ newView := vw.popupHandler(uri)
519+ if newView != nil {
520+ widget := newView.Widget()
521+ if widget != 0 {
522+ log.Printf("[webkit] Create signal: created new workspace pane for popup")
523+ return (*C.GtkWidget)(unsafe.Pointer(widget))
524+ }
525+ }
526+
527+ log.Printf("[webkit] Create signal: popup handler failed to create pane, falling back to native")
528+ return nil // Fallback to native popup if workspace creation fails
529+}
530+
531+
532+//export goHandlePopupGeometry
533+func goHandlePopupGeometry(id C.ulong, x, y, width, height C.int) {
534+ // Store geometry for workspace manager to use
535+ uid := uintptr(id)
536+ regMu.Lock()
537+ if vw := viewByID[uid]; vw != nil {
538+ // For now, we'll store this in a simple way
539+ // The workspace manager will handle the geometry
540+ log.Printf("[webkit] Popup geometry: %dx%d at (%d,%d)",
541+ int(width), int(height), int(x), int(y))
542+ }
543+ regMu.Unlock()
456544 }
457545
458546 //export goOnButtonPress
@@ -84,6 +84,7 @@ import (
84 "sync"84 "sync"
85 "sync/atomic"85 "sync/atomic"
86 "time"86 "time"
87+ "unsafe"
87 )88 )
88 89
89 // Registry of accelerators per view id.90 // Registry of accelerators per view id.
@@ -436,23 +437,110 @@ func goOnURIChanged(id C.ulong, curi *C.char) {
436 vw.dispatchURIChanged(uri)437 vw.dispatchURIChanged(uri)
437 }438 }
438 439
439-//export goOnPopupRequest440+//export goHandleNewWindowPolicy
440-func goOnPopupRequest(id C.ulong, curi *C.char) C.gboolean {441+func goHandleNewWindowPolicy(id C.ulong, curi *C.char, navType C.int) C.gboolean {
441 uid := uintptr(id)442 uid := uintptr(id)
442 regMu.RLock()443 regMu.RLock()
443 vw := viewByID[uid]444 vw := viewByID[uid]
444 regMu.RUnlock()445 regMu.RUnlock()
446+
445 if vw == nil {447 if vw == nil {
446- return C.gboolean(0)448+ log.Printf("[webkit] Policy decision: no WebView found for ID %d", uid)
449+ return 0 // FALSE
447 }450 }
448- var uri string451+
452+ uri := ""
449 if curi != nil {453 if curi != nil {
450 uri = C.GoString(curi)454 uri = C.GoString(curi)
451 }455 }
452- if vw.dispatchPopupRequest(uri) {456+
453- return C.gboolean(1)457+ // Navigation type constants from WebKit headers
458+ navTypeNames := map[int]string{
459+ 0: "LINK_CLICKED",
460+ 1: "FORM_SUBMITTED",
461+ 2: "BACK_FORWARD",
462+ 3: "RELOAD",
463+ 4: "FORM_RESUBMITTED",
464+ 5: "OTHER",
454 }465 }
455- return C.gboolean(0)466+
467+ navTypeName := navTypeNames[int(navType)]
468+ if navTypeName == "" {
469+ navTypeName = fmt.Sprintf("UNKNOWN(%d)", int(navType))
470+ }
471+
472+ log.Printf("[webkit] Policy decision for new window: URI=%s, NavType=%s", uri, navTypeName)
473+
474+ // Check if we have a popup handler and if popups are enabled
475+ if vw.popupHandler != nil {
476+ log.Printf("[webkit] Calling popup handler for URI: %s", uri)
477+ // Call popup handler synchronously to get immediate result
478+ result := vw.popupHandler(uri)
479+ if result != nil {
480+ log.Printf("[webkit] Popup handler created new WebView for: %s - blocking default popup", uri)
481+ return 1 // TRUE - we handled it, block the default popup
482+ } else {
483+ log.Printf("[webkit] Popup handler declined to create WebView for: %s - allowing native popup", uri)
484+ return 0 // FALSE - we didn't handle it, allow default popup
485+ }
486+ } else {
487+ log.Printf("[webkit] No popup handler registered - allowing native popup for: %s", uri)
488+ return 0 // FALSE - no handler, allow default popup
489+ }
490+}
491+
492+//export goHandleCreateWebView
493+func goHandleCreateWebView(id C.ulong, curi *C.char) *C.GtkWidget {
494+ uid := uintptr(id)
495+ regMu.RLock()
496+ vw := viewByID[uid]
497+ regMu.RUnlock()
498+
499+ if vw == nil {
500+ log.Printf("[webkit] Create signal: no WebView found for ID %d", uid)
501+ return nil // Fallback to native popup
502+ }
503+
504+ uri := ""
505+ if curi != nil {
506+ uri = C.GoString(curi)
507+ }
508+
509+ log.Printf("[webkit] Create signal: creating new pane for URI=%s", uri)
510+
511+ // Check if we have a popup handler
512+ if vw.popupHandler == nil {
513+ log.Printf("[webkit] Create signal: no popup handler registered")
514+ return nil // Fallback to native popup
515+ }
516+
517+ // Always use the popup handler to create workspace pane (tiling WM design)
518+ newView := vw.popupHandler(uri)
519+ if newView != nil {
520+ widget := newView.Widget()
521+ if widget != 0 {
522+ log.Printf("[webkit] Create signal: created new workspace pane for popup")
523+ return (*C.GtkWidget)(unsafe.Pointer(widget))
524+ }
525+ }
526+
527+ log.Printf("[webkit] Create signal: popup handler failed to create pane, falling back to native")
528+ return nil // Fallback to native popup if workspace creation fails
529+}
530+
531+
532+//export goHandlePopupGeometry
533+func goHandlePopupGeometry(id C.ulong, x, y, width, height C.int) {
534+ // Store geometry for workspace manager to use
535+ uid := uintptr(id)
536+ regMu.Lock()
537+ if vw := viewByID[uid]; vw != nil {
538+ // For now, we'll store this in a simple way
539+ // The workspace manager will handle the geometry
540+ log.Printf("[webkit] Popup geometry: %dx%d at (%d,%d)",
541+ int(width), int(height), int(x), int(y))
542+ }
543+ regMu.Unlock()
456 }544 }
457 545
458 //export goOnButtonPress546 //export goOnButtonPress
M pkg/webkit/webview.go +16 -4
@@ -5,6 +5,7 @@ package webkit
55 import (
66 "fmt"
77 "log"
8+ "unsafe"
89 )
910
1011 // WebView represents a browser view powered by WebKit2GTK.
@@ -20,7 +21,7 @@ type WebView struct {
2021 titleHandler func(title string)
2122 uriHandler func(uri string)
2223 zoomHandler func(level float64)
23- popupHandler func(string) bool
24+ popupHandler func(string) *WebView
2425 useDomZoom bool
2526 domZoomSeed float64
2627 container uintptr
@@ -45,6 +46,12 @@ func NewWebView(cfg *Config) (*WebView, error) {
4546 return wv, nil
4647 }
4748
49+// NewWebViewWithRelated creates a new WebView related to an existing one (non-CGO stub)
50+func NewWebViewWithRelated(cfg *Config, relatedView *WebView) (*WebView, error) {
51+ // In non-CGO build, just create a regular WebView
52+ return NewWebView(cfg)
53+}
54+
4855 // LoadURL navigates the webview to the specified URL.
4956 func (w *WebView) LoadURL(rawURL string) error {
5057 if w == nil || w.destroyed {
@@ -114,7 +121,7 @@ func (w *WebView) CloseDevTools() error { return nil }
114121 // RegisterScriptMessageHandler registers a callback invoked when the content script posts a message.
115122 func (w *WebView) RegisterScriptMessageHandler(cb func(payload string)) { w.msgHandler = cb }
116123
117-func (w *WebView) RegisterPopupHandler(cb func(string) bool) { w.popupHandler = cb }
124+func (w *WebView) RegisterPopupHandler(cb func(string) *WebView) { w.popupHandler = cb }
118125
119126 func (w *WebView) dispatchScriptMessage(payload string) { //nolint:unused // Called from CGO WebKit callbacks
120127 if w != nil && w.msgHandler != nil {
@@ -122,11 +129,16 @@ func (w *WebView) dispatchScriptMessage(payload string) { //nolint:unused // Cal
122129 }
123130 }
124131
125-func (w *WebView) dispatchPopupRequest(uri string) bool { //nolint:unused // Called from CGO WebKit callbacks
132+func (w *WebView) dispatchPopupRequest(uri string) *WebView { //nolint:unused // Called from CGO WebKit callbacks
126133 if w != nil && w.popupHandler != nil {
127134 return w.popupHandler(uri)
128135 }
129- return false
136+ return nil
137+}
138+
139+// GetNativePointer returns nil for non-CGO build (stub implementation)
140+func (w *WebView) GetNativePointer() unsafe.Pointer { //nolint:unused // Needed for interface compatibility
141+ return nil
130142 }
131143
132144 // RegisterTitleChangedHandler registers a callback invoked when the page title changes.
@@ -5,6 +5,7 @@ package webkit
5 import (5 import (
6 "fmt"6 "fmt"
7 "log"7 "log"
8+ "unsafe"
8 )9 )
9 10
10 // WebView represents a browser view powered by WebKit2GTK.11 // WebView represents a browser view powered by WebKit2GTK.
@@ -20,7 +21,7 @@ type WebView struct {
20 titleHandler func(title string)21 titleHandler func(title string)
21 uriHandler func(uri string)22 uriHandler func(uri string)
22 zoomHandler func(level float64)23 zoomHandler func(level float64)
23- popupHandler func(string) bool24+ popupHandler func(string) *WebView
24 useDomZoom bool25 useDomZoom bool
25 domZoomSeed float6426 domZoomSeed float64
26 container uintptr27 container uintptr
@@ -45,6 +46,12 @@ func NewWebView(cfg *Config) (*WebView, error) {
45 return wv, nil46 return wv, nil
46 }47 }
47 48
49+// NewWebViewWithRelated creates a new WebView related to an existing one (non-CGO stub)
50+func NewWebViewWithRelated(cfg *Config, relatedView *WebView) (*WebView, error) {
51+ // In non-CGO build, just create a regular WebView
52+ return NewWebView(cfg)
53+}
54+
48 // LoadURL navigates the webview to the specified URL.55 // LoadURL navigates the webview to the specified URL.
49 func (w *WebView) LoadURL(rawURL string) error {56 func (w *WebView) LoadURL(rawURL string) error {
50 if w == nil || w.destroyed {57 if w == nil || w.destroyed {
@@ -114,7 +121,7 @@ func (w *WebView) CloseDevTools() error { return nil }
114 // RegisterScriptMessageHandler registers a callback invoked when the content script posts a message.121 // RegisterScriptMessageHandler registers a callback invoked when the content script posts a message.
115 func (w *WebView) RegisterScriptMessageHandler(cb func(payload string)) { w.msgHandler = cb }122 func (w *WebView) RegisterScriptMessageHandler(cb func(payload string)) { w.msgHandler = cb }
116 123
117-func (w *WebView) RegisterPopupHandler(cb func(string) bool) { w.popupHandler = cb }124+func (w *WebView) RegisterPopupHandler(cb func(string) *WebView) { w.popupHandler = cb }
118 125
119 func (w *WebView) dispatchScriptMessage(payload string) { //nolint:unused // Called from CGO WebKit callbacks126 func (w *WebView) dispatchScriptMessage(payload string) { //nolint:unused // Called from CGO WebKit callbacks
120 if w != nil && w.msgHandler != nil {127 if w != nil && w.msgHandler != nil {
@@ -122,11 +129,16 @@ func (w *WebView) dispatchScriptMessage(payload string) { //nolint:unused // Cal
122 }129 }
123 }130 }
124 131
125-func (w *WebView) dispatchPopupRequest(uri string) bool { //nolint:unused // Called from CGO WebKit callbacks132+func (w *WebView) dispatchPopupRequest(uri string) *WebView { //nolint:unused // Called from CGO WebKit callbacks
126 if w != nil && w.popupHandler != nil {133 if w != nil && w.popupHandler != nil {
127 return w.popupHandler(uri)134 return w.popupHandler(uri)
128 }135 }
129- return false136+ return nil
137+}
138+
139+// GetNativePointer returns nil for non-CGO build (stub implementation)
140+func (w *WebView) GetNativePointer() unsafe.Pointer { //nolint:unused // Needed for interface compatibility
141+ return nil
130 }142 }
131 143
132 // RegisterTitleChangedHandler registers a callback invoked when the page title changes.144 // RegisterTitleChangedHandler registers a callback invoked when the page title changes.
M pkg/webkit/webview_cgo.go +225 -27
@@ -22,8 +22,9 @@ static GtkWidget* new_window() { return GTK_WIDGET(gtk_window_new()); }
2222 // Forward declaration
2323 extern void goQuitMainLoop();
2424 extern void goInvokeHandle(uintptr_t handle);
25-extern gboolean goOnPopupRequest(unsigned long id, char* uri);
26-
25+extern gboolean goHandleNewWindowPolicy(unsigned long id, char* uri, int nav_type);
26+extern void goHandlePopupGeometry(unsigned long id, int x, int y, int width, int height);
27+extern GtkWidget* goHandleCreateWebView(unsigned long id, char* uri);
2728 static gboolean invoke_handle_cb(gpointer data) {
2829 goInvokeHandle((uintptr_t)data);
2930 return G_SOURCE_REMOVE;
@@ -52,29 +53,79 @@ static gboolean on_load_failed_with_tls_errors(WebKitWebView *web_view,
5253 GTlsCertificateFlags errors,
5354 gpointer user_data);
5455
55-// Handle create signal for _blank links - delegate to Go workspace manager
56-static WebKitWebView* on_create_new_window(WebKitWebView* web_view,
57- WebKitNavigationAction* navigation_action,
58- gpointer user_data) {
59- unsigned long view_id = (unsigned long)user_data;
60- const gchar* uri = NULL;
61-
62- // Get the request from navigation action
63- WebKitURIRequest* request = webkit_navigation_action_get_request(navigation_action);
64- if (request) {
65- uri = webkit_uri_request_get_uri(request);
56+// Handle ready-to-show signal for popup windows to properly initialize window properties
57+static void on_popup_ready_to_show(WebKitWebView* web_view, gpointer user_data) {
58+ // Get window properties to ensure they're properly initialized
59+ WebKitWindowProperties* props = webkit_web_view_get_window_properties(web_view);
60+ if (props) {
61+ // Access properties to ensure WindowFeatures are initialized
62+ GdkRectangle geometry;
63+ webkit_window_properties_get_geometry(props, &geometry);
64+
65+ // Pass geometry to Go for workspace positioning
66+ unsigned long view_id = (unsigned long)user_data;
67+ goHandlePopupGeometry(view_id, geometry.x, geometry.y,
68+ geometry.width, geometry.height);
6669 }
70+}
6771
68- gboolean handled = goOnPopupRequest(view_id, (char*)uri);
72+// Handle create signal for popup windows - essential for window.open() support
73+static GtkWidget* on_create_web_view(WebKitWebView *web_view,
74+ WebKitNavigationAction *navigation_action,
75+ gpointer user_data) {
76+ WebKitURIRequest *request = webkit_navigation_action_get_request(navigation_action);
77+ const char* uri = webkit_uri_request_get_uri(request);
78+
79+ printf("[webkit-create] Create signal for URI: %s\n", uri ? uri : "NULL");
80+
81+ unsigned long view_id = (unsigned long)user_data;
82+ GtkWidget* new_webview = goHandleCreateWebView(view_id, (char*)uri);
6983
70- if (!handled && uri) {
71- // Load in current window instead of creating new one
72- webkit_web_view_load_uri(web_view, uri);
84+ if (new_webview) {
85+ printf("[webkit-create] Go handler provided WebView - using workspace pane\n");
86+ return new_webview;
87+ } else {
88+ printf("[webkit-create] Go handler declined - allowing native popup window\n");
89+ return NULL; // Let WebKit create native popup window
7390 }
91+}
7492
75- // Return NULL to prevent new window creation
76- return NULL;
93+// Handle decide-policy signal for new window requests - more robust than create signal
94+static gboolean on_decide_policy(WebKitWebView *web_view,
95+ WebKitPolicyDecision *decision,
96+ WebKitPolicyDecisionType type,
97+ gpointer user_data) {
98+ // Debug: log all policy decisions
99+ const char* type_names[] = {"NAVIGATION_ACTION", "NEW_WINDOW_ACTION", "RESPONSE"};
100+ const char* type_name = (type >= 0 && type < 3) ? type_names[type] : "UNKNOWN";
101+ printf("[webkit-policy] Policy decision: type=%s (%d)\n", type_name, type);
102+
103+ if (type == WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION) {
104+ WebKitNavigationPolicyDecision *nav_decision =
105+ WEBKIT_NAVIGATION_POLICY_DECISION(decision);
106+ WebKitNavigationAction *action =
107+ webkit_navigation_policy_decision_get_navigation_action(nav_decision);
108+ WebKitURIRequest *request =
109+ webkit_navigation_action_get_request(action);
110+ const char* uri = webkit_uri_request_get_uri(request);
111+
112+ // Get navigation type to understand how popup was triggered
113+ WebKitNavigationType nav_type = webkit_navigation_action_get_navigation_type(action);
114+
115+ // Call Go handler for popup creation
116+ unsigned long view_id = (unsigned long)user_data;
117+ gboolean handled = goHandleNewWindowPolicy(view_id, (char*)uri, nav_type);
118+
119+ if (handled) {
120+ webkit_policy_decision_ignore(decision);
121+ } else {
122+ webkit_policy_decision_use(decision);
123+ }
124+ return TRUE;
125+ }
126+ return FALSE;
77127 }
128+
78129 static void connect_tls_error_handler(WebKitWebView* wv);
79130
80131
@@ -162,9 +213,46 @@ static GtkWidget* new_webview_with_ucm_and_session(const char* data_dir, const c
162213 return w;
163214 }
164215
165-static void connect_create_handler(WebKitWebView* web_view, unsigned long id) {
216+// Create a new WebView related to an existing one for process sharing
217+static GtkWidget* new_webview_related(WebKitWebView* related_view, WebKitUserContentManager** out_ucm) {
218+ if (!related_view) {
219+ // Fallback to regular WebView creation - we need to provide the required parameters
220+ return new_webview_with_ucm_and_session(NULL, NULL, NULL, out_ucm, NULL);
221+ }
222+
223+ // Get the network session and user content manager from the related view
224+ WebKitNetworkSession* related_session = webkit_web_view_get_network_session(related_view);
225+ WebKitUserContentManager* related_ucm = webkit_web_view_get_user_content_manager(related_view);
226+
227+ // Create content manager if needed
228+ WebKitUserContentManager* u = related_ucm;
229+ if (!u) {
230+ u = webkit_user_content_manager_new();
231+ } else {
232+ g_object_ref(u); // Add ref since we'll pass it to new WebView
233+ }
234+
235+ // Create WebView with same session and content manager for process sharing
236+ GtkWidget* w = GTK_WIDGET(g_object_new(WEBKIT_TYPE_WEB_VIEW,
237+ "user-content-manager", u,
238+ "network-session", related_session,
239+ "related-view", related_view,
240+ NULL));
241+
242+ // Connect TLS error handler
243+ connect_tls_error_handler(WEBKIT_WEB_VIEW(w));
244+
245+ // WebView holds refs to provided objects; drop our temporary refs
246+ g_object_unref(u);
247+ if (out_ucm) { *out_ucm = u; }
248+ return w;
249+}
250+
251+static void connect_policy_handler(WebKitWebView* web_view, unsigned long id) {
166252 if (!web_view) return;
167- g_signal_connect_data(G_OBJECT(web_view), "create", G_CALLBACK(on_create_new_window), (gpointer)id, NULL, 0);
253+ g_signal_connect_data(G_OBJECT(web_view), "decide-policy", G_CALLBACK(on_decide_policy), (gpointer)id, NULL, 0);
254+ // Connect create signal for popup window support
255+ g_signal_connect_data(G_OBJECT(web_view), "create", G_CALLBACK(on_create_web_view), (gpointer)id, NULL, 0);
168256 }
169257
170258 static gboolean gtk_prefers_dark() {
@@ -944,8 +1032,8 @@ type WebView struct {
9441032 titleHandler func(title string)
9451033 uriHandler func(uri string)
9461034 zoomHandler func(level float64)
947- popupHandler func(string) bool
948- memStats *memoryStats
1035+ popupHandler func(string) *WebView
1036+ memStats *memoryStats
9491037 gcTicker *time.Ticker
9501038 gcDone chan struct{}
9511039 tlsExceptions map[string]bool // host -> whether user allowed certificate exception
@@ -1108,7 +1196,7 @@ func NewWebView(cfg *Config) (*WebView, error) {
11081196 // Assign an ID for accelerator dispatch
11091197 v.id = nextViewID()
11101198 registerView(v.id, v)
1111- C.connect_create_handler(v.native.wv, C.ulong(v.id))
1199+ C.connect_policy_handler(v.native.wv, C.ulong(v.id))
11121200
11131201 // Register with global memory manager if monitoring is enabled
11141202 if cfg.Memory.EnableMemoryMonitoring {
@@ -1252,6 +1340,13 @@ func NewWebView(cfg *Config) (*WebView, error) {
12521340 }
12531341 }
12541342
1343+ // Enable JavaScript popup windows for workspace popup handling
1344+ settings := C.webkit_web_view_get_settings(v.native.wv)
1345+ if settings != nil {
1346+ C.webkit_settings_set_javascript_can_open_windows_automatically(settings, C.gboolean(1))
1347+ log.Printf("[webkit] Enabled JavaScript popup windows")
1348+ }
1349+
12551350 // Apply custom User-Agent for codec negotiation
12561351 if settings := C.webkit_web_view_get_settings(v.native.wv); settings != nil {
12571352 if cfg.CodecPreferences.CustomUserAgent != "" {
@@ -1317,6 +1412,101 @@ func NewWebView(cfg *Config) (*WebView, error) {
13171412 return v, nil
13181413 }
13191414
1415+// NewWebViewWithRelated creates a new WebView related to an existing one for process sharing
1416+func NewWebViewWithRelated(cfg *Config, relatedView *WebView) (*WebView, error) {
1417+ if relatedView == nil || relatedView.native == nil || relatedView.native.wv == nil {
1418+ // Fallback to regular WebView creation
1419+ return NewWebView(cfg)
1420+ }
1421+
1422+ log.Printf("[webkit] Creating related WebView for process sharing (CGO)")
1423+ if C.gtk_init_check() == 0 {
1424+ return nil, errors.New("failed to initialize GTK")
1425+ }
1426+
1427+ if cfg == nil {
1428+ cfg = &Config{}
1429+ }
1430+
1431+ // Create WebView related to the existing one
1432+ var createdUcm *C.WebKitUserContentManager
1433+ viewWidget := C.new_webview_related(relatedView.native.wv, &createdUcm)
1434+ if viewWidget == nil {
1435+ return nil, errors.New("failed to create related WebKitWebView")
1436+ }
1437+
1438+ var win *C.GtkWidget
1439+ var window *Window
1440+ container := C.gtk_box_new(C.GtkOrientation(C.GTK_ORIENTATION_VERTICAL), 0)
1441+ if container == nil {
1442+ return nil, errors.New("failed to create GtkBox container")
1443+ }
1444+
1445+ C.gtk_box_append((*C.GtkBox)(unsafe.Pointer(container)), viewWidget)
1446+
1447+ if cfg.CreateWindow {
1448+ win = C.new_window()
1449+ if win == nil {
1450+ return nil, errors.New("failed to create native window")
1451+ }
1452+ C.connect_destroy_quit(win)
1453+ C.gtk_window_set_child((*C.GtkWindow)(unsafe.Pointer(win)), container)
1454+ window = &Window{win: win}
1455+ } else {
1456+ window = &Window{}
1457+ }
1458+
1459+ v := &WebView{
1460+ config: cfg,
1461+ window: window,
1462+ zoom: cfg.ZoomDefault,
1463+ native: &nativeView{
1464+ win: win,
1465+ container: container,
1466+ view: viewWidget,
1467+ wv: (*C.WebKitWebView)(unsafe.Pointer(viewWidget)),
1468+ ucm: createdUcm,
1469+ },
1470+ useDomZoom: cfg.UseDomZoom,
1471+ domZoomSeed: cfg.ZoomDefault,
1472+ gcDone: make(chan struct{}),
1473+ tlsExceptions: make(map[string]bool),
1474+ }
1475+
1476+ // Enable JavaScript popup windows for workspace popup handling
1477+ settings := C.webkit_web_view_get_settings(v.native.wv)
1478+ if settings != nil {
1479+ C.webkit_settings_set_javascript_can_open_windows_automatically(settings, C.gboolean(1))
1480+ log.Printf("[webkit] Enabled JavaScript popup windows for related WebView")
1481+ }
1482+
1483+ // Assign an ID for accelerator dispatch
1484+ v.id = nextViewID()
1485+ registerView(v.id, v)
1486+ C.connect_policy_handler(v.native.wv, C.ulong(v.id))
1487+
1488+ // Register with memory manager if enabled
1489+ if cfg.Memory.EnableMemoryMonitoring {
1490+ if globalMemoryManager == nil {
1491+ InitializeGlobalMemoryManager(
1492+ cfg.Memory.EnableMemoryMonitoring,
1493+ cfg.Memory.ProcessRecycleThreshold,
1494+ 30*time.Second,
1495+ )
1496+ }
1497+ if globalMemoryManager != nil {
1498+ globalMemoryManager.RegisterWebView(v)
1499+ }
1500+ }
1501+
1502+ if cfg.ZoomDefault <= 0 {
1503+ v.zoom = 1.0
1504+ }
1505+ v.domZoomSeed = v.zoom
1506+
1507+ return v, nil
1508+}
1509+
13201510 func (w *WebView) LoadURL(rawURL string) error {
13211511 if w == nil || w.destroyed || w.native == nil || w.native.wv == nil {
13221512 return ErrNotImplemented
@@ -1483,7 +1673,7 @@ func (w *WebView) ReloadBypassCache() error {
14831673 // RegisterScriptMessageHandler registers a callback invoked when the content script posts a message.
14841674 func (w *WebView) RegisterScriptMessageHandler(cb func(payload string)) { w.msgHandler = cb }
14851675
1486-func (w *WebView) RegisterPopupHandler(cb func(string) bool) { w.popupHandler = cb }
1676+func (w *WebView) RegisterPopupHandler(cb func(string) *WebView) { w.popupHandler = cb }
14871677
14881678 func (w *WebView) dispatchScriptMessage(payload string) {
14891679 if w != nil && w.msgHandler != nil {
@@ -1491,11 +1681,19 @@ func (w *WebView) dispatchScriptMessage(payload string) {
14911681 }
14921682 }
14931683
1494-func (w *WebView) dispatchPopupRequest(uri string) bool {
1684+func (w *WebView) dispatchPopupRequest(uri string) *WebView {
14951685 if w != nil && w.popupHandler != nil {
14961686 return w.popupHandler(uri)
14971687 }
1498- return false
1688+ return nil
1689+}
1690+
1691+// GetNativePointer returns the native WebView pointer for CGO callbacks
1692+func (w *WebView) GetNativePointer() unsafe.Pointer {
1693+ if w != nil && w.native != nil && w.native.wv != nil {
1694+ return unsafe.Pointer(w.native.wv)
1695+ }
1696+ return nil
14991697 }
15001698
15011699 // RegisterTitleChangedHandler registers a callback invoked when the page title changes.
@@ -22,8 +22,9 @@ static GtkWidget* new_window() { return GTK_WIDGET(gtk_window_new()); }
22 // Forward declaration22 // Forward declaration
23 extern void goQuitMainLoop();23 extern void goQuitMainLoop();
24 extern void goInvokeHandle(uintptr_t handle);24 extern void goInvokeHandle(uintptr_t handle);
25-extern gboolean goOnPopupRequest(unsigned long id, char* uri);25+extern gboolean goHandleNewWindowPolicy(unsigned long id, char* uri, int nav_type);
26-26+extern void goHandlePopupGeometry(unsigned long id, int x, int y, int width, int height);
27+extern GtkWidget* goHandleCreateWebView(unsigned long id, char* uri);
27 static gboolean invoke_handle_cb(gpointer data) {28 static gboolean invoke_handle_cb(gpointer data) {
28 goInvokeHandle((uintptr_t)data);29 goInvokeHandle((uintptr_t)data);
29 return G_SOURCE_REMOVE;30 return G_SOURCE_REMOVE;
@@ -52,29 +53,79 @@ static gboolean on_load_failed_with_tls_errors(WebKitWebView *web_view,
52 GTlsCertificateFlags errors,53 GTlsCertificateFlags errors,
53 gpointer user_data);54 gpointer user_data);
54 55
55-// Handle create signal for _blank links - delegate to Go workspace manager56+// Handle ready-to-show signal for popup windows to properly initialize window properties
56-static WebKitWebView* on_create_new_window(WebKitWebView* web_view,57+static void on_popup_ready_to_show(WebKitWebView* web_view, gpointer user_data) {
57- WebKitNavigationAction* navigation_action,58+ // Get window properties to ensure they're properly initialized
58- gpointer user_data) {59+ WebKitWindowProperties* props = webkit_web_view_get_window_properties(web_view);
59- unsigned long view_id = (unsigned long)user_data;60+ if (props) {
60- const gchar* uri = NULL;61+ // Access properties to ensure WindowFeatures are initialized
61-62+ GdkRectangle geometry;
62- // Get the request from navigation action63+ webkit_window_properties_get_geometry(props, &geometry);
63- WebKitURIRequest* request = webkit_navigation_action_get_request(navigation_action);64+
64- if (request) {65+ // Pass geometry to Go for workspace positioning
65- uri = webkit_uri_request_get_uri(request);66+ unsigned long view_id = (unsigned long)user_data;
67+ goHandlePopupGeometry(view_id, geometry.x, geometry.y,
68+ geometry.width, geometry.height);
66 }69 }
70+}
67 71
68- gboolean handled = goOnPopupRequest(view_id, (char*)uri);72+// Handle create signal for popup windows - essential for window.open() support
73+static GtkWidget* on_create_web_view(WebKitWebView *web_view,
74+ WebKitNavigationAction *navigation_action,
75+ gpointer user_data) {
76+ WebKitURIRequest *request = webkit_navigation_action_get_request(navigation_action);
77+ const char* uri = webkit_uri_request_get_uri(request);
78+
79+ printf("[webkit-create] Create signal for URI: %s\n", uri ? uri : "NULL");
80+
81+ unsigned long view_id = (unsigned long)user_data;
82+ GtkWidget* new_webview = goHandleCreateWebView(view_id, (char*)uri);
69 83
70- if (!handled && uri) {84+ if (new_webview) {
71- // Load in current window instead of creating new one85+ printf("[webkit-create] Go handler provided WebView - using workspace pane\n");
72- webkit_web_view_load_uri(web_view, uri);86+ return new_webview;
87+ } else {
88+ printf("[webkit-create] Go handler declined - allowing native popup window\n");
89+ return NULL; // Let WebKit create native popup window
73 }90 }
91+}
74 92
75- // Return NULL to prevent new window creation93+// Handle decide-policy signal for new window requests - more robust than create signal
76- return NULL;94+static gboolean on_decide_policy(WebKitWebView *web_view,
95+ WebKitPolicyDecision *decision,
96+ WebKitPolicyDecisionType type,
97+ gpointer user_data) {
98+ // Debug: log all policy decisions
99+ const char* type_names[] = {"NAVIGATION_ACTION", "NEW_WINDOW_ACTION", "RESPONSE"};
100+ const char* type_name = (type >= 0 && type < 3) ? type_names[type] : "UNKNOWN";
101+ printf("[webkit-policy] Policy decision: type=%s (%d)\n", type_name, type);
102+
103+ if (type == WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION) {
104+ WebKitNavigationPolicyDecision *nav_decision =
105+ WEBKIT_NAVIGATION_POLICY_DECISION(decision);
106+ WebKitNavigationAction *action =
107+ webkit_navigation_policy_decision_get_navigation_action(nav_decision);
108+ WebKitURIRequest *request =
109+ webkit_navigation_action_get_request(action);
110+ const char* uri = webkit_uri_request_get_uri(request);
111+
112+ // Get navigation type to understand how popup was triggered
113+ WebKitNavigationType nav_type = webkit_navigation_action_get_navigation_type(action);
114+
115+ // Call Go handler for popup creation
116+ unsigned long view_id = (unsigned long)user_data;
117+ gboolean handled = goHandleNewWindowPolicy(view_id, (char*)uri, nav_type);
118+
119+ if (handled) {
120+ webkit_policy_decision_ignore(decision);
121+ } else {
122+ webkit_policy_decision_use(decision);
123+ }
124+ return TRUE;
125+ }
126+ return FALSE;
77 }127 }
128+
78 static void connect_tls_error_handler(WebKitWebView* wv);129 static void connect_tls_error_handler(WebKitWebView* wv);
79 130
80 131
@@ -162,9 +213,46 @@ static GtkWidget* new_webview_with_ucm_and_session(const char* data_dir, const c
162 return w;213 return w;
163 }214 }
164 215
165-static void connect_create_handler(WebKitWebView* web_view, unsigned long id) {216+// Create a new WebView related to an existing one for process sharing
217+static GtkWidget* new_webview_related(WebKitWebView* related_view, WebKitUserContentManager** out_ucm) {
218+ if (!related_view) {
219+ // Fallback to regular WebView creation - we need to provide the required parameters
220+ return new_webview_with_ucm_and_session(NULL, NULL, NULL, out_ucm, NULL);
221+ }
222+
223+ // Get the network session and user content manager from the related view
224+ WebKitNetworkSession* related_session = webkit_web_view_get_network_session(related_view);
225+ WebKitUserContentManager* related_ucm = webkit_web_view_get_user_content_manager(related_view);
226+
227+ // Create content manager if needed
228+ WebKitUserContentManager* u = related_ucm;
229+ if (!u) {
230+ u = webkit_user_content_manager_new();
231+ } else {
232+ g_object_ref(u); // Add ref since we'll pass it to new WebView
233+ }
234+
235+ // Create WebView with same session and content manager for process sharing
236+ GtkWidget* w = GTK_WIDGET(g_object_new(WEBKIT_TYPE_WEB_VIEW,
237+ "user-content-manager", u,
238+ "network-session", related_session,
239+ "related-view", related_view,
240+ NULL));
241+
242+ // Connect TLS error handler
243+ connect_tls_error_handler(WEBKIT_WEB_VIEW(w));
244+
245+ // WebView holds refs to provided objects; drop our temporary refs
246+ g_object_unref(u);
247+ if (out_ucm) { *out_ucm = u; }
248+ return w;
249+}
250+
251+static void connect_policy_handler(WebKitWebView* web_view, unsigned long id) {
166 if (!web_view) return;252 if (!web_view) return;
167- g_signal_connect_data(G_OBJECT(web_view), "create", G_CALLBACK(on_create_new_window), (gpointer)id, NULL, 0);253+ g_signal_connect_data(G_OBJECT(web_view), "decide-policy", G_CALLBACK(on_decide_policy), (gpointer)id, NULL, 0);
254+ // Connect create signal for popup window support
255+ g_signal_connect_data(G_OBJECT(web_view), "create", G_CALLBACK(on_create_web_view), (gpointer)id, NULL, 0);
168 }256 }
169 257
170 static gboolean gtk_prefers_dark() {258 static gboolean gtk_prefers_dark() {
@@ -944,8 +1032,8 @@ type WebView struct {
944 titleHandler func(title string)1032 titleHandler func(title string)
945 uriHandler func(uri string)1033 uriHandler func(uri string)
946 zoomHandler func(level float64)1034 zoomHandler func(level float64)
947- popupHandler func(string) bool1035+ popupHandler func(string) *WebView
948- memStats *memoryStats1036+ memStats *memoryStats
949 gcTicker *time.Ticker1037 gcTicker *time.Ticker
950 gcDone chan struct{}1038 gcDone chan struct{}
951 tlsExceptions map[string]bool // host -> whether user allowed certificate exception1039 tlsExceptions map[string]bool // host -> whether user allowed certificate exception
@@ -1108,7 +1196,7 @@ func NewWebView(cfg *Config) (*WebView, error) {
1108 // Assign an ID for accelerator dispatch1196 // Assign an ID for accelerator dispatch
1109 v.id = nextViewID()1197 v.id = nextViewID()
1110 registerView(v.id, v)1198 registerView(v.id, v)
1111- C.connect_create_handler(v.native.wv, C.ulong(v.id))1199+ C.connect_policy_handler(v.native.wv, C.ulong(v.id))
1112 1200
1113 // Register with global memory manager if monitoring is enabled1201 // Register with global memory manager if monitoring is enabled
1114 if cfg.Memory.EnableMemoryMonitoring {1202 if cfg.Memory.EnableMemoryMonitoring {
@@ -1252,6 +1340,13 @@ func NewWebView(cfg *Config) (*WebView, error) {
1252 }1340 }
1253 }1341 }
1254 1342
1343+ // Enable JavaScript popup windows for workspace popup handling
1344+ settings := C.webkit_web_view_get_settings(v.native.wv)
1345+ if settings != nil {
1346+ C.webkit_settings_set_javascript_can_open_windows_automatically(settings, C.gboolean(1))
1347+ log.Printf("[webkit] Enabled JavaScript popup windows")
1348+ }
1349+
1255 // Apply custom User-Agent for codec negotiation1350 // Apply custom User-Agent for codec negotiation
1256 if settings := C.webkit_web_view_get_settings(v.native.wv); settings != nil {1351 if settings := C.webkit_web_view_get_settings(v.native.wv); settings != nil {
1257 if cfg.CodecPreferences.CustomUserAgent != "" {1352 if cfg.CodecPreferences.CustomUserAgent != "" {
@@ -1317,6 +1412,101 @@ func NewWebView(cfg *Config) (*WebView, error) {
1317 return v, nil1412 return v, nil
1318 }1413 }
1319 1414
1415+// NewWebViewWithRelated creates a new WebView related to an existing one for process sharing
1416+func NewWebViewWithRelated(cfg *Config, relatedView *WebView) (*WebView, error) {
1417+ if relatedView == nil || relatedView.native == nil || relatedView.native.wv == nil {
1418+ // Fallback to regular WebView creation
1419+ return NewWebView(cfg)
1420+ }
1421+
1422+ log.Printf("[webkit] Creating related WebView for process sharing (CGO)")
1423+ if C.gtk_init_check() == 0 {
1424+ return nil, errors.New("failed to initialize GTK")
1425+ }
1426+
1427+ if cfg == nil {
1428+ cfg = &Config{}
1429+ }
1430+
1431+ // Create WebView related to the existing one
1432+ var createdUcm *C.WebKitUserContentManager
1433+ viewWidget := C.new_webview_related(relatedView.native.wv, &createdUcm)
1434+ if viewWidget == nil {
1435+ return nil, errors.New("failed to create related WebKitWebView")
1436+ }
1437+
1438+ var win *C.GtkWidget
1439+ var window *Window
1440+ container := C.gtk_box_new(C.GtkOrientation(C.GTK_ORIENTATION_VERTICAL), 0)
1441+ if container == nil {
1442+ return nil, errors.New("failed to create GtkBox container")
1443+ }
1444+
1445+ C.gtk_box_append((*C.GtkBox)(unsafe.Pointer(container)), viewWidget)
1446+
1447+ if cfg.CreateWindow {
1448+ win = C.new_window()
1449+ if win == nil {
1450+ return nil, errors.New("failed to create native window")
1451+ }
1452+ C.connect_destroy_quit(win)
1453+ C.gtk_window_set_child((*C.GtkWindow)(unsafe.Pointer(win)), container)
1454+ window = &Window{win: win}
1455+ } else {
1456+ window = &Window{}
1457+ }
1458+
1459+ v := &WebView{
1460+ config: cfg,
1461+ window: window,
1462+ zoom: cfg.ZoomDefault,
1463+ native: &nativeView{
1464+ win: win,
1465+ container: container,
1466+ view: viewWidget,
1467+ wv: (*C.WebKitWebView)(unsafe.Pointer(viewWidget)),
1468+ ucm: createdUcm,
1469+ },
1470+ useDomZoom: cfg.UseDomZoom,
1471+ domZoomSeed: cfg.ZoomDefault,
1472+ gcDone: make(chan struct{}),
1473+ tlsExceptions: make(map[string]bool),
1474+ }
1475+
1476+ // Enable JavaScript popup windows for workspace popup handling
1477+ settings := C.webkit_web_view_get_settings(v.native.wv)
1478+ if settings != nil {
1479+ C.webkit_settings_set_javascript_can_open_windows_automatically(settings, C.gboolean(1))
1480+ log.Printf("[webkit] Enabled JavaScript popup windows for related WebView")
1481+ }
1482+
1483+ // Assign an ID for accelerator dispatch
1484+ v.id = nextViewID()
1485+ registerView(v.id, v)
1486+ C.connect_policy_handler(v.native.wv, C.ulong(v.id))
1487+
1488+ // Register with memory manager if enabled
1489+ if cfg.Memory.EnableMemoryMonitoring {
1490+ if globalMemoryManager == nil {
1491+ InitializeGlobalMemoryManager(
1492+ cfg.Memory.EnableMemoryMonitoring,
1493+ cfg.Memory.ProcessRecycleThreshold,
1494+ 30*time.Second,
1495+ )
1496+ }
1497+ if globalMemoryManager != nil {
1498+ globalMemoryManager.RegisterWebView(v)
1499+ }
1500+ }
1501+
1502+ if cfg.ZoomDefault <= 0 {
1503+ v.zoom = 1.0
1504+ }
1505+ v.domZoomSeed = v.zoom
1506+
1507+ return v, nil
1508+}
1509+
1320 func (w *WebView) LoadURL(rawURL string) error {1510 func (w *WebView) LoadURL(rawURL string) error {
1321 if w == nil || w.destroyed || w.native == nil || w.native.wv == nil {1511 if w == nil || w.destroyed || w.native == nil || w.native.wv == nil {
1322 return ErrNotImplemented1512 return ErrNotImplemented
@@ -1483,7 +1673,7 @@ func (w *WebView) ReloadBypassCache() error {
1483 // RegisterScriptMessageHandler registers a callback invoked when the content script posts a message.1673 // RegisterScriptMessageHandler registers a callback invoked when the content script posts a message.
1484 func (w *WebView) RegisterScriptMessageHandler(cb func(payload string)) { w.msgHandler = cb }1674 func (w *WebView) RegisterScriptMessageHandler(cb func(payload string)) { w.msgHandler = cb }
1485 1675
1486-func (w *WebView) RegisterPopupHandler(cb func(string) bool) { w.popupHandler = cb }1676+func (w *WebView) RegisterPopupHandler(cb func(string) *WebView) { w.popupHandler = cb }
1487 1677
1488 func (w *WebView) dispatchScriptMessage(payload string) {1678 func (w *WebView) dispatchScriptMessage(payload string) {
1489 if w != nil && w.msgHandler != nil {1679 if w != nil && w.msgHandler != nil {
@@ -1491,11 +1681,19 @@ func (w *WebView) dispatchScriptMessage(payload string) {
1491 }1681 }
1492 }1682 }
1493 1683
1494-func (w *WebView) dispatchPopupRequest(uri string) bool {1684+func (w *WebView) dispatchPopupRequest(uri string) *WebView {
1495 if w != nil && w.popupHandler != nil {1685 if w != nil && w.popupHandler != nil {
1496 return w.popupHandler(uri)1686 return w.popupHandler(uri)
1497 }1687 }
1498- return false1688+ return nil
1689+}
1690+
1691+// GetNativePointer returns the native WebView pointer for CGO callbacks
1692+func (w *WebView) GetNativePointer() unsafe.Pointer {
1693+ if w != nil && w.native != nil && w.native.wv != nil {
1694+ return unsafe.Pointer(w.native.wv)
1695+ }
1696+ return nil
1499 }1697 }
1500 1698
1501 // RegisterTitleChangedHandler registers a callback invoked when the page title changes.1699 // RegisterTitleChangedHandler registers a callback invoked when the page title changes.

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