mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 14:45:49 +01:00
fix(v3): restore Android build and runtime message handling
Fixes five regressions in Android support: 1. Restore events.Android struct (IDs 1259-1270) and JS event mappings that were removed from events.go, breaking events_common_android.go 2. Pass flags to runtime.Core() in nativeOnPageFinished to match the updated API signature used by all other platforms 3. Call setupCommonEvents() in the CGO build's run() method (the non-CGO build already did this) 4. Replace the stub handleMessageForAndroid() with real MessageProcessor routing so JS-to-Go runtime calls (bound methods, clipboard, etc.) actually work 5. Handle non-JSON string messages (e.g. "wails:runtime:ready") that the JS bridge sends before attempting JSON parse Fixes #5020 Co-authored-by: Varun Chawla <varun_6april@hotmail.com>
This commit is contained in:
parent
972bb6faa3
commit
75eaaf29fb
3 changed files with 166 additions and 17 deletions
|
|
@ -139,8 +139,11 @@ static void executeJavaScriptOnBridge(const char* js) {
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -148,8 +151,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v3/internal/runtime"
|
"github.com/wailsapp/wails/v3/internal/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -221,6 +222,12 @@ func (a *App) platformRun() {
|
||||||
globalApp = a
|
globalApp = a
|
||||||
globalAppLock.Unlock()
|
globalAppLock.Unlock()
|
||||||
|
|
||||||
|
// Create MessageProcessor so JNI message callbacks can route runtime calls
|
||||||
|
messageProc := NewMessageProcessor(slog.Default())
|
||||||
|
globalMessageProcLock.Lock()
|
||||||
|
globalMessageProc = messageProc
|
||||||
|
globalMessageProcLock.Unlock()
|
||||||
|
|
||||||
// Signal that the app is ready to serve requests
|
// Signal that the app is ready to serve requests
|
||||||
signalAppReady()
|
signalAppReady()
|
||||||
|
|
||||||
|
|
@ -259,6 +266,9 @@ func newPlatformApp(app *App) *androidApp {
|
||||||
func (a *androidApp) run() error {
|
func (a *androidApp) run() error {
|
||||||
androidLogf("info", "🤖 [application_android.go] androidApp.run() called")
|
androidLogf("info", "🤖 [application_android.go] androidApp.run() called")
|
||||||
|
|
||||||
|
// Wire platform events → common events (same as non-CGO build)
|
||||||
|
a.setupCommonEvents()
|
||||||
|
|
||||||
// Emit application started event
|
// Emit application started event
|
||||||
a.parent.Event.Emit("ApplicationStarted")
|
a.parent.Event.Emit("ApplicationStarted")
|
||||||
|
|
||||||
|
|
@ -433,7 +443,7 @@ func Java_com_wails_app_WailsBridge_nativeOnPageFinished(env *C.JNIEnv, obj C.jo
|
||||||
if win != nil {
|
if win != nil {
|
||||||
androidLogf("info", "🤖 [JNI] Injecting runtime.Core() into window %d", id)
|
androidLogf("info", "🤖 [JNI] Injecting runtime.Core() into window %d", id)
|
||||||
// Get the runtime core JavaScript
|
// Get the runtime core JavaScript
|
||||||
runtimeJS := runtime.Core()
|
runtimeJS := runtime.Core(app.impl.GetFlags(app.options))
|
||||||
androidLogf("info", "🤖 [JNI] Runtime JS length: %d bytes", len(runtimeJS))
|
androidLogf("info", "🤖 [JNI] Runtime JS length: %d bytes", len(runtimeJS))
|
||||||
app.windowsLock.RUnlock()
|
app.windowsLock.RUnlock()
|
||||||
// IMPORTANT: We must bypass win.ExecJS because it queues if runtimeLoaded is false.
|
// IMPORTANT: We must bypass win.ExecJS because it queues if runtimeLoaded is false.
|
||||||
|
|
@ -621,16 +631,60 @@ func serveAssetForAndroid(app *App, path string) ([]byte, error) {
|
||||||
return body, nil
|
return body, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Global MessageProcessor for Android JNI callbacks
|
||||||
|
var (
|
||||||
|
globalMessageProc *MessageProcessor
|
||||||
|
globalMessageProcLock sync.RWMutex
|
||||||
|
)
|
||||||
|
|
||||||
func handleMessageForAndroid(app *App, message string) string {
|
func handleMessageForAndroid(app *App, message string) string {
|
||||||
// Parse the message
|
// Some messages are plain strings (e.g. "wails:runtime:ready"), not JSON.
|
||||||
var msg map[string]interface{}
|
// The JS bridge sends: typeof m==='string' ? m : JSON.stringify(m)
|
||||||
if err := json.Unmarshal([]byte(message), &msg); err != nil {
|
if len(message) == 0 || message[0] != '{' {
|
||||||
|
androidLogf("debug", "🤖 [handleMessageForAndroid] Non-JSON message: %s", message)
|
||||||
|
return `{"success":true}`
|
||||||
|
}
|
||||||
|
|
||||||
|
var req RuntimeRequest
|
||||||
|
if err := json.Unmarshal([]byte(message), &req); err != nil {
|
||||||
|
androidLogf("error", "🤖 [handleMessageForAndroid] Failed to parse: %v", err)
|
||||||
return fmt.Sprintf(`{"error":"%s"}`, err.Error())
|
return fmt.Sprintf(`{"error":"%s"}`, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Route to appropriate handler based on message type
|
// Fill in a window ID if none was provided (Android typically has one window)
|
||||||
// For now, return success
|
if req.WebviewWindowID == 0 && req.WebviewWindowName == "" {
|
||||||
return `{"success":true}`
|
windows := app.Window.GetAll()
|
||||||
|
if len(windows) > 0 {
|
||||||
|
req.WebviewWindowID = uint32(windows[0].ID())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
globalMessageProcLock.RLock()
|
||||||
|
messageProc := globalMessageProc
|
||||||
|
globalMessageProcLock.RUnlock()
|
||||||
|
|
||||||
|
if messageProc == nil {
|
||||||
|
androidLogf("error", "🤖 [handleMessageForAndroid] MessageProcessor not initialized")
|
||||||
|
return `{"error":"MessageProcessor not initialized"}`
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
result, err := messageProc.HandleRuntimeCallWithIDs(ctx, &req)
|
||||||
|
if err != nil {
|
||||||
|
androidLogf("error", "🤖 [handleMessageForAndroid] Error: %v", err)
|
||||||
|
return fmt.Sprintf(`{"error":"%s"}`, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if result == nil {
|
||||||
|
return `{"success":true}`
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := json.Marshal(result)
|
||||||
|
if err != nil {
|
||||||
|
androidLogf("error", "🤖 [handleMessageForAndroid] Marshal error: %v", err)
|
||||||
|
return fmt.Sprintf(`{"error":"%s"}`, err.Error())
|
||||||
|
}
|
||||||
|
return string(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMimeTypeForPath(path string) string {
|
func getMimeTypeForPath(path string) string {
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,12 @@
|
||||||
package application
|
package application
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"sync"
|
"sync"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"encoding/json"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -38,6 +39,12 @@ func (a *App) platformRun() {
|
||||||
globalApp = a
|
globalApp = a
|
||||||
globalAppLock.Unlock()
|
globalAppLock.Unlock()
|
||||||
|
|
||||||
|
// Create MessageProcessor so message callbacks can route runtime calls
|
||||||
|
messageProc := NewMessageProcessor(slog.Default())
|
||||||
|
globalMessageProcLock.Lock()
|
||||||
|
globalMessageProc = messageProc
|
||||||
|
globalMessageProcLock.Unlock()
|
||||||
|
|
||||||
androidLogf("info", "🤖 [application_android.go] Waiting for Android lifecycle...")
|
androidLogf("info", "🤖 [application_android.go] Waiting for Android lifecycle...")
|
||||||
|
|
||||||
// Block forever - Android manages the app lifecycle via JNI callbacks
|
// Block forever - Android manages the app lifecycle via JNI callbacks
|
||||||
|
|
@ -171,16 +178,58 @@ func serveAssetForAndroid(app *App, path string) ([]byte, error) {
|
||||||
return nil, fmt.Errorf("asset serving not yet implemented: %s", path)
|
return nil, fmt.Errorf("asset serving not yet implemented: %s", path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Global MessageProcessor for message callbacks
|
||||||
|
var (
|
||||||
|
globalMessageProc *MessageProcessor
|
||||||
|
globalMessageProcLock sync.RWMutex
|
||||||
|
)
|
||||||
|
|
||||||
func handleMessageForAndroid(app *App, message string) string {
|
func handleMessageForAndroid(app *App, message string) string {
|
||||||
// Parse the message
|
// Some messages are plain strings (e.g. "wails:runtime:ready"), not JSON.
|
||||||
var msg map[string]interface{}
|
if len(message) == 0 || message[0] != '{' {
|
||||||
if err := json.Unmarshal([]byte(message), &msg); err != nil {
|
androidLogf("debug", "🤖 [handleMessageForAndroid] Non-JSON message: %s", message)
|
||||||
|
return `{"success":true}`
|
||||||
|
}
|
||||||
|
|
||||||
|
var req RuntimeRequest
|
||||||
|
if err := json.Unmarshal([]byte(message), &req); err != nil {
|
||||||
|
androidLogf("error", "🤖 [handleMessageForAndroid] Failed to parse: %v", err)
|
||||||
return fmt.Sprintf(`{"error":"%s"}`, err.Error())
|
return fmt.Sprintf(`{"error":"%s"}`, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Route to appropriate handler based on message type
|
if req.WebviewWindowID == 0 && req.WebviewWindowName == "" {
|
||||||
// For now, return success
|
windows := app.Window.GetAll()
|
||||||
return `{"success":true}`
|
if len(windows) > 0 {
|
||||||
|
req.WebviewWindowID = uint32(windows[0].ID())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
globalMessageProcLock.RLock()
|
||||||
|
messageProc := globalMessageProc
|
||||||
|
globalMessageProcLock.RUnlock()
|
||||||
|
|
||||||
|
if messageProc == nil {
|
||||||
|
androidLogf("error", "🤖 [handleMessageForAndroid] MessageProcessor not initialized")
|
||||||
|
return `{"error":"MessageProcessor not initialized"}`
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
result, err := messageProc.HandleRuntimeCallWithIDs(ctx, &req)
|
||||||
|
if err != nil {
|
||||||
|
androidLogf("error", "🤖 [handleMessageForAndroid] Error: %v", err)
|
||||||
|
return fmt.Sprintf(`{"error":"%s"}`, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if result == nil {
|
||||||
|
return `{"success":true}`
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := json.Marshal(result)
|
||||||
|
if err != nil {
|
||||||
|
androidLogf("error", "🤖 [handleMessageForAndroid] Marshal error: %v", err)
|
||||||
|
return fmt.Sprintf(`{"error":"%s"}`, err.Error())
|
||||||
|
}
|
||||||
|
return string(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMimeTypeForPath(path string) string {
|
func getMimeTypeForPath(path string) string {
|
||||||
|
|
|
||||||
|
|
@ -523,6 +523,40 @@ func newIOSEvents() iosEvents {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var Android = newAndroidEvents()
|
||||||
|
|
||||||
|
type androidEvents struct {
|
||||||
|
ActivityCreated ApplicationEventType
|
||||||
|
ActivityStarted ApplicationEventType
|
||||||
|
ActivityResumed ApplicationEventType
|
||||||
|
ActivityPaused ApplicationEventType
|
||||||
|
ActivityStopped ApplicationEventType
|
||||||
|
ActivityDestroyed ApplicationEventType
|
||||||
|
ApplicationLowMemory ApplicationEventType
|
||||||
|
ApplicationConfigChanged ApplicationEventType
|
||||||
|
WebViewDidStartNavigation WindowEventType
|
||||||
|
WebViewDidFinishNavigation WindowEventType
|
||||||
|
WebViewDidFailNavigation WindowEventType
|
||||||
|
WebViewReceivedError WindowEventType
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAndroidEvents() androidEvents {
|
||||||
|
return androidEvents{
|
||||||
|
ActivityCreated: 1259,
|
||||||
|
ActivityStarted: 1260,
|
||||||
|
ActivityResumed: 1261,
|
||||||
|
ActivityPaused: 1262,
|
||||||
|
ActivityStopped: 1263,
|
||||||
|
ActivityDestroyed: 1264,
|
||||||
|
ApplicationLowMemory: 1265,
|
||||||
|
ApplicationConfigChanged: 1266,
|
||||||
|
WebViewDidStartNavigation: 1267,
|
||||||
|
WebViewDidFinishNavigation: 1268,
|
||||||
|
WebViewDidFailNavigation: 1269,
|
||||||
|
WebViewReceivedError: 1270,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func JSEvent(event uint) string {
|
func JSEvent(event uint) string {
|
||||||
return eventToJS[event]
|
return eventToJS[event]
|
||||||
}
|
}
|
||||||
|
|
@ -763,4 +797,16 @@ var eventToJS = map[uint]string{
|
||||||
1256: "ios:WebViewDidFinishNavigation",
|
1256: "ios:WebViewDidFinishNavigation",
|
||||||
1257: "ios:WebViewDidFailNavigation",
|
1257: "ios:WebViewDidFailNavigation",
|
||||||
1258: "ios:WebViewDecidePolicyForNavigationAction",
|
1258: "ios:WebViewDecidePolicyForNavigationAction",
|
||||||
|
1259: "android:ActivityCreated",
|
||||||
|
1260: "android:ActivityStarted",
|
||||||
|
1261: "android:ActivityResumed",
|
||||||
|
1262: "android:ActivityPaused",
|
||||||
|
1263: "android:ActivityStopped",
|
||||||
|
1264: "android:ActivityDestroyed",
|
||||||
|
1265: "android:ApplicationLowMemory",
|
||||||
|
1266: "android:ApplicationConfigChanged",
|
||||||
|
1267: "android:WebViewDidStartNavigation",
|
||||||
|
1268: "android:WebViewDidFinishNavigation",
|
||||||
|
1269: "android:WebViewDidFailNavigation",
|
||||||
|
1270: "android:WebViewReceivedError",
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue