From b1a07af1f72dc4bde98c0224eb8425f6d60b513d Mon Sep 17 00:00:00 2001 From: Chen Bin Date: Thu, 21 Nov 2024 18:22:09 +0800 Subject: [PATCH 1/3] WebView Javascript Support (macOS, ios, android). Related APIs: - `webView:registerCallback(name, function)`: Register a Lua function that can be called from JavaScript - `webView:on(eventName, listener)`: Listen for events from JavaScript - `webView:send(eventName, data)`: Send data to WebView - `webView:injectJS(script)`: Inject JavaScript code into WebView How to use: ```lua local myWebView = native.newWebView( display.contentCenterX, display.contentCenterY, display.actualContentWidth, display.actualContentHeight ) webView:registerCallback("getDeviceInfo", function(data) return { platform = system.getInfo("platform"), version = system.getInfo("architectureInfo"), language = system.getPreference("locale", "language"), deviceModel = system.getInfo("model") } end) webView:on("buttonClicked", function(data) print("Button clicked in WebView: " .. data.buttonId) -- Respond to WebView webView:send("buttonResponse", {message = "Received click for button " .. data.buttonId}) end) local function injectJS() webView:injectJS[[ function updateDeviceInfo() { NativeBridge.callNative("getDeviceInfo").then(info => { document.getElementById("deviceInfo").innerHTML = `Platform: ${info.platform}
Version: ${info.version}
Language: ${info.language}
Model: ${info.deviceModel}`; }); } document.getElementById("updateButton").addEventListener('click', function() { updateDeviceInfo(); NativeBridge.sendToLua("buttonClicked", {buttonId: "update"}); }); document.getElementById("greetButton").addEventListener('click', function() { NativeBridge.sendToLua("buttonClicked", {buttonId: "greet"}); }); NativeBridge.on("buttonResponse", function(data) { document.getElementById("response").textContent = data.message; }); ]] end local function webListener( event ) if event.type == "loaded" then injectJS() end end webView:request( "index.html", system.ResourceDirectory ) webView:addEventListener( "urlRequest", webListener ) ``` Here's an example HTML file that works with the above Lua and JavaScript code: ```html WebView Example

WebView Communication Example

