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) { | |||
| 438 | 438 | } | |
| 439 | 439 | ||
| 440 | 440 | //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 | 442 | uid := uintptr(id) | |
| 443 | 443 | regMu.RLock() | |
| 444 | 444 | vw := viewByID[uid] | |
| @@ -454,6 +454,11 @@ func goHandleNewWindowPolicy(id C.ulong, curi *C.char, navType C.int) C.gboolean | |||
| 454 | 454 | uri = C.GoString(curi) | |
| 455 | 455 | } | |
| 456 | 456 | ||
| 457 | + | frameName := "" | |
| 458 | + | if cframeName != nil { | |
| 459 | + | frameName = C.GoString(cframeName) | |
| 460 | + | } | |
| 461 | + | ||
| 457 | 462 | // Navigation type constants from WebKit headers | |
| 458 | 463 | navTypeNames := map[int]string{ | |
| 459 | 464 | 0: "LINK_CLICKED", | |
| @@ -469,23 +474,94 @@ func goHandleNewWindowPolicy(id C.ulong, curi *C.char, navType C.int) C.gboolean | |||
| 469 | 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 enabled | |
| 526 | + | // For actual popups, let the create signal handle it | |
| 475 | 527 | 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) | |
| 479 | 558 | 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) | |
| 482 | 560 | } 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) | |
| 485 | 562 | } | |
| 486 | 563 | } 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") | |
| 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 | 673 | //export goHandlePopupGeometry | |
| 570 | 674 | func goHandlePopupGeometry(id C.ulong, x, y, width, height C.int) { | |
| 571 | 675 | // 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 goHandleNewWindowPolicy | 440 | //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 headers | 462 | // 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 enabled | 526 | + | // 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 result | 529 | + | 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 goHandlePopupGeometry | 673 | //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 use | 675 | // 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()); } | |||
| 22 | 22 | // Forward declaration | |
| 23 | 23 | extern void goQuitMainLoop(); | |
| 24 | 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 | 26 | extern void goHandlePopupGeometry(unsigned long id, int x, int y, int width, int height); | |
| 27 | 27 | extern GtkWidget* goHandleCreateWebView(unsigned long id, char* uri); | |
| 28 | + | extern void goNotifyWebViewCreated(unsigned long parent_id, unsigned long webview_ptr, char* uri); | |
| 28 | 29 | extern void goHandleLoadChanged(unsigned long id, char* uri, int load_event); | |
| 29 | 30 | extern void goHandleWebViewClose(unsigned long id); | |
| 31 | + | extern void goCreateNewPanel(unsigned long id, char* uri); | |
| 30 | 32 | static gboolean invoke_handle_cb(gpointer data) { | |
| 31 | 33 | goInvokeHandle((uintptr_t)data); | |
| 32 | 34 | return G_SOURCE_REMOVE; | |
| @@ -57,17 +59,32 @@ static gboolean on_load_failed_with_tls_errors(WebKitWebView *web_view, | |||
| 57 | 59 | ||
| 58 | 60 | // Handle ready-to-show signal for popup windows to properly initialize window properties | |
| 59 | 61 | 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 | |
| 61 | 65 | WebKitWindowProperties* props = webkit_web_view_get_window_properties(web_view); | |
| 62 | 66 | 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 | + | } | |
| 66 | 78 | ||
| 67 | 79 | // Pass geometry to Go for workspace positioning | |
| 68 | 80 | unsigned long view_id = (unsigned long)user_data; | |
| 69 | 81 | goHandlePopupGeometry(view_id, geometry.x, geometry.y, | |
| 70 | 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 | 114 | WebKitURIRequest *request = webkit_navigation_action_get_request(navigation_action); | |
| 98 | 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 | 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 | 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 | 228 | WebKitWebView* popup_webview = WEBKIT_WEB_VIEW(new_webview); | |
| 110 | 229 | g_signal_connect_data(G_OBJECT(popup_webview), "load-changed", | |
| 111 | 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 providers | |
| 233 | + | // Connect close signal to handle window.close() from any popup (OAuth, ads, etc.) | |
| 115 | 234 | g_signal_connect_data(G_OBJECT(popup_webview), "close", | |
| 116 | 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 | 238 | return new_webview; | |
| 120 | 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 | 241 | 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 | 264 | // Get navigation type to understand how popup was triggered | |
| 146 | 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 | 275 | // Call Go handler for popup creation | |
| 149 | 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 | 280 | if (handled) { | |
| 153 | 281 | webkit_policy_decision_ignore(decision); | |
| @@ -284,7 +412,7 @@ static GtkWidget* new_webview_related(WebKitWebView* related_view, WebKitUserCon | |||
| 284 | 412 | static void connect_policy_handler(WebKitWebView* web_view, unsigned long id) { | |
| 285 | 413 | if (!web_view) return; | |
| 286 | 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 support | |
| 415 | + | // Keep create signal for JavaScript window.open() and other cases not caught by decide-policy | |
| 288 | 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 | 1200 | titleHandler func(title string) | |
| 1073 | 1201 | uriHandler func(uri string) | |
| 1074 | 1202 | 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 | |
| 1078 | 1206 | gcTicker *time.Ticker | |
| 1079 | 1207 | gcDone chan struct{} | |
| 1080 | 1208 | 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 | 1674 | 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 | 1681 | return v, nil | |
| 1549 | 1682 | } | |
| 1550 | 1683 | ||
| @@ -22,11 +22,13 @@ static GtkWidget* new_window() { return GTK_WIDGET(gtk_window_new()); } | |||||
| 22 | // Forward declaration | 22 | // 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 properties | 60 | // 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 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 | |||
| 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 initialized | 67 | + | // 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 positioning | 79 | // 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 providers | 233 | + | // 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 window | 241 | 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 triggered | 264 | // 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 creation | 275 | // 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 support | 415 | + | // 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) *WebView | 1203 | + | popupHandler func(string) *WebView |
| 1076 | - | closeHandler func() | 1204 | + | closeHandler func() |
| 1077 | - | memStats *memoryStats | 1205 | + | memStats *memoryStats |
| 1078 | gcTicker *time.Ticker | 1206 | gcTicker *time.Ticker | ||
| 1079 | gcDone chan struct{} | 1207 | gcDone chan struct{} | ||
| 1080 | tlsExceptions map[string]bool // host -> whether user allowed certificate exception | 1208 | 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.zoom | 1674 | 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, nil | 1681 | return v, nil | ||
| 1549 | } | 1682 | } | ||
| 1550 | 1683 | ||||