Skip to content

Commit 289706c

Browse files
committed
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}<br> Version: ${info.version}<br> Language: ${info.language}<br> 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 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>WebView Example</title> <style> body { font-family: Arial, sans-serif; padding: 20px; } button { margin: 10px 0; } </style> </head> <body> <h1>WebView Communication Example</h1> <div id="deviceInfo"></div> <button id="updateButton">Update Device Info</button> <button id="greetButton">Send Greeting</button> <p id="response"></p> </body> </html> ```
1 parent 9c51135 commit 289706c

28 files changed

+1009
-139
lines changed

librtt/Rtt_Event.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2815,6 +2815,44 @@ UrlRequestEvent::Push( lua_State *L ) const
28152815

28162816
// ----------------------------------------------------------------------------
28172817

2818+
CommonEvent::CommonEvent( const char *fEventName, const char *fData )
2819+
: fEventName( fEventName ),
2820+
fData( fData )
2821+
{
2822+
}
2823+
2824+
const char*
2825+
CommonEvent::Name() const
2826+
{
2827+
return fEventName;
2828+
}
2829+
2830+
int
2831+
CommonEvent::Push( lua_State *L ) const
2832+
{
2833+
if ( Rtt_VERIFY( Super::Push( L ) ) )
2834+
{
2835+
if ( fData )
2836+
{
2837+
if ( 0 == LuaContext::JsonDecode( L, fData) )
2838+
{
2839+
Rtt_Log( "CommonEvent::Push fData=%s, type=%s", fData, luaL_typename( L, -1 ) );
2840+
lua_setfield( L, -2, "detail" );
2841+
}
2842+
else
2843+
{
2844+
lua_pop( L, 1 );
2845+
lua_pushstring( L, fData );
2846+
lua_setfield( L, -2, "detail" );
2847+
}
2848+
}
2849+
}
2850+
2851+
return 1;
2852+
}
2853+
2854+
// ----------------------------------------------------------------------------
2855+
28182856
const char UserInputEvent::kName[] = "userInput";
28192857

28202858
const char*

librtt/Rtt_Event.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1316,6 +1316,27 @@ class UrlRequestEvent : public VirtualEvent
13161316

13171317
// ----------------------------------------------------------------------------
13181318

1319+
// Common event
1320+
class CommonEvent : public VirtualEvent
1321+
{
1322+
public:
1323+
typedef VirtualEvent Super;
1324+
typedef CommonEvent Self;
1325+
1326+
public:
1327+
CommonEvent( const char *fEventName, const char *fData );
1328+
1329+
public:
1330+
virtual const char* Name() const;
1331+
virtual int Push( lua_State *L ) const;
1332+
1333+
private:
1334+
const char *fEventName;
1335+
const char *fData;
1336+
};
1337+
1338+
// ----------------------------------------------------------------------------
1339+
13191340
// Local event
13201341
class UserInputEvent : public VirtualEvent
13211342
{

librtt/Rtt_LuaContext.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,6 +1192,40 @@ LuaContext::IsBinaryLua( const char* filename )
11921192
return result;
11931193
}
11941194

1195+
int
1196+
LuaContext::JsonEncode( lua_State *L, int index )
1197+
{
1198+
bool success = false;
1199+
1200+
lua_getglobal( L, "require" );
1201+
lua_pushstring( L, "json" );
1202+
if ( DoCall( L, 1, 1 ) == 0 )
1203+
{
1204+
lua_getfield( L, -1, "encode" );
1205+
lua_remove(L, -2);
1206+
lua_pushvalue( L, index );
1207+
success = ( DoCall( L, 1, 1 ) == 0 );
1208+
}
1209+
return success ? 0 : -1;
1210+
}
1211+
1212+
int
1213+
LuaContext::JsonDecode( lua_State *L, const char* json )
1214+
{
1215+
bool success = false;
1216+
1217+
lua_getglobal( L, "require" );
1218+
lua_pushstring( L, "json" );
1219+
if ( DoCall( L, 1, 1 ) == 0 )
1220+
{
1221+
lua_getfield( L, -1, "decode" );
1222+
lua_remove(L, -2);
1223+
lua_pushstring(L, json);
1224+
success = ( DoCall( L, 1, 1 ) == 0 );
1225+
}
1226+
return success ? 0 : -1;
1227+
}
1228+
11951229
// ----------------------------------------------------------------------------
11961230

11971231
LuaContext::LuaContext( ::lua_State* L )

librtt/Rtt_LuaContext.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ class LuaContext
5151
static int OpenJson( lua_State *L );
5252
static int OpenWidget( lua_State *L );
5353
static int OpenStoryboard( lua_State *L );
54+
static int JsonEncode( lua_State *L, int index );
55+
static int JsonDecode( lua_State *L, const char *json );
5456

5557
public:
5658
// Generic, re-entrant Lua callbacks

platform/android/ndk/Rtt_AndroidWebViewObject.cpp

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@
2626

2727
namespace Rtt
2828
{
29-
3029
// ----------------------------------------------------------------------------
3130

31+
static const char* kCoronaEventPrefix = "JS_";
32+
3233
AndroidWebViewObject::AndroidWebViewObject(
3334
const Rect& bounds, AndroidDisplayObjectRegistry *displayObjectRegistry, NativeToJavaBridge *ntjb )
3435
: Super( bounds, displayObjectRegistry, ntjb ),
@@ -251,6 +252,85 @@ AndroidWebViewObject::DeleteCookies( lua_State *L )
251252
return 0;
252253
}
253254

255+
int
256+
AndroidWebViewObject::InjectJS( lua_State *L )
257+
{
258+
const LuaProxyVTable& table = PlatformDisplayObject::GetWebViewObjectProxyVTable();
259+
AndroidWebViewObject *view = (AndroidWebViewObject *)luaL_todisplayobject(L, 1, table);
260+
if ( view )
261+
{
262+
const char *jsCode = lua_tostring( L, 2 );
263+
view->InjectJSCode( jsCode );
264+
}
265+
266+
return 0;
267+
}
268+
269+
int
270+
AndroidWebViewObject::RegisterCallback( lua_State *L )
271+
{
272+
const LuaProxyVTable& table = PlatformDisplayObject::GetWebViewObjectProxyVTable();
273+
AndroidWebViewObject *view = (AndroidWebViewObject *)luaL_todisplayobject( L, 1, table );
274+
if ( view )
275+
{
276+
const char *eventName = lua_tostring( L, 2 );
277+
String jsEventName(kCoronaEventPrefix);
278+
jsEventName.Append( eventName );
279+
view->AddEventListener( L, 3, jsEventName.GetString() );
280+
}
281+
282+
return 0;
283+
}
284+
285+
int
286+
AndroidWebViewObject::On( lua_State *L )
287+
{
288+
const LuaProxyVTable& table = PlatformDisplayObject::GetWebViewObjectProxyVTable();
289+
AndroidWebViewObject *view = (AndroidWebViewObject *)luaL_todisplayobject( L, 1, table );
290+
if ( view )
291+
{
292+
const char *eventName = lua_tostring( L, 2 );
293+
String jsEventName(kCoronaEventPrefix);
294+
jsEventName.Append( eventName );
295+
view->AddEventListener( L, 3, jsEventName.GetString() );
296+
}
297+
298+
return 0;
299+
}
300+
301+
int
302+
AndroidWebViewObject::Send( lua_State *L )
303+
{
304+
const LuaProxyVTable& table = PlatformDisplayObject::GetWebViewObjectProxyVTable();
305+
AndroidWebViewObject *view = (AndroidWebViewObject *)luaL_todisplayobject( L, 1, table );
306+
if ( view )
307+
{
308+
const char* eventName = lua_tostring( L, 2 );
309+
const char* jsonContent = "{}";
310+
if ( 0 == LuaContext::JsonEncode( L, 3 ) )
311+
{
312+
jsonContent = lua_tostring( L, -1 );
313+
}
314+
315+
String s( "window.dispatchEvent(new CustomEvent('" );
316+
s.Append( kCoronaEventPrefix );
317+
s.Append( eventName );
318+
s.Append( "', {detail: " );
319+
s.Append( jsonContent );
320+
s.Append( "}));" );
321+
322+
view->InjectJSCode( s.GetString() );
323+
}
324+
325+
return 0;
326+
}
327+
328+
void
329+
AndroidWebViewObject::InjectJSCode( const char *jsCode )
330+
{
331+
fNativeToJavaBridge->WebViewRequestInjectJS( GetId(), jsCode );
332+
}
333+
254334
int
255335
AndroidWebViewObject::ValueForKey( lua_State *L, const char key[] ) const
256336
{
@@ -263,6 +343,26 @@ AndroidWebViewObject::ValueForKey( lua_State *L, const char key[] ) const
263343
lua_pushlightuserdata( L, fNativeToJavaBridge );
264344
lua_pushcclosure( L, Request, 1 );
265345
}
346+
else if ( strcmp( "injectJS", key ) == 0 )
347+
{
348+
lua_pushlightuserdata( L, fNativeToJavaBridge );
349+
lua_pushcclosure( L, InjectJS, 1 );
350+
}
351+
else if ( strcmp( "registerCallback", key ) == 0 )
352+
{
353+
lua_pushlightuserdata( L, fNativeToJavaBridge );
354+
lua_pushcclosure( L, RegisterCallback, 1 );
355+
}
356+
else if ( strcmp( "on", key ) == 0 )
357+
{
358+
lua_pushlightuserdata( L, fNativeToJavaBridge );
359+
lua_pushcclosure( L, On, 1 );
360+
}
361+
else if ( strcmp( "send", key ) == 0 )
362+
{
363+
lua_pushlightuserdata( L, fNativeToJavaBridge );
364+
lua_pushcclosure( L, Send, 1 );
365+
}
266366
else if ( strcmp( "stop", key ) == 0 )
267367
{
268368
lua_pushlightuserdata( L, fNativeToJavaBridge );

platform/android/ndk/Rtt_AndroidWebViewObject.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,15 @@ class AndroidWebViewObject : public AndroidDisplayObject
5151
static int Reload( lua_State *L );
5252
static int Resize( lua_State *L );
5353
static int DeleteCookies( lua_State *L );
54+
static int InjectJS( lua_State *L );
55+
static int RegisterCallback( lua_State *L );
56+
static int On( lua_State *L );
57+
static int Send( lua_State *L );
5458

5559
public:
5660
void Request(const char *url, const MPlatform::Directory baseDirectory);
5761
void Request(const char *url, const char *baseUrl);
62+
void InjectJSCode( const char *jsCode );
5863
bool IsPopup() const { return fIsPopup; }
5964
bool IsAutoCancelEnabled() const { return fAutoCancelEnabled; }
6065
bool CanGoBack() const { return fCanGoBack; }

platform/android/ndk/jni/JavaToNativeBridge.cpp

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1387,6 +1387,49 @@ JavaToNativeBridge::WebViewHistoryUpdated( JNIEnv * env, int id, jboolean canGoB
13871387
view->SetCanGoForward(canGoForward);
13881388
}
13891389

1390+
void
1391+
JavaToNativeBridge::WebViewJSInterfaceCommonEvent( JNIEnv * env, int id, jstring type, jstring data, jboolean noResult )
1392+
{
1393+
// Validate.
1394+
if (!fPlatform)
1395+
{
1396+
return;
1397+
}
1398+
1399+
// Fetch the display object by ID.
1400+
Rtt::AndroidWebViewObject *view = (Rtt::AndroidWebViewObject*)(fPlatform->GetNativeDisplayObjectById(id));
1401+
if (!view)
1402+
{
1403+
return;
1404+
}
1405+
1406+
jstringResult typeS(env, type);
1407+
jstringResult dataS(env, data);
1408+
Rtt::CommonEvent e(typeS.getUTF8(), dataS.getUTF8());
1409+
1410+
lua_State *L = view->GetL();
1411+
Rtt_Log( "WebViewJSInterfaceCommonEvent: type=%s, data=%s", typeS.getUTF8(), dataS.getUTF8() );
1412+
int status = view->Rtt::DisplayObject::DispatchEventWithTarget( L, e, 1 );
1413+
if ( status == 0 && (! noResult ) )
1414+
{
1415+
int retValueIndex = lua_gettop( L );
1416+
const char* jsonContent = "{}";
1417+
if ( 0 == Rtt::LuaContext::JsonEncode( L, retValueIndex ) )
1418+
{
1419+
jsonContent = lua_tostring( L, -1 );
1420+
}
1421+
Rtt::String s( "window.dispatchEvent(new CustomEvent('" );
1422+
s.Append( typeS.getUTF8() );
1423+
s.Append( "', {detail: " );
1424+
s.Append( jsonContent );
1425+
s.Append( "}));" );
1426+
view->InjectJSCode( s.GetString() );
1427+
1428+
lua_pop( L, 1 );
1429+
}
1430+
lua_pop( L, 1 );
1431+
}
1432+
13901433
void
13911434
JavaToNativeBridge::WebViewClosed( JNIEnv * env, int id )
13921435
{

platform/android/ndk/jni/JavaToNativeBridge.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ class JavaToNativeBridge
9898
void WebViewFinishedLoadUrl( JNIEnv * env, int id, jstring url );
9999
void WebViewDidFailLoadUrl( JNIEnv * env, int id, jstring url, jstring msg, int code );
100100
void WebViewHistoryUpdated( JNIEnv * env, int id, jboolean canGoBack, jboolean canGoForward );
101+
void WebViewJSInterfaceCommonEvent( JNIEnv * env, int id, jstring type, jstring data, jboolean noResult );
101102
void WebViewClosed( JNIEnv * env, int id );
102103
void AdsRequestEvent(bool isError);
103104
void ImagePickerEvent(JNIEnv *env, jstring selectedImageFileName, jint multipleFilesCount);

platform/android/ndk/jni/JavaToNativeShim.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,12 @@ Java_com_ansca_corona_JavaToNativeShim_nativeWebViewHistoryUpdated(JNIEnv * env,
499499
JavaToNativeBridgeFromMemoryAddress(bridgeAddress)->WebViewHistoryUpdated( env, id, canGoBack, canGoForward );
500500
}
501501

502+
JNIEXPORT void JNICALL
503+
Java_com_ansca_corona_JavaToNativeShim_nativeWebViewJSInterfaceCommonEvent(JNIEnv * env, jclass cd, jlong bridgeAddress, jint id, jstring type, jstring data, jboolean noResult)
504+
{
505+
JavaToNativeBridgeFromMemoryAddress(bridgeAddress)->WebViewJSInterfaceCommonEvent( env, id, type, data, noResult );
506+
}
507+
502508
JNIEXPORT void JNICALL
503509
Java_com_ansca_corona_JavaToNativeShim_nativeWebViewClosed(JNIEnv * env, jclass cd, jlong bridgeAddress, jint id)
504510
{

platform/android/ndk/jni/NativeToJavaBridge.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3126,6 +3126,31 @@ NativeToJavaBridge::WebViewRequestDeleteCookies( int id )
31263126
HandleJavaException();
31273127
}
31283128

3129+
void
3130+
NativeToJavaBridge::WebViewRequestInjectJS( int id, const char * jsCode )
3131+
{
3132+
NativeTrace trace( "NativeToJavaBridge::WebViewRequestInjectJS" );
3133+
3134+
jclassInstance bridge( GetJNIEnv(), kNativeToJavaBridge );
3135+
3136+
if ( bridge.isValid() )
3137+
{
3138+
jmethodID mid = bridge.getEnv()->GetStaticMethodID( bridge.getClass(),
3139+
"callWebViewInjectJS", "(Lcom/ansca/corona/CoronaRuntime;ILjava/lang/String;)V" );
3140+
3141+
if ( mid != NULL )
3142+
{
3143+
jstringParam textJ( bridge.getEnv(), jsCode );
3144+
if ( textJ.isValid() )
3145+
{
3146+
bridge.getEnv()->CallStaticVoidMethod( bridge.getClass(), mid, fCoronaRuntime, id, textJ.getValue() );
3147+
HandleJavaException();
3148+
}
3149+
}
3150+
}
3151+
}
3152+
3153+
31293154
void
31303155
NativeToJavaBridge::VideoViewCreate(
31313156
int id, int left, int top, int width, int height)

0 commit comments

Comments
 (0)