Migrating from QtWebKit to QtWebEngine, using QWebChannel.
I have an invokable function that sends a QVariant Object to the Javascript, which is seen as a JSON object. So a QString becomes a string, a QInt an int, etc.
Using QtWebKit without QWebChannel, a QByteArray was seen as a Uint8ClampedArray, but is now directly transformed to a string using UTF-8 (Which my QByteArray is not :( )
Did I do something wrong ? What should I do ?
Here is the relevant code part :
//Qt Window class signal to javascript
void MyWindow::uplink(Response msg)
{
emit _nativeToJs(msg->toJson());
}
//Response class toJson() method
QVariantMap Response::toJson() const
{
QVariantMap map;
map["id"] = m_id; //qulonglong
map["src"] = QString(m_src);
map["dst"] = QString(m_dst);
map["status"] = m_status; //qint16
map["result"] = m_result; //QVariant, can be a map of string, arrays, etc
return map;
}
//Javascript
var foo;
new QWebChannel(qt.webChannelTransport, function(channel) {
//we connect the signal
channel.objects.foo._nativeToJs.connect(function(msg){
//msg is now a JSON object
});
});
msg.result should contains a clamped array (msgpack data) that I later decode. Now I have an ugly string of not UTF-8 chars interpreted as UTF-8, which I can't do anything with.
Not an answer at all, but a beginning of research, as it is a very interesting question.
In Qt versions < Qt5.6, you can find how the conversion is done by looking into Qt sources. In particular, I found this function in the file C:\Qt\5.5\Src\qtwebkit\Source\WebCore\bridge\qt\qt_runtime.cpp:
JSValueRef convertQVariantToValue(JSContextRef context, PassRefPtr<RootObject> root, const QVariant& variant, JSValueRef *exception)
and this piece of code inside it :
if (type == QMetaType::QByteArray) {
QByteArray qtByteArray = variant.value<QByteArray>();
WTF::RefPtr<WTF::Uint8ClampedArray> wtfByteArray = WTF::Uint8ClampedArray::createUninitialized(qtByteArray.length());
memcpy(wtfByteArray->data(), qtByteArray.constData(), qtByteArray.length());
ExecState* exec = toJS(context);
APIEntryShim entryShim(exec);
return toRef(exec, toJS(exec, static_cast<JSDOMGlobalObject*>(exec->lexicalGlobalObject()), wtfByteArray.get()));
}
which seems to be the processing of a QByteArray on the JS side.
I also believe that by migrating from Qt WebKit to Qt WebEngine, Qt uses now V8, whereas before it was WebCore and JavaScript Core (source : this thread). So, things might have changed but I don't know to what extent.
At the moment, I can't search further in the Qt sources for Qt5.6, and thus I can't provide a real answer, but I hope this will motivate you or anyone else to look into it and to clarify this behavior :-).
Related
I have a pretty straightforward piece of Typescript code that parses a specific data format, the input is a UInt8Array. I've optimized it as far as I can, but I think this rather simple parser should be able to run faster than I can make it run as JS. I wanted to try out writing it in web assembly using AssemblyScript to make sure I'm not running into any quirks of the Javascript engines.
As I figured out now, I can't just pass a TypedArray to Wasm and have it work automatically. As far as I understand, I can pass a pointer to the array and should be able to access this directly from Wasm without copying the array. But I can't get this to work with AssemblyScript.
The following is a minimal example that shows how I'm failing to pass an ArrayBuffer to Wasm.
The code to set up the Wasm export is mostly from the automatically generated boilerplate:
const fs = require("fs");
const compiled = new WebAssembly.Module(
fs.readFileSync(__dirname + "/build/optimized.wasm")
);
const imports = {
env: {
abort(msgPtr, filePtr, line, column) {
throw new Error(`index.ts: abort at [${line}:${column}]`);
}
}
};
Object.defineProperty(module, "exports", {
get: () => new WebAssembly.Instance(compiled, imports).exports
});
The following code invokes the WASM, index.js is the glue code above.
const m = require("./index.js");
const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
const result = m.parse(data.buffer);
And the AssemblyScript that is compiled to WASM is the following:
import "allocator/arena";
export function parse(offset: usize): number {
return load<u8>(offset);
}
I get a "RuntimeError: memory access out of bounds" when I execute that code.
The major problem is that the errors I get back from Wasm are simply not helpful to figure this out on my own. I'm obviously missing some major aspects of how this actually works behind the scenes.
How do I actually pass a TypedArray or an ArrayBuffer from JS to Wasm using AssemblyScript?
In AssemblyScript, there are many ways to read data from the memory. The quickest and fastest way to get this data is to use a linked function in your module's function imports to return a pointer to the data itself.
let myData = new Float64Array(100); // have some data in AssemblyScript
// We should specify the location of our linked function
#external("env", "sendFloat64Array")
declare function sendFloat64Array(pointer: usize, length: i32): void;
/**
* The underlying array buffer has a special property called `data` which
* points to the start of the memory.
*/
sendFloat64Data(myData.buffer.data, myData.length);
Then in JavaScript, we can use the Float64Array constructor inside our linked function to return the values directly.
/**
* This is the fastest way to receive the data. Add a linked function like this.
*/
imports.env.sendFloat64Array = function sendFloat64Array(pointer, length) {
var data = new Float64Array(wasmmodule.memory.buffer, pointer, length);
};
However, there is a much clearer way to obtain the data, and it involves returning a reference from AssemblyScript, and then using the AssemblyScript loader.
let myData = new Float64Array(100); // have some data in AssemblyScript
export function getData(): Float64Array {
return myData;
}
Then in JavaScript, we can use the ASUtil loader provided by AssemblyScript.
import { instantiateStreaming } from "assemblyscript/lib/loader";
let wasm: ASUtil = await instantiateStreaming(fetch("myAssemblyScriptModule.wasm"), imports);
let dataReference: number = wasm.getData();
let data: Float64Array = wasm.getArray(Float64Array, dataReference);
I highly recommend using the second example for code clarity reasons, unless performance is absolutely critical.
Good luck with your AssemblyScript project!
Edit: Here is a minimal project that illustrates my issue. You can see the error described by serving it to the browser: pub get and then either pub serve (dartium) or pub build --mode=debug (other browsers).
How can I access an arbitrary JavaScript property from Dart through a JsObjectImpl? I am using the ace.js library with an interop to Dart that I've adapted from a typescript interface, and the method I am calling returns a plain javascript object with key-value pairs.
Dart gives me a JsObjectImpl, which cannot be casted to a Map or a JsObject, both of which have [] accessors. It confusingly seems to inherit from the deprecated JSObject (note the 's' is capitalized in the latter) which does not have the [] accessor, so I can't get the data out.
Some error messages:
When attempting a cast from JsObjectImpl to JsObject:
ORIGINAL EXCEPTION: type 'JSObjectImpl' is not a subtype of type 'JsObject' of 'obj' where
JSObjectImpl is from dart:js
JsObject is from dart:js. I get a similar message when using Map as well.
Looking at the object in the debugger, I can frustratingly see the property in JS view but not in the Dart object: The 4: Object is the data I want.
Ok, this was a fun one, happy holidays :)
It looks like Map is not a supported auto-conversion for package:js. So a couple of things:
Filed https://github.com/dart-lang/sdk/issues/28194
Sent your a PR introducing a workaround
For interested parties, we can use the browser-native Object.keys:
#JS()
library example;
import 'package:js/js.dart';
/// A workaround to converting an object from JS to a Dart Map.
Map jsToMap(jsObject) {
return new Map.fromIterable(
_getKeysOfObject(jsObject),
value: (key) => getProperty(jsObject, key),
);
}
// Both of these interfaces exist to call `Object.keys` from Dart.
//
// But you don't use them directly. Just see `jsToMap`.
#JS('Object.keys')
external List<String> _getKeysOfObject(jsObject);
And call it once we have an arbitrary JavaScript object:
var properties = jsToMap(toy.getData());
print(properties);
I had to modify #matanlurey solution so it works on dart 2.12 and is recursive.
import 'dart:js';
/// A workaround to deep-converting an object from JS to a Dart Object.
Object jsToDart(jsObject) {
if (jsObject is JsArray || jsObject is Iterable) {
return jsObject.map(jsToDart).toList();
}
if (jsObject is JsObject) {
return Map.fromIterable(
getObjectKeys(jsObject),
value: (key) => jsToDart(jsObject[key]),
);
}
return jsObject;
}
List<String> getObjectKeys(JsObject object) => context['Object']
.callMethod('getOwnPropertyNames', [object])
.toList()
.cast<String>();
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()
im thinking about replacing all my xml files and builders that i use for configuration with javascript / nashorn. lets say i have a java class that is builder style configuration object
class Configuration {
String name;
Configuration withName(String name) {
this.name = name;
return this;
}
int number;
Configuration withNumber(int number) {
this.number = number;
return this;
}
}
i would like to instantiate this class directly in javascript and have nashorn return to me an instance of it. i would like to code it in javascript like
{
name: 'qwerty',
number: 42
};
and then finally read the file, pass it into the script engine, and have it evaluate the object as an instance of Configuration.
is this possible with a json like syntax?
i wouldnt have a problem using the Packages.Configuration / Java.type("Configuration"); Java.extend(), but have yet to have any success with that.
or would i have to make / use a proper reader for the returned value?
No, Nashorn won't support this from what I've seen from available docs and JSR-223. You might need to use i.e. Jackson's ObjectMapper to deserialize JSON. And this should not be a problem, because Nashorn allows you to do almost anything in JavaScript: A JavaFX Script Application Examples.
You might also want to consider Groovy which has it's own map syntax which can be used inside constructors: Groovy Goodness: Using Lists and Maps As Constructors. Groovy is often used to define configuration.
You could cheat a little by attaching a converter function to your configuration type. This could be added easily on the JS side rather than trying to make it in Java.
var ConfigClass = Java.type('Configuration');
ConfigClass.fromJsObj = function(jsObj) {
var newConfig = new ConfigClass();
foreach(var prop in jsObj) {
newConfig[prop] = jsObj[prop];
}
return newConfig;
}
var myConfig = ConfigClass.fromJsObj( { name: "qwerty", number: 42 } );
Store and retrieve Google Dart objects in JavaScript library containers
In a Dart application I am using an external JavaScript library to do various matrix calculations.
The specific functionality of the library is not important, what it's important is that I need to store and retrieve Dart object that I put in the matrix.
Dart Class - Lets image i have a dart object that which has a parameter called name
MyDartClass mydc = new MyDartClass(something, something);
mydc.name;
// Everything works as planned
Storing
matrix = js.context.matrix
matrix.cell(1,1).store("thing", new MyDartClass(something, something));
Retrieving
matrix.cell(1,1).has_object_of_type("thing");
// true
MyDartClass mydc = matrix.cell(1,1).retrieve("thing");
Do something with the object
mydc.name;
// Exception: The null object does not have a getter 'name'.
// NoSuchMethodError : method not found: 'name'
// Receiver: null
// Arguments: []
Does the library really work?
Yes it does. I have done the exact same thing in pure javascript many times and there are plenty of test to test the behaviour ( in Javascript )
Is Dart Broken?
When I try to use a javascriptified Hash to do the same behavoiur it works like a charm.
var options = js.map({ 'dart' : new MyDartclass(something, something));
var y = options["dart"];
js.context.console.log(y.name);
// Name is printed
What do you get out from the retrieve?
It seems that I get some kind of Dart Proxy
MyDartClass mydc = matrix.cell(1,1). retrieve("thing");
js.context.console.log(mydc);
DartProxy {id: "dart-ref-20", port: DartSendPortSync}
id: "dart-ref-20"
port: DartSendPortSync
__proto__: DartProxy
I belive that the lib stores the objects, deep down, in a hash map. But it seems like when I retrieve the object into the Dart I get something, but not in a way that I can work with it. So i need help since I don't know how to make it work.
Do I need to de-proxify the object?
Perhaps it IS a Dart bug when you try to retrieve objects from hashes inside objects
Perhaps I missunderstod everything that this is not suppose to work.
Passing and retrieving Dart objects inside the same scope is working. There's the following test case in the tests of js-interop to proove it :
test('retrieve same dart Object', () {
final date = new DateTime.now();
js.context.dartDate = date;
expect(js.context.dartDate, equals(date));
});
However there seems to be an issue with multiple scopes (and multiple event loops as well). There is no way to retain a dart object for now. So your dart object reference goes away at the end of scope. Here's a simple test case that fails :
test('retrieve same dart Object', () {
final date = new DateTime.now();
js.scoped(() {
js.context.dartDate = date;
});
js.scoped(() {
expect(js.context.dartDate, equals(date));
});
});
Please file an issue.