Recently i have been using GraalVm to execute javascript files from java.
I have the need to reimplement the print() function but i didn't figure out how.
I tried messing with some Bindings's methods and GraalJSScriptEngine's methods, but i didn't get what i was looking for.
Then how can i achieve this?
Note: the implementation of the new print() has to be done in java
Ok i found out how to do that.
It was enough to do this:
ScriptEngine engine = ...
engine.put("print", new Function<String, Object>() {
#Override
public Object apply(String msg) {
System.out.println(msg);
return null;
}
});
Related
I am passing an object to a scriptEngine using the engine.put() method, and am attempting to retrieve a property of said object using the engine.eval() method. However I can't seem to access them as the object seems to lose its methods when put in the engine, and the get() method I'd normally use in Javascript also seems to fail.
try {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("graal.js");
engine.put("transformContext",transformContext);
engine.put("dataRecordsByName",transformContext.getDataRecordsByName());
//These three all return what I'm expecting - 2x the whole object and then just dataRecordsByName property
System.out.println(engine.get(transformContext));
System.out.println(engine.eval("print(transformContext"));
System.out.println(engine.get(dataRecordsByName));
//These throw errors get() and getDataRecordsByName() seemingly do not exist for transformContext in the engine
System.out.println(engine.eval("print(transformContext.getDataRecordsByName())"));
System.out.println(engine.eval("print(transformContext.get('dataRecordsByName'))"));
}catch(Exception e){
System.err.println("Error evaluating script: "+e.getMessage());
}
I have read that scriptEngine only imports public methods from public classes. In this case though TransformContext is public as are all its methods, so that should be fine?
Any help understanding this or a solution would be appreciated.
After some further research it seems that graal.js does not map properties as you may expect by default, but can do so if you run it in nashorn compatability mode.
System.setProperty("polyglot.js.nashorn-compat", "true");
After doing this, you can retrieve the object properties as you normally would in JavaScript:
engine.eval("console.log(transformContext.dataRecordsByName)");
Thanks to the guys on this thread:
https://github.com/oracle/graaljs/issues/169
I have loaded a webpage using WebView component and added a JavascriptInterface. Please check the code below,
val webview = WebView(this)
setContentView(webview)
webview.settings.javaScriptEnabled = true
webview.loadUrl(HOME_PAGE_URL)
webview.addJavascriptInterface(JavascriptInterface(),”javascript_bridge”)
And when I call the invoke from Javascript using window.javascript_bridge.showToast(“Information Saved”);
private inner class JavascriptInterface
{
#android.webkit.JavascriptInterface
fun showToast(text: String?)
{
Log.d("WEBVIEW", text);
}
}
I am able to call the method from Javascript to Kotlin without any trouble.
But now I want to pass an Object from Javascript to Kotlin like below,
var info = {
message: “Information Saved”,
ID: 123456
}
And when I call the invoke from Javascript using window.javascript_bridge.showToast(info);
I tried to change to the data type to Any, but the value passed from Javascript is null
private inner class JavascriptInterface
{
#android.webkit.JavascriptInterface
fun showToast(text: Any?)
{
Log.d("WEBVIEW", text.toString());
}
}
As far as i know, the javaScript interface methods only accepts primitive types of data as parameters (as discussed on this question).
If you still want to achieve that, you may serialize the object (to JSON format, for instance) in the javaScript and then Deserialize it in Java.
Hope it helps :)
I'm using Selenium WebDriver (Java) and TestNG to do some testing on a website I created. In this website, I also have JavaScript and in some of the functions, it returns values and also outputs values to the browser console through console.log().
I was wondering if there is an easy way for Selenium WebDriver to access some of this JavaScript information so I can perform assertions using TestNG.
I'm quite new to Selenium but I understand that you can do something like:
WebDriver driver = new ChromeDriver();
driver.findElement(By.id("btn")).click();
So is there anything similar I can do using WebDriver to read the JavaScript on the site?
Clarification
It looks like people are making the assumption that I'm trying to "execute" JavaScript code through Selenium.
Thats not the case. Instead, I'm trying to store already-defined JavaScript variable using Selenium.
Basically, I want Selenium to be able to grab the JavaScript variable's value, store it locally, and then do an assertion test on it.
Attempt 1
Say I have the following JS code for my website:
$(document).ready(function() {
var foo = $(#"input-field-val").val();
function returnFoo() {
return foo;
}
});
From what I've reading and understanding, in my seperate Selenium test file (Selenium.java), I should be able to do something like this?:
public class Selenium {
WebDriver driver = new FirefoxDriver();
JavascriptExecutor js = (JavascriptExecutor) driver;
#Test
public void testSample() {
driver.get("www.mywebsite.com");
js.executeScript("alert(returnFoo());");
}
}
I do something similar to what's above but no alert box pops up. Instead, I get an error message:
Exception in thread "main" org.openqa.selenium.WebDriverException: ReferenceError: returnFoo is not defined
I'm pretty sure I'm not understanding what it means when its said that the JS variable
should not be part of a closure or local variable
I have also tried defining a global variable above the $(document).ready(function()... and setting is within function returnFoo() but still doesn't work.
Attempt 2
I've moved both foo and returnFoo() outside of the $(document).ready(function().... That has fixed ReferenceError message that I was getting in Attempt 1 above.
I hav also given foo a value so my JS code looks something like this:
var foo = "Selenium test run";
$(document).ready(function() {
...
});
function returnFoo() {
return foo;
}
Now, I'm having a tough time assigning the return of returnFoo() to a local variable within my Selenium test. Here's what I've attempted:
public static void main(String[] args) {
WebDriver driver = new FirefoxDriver();
JavascriptExecutor js = (JavascriptExecutor) driver;
driver.get("http://localhost:8080/HTML5TestApp/prod.html");
Object val = js.executeScript("window.returnFoo();");
System.out.println(val);
}
But the console display null instead of the actual value of "Selenium test run".
Attempt 2 - SOLUTION
It looks like if I do Object val = js.executeScript("alert(returnFoo());"); I get the value of foo.
SOLUTION
So here's the solution I've come up w/ to my problem thanks to the solution by Martin Foot below.
In my JavaScript file, I created a var and a setter/getter function like so:
index.js
var seleniumGlobal;
$(document).ready(function() {
...
)};
function setSG(toSet) {
seleniumGlobal = toSet;
}
function getSG() {
return seleniumGlobal;
}
SampleTest.java
// Do all the necessary imports
public class SampleTest {
WebDriver driver = new FirefoxDriver();
JavascriptExecutor js = (JavascriptExecutor) driver;
#Test
public void testPrintVal() {
String sgVal = (String) js.executeScript("alert(getSG());");
Assert.assertEquals("new value for seleniumGlobal", sgVal);
}
}
So whenever some code in my JavaScript sets my seleniumGlobal variable through the setter method, I can call it through my Selenium test and do assertions on it.
This is probably not the most efficient way to do but if someone else has a better solution, please let me know.
All you have to do is:
Object val = js.executeScript("return returnFoo();");
That will give you what you are looking for.
No JavaScript functions need be defined. Nor is alert() needed.
Object result = js.executeScript("return globalVar");
For Python:
result = driver.execute_script("return globalVar")
In Ruby you can use page.execute_script to evaluate a JavaScript variable (if it is accessable from the scope of the web browser). It looks like there is a similar method in Java here.
Edit: This might be a use case that is more suited to a JavaScript unit testing framework such as Jasmine.
Awesomium easily allows for C++ code to call Javascript methods, but I haven't found a definite answer as to if it can do the opposite. This site seems to say that you can, but looking through the text and examples doesn't enlighten me.
So, I'm looking for a definite answer: can I call C++ variables/methods in my Javascript(Jquery), or not?
If you could include a simple example, that would be extremely appreciated as well.
Thank you!
You definitely can-- you'll just need to build an extra layer on top of WebView::setObjectCallback and WebViewListener::onCallback using delegates/function-pointers.
I wrote a quick JSDelegate.h class (view it here) that you can use to hookup "onCallback" events directly to C++ member functions.
The basic idea is to maintain a mapping of callback names to delegates:
typedef std::map<std::wstring, Awesomium::JSDelegate> DelegateMap;
DelegateMap _delegateMap;
And call the corresponding function from your WebViewListener::onCallback:
void MyListener::onCallback(Awesomium::WebView* caller, const std::wstring& objectName,
const std::wstring& callbackName, const Awesomium::JSArguments& args)
{
DelegateMap::iterator i = _delegateMap.find(callbackName);
if(i != _delegateMap.end())
i->second(caller, args);
}
And then, each time you wish to bind a specific C++ function, you would do it like so:
// Member function we wish to bind, must have this signature for JSDelegate
void MyClass::myFunction(Awesomium::WebView* caller, const Awesomium::JSArguments& args)
{
// handle args here
}
// Instantiate MyClass instance in C++
MyClass* myClass = new MyClass();
// Create corresponding 'MyClass' object in Javascript
webView->createObject(L"MyClass");
// Do the following for each member function:
// Bind MyClass::myFunction delegate to MyClass.myFunction in JS
_delegateMap[L"myFunction"] = Awesomium::JSDelegate(myClass, &MyClass::myFunction);
webView->setObjectCallback(L"MyClass", L"myFunction");
Then, you should be able to call MyClass::myFunction directly from Javascript like so:
MyClass.myFunction("foo", 1, 2 3)
Hope this helps! I haven't tested any of the code but I wrote it with Awesomium v1.6 RC4 SDK in mind.
I'm programming a JavaScript application which accesses some C++ code over Google's V8.
Everything works fine, but I couldn't figure out how I can throw a JavaScript exception which can be catched in the JavaScript code from the C++ method.
For example, if I have a function in C++ like
...
using namespace std;
using namespace v8;
...
static Handle<Value> jsHello(const Arguments& args) {
String::Utf8Value input(args[0]);
if (input == "Hello") {
string result = "world";
return String::New(result.c_str());
} else {
// throw exception
}
}
...
global->Set(String::New("hello"), FunctionTemplate::New(jsHello));
Persistent<Context> context = Context::New(NULL, global);
...
exposed to JavaScript, I'ld like to use it in the JavaScript code like
try {
hello("throw me some exception!");
} catch (e) {
// catched it!
}
What is the correct way to throw a V8-exception out of the C++ code?
Edit: This answer is for older versions of V8. For current versions, see Sutarmin Anton's Answer.
return v8::ThrowException(v8::String::New("Exception message"));
You can also throw a more specific exception with the static functions in v8::Exception:
return v8::ThrowException(v8::Exception::RangeError(v8::String::New("...")));
return v8::ThrowException(v8::Exception::ReferenceError(v8::String::New("...")));
return v8::ThrowException(v8::Exception::SyntaxError(v8::String::New("...")));
return v8::ThrowException(v8::Exception::TypeError(v8::String::New("...")));
return v8::ThrowException(v8::Exception::Error(v8::String::New("...")));
In last versions of v8 Mattew's answer doesn't work. Now in every function that you use you get an Isolate object.
New exception raising with Isolate object look like this:
Isolate* isolate = Isolate::GetCurrent();
isolate->ThrowException(String::NewFromUtf8(isolate, "error string here"));