``` --- librtt/Rtt_Event.cpp | 38 ++ librtt/Rtt_Event.h | 21 + librtt/Rtt_LuaContext.cpp | 34 ++ librtt/Rtt_LuaContext.h | 2 + .../android/ndk/Rtt_AndroidWebViewObject.cpp | 102 ++++- .../android/ndk/Rtt_AndroidWebViewObject.h | 5 + .../android/ndk/jni/JavaToNativeBridge.cpp | 43 ++ platform/android/ndk/jni/JavaToNativeBridge.h | 1 + platform/android/ndk/jni/JavaToNativeShim.cpp | 6 + .../android/ndk/jni/NativeToJavaBridge.cpp | 25 ++ platform/android/ndk/jni/NativeToJavaBridge.h | 1 + .../jni/com_ansca_corona_JavaToNativeShim.h | 8 + .../src/com/ansca/corona/CoronaWebView.java | 65 ++- .../com/ansca/corona/JavaToNativeShim.java | 10 +- .../com/ansca/corona/NativeToJavaBridge.java | 4 + .../sdk/src/com/ansca/corona/ViewManager.java | 13 + .../events/WebJSInterfaceCommonTask.java | 29 ++ .../Rtt_EmscriptenWebViewObject.cpp | 36 ++ .../emscripten/Rtt_EmscriptenWebViewObject.h | 4 + platform/iphone/Rtt_IPhoneWebViewObject.h | 4 + platform/iphone/Rtt_IPhoneWebViewObject.mm | 187 ++++++++- platform/linux/src/Rtt_LinuxWebView.cpp | 40 ++ platform/linux/src/Rtt_LinuxWebView.h | 4 + platform/mac/Rtt_MacWebViewObject.h | 31 +- platform/mac/Rtt_MacWebViewObject.mm | 375 ++++++++++++------ platform/resources/init.lua | 16 +- .../Rtt/Rtt_WinWebViewObject.cpp | 40 ++ .../Rtt/Rtt_WinWebViewObject.h | 4 + 28 files changed, 1009 insertions(+), 139 deletions(-) create mode 100644 platform/android/sdk/src/com/ansca/corona/events/WebJSInterfaceCommonTask.java diff --git a/librtt/Rtt_Event.cpp b/librtt/Rtt_Event.cpp index 5efc34922..34da2a04d 100755 --- a/librtt/Rtt_Event.cpp +++ b/librtt/Rtt_Event.cpp @@ -2742,6 +2742,44 @@ UrlRequestEvent::Push( lua_State *L ) const // ---------------------------------------------------------------------------- +CommonEvent::CommonEvent( const char *fEventName, const char *fData ) +: fEventName( fEventName ), + fData( fData ) +{ +} + +const char* +CommonEvent::Name() const +{ + return fEventName; +} + +int +CommonEvent::Push( lua_State *L ) const +{ + if ( Rtt_VERIFY( Super::Push( L ) ) ) + { + if ( fData ) + { + if ( 0 == LuaContext::JsonDecode( L, fData) ) + { + Rtt_Log( "CommonEvent::Push fData=%s, type=%s", fData, luaL_typename( L, -1 ) ); + lua_setfield( L, -2, "detail" ); + } + else + { + lua_pop( L, 1 ); + lua_pushstring( L, fData ); + lua_setfield( L, -2, "detail" ); + } + } + } + + return 1; +} + +// ---------------------------------------------------------------------------- + const char UserInputEvent::kName[] = "userInput"; const char* diff --git a/librtt/Rtt_Event.h b/librtt/Rtt_Event.h index 28b6f96de..16d523e74 100755 --- a/librtt/Rtt_Event.h +++ b/librtt/Rtt_Event.h @@ -1285,6 +1285,27 @@ class UrlRequestEvent : public VirtualEvent // ---------------------------------------------------------------------------- +// Common event +class CommonEvent : public VirtualEvent +{ + public: + typedef VirtualEvent Super; + typedef CommonEvent Self; + + public: + CommonEvent( const char *fEventName, const char *fData ); + + public: + virtual const char* Name() const; + virtual int Push( lua_State *L ) const; + + private: + const char *fEventName; + const char *fData; +}; + +// ---------------------------------------------------------------------------- + // Local event class UserInputEvent : public VirtualEvent { diff --git a/librtt/Rtt_LuaContext.cpp b/librtt/Rtt_LuaContext.cpp index 93672760b..3813eb7b1 100755 --- a/librtt/Rtt_LuaContext.cpp +++ b/librtt/Rtt_LuaContext.cpp @@ -1192,6 +1192,40 @@ LuaContext::IsBinaryLua( const char* filename ) return result; } +int +LuaContext::JsonEncode( lua_State *L, int index ) +{ + bool success = false; + + lua_getglobal( L, "require" ); + lua_pushstring( L, "json" ); + if ( DoCall( L, 1, 1 ) == 0 ) + { + lua_getfield( L, -1, "encode" ); + lua_remove(L, -2); + lua_pushvalue( L, index ); + success = ( DoCall( L, 1, 1 ) == 0 ); + } + return success ? 0 : -1; +} + +int +LuaContext::JsonDecode( lua_State *L, const char* json ) +{ + bool success = false; + + lua_getglobal( L, "require" ); + lua_pushstring( L, "json" ); + if ( DoCall( L, 1, 1 ) == 0 ) + { + lua_getfield( L, -1, "decode" ); + lua_remove(L, -2); + lua_pushstring(L, json); + success = ( DoCall( L, 1, 1 ) == 0 ); + } + return success ? 0 : -1; +} + // ---------------------------------------------------------------------------- LuaContext::LuaContext( ::lua_State* L ) diff --git a/librtt/Rtt_LuaContext.h b/librtt/Rtt_LuaContext.h index 3c94ced12..0e47a6cef 100644 --- a/librtt/Rtt_LuaContext.h +++ b/librtt/Rtt_LuaContext.h @@ -51,6 +51,8 @@ class LuaContext static int OpenJson( lua_State *L ); static int OpenWidget( lua_State *L ); static int OpenStoryboard( lua_State *L ); + static int JsonEncode( lua_State *L, int index ); + static int JsonDecode( lua_State *L, const char *json ); public: // Generic, re-entrant Lua callbacks diff --git a/platform/android/ndk/Rtt_AndroidWebViewObject.cpp b/platform/android/ndk/Rtt_AndroidWebViewObject.cpp index 0d91d66d7..e19c9f8b9 100644 --- a/platform/android/ndk/Rtt_AndroidWebViewObject.cpp +++ b/platform/android/ndk/Rtt_AndroidWebViewObject.cpp @@ -26,9 +26,10 @@ namespace Rtt { - // ---------------------------------------------------------------------------- +static const char* kCoronaEventPrefix = "JS_"; + AndroidWebViewObject::AndroidWebViewObject( const Rect& bounds, AndroidDisplayObjectRegistry *displayObjectRegistry, NativeToJavaBridge *ntjb ) : Super( bounds, displayObjectRegistry, ntjb ), @@ -251,6 +252,85 @@ AndroidWebViewObject::DeleteCookies( lua_State *L ) return 0; } +int +AndroidWebViewObject::InjectJS( lua_State *L ) +{ + const LuaProxyVTable& table = PlatformDisplayObject::GetWebViewObjectProxyVTable(); + AndroidWebViewObject *view = (AndroidWebViewObject *)luaL_todisplayobject(L, 1, table); + if ( view ) + { + const char *jsCode = lua_tostring( L, 2 ); + view->InjectJSCode( jsCode ); + } + + return 0; +} + +int +AndroidWebViewObject::RegisterCallback( lua_State *L ) +{ + const LuaProxyVTable& table = PlatformDisplayObject::GetWebViewObjectProxyVTable(); + AndroidWebViewObject *view = (AndroidWebViewObject *)luaL_todisplayobject( L, 1, table ); + if ( view ) + { + const char *eventName = lua_tostring( L, 2 ); + String jsEventName(kCoronaEventPrefix); + jsEventName.Append( eventName ); + view->AddEventListener( L, 3, jsEventName.GetString() ); + } + + return 0; +} + +int +AndroidWebViewObject::On( lua_State *L ) +{ + const LuaProxyVTable& table = PlatformDisplayObject::GetWebViewObjectProxyVTable(); + AndroidWebViewObject *view = (AndroidWebViewObject *)luaL_todisplayobject( L, 1, table ); + if ( view ) + { + const char *eventName = lua_tostring( L, 2 ); + String jsEventName(kCoronaEventPrefix); + jsEventName.Append( eventName ); + view->AddEventListener( L, 3, jsEventName.GetString() ); + } + + return 0; +} + +int +AndroidWebViewObject::Send( lua_State *L ) +{ + const LuaProxyVTable& table = PlatformDisplayObject::GetWebViewObjectProxyVTable(); + AndroidWebViewObject *view = (AndroidWebViewObject *)luaL_todisplayobject( L, 1, table ); + if ( view ) + { + const char* eventName = lua_tostring( L, 2 ); + const char* jsonContent = "{}"; + if ( 0 == LuaContext::JsonEncode( L, 3 ) ) + { + jsonContent = lua_tostring( L, -1 ); + } + + String s( "window.dispatchEvent(new CustomEvent('" ); + s.Append( kCoronaEventPrefix ); + s.Append( eventName ); + s.Append( "', {detail: " ); + s.Append( jsonContent ); + s.Append( "}));" ); + + view->InjectJSCode( s.GetString() ); + } + + return 0; +} + +void +AndroidWebViewObject::InjectJSCode( const char *jsCode ) +{ + fNativeToJavaBridge->WebViewRequestInjectJS( GetId(), jsCode ); +} + int AndroidWebViewObject::ValueForKey( lua_State *L, const char key[] ) const { @@ -263,6 +343,26 @@ AndroidWebViewObject::ValueForKey( lua_State *L, const char key[] ) const lua_pushlightuserdata( L, fNativeToJavaBridge ); lua_pushcclosure( L, Request, 1 ); } + else if ( strcmp( "injectJS", key ) == 0 ) + { + lua_pushlightuserdata( L, fNativeToJavaBridge ); + lua_pushcclosure( L, InjectJS, 1 ); + } + else if ( strcmp( "registerCallback", key ) == 0 ) + { + lua_pushlightuserdata( L, fNativeToJavaBridge ); + lua_pushcclosure( L, RegisterCallback, 1 ); + } + else if ( strcmp( "on", key ) == 0 ) + { + lua_pushlightuserdata( L, fNativeToJavaBridge ); + lua_pushcclosure( L, On, 1 ); + } + else if ( strcmp( "send", key ) == 0 ) + { + lua_pushlightuserdata( L, fNativeToJavaBridge ); + lua_pushcclosure( L, Send, 1 ); + } else if ( strcmp( "stop", key ) == 0 ) { lua_pushlightuserdata( L, fNativeToJavaBridge ); diff --git a/platform/android/ndk/Rtt_AndroidWebViewObject.h b/platform/android/ndk/Rtt_AndroidWebViewObject.h index 037ac2504..a1d7a25f6 100644 --- a/platform/android/ndk/Rtt_AndroidWebViewObject.h +++ b/platform/android/ndk/Rtt_AndroidWebViewObject.h @@ -51,10 +51,15 @@ class AndroidWebViewObject : public AndroidDisplayObject static int Reload( lua_State *L ); static int Resize( lua_State *L ); static int DeleteCookies( lua_State *L ); + static int InjectJS( lua_State *L ); + static int RegisterCallback( lua_State *L ); + static int On( lua_State *L ); + static int Send( lua_State *L ); public: void Request(const char *url, const MPlatform::Directory baseDirectory); void Request(const char *url, const char *baseUrl); + void InjectJSCode( const char *jsCode ); bool IsPopup() const { return fIsPopup; } bool IsAutoCancelEnabled() const { return fAutoCancelEnabled; } bool CanGoBack() const { return fCanGoBack; } diff --git a/platform/android/ndk/jni/JavaToNativeBridge.cpp b/platform/android/ndk/jni/JavaToNativeBridge.cpp index b51112ef4..c2869ed3e 100644 --- a/platform/android/ndk/jni/JavaToNativeBridge.cpp +++ b/platform/android/ndk/jni/JavaToNativeBridge.cpp @@ -1387,6 +1387,49 @@ JavaToNativeBridge::WebViewHistoryUpdated( JNIEnv * env, int id, jboolean canGoB view->SetCanGoForward(canGoForward); } +void +JavaToNativeBridge::WebViewJSInterfaceCommonEvent( JNIEnv * env, int id, jstring type, jstring data, jboolean noResult ) +{ + // Validate. + if (!fPlatform) + { + return; + } + + // Fetch the display object by ID. + Rtt::AndroidWebViewObject *view = (Rtt::AndroidWebViewObject*)(fPlatform->GetNativeDisplayObjectById(id)); + if (!view) + { + return; + } + + jstringResult typeS(env, type); + jstringResult dataS(env, data); + Rtt::CommonEvent e(typeS.getUTF8(), dataS.getUTF8()); + + lua_State *L = view->GetL(); + Rtt_Log( "WebViewJSInterfaceCommonEvent: type=%s, data=%s", typeS.getUTF8(), dataS.getUTF8() ); + int status = view->Rtt::DisplayObject::DispatchEventWithTarget( L, e, 1 ); + if ( status == 0 && (! noResult ) ) + { + int retValueIndex = lua_gettop( L ); + const char* jsonContent = "{}"; + if ( 0 == Rtt::LuaContext::JsonEncode( L, retValueIndex ) ) + { + jsonContent = lua_tostring( L, -1 ); + } + Rtt::String s( "window.dispatchEvent(new CustomEvent('" ); + s.Append( typeS.getUTF8() ); + s.Append( "', {detail: " ); + s.Append( jsonContent ); + s.Append( "}));" ); + view->InjectJSCode( s.GetString() ); + + lua_pop( L, 1 ); + } + lua_pop( L, 1 ); +} + void JavaToNativeBridge::WebViewClosed( JNIEnv * env, int id ) { diff --git a/platform/android/ndk/jni/JavaToNativeBridge.h b/platform/android/ndk/jni/JavaToNativeBridge.h index b4dd2b998..3011ee1d4 100644 --- a/platform/android/ndk/jni/JavaToNativeBridge.h +++ b/platform/android/ndk/jni/JavaToNativeBridge.h @@ -98,6 +98,7 @@ class JavaToNativeBridge void WebViewFinishedLoadUrl( JNIEnv * env, int id, jstring url ); void WebViewDidFailLoadUrl( JNIEnv * env, int id, jstring url, jstring msg, int code ); void WebViewHistoryUpdated( JNIEnv * env, int id, jboolean canGoBack, jboolean canGoForward ); + void WebViewJSInterfaceCommonEvent( JNIEnv * env, int id, jstring type, jstring data, jboolean noResult ); void WebViewClosed( JNIEnv * env, int id ); void AdsRequestEvent(bool isError); void ImagePickerEvent(JNIEnv *env, jstring selectedImageFileName); diff --git a/platform/android/ndk/jni/JavaToNativeShim.cpp b/platform/android/ndk/jni/JavaToNativeShim.cpp index f1b2fb467..4a67037ef 100644 --- a/platform/android/ndk/jni/JavaToNativeShim.cpp +++ b/platform/android/ndk/jni/JavaToNativeShim.cpp @@ -499,6 +499,12 @@ Java_com_ansca_corona_JavaToNativeShim_nativeWebViewHistoryUpdated(JNIEnv * env, JavaToNativeBridgeFromMemoryAddress(bridgeAddress)->WebViewHistoryUpdated( env, id, canGoBack, canGoForward ); } +JNIEXPORT void JNICALL +Java_com_ansca_corona_JavaToNativeShim_nativeWebViewJSInterfaceCommonEvent(JNIEnv * env, jclass cd, jlong bridgeAddress, jint id, jstring type, jstring data, jboolean noResult) +{ + JavaToNativeBridgeFromMemoryAddress(bridgeAddress)->WebViewJSInterfaceCommonEvent( env, id, type, data, noResult ); +} + JNIEXPORT void JNICALL Java_com_ansca_corona_JavaToNativeShim_nativeWebViewClosed(JNIEnv * env, jclass cd, jlong bridgeAddress, jint id) { diff --git a/platform/android/ndk/jni/NativeToJavaBridge.cpp b/platform/android/ndk/jni/NativeToJavaBridge.cpp index e28ac4048..35586e4d8 100644 --- a/platform/android/ndk/jni/NativeToJavaBridge.cpp +++ b/platform/android/ndk/jni/NativeToJavaBridge.cpp @@ -3107,6 +3107,31 @@ NativeToJavaBridge::WebViewRequestDeleteCookies( int id ) HandleJavaException(); } +void +NativeToJavaBridge::WebViewRequestInjectJS( int id, const char * jsCode ) +{ + NativeTrace trace( "NativeToJavaBridge::WebViewRequestInjectJS" ); + + jclassInstance bridge( GetJNIEnv(), kNativeToJavaBridge ); + + if ( bridge.isValid() ) + { + jmethodID mid = bridge.getEnv()->GetStaticMethodID( bridge.getClass(), + "callWebViewInjectJS", "(Lcom/ansca/corona/CoronaRuntime;ILjava/lang/String;)V" ); + + if ( mid != NULL ) + { + jstringParam textJ( bridge.getEnv(), jsCode ); + if ( textJ.isValid() ) + { + bridge.getEnv()->CallStaticVoidMethod( bridge.getClass(), mid, fCoronaRuntime, id, textJ.getValue() ); + HandleJavaException(); + } + } + } +} + + void NativeToJavaBridge::VideoViewCreate( int id, int left, int top, int width, int height) diff --git a/platform/android/ndk/jni/NativeToJavaBridge.h b/platform/android/ndk/jni/NativeToJavaBridge.h index fddd8f928..a033cfc98 100644 --- a/platform/android/ndk/jni/NativeToJavaBridge.h +++ b/platform/android/ndk/jni/NativeToJavaBridge.h @@ -230,6 +230,7 @@ class NativeToJavaBridge void WebViewRequestGoBack( int id ); void WebViewRequestGoForward( int id ); void WebViewRequestDeleteCookies( int id ); + void WebViewRequestInjectJS( int id, const char * jsCode ); bool WebPopupShouldLoadUrl( int id, const char * url ); bool WebPopupDidFailLoadUrl( int id, const char * url, const char * msg, int code ); diff --git a/platform/android/ndk/jni/com_ansca_corona_JavaToNativeShim.h b/platform/android/ndk/jni/com_ansca_corona_JavaToNativeShim.h index 4fbd1c288..d6c289f04 100644 --- a/platform/android/ndk/jni/com_ansca_corona_JavaToNativeShim.h +++ b/platform/android/ndk/jni/com_ansca_corona_JavaToNativeShim.h @@ -427,6 +427,14 @@ JNIEXPORT void JNICALL Java_com_ansca_corona_JavaToNativeShim_nativeWebViewDidFa JNIEXPORT void JNICALL Java_com_ansca_corona_JavaToNativeShim_nativeWebViewHistoryUpdated (JNIEnv *, jclass, jlong, jint, jboolean, jboolean); +/* + * Class: com_ansca_corona_JavaToNativeShim + * Method: nativeWebViewJSInterfaceCommonEvent + * Signature: (ILjava/lang/String;Ljava/lang/String;I)V + */ +JNIEXPORT void JNICALL Java_com_ansca_corona_JavaToNativeShim_nativeWebViewJSInterfaceCommonEvent + (JNIEnv *, jclass, jlong, jint, jstring, jstring, jboolean); + /* * Class: com_ansca_corona_JavaToNativeShim * Method: nativeWebViewShouldLoadUrl diff --git a/platform/android/sdk/src/com/ansca/corona/CoronaWebView.java b/platform/android/sdk/src/com/ansca/corona/CoronaWebView.java index 9f02d05fe..9ef39c615 100644 --- a/platform/android/sdk/src/com/ansca/corona/CoronaWebView.java +++ b/platform/android/sdk/src/com/ansca/corona/CoronaWebView.java @@ -13,15 +13,70 @@ import android.graphics.Color; import android.webkit.WebView; import android.webkit.WebViewClient; +import android.webkit.JavascriptInterface; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; +import org.json.JSONException; +import org.json.JSONObject; + import java.util.LinkedList; import java.util.List; /** View for displaying web pages. */ public class CoronaWebView extends WebView implements NativePropertyResponder { + private static final String CORONA_INTERFACE_NAME = "corona"; + + // JavaScript injection code + private static final String NATIVE_BRIDGE_CODE = + "const NativeBridge = {\n" + + " callNative: function(method, args) {\n" + + " return new Promise((resolve, reject) => {\n" + + " var eventName = 'JS_' + method;\n" + + " window.addEventListener(eventName, function(e) {\n" + + " resolve(e.detail);\n" + + " }, { once: true });\n" + + " window.corona.postMessage(JSON.stringify({\n" + + " type: eventName,\n" + + " data: JSON.stringify(args),\n" + + " noResult: false\n" + + " }));\n" + + " });\n" + + " },\n" + + " sendToLua: function(event, data) {\n" + + " var eventName = 'JS_' + event;\n" + + " window.corona.postMessage(JSON.stringify({\n" + + " type: eventName,\n" + + " data: JSON.stringify(data),\n" + + " noResult: true\n" + + " }));\n" + + " },\n" + + " on: function(event, callback, options) {\n" + + " var eventName = 'JS_' + event;\n" + + " window.addEventListener(eventName, function(e) {\n" + + " callback(e.detail)\n" + + " }, options);\n" + + " }\n" + + "};\n"; + + // JavaScript interface class + private class WebViewJSInterface { + @JavascriptInterface + public void postMessage(String message) { + try { + JSONObject json = new JSONObject(message); + String type = json.getString("type"); + String data = json.getString("data"); + boolean noResult = json.optBoolean("noResult", false); + + fCoronaRuntime.getTaskDispatcher().send(new com.ansca.corona.events.WebJSInterfaceCommonTask(getId(), type, data, noResult)); + } catch (JSONException e) { + e.printStackTrace(); + } + } + } + /** * Class providing URL request source type IDs matching Corona's C++ UrlRequestEvent::Type enum. * These types indicate where the URL request came from such as via a tapped link, reload, etc. @@ -183,7 +238,9 @@ public void onHideCustomView() { ApiLevel21.setAcceptThirdPartyCookies(this, true); ApiLevel21.setMixedContentModeToAlwaysAllowFor(settings); } - + + addJavascriptInterface(new WebViewJSInterface(), CORONA_INTERFACE_NAME); + // Set up web view to have the focus when touched. // This allows the virtual keyboard to appear when the user taps on a text field. setOnTouchListener(new android.view.View.OnTouchListener() { @@ -484,7 +541,11 @@ public void onPageFinished(WebView view, String url) { fIsLoading = false; super.onPageFinished(view, url); - + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { + view.evaluateJavascript(NATIVE_BRIDGE_CODE, null); + } + if (fCoronaRuntime == null || !fCoronaRuntime.isRunning()) { return; } diff --git a/platform/android/sdk/src/com/ansca/corona/JavaToNativeShim.java b/platform/android/sdk/src/com/ansca/corona/JavaToNativeShim.java index 3bf70e5eb..a34b46d69 100644 --- a/platform/android/sdk/src/com/ansca/corona/JavaToNativeShim.java +++ b/platform/android/sdk/src/com/ansca/corona/JavaToNativeShim.java @@ -95,6 +95,7 @@ private static native boolean nativeKeyEvent( private static native void nativeWebViewFinishedLoadUrl( long bridgeAddress, int id, String url ); private static native void nativeWebViewDidFailLoadUrl( long bridgeAddress, int id, String url, String msg, int code ); private static native void nativeWebViewHistoryUpdated( long bridgeAddress, int id, boolean canGoBack, boolean canGoForward ); + private static native void nativeWebViewJSInterfaceCommonEvent( long bridgeAddress, int id, String type, String data, boolean noResult ); private static native void nativeWebViewClosed( long bridgeAddress, int id ); private static native void nativeImagePickerEvent( long bridgeAddress, String selectedImageFileName ); private static native void nativeAbortShowingImageProvider( long bridgeAddress ); @@ -620,7 +621,14 @@ public static void webViewHistoryUpdated( CoronaRuntime runtime, int id, boolean } nativeWebViewHistoryUpdated( runtime.getJavaToNativeBridgeAddress(), id, canGoBack, canGoForward ); } - + + public static void webViewJSInterfaceCommonEvent( CoronaRuntime runtime, int id, String type, String data, boolean noResult ) { + if (runtime == null || runtime.wasDisposed()) { + return; + } + nativeWebViewJSInterfaceCommonEvent( runtime.getJavaToNativeBridgeAddress(), id, type, data, noResult ); + } + public static void webViewClosed( CoronaRuntime runtime, int id ) { if (runtime == null || runtime.wasDisposed()) { return; diff --git a/platform/android/sdk/src/com/ansca/corona/NativeToJavaBridge.java b/platform/android/sdk/src/com/ansca/corona/NativeToJavaBridge.java index b6dc05351..db6da62d3 100644 --- a/platform/android/sdk/src/com/ansca/corona/NativeToJavaBridge.java +++ b/platform/android/sdk/src/com/ansca/corona/NativeToJavaBridge.java @@ -2624,6 +2624,10 @@ protected static void callWebViewRequestDeleteCookies(int id, CoronaRuntime runt runtime.getViewManager().requestWebViewDeleteCookies(id); } + protected static void callWebViewInjectJS( CoronaRuntime runtime, int id, String jsCode ) { + runtime.getViewManager().requestWebViewInjectJS(id, jsCode); + } + protected static void callVideoViewCreate(CoronaRuntime runtime, int id, int left, int top, int width, int height) { runtime.getViewManager().addVideoView(id, left, top, width, height); diff --git a/platform/android/sdk/src/com/ansca/corona/ViewManager.java b/platform/android/sdk/src/com/ansca/corona/ViewManager.java index bae043898..9831563a5 100644 --- a/platform/android/sdk/src/com/ansca/corona/ViewManager.java +++ b/platform/android/sdk/src/com/ansca/corona/ViewManager.java @@ -1397,6 +1397,19 @@ public void run() { }); } + public void requestWebViewInjectJS(final int id, final String jsCode) { + postOnUiThread(new Runnable() { + public void run() { + CoronaWebView view = getDisplayObjectById(CoronaWebView.class, id); + if (view != null) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { + view.evaluateJavascript(jsCode, null); + } + } + } + }); + } + public void addMapView(final int id, final int left, final int top, final int width, final int height) { // Throw an exception if this application does not have the following permission. android.content.Context context = CoronaEnvironment.getApplicationContext(); diff --git a/platform/android/sdk/src/com/ansca/corona/events/WebJSInterfaceCommonTask.java b/platform/android/sdk/src/com/ansca/corona/events/WebJSInterfaceCommonTask.java new file mode 100644 index 000000000..6ec8153d2 --- /dev/null +++ b/platform/android/sdk/src/com/ansca/corona/events/WebJSInterfaceCommonTask.java @@ -0,0 +1,29 @@ +////////////////////////////////////////////////////////////////////////////// +// +// This file is part of the Corona game engine. +// For overview and more information on licensing please refer to README.md +// Home page: https://github.com/coronalabs/corona +// Contact: support@coronalabs.com +// +////////////////////////////////////////////////////////////////////////////// + +package com.ansca.corona.events; + +public class WebJSInterfaceCommonTask implements com.ansca.corona.CoronaRuntimeTask { + private int fId; + private String fType; + private String fData; + private boolean fNoResult; + + public WebJSInterfaceCommonTask(int id, String type, String data, boolean noResult) { + fId = id; + fType = type; + fData = data; + fNoResult = noResult; + } + + @Override + public void executeUsing(com.ansca.corona.CoronaRuntime runtime) { + com.ansca.corona.JavaToNativeShim.webViewJSInterfaceCommonEvent(runtime, fId, fType, fData, fNoResult); + } +} diff --git a/platform/emscripten/Rtt_EmscriptenWebViewObject.cpp b/platform/emscripten/Rtt_EmscriptenWebViewObject.cpp index b3db8575e..7a09ccb25 100644 --- a/platform/emscripten/Rtt_EmscriptenWebViewObject.cpp +++ b/platform/emscripten/Rtt_EmscriptenWebViewObject.cpp @@ -63,6 +63,22 @@ namespace Rtt { lua_pushcfunction(L, Request); } + else if ( strcmp( "injectJS", key ) == 0 ) + { + lua_pushcfunction( L, InjectJS ); + } + else if ( strcmp( "registerCallback", key ) == 0 ) + { + lua_pushcfunction( L, RegisterCallback ); + } + else if ( strcmp( "on", key ) == 0 ) + { + lua_pushcfunction( L, On ); + } + else if ( strcmp( "send", key ) == 0 ) + { + lua_pushcfunction( L, Send ); + } else if (strcmp("stop", key) == 0) { lua_pushcfunction(L, Stop); @@ -204,6 +220,26 @@ namespace Rtt return 0; } + int EmscriptenWebViewObject::InjectJS(lua_State *L) + { + return 0; + } + + int EmscriptenWebViewObject::RegisterCallback(lua_State *L) + { + return 0; + } + + int EmscriptenWebViewObject::On(lua_State *L) + { + return 0; + } + + int EmscriptenWebViewObject::Send(lua_State *L) + { + return 0; + } + #pragma endregion // ---------------------------------------------------------------------------- diff --git a/platform/emscripten/Rtt_EmscriptenWebViewObject.h b/platform/emscripten/Rtt_EmscriptenWebViewObject.h index ec0211a40..11ac3ade5 100644 --- a/platform/emscripten/Rtt_EmscriptenWebViewObject.h +++ b/platform/emscripten/Rtt_EmscriptenWebViewObject.h @@ -41,6 +41,10 @@ class EmscriptenWebViewObject : public EmscriptenDisplayObject static int Forward(lua_State *L); static int Reload(lua_State *L); static int Resize(lua_State *L); + static int InjectJS(lua_State *L); + static int RegisterCallback(lua_State *L); + static int On(lua_State *L); + static int Send(lua_State *L); }; // ---------------------------------------------------------------------------- diff --git a/platform/iphone/Rtt_IPhoneWebViewObject.h b/platform/iphone/Rtt_IPhoneWebViewObject.h index 14650e9ce..4e0e186af 100644 --- a/platform/iphone/Rtt_IPhoneWebViewObject.h +++ b/platform/iphone/Rtt_IPhoneWebViewObject.h @@ -47,6 +47,10 @@ class IPhoneWebViewObject : public IPhoneDisplayObject static int Reload( lua_State *L ); static int Resize( lua_State *L ); static int DeleteCookies( lua_State *L ); + static int InjectJS( lua_State *L ); + static int RegisterCallback( lua_State *L ); + static int On( lua_State *L ); + static int Send( lua_State *L ); // static int SetBackgroundColor( lua_State *L ); public: diff --git a/platform/iphone/Rtt_IPhoneWebViewObject.mm b/platform/iphone/Rtt_IPhoneWebViewObject.mm index a5637c6c5..3a61b8873 100644 --- a/platform/iphone/Rtt_IPhoneWebViewObject.mm +++ b/platform/iphone/Rtt_IPhoneWebViewObject.mm @@ -41,10 +41,46 @@ #import "CoronaLuaObjC+NSObject.h" +#define JS(...) [[NSString alloc] initWithCString:#__VA_ARGS__ encoding:NSUTF8StringEncoding] + // ---------------------------------------------------------------------------- static CGFloat kAnimationDuration = 0.3; +NSString * const kCoronaEventPrefix = @"JS_"; +NSString * const kCorona4JS = @"corona"; +NSString * const kNativeBridgeCode = JS( + const NativeBridge = { + callNative: function(method, args) { + return new Promise((resolve, reject) => { + var eventName = "JS_" + method; + window.addEventListener(eventName, function(e) { + resolve(e.detail); + }, { once: true }); + window.webkit.messageHandlers.corona.postMessage({ + type: eventName, + data: JSON.stringify(args), + noResult: false + }); + }); + }, + sendToLua: function(event, data) { + var eventName = "JS_" + event; + window.webkit.messageHandlers.corona.postMessage({ + type: eventName, + data: JSON.stringify(data), + noResult: true + }); + }, + on: function(event, callback, options) { + var eventName = "JS_" + event; + window.addEventListener(eventName, function(e) { + callback(e.detail) + }, options); + } + }; +); + //static void //RectToCGRect( const Rtt::Rect& bounds, CGRect * outRect ) //{ @@ -68,7 +104,7 @@ // ---------------------------------------------------------------------------- -@interface Rtt_iOSWebViewContainer : UIView< WKNavigationDelegate > +@interface Rtt_iOSWebViewContainer : UIView< WKNavigationDelegate, WKUIDelegate, WKScriptMessageHandler > { Rtt::IPhoneWebViewObject *fOwner; WKWebViewConfiguration *fWebViewConfiguration; @@ -120,6 +156,7 @@ -(WKWebView*)webView [fWebViewConfiguration release]; fWebViewConfiguration = nil; fWebView.navigationDelegate = self; + fWebView.UIDelegate = self; // fWebView.scalesPageToFit = YES; fActivityView = [[UIView alloc] initWithFrame:webViewRect]; @@ -154,6 +191,10 @@ - (id)initWithFrame:(CGRect)rect if ( self ) { fWebViewConfiguration = [[WKWebViewConfiguration alloc] init]; + WKUserContentController *userContentController = [[WKUserContentController alloc] init]; + [userContentController addScriptMessageHandler:self name:kCorona4JS]; + fWebViewConfiguration.userContentController = userContentController; + fOwner = nil; fLoadingURL = nil; fWebView = nil; @@ -466,6 +507,14 @@ - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigati decisionHandler(WKNavigationActionPolicyAllow); // Always load } +- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures +{ + if (!navigationAction.targetFrame.isMainFrame) { + [webView loadRequest:navigationAction.request]; + } + return nil; +} + - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation { UIActivityIndicatorView *indicator = [[fActivityView subviews] objectAtIndex:0]; @@ -482,6 +531,8 @@ - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigat [self hideActivityViewIndicator]; + [fWebView evaluateJavaScript:kNativeBridgeCode completionHandler:nil]; + NSURL *url = webView.URL; const char *urlString = [[url absoluteString] UTF8String]; UrlRequestEvent e( urlString, UrlRequestEvent::kLoaded ); @@ -521,6 +572,44 @@ - (void)hideActivityViewIndicator [UIView commitAnimations]; } +// Implement WKScriptMessageHandler protocol +- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message +{ + if ([message.name isEqualToString:kCorona4JS]) + { + NSDictionary *messageBody = message.body; + + using namespace Rtt; + + const char* type = [messageBody[@"type"] UTF8String]; + const char* data = [messageBody[@"data"] UTF8String]; + bool noResult = [messageBody[@"noResult"] boolValue]; + + CommonEvent e(type, data); + + lua_State *L = fOwner->GetL(); + int status = fOwner->DisplayObject::DispatchEventWithTarget( L, e, 1 ); + if ( status == 0 && (! noResult ) ) + { + int retValueIndex = lua_gettop( L ); + const char* jsonContent = "{}"; + if ( 0 == LuaContext::JsonEncode( L, retValueIndex ) ) + { + jsonContent = lua_tostring( L, -1 ); + } + + NSString *jsCode = [NSString stringWithFormat:@"window.dispatchEvent(new CustomEvent('%s', { detail: %s }));", type, jsonContent]; + [fWebView evaluateJavaScript:jsCode completionHandler:^(id _Nullable response, NSError * _Nullable error){ + if ( error != nil ) { + NSLog(@"WKUserContentController error: %s, %@", type, error); + } + }]; + lua_pop( L, 1 ); + } + lua_pop( L, 1 ); + } +} + @end // ---------------------------------------------------------------------------- @@ -802,6 +891,86 @@ - (void)hideActivityViewIndicator } */ +int +IPhoneWebViewObject::InjectJS( lua_State *L ) +{ + const LuaProxyVTable& table = PlatformDisplayObject::GetWebViewObjectProxyVTable(); + IPhoneWebViewObject *o = (IPhoneWebViewObject *)luaL_todisplayobject( L, 1, table ); + if ( o ) + { + Rtt_iOSWebViewContainer *container = (Rtt_iOSWebViewContainer*)o->GetView(); + const char *jsContent = lua_tostring( L, 2 ); + NSString *jsCode = [NSString stringWithUTF8String:jsContent]; + // Rtt_Log( "InjectJS: %s ", [jsCode UTF8String] ); + + [container.webView evaluateJavaScript:jsCode completionHandler:^(id _Nullable response, NSError * _Nullable error){ + if ( error != nil ) { + NSLog(@"InjectJS error: %@", error); + } + }]; + } + + return 0; +} + +int +IPhoneWebViewObject::RegisterCallback( lua_State *L ) +{ + const LuaProxyVTable& table = PlatformDisplayObject::GetWebViewObjectProxyVTable(); + IPhoneWebViewObject *o = (IPhoneWebViewObject *)luaL_todisplayobject( L, 1, table ); + if ( o ) + { + const char *eventName = lua_tostring( L, 2 ); + NSString *jsEventName = [NSString stringWithFormat:@"%@%s", kCoronaEventPrefix, eventName]; + o->AddEventListener( L, 3, [jsEventName UTF8String] ); + } + + return 0; +} + +int +IPhoneWebViewObject::On( lua_State *L ) +{ + const LuaProxyVTable& table = PlatformDisplayObject::GetWebViewObjectProxyVTable(); + IPhoneWebViewObject *o = (IPhoneWebViewObject *)luaL_todisplayobject( L, 1, table ); + if ( o ) + { + const char *eventName = lua_tostring( L, 2 ); + NSString *jsEventName = [NSString stringWithFormat:@"%@%s", kCoronaEventPrefix, eventName]; + o->AddEventListener( L, 3, [jsEventName UTF8String] ); + } + + return 0; +} + +int +IPhoneWebViewObject::Send( lua_State *L ) +{ + const LuaProxyVTable& table = PlatformDisplayObject::GetWebViewObjectProxyVTable(); + IPhoneWebViewObject *o = (IPhoneWebViewObject *)luaL_todisplayobject( L, 1, table ); + if ( o ) + { + const char* eventName = lua_tostring( L, 2 ); + + Rtt_iOSWebViewContainer *container = (Rtt_iOSWebViewContainer*)o->GetView(); + const char* jsonContent = "{}"; + if ( 0 == LuaContext::JsonEncode( L, 3 ) ) + { + jsonContent = lua_tostring( L, -1 ); + } + + NSString *jsCode = [NSString stringWithFormat:@"window.dispatchEvent(new CustomEvent('%@%s', { detail: %s }));", kCoronaEventPrefix, eventName, jsonContent]; + [container.webView evaluateJavaScript:jsCode completionHandler:^(id _Nullable response, NSError * _Nullable error){ + if ( error != nil ) { + NSLog(@"Send '%s' error: %@", eventName, error); + } + }]; + lua_pop( L, 1 ); + } + + return 0; +} + int IPhoneWebViewObject::ValueForKey( lua_State *L, const char key[] ) const { @@ -818,6 +987,22 @@ - (void)hideActivityViewIndicator { lua_pushcfunction( L, Request ); } + else if ( strcmp( "injectJS", key ) == 0 ) + { + lua_pushcfunction( L, InjectJS ); + } + else if ( strcmp( "registerCallback", key ) == 0 ) + { + lua_pushcfunction( L, RegisterCallback ); + } + else if ( strcmp( "on", key ) == 0 ) + { + lua_pushcfunction( L, On ); + } + else if ( strcmp( "send", key ) == 0 ) + { + lua_pushcfunction( L, Send ); + } else if ( strcmp( "stop", key ) == 0 ) { lua_pushcfunction( L, Stop ); diff --git a/platform/linux/src/Rtt_LinuxWebView.cpp b/platform/linux/src/Rtt_LinuxWebView.cpp index a0c4c7bd0..04645424b 100644 --- a/platform/linux/src/Rtt_LinuxWebView.cpp +++ b/platform/linux/src/Rtt_LinuxWebView.cpp @@ -53,6 +53,22 @@ namespace Rtt { lua_pushcfunction(L, Request); } + else if ( strcmp( "injectJS", key ) == 0 ) + { + lua_pushcfunction( L, InjectJS ); + } + else if ( strcmp( "registerCallback", key ) == 0 ) + { + lua_pushcfunction( L, RegisterCallback ); + } + else if ( strcmp( "on", key ) == 0 ) + { + lua_pushcfunction( L, On ); + } + else if ( strcmp( "send", key ) == 0 ) + { + lua_pushcfunction( L, Send ); + } else if (strcmp("stop", key) == 0) { lua_pushcfunction(L, Stop); @@ -292,6 +308,30 @@ namespace Rtt return 0; } + int LinuxWebView::InjectJS(lua_State *L) + { + CoronaLuaWarning(L, "The native WebView:injectJS() function is not supported on Linux."); + return 0; + } + + int LinuxWebView::RegisterCallback(lua_State *L) + { + CoronaLuaWarning(L, "The native WebView:registerCallback() function is not supported on Linux."); + return 0; + } + + int LinuxWebView::On(lua_State *L) + { + CoronaLuaWarning(L, "The native WebView:on() function is not supported on Linux."); + return 0; + } + + int LinuxWebView::Send(lua_State *L) + { + CoronaLuaWarning(L, "The native WebView:send() function is not supported on Linux."); + return 0; + } + void LinuxWebView::Draw(Renderer& renderer) const { if (fWebView && fWebView->fData.fFillTexture0) diff --git a/platform/linux/src/Rtt_LinuxWebView.h b/platform/linux/src/Rtt_LinuxWebView.h index d4120ba18..f03ed3821 100644 --- a/platform/linux/src/Rtt_LinuxWebView.h +++ b/platform/linux/src/Rtt_LinuxWebView.h @@ -47,6 +47,10 @@ namespace Rtt static void onWebPopupLoadedEvent(); static void onWebPopupErrorEvent(); static int OnDeleteCookies(lua_State *L); + static int InjectJS(lua_State *L); + static int RegisterCallback(lua_State *L); + static int On(lua_State *L); + static int Send(lua_State *L); smart_ptr fWebView; diff --git a/platform/mac/Rtt_MacWebViewObject.h b/platform/mac/Rtt_MacWebViewObject.h index ee084e858..823964137 100644 --- a/platform/mac/Rtt_MacWebViewObject.h +++ b/platform/mac/Rtt_MacWebViewObject.h @@ -16,8 +16,6 @@ // ---------------------------------------------------------------------------- -@class Rtt_NSWebView; - namespace Rtt { @@ -50,6 +48,10 @@ class MacWebViewObject : public MacDisplayObject static int Reload( lua_State *L ); static int Resize( lua_State *L ); static int DeleteCookies( lua_State *L ); + static int InjectJS( lua_State *L ); + static int RegisterCallback( lua_State *L ); + static int On( lua_State *L ); + static int Send( lua_State *L ); // static int SetBackgroundColor( lua_State *L ); public: @@ -78,23 +80,20 @@ class MacWebViewObject : public MacDisplayObject // These WebKit protocols are not explicitly declared until 10.11 SDK, so // declare dummy protocols to keep the build working on earlier SDKs. -#if __MAC_OS_X_VERSION_MAX_ALLOWED < 101100 -@protocol WebUIDelegate -@end -@protocol WebFrameLoadDelegate -@end -@protocol WebPolicyDelegate -@end -#endif +// #if __MAC_OS_X_VERSION_MAX_ALLOWED < 101100 +// @protocol WebUIDelegate +// @end +// @protocol WebFrameLoadDelegate +// @end +// @protocol WebPolicyDelegate +// @end +// #endif -@interface Rtt_WebView : WebView -{ - Rtt::MacWebViewObject *owner; -} +// @interface Rtt_WebView : WKWebView -@property(nonatomic, assign) Rtt::MacWebViewObject *owner; +// @property(nonatomic, assign) Rtt::MacWebViewObject *owner; -@end +// @end // ---------------------------------------------------------------------------- diff --git a/platform/mac/Rtt_MacWebViewObject.mm b/platform/mac/Rtt_MacWebViewObject.mm index f80395f91..9cba9afd1 100644 --- a/platform/mac/Rtt_MacWebViewObject.mm +++ b/platform/mac/Rtt_MacWebViewObject.mm @@ -25,151 +25,196 @@ #include "Rtt_Rendering.h" #import -#import -// ---------------------------------------------------------------------------- - -@interface Rtt_WebView () +#define JS(...) [[NSString alloc] initWithCString:#__VA_ARGS__ encoding:NSUTF8StringEncoding] -- (id)initWithFrame:(NSRect)frameRect; +// ---------------------------------------------------------------------------- +NSString * const kCoronaEventPrefix = @"JS_"; +NSString * const kCorona4JS = @"corona"; +NSString * const kNativeBridgeCode = JS( + const NativeBridge = { + callNative: function(method, args) { + return new Promise((resolve, reject) => { + var eventName = "JS_" + method; + window.addEventListener(eventName, function(e) { + resolve(e.detail); + }, { once: true }); + window.webkit.messageHandlers.corona.postMessage({ + type: eventName, + data: JSON.stringify(args), + noResult: false + }); + }); + }, + sendToLua: function(event, data) { + var eventName = "JS_" + event; + window.webkit.messageHandlers.corona.postMessage({ + type: eventName, + data: JSON.stringify(data), + noResult: true + }); + }, + on: function(event, callback, options) { + var eventName = "JS_" + event; + window.addEventListener(eventName, function(e) { + callback(e.detail) + }, options); + } + }; +); -- (void)setDelegate:(id)delegate; +@interface Rtt_WebView : WKWebView -- (void)loadRequest:(NSURLRequest*)request; +@property(nonatomic, assign) Rtt::MacWebViewObject *owner; -- (void)loadHtmlString:(NSString*)htmlString baseURL:(NSURL*)baseUrl; +- (id)initWithFrame:(CGRect)rect; @end - @implementation Rtt_WebView @synthesize owner; -- (id)initWithFrame:(NSRect)frameRect; +- (id)initWithFrame:(CGRect)frameRect { - self = [super initWithFrame:frameRect frameName:nil groupName:nil]; - if ( self ) + WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init]; + configuration.preferences.javaScriptEnabled = YES; + configuration.preferences.javaScriptCanOpenWindowsAutomatically = YES; + + // Add userContentController + WKUserContentController *userContentController = [[WKUserContentController alloc] init]; + [userContentController addScriptMessageHandler:self name:kCorona4JS]; + configuration.userContentController = userContentController; + + self = [super initWithFrame:frameRect configuration:configuration]; + if (self) { owner = NULL; [self setWantsLayer:YES]; - [self setUIDelegate:self]; - [self setPolicyDelegate:self]; - [self setFrameLoadDelegate:self]; + [self setNavigationDelegate:self]; + [self setUIDelegate:self]; // Set UIDelegate to handle new windows } - return self; } -// Overriding this delegate method and doing nothing prevents JavaScript from -// modifying the size or position of the window containing the webview. Such -// behavior is usually disallowed by current web browsers so we ignore it too. -// (note that "- (BOOL)webViewIsResizable:(WebView *)sender;" is not effective) -- (void)webView:(WebView *)sender setFrame:(NSRect)frame +- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { - // NSLog(@"WebView: setFrame: %@", NSStringFromRect(frame)); + using namespace Rtt; - // do nothing -} + NSURL *url = navigationAction.request.URL; + BOOL shouldLoad = YES; -// Ignore window.close() from JavaScript because it exits the entire app but -// notify the app if it's listening -- (void)webViewClose:(WebView *)sender -{ - Rtt::UrlRequestEvent e( "window.close", Rtt::UrlRequestEvent::kOther ); - owner->DispatchEventWithTarget( e ); -} + // Handle special protocols (like tel:, mailto:, etc.) + if (![url.scheme isEqualToString:@"http"] && + ![url.scheme isEqualToString:@"https"] && + ![url.scheme isEqualToString:@"file"] && + ![url.scheme isEqualToString:@"about"]) + { + // For special protocols, use the system default handling method + if ([[NSWorkspace sharedWorkspace] openURL:url]) { + shouldLoad = NO; + } + } -- (void)setDelegate:(id)delegate -{ - [self setPolicyDelegate:delegate]; - [self setFrameLoadDelegate:delegate]; -} -- (void)loadHtmlString:(NSString*)htmlString baseURL:(NSURL*)baseUrl -{ - [[self mainFrame] loadHTMLString:htmlString baseURL:baseUrl]; + UrlRequestEvent::Type urlRequestType = UrlRequestEvent::kUnknown; + switch (navigationAction.navigationType) { + case WKNavigationTypeLinkActivated: + urlRequestType = UrlRequestEvent::kLink; + break; + case WKNavigationTypeFormSubmitted: + urlRequestType = UrlRequestEvent::kForm; + break; + case WKNavigationTypeBackForward: + urlRequestType = UrlRequestEvent::kHistory; + break; + case WKNavigationTypeReload: + urlRequestType = UrlRequestEvent::kReload; + break; + case WKNavigationTypeFormResubmitted: + urlRequestType = UrlRequestEvent::kFormResubmitted; + break; + case WKNavigationTypeOther: + urlRequestType = UrlRequestEvent::kOther; + break; + } + + const char *urlString = [[url absoluteString] UTF8String]; + UrlRequestEvent e(urlString, urlRequestType); + owner->DispatchEventWithTarget(e); + + // Determine whether to allow navigation based on shouldLoad + decisionHandler(shouldLoad ? WKNavigationActionPolicyAllow : WKNavigationActionPolicyCancel); } -- (void)loadRequest:(NSURLRequest*)request + +- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures { - [[self mainFrame] loadRequest:request]; + // If a link opens in a new window, open it in the current window instead + if (!navigationAction.targetFrame.isMainFrame) { + [webView loadRequest:navigationAction.request]; + } + return nil; } -// WebView doesn't have UIWebview: -//- (BOOL)webView:(WebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(WebViewNavigationType)navigationType -// http://forums.macrumors.com/showthread.php?t=1077692 -// Maybe this will work? -- (void)webView:(WebView *)sender decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id)listener +- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { - using namespace Rtt; + [self evaluateJavaScript:kNativeBridgeCode completionHandler:nil]; - NSURL *url = [request URL]; - - UrlRequestEvent::Type urlRequestType = UrlRequestEvent::kUnknown; - if (WebNavigationTypeLinkClicked == [[actionInformation objectForKey:WebActionNavigationTypeKey] intValue]) - { - urlRequestType = UrlRequestEvent::kLink; - } - else if (WebNavigationTypeFormSubmitted == [[actionInformation objectForKey:WebActionNavigationTypeKey] intValue]) - { - urlRequestType = UrlRequestEvent::kForm; - } - else if (WebNavigationTypeBackForward == [[actionInformation objectForKey:WebActionNavigationTypeKey] intValue]) - { - urlRequestType = UrlRequestEvent::kHistory; - } - else if (WebNavigationTypeReload == [[actionInformation objectForKey:WebActionNavigationTypeKey] intValue]) - { - urlRequestType = UrlRequestEvent::kReload; - } - else if (WebNavigationTypeFormResubmitted == [[actionInformation objectForKey:WebActionNavigationTypeKey] intValue]) - { - urlRequestType = UrlRequestEvent::kFormResubmitted; - } - else if (WebNavigationTypeOther == [[actionInformation objectForKey:WebActionNavigationTypeKey] intValue]) - { - urlRequestType = UrlRequestEvent::kOther; - } + using namespace Rtt; - const char *urlString = [[url absoluteString] UTF8String]; - UrlRequestEvent e( urlString, urlRequestType ); - owner->DispatchEventWithTarget( e ); // if listener succeeds, result will be modified as appropriate - [listener use]; + const char *urlString = [webView.URL.absoluteString UTF8String]; + UrlRequestEvent e(urlString, UrlRequestEvent::kLoaded); + owner->DispatchEventWithTarget(e); } -- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame +- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error { using namespace Rtt; - //This is a javascript hack, not sure how else to get the url request - NSString *rawLocationString = [sender stringByEvaluatingJavaScriptFromString:@"location.href;"]; - - const char *urlString = [rawLocationString UTF8String]; - UrlRequestEvent e( urlString, UrlRequestEvent::kLoaded ); - owner->DispatchEventWithTarget( e ); - + const char *urlString = [error.userInfo[NSURLErrorFailingURLStringErrorKey] UTF8String]; + + UrlRequestEvent e(urlString, [[error localizedDescription] UTF8String], (S32)[error code]); + owner->DispatchEventWithTarget(e); } -- (void)webView:(WebView *)sender didFailLoadWithError:(NSError *)error forFrame:(WebFrame *)frame +// Implement WKScriptMessageHandler protocol +- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { - using namespace Rtt; + if ([message.name isEqualToString:kCorona4JS]) + { + NSDictionary *messageBody = message.body; - const char *urlString = - [[[error userInfo] valueForKey:NSURLErrorFailingURLStringErrorKey] UTF8String]; + using namespace Rtt; - UrlRequestEvent e( - urlString, [[error localizedDescription] UTF8String], (S32) [error code] ); - owner->DispatchEventWithTarget( e ); + const char* type = [messageBody[@"type"] UTF8String]; + const char* data = [messageBody[@"data"] UTF8String]; + bool noResult = [messageBody[@"noResult"] boolValue]; -} + CommonEvent e(type, data); -// Implementing this UI delegate method with nothing in it, makes links with "target=_blank" load -// in the same webview (because it is returned here) which is the behavior we want (it matches -// iOS and Android) -- (WebView*)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)request -{ - return sender; + lua_State *L = owner->GetL(); + int status = owner->DisplayObject::DispatchEventWithTarget( L, e, 1 ); + if ( status == 0 && (! noResult ) ) + { + int retValueIndex = lua_gettop( L ); + const char* jsonContent = "{}"; + if ( 0 == LuaContext::JsonEncode( L, retValueIndex ) ) + { + jsonContent = lua_tostring( L, -1 ); + } + + NSString *jsCode = [NSString stringWithFormat:@"window.dispatchEvent(new CustomEvent('%s', { detail: %s }));", type, jsonContent]; + [self evaluateJavaScript:jsCode completionHandler:^(id _Nullable response, NSError * _Nullable error){ + if ( error != nil ) { + NSLog(@"WKUserContentController error: %s, %@", type, error); + } + }]; + lua_pop( L, 1 ); + } + lua_pop( L, 1 ); + } } + @end // ---------------------------------------------------------------------------- @@ -188,9 +233,11 @@ - (WebView*)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)r { // Nil out the delegate to prevent any notifications from being triggered on this dead object Rtt_WebView *view = (Rtt_WebView*)GetView(); - [view setDelegate:nil]; + [view.configuration.userContentController removeScriptMessageHandlerForName:kCorona4JS]; +// [view setDelegate:nil]; [view stopLoading:nil]; - [view close]; +// [view close]; + view.owner = NULL; view = NULL; } @@ -202,7 +249,7 @@ - (WebView*)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)r Rect screenBounds; GetScreenBounds( screenBounds ); - NSRect r = NSMakeRect( screenBounds.xMin, screenBounds.yMin, screenBounds.Width(), screenBounds.Height() ); + CGRect r = CGRectMake( screenBounds.xMin, screenBounds.yMin, screenBounds.Width(), screenBounds.Height() ); Rtt_WebView *v = [[Rtt_WebView alloc] initWithFrame:r]; // t.borderStyle = UITextBorderStyleRoundedRect; @@ -218,7 +265,7 @@ - (WebView*)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)r Super::InitializeView( v ); [v release]; - + return (v != nil); } @@ -232,7 +279,7 @@ - (WebView*)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)r MacWebViewObject::GetBaseURLFromLuaState(lua_State *L, int index) { NSURL *result = nil; - + if ( lua_isstring( L, index ) ) { NSString *str = [[NSString alloc] initWithUTF8String:lua_tostring( L, index )]; @@ -298,7 +345,7 @@ - (WebView*)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)r MacWebViewObject::Load( NSString *htmlBody, NSURL *baseUrl ) { Rtt_WebView *container = (Rtt_WebView*)GetView(); - [container loadHtmlString:htmlBody baseURL:baseUrl]; + [container loadHTMLString:htmlBody baseURL:baseUrl]; } void @@ -309,7 +356,7 @@ - (WebView*)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)r : [[NSURL alloc] initWithString:urlString]; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:u]; [u release]; - + [request setHTTPMethod:@"GET"]; /* if ( NSOrderedSame == [fMethod caseInsensitiveCompare:@"POST"] ) @@ -362,7 +409,7 @@ - (WebView*)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)r Rtt_WebView *view = (Rtt_WebView*)o->GetView(); result = [view goBack]; } - + lua_pushboolean( L, result ); return 1; @@ -406,7 +453,7 @@ - (WebView*)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)r Rtt_WebView *view = (Rtt_WebView*)o->GetView(); [view reload:nil]; } - + return 0; } @@ -414,14 +461,14 @@ - (WebView*)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)r int MacWebViewObject::DeleteCookies( lua_State *L ) { - + // This isn't implemented because the cookies in the webview are tied to safari. // If we delete it from here then they're deleted from safari as well. - + return 0; - + } - + /* int MacWebViewObject::SetBackgroundColor( lua_State *L ) @@ -453,13 +500,92 @@ - (WebView*)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)r } */ +int +MacWebViewObject::InjectJS( lua_State *L ) +{ + const LuaProxyVTable& table = PlatformDisplayObject::GetWebViewObjectProxyVTable(); + MacWebViewObject *o = (MacWebViewObject *)luaL_todisplayobject( L, 1, table ); + if ( o ) + { + Rtt_WebView *view = (Rtt_WebView*)o->GetView(); + const char *jsContent = lua_tostring( L, 2 ); + NSString *jsCode = [NSString stringWithExternalString:jsContent]; + + [view evaluateJavaScript:jsCode completionHandler:^(id _Nullable response, NSError * _Nullable error){ + if ( error != nil ) { + NSLog(@"InjectJS error: %@", error); + } + }]; + } + + return 0; +} + +int +MacWebViewObject::RegisterCallback( lua_State *L ) +{ + const LuaProxyVTable& table = PlatformDisplayObject::GetWebViewObjectProxyVTable(); + MacWebViewObject *o = (MacWebViewObject *)luaL_todisplayobject( L, 1, table ); + if ( o ) + { + const char *eventName = lua_tostring( L, 2 ); + NSString *jsEventName = [NSString stringWithFormat:@"%@%s", kCoronaEventPrefix, eventName]; + o->AddEventListener( L, 3, [jsEventName UTF8String] ); + } + + return 0; +} + +int +MacWebViewObject::On( lua_State *L ) +{ + const LuaProxyVTable& table = PlatformDisplayObject::GetWebViewObjectProxyVTable(); + MacWebViewObject *o = (MacWebViewObject *)luaL_todisplayobject( L, 1, table ); + if ( o ) + { + const char *eventName = lua_tostring( L, 2 ); + NSString *jsEventName = [NSString stringWithFormat:@"%@%s", kCoronaEventPrefix, eventName]; + o->AddEventListener( L, 3, [jsEventName UTF8String] ); + } + + return 0; +} + +int +MacWebViewObject::Send( lua_State *L ) +{ + const LuaProxyVTable& table = PlatformDisplayObject::GetWebViewObjectProxyVTable(); + MacWebViewObject *o = (MacWebViewObject *)luaL_todisplayobject( L, 1, table ); + if ( o ) + { + const char* eventName = lua_tostring( L, 2 ); + + Rtt_WebView *view = (Rtt_WebView*)o->GetView(); + const char* jsonContent = "{}"; + if ( 0 == LuaContext::JsonEncode( L, 3 ) ) + { + jsonContent = lua_tostring( L, -1 ); + } + + NSString *jsCode = [NSString stringWithFormat:@"window.dispatchEvent(new CustomEvent('%@%s', { detail: %s }));", kCoronaEventPrefix, eventName, jsonContent]; + [view evaluateJavaScript:jsCode completionHandler:^(id _Nullable response, NSError * _Nullable error){ + if ( error != nil ) { + NSLog(@"Send '%s' error: %@", eventName, error); + } + }]; + lua_pop( L, 1 ); + } + + return 0; +} + int MacWebViewObject::ValueForKey( lua_State *L, const char key[] ) const { Rtt_ASSERT( key ); int result = 1; - + /* else if ( strcmp( "autoCancel", key ) == 0 ) { @@ -470,6 +596,22 @@ - (WebView*)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)r { lua_pushcfunction( L, Request ); } + else if ( strcmp( "injectJS", key ) == 0 ) + { + lua_pushcfunction( L, InjectJS ); + } + else if ( strcmp( "registerCallback", key ) == 0 ) + { + lua_pushcfunction( L, RegisterCallback ); + } + else if ( strcmp( "on", key ) == 0 ) + { + lua_pushcfunction( L, On ); + } + else if ( strcmp( "send", key ) == 0 ) + { + lua_pushcfunction( L, Send ); + } else if ( strcmp( "stop", key ) == 0 ) { lua_pushcfunction( L, Stop ); @@ -511,7 +653,7 @@ - (WebView*)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)r else if ( strcmp( "hasBackground", key ) == 0 ) { Rtt_WebView *view = (Rtt_WebView*)GetView(); - lua_pushboolean( L, view.drawsBackground ); + lua_pushboolean( L, false ); } else if ( strcmp( "load", key ) == 0 ) { @@ -535,7 +677,8 @@ - (WebView*)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)r if ( strcmp( "hasBackground", key ) == 0 ) { Rtt_WebView *view = (Rtt_WebView*)GetView(); - [view setDrawsBackground:lua_toboolean( L, valueIndex )]; + bool hasBackground = lua_toboolean( L, valueIndex ); + [view setValue:hasBackground ? @YES : @NO forKey:@"drawsBackground"]; } else if ( strcmp( "bounces", key ) == 0 ) { diff --git a/platform/resources/init.lua b/platform/resources/init.lua index 53d606863..7ecd8f51a 100755 --- a/platform/resources/init.lua +++ b/platform/resources/init.lua @@ -206,9 +206,11 @@ local function lookupProfile( name ) return display._beginProfile( id ) end +local WEBVIEW_JS_CUSTOM_EVENT_PREFIX = "JS_" function EventDispatcher:dispatchEvent( event ) local result = false; local eventName = event.name + local isWebviewJSCustomEvent = (event.detail ~= nil and eventName:starts(WEBVIEW_JS_CUSTOM_EVENT_PREFIX)) local profile -- array of functions is self._functionListeners[eventName] @@ -224,7 +226,12 @@ function EventDispatcher:dispatchEvent( event ) display._addProfileEntry( profile, func ) if self:hasEventListener( eventName, func ) then -- Dispatch event to function listener. - local handled = func( event ) + local handled + if isWebviewJSCustomEvent then + handled = func( event.detail ) + else + handled = func( event ) + end result = handled or result end end @@ -247,7 +254,12 @@ function EventDispatcher:dispatchEvent( event ) local method = obj[eventName] if ( type(method) == "function" ) then -- Dispatch event to table listener. - local handled = method( obj, event ) + local handled + if isWebviewJSCustomEvent then + handled = method( obj, event.detail ) + else + handled = method( obj, event ) + end result = handled or result end end diff --git a/platform/windows/Corona.Native.Library.Win32/Rtt/Rtt_WinWebViewObject.cpp b/platform/windows/Corona.Native.Library.Win32/Rtt/Rtt_WinWebViewObject.cpp index 6d9e0d9de..5447fa399 100644 --- a/platform/windows/Corona.Native.Library.Win32/Rtt/Rtt_WinWebViewObject.cpp +++ b/platform/windows/Corona.Native.Library.Win32/Rtt/Rtt_WinWebViewObject.cpp @@ -116,6 +116,22 @@ int WinWebViewObject::ValueForKey(lua_State *L, const char key[]) const { lua_pushcfunction(L, OnRequest); } + else if ( strcmp( "injectJS", key ) == 0 ) + { + lua_pushcfunction( L, InjectJS ); + } + else if ( strcmp( "registerCallback", key ) == 0 ) + { + lua_pushcfunction( L, RegisterCallback ); + } + else if ( strcmp( "on", key ) == 0 ) + { + lua_pushcfunction( L, On ); + } + else if ( strcmp( "send", key ) == 0 ) + { + lua_pushcfunction( L, Send ); + } else if (strcmp("stop", key) == 0) { lua_pushcfunction(L, OnStop); @@ -344,6 +360,30 @@ int WinWebViewObject::OnDeleteCookies(lua_State *L) return 0; } +int WinWebViewObject::InjectJS(lua_State *L) +{ + CoronaLuaWarning(L, "The native WebView:injectJS() function is not supported on Windows."); + return 0; +} + +int WinWebViewObject::RegisterCallback(lua_State *L) +{ + CoronaLuaWarning(L, "The native WebView:registerCallback() function is not supported on Windows."); + return 0; +} + +int WinWebViewObject::On(lua_State *L) +{ + CoronaLuaWarning(L, "The native WebView:on() function is not supported on Windows."); + return 0; +} + +int WinWebViewObject::Send(lua_State *L) +{ + CoronaLuaWarning(L, "The native WebView:send() function is not supported on Windows."); + return 0; +} + #pragma endregion } // namespace Rtt diff --git a/platform/windows/Corona.Native.Library.Win32/Rtt/Rtt_WinWebViewObject.h b/platform/windows/Corona.Native.Library.Win32/Rtt/Rtt_WinWebViewObject.h index eb60151df..a3e67b53b 100644 --- a/platform/windows/Corona.Native.Library.Win32/Rtt/Rtt_WinWebViewObject.h +++ b/platform/windows/Corona.Native.Library.Win32/Rtt/Rtt_WinWebViewObject.h @@ -84,6 +84,10 @@ class WinWebViewObject : public WinDisplayObject static int OnReload(lua_State *L); static int OnResize(lua_State *L); static int OnDeleteCookies(lua_State *L); + static int InjectJS(lua_State *L ); + static int RegisterCallback(lua_State *L); + static int On(lua_State *L); + static int Send(lua_State *L); #pragma endregion From c9c51aa91c247cdf6c907b4b00261aac3c816a86 Mon Sep 17 00:00:00 2001 From: Chen Bin Date: Tue, 26 Nov 2024 17:58:16 +0800 Subject: [PATCH 2/3] Enable webview javaScript alert, confirm, prompt and console.log. Now we will call window.onNativeBridgeLoaded on page loaded. Use this feature in html: ```html Test

