Setting variables directly in Java classes doesn't seem to be working. Why not? What is the proper syntax? Where does the variable go??
The following prints out 2 and 1. Thus the f.x=2; never happened according to the object f of Foo.
#Test
public void testJS2Java() throws IOException, ScriptException, Exception {
ScriptEngineManager factory = new ScriptEngineManager();// create JavaScript engine
ScriptEngine engine = factory.getEngineByName("JavaScript");
class Foo {
int x = 1;
}
Foo f = new Foo();
engine.put("f", f);
System.out.println(engine.eval("f.x=2;"));
System.out.println(f.x);
}
The f.x=2; executes without error but which x was set?
Three issues with your test:
Nashorn allows access only to public members of public classes (from
exported modules for jdk9+) only. The local class Foo is
not public. So its members are not accessible from JavaScript.
Nashorn allows access to static members only from "Java type
objects" and not from instances of Java types. (different from Java).
Nashorn would ignore property sets on Java object if no public
field or public bean property with appropriate setter is found.
A working sample demonstrating access to a static Java field from Nashorn:
import javax.script.*;
public class Main {
public static int x = 10;
public static void main(String[] args) throws Exception {
ScriptEngine e = new ScriptEngineManager().
getEngineByName("JavaScript");
// access type object for Java class "Main" using Java.type
e.eval("var M = Java.type('Main');");
// access public static field 'x' of Main class
e.eval("print(M.x)");
// assign to public static field 'x' of Main class
e.eval("M.x += 10;");
// change is seen from Java
System.out.println(Main.x);
}
}
Related
Upgrading from nashorn to graalvm with openjdk 17. getting a PolyglotException: java.lang.ExceptionInInitializerError when adding bindings to engine scope. my script engine is getting initialized in init method & later used in different class. Wondering if its multi threading issue. unable to figure out the root cause
script engine gets initialized in init block of class 1 & 2.
Class 1
public void init() {
createScriptEngine
}
Class 2
public static ScriptEngine createScriptEngine (){
ScriptEngineManager manager = new ScriptEngineManager();
ret = manager.getEngineByName(engineName);
if(ret instanceof GraalJSScriptEngine) {
Bindings bindings = ret.getBindings(ScriptContext.ENGINE_SCOPE);
bindings.put("polyglot.js.nashorn-compat", true);
}
}
The engine itself is used in class 3 to evaluate script
class 3
private object evaluateScript(ScriptEngine scriptEngine, String script){
Bindings bindings = scriptEngine.getContext().getBindings(ScriptContext.ENGINE_SCOPE);
bindings.put(SCRIPT_VAR_t1, this);
bindings.put(SCRIPT_VAR_t2, cTag);
bindings.put(SCRIPT_VAR_t3, tAttribs
Object ret = scriptEngine.eval(script, scriptEngine.getContext());
}
The issue is resolved now after creating context only once.
I need to parse JavaScript from java.
I have already tried the NashornScriptEngine (builtin in the jdk), and RhinoScriptEngine, but they both (apparently) do not support the "class" keyword.
This is what i tried:
public static void main(String[] args) throws Throwable {
RhinoScriptEngineFactory factory = new RhinoScriptEngineFactory();
RhinoScriptEngine engine = (RhinoScriptEngine) factory.getScriptEngine();
engine.eval(new FileReader("js/SomeClass.js"));
}
And this is the content of SomeClass.js:
class SomeTestClass {
}
function start() {
print("hello")
}
And this is the stacktrace:
Exception in thread "main" javax.script.ScriptException: identifier is a reserved word: class in eval at line number 0 at column number 6
at org.mozilla.javascript.engine.RhinoScriptEngine.eval(RhinoScriptEngine.java:124)
at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:249)
at com.clut.MyMain.main(MyMain.java:21)
So what can i do to parse javascript containing classes?
I can even use another library if its needed
I found out that GraalVm has builtin javascript parsing
I have created an android WebView, and injected javascript interface using addJavascriptInterface(mObject, "jsinterface"). It works fine until I create an object with same name (jsinterface) in JavaScript using the new operator.
My Java Code:
WebView mWebView = findViewById(R.id.myWebView);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.setWebChromeClient(new MyWebChromeClient((Activity)mContext));
mWebView.addJavascriptInterface(new testClass(), "jsinterface");
mWebView.loadUrl("UrlToLoad");
testClass.java
public class testClass{
public testClass() {
}
#JavascriptInterface
public String testNativeMethod() {
return "Java method called!!";
}
}
My Java Script Code
test.js
function test(msg){
this.message = msg;
this.testJSMethod = function(){
return this.message;
}
}
alert(jsinterface.testNativeMethod()); // prints Java method called!!
jsinterface= new test("JS method called...");
alert(jsinterface.testJSMethod()); // prints JS method called...
alert(jsinterface.testNativeMethod()); // errors "NPMethod called on non- NPObject"
Problem:
Is this possible for a javascript object to have access to both , i.e javascript methods and native JAVA methods(exposed to it via javascriptinterface) ? Is there any possibility of setting any property to webview OR executing any JS script to get this done?
Think about document in javascript. When you are in a web browser, this is a global object that you have access to at any point. If you make your own new var called document, you are going to have problems accessing the global document.
When you execute this line:
mWebView.addJavascriptInterface(new testClass(), "jsinterface");
you are adding a global object called jsinterface. This is the same situation as document. If you create a var with the same name, it will overwrite the existing global reference.
Once you add a javascript interface to the WebView, you don't need to create a new reference to the interface. addJavascriptInterface has already done that for you.
TRY
You may try to make another object, which will retranslate calls to javascript interface.Implement onPageStarted method in WebViewClient , and inject javascript in onPageStarted method, in the following way.
mWebView.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
#Override
public void onPageStarted (WebView view, String url, Bitmap favicon){
String jsScript= "javascript:var functions_array = ['testNativeMethod'];";
jsScript+="var jsinterface = {};"
jsScript+="functions_array.map(function(id){"
jsScript+="jsinterface[id]= function() {"
jsScript+="try{return temp_obj[id].apply(temp_obj, arguments);}"
jsScript+="catch(e) { console.log('ERROR: ' + e + ', method ' + id);"
jsScript+="return false;}}})"
view.loadUrl(jsScript);
}
});
Hope this helps :-)
I want to make a class of mine accessible in JavaScript via a C# WebView-Control.
Therefore I am using the WebView.AddWebAllowedObject method. However if I assign an attribute, it works fine, but if I assign the whole class to get all attributes in js, all of the attributes(and methods btw) are "undefined". I tried everything I found in the www. See the attached code:
//The class I want to make accessible
[AllowForWeb, ComVisible(true)]
[MarshalingBehavior(MarshalingType.Agile)]
public class DeviceInformation
{
public string IPAdress { get; private set; }
public DeviceInformation()
{
IPAdress = GetIPAdress();
}
public string GetDeviceUUID()
{
EasClientDeviceInformation deviceinfo = new EasClientDeviceInformation();
return deviceinfo.Id.ToString();
}
public string GetIPAdress()
{
List<string> ipAddresses = new List<string>();
var hostnames = NetworkInformation.GetHostNames();
foreach (var hn in hostnames)
{
if (hn?.IPInformation != null && (hn.IPInformation.NetworkAdapter.IanaInterfaceType == 71 ||
hn.IPInformation.NetworkAdapter.IanaInterfaceType == 6))
{
string ipadress = hn.DisplayName;
return ipadress;
}
}
return string.Empty;
}
}
Here the objects are initialized.
DeviceInformation devinf = new DeviceInformation();
private void View_NavigationStarting(WebView sender, WebViewNavigationStartingEventArgs args)
{
if (args.Uri.Host == "")
{
//win_ipadress has an ipadress as value
view.AddWebAllowedObject("win_ipadress", devinf.IPAdress);
//deviceInformation is initialized as well but I have no access to its attributes
view.AddWebAllowedObject("deviceInformation", devinf);
}
}
That's the way i call it in js:
else if ($.os.ie) {
myIpAdr = window.win_ipadress;
//Throws an exception because GetIPAdress() is "undefined"
myIpAdr = window.deviceInformation.GetIPAdress();
}
I am using this in a Windows Universal App. The Javascript and in the WebView displayed HTML-Code is already in use for Android an iOS.
I believe you need to define the method name starting with a lower case character.
For example: change GetIPAddress to getIPAddress.
I tested it on my side and found if I use the upper case name 'GetIPAddress', it won't work. But if I use getIPAddress, it works.
And after I read kangax's explanation in this thread, I think it makes sense.
[Update]
Since it still doesn't work after you make the change on method name, I think the issue should be related to how you expose the windows runtime object. I guess you simply defined the DeviceInformation class and tried to use it in the same project.
First, we need to create a separate windows universal windows runtime component project.
The c# class DeviceInformation should be put into this project. Keep the same code.
Then, in your universal app project, add reference to the windows runtime component and keep rest code to consume the windows runtime object.
[Update 2]
Just noticed an interesting behavior in VS. No matter if the Method name we defined in C# is starting with uppercase or lowercase, the visual studio intellisense shows the lowercase, so the method name will be automatically converted when we try to use it in js.
I'm evaluating the JavaScript/Silverlight interop capabilities and have been able to create a Silverlight instance using JavaScript and call methods on it. However, I now need a way of passing a JavaScript callback function to Silverlight.
Simply passing a JavaScript function to a Silverlight method expecting an Action doesn't work although the error suggest that it's intended to. What am I missing? The exception details:
Microsoft JScript runtime error: System.ArgumentException: Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type.
at System.Delegate.CreateDelegate(Type type, Object firstArgument, MethodInfo method, Boolean throwOnBindFailure)
at System.Windows.Hosting.ScriptingInterface.GetDelegateForScriptObject(Type eventHandlerType, ScriptObject obj)
at System.Windows.Browser.ScriptObject.ConvertTo(Type targetType, Boolean allowSerialization)
at System.Windows.Hosting.ScriptingInterface.GetScriptParamValueForType(ScriptParam scriptParam, Type desiredType)
at System.Windows.Hosting.ScriptingInterface.ConvertFromScriptParams(ParameterInfo[] parameters, ScriptParam[] args)
at System.Windows.Browser.ManagedObjectInfo.ScriptMethod.Invoke(ManagedObject obj, InvokeType invokeType, ScriptParam[] args)
at System.Windows.Browser.ManagedObjectInfo.Invoke(ManagedObject obj, InvokeType invokeType, String memberName, ScriptParam[] args)
at System.Windows.Hosting.ManagedHost.InvokeScriptableMember(IntPtr pHandle, Int32 nMemberID, Int32 nInvokeType, Int32 nArgCount, ScriptParam[] pArgs, ScriptParam& pResult, ExceptionInfo& pExcepInfo)
Without seeing your code I cannot say what you are doing wrong but I can describe what has worked for me in the past.
On the Silverlight side you need to register a class as a scriptable object. Then create a method that is marked as a ScriptableMember and takes a string that will be your passed in JavaScript method. I also added a method called InvokeCallback which will invoke the passed in javascript callback.
[ScriptableType]
public partial class Page : UserControl
{
private string jsCallback;
public Page()
{
InitializeComponent();
HtmlPage.RegisterScriptableObject("silverlightInterop", this);
}
[ScriptableMember]
public void RegisterCallback(string callback)
{
jsCallback = callback;
}
public boid InvokeCallback(string arg)
{
if(!string.IsNullOrEmpty(jsCallback))
{
System.Windows.Browser.HtmlPage.Window.Invoke(jsCallback, arg);
}
}
}
On the JavaScript side you can call the RegisterCallback method that you defined in Silverlight by grabbing the silverlight object on the page and calling the method off of the name "silverlightInterop" which we registered as the name of our scriptable object.
function jsCallback(someArg) {
alert(someArg);
}
var silverLightControl = document.getElementById("silverlightControl");
silverLightControl.content.silverlightInterop.RegisterCallback("jsCallback");
I hope this helps. I also have some sample code the demonstrates this which I wrote a while ago here
There is a simpler way to pass Javascript function to Silverlight. All values from javascript can be represented by the ScriptObject type, this includes a function. Its for this reason that the ScriptObject has a InvokeSelf method.
You can create a property as simple as:-
[ScriptableMember]
public ScriptObject Callback { get; set; }
Now lets say in Javascript we have this function:-
function sayHello(name)
{
alert("Hello " + name);
}
We can assign this to the property (assume the RegisterScriptableObject("Page", this) has been done) with this JS:-
document.getElementById("mySL").Content.Page.Callback = sayHello;
Now in Silverlight code we can invoke this Callback with:-
Callback.InvokeSelf("Freed");
Apparently, this does work if the using a delegate type of EventHandler (or EventHandler<>), but not for other delegate types.
My Code:
<!-- language: JavaScript -->
function sendText() {
return "Hi from Javascript!";
}
<!-- language: C# -->
string obj = HtmlPage.Window.Invoke("sendText", null) as string;
txtReturnData.Text = obj;
<!-- language: VB.Net -->
Dim obj As String = TryCast(HtmlPage.Window.Invoke("sendText", Nothing), String)
txtReturnData.Text = obj