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.
We're trying to optimize for start-up time of JS code on mobile and looking for the opportunities. I've found Facebook Hermes JS engine created for a similar purpose but we heavily depend on V8 at the moment.
Can build-time precompilation be done with V8 meaning parsing and code optimization will be done in compile-time and saving some time in runtime? Generating LLVM bitcode from the source code and executing bitcode in runtime seems to be pretty close to what i imagine. WASM seems to be not an option (at least for mobile).
If it's possible, can one provide a simple example of trivial JS code optimized with V8?
PS. Probably it would also help with memory consumption which can be the secondary goal.
V8 supports heap snapshots for this very purpose. It's used by the Atom editor, for instance, to improve startup time. It's not so much about precompiling as it is about prebuilding your global environment and instantiating your functions (which may not be compiled [yet], just converted to bytecode for Ignition, which is sufficient). If you're using Electron, the mksnapshot npm package may be useful. (And if not, looking at how it works may still be useful.)
I haven't done any V8 hacking, but the example they link from the blog post above is as follows:
TEST(PerIsolateSnapshotBlobs) {
DisableTurbofan();
const char* source1 = "function f() { return 42; }";
const char* source2 =
"function f() { return g() * 2; }"
"function g() { return 43; }"
"/./.test('a')";
v8::StartupData data1 = v8::V8::CreateSnapshotDataBlob(source1);
v8::StartupData data2 = v8::V8::CreateSnapshotDataBlob(source2);
v8::Isolate::CreateParams params1;
params1.snapshot_blob = &data1;
params1.array_buffer_allocator = CcTest::array_buffer_allocator();
v8::Isolate* isolate1 = v8::Isolate::New(params1);
{
v8::Isolate::Scope i_scope(isolate1);
v8::HandleScope h_scope(isolate1);
v8::Local<v8::Context> context = v8::Context::New(isolate1);
delete[] data1.data; // We can dispose of the snapshot blob now.
v8::Context::Scope c_scope(context);
CHECK_EQ(42, CompileRun("f()")->ToInt32(isolate1)->Int32Value());
CHECK(CompileRun("this.g")->IsUndefined());
}
isolate1->Dispose();
v8::Isolate::CreateParams params2;
params2.snapshot_blob = &data2;
params2.array_buffer_allocator = CcTest::array_buffer_allocator();
v8::Isolate* isolate2 = v8::Isolate::New(params2);
{
v8::Isolate::Scope i_scope(isolate2);
v8::HandleScope h_scope(isolate2);
v8::Local<v8::Context> context = v8::Context::New(isolate2);
delete[] data2.data; // We can dispose of the snapshot blob now.
v8::Context::Scope c_scope(context);
CHECK_EQ(86, CompileRun("f()")->ToInt32(isolate2)->Int32Value());
CHECK_EQ(43, CompileRun("g()")->ToInt32(isolate2)->Int32Value());
}
isolate2->Dispose();
}
That blog post (and the associated example?) is from 2015 so things have probably moved on since.
I'm using the new WebEngine to play around and learn.
I've been trying to find some similar methods found using Qt WebKit: addToJavaScriptWindowObject()
I found that using Qt WebEngine, I have to use the QWebChannel to register functions to the JavaScript window object. If this is correct, it takes me to the following question.
I've installed Qt 5.4.0 on my computer. I noticed that qwebchannel.js is not found in the SDK installed on my computer. I found it on the Git source.
If I have a Qt native desktop application with a QWebEnginePage and QWebEngineView, what do I need to be able to register functions on the JavaScript window object?
My desktop application navigates automatically to a http page that I have created. So I have access to the content connected to the QWebEngineView.
What are the steps to take so I can make this work?
In Qt5.6, if you want to make C++ part and JavaScript to communicate, the only way to do it is using QWebChannel on a QWebEngineView, as you stated. You do it this way in the .cpp file:
m_pView = new QWebEngineView(this);
QWebChannel * channel = new QWebChannel(page);
m_pView->page()->setWebChannel(channel);
channel->registerObject(QString("TheNameOfTheObjectUsed"), this);
Here, you just say that you register an object named TheNameOfTheObjectUsed that will be available on the JS side. Now, this is the part of code to use in the JS side :
new QWebChannel(qt.webChannelTransport, function (channel) {
// now you retrieve your object
var JSobject = channel.objects.TheNameOfTheObjectUsed;
});
Now, if you want to retrieve some properties of the class in the JS side, you need to have a method on the C++ side which returns a string, an integer, a long... This is what it looks like on the C++ side, in your .h:
Q_INVOKABLE int getInt();
Q_PROPERTY(int myIntInCppSide READ getInt);
And now, you get the int like this on the JS side :
var myIntInJSside= JSobject.myIntInCppSide;
This is a very simple explanation, and I recommend you to watch this video which was very useful to me. Also, you might want to read more about the JavaScript API provided by QWebChannel, as well as the documentation about QWebChannel.
Hope that helps!
I will summarize your questions as following:
Do I need QWebChannel to register JavaScript functions in the WebEngine?
Where can I find QWebChannel.js
How to communicate JS to C++ and C++ to JS
First, let take a simple code to play with:
#include <QApplication>
#include <QDebug>
#include <QWebEngineView>
#include <QWebChannel>
// ... DEFINITIONS HERE
auto main( int argn, char* argv[] )-> int
{
QApplication app(argn, argv);
QWebEngineView browser;
browser.resize(QSize(800,600));
browser.show();
browser.load(QUrl("http://www.wikipedia.org"));
// .. SETUP HERE
QObject::connect(&browser, &QWebEngineView::loadFinished, [&browser](bool ok)
{
qDebug()<<"Load Finished " << ok;
// TEST CODE HERE
));
return app.exec();
}
Explanation: This code creates a Qt application, creates a QWebEngineView and set some minimal properties to make it visible.
A page from 'Wikipedia' is loaded inside and a signal/slot event is connected to print some log when the page is finally loaded.
How to call JS functions from C++ ?
You can simply call JS using QWebEnginePage::runJavaScript as following. Add this code to the TEST CODE HERE.
QString code = QStringLiteral(
R"DELIM(
var links = document.getElementsByTagName('a');
for ( var i=0; i<links.length; ++i)
{
links[i].style.backgroundColor = 'yellow';
};
)DELIM");
browser.page()->runJavaScript(code, 42);
Explanation: This code execute some JS into the browser, on a context ID 42, avoiding collision with the default context of the page ID 0. The script change the background-color of each link to yellow.
How to call C++ from JS?
In this case, we need the QWebChannel mechanism to register C++ objects into JavaScript.
First, let create the C++ interface, callable from JS (in DEFINITION):
class JsInterface: public QObject
{
Q_OBJECT
public:
/// Log, for debugging
Q_INVOKABLE void log(const QString& str) const
{
qDebug() << "LOG from JS: " << str;
}
};
#include "main.moc"
Explanation: This code declare and define a QObject class with a simple log function inside. It is important to declare the function Q_INVOKABLE otherwise JavaScript can not find it!. As the declaration is inside the same file as the rest of the code, we include the auto-moc file from QT after (it is main.moc because my file is main.cpp).
Create a function in DEFINITION which return the JavaScript QWebChannel.js content. The content of QWebChannel.js can be found in your QT library (./5.12.2/Src/qtwebchannel/examples/webchannel/shared/qwebchannel.js or ./Examples/Qt-5.12.2/webchannel/shared/qwebchannel.js). You are free to load this directly in your page.
In DECLARATION section, append:
QString qWebChannelJs()
{
return R"DELIMITER(
// COPY HERE ALL THE FILE
)DELIMITER";
}
And we inject it in our code (Append it to TEST CODE HERE section):
browser.page()->runJavaScript(qWebChannelJs(), 42);
We need to setup the QWebChannel in C++ side (SETUP section):
QWebChannel channel;
JsInterface jsInterface;
browser.page()->setWebChannel(&channel, 42);
channel.registerObject(QString("JsInterface"), &jsInterface);
Explanation: We create a channel, the JsInterface object and register them into the browser. We need to use the same context id 42 (but could be another other number between 0 and 255).
Finally, in our JS code, we access the channel and call the function of the interface (append to TEST CODE section):
QString code2 = QStringLiteral(
R"DELIM(
window.webChannel = new QWebChannel(qt.webChannelTransport, function( channel)
{
var cpp = channel.objects.JsInterface;
cpp.log("Hello from JavaScript");
});
)DELIM");
browser.page()->runJavaScript(code2, 42);
Considerations
It is worth to mention that any call from C++ to JavaScript or from JavaScript to C++ goes through an Inter-Process-Communication (IPC) which is asynchronous. This means that runJavaScript returns before the JavaScript is executed, and that JavaScript returns before the C++ logis executed.
Full code
#include <QApplication>
#include <QDebug>
#include <QWebEngineView>
#include <QWebChannel>
QString qWebChannelJs()
{
return R"DELIMITER(
// TODO INSERT JS code here
)DELIMITER";
}
class JsInterface: public QObject
{
Q_OBJECT
public:
/// Log, for debugging
Q_INVOKABLE void log(const QString& str) const
{
qDebug() << "LOG from JS: " << str;
}
};
#include "main.moc"
auto main( int argn, char* argv[] )-> int
{
QApplication app(argn, argv);
QWebEngineView browser;
browser.resize(QSize(800,600));
browser.show();
browser.load(QUrl("http://www.wikipedia.org"));
// .. SETUP HERE
QWebChannel channel;
JsInterface jsInterface;
browser.page()->setWebChannel(&channel, 42);
channel.registerObject(QString("JsInterface"), &jsInterface);
QObject::connect(&browser, &QWebEngineView::loadFinished, [&browser](bool ok)
{
qDebug()<<"Load Finished " << ok;
// TEST CODE HERE
QString code = QStringLiteral(
R"DELIM(
var links = document.getElementsByTagName('a');
for ( var i=0; i<links.length; ++i)
{
links[i].style.backgroundColor = 'yellow';
};
)DELIM");
browser.page()->runJavaScript(code, 42);
browser.page()->runJavaScript(qWebChannelJs(), 42);
QString code2 = QStringLiteral(
R"DELIM(
window.webChannel = new QWebChannel(qt.webChannelTransport, function( channel)
{
var cpp = channel.objects.JsInterface;
cpp.log("Hello from JavaScript");
});
)DELIM");
browser.page()->runJavaScript(code2, 42);
});
return app.exec();
}
Related topics:
How to setup QWebChannel JS API for use in a QWebEngineView?
External documentation:
https://doc.qt.io/qt-5/qwebengineview.html
https://doc.qt.io/qt-5/qwebchannel.html
https://doc.qt.io/qt-5/qtwebengine-webenginewidgets-contentmanipulation-example.html
Qt has documentation on this now:
Qt WebChannel Standalone Example
You have to add a QWebSocketServer to your cpp app that the QWebEngineView's HTML/Javascript will connect to using a WebSocket. Then use QWebChannel for two way communication.
An alternative and much simpler way of communication with the page is to use runJavaScript function:
view->page()->runJavaScript("alert('Hello from C++');");
It has its limitations: the call must be initiated from the C++ side and you can get only synchronous response from JS. But there is an upside too: no modification of the underlying webpage is necessary.
Currently opened webpage can be accessed using QWebEngineView::page() function, as in the example above. During the navigation, the browser doesn't change the page until the next one is received from the network, so this function returns valid page object at any time. But your JS may still interrupt new page loading in a way that you'll appear in the document.readyState == 'loading' where the DOM tree is not yet constructed and some scripts on the page might not have been run yet. In this case you should wait for the DOMContentLoaded event.
I am trying to initialize Oracle's javascript nashorn engine directy from jdk.nashorn.* namespace.
(nashorn library is a beta version of 2013 Jan).
There is a web sample which calles Nashorn engine instance of engine, using javax.script.ScriptEngineManager utility class.
var engine = ScriptEngineManager.getEngineByName(*)
However, I like to keep away from ScriptEngineManager, so i need to call engine directly in the same way Rhino can.
Context cx = Context.enter();
Scriptable scope = cx.initStandardObjects();
How can I create nashorn engine instance directly?
javax script engine by type application/javascript Hashorn, get back a script engine and tell it to do stuff, it also provides invokable and compilable interfaces.
Yout might be interested to read this : How can I start coding with Oracle's Nashorn JS Engine and when will it replace Rhino in the OpenJDK?
Example usage:
import javax.*; //lib imports
// we should use the javax.script API for Nahsorn
ScriptEngineManager m = new ScripteEngineManager();
ScriptEngine e = m.getEngineByname("nashorn");
try {
e.eval("print('hello nashorn !')");
} catch(Exception e) {
// using jdk lower then version 8 maybe ?
}
I found the way to init engine directly using .NET without "
"javax.script.ScriptEngineManager"
Environment:
IKVM.NET Version 8 + .NET Framework 4.52
static void Main(string[] args)
{
jdk.nashorn.api.scripting.NashornScriptEngineFactory fact = new jdk.nashorn.api.scripting.NashornScriptEngineFactory();
jdk.nashorn.api.scripting.NashornScriptEngine nashornengine = fact.getScriptEngine() as jdk.nashorn.api.scripting.NashornScriptEngine;
nashornengine.eval("var x = 1/3;");
object result = nashornengine.get("x");
Console.WriteLine("{0}", result);
}
This allows me directly interact with nashorn context methods mot more directly.
compile()
getFactory()
invokeMethod()
invokeFunction()
Given the following, running with Oracle JRE 6 gives the output boo, but OpenJDK 6 gives an exception
javax.script.ScriptException: sun.org.mozilla.javascript.EvaluatorException: The choice of Java
constructor replace matching JavaScript argument types (function,string) is ambiguous; candidate
constructors are:
class java.lang.String replace(char,char)
class java.lang.String replace(java.lang.CharSequence,java.lang.CharSequence) (<Unknown source>#1)
in <Unknown source> at line number 1
That's presumably because with OpenJDK (presumably the rt.jar supplied with it) the function's getting a java.lang.String, but with Oracle's it's getting a JavaScript String (or something that can be implicitly coerced to one).
So which is more correct? The Javascript (in this case) is the API, so can we write the Java such that the API's the same for either implementation? (If the OpenJDK implementation is "more correct" (and so likely to be what everyone does in the future), then I guess changing the API (documentation, examples, tests) throwing in new String(...) as appropriate wouldn't be impossible, but I'd rather not uglify the API unless I'm more confident.)
import javax.script.*;
class st {
public static void main(String[] args) {
ScriptEngineManager mgr = new ScriptEngineManager();
ScriptEngine jsEngine = mgr.getEngineByName("JavaScript");
Bindings bindings = jsEngine.getBindings(ScriptContext.ENGINE_SCOPE);
Foo foo = new Foo();
bindings.put("v", foo);
try {
jsEngine.eval("v.run(function(a) {println(a.replace(/f/,\"b\"));})");
} catch (ScriptException ex) {
ex.printStackTrace();
}
}
}
and
public class Foo {
public void run(FooCB cb) {
cb.run("foo");
}
public static interface FooCB {
public void run(Object val);
}
}
The Java SE 6 spec (JSR 270) merely says:
There will be no requirement that any
particular scripting language be
supported by the platform;
implementors may choose to include
support for the scripting language(s)
of their choice as they see fit.
To the best of my knowledge, there is no formal spec for how to integrate Java types into JavaScript. It's unfortunate, but there's no reason to expect 100% compatibility across implementations.
I believe both the Oracle JRE and OpenJDK ship with Rhino, but there's no guarantee about version level, patches, etc.