This is a simple HTML page.

``` --- librtt/Rtt_Event.cpp | 1 - .../android/ndk/jni/JavaToNativeBridge.cpp | 1 - .../src/com/ansca/corona/CoronaWebView.java | 116 +++++++- platform/iphone/Rtt_IPhoneWebViewObject.mm | 272 +++++++++++++----- platform/mac/Rtt_MacWebViewObject.mm | 106 ++++++- 5 files changed, 418 insertions(+), 78 deletions(-) diff --git a/librtt/Rtt_Event.cpp b/librtt/Rtt_Event.cpp index 34da2a04d..3e1dcc902 100755 --- a/librtt/Rtt_Event.cpp +++ b/librtt/Rtt_Event.cpp @@ -2763,7 +2763,6 @@ CommonEvent::Push( lua_State *L ) const { if ( 0 == LuaContext::JsonDecode( L, fData) ) { - Rtt_Log( "CommonEvent::Push fData=%s, type=%s", fData, luaL_typename( L, -1 ) ); lua_setfield( L, -2, "detail" ); } else diff --git a/platform/android/ndk/jni/JavaToNativeBridge.cpp b/platform/android/ndk/jni/JavaToNativeBridge.cpp index c2869ed3e..ca3623166 100644 --- a/platform/android/ndk/jni/JavaToNativeBridge.cpp +++ b/platform/android/ndk/jni/JavaToNativeBridge.cpp @@ -1408,7 +1408,6 @@ JavaToNativeBridge::WebViewJSInterfaceCommonEvent( JNIEnv * env, int id, jstring Rtt::CommonEvent e(typeS.getUTF8(), dataS.getUTF8()); lua_State *L = view->GetL(); - Rtt_Log( "WebViewJSInterfaceCommonEvent: type=%s, data=%s", typeS.getUTF8(), dataS.getUTF8() ); int status = view->Rtt::DisplayObject::DispatchEventWithTarget( L, e, 1 ); if ( status == 0 && (! noResult ) ) { diff --git a/platform/android/sdk/src/com/ansca/corona/CoronaWebView.java b/platform/android/sdk/src/com/ansca/corona/CoronaWebView.java index 9ef39c615..859e0bd44 100644 --- a/platform/android/sdk/src/com/ansca/corona/CoronaWebView.java +++ b/platform/android/sdk/src/com/ansca/corona/CoronaWebView.java @@ -30,7 +30,7 @@ public class CoronaWebView extends WebView implements NativePropertyResponder { // JavaScript injection code private static final String NATIVE_BRIDGE_CODE = - "const NativeBridge = {\n" + + "window.NativeBridge = {\n" + " callNative: function(method, args) {\n" + " return new Promise((resolve, reject) => {\n" + " var eventName = 'JS_' + method;\n" + @@ -58,6 +58,10 @@ public class CoronaWebView extends WebView implements NativePropertyResponder { " callback(e.detail)\n" + " }, options);\n" + " }\n" + + "};\n" + + "if (window.onNativeBridgeLoaded != undefined) {\n" + + " window.onNativeBridgeLoaded();\n" + + " delete window.onNativeBridgeLoaded;\n" + "};\n"; // JavaScript interface class @@ -150,6 +154,116 @@ public CoronaWebView(Context context, CoronaRuntime runtime) { setWebChromeClient(new android.webkit.WebChromeClient() { View mCustomView; + // Handle JavaScript alert() + @Override + public boolean onJsAlert(WebView view, String url, String message, final android.webkit.JsResult result) { + fCoronaRuntime.getController().createAlertDialogBuilder(view.getContext()) + .setMessage(message) + .setPositiveButton(android.R.string.ok, new android.content.DialogInterface.OnClickListener() { + @Override + public void onClick(android.content.DialogInterface dialog, int which) { + result.confirm(); + } + }) + .setCancelable(false) + .create() + .show(); + return true; + } + + // Handle JavaScript confirm() + @Override + public boolean onJsConfirm(WebView view, String url, String message, final android.webkit.JsResult result) { + fCoronaRuntime.getController().createAlertDialogBuilder(view.getContext()) + .setMessage(message) + .setPositiveButton(android.R.string.ok, new android.content.DialogInterface.OnClickListener() { + @Override + public void onClick(android.content.DialogInterface dialog, int which) { + result.confirm(); + } + }) + .setNegativeButton(android.R.string.cancel, new android.content.DialogInterface.OnClickListener() { + @Override + public void onClick(android.content.DialogInterface dialog, int which) { + result.cancel(); + } + }) + .setCancelable(false) + .create() + .show(); + return true; + } + + // Handle JavaScript prompt() + @Override + public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, final android.webkit.JsPromptResult result) { + final android.widget.EditText input = new android.widget.EditText(view.getContext()); + input.setText(defaultValue); + + // Set input field styling + int padding = (int) (16 * view.getContext().getResources().getDisplayMetrics().density); + input.setPadding(padding, padding/2, padding, padding/2); + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { + input.setBackgroundTintList(android.content.res.ColorStateList.valueOf(0x88FFFFFF)); // Material Blue + } + input.setSingleLine(true); + + fCoronaRuntime.getController().createAlertDialogBuilder(view.getContext()) + .setMessage(message) + .setView(input) + .setPositiveButton(android.R.string.ok, new android.content.DialogInterface.OnClickListener() { + @Override + public void onClick(android.content.DialogInterface dialog, int which) { + result.confirm(input.getText().toString()); + } + }) + .setNegativeButton(android.R.string.cancel, new android.content.DialogInterface.OnClickListener() { + @Override + public void onClick(android.content.DialogInterface dialog, int which) { + result.cancel(); + } + }) + .setCancelable(false) + .create() + .show(); + + return true; + } + + // Handle console.log() + @Override + public boolean onConsoleMessage(android.webkit.ConsoleMessage consoleMessage) { + String message = consoleMessage.message(); + int lineNumber = consoleMessage.lineNumber(); + String sourceId = consoleMessage.sourceId(); + android.webkit.ConsoleMessage.MessageLevel level = consoleMessage.messageLevel(); + + String tag = "Corona"; + String fullMessage = "[WebView_%S] " + String.format("(%s:%d) %s", sourceId, lineNumber, message); + + // Log messages with appropriate log level + switch (level) { + case ERROR: + android.util.Log.e(tag, String.format(fullMessage, "ERROR")); + break; + case WARNING: + android.util.Log.w(tag, String.format(fullMessage, "WARNING")); + break; + case DEBUG: + android.util.Log.d(tag, String.format(fullMessage, "DEBUG")); + break; + case TIP: + android.util.Log.i(tag, String.format(fullMessage, "TIP")); + break; + case LOG: + default: + android.util.Log.i(tag, String.format(fullMessage, "LOG")); + break; + } + return true; + } + @Override public void onGeolocationPermissionsShowPrompt( String origin, android.webkit.GeolocationPermissions.Callback callback) diff --git a/platform/iphone/Rtt_IPhoneWebViewObject.mm b/platform/iphone/Rtt_IPhoneWebViewObject.mm index 3a61b8873..339ce9db9 100644 --- a/platform/iphone/Rtt_IPhoneWebViewObject.mm +++ b/platform/iphone/Rtt_IPhoneWebViewObject.mm @@ -50,7 +50,7 @@ NSString * const kCoronaEventPrefix = @"JS_"; NSString * const kCorona4JS = @"corona"; NSString * const kNativeBridgeCode = JS( - const NativeBridge = { + window.NativeBridge = { callNative: function(method, args) { return new Promise((resolve, reject) => { var eventName = "JS_" + method; @@ -79,6 +79,36 @@ }, options); } }; + window.originalConsole = window.console; + window.console = { + log: function() { + var args = Array.prototype.slice.call(arguments); + window.webkit.messageHandlers.console.postMessage({ + type: 'log', + message: args.map(function(arg) { return JSON.stringify(arg); }).join(', ') + }); + }, + warn: function() { + var args = Array.prototype.slice.call(arguments); + window.webkit.messageHandlers.console.postMessage({ + type: 'warn', + message: args.map(function(arg) { return JSON.stringify(arg); }).join(', ') + }); + }, + error: function() { + var args = Array.prototype.slice.call(arguments); + window.webkit.messageHandlers.console.postMessage({ + type: 'error', + message: args.map(function(arg) { return JSON.stringify(arg); }).join(', ') + }); + } + }; +); +NSString * const kOnLoadedJSCode = JS( + if (window.onNativeBridgeLoaded != undefined) { + window.onNativeBridgeLoaded(); + delete window.onNativeBridgeLoaded; + } ); //static void @@ -148,8 +178,8 @@ @implementation Rtt_iOSWebViewContainer -(WKWebView*)webView { if(fWebView == nil) { - // Propagate the w,h, but do not propagate the origin, as the parent already accounts for it. - CGRect rect = [super frame]; + // Propagate the w,h, but do not propagate the origin, as the parent already accounts for it. + CGRect rect = [super frame]; CGRect webViewRect = rect; webViewRect.origin = CGPointZero; fWebView = [[WKWebView alloc] initWithFrame:webViewRect configuration:fWebViewConfiguration]; @@ -158,10 +188,10 @@ -(WKWebView*)webView fWebView.navigationDelegate = self; fWebView.UIDelegate = self; // fWebView.scalesPageToFit = YES; - + fActivityView = [[UIView alloc] initWithFrame:webViewRect]; fActivityView.backgroundColor = [UIColor grayColor]; - + UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; [fActivityView addSubview:indicator]; [indicator release]; @@ -175,7 +205,7 @@ -(WKWebView*)webView AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate; UIViewController *viewController = delegate.viewController; initialOrientation = viewController.interfaceOrientation; - + [self addSubview:fWebView]; [self addSubview:fActivityView]; @@ -192,7 +222,12 @@ - (id)initWithFrame:(CGRect)rect { fWebViewConfiguration = [[WKWebViewConfiguration alloc] init]; WKUserContentController *userContentController = [[WKUserContentController alloc] init]; + WKUserScript *consoleScript = [[WKUserScript alloc] initWithSource:kNativeBridgeCode + injectionTime:WKUserScriptInjectionTimeAtDocumentStart + forMainFrameOnly:YES]; + [userContentController addUserScript:consoleScript]; [userContentController addScriptMessageHandler:self name:kCorona4JS]; + [userContentController addScriptMessageHandler:self name:@"console"]; fWebViewConfiguration.userContentController = userContentController; fOwner = nil; @@ -202,8 +237,8 @@ - (id)initWithFrame:(CGRect)rect isOpen = false; keyboardShown = NO; isLoading = NO; - - [self addObservers]; + + [self addObservers]; } return self; @@ -226,7 +261,7 @@ - (void)dealloc -(void) setFrame:(CGRect)frame { [super setFrame:frame]; - + frame.origin = CGPointZero; [fWebView setFrame:frame]; [fActivityView setFrame:frame]; @@ -265,15 +300,15 @@ - (void)loadHtmlString:(NSString*)htmlString baseURL:(NSURL*)baseUrl { [self.webView stopLoading]; } - + fLoadingURL = nil; - + if(htmlString) { isLoading = true; [self.webView loadHTMLString:htmlString baseURL:baseUrl]; } - + } - (void)loadRequest:(NSURLRequest*)request baseURL:(NSURL*)baseUrl { @@ -282,39 +317,39 @@ - (void)loadRequest:(NSURLRequest*)request baseURL:(NSURL*)baseUrl [self.webView stopLoading]; } - - bool loadImmediately = false; - - if (fLoadingURL) - { - if (request) - { - if (request.URL) - { - NSString *urlString1 = [fLoadingURL absoluteString]; - NSString *urlString2 = [request.URL absoluteString]; - - //If the url requests are the same, then load like normal since this - //generates a callback and will create a dispatch event (i.e. page reload) - if( [urlString1 caseInsensitiveCompare:urlString2] != NSOrderedSame ) - { - //If the requests are the same up to the hash tag then do an immediate load - //since we never get a callback for this load and therefore never removed - //the grey activity window - NSString *subString1 = [[urlString1 componentsSeparatedByString:@"#"] objectAtIndex:0]; - NSString *subString2 = [[urlString2 componentsSeparatedByString:@"#"] objectAtIndex:0]; - - //Only the hash fragments differ - if( [subString1 caseInsensitiveCompare:subString2] == NSOrderedSame ) - { - loadImmediately = true; - } - } - } - } - } - - + + bool loadImmediately = false; + + if (fLoadingURL) + { + if (request) + { + if (request.URL) + { + NSString *urlString1 = [fLoadingURL absoluteString]; + NSString *urlString2 = [request.URL absoluteString]; + + //If the url requests are the same, then load like normal since this + //generates a callback and will create a dispatch event (i.e. page reload) + if( [urlString1 caseInsensitiveCompare:urlString2] != NSOrderedSame ) + { + //If the requests are the same up to the hash tag then do an immediate load + //since we never get a callback for this load and therefore never removed + //the grey activity window + NSString *subString1 = [[urlString1 componentsSeparatedByString:@"#"] objectAtIndex:0]; + NSString *subString2 = [[urlString2 componentsSeparatedByString:@"#"] objectAtIndex:0]; + + //Only the hash fragments differ + if( [subString1 caseInsensitiveCompare:subString2] == NSOrderedSame ) + { + loadImmediately = true; + } + } + } + } + } + + [fLoadingURL release]; fLoadingURL = [request.URL retain]; if([fLoadingURL isFileURL] && baseUrl && [self.webView respondsToSelector:@selector(loadFileURL:allowingReadAccessToURL:)]) { @@ -323,23 +358,23 @@ - (void)loadRequest:(NSURLRequest*)request baseURL:(NSURL*)baseUrl [self.webView loadRequest:request]; } - if (false == loadImmediately) - { - if ( ! isLoading ) - { - fActivityView.alpha = 0.0; - [UIView beginAnimations:nil context:nil]; - [UIView setAnimationDuration:kAnimationDuration]; - [UIView setAnimationDelegate:self]; - [UIView setAnimationDidStopSelector:@selector(didAppear:finished:context:)]; - fActivityView.alpha = 1.0; - [UIView commitAnimations]; - - [self addObservers]; - } - isLoading = YES; - } - + if (false == loadImmediately) + { + if ( ! isLoading ) + { + fActivityView.alpha = 0.0; + [UIView beginAnimations:nil context:nil]; + [UIView setAnimationDuration:kAnimationDuration]; + [UIView setAnimationDelegate:self]; + [UIView setAnimationDidStopSelector:@selector(didAppear:finished:context:)]; + fActivityView.alpha = 1.0; + [UIView commitAnimations]; + + [self addObservers]; + } + isLoading = YES; + } + } - (void)stopLoading @@ -434,18 +469,18 @@ - (void)keyboardWasHidden:(NSNotification*)aNotification if ( [self.webView isFirstResponder] ) { NSDictionary* info = [aNotification userInfo]; - + // Get the size of the keyboard. NSValue* aValue = [info objectForKey:UIKeyboardBoundsUserInfoKey]; CGSize keyboardSize = [aValue CGRectValue].size; - + WKWebView *view = self.webView; // Reset the height of the scroll view to its original value CGRect viewFrame = [view frame]; viewFrame.size.height += keyboardSize.height; view.frame = viewFrame; - + keyboardShown = NO; } } @@ -490,20 +525,20 @@ - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigati NSURL *url = navigationAction.request.URL; const char *urlString = [[url absoluteString] UTF8String]; - + Rtt::UrlRequestEvent::Type navType = EventTypeForNavigationType( navigationAction.navigationType ); - + UrlRequestEvent e( urlString, navType ); - + fOwner->DispatchEventWithTarget( e ); - + if (navType == UrlRequestEvent::kLink || navType == UrlRequestEvent::kHistory || navType == UrlRequestEvent::kReload ) { isLoading = true; } - + decisionHandler(WKNavigationActionPolicyAllow); // Always load } @@ -531,7 +566,7 @@ - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigat [self hideActivityViewIndicator]; - [fWebView evaluateJavaScript:kNativeBridgeCode completionHandler:nil]; + [fWebView evaluateJavaScript:kOnLoadedJSCode completionHandler:nil]; NSURL *url = webView.URL; const char *urlString = [[url absoluteString] UTF8String]; @@ -560,7 +595,7 @@ - (void)hideActivityViewIndicator { UIActivityIndicatorView *indicator = [[fActivityView subviews] objectAtIndex:0]; [indicator stopAnimating]; - + self.webView.hidden = NO; self.webView.alpha = 1.0; fActivityView.alpha = 1.0; @@ -608,6 +643,99 @@ - (void)userContentController:(WKUserContentController *)userContentController d } lua_pop( L, 1 ); } + else if ([message.name isEqualToString:@"console"]) + { + NSDictionary *logData = message.body; + NSString *type = logData[@"type"]; + NSString *logMessage = logData[@"message"]; + + if ([type isEqualToString:@"error"]) { + NSLog(@"[WebView_ERROR] %@", logMessage); + } else if ([type isEqualToString:@"warn"]) { + NSLog(@"[WebView_WARNING] %@", logMessage); + } else if ([type isEqualToString:@"info"]) { + NSLog(@"[WebView_INFO] %@", logMessage); + } else if ([type isEqualToString:@"debug"]) { + NSLog(@"[WebView_DEBUG] %@", logMessage); + } else { + NSLog(@"[WebView_LOG] %@", logMessage); + } + } +} + +// Handle JavaScript alert() +- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler +{ + UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil + message:message + preferredStyle:UIAlertControllerStyleAlert]; + + UIAlertAction *okAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + completionHandler(); + }]; + [alert addAction:okAction]; + + id runtime = (id)Rtt::Lua::GetContext( fOwner->GetL() ); + [runtime.appViewController presentViewController:alert animated:YES completion:nil]; +} + +// Handle JavaScript confirm() +- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler +{ + UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil + message:message + preferredStyle:UIAlertControllerStyleAlert]; + + UIAlertAction *okAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + completionHandler(YES); + }]; + + UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", nil) + style:UIAlertActionStyleCancel + handler:^(UIAlertAction *action) { + completionHandler(NO); + }]; + + [alert addAction:cancelAction]; + [alert addAction:okAction]; + + id runtime = (id)Rtt::Lua::GetContext( fOwner->GetL() ); + [runtime.appViewController presentViewController:alert animated:YES completion:nil]; +} + +// Handle JavaScript prompt() +- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler +{ + UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil + message:prompt + preferredStyle:UIAlertControllerStyleAlert]; + + [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.text = defaultText; + }]; + + UIAlertAction *okAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + NSString *input = alert.textFields.firstObject.text; + completionHandler(input); + }]; + + UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", nil) + style:UIAlertActionStyleCancel + handler:^(UIAlertAction *action) { + completionHandler(nil); + }]; + + [alert addAction:cancelAction]; + [alert addAction:okAction]; + + id runtime = (id)Rtt::Lua::GetContext( fOwner->GetL() ); + [runtime.appViewController presentViewController:alert animated:YES completion:nil]; } @end diff --git a/platform/mac/Rtt_MacWebViewObject.mm b/platform/mac/Rtt_MacWebViewObject.mm index 9cba9afd1..99092ad98 100644 --- a/platform/mac/Rtt_MacWebViewObject.mm +++ b/platform/mac/Rtt_MacWebViewObject.mm @@ -32,7 +32,7 @@ NSString * const kCoronaEventPrefix = @"JS_"; NSString * const kCorona4JS = @"corona"; NSString * const kNativeBridgeCode = JS( - const NativeBridge = { + window.NativeBridge = { callNative: function(method, args) { return new Promise((resolve, reject) => { var eventName = "JS_" + method; @@ -61,6 +61,37 @@ }, options); } }; + + window.originalConsole = window.console; + window.console = { + log: function() { + var args = Array.prototype.slice.call(arguments); + window.webkit.messageHandlers.console.postMessage({ + type: 'log', + message: args.map(function(arg) { return JSON.stringify(arg); }).join(', ') + }); + }, + warn: function() { + var args = Array.prototype.slice.call(arguments); + window.webkit.messageHandlers.console.postMessage({ + type: 'warn', + message: args.map(function(arg) { return JSON.stringify(arg); }).join(', ') + }); + }, + error: function() { + var args = Array.prototype.slice.call(arguments); + window.webkit.messageHandlers.console.postMessage({ + type: 'error', + message: args.map(function(arg) { return JSON.stringify(arg); }).join(', ') + }); + } + }; +); +NSString * const kOnLoadedJSCode = JS( + if (window.onNativeBridgeLoaded != undefined) { + window.onNativeBridgeLoaded(); + delete window.onNativeBridgeLoaded; + } ); @interface Rtt_WebView : WKWebView @@ -83,7 +114,13 @@ - (id)initWithFrame:(CGRect)frameRect // Add userContentController WKUserContentController *userContentController = [[WKUserContentController alloc] init]; + WKUserScript *consoleScript = [[WKUserScript alloc] initWithSource:kNativeBridgeCode + injectionTime:WKUserScriptInjectionTimeAtDocumentStart + forMainFrameOnly:YES]; + [userContentController addUserScript:consoleScript]; [userContentController addScriptMessageHandler:self name:kCorona4JS]; + [userContentController addScriptMessageHandler:self name:@"console"]; + configuration.userContentController = userContentController; self = [super initWithFrame:frameRect configuration:configuration]; @@ -92,7 +129,7 @@ - (id)initWithFrame:(CGRect)frameRect owner = NULL; [self setWantsLayer:YES]; [self setNavigationDelegate:self]; - [self setUIDelegate:self]; // Set UIDelegate to handle new windows + [self setUIDelegate:self]; } return self; } @@ -157,7 +194,7 @@ - (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWe - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { - [self evaluateJavaScript:kNativeBridgeCode completionHandler:nil]; + [self evaluateJavaScript:kOnLoadedJSCode completionHandler:nil]; using namespace Rtt; @@ -212,8 +249,70 @@ - (void)userContentController:(WKUserContentController *)userContentController d } lua_pop( L, 1 ); } + else if ([message.name isEqualToString:@"console"]) + { + NSDictionary *logData = message.body; + NSString *type = logData[@"type"]; + NSString *logMessage = logData[@"message"]; + + if ([type isEqualToString:@"error"]) { + NSLog(@"[WebView_ERROR] %@", logMessage); + } else if ([type isEqualToString:@"warn"]) { + NSLog(@"[WebView_WARNING] %@", logMessage); + } else if ([type isEqualToString:@"info"]) { + NSLog(@"[WebView_INFO] %@", logMessage); + } else if ([type isEqualToString:@"debug"]) { + NSLog(@"[WebView_DEBUG] %@", logMessage); + } else { + NSLog(@"[WebView_LOG] %@", logMessage); + } + } +} + +// Handle JavaScript alert() +- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler +{ + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:message]; + // Use system default "OK" button + [[alert addButtonWithTitle:NSLocalizedString(@"OK", nil)] setKeyEquivalent:@"\r"]; + [alert runModal]; + completionHandler(); } +// Handle JavaScript confirm() +- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler +{ + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:message]; + // Use system default "OK" and "Cancel" buttons + [[alert addButtonWithTitle:NSLocalizedString(@"OK", nil)] setKeyEquivalent:@"\r"]; + [[alert addButtonWithTitle:NSLocalizedString(@"Cancel", nil)] setKeyEquivalent:@"\033"]; + + NSModalResponse response = [alert runModal]; + completionHandler(response == NSAlertFirstButtonReturn); +} + +// Handle JavaScript prompt() +- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler +{ + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:prompt]; + // Use system default "OK" and "Cancel" buttons + [[alert addButtonWithTitle:NSLocalizedString(@"OK", nil)] setKeyEquivalent:@"\r"]; + [[alert addButtonWithTitle:NSLocalizedString(@"Cancel", nil)] setKeyEquivalent:@"\033"]; + + NSTextField *input = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 24)]; + [input setStringValue:defaultText]; + [alert setAccessoryView:input]; + + NSModalResponse response = [alert runModal]; + if (response == NSAlertFirstButtonReturn) { + completionHandler([input stringValue]); + } else { + completionHandler(nil); + } +} @end @@ -234,6 +333,7 @@ - (void)userContentController:(WKUserContentController *)userContentController d // Nil out the delegate to prevent any notifications from being triggered on this dead object Rtt_WebView *view = (Rtt_WebView*)GetView(); [view.configuration.userContentController removeScriptMessageHandlerForName:kCorona4JS]; + [view.configuration.userContentController removeScriptMessageHandlerForName:@"console"]; // [view setDelegate:nil]; [view stopLoading:nil]; // [view close]; From 1c6b305a5eeeacda027de39761ff21b9d9488114 Mon Sep 17 00:00:00 2001 From: Chen Bin Date: Wed, 27 Nov 2024 10:30:17 +0800 Subject: [PATCH 3/3] Native webview: change window.onNativeBridgeLoaded to window.onNativeBridgeReady. Now we will call window.onNativeBridgeReady on page loaded. Use this feature in html: ```html Test

