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.
Related
I'm now struggling to integrate C++ library(zserge/webview) with V8. This library is designed to invoke C function whenever I run certain Javascript code on Webview.
As an example:
When you run this code below (inside a webview instance made by zserge/webview)
window.external.invoke('hello world');
then the C function is executed.
void my_cb(struct webview *w, const char *arg){
...
}
As I stated, I'm now integrating this with V8, so I want this function 'my_cb' to invoke Javascript function that I stored into a variable.
I made a function name as 'SetCallback' like below and exposed to node,
void SetCallback(const FunctionCallbackInfo<v8::Value> & args){
Environment *env = Environment::GetCurrent(args);
Isolate *isolate = env->isolate();
auto cb = v8::Local<v8::Function>::Cast(args[0]);
//g_callback is defined like this-> v8::Persistent<v8::Function> g_callback;
g_callback = v8::Persistent<v8::Function>::New(cb);
}
I thought that if I insert some code to my_cb I can invoke a function that I stored to g_callback.
So I coded like below:
void my_cb(struct webview *w, const char *arg){
Isolate *isolate = Isolate::GetCurrent();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Function> cb = v8::Local<v8::Function>::New(isolate, g_callback);
v8::Local<v8::Value> argv[] = {};
cb->Call(isolate->GetCurrentContext()->Global(), 0, argv);
}
But this code doesn't work. I assume that my_cb is unsafe to call v8 function because it's outside of v8 instance. However, I don't know how I can fix this.
Thank you in advance, please help me fix this.
How can I check or receive a message when the a Unity WebGL app is finished with loading and ready for use? What I want is to run a JavaScript function of my Webinterface after the WebGL App is ready.
If you're looking into using a front-end framework, you might want to look into this library I've made. It adds functionality for two way communication between your webpage and Unity content. To send messages from your Unity content, back to the web page, you can create JavaScript listeners which can be triggered from your CSharp code, and even pass data.
// Create a new Unity Content object (using the library)
this.unityContent = new UnityContent(
"MyGame/Build.json",
"MyGame/UnityLoader.js" );
// Then add a new listener to the object.
this.unityContent.on("GameOver", score => {
// Do somehting...
});
In order to trigger the event we've just created, you have to create a JSLib file to bind the communication. The listener registered in React is now available in the ReactUnityWebGL object in any JSLib file. You can now create a JSLib file and get started. We're going to create a new JSLib file in the following directory. Assets/Plugins/WebGL/MyPlugin.jslib.
mergeInto(LibraryManager.library, {
// Create a new function with the same name as
// the event listeners name and make sure the
// parameters match as well.
GameOver: function(score) {
// Within the function we're going to trigger
// the event within the ReactUnityWebGL object
// which is exposed by the library to the window.
ReactUnityWebGL.GameOver(score);
}
});
Finally, to trigger to event within your CSharp code. We have to import the JSLib as following.
using UnityEngine;
public class GameController : MonoBehaviour {
// Import the JSLib as following. Make sure the
// names match with the JSLib file we've just created.
[DllImport("__Internal")]
private static extern void GameOver (int score);
// Then create a function that is going to trigger
// the imported function from our JSLib.
public void GameOver (int score) {
GameOver (score);
}
}
I use a different approach. This is especially helpful because you can define callbacks in whatever part of your Unity code you wish i.e. you can react to async events.
//in your html|js
function OnAppReady(){
console.log("### application is loaded")
}
//in Unity
void Start()
{
//js callback when application is loaded
Application.ExternalEval("OnAppReady()");
}
I've found a solution that works for me. There is a JavaScript file UnityProgress.js in the Build folder of your WebGL build. Inside you can find a variable progresswhich will be set to 1.0 after the loading / downloading progress is finished. You can place your code after that if-statement at the bottom to run your JavaScript code (Don't forget the brackets^^).
But there is some initialization time that the Unity App needs to start. So you probably have to set a delay time. For me 2500ms worked well.
function UnityProgress(gameInstance, progress) {
...
if (progress == 1.0) {
gameInstance.logo.style.display = gameInstance.progress.style.display = "none";
// call of my function:
console.log("#### WebGL is ready now ####");
setTimeout(function() {
myFunction();
}, 2500);
}
}
I managed to get DukTape working in my GLUT project (it is able to run inline javascript with duk_eval_string();). Is it possible to display a static html canvas with javascript graphics in a C++ GLUT window using DukTape?
Using the HTML5 canvas drawing methods is not possible in Duktape.
Duktape is a Javascript engine, what means that it allows you to execute ES5/5.1 compliant code. Displaying a HTML canvas is a task Duktape can't do.
If you ultimately want to achieve this, try searching for a library to achieve such a task, maybe look at the Firefox source.
If you want to do it completely from scratch you would need to add C function bindings (Example at duktape.org/) for every draw method you want. An example would be like this:
// C/C++ code:
// C function to be used in the Javascript engine
int js_draw_rect(duk_context *ctx) {
// get parameters from javascript function call
int pos_x = duk_get_number(ctx, -4);
int pos_y = duk_get_number(ctx, -3);
...
// C/C++ code to draw the rectangle (in your case probably GLUT)
draw_rectangle(pos_x, pos_y, ...);
return 0;
}
int main(void) {
duk_context *ctx;
...
// this snippet adds a binding for the function 'js_draw_rect' so it can be called from Javascript code
duk_push_global_object(ctx);
duk_push_c_function(ctx, js_draw_rect, 4/*number of args the JS function has*/);
duk_put_prop_string(ctx, -2 /*idx:global*/, "drawRect"/*name of function in JS environment*/);
duk_pop(ctx);
}
// Javascript code:
drawRect(50, 50, 100, 200);
...
This method allows you to create C/C++ functions which handle all drawing and later bind them all to the Javascript engine so they can be called in JS.
I'm writing a serialization of objects to QML, and want to be able to get source code of functions defined in QML object. Suppose I have the following example in QML (test.qml):
import QtQml 2.2
QtObject {
function foo() {
return 42;
}
}
I created a QObject: obj from that.
Is there any way (can be hacky) to get the source code of obj's method foo without parsing the QML file obj was created from?
It's okay to use QQmlComponent obj was created from or any other Qt classes, as long as I don't have to parse it myself. Alternatively, how to get the function's source code from the test.qml file without writing my own parser? I don't want to assume anything special about test.qml (e.g. it can be different than the one above and it doesn't have to be simple enough to use a regexp or other not full-fledged QML parser).
Assuming this works like JavaScript, I tried something like:
QQmlExpression expr(engine.rootContext(), obj, "foo.toString()");
QVariant sourceCode = expr.evaluate();
However, it doesn't work.
Edit: According to http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.4.2 the toString method of a function object prototype is implementation-defined. In case of QML I get the result:
QVariant(QString, "function() { [code] }")
Since there doesn't seem to be a way to get the code by JS or C++, I'm not limiting myself to public Qt API anymore.
I think it is impossible to get a source code of a function from an already created QML object. There doesn't seem to be any C++ interface for it and JavaScript doesn't return it using toSource method either.
However, it can be retrieved using QML parser. The bad news are that QML parser is a part of Qt private API, so it might not work when using different Qt library builds.
The code to parse QML using Qt 5.3.0 private API is more or less:
.pro file:
QT += qml qml-private
cpp file:
using namespace QQmlJS::AST;
class LVisitor: public QQmlJS::AST::Visitor {
public:
LVisitor(QString code): _code(code) {}
virtual bool visit(FunctionBody *fb) {
qDebug() << "Visiting FunctionBody" <<
printable(fb->firstSourceLocation(), fb->lastSourceLocation());
return true;
}
private:
QStringRef printable(const SourceLocation &start, const SourceLocation &end) {
return QStringRef(&_code, start.offset, end.offset + end.length - start.offset);
}
private:
QString _code;
};
void testQmlParser() {
QFile file(":/test.qml");
file.open(QFile::ReadOnly);
QString code = file.readAll();
file.close();
QQmlJS::Engine engine;
QQmlJS::Lexer lexer(&engine);
lexer.setCode(code, 1, true);
QQmlJS::Parser parser(&engine);
if (!parser.parse() || !parser.diagnosticMessages().isEmpty()) {
foreach (const QQmlJS::DiagnosticMessage &m, parser.diagnosticMessages()) {
qDebug() << "Parse" << (m.isWarning() ? "warning" : "error") << m.message;
}
}
UiProgram *ast = parser.ast();
LVisitor visitor(code);
ast->accept(&visitor);
}
To get more precise information about the object where the function is defined or just get more information from the AST, implement more methods of QQmlJS::AST::Visitor.
I also didn't find any way to do so. but I found a way around to do it.
reffer this firts
Now, As you knwo we can access an QML object in C++. will do following to run the function in QML.
Item {
width: 100; height: 100
Rectangle {
property bool call:true
objectName: "rect"
onCallChanged()
{
myFunction();
}
function myFunction()
{
//your code
}
}
}
and in C++ you have to do following:
QObject *rect = object->findChild<QObject*>("rect");
if (rect)
rect->setProperty("call", !(rect->property("call").toBool()));
here we are using change event of property 'call' to call the myFunction()
I have an MFC application that uses CHtmlView. It displays some text in html format from some temp html file. Is it possible to handle mouse click on a paragraph to send some data to the program? I understand that javascript can be used to handle click, but how to pass the data from javascript function to the application??
Thanks.
It is possible to cleanly call the containing application from within the Javascript of the HTML page. At the Javascript level the MSHTML interface that is doing the actual work of the CHtmlView provides an "external" object that acts as a way back to the calling application.
Suppose we want to add a method "someCall()" that can be called from Javascript, and that the method takes a string as an argument. In JavaScript we would call it with something like
external.someCall("An example string");
In the MFC application, we need to write a CCmdTarget derived object to act as the implementation of the "external" object as a dispatch-based COM object, something like:
class TestExternal : public CCmdTarget
{
public:
TestExternal()
{
EnableAutomation();
}
void SomeCall(LPCWSTR str)
{
// This is where we get called when the Javascript runs...
}
private:
DECLARE_DISPATCH_MAP()
};
BEGIN_DISPATCH_MAP(TestExternal,CCmdTarget)
DISP_FUNCTION(TestExternal,"someCall",SomeCall,VT_EMPTY,VTS_WBSTR)
END_DISPATCH_MAP()
To tie this implementation of "external" with the HTML view, in a class derived from CHtmlView you need to over-ride OnGetExternal() and to point it to an instance of TestExternal that lives at least as long as the CHtmlView:
class TestHtmlView : public CHtmlView
{
// Usual implementation stuff goes here...
public:
HRESULT OnGetExternal(LPDISPATCH *lppDispatch)
{
*lppDispatch = m_external.GetIDispatch(TRUE);
return S_OK;
}
private:
TestExternal m_external;
};
Note that I haven't actually tested this, but it seems about right from memory ...