detached d542439

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 {
528528 return nil // Fallback to native popup if workspace creation fails
529529 }
530530
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+}
531568
532569 //export goHandlePopupGeometry
533570 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 fails528 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 goHandlePopupGeometry569 //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 {
2222 uriHandler func(uri string)
2323 zoomHandler func(level float64)
2424 popupHandler func(string) *WebView
25+ closeHandler func()
2526 useDomZoom bool
2627 domZoomSeed float64
2728 container uintptr
@@ -123,6 +124,8 @@ func (w *WebView) RegisterScriptMessageHandler(cb func(payload string)) { w.msgH
123124
124125 func (w *WebView) RegisterPopupHandler(cb func(string) *WebView) { w.popupHandler = cb }
125126
127+func (w *WebView) RegisterCloseHandler(cb func()) { w.closeHandler = cb }
128+
126129 func (w *WebView) dispatchScriptMessage(payload string) { //nolint:unused // Called from CGO WebKit callbacks
127130 if w != nil && w.msgHandler != nil {
128131 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) *WebView24 popupHandler func(string) *WebView
25+ closeHandler func()
25 useDomZoom bool26 useDomZoom bool
26 domZoomSeed float6427 domZoomSeed float64
27 container uintptr28 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 callbacks129 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);
2525 extern gboolean goHandleNewWindowPolicy(unsigned long id, char* uri, int nav_type);
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 goHandleLoadChanged(unsigned long id, char* uri, int load_event);
29+extern void goHandleWebViewClose(unsigned long id);
2830 static gboolean invoke_handle_cb(gpointer data) {
2931 goInvokeHandle((uintptr_t)data);
3032 return G_SOURCE_REMOVE;
@@ -69,6 +71,25 @@ static void on_popup_ready_to_show(WebKitWebView* web_view, gpointer user_data)
6971 }
7072 }
7173
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+
7293 // Handle create signal for popup windows - essential for window.open() support
7394 static GtkWidget* on_create_web_view(WebKitWebView *web_view,
7495 WebKitNavigationAction *navigation_action,
@@ -83,6 +104,18 @@ static GtkWidget* on_create_web_view(WebKitWebView *web_view,
83104
84105 if (new_webview) {
85106 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+
86119 return new_webview;
87120 } else {
88121 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) {
255288 g_signal_connect_data(G_OBJECT(web_view), "create", G_CALLBACK(on_create_web_view), (gpointer)id, NULL, 0);
256289 }
257290
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+
258298 static gboolean gtk_prefers_dark() {
259299 // Method 1: Check GNOME desktop interface color-scheme (primary method)
260300 GSettings* desktop_settings = g_settings_new("org.gnome.desktop.interface");
@@ -1033,6 +1073,7 @@ type WebView struct {
10331073 uriHandler func(uri string)
10341074 zoomHandler func(level float64)
10351075 popupHandler func(string) *WebView
1076+ closeHandler func()
10361077 memStats *memoryStats
10371078 gcTicker *time.Ticker
10381079 gcDone chan struct{}
@@ -1675,6 +1716,14 @@ func (w *WebView) RegisterScriptMessageHandler(cb func(payload string)) { w.msgH
16751716
16761717 func (w *WebView) RegisterPopupHandler(cb func(string) *WebView) { w.popupHandler = cb }
16771718
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+
16781727 func (w *WebView) dispatchScriptMessage(payload string) {
16791728 if w != nil && w.msgHandler != nil {
16801729 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() support93 // 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) *WebView1075 popupHandler func(string) *WebView
1076+ closeHandler func()
1036 memStats *memoryStats1077 memStats *memoryStats
1037 gcTicker *time.Ticker1078 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)

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