We are trying to replace our webview and html with a layout file and a javascript engine of some sort. There is a TON of javascript that must be called and we have a rather large JavaScriptInterface that will need to be accessable by the JS engine. I have been trying out Rhino and J2V8 but cannot figure out a way to give javascript access to a full class of methods or an annotation that works similarly to how you annotate methods for WebView.
If anyone has any insight, it would be much appreciated!
Thanks,
Jon
AFAIK there is no "out-of-the-box" solution for this for JSV8.
But have a look at following example:
public class V8ConsoleBridge implements JavaVoidCallback {
#Override
public void invoke(V8Object receiver, V8Array parameters) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < parameters.length(); ++i) {
if (i > 0) {
sb.append(", ");
}
Object object = parameters.get(i);
if (object instanceof V8Object) {
V8Object v8Object = (V8Object) object;
sb.append(v8Object.toString());
v8Object.release();
} else {
sb.append(object);
}
}
Log.i("goebl-v8", sb.toString());
}
public static void register(V8 v8) {
V8ConsoleBridge console = new V8ConsoleBridge();
V8Object v8Console = new V8Object(v8);
v8.add("console", v8Console);
v8Console.registerJavaMethod(console, "debug");
v8Console.registerJavaMethod(console, "log");
v8Console.registerJavaMethod(console, "info");
v8Console.registerJavaMethod(console, "warn");
v8Console.registerJavaMethod(console, "error");
v8Console.release();
}
}
This is a hardcoded bridge for a JS console object to access Android logging system.
You could write generic code to
scan a class you want to expose in JavaScript, much like JavaScript-Interface for WebView, even with annotations like #JavascriptInterface to only include certain members
write generic code for the invoke which actually invokes members of the receiver class by using Java reflection.
Of course it would be great if J2V8 had this useful code, because it might be used by many projects. When you have a solid solution, create a pull request so I can use it too :-)
If you don't mind wading deep in source code, you might find it useful to check out NativeScript. They provide a generic way to access all Java classes known at compile time in JavaScript, which is internally done via reflection. Well, I've heard that they do it this way, I actually didn't read the source code of NativeScript. In NativeScript, you don't have to create bridges, it's done magically by the build- and runtime-system. Maybe the source inspires you to port the ideas to J2V8.
Related
Can I receive an Object through the WebView.addJavascriptInterface interface ? something like:
public class JavaScriptInterface {
public void method(WhatEverKindOfType param) {
Log.i(tag, param.toString());
}
}
( with: theView.addJavascriptInterface(new JavaScriptInterface(), "obj"); )
and in the JS, for example: obj.method(myEvent);
I obviously tried that, with Object param and String param, but they all come as null. I know I can JSON.stringify it, but this brings that circular object issue (which is solvable, I know, but I don't want to start messing with it)
Is it even possible ?
First off you should not use addJavascriptInterface if you are using PhoneGap. Please, please write a PhoneGap plugin instead. The reason you want to use our Plugin interface is that we have worked around a number of issues with addJavascriptInterface.
When you use the plugin interface you pass a JSONArray to the Java side. That supports all the basic types like int, String, boolean and of course JSONObject. It is the JSONObject that you can store more structured data.
I'm working on an app where I'm going to use both HTML5 in UIWebView and native iOS framework together. I know that I can implement communication between JavaScript and Objective-C. Are there any libraries that simplify implementing this communication? I know that there are several libraries to create native iOS apps in HTML5 and javascript (for example AppMobi, PhoneGap), but I'm not sure if there is a library to help create native iOS apps with heavy JavaScript usage. I need to:
Execute JS methods from Objective-C
Execute Objective-C methods from JS
Listen to native JS events from Objective-C (for example DOM ready event)
There are a few libraries, but I didn't used any of these in big projects, so you might want to try them out:
WebViewJavascriptBridge: https://github.com/marcuswestin/WebViewJavascriptBridge
GAJavaScript: https://github.com/newyankeecodeshop/GAJavaScript
—
However, I think it's something simple enough that you might give it a try yourself. I personally did exactly this when I needed to do that. You might also create a simple library that suits your needs.
1. Execute JS methods from Objective-C
This is really just one line of code.
NSString *returnvalue = [webView stringByEvaluatingJavaScriptFromString:#"your javascript code string here"];
More details on the official UIWebView Documentation.
2. Execute Objective-C methods from JS
This is unfortunately slightly more complex, because there isn't the same windowScriptObject property (and class) that exists on Mac OSX allowing complete communication between the two.
However, you can easily call from javascript custom-made URLs, like:
window.location = yourscheme://callfunction/parameter1/parameter2?parameter3=value
And intercept it from Objective-C with this:
- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType {
NSURL *URL = [request URL];
if ([[URL scheme] isEqualToString:#"yourscheme"]) {
// parse the rest of the URL object and execute functions
}
}
This is not as clean as it should be (or by using windowScriptObject) but it works.
3. Listen to native JS events from Objective-C (for example DOM ready event)
From the above explanation, you see that if you want to do that, you have to create some JavaScript code, attach it to the event you want to monitor and call the correct window.location call to be then intercepted.
Again, not clean as it should be, but it works.
The suggested method of calling objective c from JS in the accepted answer isn't recommended. One example of problems: if you make two immediate consecutive calls one is ignored (you can't change location too quickly).
I recommend the following alternative approach:
function execute(url)
{
var iframe = document.createElement("IFRAME");
iframe.setAttribute("src", url);
document.documentElement.appendChild(iframe);
iframe.parentNode.removeChild(iframe);
iframe = null;
}
You call the execute function repeatedly and since each call executes in its own iframe, they should not be ignored when called quickly.
Credits to this guy.
Update: This has changed in iOS 8. My answer applies to previous versions.
An alternative, that may get you rejected from the app store, is to use WebScriptObject.
These APIs are public on OSX but are not on iOS.
You need to define interfaces to the internal classes.
#interface WebScriptObject: NSObject
#end
#interface WebView
- (WebScriptObject *)windowScriptObject;
#end
#interface UIWebDocumentView: UIView
- (WebView *)webView;
#end
You need to define your object that's going to serve as your WebScriptObject
#interface WebScriptBridge: NSObject
- (void)someEvent: (uint64_t)foo :(NSString *)bar;
- (void)testfoo;
+ (BOOL)isKeyExcludedFromWebScript:(const char *)name;
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector;
+ (WebScriptBridge*)getWebScriptBridge;
#end
static WebScriptBridge *gWebScriptBridge = nil;
#implementation WebScriptBridge
- (void)someEvent: (uint64_t)foo :(NSString *)bar
{
NSLog(bar);
}
-(void)testfoo {
NSLog(#"testfoo!");
}
+ (BOOL)isKeyExcludedFromWebScript:(const char *)name;
{
return NO;
}
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector;
{
return NO;
}
+ (NSString *)webScriptNameForSelector:(SEL)sel
{
// Naming rules can be found at: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/WebKit/Protocols/WebScripting_Protocol/Reference/Reference.html
if (sel == #selector(testfoo)) return #"testfoo";
if (sel == #selector(someEvent::)) return #"someEvent";
return nil;
}
+ (WebScriptBridge*)getWebScriptBridge {
if (gWebScriptBridge == nil)
gWebScriptBridge = [WebScriptBridge new];
return gWebScriptBridge;
}
#end
Now set that an instance to your UIWebView
if ([uiWebView.subviews count] > 0) {
UIView *scrollView = uiWebView.subviews[0];
for (UIView *childView in scrollView.subviews) {
if ([childView isKindOfClass:[UIWebDocumentView class]]) {
UIWebDocumentView *documentView = (UIWebDocumentView *)childView;
WebScriptObject *wso = documentView.webView.windowScriptObject;
[wso setValue:[WebScriptBridge getWebScriptBridge] forKey:#"yourBridge"];
}
}
}
Now inside of your javascript you can call:
yourBridge.someEvent(100, "hello");
yourBridge.testfoo();
In iOS8 you can look at WKWebView instead of UIWebView. This has the following class:
WKScriptMessageHandler: Provides a method for receiving messages from JavaScript running in a webpage.
This is possible with iOS7, checkout http://blog.bignerdranch.com/3784-javascriptcore-and-ios-7/
Your best bet is Appcelerators Titanium offering. They already have built a Obj-C javascript bridge using the V8 engine JavascriptCore engine used by webkit. It's also open source, so you'll be able to download it and tinker with the Obj-C as you like.
Have a look at the KirinJS project: Kirin JS which allows to use Javascript for the application logic and native UI adequate to the platform it runs on.
I created a library like WebViewJavascriptBridge, but it's more JQuery-like, has easier to setup and is easier to use. Doesn't rely on jQuery (though to its credit, had I known WebViewJavascriptBridge existed before writing this I may just have held back slightly before diving in). Let me know what you think! jockeyjs
If you are using WKWebView on iOS 8, take a look the XWebView which can automatically expose the native interface to javascript.
I'm exercising executing javascript from Java. Rhino works very well for this on desktop, but has to fall back to (slow) interpreted mode on Android (due to dalvik being unable to execute the Java bytecode the Rhino JIT compiles).
Android has its built-in V8 javascript engine which is accessed internally via JNI and ought to give much better performance than Rhino; however, the only way I can find to access it is indirectly through a WebView.
Unfortunately, WebView requires a Context, and crashes with NPE with a null context, so I'm unable to even instantiate a dummy WebView to merely execute the code and return the result. The nature of my exercise doesn't really allow me to provide a Context for WebView, so I'm hoping perhaps there's something I'm overlooking.
Several of these V8Threads run in parallel, so it's not really feasible (as far as I'm aware) to add a WebView to my layout and hide it, as I don't believe a single WebView can execute functions in multiple threads.
private class V8Thread extends Thread
{
private WebView webView;
private String source;
private double pi;
private int i, j;
public V8Thread(int i, int j)
{
pi = 0.0;
this.i = i;
this.j = j;
source = "";
try {
InputStreamReader isReader = new InputStreamReader(assetManager.open("pi.js"));
int blah = isReader.read();
while (blah != -1)
{
source += (char)blah;
blah = isReader.read();
}
webView = new WebView(null);
webView.loadData(source, "text/html", "utf-8");
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(this, "V8Thread");
} catch (IOException e) {
e.printStackTrace();
}
}
public double getResult()
{
return pi;
}
#Override
public void run()
{
webView.loadUrl("javascript:Androidpicalc("+i+","+j+")");
}
}
Ideally there must be some supported way to call V8 directly, or at least execute javascript without requiring an actual WebView, as it seems a rather clunky and convoluted method just to run javascript code.
UPDATE
I've rearranged my code a bit, though unseen here is that now I am instantiating the V8Threads on the AsyncTasks's onPreExecute() while keeping everything else in doInBackground(). The source code is read in earlier in the program, so it's not redundantly re-read for each thread.
Because now the V8Thread is instantiated on the UI Thread, I can pass it the current view's Context (I'm using fragments so I can't just pass it "this"), so it no longer crashes.
private class V8Thread extends Thread
{
private WebView webView;
private double pi;
private int i, j;
public V8Thread(int i, int j)
{
pi = 0.0;
this.i = i;
this.j = j;
source = "";
webView = new WebView(v.getContext());
}
#SuppressWarnings("unused")
public void setResult(String in)
{
Log.d("Pi",in);
}
public double getResult()
{
return pi;
}
#Override
public void run()
{
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(this, "V8Thread");
webView.loadData(source, "text/html", "utf-8");
//webView.loadUrl("javascript:Androidpicalc("+i+","+j+")");
webView.loadUrl("javascript:test()");
Log.d("V8Thread","Here");
}
}
However, when executing, logcat spits out one per thread of the error "Can't get the viewWidth after the first layout" and the javascript code never executes. I know the thread fires completely, because the "Here" log message is sent. Here's the relevant test() function in the js code.
function test()
{
V8Thread.setResult("blah");
}
Working correctly, "blah" should show up four times in logcat, but it never shows up. Could be my source code is read incorrectly, but I doubt that.
Scanner scan = new Scanner(assetManager.open("pi.js"));
while (scan.hasNextLine()) source += scan.nextLine();
The only other thing I can imagine is that due to these aforementioned errors, the webView never actually gets around to executing the javascript.
I'll also add that pi.js contains only javascript, no HTML whatsoever. However, even when I wrap it in just enough HTML for it to qualify as a webpage, still no luck.
You can create a new V8 Context via its API and use that to execute your JavaScript, look into https://android.googlesource.com/platform/external/v8 include directory which contains two C++ header files. Link against the libwebcore.so (compiled from https://android.googlesource.com/platform/external/webkit) library via the NDK, nothing special.
v8::Persistent<v8::Context> context = v8::Persistent<v8::Context>::New(v8::Context::New());
context->Enter();
Refer to https://developers.google.com/v8/get_started which will work on Android. Just make sure the device actually ships with V8 (some older devices ship with JSC [JavaScript Core]).
A bit of a late response but it may be useful to anyone stumbling upon this question. I used the J2V8 library which is a Java wrapper on Google's V8 engine. This library comes with pre-compiled V8 binaries for x86 and armv7l Android devices. It work seamlessly. See here for tutorials. Just keep in mid that since pure V8 is just an Ecmascript engine, there is no DOM element available.
I found this really nifty open source ECMAScript compliant JS Engine completely written in C called duktape
Duktape is an embeddable Javascript engine, with a focus on portability and compact footprint.
You'd still have to go through the ndk-jni business, but it's pretty straight forward. Just include the duktape.c and duktape.h from the distributable source here(If you don't want to go through the build process yourself) into the jni folder, update the Android.mk and all that stuff.
Here's a sample C snippet to get you started.
#include "duktape.h"
JNIEXPORT jstring JNICALL
Java_com_ndktest_MainActivity_evalJS
(JNIEnv * env, jobject obj, jstring input){
duk_context *ctx = duk_create_heap_default();
const char *nativeString = (*env)->GetStringUTFChars(env, input, 0);
duk_push_string(ctx, nativeString);
duk_eval(ctx);
(*env)->ReleaseStringUTFChars(env, input, nativeString);
jstring result = (*env)->NewStringUTF(env, duk_to_string(ctx, -1));
duk_destroy_heap(ctx);
return result;
}
Good luck!
Can you get a hold of the Context that is your Application? There are a couple ways to do this.
Call getApplication() from your Activity (Application is a child of Context)
Call getApplicationContent() from a Context (Context likely being your Activity)
UPDATE
According to this Android documentation, your bound Javascript code will run in a separate process anyways, so there should be no need to set it up in its own Thread.
From the link:
Note: The object that is bound to your JavaScript runs in another thread and not in the thread in which it was constructed. (The 'object' being referred to is the JavascriptInterface class)
You can use the AndroidJSCore project. It is not based on V8, but JavaScriptCore. The current version (2.2+) supports JIT compilation on all processors not named MIPS.
UPDATE 2018: AndroidJSCore has been superseded by LiquidCore, which is, in fact, based on V8. Not only does it include the V8 engine, but all of Node.js is available as well.
The scenario is I have a list of items in HTML; when I click on an item I use JS to dynamically create the HTML to load a silverlight app passing in the specific item # (using initParams); and my silverlight app visualizes this in a nice way. I do this on the same page rather than loading a new webpage, and the transition is smooth.
I know it is possible to have silverlight call a JS function on my page (opposite to what I need). I'm thinking it is also possible for my JS function to raise an event/call a method in silverlight, but not exactly sure how - has anyone tried this? While a workaround would be to recreate the silverlight app each time, just raising an event in existing, loaded SL app would would be the perfect solution to my problem.
regards
ewart.
You can call a method in your Silverlight application from JavaScript.
See this blog post
You just need to create a class in your silverlight app that registers itself as callable from JS:
[ScriptableType]
public partial class SomeClass
{
private bool mouseHeldDown = false;
private Point moveMeOffset = new Point();
public SomeClass()
{
HtmlPage.RegisterScriptableObject("SilverlightObject", this);
}
[ScriptableMember]
public void DoThing(int x)
{
//do some stuff
}
}
Then you can call this from JS
document.getElementById("mySilverlightControl").content.SilverlightObject.DoThing(5);
I have a Cocoa app that uses a WebView to display an HTML interface. How would I go about calling an Objective-C method from a Javascript function within the HTML interface?
This is documented at developer.apple.com.
Being rather green, Apple's documentation is pretty unusable for me, so I made a proof of concept of calling Objective C methods from javascript and vice versa in Cocoa, though the latter was much easier.
First make sure you have your webview as the setFrameLoadDelegate:
[testWinWebView setFrameLoadDelegate:self];
You need to tell the webview to watch for a specific object as soon as it's loaded:
- (void)webView:(WebView *)sender didClearWindowObject:(WebScriptObject *)windowScriptObject forFrame:(WebFrame *)frame {
//add the controller to the script environment
//the "ObjCConnector" object will now be available to JavaScript
[windowScriptObject setValue:self forKey:#"ObjCConnector"];
}
Then the business of the communication:
// a few methods to log activity
- (void)acceptJavaScriptFunctionOne:(NSString*) logText {
NSLog(#"acceptJavaScriptFunctionOne: %#",logText);
}
- (void)acceptJavaScriptFunctionTwo:(NSString*) logText {
NSLog(#"acceptJavaScriptFunctionTwo: %#",logText);
}
//this returns a nice name for the method in the JavaScript environment
+(NSString*)webScriptNameForSelector:(SEL)sel {
NSLog(#"%# received %# with sel='%#'", self, NSStringFromSelector(_cmd), NSStringFromSelector(sel));
if(sel == #selector(acceptJavaScriptFunctionOne:))
return #"functionOne"; // this is what you're sending in from JS to map to above line
if(sel == #selector(acceptJavaScriptFunctionTwo:))
return #"functionTwo"; // this is what you're sending in from JS to map to above line
return nil;
}
//this allows JavaScript to call the -logJavaScriptString: method
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)sel {
NSLog(#"isSelectorExcludedFromWebScript: %#", NSStringFromSelector(sel));
if(sel == #selector(acceptJavaScriptFunctionOne:) ||
sel == #selector(acceptJavaScriptFunctionTwo:))
return NO;
return YES;
}
The key is that if you have multiple methods you'd like to call, you need to have them all excluded in the isSelectorExcludedFromWebScript method, and you need the javascript call to map out to the ObjC method in webScriptNameForSelector.
Full project proof of concept file:
https://github.com/bytestudios/JS-function-and-ObjC-method-connector
If you wanna do it in iPhone apps, you would need to do a trick with the UIWebViewDelegate method shouldStartLoadWithRequest:
This api http://code.google.com/p/jsbridge-to-cocoa/ does it for you. It is very lightweight.
I have a solution using NimbleKit. It can call Objective C functions from Javascript.