detached aab0b08

refactor(webkit): update messaging signal handling and simplify handler registration

commit
aab0b0846831a1fed13ea11e4e56443c8801d523
author
brice · · 2025-12-14 17:50 UTC
committer
brice · · 2025-12-14 19:42 UTC
parent
105c961
signature
unsigned
1 file · +26 -12
M internal/infrastructure/webkit/messaging.go +26 -12
@@ -103,22 +103,25 @@ func (r *MessageRouter) RegisterHandlerWithCallbacks(msgType, callback, errorCal
103103 }
104104
105105 // SetupMessageHandler wires the router into the given UserContentManager.
106-// It registers the script message handler and connects the signal.
106+// It registers the script message handler in the MAIN world (not isolated).
107+// WebKit's messageHandlers is only available in main world.
108+// The isolated world GUI scripts dispatch CustomEvents to main world, which forwards to this handler.
107109 func (r *MessageRouter) SetupMessageHandler(ucm *webkit.UserContentManager, worldName string) (uint32, error) {
108110 log := logging.FromContext(r.baseCtx).With().Str("component", "message-router").Logger()
109111
112+ log.Debug().Msg("SetupMessageHandler called")
113+
110114 if ucm == nil {
115+ log.Warn().Msg("SetupMessageHandler: ucm is nil")
111116 return 0, errors.New("user content manager is nil")
112117 }
113- if worldName == "" {
114- worldName = ScriptWorldName
115- }
116118
117- if ok := ucm.RegisterScriptMessageHandler(MessageHandlerName, worldName); !ok {
118- return 0, fmt.Errorf("failed to register script message handler %q in world %q", MessageHandlerName, worldName)
119- }
119+ log.Debug().Msg("SetupMessageHandler: connecting signal with detail")
120120
121+ // Connect to signal BEFORE registering handler to avoid race conditions
122+ // (as recommended by WebKit documentation)
121123 cb := func(_ webkit.UserContentManager, valuePtr uintptr) {
124+ log.Debug().Uint64("value_ptr", uint64(valuePtr)).Msg("signal callback invoked")
122125 r.handleScriptMessage(valuePtr)
123126 }
124127
@@ -126,16 +129,28 @@ func (r *MessageRouter) SetupMessageHandler(ucm *webkit.UserContentManager, worl
126129 r.callbacks = append(r.callbacks, cb) // keep callback alive
127130 r.mu.Unlock()
128131
129- signalID := ucm.ConnectScriptMessageReceived(&cb)
132+ // Connect to "script-message-received::dumber" with handler name as signal detail
133+ signalID := ucm.ConnectScriptMessageReceivedWithDetail(MessageHandlerName, &cb)
134+ log.Debug().Uint32("signal_id", signalID).Str("handler", MessageHandlerName).Msg("signal connected with detail")
135+
130136 r.mu.Lock()
131137 r.signals = append(r.signals, signalID)
132138 r.mu.Unlock()
133139
140+ log.Debug().Msg("SetupMessageHandler: registering handler in main world")
141+
142+ // Register handler in main world - webkit.messageHandlers is only available there
143+ // nil = main/default world (NULL in C), empty string "" would be a world named ""
144+ if ok := ucm.RegisterScriptMessageHandler(MessageHandlerName, nil); !ok {
145+ log.Warn().Str("handler", MessageHandlerName).Msg("RegisterScriptMessageHandler returned false")
146+ return 0, fmt.Errorf("failed to register script message handler %q in main world", MessageHandlerName)
147+ }
148+
134149 log.Debug().
135150 Str("handler", MessageHandlerName).
136- Str("world", worldName).
151+ Str("world", "main").
137152 Uint32("signal_id", signalID).
138- Msg("script message handler connected")
153+ Msg("script message handler connected successfully")
139154
140155 return signalID, nil
141156 }
@@ -236,7 +251,6 @@ func (r *MessageRouter) dispatchResponse(ctx context.Context, webviewID WebViewI
236251
237252 script := fmt.Sprintf(`(function(){try{if(window.%[1]s){window.%[1]s(%[2]s);}else{console.warn("dumber callback missing: %[1]s");}}catch(e){console.error("dumber callback %[1]s failed", e);}})();`, callback, string(data))
238253
239- // Use async to avoid blocking the GTK main loop (signal handler context)
240- wv.RunJavaScriptAsync(ctx, script, world)
254+ wv.RunJavaScript(ctx, script, world)
241255 return nil
242256 }
@@ -103,22 +103,25 @@ func (r *MessageRouter) RegisterHandlerWithCallbacks(msgType, callback, errorCal
103 }103 }
104 104
105 // SetupMessageHandler wires the router into the given UserContentManager.105 // SetupMessageHandler wires the router into the given UserContentManager.
106-// It registers the script message handler and connects the signal.106+// It registers the script message handler in the MAIN world (not isolated).
107+// WebKit's messageHandlers is only available in main world.
108+// The isolated world GUI scripts dispatch CustomEvents to main world, which forwards to this handler.
107 func (r *MessageRouter) SetupMessageHandler(ucm *webkit.UserContentManager, worldName string) (uint32, error) {109 func (r *MessageRouter) SetupMessageHandler(ucm *webkit.UserContentManager, worldName string) (uint32, error) {
108 log := logging.FromContext(r.baseCtx).With().Str("component", "message-router").Logger()110 log := logging.FromContext(r.baseCtx).With().Str("component", "message-router").Logger()
109 111
112+ log.Debug().Msg("SetupMessageHandler called")
113+
110 if ucm == nil {114 if ucm == nil {
115+ log.Warn().Msg("SetupMessageHandler: ucm is nil")
111 return 0, errors.New("user content manager is nil")116 return 0, errors.New("user content manager is nil")
112 }117 }
113- if worldName == "" {
114- worldName = ScriptWorldName
115- }
116 118
117- if ok := ucm.RegisterScriptMessageHandler(MessageHandlerName, worldName); !ok {119+ log.Debug().Msg("SetupMessageHandler: connecting signal with detail")
118- return 0, fmt.Errorf("failed to register script message handler %q in world %q", MessageHandlerName, worldName)
119- }
120 120
121+ // Connect to signal BEFORE registering handler to avoid race conditions
122+ // (as recommended by WebKit documentation)
121 cb := func(_ webkit.UserContentManager, valuePtr uintptr) {123 cb := func(_ webkit.UserContentManager, valuePtr uintptr) {
124+ log.Debug().Uint64("value_ptr", uint64(valuePtr)).Msg("signal callback invoked")
122 r.handleScriptMessage(valuePtr)125 r.handleScriptMessage(valuePtr)
123 }126 }
124 127
@@ -126,16 +129,28 @@ func (r *MessageRouter) SetupMessageHandler(ucm *webkit.UserContentManager, worl
126 r.callbacks = append(r.callbacks, cb) // keep callback alive129 r.callbacks = append(r.callbacks, cb) // keep callback alive
127 r.mu.Unlock()130 r.mu.Unlock()
128 131
129- signalID := ucm.ConnectScriptMessageReceived(&cb)132+ // Connect to "script-message-received::dumber" with handler name as signal detail
133+ signalID := ucm.ConnectScriptMessageReceivedWithDetail(MessageHandlerName, &cb)
134+ log.Debug().Uint32("signal_id", signalID).Str("handler", MessageHandlerName).Msg("signal connected with detail")
135+
130 r.mu.Lock()136 r.mu.Lock()
131 r.signals = append(r.signals, signalID)137 r.signals = append(r.signals, signalID)
132 r.mu.Unlock()138 r.mu.Unlock()
133 139
140+ log.Debug().Msg("SetupMessageHandler: registering handler in main world")
141+
142+ // Register handler in main world - webkit.messageHandlers is only available there
143+ // nil = main/default world (NULL in C), empty string "" would be a world named ""
144+ if ok := ucm.RegisterScriptMessageHandler(MessageHandlerName, nil); !ok {
145+ log.Warn().Str("handler", MessageHandlerName).Msg("RegisterScriptMessageHandler returned false")
146+ return 0, fmt.Errorf("failed to register script message handler %q in main world", MessageHandlerName)
147+ }
148+
134 log.Debug().149 log.Debug().
135 Str("handler", MessageHandlerName).150 Str("handler", MessageHandlerName).
136- Str("world", worldName).151+ Str("world", "main").
137 Uint32("signal_id", signalID).152 Uint32("signal_id", signalID).
138- Msg("script message handler connected")153+ Msg("script message handler connected successfully")
139 154
140 return signalID, nil155 return signalID, nil
141 }156 }
@@ -236,7 +251,6 @@ func (r *MessageRouter) dispatchResponse(ctx context.Context, webviewID WebViewI
236 251
237 script := fmt.Sprintf(`(function(){try{if(window.%[1]s){window.%[1]s(%[2]s);}else{console.warn("dumber callback missing: %[1]s");}}catch(e){console.error("dumber callback %[1]s failed", e);}})();`, callback, string(data))252 script := fmt.Sprintf(`(function(){try{if(window.%[1]s){window.%[1]s(%[2]s);}else{console.warn("dumber callback missing: %[1]s");}}catch(e){console.error("dumber callback %[1]s failed", e);}})();`, callback, string(data))
238 253
239- // Use async to avoid blocking the GTK main loop (signal handler context)254+ wv.RunJavaScript(ctx, script, world)
240- wv.RunJavaScriptAsync(ctx, script, world)
241 return nil255 return nil
242 }256 }

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