This commit is contained in:
ekibun
2021-01-24 21:12:13 +08:00
parent 6b0bab2faf
commit ee110f55e1
12 changed files with 202 additions and 170 deletions

View File

@@ -6,6 +6,11 @@
* @LastEditTime: 2020-12-02 11:36:40 * @LastEditTime: 2020-12-02 11:36:40
--> -->
## 0.3.0
* breakdown change to remove `channel`.
* convert dart function to js.
## 0.2.7 ## 0.2.7
* fix error in ios build. * fix error in ios build.

View File

@@ -60,23 +60,6 @@ Data conversion between dart and js are implemented as follow:
**notice:** Dart function parameter `thisVal` is used to store `this` in js. **notice:** Dart function parameter `thisVal` is used to store `this` in js.
### Set into global object
Method `setToGlobalObject` is presented to set dart object into global object.
For example, you can pass a function implement http in JavaScript with `Dio`:
```dart
engine.setToGlobalObject("http", (String url) {
return Dio().get(url).then((response) => response.data);
});
```
then, in java script you can use `http` function to invoke dart function:
```javascript
http("http://example.com/");
```
### Use modules ### Use modules
ES6 module with `import` function is supported and can be managed in dart with `moduleHandler`: ES6 module with `import` function is supported and can be managed in dart with `moduleHandler`:
@@ -105,8 +88,6 @@ To use async function in module handler, try [Run on isolate thread](#Run-on-iso
Create a `IsolateQjs` object, pass handlers to resolving modules. Async function such as `rootBundle.loadString` can be used now to get modules: Create a `IsolateQjs` object, pass handlers to resolving modules. Async function such as `rootBundle.loadString` can be used now to get modules:
The `methodHandler` is used in isolate, so **the handler function must be a top-level function or a static method**.
```dart ```dart
dynamic methodHandler(String method, List arg) { dynamic methodHandler(String method, List arg) {
switch (method) { switch (method) {
@@ -126,21 +107,11 @@ final engine = IsolateQjs(
// not need engine.dispatch(); // not need engine.dispatch();
``` ```
Method `setToGlobalObject` is still here to set dart object into global object. Use `await` to make sure it is finished.
**Make sure the object that can pass through isolate**, For example, a top level function:
```dart
dynamic http(String url) {
return Dio().get(url).then((response) => response.data);
}
await engine.setToGlobalObject("http", http);
```
Same as run on main thread, use `evaluate` to run js script. In this way, `Promise` return by `evaluate` will be automatically tracked and return the resolved data: Same as run on main thread, use `evaluate` to run js script. In this way, `Promise` return by `evaluate` will be automatically tracked and return the resolved data:
```dart ```dart
try { try {
print(await engine.evaluate(code ?? '', "<eval>")); print(await engine.evaluate(code ?? ''));
} catch (e) { } catch (e) {
print(e.toString()); print(e.toString());
} }
@@ -148,4 +119,22 @@ try {
Method `close` can destroy quickjs runtime that can be recreated again if you call `evaluate`. 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:
```dart
await setToGlobalObject("func", await engine.bind(() {
// DO SOMETHING
}))
```
[This example](example/lib/main.dart) contains a complete demonstration on how to use this plugin. [This example](example/lib/main.dart) contains a complete demonstration on how to use this plugin.
## Breaking change in v0.3.0
`channel` function is no longer utilized by default.
Use js function to set to global:
```dart
final setToGlobalObject = await engine.evaluate("(key, val) => this[key] = val;");
setToGlobalObject("channel", methodHandler);
```

View File

@@ -141,11 +141,6 @@ extern "C"
return new JSValue(JS_NewCFunctionData(ctx, js_channel, 0, 0, 1, funcData)); return new JSValue(JS_NewCFunctionData(ctx, js_channel, 0, 0, 1, funcData));
} }
DLLEXPORT JSValue *jsGetGlobalObject(JSContext *ctx)
{
return new JSValue(JS_GetGlobalObject(ctx));
}
DLLEXPORT JSContext *jsNewContext(JSRuntime *rt) DLLEXPORT JSContext *jsNewContext(JSRuntime *rt)
{ {
JSContext *ctx = JS_NewContext(rt); JSContext *ctx = JS_NewContext(rt);

View File

@@ -73,7 +73,7 @@ class _TestPageState extends State<TestPage> {
CodeInputController _controller = CodeInputController( CodeInputController _controller = CodeInputController(
text: 'import("hello").then(({default: greet}) => greet("world"));'); text: 'import("hello").then(({default: greet}) => greet("world"));');
_ensureEngine() { _ensureEngine() async {
if (engine != null) return; if (engine != null) return;
engine = IsolateQjs( engine = IsolateQjs(
moduleHandler: (String module) async { moduleHandler: (String module) async {
@@ -82,7 +82,9 @@ class _TestPageState extends State<TestPage> {
"js/" + module.replaceFirst(new RegExp(r".js$"), "") + ".js"); "js/" + module.replaceFirst(new RegExp(r".js$"), "") + ".js");
}, },
); );
engine.setToGlobalObject("channel", methodHandler); final setToGlobalObject =
await engine.evaluate("(key, val) => this[key] = val;");
setToGlobalObject("channel", methodHandler);
} }
@override @override
@@ -103,7 +105,7 @@ class _TestPageState extends State<TestPage> {
FlatButton( FlatButton(
child: Text("evaluate"), child: Text("evaluate"),
onPressed: () async { onPressed: () async {
_ensureEngine(); await _ensureEngine();
try { try {
resp = (await engine.evaluate(_controller.text ?? '', resp = (await engine.evaluate(_controller.text ?? '',
name: "<eval>")) name: "<eval>"))

View File

@@ -82,7 +82,7 @@ packages:
path: ".." path: ".."
relative: true relative: true
source: path source: path
version: "0.2.7" version: "0.3.0"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter

View File

@@ -1,27 +0,0 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_qjs_example/main.dart';
void main() {
testWidgets('Verify Platform version', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());
// Verify that platform version is retrieved.
expect(
find.byWidgetPredicate(
(Widget widget) =>
widget is Text && widget.data.startsWith('Running on:'),
),
findsOneWidget,
);
});
}

View File

@@ -186,17 +186,6 @@ final Pointer Function(
)>>("jsNewCFunction") )>>("jsNewCFunction")
.asFunction(); .asFunction();
/// JSValue *jsGetGlobalObject(JSContext *ctx)
final Pointer Function(
Pointer ctx,
) jsGetGlobalObject = qjsLib
.lookup<
NativeFunction<
Pointer Function(
Pointer,
)>>("jsGetGlobalObject")
.asFunction();
/// JSContext *jsNewContext(JSRuntime *rt) /// JSContext *jsNewContext(JSRuntime *rt)
final Pointer Function( final Pointer Function(
Pointer rt, Pointer rt,

View File

@@ -13,6 +13,8 @@ import 'package:ffi/ffi.dart';
import 'package:flutter_qjs/ffi.dart'; import 'package:flutter_qjs/ffi.dart';
import 'package:flutter_qjs/wrapper.dart'; import 'package:flutter_qjs/wrapper.dart';
import 'isolate.dart';
/// Handler function to manage js module. /// Handler function to manage js module.
typedef JsModuleHandler = String Function(String name); typedef JsModuleHandler = String Function(String name);
@@ -44,11 +46,10 @@ class FlutterQjs {
this.hostPromiseRejectionHandler, this.hostPromiseRejectionHandler,
}); });
setToGlobalObject(dynamic key, dynamic val) { static applyFunction(Function func, List args, dynamic thisVal) {
_ensureEngine(); final passThis =
final globalObject = jsGetGlobalObject(_ctx); RegExp("{.*thisVal.*}").hasMatch(func.runtimeType.toString());
definePropertyValue(_ctx, globalObject, key, val); return Function.apply(func, args, passThis ? {#thisVal: thisVal} : null);
jsFreeValue(_ctx, globalObject);
} }
_ensureEngine() { _ensureEngine() {
@@ -68,13 +69,11 @@ class FlutterQjs {
))); )));
} }
final thisVal = jsToDart(ctx, pdata.elementAt(0).value); final thisVal = jsToDart(ctx, pdata.elementAt(0).value);
Function func = jsToDart(ctx, pdata.elementAt(3).value); final func = jsToDart(ctx, pdata.elementAt(3).value);
final passThis = final ret = func is QjsInvokable
RegExp("{.*thisVal.*}").hasMatch(func.runtimeType.toString()); ? func.invoke(args, thisVal)
return dartToJs( : applyFunction(func, args, thisVal);
ctx, return dartToJs(ctx, ret);
Function.apply(func, args, passThis ? {#thisVal: thisVal} : null),
);
case JSChannelType.MODULE: case JSChannelType.MODULE:
if (moduleHandler == null) throw Exception("No ModuleHandler"); if (moduleHandler == null) throw Exception("No ModuleHandler");
var ret = Utf8.toUtf8(moduleHandler( var ret = Utf8.toUtf8(moduleHandler(

View File

@@ -14,13 +14,13 @@ import 'package:ffi/ffi.dart';
import 'package:flutter_qjs/flutter_qjs.dart'; import 'package:flutter_qjs/flutter_qjs.dart';
import 'package:flutter_qjs/wrapper.dart'; import 'package:flutter_qjs/wrapper.dart';
class IsolateJSFunction { class IsolateJSFunction implements QjsInvokable {
int val; int val;
int ctx; int ctx;
SendPort port; SendPort port;
IsolateJSFunction(this.ctx, this.val, this.port); IsolateJSFunction(this.ctx, this.val, this.port);
Future<dynamic> invoke(List<dynamic> arguments) async { Future invoke(List arguments, [thisVal]) async {
if (0 == val ?? 0) return; if (0 == val ?? 0) return;
var evaluatePort = ReceivePort(); var evaluatePort = ReceivePort();
port.send({ port.send({
@@ -28,9 +28,11 @@ class IsolateJSFunction {
'ctx': ctx, 'ctx': ctx,
'val': val, 'val': val,
'args': _encodeData(arguments), 'args': _encodeData(arguments),
'this': _encodeData(thisVal),
'port': evaluatePort.sendPort, 'port': evaluatePort.sendPort,
}); });
var result = await evaluatePort.first; var result = await evaluatePort.first;
evaluatePort.close();
if (result['data'] != null) if (result['data'] != null)
return _decodeData(result['data'], port); return _decodeData(result['data'], port);
else else
@@ -39,7 +41,63 @@ class IsolateJSFunction {
@override @override
noSuchMethod(Invocation invocation) { noSuchMethod(Invocation invocation) {
return invoke(invocation.positionalArguments); 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],
);
} }
} }
@@ -75,16 +133,23 @@ dynamic _encodeData(dynamic data, {Map<dynamic, dynamic> cache}) {
'__js_function_val': data.val, '__js_function_val': data.val,
}; };
} }
if (data is IsolateFunction) {
return {
'__js_function_port': data.func,
};
}
if (data is Future) { if (data is Future) {
var futurePort = ReceivePort(); var futurePort = ReceivePort();
data.then((value) { data.then((value) {
futurePort.first.then((port) => { futurePort.first.then((port) {
(port as SendPort).send({'data': _encodeData(value)}) futurePort.close();
(port as SendPort).send({'data': _encodeData(value)});
}); });
}, onError: (e, stack) { }, onError: (e, stack) {
futurePort.first.then((port) => { futurePort.first.then((port) {
futurePort.close();
(port as SendPort) (port as SendPort)
.send({'error': e.toString() + "\n" + stack.toString()}) .send({'error': e.toString() + "\n" + stack.toString()});
}); });
}); });
return { return {
@@ -116,12 +181,17 @@ dynamic _decodeData(dynamic data, SendPort port,
return JSFunction.fromAddress(ctx, val); return JSFunction.fromAddress(ctx, val);
} }
} }
if (data.containsKey('__js_function_port')) {
return IsolateFunction(data['__js_function_port'], port);
}
if (data.containsKey('__js_future_port')) { if (data.containsKey('__js_future_port')) {
SendPort port = data['__js_future_port']; SendPort port = data['__js_future_port'];
var futurePort = ReceivePort(); var futurePort = ReceivePort();
port.send(futurePort.sendPort); port.send(futurePort.sendPort);
var futureCompleter = Completer(); var futureCompleter = Completer();
futureCompleter.future.catchError((e) {});
futurePort.first.then((value) { futurePort.first.then((value) {
futurePort.close();
if (value['error'] != null) { if (value['error'] != null) {
futureCompleter.completeError(value['error']); futureCompleter.completeError(value['error']);
} else { } else {
@@ -172,8 +242,7 @@ void _runJsIsolate(Map spawnMessage) async {
return ret; return ret;
}, },
); );
qjs.dispatch(); port.listen((msg) async {
await for (var msg in port) {
var data; var data;
SendPort msgPort = msg['port']; SendPort msgPort = msg['port'];
try { try {
@@ -189,10 +258,10 @@ void _runJsIsolate(Map spawnMessage) async {
data = JSFunction.fromAddress( data = JSFunction.fromAddress(
msg['ctx'], msg['ctx'],
msg['val'], msg['val'],
).invoke(_decodeData(msg['args'], null)); ).invoke(
break; _decodeData(msg['args'], null),
case 'setToGlobalObject': _decodeData(msg['this'], null),
qjs.setToGlobalObject(msg['key'], msg['val']); );
break; break;
case 'close': case 'close':
qjs.port.close(); qjs.port.close();
@@ -210,7 +279,8 @@ void _runJsIsolate(Map spawnMessage) async {
'error': e.toString() + "\n" + stack.toString(), 'error': e.toString() + "\n" + stack.toString(),
}); });
} }
} });
await qjs.dispatch();
} }
typedef JsAsyncModuleHandler = Future<String> Function(String name); typedef JsAsyncModuleHandler = Future<String> Function(String name);
@@ -290,6 +360,12 @@ class IsolateQjs {
_sendPort = completer.future; _sendPort = completer.future;
} }
/// Create isolate function
Future<IsolateFunction> bind(Function func) async {
_ensureEngine();
return IsolateFunction.bind(func, await _sendPort);
}
/// Free Runtime and close isolate thread that can be recreate when evaluate again. /// Free Runtime and close isolate thread that can be recreate when evaluate again.
close() { close() {
if (_sendPort == null) return; if (_sendPort == null) return;
@@ -301,23 +377,6 @@ class IsolateQjs {
_sendPort = null; _sendPort = null;
} }
setToGlobalObject(dynamic key, dynamic val) async {
_ensureEngine();
var evaluatePort = ReceivePort();
var sendPort = await _sendPort;
sendPort.send({
'type': 'setToGlobalObject',
'key': key,
'val': val,
'port': evaluatePort.sendPort,
});
var result = await evaluatePort.first;
if (result['error'] == null) {
return;
} else
throw result['error'];
}
/// Evaluate js script. /// Evaluate js script.
Future<dynamic> evaluate(String command, {String name, int evalFlags}) async { Future<dynamic> evaluate(String command, {String name, int evalFlags}) async {
_ensureEngine(); _ensureEngine();
@@ -331,6 +390,7 @@ class IsolateQjs {
'port': evaluatePort.sendPort, 'port': evaluatePort.sendPort,
}); });
var result = await evaluatePort.first; var result = await evaluatePort.first;
evaluatePort.close();
if (result['error'] == null) { if (result['error'] == null) {
return _decodeData(result['data'], sendPort); return _decodeData(result['data'], sendPort);
} else } else

View File

@@ -12,6 +12,7 @@ import 'dart:typed_data';
import 'package:ffi/ffi.dart'; import 'package:ffi/ffi.dart';
import 'ffi.dart'; import 'ffi.dart';
import 'isolate.dart';
class JSRefValue implements JSRef { class JSRefValue implements JSRef {
Pointer val; Pointer val;
@@ -37,6 +38,14 @@ class JSRefValue implements JSRef {
} }
} }
abstract class QjsReleasable {
void release();
}
abstract class QjsInvokable {
dynamic invoke(List positionalArguments, [dynamic thisVal]);
}
class DartObject implements JSRef { class DartObject implements JSRef {
Object obj; Object obj;
Pointer ctx; Pointer ctx;
@@ -53,6 +62,7 @@ class DartObject implements JSRef {
@override @override
void release() { void release() {
if (obj is QjsReleasable) (obj as QjsReleasable).release();
obj = null; obj = null;
ctx = null; ctx = null;
} }
@@ -90,19 +100,19 @@ class JSPromise extends JSRefValue {
} }
} }
class JSFunction extends JSRefValue { class JSFunction extends JSRefValue implements QjsInvokable {
JSFunction(Pointer ctx, Pointer val) : super(ctx, val); JSFunction(Pointer ctx, Pointer val) : super(ctx, val);
JSFunction.fromAddress(int ctx, int val) : super.fromAddress(ctx, val); JSFunction.fromAddress(int ctx, int val) : super.fromAddress(ctx, val);
invoke(List<dynamic> arguments) { invoke(List<dynamic> arguments, [dynamic thisVal]) {
if (val == null) return; if (val == null) return;
List<Pointer> args = arguments List<Pointer> args = arguments
.map( .map(
(e) => dartToJs(ctx, e), (e) => dartToJs(ctx, e),
) )
.toList(); .toList();
Pointer jsRet = jsCall(ctx, val, null, args); Pointer jsRet = jsCall(ctx, val, dartToJs(ctx, thisVal), args);
for (Pointer jsArg in args) { for (Pointer jsArg in args) {
jsFreeValue(ctx, jsArg); jsFreeValue(ctx, jsArg);
} }
@@ -118,7 +128,10 @@ class JSFunction extends JSRefValue {
@override @override
noSuchMethod(Invocation invocation) { noSuchMethod(Invocation invocation) {
return invoke(invocation.positionalArguments); return invoke(
invocation.positionalArguments,
invocation.namedArguments[#thisVal],
);
} }
} }
@@ -228,7 +241,7 @@ Pointer dartToJs(Pointer ctx, dynamic val, {Map<dynamic, dynamic> cache}) {
dartObjectClassId, dartObjectClassId,
identityHashCode(DartObject(ctx, val)), identityHashCode(DartObject(ctx, val)),
); );
if (val is Function) { if (val is Function || val is IsolateFunction) {
final ret = jsNewCFunction(ctx, dartObject); final ret = jsNewCFunction(ctx, dartObject);
jsFreeValue(ctx, dartObject); jsFreeValue(ctx, dartObject);
return ret; return ret;
@@ -343,6 +356,7 @@ Pointer jsNewContextWithPromsieWrapper(Pointer rt) {
jsFreeValue(ctx, jsPromiseWrapper); jsFreeValue(ctx, jsPromiseWrapper);
runtimeOpaque.promiseToFuture = (promise) { runtimeOpaque.promiseToFuture = (promise) {
var completer = Completer(); var completer = Completer();
completer.future.catchError((e) {});
var wrapper = promiseWrapper.val; var wrapper = promiseWrapper.val;
if (wrapper == null) if (wrapper == null)
completer.completeError(Exception("Runtime has been released!")); completer.completeError(Exception("Runtime has been released!"));

View File

@@ -1,6 +1,6 @@
name: flutter_qjs 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! description: This plugin is a simple js engine for flutter using the `quickjs` project. Plugin currently supports all the platforms except web!
version: 0.2.7 version: 0.3.0
homepage: https://github.com/ekibun/flutter_qjs homepage: https://github.com/ekibun/flutter_qjs
environment: environment:

View File

@@ -14,32 +14,42 @@ import 'package:flutter_qjs/flutter_qjs.dart';
import 'package:flutter_qjs/isolate.dart'; import 'package:flutter_qjs/isolate.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
dynamic myMethodHandler(List args, {String thisVal}) { dynamic myFunction(String args, {String thisVal}) {
return [thisVal, ...args]; return [thisVal, args];
} }
Future testEvaluate(qjs) async { Future testEvaluate(qjs) async {
final value = await qjs.evaluate(""" final testWrap = await qjs.evaluate("(a) => a", name: "<testWrap>");
const a = {}; final primities = [0, 1, 0.1, true, false, "str"];
a.a = a; final wrapPrimities = await testWrap(primities);
import('test').then((module) => channel.call('this', [ for (var i = 0; i < primities.length; i++) {
(...args)=>`hello \${args}!`, a, expect(wrapPrimities[i], primities[i], reason: "wrap primities");
Promise.reject('test Promise.reject'), Promise.resolve('test Promise.resolve'), }
0.1, true, false, 1, "world", module final a = {};
])); a["a"] = a;
""", name: "<eval>"); final wrapA = await testWrap(a);
expect(value[0], 'this', reason: "js function this"); expect(wrapA['a'], wrapA, reason: "recursive object");
expect(await value[1]('world'), 'hello world!', reason: "js function call"); final testThis = await qjs.evaluate(
expect(value[2]['a'], value[2], reason: "recursive object"); "(func) => func.call('this', 'arg')",
expect(value[3], isInstanceOf<Future>(), reason: "promise object"); name: "<testThis>",
);
final funcRet = await testThis(myFunction);
expect(funcRet[0], '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')]",
name: "<promises>",
));
for (final promise in promises)
expect(promise, isInstanceOf<Future>(), reason: "promise object");
try { try {
await value[3]; await promises[0];
throw 'Future not reject'; throw 'Future not reject';
} catch (e) { } catch (e) {
expect(e, startsWith('test Promise.reject\n'), expect(e, startsWith('test Promise.reject\n'),
reason: "promise object reject"); reason: "promise object reject");
} }
expect(await value[4], 'test Promise.resolve', expect(await promises[1], 'test Promise.resolve',
reason: "promise object resolve"); reason: "promise object resolve");
} }
@@ -95,41 +105,37 @@ void main() async {
expect(result['default']['data'], 'test module', reason: "eval module"); expect(result['default']['data'], 'test module', reason: "eval module");
qjs.close(); qjs.close();
}); });
test('jsToDart', () async { test('data conversion', () async {
final qjs = FlutterQjs( final qjs = FlutterQjs(
moduleHandler: (name) { moduleHandler: (name) {
return "export default '${new DateTime.now()}'"; return "export default '${new DateTime.now()}'";
}, },
hostPromiseRejectionHandler: (_) {}, hostPromiseRejectionHandler: (_) {},
); );
qjs.setToGlobalObject("channel", myMethodHandler);
qjs.dispatch(); qjs.dispatch();
await testEvaluate(qjs); await testEvaluate(qjs);
qjs.close(); qjs.close();
}); });
test('isolate', () async { test('isolate conversion', () async {
await runZonedGuarded(() async {
final qjs = IsolateQjs( final qjs = IsolateQjs(
moduleHandler: (name) async { moduleHandler: (name) async {
return "export default '${new DateTime.now()}'"; return "export default '${new DateTime.now()}'";
}, },
hostPromiseRejectionHandler: (_) {}, hostPromiseRejectionHandler: (_) {},
); );
await qjs.setToGlobalObject("channel", myMethodHandler);
await testEvaluate(qjs); await testEvaluate(qjs);
qjs.close(); qjs.close();
}, (e, stack) {
if (!e.toString().startsWith("test Promise.reject")) throw e;
}); });
}); test('isolate bind function', () async {
test('dart object', () async { final qjs = IsolateQjs();
final qjs = FlutterQjs(); var localVar;
qjs.setToGlobalObject("channel", () { final testFunc = await qjs.evaluate("(func)=>func('ret')", name: "<eval>");
return FlutterQjs(); final testFuncRet = await testFunc(await qjs.bind((args) {
}); localVar = 'test';
qjs.dispatch(); return args;
var value = await qjs.evaluate("channel()", name: "<eval>"); }));
expect(value, isInstanceOf<FlutterQjs>(), reason: "dart object"); expect(localVar, 'test', reason: "bind function");
expect(testFuncRet, 'ret', reason: "bind function args return");
qjs.close(); qjs.close();
}); });
test('stack overflow', () async { test('stack overflow', () async {