detached 255e713

fix(webkit): prevent windowfeatures crash on blank target links

commit
255e713fc133474cbc03ace1ccc05c019ff17a93
author
bamen · · 2025-09-20 13:26 UTC
parent
dc10bc8
signature
unsigned
2 files · +267 -30
M pkg/webkit/keyboard_cgo.go +116 -12
@@ -438,7 +438,7 @@ func goOnURIChanged(id C.ulong, curi *C.char) {
438438 }
439439
440440 //export goHandleNewWindowPolicy
441-func goHandleNewWindowPolicy(id C.ulong, curi *C.char, navType C.int) C.gboolean {
441+func goHandleNewWindowPolicy(id C.ulong, curi *C.char, navType C.int, cframeName *C.char, isUserGesture C.gboolean, modifiers C.guint, mouseButton C.guint) C.gboolean {
442442 uid := uintptr(id)
443443 regMu.RLock()
444444 vw := viewByID[uid]
@@ -454,6 +454,11 @@ func goHandleNewWindowPolicy(id C.ulong, curi *C.char, navType C.int) C.gboolean
454454 uri = C.GoString(curi)
455455 }
456456
457+ frameName := ""
458+ if cframeName != nil {
459+ frameName = C.GoString(cframeName)
460+ }
461+
457462 // Navigation type constants from WebKit headers
458463 navTypeNames := map[int]string{
459464 0: "LINK_CLICKED",
@@ -469,23 +474,94 @@ func goHandleNewWindowPolicy(id C.ulong, curi *C.char, navType C.int) C.gboolean
469474 navTypeName = fmt.Sprintf("UNKNOWN(%d)", int(navType))
470475 }
471476
472- log.Printf("[webkit] Policy decision for new window: URI=%s, NavType=%s", uri, navTypeName)
477+ // Convert parameters for analysis
478+ userGesture := isUserGesture != 0
479+ mods := uint(modifiers)
480+ button := uint(mouseButton)
481+
482+ // Check for modifier keys that indicate new tab intent
483+ const (
484+ GDK_CONTROL_MASK = 0x04
485+ GDK_META_MASK = 0x10000000 // Command key on macOS
486+ )
487+ hasCtrlOrCmd := (mods&GDK_CONTROL_MASK != 0) || (mods&GDK_META_MASK != 0)
488+
489+ log.Printf("[webkit] Policy decision for new window: URI=%s, NavType=%s, FrameName=%s, UserGesture=%v, Modifiers=0x%x, Button=%d, CtrlOrCmd=%v",
490+ uri, navTypeName, frameName, userGesture, mods, button, hasCtrlOrCmd)
491+
492+ // Enhanced detection logic for _blank vs popup using reliable heuristics
493+ isRegularPane := false
494+
495+ // Criteria for treating as regular pane (new panel):
496+ // 1. Explicit _blank frame name (target="_blank" links)
497+ // 2. User gesture with Ctrl/Cmd+click (open in new panel intent)
498+ // 3. User-initiated OTHER navigation with no frame name (window.open("url", "_blank"))
499+ if frameName == "_blank" {
500+ isRegularPane = true
501+ log.Printf("[webkit] _blank frame detected - will handle as new panel")
502+ } else if userGesture && hasCtrlOrCmd && (navType == 0 || navType == 5) {
503+ // WEBKIT_NAVIGATION_TYPE_LINK_CLICKED = 0, WEBKIT_NAVIGATION_TYPE_OTHER = 5
504+ isRegularPane = true
505+ log.Printf("[webkit] Ctrl/Cmd+click detected - will handle as new panel")
506+ } else if userGesture && navType == 5 && frameName == "" {
507+ // window.open("url", "_blank") typically: user gesture + OTHER + no frame name
508+ isRegularPane = true
509+ log.Printf("[webkit] User-initiated window.open() detected - will handle as new panel")
510+ }
511+
512+ // Handle _blank targets by creating new panels directly (prevent WindowFeatures crash)
513+ if isRegularPane {
514+ log.Printf("[webkit] Creating new panel for _blank target: %s", uri)
515+
516+ // Create new panel through workspace manager using existing popup handler
517+ if vw.popupHandler != nil {
518+ // Call popup handler to create a new panel
519+ // The workspace manager will handle this as a regular panel based on URI markers
520+ vw.popupHandler(uri + "#__dumber_frame_blank")
521+ }
522+
523+ return 1 // TRUE - we handled it by creating a new panel
524+ }
473525
474- // Check if we have a popup handler and if popups are enabled
526+ // For actual popups, let the create signal handle it
475527 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)
528+ log.Printf("[webkit] Letting popup go through create signal for WindowFeatures handling: %s", uri)
529+ return 0 // FALSE - let WebKit call create signal for popup handling
530+ } else {
531+ log.Printf("[webkit] No popup handler registered - allowing native popup for: %s", uri)
532+ return 0 // FALSE - no handler, allow default popup
533+ }
534+}
535+
536+//export goNotifyWebViewCreated
537+func goNotifyWebViewCreated(parentID C.ulong, webViewPtr C.ulong, curi *C.char) {
538+ uid := uintptr(parentID)
539+ regMu.RLock()
540+ parentView := viewByID[uid]
541+ regMu.RUnlock()
542+
543+ if parentView == nil {
544+ log.Printf("[webkit] Notify WebView created: no parent WebView found for ID %d", uid)
545+ return
546+ }
547+
548+ uri := ""
549+ if curi != nil {
550+ uri = C.GoString(curi)
551+ }
552+
553+ log.Printf("[webkit] Notify WebView created: parent_id=%d, webview_ptr=%d, uri=%s", uid, uintptr(webViewPtr), uri)
554+
555+ // Handle the new WebView through the popup handler
556+ if parentView.popupHandler != nil {
557+ result := parentView.popupHandler(uri)
479558 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
559+ log.Printf("[webkit] Popup handler accepted WebView for: %s", uri)
482560 } 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
561+ log.Printf("[webkit] Popup handler declined WebView for: %s", uri)
485562 }
486563 } else {
487- log.Printf("[webkit] No popup handler registered - allowing native popup for: %s", uri)
488- return 0 // FALSE - no handler, allow default popup
564+ log.Printf("[webkit] No popup handler registered for WebView notification")
489565 }
490566 }
491567
@@ -566,6 +642,34 @@ func goHandleWebViewClose(id C.ulong) {
566642 }
567643 }
568644
645+//export goCreateNewPanel
646+func goCreateNewPanel(id C.ulong, curi *C.char) {
647+ uid := uintptr(id)
648+ regMu.RLock()
649+ vw := viewByID[uid]
650+ regMu.RUnlock()
651+
652+ if vw == nil {
653+ log.Printf("[webkit] Create panel: no WebView found for ID %d", uid)
654+ return
655+ }
656+
657+ uri := ""
658+ if curi != nil {
659+ uri = C.GoString(curi)
660+ }
661+
662+ log.Printf("[webkit] Creating new panel for _blank target: %s", uri)
663+
664+ // Create new panel through popup handler if available
665+ if vw.popupHandler != nil {
666+ vw.popupHandler(uri)
667+ log.Printf("[webkit] New panel creation handled by popup handler")
668+ } else {
669+ log.Printf("[webkit] No popup handler available for panel creation")
670+ }
671+}
672+
569673 //export goHandlePopupGeometry
570674 func goHandlePopupGeometry(id C.ulong, x, y, width, height C.int) {
571675 // Store geometry for workspace manager to use
@@ -438,7 +438,7 @@ func goOnURIChanged(id C.ulong, curi *C.char) {
438 }438 }
439 439
440 //export goHandleNewWindowPolicy440 //export goHandleNewWindowPolicy
441-func goHandleNewWindowPolicy(id C.ulong, curi *C.char, navType C.int) C.gboolean {441+func goHandleNewWindowPolicy(id C.ulong, curi *C.char, navType C.int, cframeName *C.char, isUserGesture C.gboolean, modifiers C.guint, mouseButton C.guint) C.gboolean {
442 uid := uintptr(id)442 uid := uintptr(id)
443 regMu.RLock()443 regMu.RLock()
444 vw := viewByID[uid]444 vw := viewByID[uid]
@@ -454,6 +454,11 @@ func goHandleNewWindowPolicy(id C.ulong, curi *C.char, navType C.int) C.gboolean
454 uri = C.GoString(curi)454 uri = C.GoString(curi)
455 }455 }
456 456
457+ frameName := ""
458+ if cframeName != nil {
459+ frameName = C.GoString(cframeName)
460+ }
461+
457 // Navigation type constants from WebKit headers462 // Navigation type constants from WebKit headers
458 navTypeNames := map[int]string{463 navTypeNames := map[int]string{
459 0: "LINK_CLICKED",464 0: "LINK_CLICKED",
@@ -469,23 +474,94 @@ func goHandleNewWindowPolicy(id C.ulong, curi *C.char, navType C.int) C.gboolean
469 navTypeName = fmt.Sprintf("UNKNOWN(%d)", int(navType))474 navTypeName = fmt.Sprintf("UNKNOWN(%d)", int(navType))
470 }475 }
471 476
472- log.Printf("[webkit] Policy decision for new window: URI=%s, NavType=%s", uri, navTypeName)477+ // Convert parameters for analysis
478+ userGesture := isUserGesture != 0
479+ mods := uint(modifiers)
480+ button := uint(mouseButton)
481+
482+ // Check for modifier keys that indicate new tab intent
483+ const (
484+ GDK_CONTROL_MASK = 0x04
485+ GDK_META_MASK = 0x10000000 // Command key on macOS
486+ )
487+ hasCtrlOrCmd := (mods&GDK_CONTROL_MASK != 0) || (mods&GDK_META_MASK != 0)
488+
489+ log.Printf("[webkit] Policy decision for new window: URI=%s, NavType=%s, FrameName=%s, UserGesture=%v, Modifiers=0x%x, Button=%d, CtrlOrCmd=%v",
490+ uri, navTypeName, frameName, userGesture, mods, button, hasCtrlOrCmd)
491+
492+ // Enhanced detection logic for _blank vs popup using reliable heuristics
493+ isRegularPane := false
494+
495+ // Criteria for treating as regular pane (new panel):
496+ // 1. Explicit _blank frame name (target="_blank" links)
497+ // 2. User gesture with Ctrl/Cmd+click (open in new panel intent)
498+ // 3. User-initiated OTHER navigation with no frame name (window.open("url", "_blank"))
499+ if frameName == "_blank" {
500+ isRegularPane = true
501+ log.Printf("[webkit] _blank frame detected - will handle as new panel")
502+ } else if userGesture && hasCtrlOrCmd && (navType == 0 || navType == 5) {
503+ // WEBKIT_NAVIGATION_TYPE_LINK_CLICKED = 0, WEBKIT_NAVIGATION_TYPE_OTHER = 5
504+ isRegularPane = true
505+ log.Printf("[webkit] Ctrl/Cmd+click detected - will handle as new panel")
506+ } else if userGesture && navType == 5 && frameName == "" {
507+ // window.open("url", "_blank") typically: user gesture + OTHER + no frame name
508+ isRegularPane = true
509+ log.Printf("[webkit] User-initiated window.open() detected - will handle as new panel")
510+ }
511+
512+ // Handle _blank targets by creating new panels directly (prevent WindowFeatures crash)
513+ if isRegularPane {
514+ log.Printf("[webkit] Creating new panel for _blank target: %s", uri)
515+
516+ // Create new panel through workspace manager using existing popup handler
517+ if vw.popupHandler != nil {
518+ // Call popup handler to create a new panel
519+ // The workspace manager will handle this as a regular panel based on URI markers
520+ vw.popupHandler(uri + "#__dumber_frame_blank")
521+ }
522+
523+ return 1 // TRUE - we handled it by creating a new panel
524+ }
473 525
474- // Check if we have a popup handler and if popups are enabled526+ // For actual popups, let the create signal handle it
475 if vw.popupHandler != nil {527 if vw.popupHandler != nil {
476- log.Printf("[webkit] Calling popup handler for URI: %s", uri)528+ log.Printf("[webkit] Letting popup go through create signal for WindowFeatures handling: %s", uri)
477- // Call popup handler synchronously to get immediate result529+ return 0 // FALSE - let WebKit call create signal for popup handling
478- result := vw.popupHandler(uri)530+ } else {
531+ log.Printf("[webkit] No popup handler registered - allowing native popup for: %s", uri)
532+ return 0 // FALSE - no handler, allow default popup
533+ }
534+}
535+
536+//export goNotifyWebViewCreated
537+func goNotifyWebViewCreated(parentID C.ulong, webViewPtr C.ulong, curi *C.char) {
538+ uid := uintptr(parentID)
539+ regMu.RLock()
540+ parentView := viewByID[uid]
541+ regMu.RUnlock()
542+
543+ if parentView == nil {
544+ log.Printf("[webkit] Notify WebView created: no parent WebView found for ID %d", uid)
545+ return
546+ }
547+
548+ uri := ""
549+ if curi != nil {
550+ uri = C.GoString(curi)
551+ }
552+
553+ log.Printf("[webkit] Notify WebView created: parent_id=%d, webview_ptr=%d, uri=%s", uid, uintptr(webViewPtr), uri)
554+
555+ // Handle the new WebView through the popup handler
556+ if parentView.popupHandler != nil {
557+ result := parentView.popupHandler(uri)
479 if result != nil {558 if result != nil {
480- log.Printf("[webkit] Popup handler created new WebView for: %s - blocking default popup", uri)559+ log.Printf("[webkit] Popup handler accepted WebView for: %s", uri)
481- return 1 // TRUE - we handled it, block the default popup
482 } else {560 } else {
483- log.Printf("[webkit] Popup handler declined to create WebView for: %s - allowing native popup", uri)561+ log.Printf("[webkit] Popup handler declined WebView for: %s", uri)
484- return 0 // FALSE - we didn't handle it, allow default popup
485 }562 }
486 } else {563 } else {
487- log.Printf("[webkit] No popup handler registered - allowing native popup for: %s", uri)564+ log.Printf("[webkit] No popup handler registered for WebView notification")
488- return 0 // FALSE - no handler, allow default popup
489 }565 }
490 }566 }
491 567
@@ -566,6 +642,34 @@ func goHandleWebViewClose(id C.ulong) {
566 }642 }
567 }643 }
568 644
645+//export goCreateNewPanel
646+func goCreateNewPanel(id C.ulong, curi *C.char) {
647+ uid := uintptr(id)
648+ regMu.RLock()
649+ vw := viewByID[uid]
650+ regMu.RUnlock()
651+
652+ if vw == nil {
653+ log.Printf("[webkit] Create panel: no WebView found for ID %d", uid)
654+ return
655+ }
656+
657+ uri := ""
658+ if curi != nil {
659+ uri = C.GoString(curi)
660+ }
661+
662+ log.Printf("[webkit] Creating new panel for _blank target: %s", uri)
663+
664+ // Create new panel through popup handler if available
665+ if vw.popupHandler != nil {
666+ vw.popupHandler(uri)
667+ log.Printf("[webkit] New panel creation handled by popup handler")
668+ } else {
669+ log.Printf("[webkit] No popup handler available for panel creation")
670+ }
671+}
672+
569 //export goHandlePopupGeometry673 //export goHandlePopupGeometry
570 func goHandlePopupGeometry(id C.ulong, x, y, width, height C.int) {674 func goHandlePopupGeometry(id C.ulong, x, y, width, height C.int) {
571 // Store geometry for workspace manager to use675 // Store geometry for workspace manager to use
M pkg/webkit/webview_cgo.go +151 -18
@@ -22,11 +22,13 @@ 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 goHandleNewWindowPolicy(unsigned long id, char* uri, int nav_type);
25+extern gboolean goHandleNewWindowPolicy(unsigned long id, char* uri, int nav_type, char* frame_name, gboolean is_user_gesture, guint modifiers, guint mouse_button);
2626 extern void goHandlePopupGeometry(unsigned long id, int x, int y, int width, int height);
2727 extern GtkWidget* goHandleCreateWebView(unsigned long id, char* uri);
28+extern void goNotifyWebViewCreated(unsigned long parent_id, unsigned long webview_ptr, char* uri);
2829 extern void goHandleLoadChanged(unsigned long id, char* uri, int load_event);
2930 extern void goHandleWebViewClose(unsigned long id);
31+extern void goCreateNewPanel(unsigned long id, char* uri);
3032 static gboolean invoke_handle_cb(gpointer data) {
3133 goInvokeHandle((uintptr_t)data);
3234 return G_SOURCE_REMOVE;
@@ -57,17 +59,32 @@ static gboolean on_load_failed_with_tls_errors(WebKitWebView *web_view,
5759
5860 // Handle ready-to-show signal for popup windows to properly initialize window properties
5961 static void on_popup_ready_to_show(WebKitWebView* web_view, gpointer user_data) {
60- // Get window properties to ensure they're properly initialized
62+ printf("[webkit-ready] Ready-to-show signal for popup WebView\n");
63+
64+ // Get window properties - this should be safe for actual popups
6165 WebKitWindowProperties* props = webkit_web_view_get_window_properties(web_view);
6266 if (props) {
63- // Access properties to ensure WindowFeatures are initialized
64- GdkRectangle geometry;
65- webkit_window_properties_get_geometry(props, &geometry);
67+ // Try to access geometry safely for popup positioning
68+ GdkRectangle geometry = {0, 0, 800, 600}; // Default fallback
69+
70+ // Check if properties are actually available (defensive programming)
71+ if (G_IS_OBJECT(props)) {
72+ webkit_window_properties_get_geometry(props, &geometry);
73+ printf("[webkit-ready] Retrieved popup geometry: %dx%d at (%d,%d)\n",
74+ geometry.width, geometry.height, geometry.x, geometry.y);
75+ } else {
76+ printf("[webkit-ready] WindowProperties object invalid, using defaults\n");
77+ }
6678
6779 // Pass geometry to Go for workspace positioning
6880 unsigned long view_id = (unsigned long)user_data;
6981 goHandlePopupGeometry(view_id, geometry.x, geometry.y,
7082 geometry.width, geometry.height);
83+ } else {
84+ printf("[webkit-ready] No window properties available for popup\n");
85+ // Still notify Go layer with default geometry
86+ unsigned long view_id = (unsigned long)user_data;
87+ goHandlePopupGeometry(view_id, 0, 0, 800, 600);
7188 }
7289 }
7390
@@ -97,28 +114,130 @@ static GtkWidget* on_create_web_view(WebKitWebView *web_view,
97114 WebKitURIRequest *request = webkit_navigation_action_get_request(navigation_action);
98115 const char* uri = webkit_uri_request_get_uri(request);
99116
100- printf("[webkit-create] Create signal for URI: %s\n", uri ? uri : "NULL");
117+ // Extract additional navigation properties for enhanced _blank detection
118+ const char* frame_name = webkit_navigation_action_get_frame_name(navigation_action);
119+ gboolean is_user_gesture = webkit_navigation_action_is_user_gesture(navigation_action);
120+ guint modifiers = webkit_navigation_action_get_modifiers(navigation_action);
121+ WebKitNavigationType nav_type = webkit_navigation_action_get_navigation_type(navigation_action);
122+
123+ printf("[webkit-create] Create signal for URI: %s, FrameName: %s, UserGesture: %s, Modifiers: 0x%x, NavType: %d\n",
124+ uri ? uri : "NULL",
125+ frame_name ? frame_name : "NULL",
126+ is_user_gesture ? "true" : "false",
127+ modifiers,
128+ nav_type);
129+
130+ // Enhanced detection logic for _blank vs popup using reliable heuristics
131+ gboolean is_regular_pane = FALSE;
132+ const guint GDK_CONTROL_MASK = 0x04;
133+ const guint GDK_META_MASK = 0x10000000; // Command key on macOS
134+ gboolean has_ctrl_or_cmd = (modifiers & GDK_CONTROL_MASK) || (modifiers & GDK_META_MASK);
135+
136+ // Criteria for treating as regular pane:
137+ // 1. Explicit _blank frame name (target="_blank" links)
138+ // 2. User gesture with Ctrl/Cmd+click (open in new tab intent)
139+ // 3. User-initiated OTHER navigation with no frame name (window.open("url", "_blank"))
140+ if (frame_name && strcmp(frame_name, "_blank") == 0) {
141+ is_regular_pane = TRUE;
142+ printf("[webkit-create] _blank frame detected - will let WebKit handle natively to avoid WindowFeatures crash\n");
143+ } else if (is_user_gesture && has_ctrl_or_cmd && (nav_type == 0 || nav_type == 5)) {
144+ // WEBKIT_NAVIGATION_TYPE_LINK_CLICKED = 0, WEBKIT_NAVIGATION_TYPE_OTHER = 5
145+ is_regular_pane = TRUE;
146+ printf("[webkit-create] Ctrl/Cmd+click detected - will let WebKit handle natively\n");
147+ } else if (is_user_gesture && nav_type == 5 && (!frame_name || strlen(frame_name) == 0)) {
148+ // window.open("url", "_blank") typically: user gesture + OTHER + no frame name
149+ is_regular_pane = TRUE;
150+ printf("[webkit-create] User-initiated window.open() detected (nav_type=%d, frame_name=%s) - will let WebKit handle natively\n",
151+ nav_type, frame_name ? frame_name : "NULL");
152+ } else {
153+ // Default to popup for non-user gestures or other navigation types
154+ is_regular_pane = FALSE;
155+ printf("[webkit-create] Non-user gesture or other navigation type - treating as popup (user_gesture=%s, nav_type=%d, frame_name=%s)\n",
156+ is_user_gesture ? "true" : "false", nav_type, frame_name ? frame_name : "NULL");
157+ }
158+
159+ // Add marker to URI for workspace manager based on detection
160+ char* marked_uri = NULL;
161+ if (uri) {
162+ if (is_regular_pane) {
163+ // Add marker for regular pane
164+ size_t len = strlen(uri) + strlen("#__dumber_frame_blank") + 1;
165+ marked_uri = malloc(len);
166+ snprintf(marked_uri, len, "%s#__dumber_frame_blank", uri);
167+ printf("[webkit-create] Marked as regular pane: %s\n", marked_uri);
168+ } else {
169+ // Add marker for popup
170+ size_t len = strlen(uri) + strlen("#__dumber_frame_popup") + 1;
171+ marked_uri = malloc(len);
172+ snprintf(marked_uri, len, "%s#__dumber_frame_popup", uri);
173+ printf("[webkit-create] Marked as popup: %s\n", marked_uri);
174+ }
175+ }
176+
177+ // CRITICAL FIX: Return NULL for _blank targets to prevent WindowFeatures crash
178+ if (is_regular_pane) {
179+ // For _blank targets, return NULL to let WebKit handle it naturally
180+ // This prevents WebKit from trying to access uninitialized WindowFeatures
181+ printf("[webkit-create] _blank target detected - returning NULL to prevent WindowFeatures crash\n");
182+ printf("[webkit-create] Creating new panel directly for _blank target\n");
183+
184+ // Create new panel directly through Go layer
185+ unsigned long view_id = (unsigned long)user_data;
186+ goCreateNewPanel(view_id, marked_uri ? marked_uri : (char*)uri);
187+
188+ // Clean up allocated memory
189+ if (marked_uri) {
190+ free(marked_uri);
191+ }
101192
193+ return NULL; // Critical: Don't return a widget for _blank targets
194+ }
195+
196+ // Only create WebView for actual popups (window.open with features)
197+ printf("[webkit-create] Creating popup WebView with shared context from parent\n");
198+
199+ // Get the web context from the parent WebView to share settings and session
200+ WebKitWebContext* parent_context = webkit_web_view_get_context(web_view);
201+ WebKitSettings* parent_settings = webkit_web_view_get_settings(web_view);
202+ WebKitUserContentManager* parent_content_manager = webkit_web_view_get_user_content_manager(web_view);
203+
204+ // Create new WebView with same context, settings, and content manager as parent
205+ GtkWidget* new_webview = GTK_WIDGET(g_object_new(WEBKIT_TYPE_WEB_VIEW,
206+ "web-context", parent_context,
207+ "settings", parent_settings,
208+ "user-content-manager", parent_content_manager,
209+ NULL));
210+
211+ // Now inform Go layer about the new popup WebView for workspace management
102212 unsigned long view_id = (unsigned long)user_data;
103- GtkWidget* new_webview = goHandleCreateWebView(view_id, (char*)uri);
213+ goNotifyWebViewCreated(view_id, (unsigned long)new_webview, marked_uri ? marked_uri : (char*)uri);
214+
215+ // Clean up allocated memory
216+ if (marked_uri) {
217+ free(marked_uri);
218+ }
104219
105220 if (new_webview) {
106- printf("[webkit-create] Go handler provided WebView - using workspace pane\n");
221+ printf("[webkit-create] Created popup WebView - connecting popup management signals\n");
222+
223+ // Connect ready-to-show signal for popup WindowFeatures handling
224+ g_signal_connect(new_webview, "ready-to-show",
225+ G_CALLBACK(on_popup_ready_to_show), user_data);
226+ printf("[webkit-create] Connected ready-to-show signal for popup WindowFeatures\n");
107227
108- // Connect load_changed signal to the new popup WebView for OAuth detection
109228 WebKitWebView* popup_webview = WEBKIT_WEB_VIEW(new_webview);
110229 g_signal_connect_data(G_OBJECT(popup_webview), "load-changed",
111230 G_CALLBACK(on_load_changed), user_data, NULL, 0);
112- printf("[webkit-create] Connected load-changed signal to popup for OAuth detection\n");
231+ printf("[webkit-create] Connected load-changed signal to popup for navigation tracking\n");
113232
114- // Connect close signal to handle window.close() from OAuth providers
233+ // Connect close signal to handle window.close() from any popup (OAuth, ads, etc.)
115234 g_signal_connect_data(G_OBJECT(popup_webview), "close",
116235 G_CALLBACK(on_webview_close), user_data, NULL, 0);
117- printf("[webkit-create] Connected close signal to popup for OAuth auto-close\n");
236+ printf("[webkit-create] Connected close signal to popup for auto-close handling\n");
118237
119238 return new_webview;
120239 } else {
121- printf("[webkit-create] Go handler declined - allowing native popup window\n");
240+ printf("[webkit-create] Go handler declined popup - allowing native popup window\n");
122241 return NULL; // Let WebKit create native popup window
123242 }
124243 }
@@ -145,9 +264,18 @@ static gboolean on_decide_policy(WebKitWebView *web_view,
145264 // Get navigation type to understand how popup was triggered
146265 WebKitNavigationType nav_type = webkit_navigation_action_get_navigation_type(action);
147266
267+ // Get frame name to distinguish _blank from other popups
268+ const char* frame_name = webkit_navigation_action_get_frame_name(action);
269+
270+ // Get additional properties to better distinguish _blank from popups
271+ gboolean is_user_gesture = webkit_navigation_action_is_user_gesture(action);
272+ guint modifiers = webkit_navigation_action_get_modifiers(action);
273+ guint mouse_button = webkit_navigation_action_get_mouse_button(action);
274+
148275 // Call Go handler for popup creation
149276 unsigned long view_id = (unsigned long)user_data;
150- gboolean handled = goHandleNewWindowPolicy(view_id, (char*)uri, nav_type);
277+ gboolean handled = goHandleNewWindowPolicy(view_id, (char*)uri, nav_type, (char*)frame_name,
278+ is_user_gesture, modifiers, mouse_button);
151279
152280 if (handled) {
153281 webkit_policy_decision_ignore(decision);
@@ -284,7 +412,7 @@ static GtkWidget* new_webview_related(WebKitWebView* related_view, WebKitUserCon
284412 static void connect_policy_handler(WebKitWebView* web_view, unsigned long id) {
285413 if (!web_view) return;
286414 g_signal_connect_data(G_OBJECT(web_view), "decide-policy", G_CALLBACK(on_decide_policy), (gpointer)id, NULL, 0);
287- // Connect create signal for popup window support
415+ // Keep create signal for JavaScript window.open() and other cases not caught by decide-policy
288416 g_signal_connect_data(G_OBJECT(web_view), "create", G_CALLBACK(on_create_web_view), (gpointer)id, NULL, 0);
289417 }
290418
@@ -1072,9 +1200,9 @@ type WebView struct {
10721200 titleHandler func(title string)
10731201 uriHandler func(uri string)
10741202 zoomHandler func(level float64)
1075- popupHandler func(string) *WebView
1076- closeHandler func()
1077- memStats *memoryStats
1203+ popupHandler func(string) *WebView
1204+ closeHandler func()
1205+ memStats *memoryStats
10781206 gcTicker *time.Ticker
10791207 gcDone chan struct{}
10801208 tlsExceptions map[string]bool // host -> whether user allowed certificate exception
@@ -1545,6 +1673,11 @@ func NewWebViewWithRelated(cfg *Config, relatedView *WebView) (*WebView, error)
15451673 }
15461674 v.domZoomSeed = v.zoom
15471675
1676+ // Initialize UCM scripts and handlers (same as NewWebView)
1677+ if v.native != nil && v.native.ucm != nil {
1678+ v.enableUserContentManager(cfg)
1679+ }
1680+
15481681 return v, nil
15491682 }
15501683
@@ -22,11 +22,13 @@ 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 goHandleNewWindowPolicy(unsigned long id, char* uri, int nav_type);25+extern gboolean goHandleNewWindowPolicy(unsigned long id, char* uri, int nav_type, char* frame_name, gboolean is_user_gesture, guint modifiers, guint mouse_button);
26 extern void goHandlePopupGeometry(unsigned long id, int x, int y, int width, int height);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 extern GtkWidget* goHandleCreateWebView(unsigned long id, char* uri);
28+extern void goNotifyWebViewCreated(unsigned long parent_id, unsigned long webview_ptr, char* uri);
28 extern void goHandleLoadChanged(unsigned long id, char* uri, int load_event);29 extern void goHandleLoadChanged(unsigned long id, char* uri, int load_event);
29 extern void goHandleWebViewClose(unsigned long id);30 extern void goHandleWebViewClose(unsigned long id);
31+extern void goCreateNewPanel(unsigned long id, char* uri);
30 static gboolean invoke_handle_cb(gpointer data) {32 static gboolean invoke_handle_cb(gpointer data) {
31 goInvokeHandle((uintptr_t)data);33 goInvokeHandle((uintptr_t)data);
32 return G_SOURCE_REMOVE;34 return G_SOURCE_REMOVE;
@@ -57,17 +59,32 @@ static gboolean on_load_failed_with_tls_errors(WebKitWebView *web_view,
57 59
58 // Handle ready-to-show signal for popup windows to properly initialize window properties60 // Handle ready-to-show signal for popup windows to properly initialize window properties
59 static void on_popup_ready_to_show(WebKitWebView* web_view, gpointer user_data) {61 static void on_popup_ready_to_show(WebKitWebView* web_view, gpointer user_data) {
60- // Get window properties to ensure they're properly initialized62+ printf("[webkit-ready] Ready-to-show signal for popup WebView\n");
63+
64+ // Get window properties - this should be safe for actual popups
61 WebKitWindowProperties* props = webkit_web_view_get_window_properties(web_view);65 WebKitWindowProperties* props = webkit_web_view_get_window_properties(web_view);
62 if (props) {66 if (props) {
63- // Access properties to ensure WindowFeatures are initialized67+ // Try to access geometry safely for popup positioning
64- GdkRectangle geometry;68+ GdkRectangle geometry = {0, 0, 800, 600}; // Default fallback
65- webkit_window_properties_get_geometry(props, &geometry);69+
70+ // Check if properties are actually available (defensive programming)
71+ if (G_IS_OBJECT(props)) {
72+ webkit_window_properties_get_geometry(props, &geometry);
73+ printf("[webkit-ready] Retrieved popup geometry: %dx%d at (%d,%d)\n",
74+ geometry.width, geometry.height, geometry.x, geometry.y);
75+ } else {
76+ printf("[webkit-ready] WindowProperties object invalid, using defaults\n");
77+ }
66 78
67 // Pass geometry to Go for workspace positioning79 // Pass geometry to Go for workspace positioning
68 unsigned long view_id = (unsigned long)user_data;80 unsigned long view_id = (unsigned long)user_data;
69 goHandlePopupGeometry(view_id, geometry.x, geometry.y,81 goHandlePopupGeometry(view_id, geometry.x, geometry.y,
70 geometry.width, geometry.height);82 geometry.width, geometry.height);
83+ } else {
84+ printf("[webkit-ready] No window properties available for popup\n");
85+ // Still notify Go layer with default geometry
86+ unsigned long view_id = (unsigned long)user_data;
87+ goHandlePopupGeometry(view_id, 0, 0, 800, 600);
71 }88 }
72 }89 }
73 90
@@ -97,28 +114,130 @@ static GtkWidget* on_create_web_view(WebKitWebView *web_view,
97 WebKitURIRequest *request = webkit_navigation_action_get_request(navigation_action);114 WebKitURIRequest *request = webkit_navigation_action_get_request(navigation_action);
98 const char* uri = webkit_uri_request_get_uri(request);115 const char* uri = webkit_uri_request_get_uri(request);
99 116
100- printf("[webkit-create] Create signal for URI: %s\n", uri ? uri : "NULL");117+ // Extract additional navigation properties for enhanced _blank detection
118+ const char* frame_name = webkit_navigation_action_get_frame_name(navigation_action);
119+ gboolean is_user_gesture = webkit_navigation_action_is_user_gesture(navigation_action);
120+ guint modifiers = webkit_navigation_action_get_modifiers(navigation_action);
121+ WebKitNavigationType nav_type = webkit_navigation_action_get_navigation_type(navigation_action);
122+
123+ printf("[webkit-create] Create signal for URI: %s, FrameName: %s, UserGesture: %s, Modifiers: 0x%x, NavType: %d\n",
124+ uri ? uri : "NULL",
125+ frame_name ? frame_name : "NULL",
126+ is_user_gesture ? "true" : "false",
127+ modifiers,
128+ nav_type);
129+
130+ // Enhanced detection logic for _blank vs popup using reliable heuristics
131+ gboolean is_regular_pane = FALSE;
132+ const guint GDK_CONTROL_MASK = 0x04;
133+ const guint GDK_META_MASK = 0x10000000; // Command key on macOS
134+ gboolean has_ctrl_or_cmd = (modifiers & GDK_CONTROL_MASK) || (modifiers & GDK_META_MASK);
135+
136+ // Criteria for treating as regular pane:
137+ // 1. Explicit _blank frame name (target="_blank" links)
138+ // 2. User gesture with Ctrl/Cmd+click (open in new tab intent)
139+ // 3. User-initiated OTHER navigation with no frame name (window.open("url", "_blank"))
140+ if (frame_name && strcmp(frame_name, "_blank") == 0) {
141+ is_regular_pane = TRUE;
142+ printf("[webkit-create] _blank frame detected - will let WebKit handle natively to avoid WindowFeatures crash\n");
143+ } else if (is_user_gesture && has_ctrl_or_cmd && (nav_type == 0 || nav_type == 5)) {
144+ // WEBKIT_NAVIGATION_TYPE_LINK_CLICKED = 0, WEBKIT_NAVIGATION_TYPE_OTHER = 5
145+ is_regular_pane = TRUE;
146+ printf("[webkit-create] Ctrl/Cmd+click detected - will let WebKit handle natively\n");
147+ } else if (is_user_gesture && nav_type == 5 && (!frame_name || strlen(frame_name) == 0)) {
148+ // window.open("url", "_blank") typically: user gesture + OTHER + no frame name
149+ is_regular_pane = TRUE;
150+ printf("[webkit-create] User-initiated window.open() detected (nav_type=%d, frame_name=%s) - will let WebKit handle natively\n",
151+ nav_type, frame_name ? frame_name : "NULL");
152+ } else {
153+ // Default to popup for non-user gestures or other navigation types
154+ is_regular_pane = FALSE;
155+ printf("[webkit-create] Non-user gesture or other navigation type - treating as popup (user_gesture=%s, nav_type=%d, frame_name=%s)\n",
156+ is_user_gesture ? "true" : "false", nav_type, frame_name ? frame_name : "NULL");
157+ }
158+
159+ // Add marker to URI for workspace manager based on detection
160+ char* marked_uri = NULL;
161+ if (uri) {
162+ if (is_regular_pane) {
163+ // Add marker for regular pane
164+ size_t len = strlen(uri) + strlen("#__dumber_frame_blank") + 1;
165+ marked_uri = malloc(len);
166+ snprintf(marked_uri, len, "%s#__dumber_frame_blank", uri);
167+ printf("[webkit-create] Marked as regular pane: %s\n", marked_uri);
168+ } else {
169+ // Add marker for popup
170+ size_t len = strlen(uri) + strlen("#__dumber_frame_popup") + 1;
171+ marked_uri = malloc(len);
172+ snprintf(marked_uri, len, "%s#__dumber_frame_popup", uri);
173+ printf("[webkit-create] Marked as popup: %s\n", marked_uri);
174+ }
175+ }
176+
177+ // CRITICAL FIX: Return NULL for _blank targets to prevent WindowFeatures crash
178+ if (is_regular_pane) {
179+ // For _blank targets, return NULL to let WebKit handle it naturally
180+ // This prevents WebKit from trying to access uninitialized WindowFeatures
181+ printf("[webkit-create] _blank target detected - returning NULL to prevent WindowFeatures crash\n");
182+ printf("[webkit-create] Creating new panel directly for _blank target\n");
183+
184+ // Create new panel directly through Go layer
185+ unsigned long view_id = (unsigned long)user_data;
186+ goCreateNewPanel(view_id, marked_uri ? marked_uri : (char*)uri);
187+
188+ // Clean up allocated memory
189+ if (marked_uri) {
190+ free(marked_uri);
191+ }
101 192
193+ return NULL; // Critical: Don't return a widget for _blank targets
194+ }
195+
196+ // Only create WebView for actual popups (window.open with features)
197+ printf("[webkit-create] Creating popup WebView with shared context from parent\n");
198+
199+ // Get the web context from the parent WebView to share settings and session
200+ WebKitWebContext* parent_context = webkit_web_view_get_context(web_view);
201+ WebKitSettings* parent_settings = webkit_web_view_get_settings(web_view);
202+ WebKitUserContentManager* parent_content_manager = webkit_web_view_get_user_content_manager(web_view);
203+
204+ // Create new WebView with same context, settings, and content manager as parent
205+ GtkWidget* new_webview = GTK_WIDGET(g_object_new(WEBKIT_TYPE_WEB_VIEW,
206+ "web-context", parent_context,
207+ "settings", parent_settings,
208+ "user-content-manager", parent_content_manager,
209+ NULL));
210+
211+ // Now inform Go layer about the new popup WebView for workspace management
102 unsigned long view_id = (unsigned long)user_data;212 unsigned long view_id = (unsigned long)user_data;
103- GtkWidget* new_webview = goHandleCreateWebView(view_id, (char*)uri);213+ goNotifyWebViewCreated(view_id, (unsigned long)new_webview, marked_uri ? marked_uri : (char*)uri);
214+
215+ // Clean up allocated memory
216+ if (marked_uri) {
217+ free(marked_uri);
218+ }
104 219
105 if (new_webview) {220 if (new_webview) {
106- printf("[webkit-create] Go handler provided WebView - using workspace pane\n");221+ printf("[webkit-create] Created popup WebView - connecting popup management signals\n");
222+
223+ // Connect ready-to-show signal for popup WindowFeatures handling
224+ g_signal_connect(new_webview, "ready-to-show",
225+ G_CALLBACK(on_popup_ready_to_show), user_data);
226+ printf("[webkit-create] Connected ready-to-show signal for popup WindowFeatures\n");
107 227
108- // Connect load_changed signal to the new popup WebView for OAuth detection
109 WebKitWebView* popup_webview = WEBKIT_WEB_VIEW(new_webview);228 WebKitWebView* popup_webview = WEBKIT_WEB_VIEW(new_webview);
110 g_signal_connect_data(G_OBJECT(popup_webview), "load-changed",229 g_signal_connect_data(G_OBJECT(popup_webview), "load-changed",
111 G_CALLBACK(on_load_changed), user_data, NULL, 0);230 G_CALLBACK(on_load_changed), user_data, NULL, 0);
112- printf("[webkit-create] Connected load-changed signal to popup for OAuth detection\n");231+ printf("[webkit-create] Connected load-changed signal to popup for navigation tracking\n");
113 232
114- // Connect close signal to handle window.close() from OAuth providers233+ // Connect close signal to handle window.close() from any popup (OAuth, ads, etc.)
115 g_signal_connect_data(G_OBJECT(popup_webview), "close",234 g_signal_connect_data(G_OBJECT(popup_webview), "close",
116 G_CALLBACK(on_webview_close), user_data, NULL, 0);235 G_CALLBACK(on_webview_close), user_data, NULL, 0);
117- printf("[webkit-create] Connected close signal to popup for OAuth auto-close\n");236+ printf("[webkit-create] Connected close signal to popup for auto-close handling\n");
118 237
119 return new_webview;238 return new_webview;
120 } else {239 } else {
121- printf("[webkit-create] Go handler declined - allowing native popup window\n");240+ printf("[webkit-create] Go handler declined popup - allowing native popup window\n");
122 return NULL; // Let WebKit create native popup window241 return NULL; // Let WebKit create native popup window
123 }242 }
124 }243 }
@@ -145,9 +264,18 @@ static gboolean on_decide_policy(WebKitWebView *web_view,
145 // Get navigation type to understand how popup was triggered264 // Get navigation type to understand how popup was triggered
146 WebKitNavigationType nav_type = webkit_navigation_action_get_navigation_type(action);265 WebKitNavigationType nav_type = webkit_navigation_action_get_navigation_type(action);
147 266
267+ // Get frame name to distinguish _blank from other popups
268+ const char* frame_name = webkit_navigation_action_get_frame_name(action);
269+
270+ // Get additional properties to better distinguish _blank from popups
271+ gboolean is_user_gesture = webkit_navigation_action_is_user_gesture(action);
272+ guint modifiers = webkit_navigation_action_get_modifiers(action);
273+ guint mouse_button = webkit_navigation_action_get_mouse_button(action);
274+
148 // Call Go handler for popup creation275 // Call Go handler for popup creation
149 unsigned long view_id = (unsigned long)user_data;276 unsigned long view_id = (unsigned long)user_data;
150- gboolean handled = goHandleNewWindowPolicy(view_id, (char*)uri, nav_type);277+ gboolean handled = goHandleNewWindowPolicy(view_id, (char*)uri, nav_type, (char*)frame_name,
278+ is_user_gesture, modifiers, mouse_button);
151 279
152 if (handled) {280 if (handled) {
153 webkit_policy_decision_ignore(decision);281 webkit_policy_decision_ignore(decision);
@@ -284,7 +412,7 @@ static GtkWidget* new_webview_related(WebKitWebView* related_view, WebKitUserCon
284 static void connect_policy_handler(WebKitWebView* web_view, unsigned long id) {412 static void connect_policy_handler(WebKitWebView* web_view, unsigned long id) {
285 if (!web_view) return;413 if (!web_view) return;
286 g_signal_connect_data(G_OBJECT(web_view), "decide-policy", G_CALLBACK(on_decide_policy), (gpointer)id, NULL, 0);414 g_signal_connect_data(G_OBJECT(web_view), "decide-policy", G_CALLBACK(on_decide_policy), (gpointer)id, NULL, 0);
287- // Connect create signal for popup window support415+ // Keep create signal for JavaScript window.open() and other cases not caught by decide-policy
288 g_signal_connect_data(G_OBJECT(web_view), "create", G_CALLBACK(on_create_web_view), (gpointer)id, NULL, 0);416 g_signal_connect_data(G_OBJECT(web_view), "create", G_CALLBACK(on_create_web_view), (gpointer)id, NULL, 0);
289 }417 }
290 418
@@ -1072,9 +1200,9 @@ type WebView struct {
1072 titleHandler func(title string)1200 titleHandler func(title string)
1073 uriHandler func(uri string)1201 uriHandler func(uri string)
1074 zoomHandler func(level float64)1202 zoomHandler func(level float64)
1075- popupHandler func(string) *WebView1203+ popupHandler func(string) *WebView
1076- closeHandler func()1204+ closeHandler func()
1077- memStats *memoryStats1205+ memStats *memoryStats
1078 gcTicker *time.Ticker1206 gcTicker *time.Ticker
1079 gcDone chan struct{}1207 gcDone chan struct{}
1080 tlsExceptions map[string]bool // host -> whether user allowed certificate exception1208 tlsExceptions map[string]bool // host -> whether user allowed certificate exception
@@ -1545,6 +1673,11 @@ func NewWebViewWithRelated(cfg *Config, relatedView *WebView) (*WebView, error)
1545 }1673 }
1546 v.domZoomSeed = v.zoom1674 v.domZoomSeed = v.zoom
1547 1675
1676+ // Initialize UCM scripts and handlers (same as NewWebView)
1677+ if v.native != nil && v.native.ucm != nil {
1678+ v.enableUserContentManager(cfg)
1679+ }
1680+
1548 return v, nil1681 return v, nil
1549 }1682 }
1550 1683

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