From 6fb2c4776b8a385800fecc0f25222c0cb582b264 Mon Sep 17 00:00:00 2001 From: ekibun Date: Mon, 25 Jan 2021 14:33:47 +0800 Subject: [PATCH] v0.3.1 --- CHANGELOG.md | 5 + README.md | 46 ++-- example/README.md | 13 - example/lib/main.dart | 27 -- example/pubspec.lock | 16 +- example/pubspec.yaml | 1 - lib/ffi.dart | 116 ++++---- lib/flutter_qjs.dart | 36 +-- lib/isolate.dart | 217 +-------------- lib/wrapper.dart | 532 +++++++++++++++++++++++++------------ pubspec.yaml | 2 +- test/flutter_qjs_test.dart | 26 +- 12 files changed, 492 insertions(+), 545 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b386bd..7941b38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ * @LastEditTime: 2020-12-02 11:36:40 --> +## 0.3.1 + +* code clean up. +* fix isolate wrap error. + ## 0.3.0 * breakdown change to remove `channel`. diff --git a/README.md b/README.md index 8b15947..1a1797e 100644 --- a/README.md +++ b/README.md @@ -45,20 +45,31 @@ engine = null; Data conversion between dart and js are implemented as follow: -| dart | js | -| --------- | ------------------ | -| Bool | boolean | -| Int | number | -| Double | number | -| String | string | -| Uint8List | ArrayBuffer | -| List | Array | -| Map | Object | -| Function | function(....args) | -| Future | Promise | -| Object | DartObject | +| dart | js | +| ----------------------- | ------------------ | +| Bool | boolean | +| Int | number | +| Double | number | +| String | string | +| Uint8List | ArrayBuffer | +| List | Array | +| Map | Object | +| Function
JSInvokable | function(....args) | +| Future | Promise | +| Object | DartObject | -**notice:** Dart function parameter `thisVal` is used to store `this` in js. +**notice:** `JSInvokable` does not extend `Function`, but can be used same as `Function`. +Dart function uses named argument `thisVal` to manage js function `this`: + +```dart +func(arg1, arg2, {thisVal}); +``` + +or use `invoke` method to pass list parameters: + +```dart +(func as JSInvokable).invoke([arg1, arg2], thisVal); +``` ### Use modules @@ -119,10 +130,11 @@ try { Method `close` can destroy quickjs runtime that can be recreated again if you call `evaluate`. -**notice:** Make sure arguments passed to `IsolateJSFunction` are avaliable for isolate, such as primities and top level function. Method `bind` can help to pass instance function to isolate: +**notice:** Make sure arguments passed to `IsolateJSFunction` are avaliable for isolate, such as primities and top level function. +Method `bind` can help to pass instance function to isolate: ```dart -await setToGlobalObject("func", await engine.bind(() { +await setToGlobalObject("func", await engine.bind(({thisVal}) { // DO SOMETHING })) ``` @@ -131,8 +143,8 @@ await setToGlobalObject("func", await engine.bind(() { ## Breaking change in v0.3.0 -`channel` function is no longer utilized by default. -Use js function to set to global: +`channel` function is no longer included by default. +Use js function to set dart object globally: ```dart final setToGlobalObject = await engine.evaluate("(key, val) => this[key] = val;"); diff --git a/example/README.md b/example/README.md index 2649690..595d78a 100644 --- a/example/README.md +++ b/example/README.md @@ -1,16 +1,3 @@ # flutter_qjs_example Demonstrates how to use the flutter_qjs plugin. - -## Getting Started - -This project is a starting point for a Flutter application. - -A few resources to get you started if this is your first Flutter project: - -- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) - -For help getting started with Flutter, view our -[online documentation](https://flutter.dev/docs), which offers tutorials, -samples, guidance on mobile development, and a full API reference. diff --git a/example/lib/main.dart b/example/lib/main.dart index 711f95e..e270b7a 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -6,9 +6,7 @@ * @LastEditTime: 2020-12-02 11:28:06 */ import 'package:flutter/material.dart'; -import 'dart:typed_data'; -import 'package:dio/dio.dart'; import 'package:flutter/services.dart'; import 'package:flutter_qjs/isolate.dart'; @@ -44,28 +42,6 @@ class TestPage extends StatefulWidget { State createState() => _TestPageState(); } -dynamic methodHandler(String method, List arg) { - switch (method) { - case "http": - return Dio().get(arg[0]).then((response) => response.data); - case "test": - return arg[0]([ - true, - 1, - 0.5, - "str", - {"key": "val", 0: 1}, - Uint8List(2), - Int32List(2), - Int64List(2), - Float64List(2), - Float32List(2) - ]); - default: - throw Exception("No such method"); - } -} - class _TestPageState extends State { String resp; IsolateQjs engine; @@ -82,9 +58,6 @@ class _TestPageState extends State { "js/" + module.replaceFirst(new RegExp(r".js$"), "") + ".js"); }, ); - final setToGlobalObject = - await engine.evaluate("(key, val) => this[key] = val;"); - setToGlobalObject("channel", methodHandler); } @override diff --git a/example/pubspec.lock b/example/pubspec.lock index 7ae5e34..2e79c76 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -43,13 +43,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.15.0-nullsafety.5" - dio: - dependency: "direct main" - description: - name: dio - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.0.10" fake_async: dependency: transitive description: @@ -82,7 +75,7 @@ packages: path: ".." relative: true source: path - version: "0.3.0" + version: "0.3.1" flutter_test: dependency: "direct dev" description: flutter @@ -95,13 +88,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "0.6.0" - http_parser: - dependency: transitive - description: - name: http_parser - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.1.4" matcher: dependency: transitive description: diff --git a/example/pubspec.yaml b/example/pubspec.yaml index bb430a3..2296396 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -22,7 +22,6 @@ dependencies: highlight: 0.6.0 flutter_highlight: 0.6.0 - dio: 3.0.10 dev_dependencies: flutter_test: diff --git a/lib/ffi.dart b/lib/ffi.dart index 855afd9..3a4d9bd 100644 --- a/lib/ffi.dart +++ b/lib/ffi.dart @@ -15,7 +15,6 @@ abstract class JSRef { void release(); } -/// JS_Eval() flags class JSEvalFlag { static const GLOBAL = 0 << 0; static const MODULE = 1 << 0; @@ -56,7 +55,7 @@ class JSTag { static const FLOAT64 = 7; } -final DynamicLibrary qjsLib = Platform.environment['FLUTTER_TEST'] == 'true' +final DynamicLibrary _qjsLib = Platform.environment['FLUTTER_TEST'] == 'true' ? (Platform.isWindows ? DynamicLibrary.open("test/build/Debug/ffiquickjs.dll") : Platform.isMacOS @@ -72,7 +71,7 @@ final DynamicLibrary qjsLib = Platform.environment['FLUTTER_TEST'] == 'true' final Pointer Function( Pointer ctx, Pointer message, -) _jsThrowInternalError = qjsLib +) _jsThrowInternalError = _qjsLib .lookup< NativeFunction< Pointer Function( @@ -89,12 +88,12 @@ Pointer jsThrowInternalError(Pointer ctx, String message) { } /// JSValue *jsEXCEPTION() -final Pointer Function() jsEXCEPTION = qjsLib +final Pointer Function() jsEXCEPTION = _qjsLib .lookup>("jsEXCEPTION") .asFunction(); /// JSValue *jsUNDEFINED() -final Pointer Function() jsUNDEFINED = qjsLib +final Pointer Function() jsUNDEFINED = _qjsLib .lookup>("jsUNDEFINED") .asFunction(); @@ -105,7 +104,7 @@ typedef JSChannelNative = Pointer Function( /// JSRuntime *jsNewRuntime(JSChannel channel) final Pointer Function( Pointer>, -) _jsNewRuntime = qjsLib +) _jsNewRuntime = _qjsLib .lookup< NativeFunction< Pointer Function( @@ -117,7 +116,6 @@ class RuntimeOpaque { JSChannel channel; List ref = []; ReceivePort port; - Future Function(Pointer) promiseToFuture; int dartObjectClassId; } @@ -143,7 +141,7 @@ Pointer jsNewRuntime( final void Function( Pointer, int, -) jsSetMaxStackSize = qjsLib +) jsSetMaxStackSize = _qjsLib .lookup< NativeFunction< Void Function( @@ -155,7 +153,7 @@ final void Function( /// void jsFreeRuntime(JSRuntime *rt) final void Function( Pointer, -) _jsFreeRuntime = qjsLib +) _jsFreeRuntime = _qjsLib .lookup< NativeFunction< Void Function( @@ -177,7 +175,7 @@ void jsFreeRuntime( final Pointer Function( Pointer ctx, Pointer funcData, -) jsNewCFunction = qjsLib +) jsNewCFunction = _qjsLib .lookup< NativeFunction< Pointer Function( @@ -189,7 +187,7 @@ final Pointer Function( /// JSContext *jsNewContext(JSRuntime *rt) final Pointer Function( Pointer rt, -) jsNewContext = qjsLib +) _jsNewContext = _qjsLib .lookup< NativeFunction< Pointer Function( @@ -197,10 +195,18 @@ final Pointer Function( )>>("jsNewContext") .asFunction(); +Pointer jsNewContext(Pointer rt) { + var ctx = _jsNewContext(rt); + final runtimeOpaque = runtimeOpaques[rt]; + if (runtimeOpaque == null) throw Exception("Runtime has been released!"); + runtimeOpaque.dartObjectClassId = jsNewClass(ctx, "DartObject"); + return ctx; +} + /// void jsFreeContext(JSContext *ctx) final void Function( Pointer, -) jsFreeContext = qjsLib +) jsFreeContext = _qjsLib .lookup< NativeFunction< Void Function( @@ -211,7 +217,7 @@ final void Function( /// JSRuntime *jsGetRuntime(JSContext *ctx) final Pointer Function( Pointer, -) jsGetRuntime = qjsLib +) jsGetRuntime = _qjsLib .lookup< NativeFunction< Pointer Function( @@ -226,7 +232,7 @@ final Pointer Function( int inputLen, Pointer filename, int evalFlags, -) _jsEval = qjsLib +) _jsEval = _qjsLib .lookup< NativeFunction< Pointer Function( @@ -262,7 +268,7 @@ Pointer jsEval( /// DLLEXPORT int32_t jsValueGetTag(JSValue *val) final int Function( Pointer val, -) jsValueGetTag = qjsLib +) jsValueGetTag = _qjsLib .lookup< NativeFunction< Int32 Function( @@ -273,7 +279,7 @@ final int Function( /// void *jsValueGetPtr(JSValue *val) final Pointer Function( Pointer val, -) jsValueGetPtr = qjsLib +) jsValueGetPtr = _qjsLib .lookup< NativeFunction< Pointer Function( @@ -284,7 +290,7 @@ final Pointer Function( /// DLLEXPORT bool jsTagIsFloat64(int32_t tag) final int Function( int val, -) jsTagIsFloat64 = qjsLib +) jsTagIsFloat64 = _qjsLib .lookup< NativeFunction< Int32 Function( @@ -296,7 +302,7 @@ final int Function( final Pointer Function( Pointer ctx, int val, -) jsNewBool = qjsLib +) jsNewBool = _qjsLib .lookup< NativeFunction< Pointer Function( @@ -309,7 +315,7 @@ final Pointer Function( final Pointer Function( Pointer ctx, int val, -) jsNewInt64 = qjsLib +) jsNewInt64 = _qjsLib .lookup< NativeFunction< Pointer Function( @@ -322,7 +328,7 @@ final Pointer Function( final Pointer Function( Pointer ctx, double val, -) jsNewFloat64 = qjsLib +) jsNewFloat64 = _qjsLib .lookup< NativeFunction< Pointer Function( @@ -335,7 +341,7 @@ final Pointer Function( final Pointer Function( Pointer ctx, Pointer str, -) _jsNewString = qjsLib +) _jsNewString = _qjsLib .lookup< NativeFunction< Pointer Function( @@ -357,7 +363,7 @@ final Pointer Function( Pointer ctx, Pointer buf, int len, -) jsNewArrayBufferCopy = qjsLib +) jsNewArrayBufferCopy = _qjsLib .lookup< NativeFunction< Pointer Function( @@ -370,7 +376,7 @@ final Pointer Function( /// JSValue *jsNewArray(JSContext *ctx) final Pointer Function( Pointer ctx, -) jsNewArray = qjsLib +) jsNewArray = _qjsLib .lookup< NativeFunction< Pointer Function( @@ -381,7 +387,7 @@ final Pointer Function( /// JSValue *jsNewObject(JSContext *ctx) final Pointer Function( Pointer ctx, -) jsNewObject = qjsLib +) jsNewObject = _qjsLib .lookup< NativeFunction< Pointer Function( @@ -394,7 +400,7 @@ final void Function( Pointer ctx, Pointer val, int free, -) _jsFreeValue = qjsLib +) _jsFreeValue = _qjsLib .lookup< NativeFunction< Void Function( @@ -417,7 +423,7 @@ final void Function( Pointer rt, Pointer val, int free, -) _jsFreeValueRT = qjsLib +) _jsFreeValueRT = _qjsLib .lookup< NativeFunction< Void Function( @@ -439,7 +445,7 @@ void jsFreeValueRT( final Pointer Function( Pointer ctx, Pointer val, -) jsDupValue = qjsLib +) jsDupValue = _qjsLib .lookup< NativeFunction< Pointer Function( @@ -452,7 +458,7 @@ final Pointer Function( final Pointer Function( Pointer rt, Pointer val, -) jsDupValueRT = qjsLib +) jsDupValueRT = _qjsLib .lookup< NativeFunction< Pointer Function( @@ -465,7 +471,7 @@ final Pointer Function( final int Function( Pointer ctx, Pointer val, -) jsToBool = qjsLib +) jsToBool = _qjsLib .lookup< NativeFunction< Int32 Function( @@ -478,7 +484,7 @@ final int Function( final int Function( Pointer ctx, Pointer val, -) jsToInt64 = qjsLib +) jsToInt64 = _qjsLib .lookup< NativeFunction< Int64 Function( @@ -491,7 +497,7 @@ final int Function( final double Function( Pointer ctx, Pointer val, -) jsToFloat64 = qjsLib +) jsToFloat64 = _qjsLib .lookup< NativeFunction< Double Function( @@ -504,7 +510,7 @@ final double Function( final Pointer Function( Pointer ctx, Pointer val, -) _jsToCString = qjsLib +) _jsToCString = _qjsLib .lookup< NativeFunction< Pointer Function( @@ -517,7 +523,7 @@ final Pointer Function( final void Function( Pointer ctx, Pointer val, -) jsFreeCString = qjsLib +) jsFreeCString = _qjsLib .lookup< NativeFunction< Void Function( @@ -541,7 +547,7 @@ String jsToCString( final int Function( Pointer ctx, Pointer name, -) _jsNewClass = qjsLib +) _jsNewClass = _qjsLib .lookup< NativeFunction< Uint32 Function( @@ -568,7 +574,7 @@ final Pointer Function( Pointer ctx, int classId, int opaque, -) jsNewObjectClass = qjsLib +) jsNewObjectClass = _qjsLib .lookup< NativeFunction< Pointer Function( @@ -582,7 +588,7 @@ final Pointer Function( final int Function( Pointer obj, int classid, -) jsGetObjectOpaque = qjsLib +) jsGetObjectOpaque = _qjsLib .lookup< NativeFunction< IntPtr Function( @@ -596,7 +602,7 @@ final Pointer Function( Pointer ctx, Pointer psize, Pointer val, -) jsGetArrayBuffer = qjsLib +) jsGetArrayBuffer = _qjsLib .lookup< NativeFunction< Pointer Function( @@ -610,7 +616,7 @@ final Pointer Function( final int Function( Pointer ctx, Pointer val, -) jsIsFunction = qjsLib +) jsIsFunction = _qjsLib .lookup< NativeFunction< Int32 Function( @@ -623,7 +629,7 @@ final int Function( final int Function( Pointer ctx, Pointer val, -) jsIsPromise = qjsLib +) jsIsPromise = _qjsLib .lookup< NativeFunction< Int32 Function( @@ -636,7 +642,7 @@ final int Function( final int Function( Pointer ctx, Pointer val, -) jsIsArray = qjsLib +) jsIsArray = _qjsLib .lookup< NativeFunction< Int32 Function( @@ -651,7 +657,7 @@ final Pointer Function( Pointer ctx, Pointer thisObj, int prop, -) jsGetProperty = qjsLib +) jsGetProperty = _qjsLib .lookup< NativeFunction< Pointer Function( @@ -669,7 +675,7 @@ final int Function( int prop, Pointer val, int flag, -) jsDefinePropertyValue = qjsLib +) jsDefinePropertyValue = _qjsLib .lookup< NativeFunction< Int32 Function( @@ -685,7 +691,7 @@ final int Function( final Pointer Function( Pointer ctx, int v, -) jsFreeAtom = qjsLib +) jsFreeAtom = _qjsLib .lookup< NativeFunction< Pointer Function( @@ -698,7 +704,7 @@ final Pointer Function( final int Function( Pointer ctx, Pointer val, -) jsValueToAtom = qjsLib +) jsValueToAtom = _qjsLib .lookup< NativeFunction< Uint32 Function( @@ -711,7 +717,7 @@ final int Function( final Pointer Function( Pointer ctx, int val, -) jsAtomToValue = qjsLib +) jsAtomToValue = _qjsLib .lookup< NativeFunction< Pointer Function( @@ -728,7 +734,7 @@ final int Function( Pointer plen, Pointer obj, int flags, -) jsGetOwnPropertyNames = qjsLib +) jsGetOwnPropertyNames = _qjsLib .lookup< NativeFunction< Int32 Function( @@ -744,7 +750,7 @@ final int Function( final int Function( Pointer ptab, int i, -) jsPropertyEnumGetAtom = qjsLib +) jsPropertyEnumGetAtom = _qjsLib .lookup< NativeFunction< Uint32 Function( @@ -754,7 +760,7 @@ final int Function( .asFunction(); /// uint32_t sizeOfJSValue() -final int Function() _sizeOfJSValue = qjsLib +final int Function() _sizeOfJSValue = _qjsLib .lookup>("sizeOfJSValue") .asFunction(); @@ -765,7 +771,7 @@ final void Function( Pointer list, int i, Pointer val, -) setJSValueList = qjsLib +) setJSValueList = _qjsLib .lookup< NativeFunction< Void Function( @@ -783,7 +789,7 @@ final Pointer Function( Pointer thisObj, int argc, Pointer argv, -) _jsCall = qjsLib +) _jsCall = _qjsLib .lookup< NativeFunction< Pointer Function( @@ -823,7 +829,7 @@ Pointer jsCall( /// int jsIsException(JSValueConst *val) final int Function( Pointer val, -) jsIsException = qjsLib +) jsIsException = _qjsLib .lookup< NativeFunction< Int32 Function( @@ -834,7 +840,7 @@ final int Function( /// JSValue *jsGetException(JSContext *ctx) final Pointer Function( Pointer ctx, -) jsGetException = qjsLib +) jsGetException = _qjsLib .lookup< NativeFunction< Pointer Function( @@ -845,7 +851,7 @@ final Pointer Function( /// int jsExecutePendingJob(JSRuntime *rt) final int Function( Pointer ctx, -) jsExecutePendingJob = qjsLib +) jsExecutePendingJob = _qjsLib .lookup< NativeFunction< Int32 Function( @@ -857,7 +863,7 @@ final int Function( final Pointer Function( Pointer ctx, Pointer resolvingFuncs, -) jsNewPromiseCapability = qjsLib +) jsNewPromiseCapability = _qjsLib .lookup< NativeFunction< Pointer Function( @@ -870,7 +876,7 @@ final Pointer Function( final void Function( Pointer ctx, Pointer ptab, -) jsFree = qjsLib +) jsFree = _qjsLib .lookup< NativeFunction< Void Function( diff --git a/lib/flutter_qjs.dart b/lib/flutter_qjs.dart index fee656a..e02deb8 100644 --- a/lib/flutter_qjs.dart +++ b/lib/flutter_qjs.dart @@ -10,10 +10,8 @@ import 'dart:ffi'; import 'dart:isolate'; import 'package:ffi/ffi.dart'; -import 'package:flutter_qjs/ffi.dart'; -import 'package:flutter_qjs/wrapper.dart'; - -import 'isolate.dart'; +import 'ffi.dart'; +import 'wrapper.dart'; /// Handler function to manage js module. typedef JsModuleHandler = String Function(String name); @@ -21,6 +19,7 @@ typedef JsModuleHandler = String Function(String name); /// Handler to manage unhandled promise rejection. typedef JsHostPromiseRejectionHandler = void Function(String reason); +/// Quickjs engine for flutter. class FlutterQjs { Pointer _rt; Pointer _ctx; @@ -37,21 +36,12 @@ class FlutterQjs { /// Handler function to manage js module. JsHostPromiseRejectionHandler hostPromiseRejectionHandler; - /// Quickjs engine for flutter. - /// - /// Pass handlers to implement js-dart interaction and resolving modules. FlutterQjs({ this.moduleHandler, this.stackSize, this.hostPromiseRejectionHandler, }); - static applyFunction(Function func, List args, dynamic thisVal) { - final passThis = - RegExp("{.*thisVal.*}").hasMatch(func.runtimeType.toString()); - return Function.apply(func, args, passThis ? {#thisVal: thisVal} : null); - } - _ensureEngine() { if (_rt != null) return; _rt = jsNewRuntime((ctx, type, ptr) { @@ -69,10 +59,8 @@ class FlutterQjs { ))); } final thisVal = jsToDart(ctx, pdata.elementAt(0).value); - final func = jsToDart(ctx, pdata.elementAt(3).value); - final ret = func is QjsInvokable - ? func.invoke(args, thisVal) - : applyFunction(func, args, thisVal); + JSInvokable func = jsToDart(ctx, pdata.elementAt(3).value); + final ret = func.invoke(args, thisVal); return dartToJs(ctx, ret); case JSChannelType.MODULE: if (moduleHandler == null) throw Exception("No ModuleHandler"); @@ -122,7 +110,7 @@ class FlutterQjs { }, port); if (this.stackSize != null && this.stackSize > 0) jsSetMaxStackSize(_rt, this.stackSize); - _ctx = jsNewContextWithPromsieWrapper(_rt); + _ctx = jsNewContext(_rt); } /// Free Runtime and Context which can be recreate when evaluate again. @@ -146,18 +134,6 @@ class FlutterQjs { break; } } - List jsPromises = runtimeOpaques[_rt] - .ref - .where( - (v) => v is JSPromise, - ) - .toList(); - for (JSPromise jsPromise in jsPromises) { - if (jsPromise.checkResolveReject()) { - jsPromise.release(); - runtimeOpaques[_rt].ref.remove(jsPromise); - } - } } } diff --git a/lib/isolate.dart b/lib/isolate.dart index 47eea09..0180216 100644 --- a/lib/isolate.dart +++ b/lib/isolate.dart @@ -11,205 +11,8 @@ import 'dart:io'; import 'dart:isolate'; import 'package:ffi/ffi.dart'; -import 'package:flutter_qjs/flutter_qjs.dart'; -import 'package:flutter_qjs/wrapper.dart'; - -class IsolateJSFunction implements QjsInvokable { - int val; - int ctx; - SendPort port; - IsolateJSFunction(this.ctx, this.val, this.port); - - Future invoke(List arguments, [thisVal]) async { - if (0 == val ?? 0) return; - var evaluatePort = ReceivePort(); - port.send({ - 'type': 'call', - 'ctx': ctx, - 'val': val, - 'args': _encodeData(arguments), - 'this': _encodeData(thisVal), - 'port': evaluatePort.sendPort, - }); - var result = await evaluatePort.first; - evaluatePort.close(); - if (result['data'] != null) - return _decodeData(result['data'], port); - else - throw result['error']; - } - - @override - noSuchMethod(Invocation invocation) { - return invoke( - invocation.positionalArguments, - invocation.namedArguments[#thisVal], - ); - } -} - -class IsolateFunction implements QjsInvokable { - SendPort _port; - SendPort func; - IsolateFunction(this.func, this._port); - - static IsolateFunction bind(Function func, SendPort port) { - final funcPort = ReceivePort(); - funcPort.listen((msg) async { - var data; - SendPort msgPort = msg['port']; - try { - List args = _decodeData(msg['args'], port); - Map thisVal = _decodeData(msg['this'], port); - data = await FlutterQjs.applyFunction(func, args, thisVal); - if (msgPort != null) - msgPort.send({ - 'data': _encodeData(data), - }); - } catch (e, stack) { - if (msgPort != null) - msgPort.send({ - 'error': e.toString() + "\n" + stack.toString(), - }); - } - }); - return IsolateFunction(funcPort.sendPort, port); - } - - Future invoke(List positionalArguments, [thisVal]) async { - if (func == null) return; - var evaluatePort = ReceivePort(); - func.send({ - 'args': _encodeData(positionalArguments), - 'this': _encodeData(thisVal), - 'port': evaluatePort.sendPort, - }); - var result = await evaluatePort.first; - evaluatePort.close(); - if (result['data'] != null) - return _decodeData(result['data'], _port); - else - throw result['error']; - } - - @override - noSuchMethod(Invocation invocation) { - return invoke( - invocation.positionalArguments, - invocation.namedArguments[#thisVal], - ); - } -} - -dynamic _encodeData(dynamic data, {Map cache}) { - if (cache == null) cache = Map(); - if (cache.containsKey(data)) return cache[data]; - if (data is List) { - var ret = []; - cache[data] = ret; - for (int i = 0; i < data.length; ++i) { - ret.add(_encodeData(data[i], cache: cache)); - } - return ret; - } - if (data is Map) { - var ret = {}; - cache[data] = ret; - for (var entry in data.entries) { - ret[_encodeData(entry.key, cache: cache)] = - _encodeData(entry.value, cache: cache); - } - return ret; - } - if (data is JSFunction) { - return { - '__js_function_ctx': data.ctx.address, - '__js_function_val': data.val.address, - }; - } - if (data is IsolateJSFunction) { - return { - '__js_function_ctx': data.ctx, - '__js_function_val': data.val, - }; - } - if (data is IsolateFunction) { - return { - '__js_function_port': data.func, - }; - } - if (data is Future) { - var futurePort = ReceivePort(); - data.then((value) { - futurePort.first.then((port) { - futurePort.close(); - (port as SendPort).send({'data': _encodeData(value)}); - }); - }, onError: (e, stack) { - futurePort.first.then((port) { - futurePort.close(); - (port as SendPort) - .send({'error': e.toString() + "\n" + stack.toString()}); - }); - }); - return { - '__js_future_port': futurePort.sendPort, - }; - } - return data; -} - -dynamic _decodeData(dynamic data, SendPort port, - {Map cache}) { - if (cache == null) cache = Map(); - if (cache.containsKey(data)) return cache[data]; - if (data is List) { - var ret = []; - cache[data] = ret; - for (int i = 0; i < data.length; ++i) { - ret.add(_decodeData(data[i], port, cache: cache)); - } - return ret; - } - if (data is Map) { - if (data.containsKey('__js_function_val')) { - int ctx = data['__js_function_ctx']; - int val = data['__js_function_val']; - if (port != null) { - return IsolateJSFunction(ctx, val, port); - } else { - return JSFunction.fromAddress(ctx, val); - } - } - if (data.containsKey('__js_function_port')) { - return IsolateFunction(data['__js_function_port'], port); - } - if (data.containsKey('__js_future_port')) { - SendPort port = data['__js_future_port']; - var futurePort = ReceivePort(); - port.send(futurePort.sendPort); - var futureCompleter = Completer(); - futureCompleter.future.catchError((e) {}); - futurePort.first.then((value) { - futurePort.close(); - if (value['error'] != null) { - futureCompleter.completeError(value['error']); - } else { - futureCompleter.complete(value['data']); - } - }); - return futureCompleter.future; - } - var ret = {}; - cache[data] = ret; - for (var entry in data.entries) { - ret[_decodeData(entry.key, port, cache: cache)] = - _decodeData(entry.value, port, cache: cache); - } - return ret; - } - return data; -} +import 'flutter_qjs.dart'; +import 'wrapper.dart'; void _runJsIsolate(Map spawnMessage) async { SendPort sendPort = spawnMessage['port']; @@ -256,11 +59,11 @@ void _runJsIsolate(Map spawnMessage) async { break; case 'call': data = JSFunction.fromAddress( - msg['ctx'], - msg['val'], + Pointer.fromAddress(msg['ctx']), + Pointer.fromAddress(msg['val']), ).invoke( - _decodeData(msg['args'], null), - _decodeData(msg['this'], null), + decodeData(msg['args'], null), + decodeData(msg['this'], null), ); break; case 'close': @@ -271,7 +74,7 @@ void _runJsIsolate(Map spawnMessage) async { } if (msgPort != null) msgPort.send({ - 'data': _encodeData(data), + 'data': encodeData(data), }); } catch (e, stack) { if (msgPort != null) @@ -389,10 +192,10 @@ class IsolateQjs { 'flag': evalFlags, 'port': evaluatePort.sendPort, }); - var result = await evaluatePort.first; + Map result = await evaluatePort.first; evaluatePort.close(); - if (result['error'] == null) { - return _decodeData(result['data'], sendPort); + if (result.containsKey('data')) { + return decodeData(result['data'], sendPort); } else throw result['error']; } diff --git a/lib/wrapper.dart b/lib/wrapper.dart index 600d327..51563cb 100644 --- a/lib/wrapper.dart +++ b/lib/wrapper.dart @@ -7,123 +7,20 @@ */ import 'dart:async'; import 'dart:ffi'; +import 'dart:isolate'; import 'dart:typed_data'; - import 'package:ffi/ffi.dart'; - import 'ffi.dart'; -import 'isolate.dart'; -class JSRefValue implements JSRef { - Pointer val; - Pointer ctx; - JSRefValue(this.ctx, Pointer val) { - Pointer rt = jsGetRuntime(ctx); - this.val = jsDupValue(ctx, val); - runtimeOpaques[rt]?.ref?.add(this); - } +abstract class JSInvokable { + dynamic invoke(List args, [dynamic thisVal]); - JSRefValue.fromAddress(int ctx, int val) { - this.ctx = Pointer.fromAddress(ctx); - this.val = Pointer.fromAddress(val); - } - - @override - void release() { - if (val != null) { - jsFreeValue(ctx, val); - } - val = null; - ctx = null; - } -} - -abstract class QjsReleasable { - void release(); -} - -abstract class QjsInvokable { - dynamic invoke(List positionalArguments, [dynamic thisVal]); -} - -class DartObject implements JSRef { - Object obj; - Pointer ctx; - DartObject(this.ctx, this.obj) { - runtimeOpaques[jsGetRuntime(ctx)]?.ref?.add(this); - } - - static DartObject fromAddress(Pointer rt, int val) { - return runtimeOpaques[rt]?.ref?.firstWhere( - (e) => identityHashCode(e) == val, - orElse: () => null, - ); - } - - @override - void release() { - if (obj is QjsReleasable) (obj as QjsReleasable).release(); - obj = null; - ctx = null; - } -} - -class JSPromise extends JSRefValue { - Completer completer; - JSPromise(Pointer ctx, Pointer val, this.completer) : super(ctx, val); - - @override - void release() { - super.release(); - if (!completer.isCompleted) { - completer.completeError("Promise cannot resolve"); - } - } - - bool checkResolveReject() { - if (val == null || completer.isCompleted) return true; - var status = jsToDart(ctx, val); - if (status["__resolved"] == true) { - completer.complete(status["__value"]); - return true; - } - if (status["__rejected"] == true) { - final err = jsGetPropertyStr(ctx, val, "__error"); - completer.completeError(parseJSException( - ctx, - perr: err, - )); - jsFreeValue(ctx, err); - return true; - } - return false; - } -} - -class JSFunction extends JSRefValue implements QjsInvokable { - JSFunction(Pointer ctx, Pointer val) : super(ctx, val); - - JSFunction.fromAddress(int ctx, int val) : super.fromAddress(ctx, val); - - invoke(List arguments, [dynamic thisVal]) { - if (val == null) return; - List args = arguments - .map( - (e) => dartToJs(ctx, e), - ) - .toList(); - Pointer jsRet = jsCall(ctx, val, dartToJs(ctx, thisVal), args); - for (Pointer jsArg in args) { - jsFreeValue(ctx, jsArg); - } - bool isException = jsIsException(jsRet) != 0; - if (isException) { - jsFreeValue(ctx, jsRet); - throw Exception(parseJSException(ctx)); - } - var ret = jsToDart(ctx, jsRet); - jsFreeValue(ctx, jsRet); - return ret; + static dynamic wrap(dynamic func) { + return func is JSInvokable + ? func + : func is Function + ? _DartFunction(func) + : func; } @override @@ -135,13 +32,316 @@ class JSFunction extends JSRefValue implements QjsInvokable { } } -Pointer jsGetPropertyStr(Pointer ctx, Pointer val, String prop) { - var jsAtomVal = jsNewString(ctx, prop); - var jsAtom = jsValueToAtom(ctx, jsAtomVal); - Pointer jsProp = jsGetProperty(ctx, val, jsAtom); - jsFreeAtom(ctx, jsAtom); - jsFreeValue(ctx, jsAtomVal); - return jsProp; +class _DartFunction extends JSInvokable { + Function _func; + _DartFunction(this._func); + + @override + invoke(List args, [thisVal]) { + /// wrap this into function + final passThis = + RegExp("{.*thisVal.*}").hasMatch(_func.runtimeType.toString()); + return Function.apply(_func, args, passThis ? {#thisVal: thisVal} : null); + } +} + +abstract class DartReleasable { + void release(); +} + +class DartObject implements JSRef { + Object _obj; + Pointer _ctx; + DartObject(this._ctx, this._obj) { + runtimeOpaques[jsGetRuntime(_ctx)]?.ref?.add(this); + } + + static DartObject fromAddress(Pointer rt, int val) { + return runtimeOpaques[rt]?.ref?.firstWhere( + (e) => identityHashCode(e) == val, + orElse: () => null, + ); + } + + @override + void release() { + if (_obj is DartReleasable) { + (_obj as DartReleasable).release(); + } + _obj = null; + _ctx = null; + } +} + +class JSObject implements JSRef { + Pointer _val; + Pointer _ctx; + + /// Create + JSObject(this._ctx, Pointer _val) { + Pointer rt = jsGetRuntime(_ctx); + this._val = jsDupValue(_ctx, _val); + runtimeOpaques[rt]?.ref?.add(this); + } + + JSObject.fromAddress(Pointer ctx, Pointer val) { + this._ctx = ctx; + this._val = val; + } + + @override + void release() { + if (_val != null) { + jsFreeValue(_ctx, _val); + } + _val = null; + _ctx = null; + } +} + +class JSFunction extends JSObject implements JSInvokable { + JSFunction(Pointer ctx, Pointer val) : super(ctx, val); + + JSFunction.fromAddress(Pointer ctx, Pointer val) + : super.fromAddress(ctx, val); + + @override + invoke(List arguments, [dynamic thisVal]) { + Pointer jsRet = _invoke(arguments, thisVal); + if (jsRet == null) return; + bool isException = jsIsException(jsRet) != 0; + if (isException) { + jsFreeValue(_ctx, jsRet); + throw Exception(parseJSException(_ctx)); + } + var ret = jsToDart(_ctx, jsRet); + jsFreeValue(_ctx, jsRet); + return ret; + } + + Pointer _invoke(List arguments, [dynamic thisVal]) { + if (_val == null) return null; + List args = arguments + .map( + (e) => dartToJs(_ctx, e), + ) + .toList(); + Pointer jsThis = dartToJs(_ctx, thisVal); + Pointer jsRet = jsCall(_ctx, _val, jsThis, args); + jsFreeValue(_ctx, jsThis); + for (Pointer jsArg in args) { + jsFreeValue(_ctx, jsArg); + } + return jsRet; + } + + @override + noSuchMethod(Invocation invocation) { + return invoke( + invocation.positionalArguments, + invocation.namedArguments[#thisVal], + ); + } +} + +class IsolateJSFunction extends JSInvokable { + int _val; + int _ctx; + SendPort port; + IsolateJSFunction(this._ctx, this._val, this.port); + + @override + Future invoke(List arguments, [thisVal]) async { + if (0 == _val ?? 0) return; + var evaluatePort = ReceivePort(); + port.send({ + 'type': 'call', + 'ctx': _ctx, + 'val': _val, + 'args': encodeData(arguments), + 'this': encodeData(thisVal), + 'port': evaluatePort.sendPort, + }); + Map result = await evaluatePort.first; + evaluatePort.close(); + if (result.containsKey('data')) + return decodeData(result['data'], port); + else + throw result['error']; + } +} + +class IsolateFunction extends JSInvokable implements DartReleasable { + SendPort _port; + SendPort func; + IsolateFunction(this.func, this._port); + + static IsolateFunction bind(Function func, SendPort port) { + final JSInvokable invokable = JSInvokable.wrap(func); + final funcPort = ReceivePort(); + funcPort.listen((msg) async { + if (msg == "close") return funcPort.close(); + var data; + SendPort msgPort = msg['port']; + try { + List args = decodeData(msg['args'], port); + Map thisVal = decodeData(msg['this'], port); + data = await invokable.invoke(args, thisVal); + if (msgPort != null) + msgPort.send({ + 'data': encodeData(data), + }); + } catch (e, stack) { + if (msgPort != null) + msgPort.send({ + 'error': e.toString() + "\n" + stack.toString(), + }); + } + }); + return IsolateFunction(funcPort.sendPort, port); + } + + @override + Future invoke(List positionalArguments, [thisVal]) async { + if (func == null) return; + var evaluatePort = ReceivePort(); + func.send({ + 'args': encodeData(positionalArguments), + 'this': encodeData(thisVal), + 'port': evaluatePort.sendPort, + }); + Map result = await evaluatePort.first; + evaluatePort.close(); + if (result.containsKey('data')) + return decodeData(result['data'], _port); + else + throw result['error']; + } + + @override + void release() { + if (func == null) return; + func.send("close"); + func = null; + } +} + +dynamic encodeData(dynamic data, {Map cache}) { + if (cache == null) cache = Map(); + if (cache.containsKey(data)) return cache[data]; + if (data is List) { + var ret = []; + cache[data] = ret; + for (int i = 0; i < data.length; ++i) { + ret.add(encodeData(data[i], cache: cache)); + } + return ret; + } + if (data is Map) { + var ret = {}; + cache[data] = ret; + for (var entry in data.entries) { + ret[encodeData(entry.key, cache: cache)] = + encodeData(entry.value, cache: cache); + } + return ret; + } + if (data is JSObject) { + return { + '__js_function': data is JSFunction, + '__js_obj_ctx': data._ctx.address, + '__js_obj_val': data._val.address, + }; + } + if (data is IsolateJSFunction) { + return { + '__js_obj_ctx': data._ctx, + '__js_obj_val': data._val, + }; + } + if (data is IsolateFunction) { + return { + '__js_function_port': data.func, + }; + } + if (data is Future) { + var futurePort = ReceivePort(); + data.then((value) { + futurePort.first.then((port) { + futurePort.close(); + (port as SendPort).send({'data': encodeData(value)}); + }); + }, onError: (e, stack) { + futurePort.first.then((port) { + futurePort.close(); + (port as SendPort) + .send({'error': e.toString() + "\n" + stack.toString()}); + }); + }); + return { + '__js_future_port': futurePort.sendPort, + }; + } + return data; +} + +dynamic decodeData(dynamic data, SendPort port, {Map cache}) { + if (cache == null) cache = Map(); + if (cache.containsKey(data)) return cache[data]; + if (data is List) { + var ret = []; + cache[data] = ret; + for (int i = 0; i < data.length; ++i) { + ret.add(decodeData(data[i], port, cache: cache)); + } + return ret; + } + if (data is Map) { + if (data.containsKey('__js_obj_val')) { + int ctx = data['__js_obj_ctx']; + int val = data['__js_obj_val']; + if (data['__js_function'] == false) { + return JSObject.fromAddress( + Pointer.fromAddress(ctx), + Pointer.fromAddress(val), + ); + } else if (port != null) { + return IsolateJSFunction(ctx, val, port); + } else { + return JSFunction.fromAddress( + Pointer.fromAddress(ctx), + Pointer.fromAddress(val), + ); + } + } + if (data.containsKey('__js_function_port')) { + return IsolateFunction(data['__js_function_port'], port); + } + if (data.containsKey('__js_future_port')) { + SendPort port = data['__js_future_port']; + var futurePort = ReceivePort(); + port.send(futurePort.sendPort); + var futureCompleter = Completer(); + futureCompleter.future.catchError((e) {}); + futurePort.first.then((value) { + futurePort.close(); + if (value['error'] != null) { + futureCompleter.completeError(value['error']); + } else { + futureCompleter.complete(value['data']); + } + }); + return futureCompleter.future; + } + var ret = {}; + cache[data] = ret; + for (var entry in data.entries) { + ret[decodeData(entry.key, port, cache: cache)] = + decodeData(entry.value, port, cache: cache); + } + return ret; + } + return data; } String parseJSException(Pointer ctx, {Pointer perr}) { @@ -149,7 +349,7 @@ String parseJSException(Pointer ctx, {Pointer perr}) { var err = jsToCString(ctx, e); if (jsValueGetTag(e) == JSTag.OBJECT) { - Pointer stack = jsGetPropertyStr(ctx, e, "stack"); + Pointer stack = jsGetPropertyValue(ctx, e, "stack"); if (jsToBool(ctx, stack) != 0) { err += '\n' + jsToCString(ctx, stack); } @@ -179,8 +379,23 @@ void definePropertyValue( jsFreeValue(ctx, jsAtomVal); } +Pointer jsGetPropertyValue( + Pointer ctx, + Pointer obj, + dynamic key, { + Map cache, +}) { + var jsAtomVal = dartToJs(ctx, key, cache: cache); + var jsAtom = jsValueToAtom(ctx, jsAtomVal); + var jsProp = jsGetProperty(ctx, obj, jsAtom); + jsFreeAtom(ctx, jsAtom); + jsFreeValue(ctx, jsAtomVal); + return jsProp; +} + Pointer dartToJs(Pointer ctx, dynamic val, {Map cache}) { if (val == null) return jsUNDEFINED(); + if (val is JSObject) return jsDupValue(ctx, val._val); if (val is Future) { var resolvingFunc = allocate(count: sizeOfJSValue * 2); var resolvingFunc2 = @@ -214,9 +429,6 @@ Pointer dartToJs(Pointer ctx, dynamic val, {Map cache}) { if (cache.containsKey(val)) { return jsDupValue(ctx, cache[val]); } - if (val is JSFunction) { - return jsDupValue(ctx, val.val); - } if (val is List) { Pointer ret = jsNewArray(ctx); cache[val] = ret; @@ -233,15 +445,17 @@ Pointer dartToJs(Pointer ctx, dynamic val, {Map cache}) { } return ret; } + // wrap Function to JSInvokable + final valWrap = JSInvokable.wrap(val); int dartObjectClassId = runtimeOpaques[jsGetRuntime(ctx)]?.dartObjectClassId ?? 0; if (dartObjectClassId == 0) return jsUNDEFINED(); var dartObject = jsNewObjectClass( ctx, dartObjectClassId, - identityHashCode(DartObject(ctx, val)), + identityHashCode(DartObject(ctx, valWrap)), ); - if (val is Function || val is IsolateFunction) { + if (valWrap is JSInvokable) { final ret = jsNewCFunction(ctx, dartObject); jsFreeValue(ctx, dartObject); return ret; @@ -268,7 +482,7 @@ dynamic jsToDart(Pointer ctx, Pointer val, {Map cache}) { if (dartObjectClassId != 0) { final dartObject = DartObject.fromAddress( rt, jsGetObjectOpaque(val, dartObjectClassId)); - if (dartObject != null) return dartObject.obj; + if (dartObject != null) return dartObject._obj; } Pointer psize = allocate(); Pointer buf = jsGetArrayBuffer(ctx, psize, val); @@ -284,18 +498,30 @@ dynamic jsToDart(Pointer ctx, Pointer val, {Map cache}) { if (jsIsFunction(ctx, val) != 0) { return JSFunction(ctx, val); } else if (jsIsPromise(ctx, val) != 0) { - return runtimeOpaques[rt]?.promiseToFuture(val); + Pointer jsPromiseThen = jsGetPropertyValue(ctx, val, "then"); + JSFunction promiseThen = jsToDart(ctx, jsPromiseThen, cache: cache); + jsFreeValue(ctx, jsPromiseThen); + var completer = Completer(); + completer.future.catchError((e) {}); + final jsRet = promiseThen._invoke([ + (v) { + if (!completer.isCompleted) completer.complete(v); + }, + (e) { + if (!completer.isCompleted) completer.completeError(e); + }, + ], JSObject.fromAddress(ctx, val)); + bool isException = jsIsException(jsRet) != 0; + jsFreeValue(ctx, jsRet); + if (isException) throw Exception(parseJSException(ctx)); + return completer.future; } else if (jsIsArray(ctx, val) != 0) { - Pointer jslength = jsGetPropertyStr(ctx, val, "length"); + Pointer jslength = jsGetPropertyValue(ctx, val, "length"); int length = jsToInt64(ctx, jslength); List ret = []; cache[valptr] = ret; for (int i = 0; i < length; ++i) { - var jsAtomVal = jsNewInt64(ctx, i); - var jsAtom = jsValueToAtom(ctx, jsAtomVal); - var jsProp = jsGetProperty(ctx, val, jsAtom); - jsFreeAtom(ctx, jsAtom); - jsFreeValue(ctx, jsAtomVal); + var jsProp = jsGetPropertyValue(ctx, val, i); ret.add(jsToDart(ctx, jsProp, cache: cache)); jsFreeValue(ctx, jsProp); } @@ -327,43 +553,3 @@ dynamic jsToDart(Pointer ctx, Pointer val, {Map cache}) { } return null; } - -Pointer jsNewContextWithPromsieWrapper(Pointer rt) { - var ctx = jsNewContext(rt); - final runtimeOpaque = runtimeOpaques[rt]; - if (runtimeOpaque == null) throw Exception("Runtime has been released!"); - - var jsPromiseWrapper = jsEval( - ctx, - """ - (value) => { - const __ret = {}; - Promise.resolve(value) - .then(v => { - __ret.__value = v; - __ret.__resolved = true; - }).catch(e => { - __ret.__error = e; - __ret.__rejected = true; - }); - return __ret; - } - """, - "", - JSEvalFlag.GLOBAL); - runtimeOpaque.dartObjectClassId = jsNewClass(ctx, "DartObject"); - final promiseWrapper = JSRefValue(ctx, jsPromiseWrapper); - jsFreeValue(ctx, jsPromiseWrapper); - runtimeOpaque.promiseToFuture = (promise) { - var completer = Completer(); - completer.future.catchError((e) {}); - var wrapper = promiseWrapper.val; - if (wrapper == null) - completer.completeError(Exception("Runtime has been released!")); - var jsPromise = jsCall(ctx, wrapper, null, [promise]); - var wrapPromise = JSPromise(ctx, jsPromise, completer); - jsFreeValue(ctx, jsPromise); - return wrapPromise.completer.future; - }; - return ctx; -} diff --git a/pubspec.yaml b/pubspec.yaml index 7f636c7..6c6deaa 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_qjs description: This plugin is a simple js engine for flutter using the `quickjs` project. Plugin currently supports all the platforms except web! -version: 0.3.0 +version: 0.3.1 homepage: https://github.com/ekibun/flutter_qjs environment: diff --git a/test/flutter_qjs_test.dart b/test/flutter_qjs_test.dart index 9170172..923a6a3 100644 --- a/test/flutter_qjs_test.dart +++ b/test/flutter_qjs_test.dart @@ -9,32 +9,46 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'package:flutter_qjs/ffi.dart'; import 'package:flutter_qjs/flutter_qjs.dart'; import 'package:flutter_qjs/isolate.dart'; +import 'package:flutter_qjs/ffi.dart'; import 'package:flutter_test/flutter_test.dart'; -dynamic myFunction(String args, {String thisVal}) { +dynamic myFunction(String args, {thisVal}) { return [thisVal, args]; } Future testEvaluate(qjs) async { - final testWrap = await qjs.evaluate("(a) => a", name: ""); + final testWrap = await qjs.evaluate( + "(a) => a", + name: "", + ); + final wrapNull = await testWrap(null); + expect(wrapNull, null, reason: "wrap null"); final primities = [0, 1, 0.1, true, false, "str"]; final wrapPrimities = await testWrap(primities); for (var i = 0; i < primities.length; i++) { expect(wrapPrimities[i], primities[i], reason: "wrap primities"); } + final wrapFunction = await testWrap(testWrap); + final testEqual = await qjs.evaluate( + "(a, b) => a === b", + name: "", + ); + expect(await testEqual(wrapFunction, testWrap), true, + reason: "wrap function"); + + expect(wrapNull, null, reason: "wrap null"); final a = {}; a["a"] = a; final wrapA = await testWrap(a); expect(wrapA['a'], wrapA, reason: "recursive object"); final testThis = await qjs.evaluate( - "(func) => func.call('this', 'arg')", + "(function (func, arg) { return func.call(this, arg) })", name: "", ); - final funcRet = await testThis(myFunction); - expect(funcRet[0], 'this', reason: "js function this"); + final funcRet = await testThis(myFunction, 'arg', thisVal: {'name': 'this'}); + expect(funcRet[0]['name'], 'this', reason: "js function this"); expect(funcRet[1], 'arg', reason: "js function argument"); final promises = await testWrap(await qjs.evaluate( "[Promise.reject('test Promise.reject'), Promise.resolve('test Promise.resolve')]",