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 | |||
| 103 | 103 | } | |
| 104 | 104 | ||
| 105 | 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 | 109 | func (r *MessageRouter) SetupMessageHandler(ucm *webkit.UserContentManager, worldName string) (uint32, error) { | |
| 108 | 110 | log := logging.FromContext(r.baseCtx).With().Str("component", "message-router").Logger() | |
| 109 | 111 | ||
| 112 | + | log.Debug().Msg("SetupMessageHandler called") | |
| 113 | + | ||
| 110 | 114 | if ucm == nil { | |
| 115 | + | log.Warn().Msg("SetupMessageHandler: ucm is nil") | |
| 111 | 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 { | |
| 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") | |
| 120 | 120 | ||
| 121 | + | // Connect to signal BEFORE registering handler to avoid race conditions | |
| 122 | + | // (as recommended by WebKit documentation) | |
| 121 | 123 | cb := func(_ webkit.UserContentManager, valuePtr uintptr) { | |
| 124 | + | log.Debug().Uint64("value_ptr", uint64(valuePtr)).Msg("signal callback invoked") | |
| 122 | 125 | r.handleScriptMessage(valuePtr) | |
| 123 | 126 | } | |
| 124 | 127 | ||
| @@ -126,16 +129,28 @@ func (r *MessageRouter) SetupMessageHandler(ucm *webkit.UserContentManager, worl | |||
| 126 | 129 | r.callbacks = append(r.callbacks, cb) // keep callback alive | |
| 127 | 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 | 136 | r.mu.Lock() | |
| 131 | 137 | r.signals = append(r.signals, signalID) | |
| 132 | 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 | 149 | log.Debug(). | |
| 135 | 150 | Str("handler", MessageHandlerName). | |
| 136 | - | Str("world", worldName). | |
| 151 | + | Str("world", "main"). | |
| 137 | 152 | Uint32("signal_id", signalID). | |
| 138 | - | Msg("script message handler connected") | |
| 153 | + | Msg("script message handler connected successfully") | |
| 139 | 154 | ||
| 140 | 155 | return signalID, nil | |
| 141 | 156 | } | |
| @@ -236,7 +251,6 @@ func (r *MessageRouter) dispatchResponse(ctx context.Context, webviewID WebViewI | |||
| 236 | 251 | ||
| 237 | 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) | |
| 240 | - | wv.RunJavaScriptAsync(ctx, script, world) | |
| 254 | + | wv.RunJavaScript(ctx, script, world) | |
| 241 | 255 | return nil | |
| 242 | 256 | } | |
| @@ -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 alive | 129 | 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, nil | 155 | 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 nil | 255 | return nil | ||
| 242 | } | 256 | } | ||