From 045277dbe351ad81f84383e843ea7266ef8668da Mon Sep 17 00:00:00 2001 From: ekibun Date: Sat, 2 Jan 2021 00:57:52 +0800 Subject: [PATCH] 0.2.0 --- CHANGELOG.md | 6 ++ README.md | 122 ++++++++++++++++--------------------- cxx/ffi.cpp | 15 +++-- cxx/ffi.h | 6 +- cxx/quickjs | 2 +- example/lib/main.dart | 14 +++-- example/pubspec.lock | 2 +- lib/ffi.dart | 47 +++++++++++--- lib/flutter_qjs.dart | 52 +++++----------- lib/isolate.dart | 93 ++++++++++++++++++---------- lib/wrapper.dart | 15 +++-- pubspec.yaml | 2 +- test/flutter_qjs_test.dart | 87 +++++++++++++++++++++----- 13 files changed, 278 insertions(+), 185 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 307ed5e..59bf684 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ * @LastEditTime: 2020-12-02 11:36:40 --> +## 0.2.0 + +* breakdown change with new constructor. +* fix make release in ios. +* fix crash in wrapping js Promise. + ## 0.1.4 * fix crash on android x86. diff --git a/README.md b/README.md index afa9418..81ea6b9 100644 --- a/README.md +++ b/README.md @@ -19,84 +19,79 @@ ES6 module with `import` function is supported and can be managed in dart with ` A global function `channel` is presented to invoke dart function. 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 | -| JSFunction | function(....args) | -| Future | Promise | +| dart | js | +| --------------------------------------------------- | ------------------ | +| Bool | boolean | +| Int | number | +| Double | number | +| String | string | +| Uint8List | ArrayBuffer | +| List | Array | +| Map | Object | +| JSFunction(...args)
IsolateJSFunction(...args) | function(....args) | +| Future | Promise | -**notice:** `function` can only be sent from js to dart. `Promise` return by `evaluate` will be automatically tracked and return the resolved data. +**notice:** `function` can only be sent from js to dart. `IsolateJSFunction` always returns asynchronously. ## Getting Started ### Run on main thread -1. Create a `FlutterQjs` object. Call `dispatch` to dispatch event loop. +1. Create a `FlutterQjs` object, pass handlers to implement js-dart interaction and resolving modules. For example, you can use `Dio` to implement http in js: ```dart -final engine = FlutterQjs(); -await engine.dispatch(); +final engine = FlutterQjs( + methodHandler: (String method, List arg) { + switch (method) { + case "http": + return Dio().get(arg[0]).then((response) => response.data); + default: + throw Exception("No such method"); + } + }, + moduleHandler: (String module) { + if(module == "hello") + return "export default (name) => `hello \${name}!`;"; + throw Exception("Module Not found"); + }, +); ``` -2. Call `setMethodHandler` to implement js-dart interaction. For example, you can use `Dio` to implement http in js: - -```dart -await engine.setMethodHandler((String method, List arg) { - switch (method) { - case "http": - return Dio().get(arg[0]).then((response) => response.data); - default: - throw Exception("No such method"); - } -}); -``` - -and in javascript, call `channel` function to get data, make sure the second parameter is a list: +in javascript, `channel` function is equiped to invoke `methodHandler`, make sure the second parameter is a list: ```javascript channel("http", ["http://example.com/"]); ``` -3. Call `setModuleHandler` to resolve the js module. - -~~I cannot find a way to convert the sync ffi callback into an async function. So the assets files received by async function `rootBundle.loadString` cannot be used in this version. I will appreciate it if you can provide me a solution to make `ModuleHandler` async.~~ - -To use async function in module handler, try [Run on isolate thread](#Run-on-isolate-thread) - -```dart -await engine.setModuleHandler((String module) { - if(module == "hello") return "export default (name) => `hello \${name}!`;"; - throw Exception("Module Not found"); -}); -``` - -and in javascript, call `import` function to get module: +`import` function is used to get modules: ```javascript import("hello").then(({default: greet}) => greet("world")); ``` -4. Use `evaluate` to run js script: +**notice:** To use async function in module handler, try [Run on isolate thread](#Run-on-isolate-thread) + +2. Then call `dispatch` to dispatch event loop. + +```dart +engine.dispatch(); +``` + +1. Use `evaluate` to run js script, now you can use it synchronously, or use await to resolve `Promise`: ```dart try { - print(await engine.evaluate(code ?? '', "")); + print(engine.evaluate(code ?? '')); } catch (e) { print(e.toString()); } ``` -5. Method `recreate` can destroy quickjs runtime that can be recreated again if you call `evaluate`, `recreat` can be used to reset the module cache. Call `close` to stop `dispatch` when you do not need it. +1. Method `close` can destroy quickjs runtime that can be recreated again if you call `evaluate`. ### Run on isolate thread -1. Create a `IsolateQjs` object, pass a handler to implement js-dart interaction. The handler is used in isolate, so the function must be a top-level function or a static method. +1. Create a `IsolateQjs` object, pass handlers to implement js-dart interaction and resolving modules. The `methodHandler` is used in isolate, so **the handler function must be a top-level function or a static method**. Async function such as `rootBundle.loadString` can be used now to get module: ```dart dynamic methodHandler(String method, List arg) { @@ -107,32 +102,17 @@ dynamic methodHandler(String method, List arg) { throw Exception("No such method"); } } -final engine = IsolateQjs(methodHandler); +final engine = IsolateQjs( + methodHandler: methodHandler, + moduleHandler: (String module) async { + return await rootBundle.loadString( + "js/" + module.replaceFirst(new RegExp(r".js$"), "") + ".js"); + }, +); // not need engine.dispatch(); ``` -and in javascript, call `channel` function to get data, make sure the second parameter is a list: - -```javascript -channel("http", ["http://example.com/"]); -``` - -2. Call `setModuleHandler` to resolve the js module. Async function such as `rootBundle.loadString` can be used now to get module. The handler is called in main thread. - -```dart -await engine.setModuleHandler((String module) async { - return await rootBundle.loadString( - "js/" + module.replaceFirst(new RegExp(r".js$"), "") + ".js"); -}); -``` - -and in javascript, call `import` function to get module: - -```javascript -import("hello").then(({default: greet}) => greet("world")); -``` - -3. Same as run on main thread, use `evaluate` to run js script: +2. 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 try { @@ -142,7 +122,7 @@ try { } ``` -4. Method `close` (same as `recreate` in main thread) can destroy quickjs runtime that can be recreated again if you call `evaluate`. +3. Method `close` can destroy quickjs runtime that can be recreated again if you call `evaluate`. [This example](example/lib/main.dart) contains a complete demonstration on how to use this plugin. diff --git a/cxx/ffi.cpp b/cxx/ffi.cpp index 5b8c186..e3dc7ba 100644 --- a/cxx/ffi.cpp +++ b/cxx/ffi.cpp @@ -154,16 +154,18 @@ extern "C" return new JSValue(JS_NewObject(ctx)); } - DLLEXPORT void jsFreeValue(JSContext *ctx, JSValue *v) + DLLEXPORT void jsFreeValue(JSContext *ctx, JSValue *v, int32_t free) { JS_FreeValue(ctx, *v); - delete v; + if (free) + delete v; } - DLLEXPORT void jsFreeValueRT(JSRuntime *rt, JSValue *v) + DLLEXPORT void jsFreeValueRT(JSRuntime *rt, JSValue *v, int32_t free) { JS_FreeValueRT(rt, *v); - delete v; + if (free) + delete v; } DLLEXPORT JSValue *jsDupValue(JSContext *ctx, JSValueConst *v) @@ -215,6 +217,11 @@ extern "C" return JS_IsFunction(ctx, *val); } + DLLEXPORT int32_t jsIsPromise(JSContext *ctx, JSValueConst *val) + { + return JS_IsPromise(ctx, *val); + } + DLLEXPORT int32_t jsIsArray(JSContext *ctx, JSValueConst *val) { return JS_IsArray(ctx, *val); diff --git a/cxx/ffi.h b/cxx/ffi.h index 7f2b299..be25049 100644 --- a/cxx/ffi.h +++ b/cxx/ffi.h @@ -51,9 +51,9 @@ extern "C" DLLEXPORT JSValue *jsNewObject(JSContext *ctx); - DLLEXPORT void jsFreeValue(JSContext *ctx, JSValue *v); + DLLEXPORT void jsFreeValue(JSContext *ctx, JSValue *v, int32_t free); - DLLEXPORT void jsFreeValueRT(JSRuntime *rt, JSValue *v); + DLLEXPORT void jsFreeValueRT(JSRuntime *rt, JSValue *v, int32_t free); DLLEXPORT JSValue *jsDupValue(JSContext *ctx, JSValueConst *v); @@ -73,6 +73,8 @@ extern "C" DLLEXPORT int32_t jsIsFunction(JSContext *ctx, JSValueConst *val); + DLLEXPORT int32_t jsIsPromise(JSContext *ctx, JSValueConst *val); + DLLEXPORT int32_t jsIsArray(JSContext *ctx, JSValueConst *val); DLLEXPORT JSValue *jsGetProperty(JSContext *ctx, JSValueConst *this_obj, diff --git a/cxx/quickjs b/cxx/quickjs index 5143636..fc5dca5 160000 --- a/cxx/quickjs +++ b/cxx/quickjs @@ -1 +1 @@ -Subproject commit 5143636b2db8183cd45e6143d3e7d32d182e78d8 +Subproject commit fc5dca513b3b6a5e1cbe9da24fbd8491887bdc0d diff --git a/example/lib/main.dart b/example/lib/main.dart index 3717589..269c9af 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -75,12 +75,14 @@ class _TestPageState extends State { _ensureEngine() { if (engine != null) return; - engine = IsolateQjs(methodHandler); - engine.setModuleHandler((String module) async { - if (module == "test") return "export default '${new DateTime.now()}'"; - return await rootBundle.loadString( - "js/" + module.replaceFirst(new RegExp(r".js$"), "") + ".js"); - }); + engine = IsolateQjs( + methodHandler: methodHandler, + moduleHandler: (String module) async { + if (module == "test") return "export default '${new DateTime.now()}'"; + return await rootBundle.loadString( + "js/" + module.replaceFirst(new RegExp(r".js$"), "") + ".js"); + }, + ); } @override diff --git a/example/pubspec.lock b/example/pubspec.lock index 3b5e630..fc2018c 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -82,7 +82,7 @@ packages: path: ".." relative: true source: path - version: "0.1.4" + version: "0.2.0" flutter_test: dependency: "direct dev" description: flutter diff --git a/lib/ffi.dart b/lib/ffi.dart index 1968d18..f606297 100644 --- a/lib/ffi.dart +++ b/lib/ffi.dart @@ -16,7 +16,7 @@ abstract class JSRef { } /// JS_Eval() flags -class JSEvalType { +class JSEvalFlag { static const GLOBAL = 0 << 0; static const MODULE = 1 << 0; } @@ -110,9 +110,9 @@ typedef JSChannel = Pointer Function(Pointer ctx, Pointer method, Pointer argv); class RuntimeOpaque { JSChannel channel; - List ref = List(); + List ref = []; ReceivePort port; - Future Function(Pointer) promsieToFuture; + Future Function(Pointer) promiseToFuture; } final Map runtimeOpaques = Map(); @@ -356,32 +356,52 @@ final Pointer Function( )>>("jsNewObject") .asFunction(); -/// void jsFreeValue(JSContext *ctx, JSValue *val) +/// void jsFreeValue(JSContext *ctx, JSValue *val, int32_t free) final void Function( Pointer ctx, Pointer val, -) jsFreeValue = qjsLib + int free, +) _jsFreeValue = qjsLib .lookup< NativeFunction< Void Function( Pointer, Pointer, + Int32, )>>("jsFreeValue") .asFunction(); -/// void jsFreeValueRT(JSRuntime *rt, JSValue *v) +void jsFreeValue( + Pointer ctx, + Pointer val, { + bool free = true, +}) { + _jsFreeValue(ctx, val, free ? 1 : 0); +} + +/// void jsFreeValue(JSRuntime *rt, JSValue *val, int32_t free) final void Function( Pointer rt, Pointer val, -) jsFreeValueRT = qjsLib + int free, +) _jsFreeValueRT = qjsLib .lookup< NativeFunction< Void Function( Pointer, Pointer, + Int32, )>>("jsFreeValueRT") .asFunction(); +void jsFreeValueRT( + Pointer rt, + Pointer val, { + bool free = true, +}) { + _jsFreeValueRT(rt, val, free ? 1 : 0); +} + /// JSValue *jsDupValue(JSContext *ctx, JSValueConst *v) final Pointer Function( Pointer ctx, @@ -512,6 +532,19 @@ final int Function( )>>("jsIsFunction") .asFunction(); +/// int32_t jsIsPromise(JSContext *ctx, JSValueConst *val) +final int Function( + Pointer ctx, + Pointer val, +) jsIsPromise = qjsLib + .lookup< + NativeFunction< + Int32 Function( + Pointer, + Pointer, + )>>("jsIsPromise") + .asFunction(); + /// int32_t jsIsArray(JSContext *ctx, JSValueConst *val) final int Function( Pointer ctx, diff --git a/lib/flutter_qjs.dart b/lib/flutter_qjs.dart index 743f0c1..2d79227 100644 --- a/lib/flutter_qjs.dart +++ b/lib/flutter_qjs.dart @@ -23,9 +23,15 @@ class FlutterQjs { Pointer _rt; Pointer _ctx; ReceivePort port = ReceivePort(); + + /// Set a handler to manage js call with `channel(method, args)` function. JsMethodHandler methodHandler; + + /// Set a handler to manage js module. JsModuleHandler moduleHandler; + FlutterQjs({this.methodHandler, this.moduleHandler}); + _ensureEngine() { if (_rt != null) return; _rt = jsNewRuntime((ctx, method, argv) { @@ -61,18 +67,8 @@ class FlutterQjs { _ctx = jsNewContextWithPromsieWrapper(_rt); } - /// Set a handler to manage js call with `channel(method, args)` function. - setMethodHandler(JsMethodHandler handler) { - methodHandler = handler; - } - - /// Set a handler to manage js module. - setModuleHandler(JsModuleHandler handler) { - moduleHandler = handler; - } - /// Free Runtime and Context which can be recreate when evaluate again. - recreate() { + close() { if (_rt != null) { jsFreeContext(_ctx); jsFreeRuntime(_rt); @@ -81,18 +77,10 @@ class FlutterQjs { _ctx = null; } - /// Close ReceivePort. - close() { - if (port != null) { - port.close(); - recreate(); - } - port = null; - } - /// DispatchMessage Future dispatch() async { await for (var _ in port) { + if (_rt == null) continue; while (true) { int err = jsExecutePendingJob(_rt); if (err <= 0) { @@ -116,24 +104,14 @@ class FlutterQjs { } /// Evaluate js script. - Future evaluate(String command, {String name, int evalFlags}) async { + dynamic evaluate(String command, {String name, int evalFlags}) { _ensureEngine(); - var jsval = - jsEval(_ctx, command, name ?? "", evalFlags ?? JSEvalType.GLOBAL); - if (jsIsException(jsval) != 0) { - jsFreeValue(_ctx, jsval); - throw Exception(parseJSException(_ctx)); - } - var ret = runtimeOpaques[_rt]?.promsieToFuture(jsval); - jsFreeValue(_ctx, jsval); - return ret; - } - - /// Evaluate js script (Sync). - dynamic evaluateSync(String command, {String name, int evalFlags}) { - _ensureEngine(); - var jsval = - jsEval(_ctx, command, name ?? "", evalFlags ?? JSEvalType.GLOBAL); + var jsval = jsEval( + _ctx, + command, + name ?? "", + evalFlags ?? JSEvalFlag.GLOBAL, + ); if (jsIsException(jsval) != 0) { jsFreeValue(_ctx, jsval); throw Exception(parseJSException(_ctx)); diff --git a/lib/isolate.dart b/lib/isolate.dart index 80099cb..7b4b8aa 100644 --- a/lib/isolate.dart +++ b/lib/isolate.dart @@ -76,8 +76,20 @@ dynamic _encodeData(dynamic data, {Map cache}) { }; } if (data is Future) { - // Not support - return {}; + var futurePort = ReceivePort(); + data.then((value) { + futurePort.first.then((port) => { + (port as SendPort).send({'data': _encodeData(value)}) + }); + }, onError: (e, stack) { + futurePort.first.then((port) => { + (port as SendPort) + .send({'error': e.toString() + "\n" + stack.toString()}) + }); + }); + return { + '__js_future_port': futurePort.sendPort, + }; } return data; } @@ -104,6 +116,20 @@ dynamic _decodeData(dynamic data, SendPort port, return JSFunction.fromAddress(ctx, val); } } + if (data.containsKey('__js_future_port')) { + SendPort port = data['__js_future_port']; + var futurePort = ReceivePort(); + port.send(futurePort.sendPort); + var futureCompleter = Completer(); + futurePort.first.then((value) { + 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) { @@ -116,30 +142,31 @@ dynamic _decodeData(dynamic data, SendPort port, } void _runJsIsolate(Map spawnMessage) async { - var qjs = FlutterQjs(); SendPort sendPort = spawnMessage['port']; JsMethodHandler methodHandler = spawnMessage['handler']; ReceivePort port = ReceivePort(); sendPort.send(port.sendPort); - qjs.setMethodHandler(methodHandler); - qjs.setModuleHandler((name) { - var ptr = allocate>(); - ptr.value = Pointer.fromAddress(0); - sendPort.send({ - 'type': 'module', - 'name': name, - 'ptr': ptr.address, - }); - while (ptr.value.address == 0) sleep(Duration.zero); - if (ptr.value.address == -1) throw Exception("Module Not found"); - var ret = Utf8.fromUtf8(ptr.value); - sendPort.send({ - 'type': 'release', - 'ptr': ptr.value.address, - }); - free(ptr); - return ret; - }); + var qjs = FlutterQjs( + methodHandler: methodHandler, + moduleHandler: (name) { + var ptr = allocate>(); + ptr.value = Pointer.fromAddress(0); + sendPort.send({ + 'type': 'module', + 'name': name, + 'ptr': ptr.address, + }); + while (ptr.value.address == 0) sleep(Duration.zero); + if (ptr.value.address == -1) throw Exception("Module Not found"); + var ret = Utf8.fromUtf8(ptr.value); + sendPort.send({ + 'type': 'release', + 'ptr': ptr.value.address, + }); + free(ptr); + return ret; + }, + ); qjs.dispatch(); await for (var msg in port) { var data; @@ -160,6 +187,7 @@ void _runJsIsolate(Map spawnMessage) async { ).invoke(_decodeData(msg['args'], null)); break; case 'close': + qjs.port.close(); qjs.close(); port.close(); break; @@ -182,12 +210,15 @@ typedef JsIsolateSpawn = void Function(SendPort sendPort); class IsolateQjs { Future _sendPort; - JsMethodHandler _methodHandler; - JsAsyncModuleHandler _moduleHandler; /// Set a handler to manage js call with `channel(method, args)` function. /// The function must be a top-level function or a static method - IsolateQjs(this._methodHandler); + JsMethodHandler methodHandler; + + /// Set a handler to manage js module. + JsAsyncModuleHandler moduleHandler; + + IsolateQjs({this.methodHandler, this.moduleHandler}); _ensureEngine() { if (_sendPort != null) return; @@ -196,7 +227,7 @@ class IsolateQjs { _runJsIsolate, { 'port': port.sendPort, - 'handler': _methodHandler, + 'handler': methodHandler, }, errorsAreFatal: true, ); @@ -210,7 +241,7 @@ class IsolateQjs { case 'module': var ptr = Pointer.fromAddress(msg['ptr']); try { - ptr.value = Utf8.toUtf8(await _moduleHandler(msg['name'])); + ptr.value = Utf8.toUtf8(await moduleHandler(msg['name'])); } catch (e) { ptr.value = Pointer.fromAddress(-1); } @@ -226,11 +257,6 @@ class IsolateQjs { _sendPort = completer.future; } - /// Set a handler to manage js module. - setModuleHandler(JsAsyncModuleHandler handler) { - _moduleHandler = handler; - } - close() { if (_sendPort == null) return; _sendPort.then((sendPort) { @@ -253,8 +279,9 @@ class IsolateQjs { 'port': evaluatePort.sendPort, }); var result = await evaluatePort.first; - if (result['error'] == null) + if (result['error'] == null){ return _decodeData(result['data'], sendPort); + } else throw result['error']; } diff --git a/lib/wrapper.dart b/lib/wrapper.dart index df92656..d9b8ff8 100644 --- a/lib/wrapper.dart +++ b/lib/wrapper.dart @@ -130,8 +130,8 @@ Pointer dartToJs(Pointer ctx, dynamic val, {Map cache}) { var ret = jsNewPromiseCapability(ctx, resolvingFunc); var res = jsToDart(ctx, resolvingFunc); var rej = jsToDart(ctx, resolvingFunc2); - jsFreeValue(ctx, resolvingFunc); - jsFreeValue(ctx, resolvingFunc2); + jsFreeValue(ctx, resolvingFunc, free: false); + jsFreeValue(ctx, resolvingFunc2, free: false); free(resolvingFunc); val.then((value) { res(value); @@ -225,10 +225,12 @@ 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[jsGetRuntime(ctx)]?.promiseToFuture(val); } else if (jsIsArray(ctx, val) != 0) { Pointer jslength = jsGetPropertyStr(ctx, val, "length"); int length = jsToInt64(ctx, jslength); - List ret = List(); + List ret = []; cache[valptr] = ret; for (int i = 0; i < length; ++i) { var jsAtomVal = jsNewInt64(ctx, i); @@ -274,7 +276,8 @@ Pointer jsNewContextWithPromsieWrapper(Pointer rt) { ctx, """ (value) => { - const __ret = Promise.resolve(value) + const __ret = {}; + Promise.resolve(value) .then(v => { __ret.__value = v; __ret.__resolved = true; @@ -286,10 +289,10 @@ Pointer jsNewContextWithPromsieWrapper(Pointer rt) { } """, "", - JSEvalType.GLOBAL); + JSEvalFlag.GLOBAL); var promiseWrapper = JSRefValue(ctx, jsPromiseWrapper); jsFreeValue(ctx, jsPromiseWrapper); - runtimeOpaques[rt].promsieToFuture = (promise) { + runtimeOpaques[rt].promiseToFuture = (promise) { var completer = Completer(); var wrapper = promiseWrapper.val; if (wrapper == null) diff --git a/pubspec.yaml b/pubspec.yaml index aaa237b..ec291fe 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.1.4 +version: 0.2.0 homepage: https://github.com/ekibun/flutter_qjs environment: diff --git a/test/flutter_qjs_test.dart b/test/flutter_qjs_test.dart index 48eff8c..6ba709b 100644 --- a/test/flutter_qjs_test.dart +++ b/test/flutter_qjs_test.dart @@ -5,9 +5,11 @@ * @LastEditors: ekibun * @LastEditTime: 2020-10-07 00:11:27 */ +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_test/flutter_test.dart'; @@ -16,6 +18,30 @@ dynamic myMethodHandler(method, args) { return args; } +Future testEvaluate(qjs) async { + var value = await qjs.evaluate(""" + const a = {}; + a.a = a; + import('test').then((module) => channel('channel', [ + (...args)=>`hello \${args}!`, a, + Promise.reject('test Promise.reject'), Promise.resolve('test Promise.resolve'), + 0.1, true, false, 1, "world", module + ])); + """, name: ""); + expect(await value[0]('world'), 'hello world!', reason: "js function call"); + expect(value[1]['a'], value[1], reason: "recursive object"); + expect(value[2], isInstanceOf(), reason: "promise object"); + try { + await value[2]; + throw 'Future not reject'; + } catch (e) { + expect(e, startsWith('test Promise.reject\n'), + reason: "promise object reject"); + } + expect(await value[3], 'test Promise.resolve', + reason: "promise object resolve"); +} + void main() async { test('make.windows', () async { final utf8Encoding = Encoding.getByName('utf-8'); @@ -54,27 +80,56 @@ void main() async { stderr.write(result.stderr); expect(result.exitCode, 0); }, testOn: 'mac-os'); - test('jsToDart', () async { - final qjs = IsolateQjs(myMethodHandler); - qjs.setModuleHandler((name) async { - return "export default '${new DateTime.now()}'"; - }); - var value = await qjs.evaluate(""" - const a = {}; - a.a = a; - import("test").then((module) => channel('channel', [ - (...args)=>`hello \${args}!`, a, - 0.1, true, false, 1, "world", module - ])); - """, name: ""); - expect(value[1]['a'], value[1], reason: "recursive object"); - expect(await value[0]('world'), 'hello world!', reason: "js function call"); + test('module', () async { + final qjs = FlutterQjs( + moduleHandler: (name) { + return "export default 'test module'"; + }, + ); + qjs.dispatch(); + qjs.evaluate(''' + import handlerData from 'test'; + export default { + data: handlerData + }; + ''', name: 'evalModule', evalFlags: JSEvalFlag.MODULE); + var result = await qjs.evaluate('import("evalModule")'); + expect(result['default']['data'], 'test module', reason: "eval module"); qjs.close(); }); + test('jsToDart', () async { + await runZonedGuarded(() async { + final qjs = FlutterQjs( + methodHandler: myMethodHandler, + moduleHandler: (name) { + return "export default '${new DateTime.now()}'"; + }, + ); + qjs.dispatch(); + await testEvaluate(qjs); + qjs.close(); + }, (e, stack) { + if (e is TestFailure) throw e; + }); + }); + test('isolate', () async { + await runZonedGuarded(() async { + final qjs = IsolateQjs( + methodHandler: myMethodHandler, + moduleHandler: (name) async { + return "export default '${new DateTime.now()}'"; + }, + ); + await testEvaluate(qjs); + qjs.close(); + }, (e, stack) { + if (e is TestFailure) throw e; + }); + }); test('stack overflow', () async { final qjs = FlutterQjs(); try { - await qjs.evaluate("a=()=>a();a();", name: ""); + qjs.evaluate("a=()=>a();a();", name: ""); } catch (e) { expect( e.toString(), startsWith('Exception: InternalError: stack overflow'),