feat(webkit): add close and load-changed signal handlers for popup auto-close
- commit
- d542439487419622a79ef6ae19f91c2c1f663a87
- author
- bamen · · 2025-09-20 06:03 UTC
- parent
- fa08de5
- signature
- unsigned
3 files · +89 -0
M pkg/webkit/keyboard_cgo.go +37 -0
| @@ -528,6 +528,43 @@ func goHandleCreateWebView(id C.ulong, curi *C.char) *C.GtkWidget { | |||
| 528 | 528 | return nil // Fallback to native popup if workspace creation fails | |
| 529 | 529 | } | |
| 530 | 530 | ||
| 531 | + | //export goHandleLoadChanged | |
| 532 | + | func goHandleLoadChanged(id C.ulong, curi *C.char, loadEvent C.int) { | |
| 533 | + | uid := uintptr(id) | |
| 534 | + | regMu.RLock() | |
| 535 | + | vw := viewByID[uid] | |
| 536 | + | regMu.RUnlock() | |
| 537 | + | ||
| 538 | + | if vw == nil { | |
| 539 | + | return | |
| 540 | + | } | |
| 541 | + | ||
| 542 | + | // OAuth auto-close is handled via WebKit's "close" signal when providers call window.close() | |
| 543 | + | // No need to detect OAuth callback URLs - let the providers handle it | |
| 544 | + | _ = curi // Suppress unused parameter warning | |
| 545 | + | } | |
| 546 | + | ||
| 547 | + | //export goHandleWebViewClose | |
| 548 | + | func goHandleWebViewClose(id C.ulong) { | |
| 549 | + | uid := uintptr(id) | |
| 550 | + | regMu.RLock() | |
| 551 | + | vw := viewByID[uid] | |
| 552 | + | regMu.RUnlock() | |
| 553 | + | ||
| 554 | + | if vw == nil { | |
| 555 | + | log.Printf("[webkit] WebView close signal: no WebView found for ID %d", uid) | |
| 556 | + | return | |
| 557 | + | } | |
| 558 | + | ||
| 559 | + | log.Printf("[webkit] WebView close signal received for view_id: %d", uid) | |
| 560 | + | ||
| 561 | + | if vw.closeHandler != nil { | |
| 562 | + | log.Printf("[webkit] Calling close handler for view_id: %d", uid) | |
| 563 | + | vw.closeHandler() | |
| 564 | + | } else { | |
| 565 | + | log.Printf("[webkit] No close handler registered for view_id: %d", uid) | |
| 566 | + | } | |
| 567 | + | } | |
| 531 | 568 | ||
| 532 | 569 | //export goHandlePopupGeometry | |
| 533 | 570 | func goHandlePopupGeometry(id C.ulong, x, y, width, height C.int) { | |
| @@ -528,6 +528,43 @@ func goHandleCreateWebView(id C.ulong, curi *C.char) *C.GtkWidget { | |||||
| 528 | return nil // Fallback to native popup if workspace creation fails | 528 | return nil // Fallback to native popup if workspace creation fails | ||
| 529 | } | 529 | } | ||
| 530 | 530 | ||||
| 531 | + | //export goHandleLoadChanged | |||
| 532 | + | func goHandleLoadChanged(id C.ulong, curi *C.char, loadEvent C.int) { | |||
| 533 | + | uid := uintptr(id) | |||
| 534 | + | regMu.RLock() | |||
| 535 | + | vw := viewByID[uid] | |||
| 536 | + | regMu.RUnlock() | |||
| 537 | + | ||||
| 538 | + | if vw == nil { | |||
| 539 | + | return | |||
| 540 | + | } | |||
| 541 | + | ||||
| 542 | + | // OAuth auto-close is handled via WebKit's "close" signal when providers call window.close() | |||
| 543 | + | // No need to detect OAuth callback URLs - let the providers handle it | |||
| 544 | + | _ = curi // Suppress unused parameter warning | |||
| 545 | + | } | |||
| 546 | + | ||||
| 547 | + | //export goHandleWebViewClose | |||
| 548 | + | func goHandleWebViewClose(id C.ulong) { | |||
| 549 | + | uid := uintptr(id) | |||
| 550 | + | regMu.RLock() | |||
| 551 | + | vw := viewByID[uid] | |||
| 552 | + | regMu.RUnlock() | |||
| 553 | + | ||||
| 554 | + | if vw == nil { | |||
| 555 | + | log.Printf("[webkit] WebView close signal: no WebView found for ID %d", uid) | |||
| 556 | + | return | |||
| 557 | + | } | |||
| 558 | + | ||||
| 559 | + | log.Printf("[webkit] WebView close signal received for view_id: %d", uid) | |||
| 560 | + | ||||
| 561 | + | if vw.closeHandler != nil { | |||
| 562 | + | log.Printf("[webkit] Calling close handler for view_id: %d", uid) | |||
| 563 | + | vw.closeHandler() | |||
| 564 | + | } else { | |||
| 565 | + | log.Printf("[webkit] No close handler registered for view_id: %d", uid) | |||
| 566 | + | } | |||
| 567 | + | } | |||
| 531 | 568 | ||||
| 532 | //export goHandlePopupGeometry | 569 | //export goHandlePopupGeometry | ||
| 533 | func goHandlePopupGeometry(id C.ulong, x, y, width, height C.int) { | 570 | func goHandlePopupGeometry(id C.ulong, x, y, width, height C.int) { | ||
M pkg/webkit/webview.go +3 -0
| @@ -22,6 +22,7 @@ type WebView struct { | |||
| 22 | 22 | uriHandler func(uri string) | |
| 23 | 23 | zoomHandler func(level float64) | |
| 24 | 24 | popupHandler func(string) *WebView | |
| 25 | + | closeHandler func() | |
| 25 | 26 | useDomZoom bool | |
| 26 | 27 | domZoomSeed float64 | |
| 27 | 28 | container uintptr | |
| @@ -123,6 +124,8 @@ func (w *WebView) RegisterScriptMessageHandler(cb func(payload string)) { w.msgH | |||
| 123 | 124 | ||
| 124 | 125 | func (w *WebView) RegisterPopupHandler(cb func(string) *WebView) { w.popupHandler = cb } | |
| 125 | 126 | ||
| 127 | + | func (w *WebView) RegisterCloseHandler(cb func()) { w.closeHandler = cb } | |
| 128 | + | ||
| 126 | 129 | func (w *WebView) dispatchScriptMessage(payload string) { //nolint:unused // Called from CGO WebKit callbacks | |
| 127 | 130 | if w != nil && w.msgHandler != nil { | |
| 128 | 131 | w.msgHandler(payload) | |
| @@ -22,6 +22,7 @@ type WebView struct { | |||||
| 22 | uriHandler func(uri string) | 22 | uriHandler func(uri string) | ||
| 23 | zoomHandler func(level float64) | 23 | zoomHandler func(level float64) | ||
| 24 | popupHandler func(string) *WebView | 24 | popupHandler func(string) *WebView | ||
| 25 | + | closeHandler func() | |||
| 25 | useDomZoom bool | 26 | useDomZoom bool | ||
| 26 | domZoomSeed float64 | 27 | domZoomSeed float64 | ||
| 27 | container uintptr | 28 | container uintptr | ||
| @@ -123,6 +124,8 @@ func (w *WebView) RegisterScriptMessageHandler(cb func(payload string)) { w.msgH | |||||
| 123 | 124 | ||||
| 124 | func (w *WebView) RegisterPopupHandler(cb func(string) *WebView) { w.popupHandler = cb } | 125 | func (w *WebView) RegisterPopupHandler(cb func(string) *WebView) { w.popupHandler = cb } | ||
| 125 | 126 | ||||
| 127 | + | func (w *WebView) RegisterCloseHandler(cb func()) { w.closeHandler = cb } | |||
| 128 | + | ||||
| 126 | func (w *WebView) dispatchScriptMessage(payload string) { //nolint:unused // Called from CGO WebKit callbacks | 129 | func (w *WebView) dispatchScriptMessage(payload string) { //nolint:unused // Called from CGO WebKit callbacks | ||
| 127 | if w != nil && w.msgHandler != nil { | 130 | if w != nil && w.msgHandler != nil { | ||
| 128 | w.msgHandler(payload) | 131 | w.msgHandler(payload) | ||
M pkg/webkit/webview_cgo.go +49 -0
| @@ -25,6 +25,8 @@ extern void goInvokeHandle(uintptr_t handle); | |||
| 25 | 25 | extern gboolean goHandleNewWindowPolicy(unsigned long id, char* uri, int nav_type); | |
| 26 | 26 | extern void goHandlePopupGeometry(unsigned long id, int x, int y, int width, int height); | |
| 27 | 27 | extern GtkWidget* goHandleCreateWebView(unsigned long id, char* uri); | |
| 28 | + | extern void goHandleLoadChanged(unsigned long id, char* uri, int load_event); | |
| 29 | + | extern void goHandleWebViewClose(unsigned long id); | |
| 28 | 30 | static gboolean invoke_handle_cb(gpointer data) { | |
| 29 | 31 | goInvokeHandle((uintptr_t)data); | |
| 30 | 32 | return G_SOURCE_REMOVE; | |
| @@ -69,6 +71,25 @@ static void on_popup_ready_to_show(WebKitWebView* web_view, gpointer user_data) | |||
| 69 | 71 | } | |
| 70 | 72 | } | |
| 71 | 73 | ||
| 74 | + | // Handle load_changed signal for OAuth completion detection | |
| 75 | + | static void on_load_changed(WebKitWebView *web_view, WebKitLoadEvent load_event, gpointer user_data) { | |
| 76 | + | const char* uri = webkit_web_view_get_uri(web_view); | |
| 77 | + | unsigned long view_id = (unsigned long)user_data; | |
| 78 | + | ||
| 79 | + | // Only process WEBKIT_LOAD_FINISHED events | |
| 80 | + | if (load_event == WEBKIT_LOAD_FINISHED && uri) { | |
| 81 | + | printf("[webkit-load] Load finished for URI: %s\n", uri); | |
| 82 | + | goHandleLoadChanged(view_id, (char*)uri, load_event); | |
| 83 | + | } | |
| 84 | + | } | |
| 85 | + | ||
| 86 | + | // Handle close signal when JavaScript calls window.close() | |
| 87 | + | static void on_webview_close(WebKitWebView* web_view, gpointer user_data) { | |
| 88 | + | unsigned long view_id = (unsigned long)user_data; | |
| 89 | + | printf("[webkit-close] WebView close signal from view_id: %lu\n", view_id); | |
| 90 | + | goHandleWebViewClose(view_id); | |
| 91 | + | } | |
| 92 | + | ||
| 72 | 93 | // Handle create signal for popup windows - essential for window.open() support | |
| 73 | 94 | static GtkWidget* on_create_web_view(WebKitWebView *web_view, | |
| 74 | 95 | WebKitNavigationAction *navigation_action, | |
| @@ -83,6 +104,18 @@ static GtkWidget* on_create_web_view(WebKitWebView *web_view, | |||
| 83 | 104 | ||
| 84 | 105 | if (new_webview) { | |
| 85 | 106 | printf("[webkit-create] Go handler provided WebView - using workspace pane\n"); | |
| 107 | + | ||
| 108 | + | // Connect load_changed signal to the new popup WebView for OAuth detection | |
| 109 | + | WebKitWebView* popup_webview = WEBKIT_WEB_VIEW(new_webview); | |
| 110 | + | g_signal_connect_data(G_OBJECT(popup_webview), "load-changed", | |
| 111 | + | G_CALLBACK(on_load_changed), user_data, NULL, 0); | |
| 112 | + | printf("[webkit-create] Connected load-changed signal to popup for OAuth detection\n"); | |
| 113 | + | ||
| 114 | + | // Connect close signal to handle window.close() from OAuth providers | |
| 115 | + | g_signal_connect_data(G_OBJECT(popup_webview), "close", | |
| 116 | + | G_CALLBACK(on_webview_close), user_data, NULL, 0); | |
| 117 | + | printf("[webkit-create] Connected close signal to popup for OAuth auto-close\n"); | |
| 118 | + | ||
| 86 | 119 | return new_webview; | |
| 87 | 120 | } else { | |
| 88 | 121 | printf("[webkit-create] Go handler declined - allowing native popup window\n"); | |
| @@ -255,6 +288,13 @@ static void connect_policy_handler(WebKitWebView* web_view, unsigned long id) { | |||
| 255 | 288 | g_signal_connect_data(G_OBJECT(web_view), "create", G_CALLBACK(on_create_web_view), (gpointer)id, NULL, 0); | |
| 256 | 289 | } | |
| 257 | 290 | ||
| 291 | + | // Connect close signal to a WebView for popup auto-close functionality | |
| 292 | + | static void connect_close_signal(WebKitWebView* web_view, unsigned long id) { | |
| 293 | + | if (!web_view) return; | |
| 294 | + | g_signal_connect_data(G_OBJECT(web_view), "close", G_CALLBACK(on_webview_close), (gpointer)id, NULL, 0); | |
| 295 | + | printf("[webkit-connect] Connected close signal to WebView with id: %lu\n", id); | |
| 296 | + | } | |
| 297 | + | ||
| 258 | 298 | static gboolean gtk_prefers_dark() { | |
| 259 | 299 | // Method 1: Check GNOME desktop interface color-scheme (primary method) | |
| 260 | 300 | GSettings* desktop_settings = g_settings_new("org.gnome.desktop.interface"); | |
| @@ -1033,6 +1073,7 @@ type WebView struct { | |||
| 1033 | 1073 | uriHandler func(uri string) | |
| 1034 | 1074 | zoomHandler func(level float64) | |
| 1035 | 1075 | popupHandler func(string) *WebView | |
| 1076 | + | closeHandler func() | |
| 1036 | 1077 | memStats *memoryStats | |
| 1037 | 1078 | gcTicker *time.Ticker | |
| 1038 | 1079 | gcDone chan struct{} | |
| @@ -1675,6 +1716,14 @@ func (w *WebView) RegisterScriptMessageHandler(cb func(payload string)) { w.msgH | |||
| 1675 | 1716 | ||
| 1676 | 1717 | func (w *WebView) RegisterPopupHandler(cb func(string) *WebView) { w.popupHandler = cb } | |
| 1677 | 1718 | ||
| 1719 | + | func (w *WebView) RegisterCloseHandler(cb func()) { | |
| 1720 | + | w.closeHandler = cb | |
| 1721 | + | // Also connect the CGO close signal to ensure window.close() is detected | |
| 1722 | + | if w.native != nil && w.native.wv != nil { | |
| 1723 | + | C.connect_close_signal(w.native.wv, C.ulong(w.id)) | |
| 1724 | + | } | |
| 1725 | + | } | |
| 1726 | + | ||
| 1678 | 1727 | func (w *WebView) dispatchScriptMessage(payload string) { | |
| 1679 | 1728 | if w != nil && w.msgHandler != nil { | |
| 1680 | 1729 | w.msgHandler(payload) | |
| @@ -25,6 +25,8 @@ 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); | ||
| 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 goHandleLoadChanged(unsigned long id, char* uri, int load_event); | |||
| 29 | + | extern void goHandleWebViewClose(unsigned long id); | |||
| 28 | static gboolean invoke_handle_cb(gpointer data) { | 30 | static gboolean invoke_handle_cb(gpointer data) { | ||
| 29 | goInvokeHandle((uintptr_t)data); | 31 | goInvokeHandle((uintptr_t)data); | ||
| 30 | return G_SOURCE_REMOVE; | 32 | return G_SOURCE_REMOVE; | ||
| @@ -69,6 +71,25 @@ static void on_popup_ready_to_show(WebKitWebView* web_view, gpointer user_data) | |||||
| 69 | } | 71 | } | ||
| 70 | } | 72 | } | ||
| 71 | 73 | ||||
| 74 | + | // Handle load_changed signal for OAuth completion detection | |||
| 75 | + | static void on_load_changed(WebKitWebView *web_view, WebKitLoadEvent load_event, gpointer user_data) { | |||
| 76 | + | const char* uri = webkit_web_view_get_uri(web_view); | |||
| 77 | + | unsigned long view_id = (unsigned long)user_data; | |||
| 78 | + | ||||
| 79 | + | // Only process WEBKIT_LOAD_FINISHED events | |||
| 80 | + | if (load_event == WEBKIT_LOAD_FINISHED && uri) { | |||
| 81 | + | printf("[webkit-load] Load finished for URI: %s\n", uri); | |||
| 82 | + | goHandleLoadChanged(view_id, (char*)uri, load_event); | |||
| 83 | + | } | |||
| 84 | + | } | |||
| 85 | + | ||||
| 86 | + | // Handle close signal when JavaScript calls window.close() | |||
| 87 | + | static void on_webview_close(WebKitWebView* web_view, gpointer user_data) { | |||
| 88 | + | unsigned long view_id = (unsigned long)user_data; | |||
| 89 | + | printf("[webkit-close] WebView close signal from view_id: %lu\n", view_id); | |||
| 90 | + | goHandleWebViewClose(view_id); | |||
| 91 | + | } | |||
| 92 | + | ||||
| 72 | // Handle create signal for popup windows - essential for window.open() support | 93 | // Handle create signal for popup windows - essential for window.open() support | ||
| 73 | static GtkWidget* on_create_web_view(WebKitWebView *web_view, | 94 | static GtkWidget* on_create_web_view(WebKitWebView *web_view, | ||
| 74 | WebKitNavigationAction *navigation_action, | 95 | WebKitNavigationAction *navigation_action, | ||
| @@ -83,6 +104,18 @@ static GtkWidget* on_create_web_view(WebKitWebView *web_view, | |||||
| 83 | 104 | ||||
| 84 | if (new_webview) { | 105 | if (new_webview) { | ||
| 85 | printf("[webkit-create] Go handler provided WebView - using workspace pane\n"); | 106 | printf("[webkit-create] Go handler provided WebView - using workspace pane\n"); | ||
| 107 | + | ||||
| 108 | + | // Connect load_changed signal to the new popup WebView for OAuth detection | |||
| 109 | + | WebKitWebView* popup_webview = WEBKIT_WEB_VIEW(new_webview); | |||
| 110 | + | g_signal_connect_data(G_OBJECT(popup_webview), "load-changed", | |||
| 111 | + | G_CALLBACK(on_load_changed), user_data, NULL, 0); | |||
| 112 | + | printf("[webkit-create] Connected load-changed signal to popup for OAuth detection\n"); | |||
| 113 | + | ||||
| 114 | + | // Connect close signal to handle window.close() from OAuth providers | |||
| 115 | + | g_signal_connect_data(G_OBJECT(popup_webview), "close", | |||
| 116 | + | G_CALLBACK(on_webview_close), user_data, NULL, 0); | |||
| 117 | + | printf("[webkit-create] Connected close signal to popup for OAuth auto-close\n"); | |||
| 118 | + | ||||
| 86 | return new_webview; | 119 | return new_webview; | ||
| 87 | } else { | 120 | } else { | ||
| 88 | printf("[webkit-create] Go handler declined - allowing native popup window\n"); | 121 | printf("[webkit-create] Go handler declined - allowing native popup window\n"); | ||
| @@ -255,6 +288,13 @@ static void connect_policy_handler(WebKitWebView* web_view, unsigned long id) { | |||||
| 255 | g_signal_connect_data(G_OBJECT(web_view), "create", G_CALLBACK(on_create_web_view), (gpointer)id, NULL, 0); | 288 | g_signal_connect_data(G_OBJECT(web_view), "create", G_CALLBACK(on_create_web_view), (gpointer)id, NULL, 0); | ||
| 256 | } | 289 | } | ||
| 257 | 290 | ||||
| 291 | + | // Connect close signal to a WebView for popup auto-close functionality | |||
| 292 | + | static void connect_close_signal(WebKitWebView* web_view, unsigned long id) { | |||
| 293 | + | if (!web_view) return; | |||
| 294 | + | g_signal_connect_data(G_OBJECT(web_view), "close", G_CALLBACK(on_webview_close), (gpointer)id, NULL, 0); | |||
| 295 | + | printf("[webkit-connect] Connected close signal to WebView with id: %lu\n", id); | |||
| 296 | + | } | |||
| 297 | + | ||||
| 258 | static gboolean gtk_prefers_dark() { | 298 | static gboolean gtk_prefers_dark() { | ||
| 259 | // Method 1: Check GNOME desktop interface color-scheme (primary method) | 299 | // Method 1: Check GNOME desktop interface color-scheme (primary method) | ||
| 260 | GSettings* desktop_settings = g_settings_new("org.gnome.desktop.interface"); | 300 | GSettings* desktop_settings = g_settings_new("org.gnome.desktop.interface"); | ||
| @@ -1033,6 +1073,7 @@ type WebView struct { | |||||
| 1033 | uriHandler func(uri string) | 1073 | uriHandler func(uri string) | ||
| 1034 | zoomHandler func(level float64) | 1074 | zoomHandler func(level float64) | ||
| 1035 | popupHandler func(string) *WebView | 1075 | popupHandler func(string) *WebView | ||
| 1076 | + | closeHandler func() | |||
| 1036 | memStats *memoryStats | 1077 | memStats *memoryStats | ||
| 1037 | gcTicker *time.Ticker | 1078 | gcTicker *time.Ticker | ||
| 1038 | gcDone chan struct{} | 1079 | gcDone chan struct{} | ||
| @@ -1675,6 +1716,14 @@ func (w *WebView) RegisterScriptMessageHandler(cb func(payload string)) { w.msgH | |||||
| 1675 | 1716 | ||||
| 1676 | func (w *WebView) RegisterPopupHandler(cb func(string) *WebView) { w.popupHandler = cb } | 1717 | func (w *WebView) RegisterPopupHandler(cb func(string) *WebView) { w.popupHandler = cb } | ||
| 1677 | 1718 | ||||
| 1719 | + | func (w *WebView) RegisterCloseHandler(cb func()) { | |||
| 1720 | + | w.closeHandler = cb | |||
| 1721 | + | // Also connect the CGO close signal to ensure window.close() is detected | |||
| 1722 | + | if w.native != nil && w.native.wv != nil { | |||
| 1723 | + | C.connect_close_signal(w.native.wv, C.ulong(w.id)) | |||
| 1724 | + | } | |||
| 1725 | + | } | |||
| 1726 | + | ||||
| 1678 | func (w *WebView) dispatchScriptMessage(payload string) { | 1727 | func (w *WebView) dispatchScriptMessage(payload string) { | ||
| 1679 | if w != nil && w.msgHandler != nil { | 1728 | if w != nil && w.msgHandler != nil { | ||
| 1680 | w.msgHandler(payload) | 1729 | w.msgHandler(payload) | ||