This is a simple HTML page.

``` --- .../android/sdk/src/com/ansca/corona/CoronaWebView.java | 6 +++--- platform/iphone/Rtt_IPhoneWebViewObject.mm | 6 +++--- platform/mac/Rtt_MacWebViewObject.mm | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/platform/android/sdk/src/com/ansca/corona/CoronaWebView.java b/platform/android/sdk/src/com/ansca/corona/CoronaWebView.java index 859e0bd44..eb607c884 100644 --- a/platform/android/sdk/src/com/ansca/corona/CoronaWebView.java +++ b/platform/android/sdk/src/com/ansca/corona/CoronaWebView.java @@ -59,9 +59,9 @@ public class CoronaWebView extends WebView implements NativePropertyResponder { " }, options);\n" + " }\n" + "};\n" + - "if (window.onNativeBridgeLoaded != undefined) {\n" + - " window.onNativeBridgeLoaded();\n" + - " delete window.onNativeBridgeLoaded;\n" + + "if (window.onNativeBridgeReady != undefined) {\n" + + " window.onNativeBridgeReady();\n" + + " delete window.onNativeBridgeReady;\n" + "};\n"; // JavaScript interface class diff --git a/platform/iphone/Rtt_IPhoneWebViewObject.mm b/platform/iphone/Rtt_IPhoneWebViewObject.mm index 339ce9db9..8dd738ef1 100644 --- a/platform/iphone/Rtt_IPhoneWebViewObject.mm +++ b/platform/iphone/Rtt_IPhoneWebViewObject.mm @@ -105,9 +105,9 @@ }; ); NSString * const kOnLoadedJSCode = JS( - if (window.onNativeBridgeLoaded != undefined) { - window.onNativeBridgeLoaded(); - delete window.onNativeBridgeLoaded; + if (window.onNativeBridgeReady != undefined) { + window.onNativeBridgeReady(); + delete window.onNativeBridgeReady; } ); diff --git a/platform/mac/Rtt_MacWebViewObject.mm b/platform/mac/Rtt_MacWebViewObject.mm index 99092ad98..3f9985533 100644 --- a/platform/mac/Rtt_MacWebViewObject.mm +++ b/platform/mac/Rtt_MacWebViewObject.mm @@ -88,9 +88,9 @@ }; ); NSString * const kOnLoadedJSCode = JS( - if (window.onNativeBridgeLoaded != undefined) { - window.onNativeBridgeLoaded(); - delete window.onNativeBridgeLoaded; + if (window.onNativeBridgeReady != undefined) { + window.onNativeBridgeReady(); + delete window.onNativeBridgeReady; } );