This commit is contained in:
ekibun
2021-01-02 00:57:52 +08:00
parent d44af3d093
commit 045277dbe3
13 changed files with 278 additions and 185 deletions

View File

@@ -6,6 +6,12 @@
* @LastEditTime: 2020-12-02 11:36:40 * @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 ## 0.1.4
* fix crash on android x86. * fix crash on android x86.

View File

@@ -20,7 +20,7 @@ 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: A global function `channel` is presented to invoke dart function. Data conversion between dart and js are implemented as follow:
| dart | js | | dart | js |
| --- | --- | | --------------------------------------------------- | ------------------ |
| Bool | boolean | | Bool | boolean |
| Int | number | | Int | number |
| Double | number | | Double | number |
@@ -28,75 +28,70 @@ A global function `channel` is presented to invoke dart function. Data conversio
| Uint8List | ArrayBuffer | | Uint8List | ArrayBuffer |
| List | Array | | List | Array |
| Map | Object | | Map | Object |
| JSFunction | function(....args) | | JSFunction(...args) <br> IsolateJSFunction(...args) | function(....args) |
| Future | Promise | | 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 ## Getting Started
### Run on main thread ### 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 ```dart
final engine = FlutterQjs(); final engine = FlutterQjs(
await engine.dispatch(); methodHandler: (String method, List arg) {
```
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) { switch (method) {
case "http": case "http":
return Dio().get(arg[0]).then((response) => response.data); return Dio().get(arg[0]).then((response) => response.data);
default: default:
throw Exception("No such method"); throw Exception("No such method");
} }
}); },
moduleHandler: (String module) {
if(module == "hello")
return "export default (name) => `hello \${name}!`;";
throw Exception("Module Not found");
},
);
``` ```
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 ```javascript
channel("http", ["http://example.com/"]); channel("http", ["http://example.com/"]);
``` ```
3. Call `setModuleHandler` to resolve the js module. `import` function is used to get modules:
~~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:
```javascript ```javascript
import("hello").then(({default: greet}) => greet("world")); 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 ```dart
try { try {
print(await engine.evaluate(code ?? '', "<eval>")); print(engine.evaluate(code ?? ''));
} catch (e) { } catch (e) {
print(e.toString()); 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 ### 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 ```dart
dynamic methodHandler(String method, List arg) { dynamic methodHandler(String method, List arg) {
@@ -107,32 +102,17 @@ dynamic methodHandler(String method, List arg) {
throw Exception("No such method"); 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(); // not need engine.dispatch();
``` ```
and in javascript, call `channel` function to get data, make sure the second parameter is a list: 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:
```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:
```dart ```dart
try { 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. [This example](example/lib/main.dart) contains a complete demonstration on how to use this plugin.

View File

@@ -154,15 +154,17 @@ extern "C"
return new JSValue(JS_NewObject(ctx)); 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); JS_FreeValue(ctx, *v);
if (free)
delete v; delete v;
} }
DLLEXPORT void jsFreeValueRT(JSRuntime *rt, JSValue *v) DLLEXPORT void jsFreeValueRT(JSRuntime *rt, JSValue *v, int32_t free)
{ {
JS_FreeValueRT(rt, *v); JS_FreeValueRT(rt, *v);
if (free)
delete v; delete v;
} }
@@ -215,6 +217,11 @@ extern "C"
return JS_IsFunction(ctx, *val); 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) DLLEXPORT int32_t jsIsArray(JSContext *ctx, JSValueConst *val)
{ {
return JS_IsArray(ctx, *val); return JS_IsArray(ctx, *val);

View File

@@ -51,9 +51,9 @@ extern "C"
DLLEXPORT JSValue *jsNewObject(JSContext *ctx); 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); DLLEXPORT JSValue *jsDupValue(JSContext *ctx, JSValueConst *v);
@@ -73,6 +73,8 @@ extern "C"
DLLEXPORT int32_t jsIsFunction(JSContext *ctx, JSValueConst *val); 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 int32_t jsIsArray(JSContext *ctx, JSValueConst *val);
DLLEXPORT JSValue *jsGetProperty(JSContext *ctx, JSValueConst *this_obj, DLLEXPORT JSValue *jsGetProperty(JSContext *ctx, JSValueConst *this_obj,

View File

@@ -75,12 +75,14 @@ class _TestPageState extends State<TestPage> {
_ensureEngine() { _ensureEngine() {
if (engine != null) return; if (engine != null) return;
engine = IsolateQjs(methodHandler); engine = IsolateQjs(
engine.setModuleHandler((String module) async { methodHandler: methodHandler,
moduleHandler: (String module) async {
if (module == "test") return "export default '${new DateTime.now()}'"; if (module == "test") return "export default '${new DateTime.now()}'";
return await rootBundle.loadString( return await rootBundle.loadString(
"js/" + module.replaceFirst(new RegExp(r".js$"), "") + ".js"); "js/" + module.replaceFirst(new RegExp(r".js$"), "") + ".js");
}); },
);
} }
@override @override

View File

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

View File

@@ -16,7 +16,7 @@ abstract class JSRef {
} }
/// JS_Eval() flags /// JS_Eval() flags
class JSEvalType { class JSEvalFlag {
static const GLOBAL = 0 << 0; static const GLOBAL = 0 << 0;
static const MODULE = 1 << 0; static const MODULE = 1 << 0;
} }
@@ -110,9 +110,9 @@ typedef JSChannel = Pointer Function(Pointer ctx, Pointer method, Pointer argv);
class RuntimeOpaque { class RuntimeOpaque {
JSChannel channel; JSChannel channel;
List<JSRef> ref = List(); List<JSRef> ref = [];
ReceivePort port; ReceivePort port;
Future Function(Pointer) promsieToFuture; Future Function(Pointer) promiseToFuture;
} }
final Map<Pointer, RuntimeOpaque> runtimeOpaques = Map(); final Map<Pointer, RuntimeOpaque> runtimeOpaques = Map();
@@ -356,32 +356,52 @@ final Pointer Function(
)>>("jsNewObject") )>>("jsNewObject")
.asFunction(); .asFunction();
/// void jsFreeValue(JSContext *ctx, JSValue *val) /// void jsFreeValue(JSContext *ctx, JSValue *val, int32_t free)
final void Function( final void Function(
Pointer ctx, Pointer ctx,
Pointer val, Pointer val,
) jsFreeValue = qjsLib int free,
) _jsFreeValue = qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Void Function( Void Function(
Pointer, Pointer,
Pointer, Pointer,
Int32,
)>>("jsFreeValue") )>>("jsFreeValue")
.asFunction(); .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( final void Function(
Pointer rt, Pointer rt,
Pointer val, Pointer val,
) jsFreeValueRT = qjsLib int free,
) _jsFreeValueRT = qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Void Function( Void Function(
Pointer, Pointer,
Pointer, Pointer,
Int32,
)>>("jsFreeValueRT") )>>("jsFreeValueRT")
.asFunction(); .asFunction();
void jsFreeValueRT(
Pointer rt,
Pointer val, {
bool free = true,
}) {
_jsFreeValueRT(rt, val, free ? 1 : 0);
}
/// JSValue *jsDupValue(JSContext *ctx, JSValueConst *v) /// JSValue *jsDupValue(JSContext *ctx, JSValueConst *v)
final Pointer Function( final Pointer Function(
Pointer ctx, Pointer ctx,
@@ -512,6 +532,19 @@ final int Function(
)>>("jsIsFunction") )>>("jsIsFunction")
.asFunction(); .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) /// int32_t jsIsArray(JSContext *ctx, JSValueConst *val)
final int Function( final int Function(
Pointer ctx, Pointer ctx,

View File

@@ -23,9 +23,15 @@ class FlutterQjs {
Pointer _rt; Pointer _rt;
Pointer _ctx; Pointer _ctx;
ReceivePort port = ReceivePort(); ReceivePort port = ReceivePort();
/// Set a handler to manage js call with `channel(method, args)` function.
JsMethodHandler methodHandler; JsMethodHandler methodHandler;
/// Set a handler to manage js module.
JsModuleHandler moduleHandler; JsModuleHandler moduleHandler;
FlutterQjs({this.methodHandler, this.moduleHandler});
_ensureEngine() { _ensureEngine() {
if (_rt != null) return; if (_rt != null) return;
_rt = jsNewRuntime((ctx, method, argv) { _rt = jsNewRuntime((ctx, method, argv) {
@@ -61,18 +67,8 @@ class FlutterQjs {
_ctx = jsNewContextWithPromsieWrapper(_rt); _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. /// Free Runtime and Context which can be recreate when evaluate again.
recreate() { close() {
if (_rt != null) { if (_rt != null) {
jsFreeContext(_ctx); jsFreeContext(_ctx);
jsFreeRuntime(_rt); jsFreeRuntime(_rt);
@@ -81,18 +77,10 @@ class FlutterQjs {
_ctx = null; _ctx = null;
} }
/// Close ReceivePort.
close() {
if (port != null) {
port.close();
recreate();
}
port = null;
}
/// DispatchMessage /// DispatchMessage
Future<void> dispatch() async { Future<void> dispatch() async {
await for (var _ in port) { await for (var _ in port) {
if (_rt == null) continue;
while (true) { while (true) {
int err = jsExecutePendingJob(_rt); int err = jsExecutePendingJob(_rt);
if (err <= 0) { if (err <= 0) {
@@ -116,24 +104,14 @@ class FlutterQjs {
} }
/// Evaluate js script. /// Evaluate js script.
Future<dynamic> evaluate(String command, {String name, int evalFlags}) async { dynamic evaluate(String command, {String name, int evalFlags}) {
_ensureEngine(); _ensureEngine();
var jsval = var jsval = jsEval(
jsEval(_ctx, command, name ?? "<eval>", evalFlags ?? JSEvalType.GLOBAL); _ctx,
if (jsIsException(jsval) != 0) { command,
jsFreeValue(_ctx, jsval); name ?? "<eval>",
throw Exception(parseJSException(_ctx)); evalFlags ?? JSEvalFlag.GLOBAL,
} );
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 ?? "<eval>", evalFlags ?? JSEvalType.GLOBAL);
if (jsIsException(jsval) != 0) { if (jsIsException(jsval) != 0) {
jsFreeValue(_ctx, jsval); jsFreeValue(_ctx, jsval);
throw Exception(parseJSException(_ctx)); throw Exception(parseJSException(_ctx));

View File

@@ -76,8 +76,20 @@ dynamic _encodeData(dynamic data, {Map<dynamic, dynamic> cache}) {
}; };
} }
if (data is Future) { if (data is Future) {
// Not support var futurePort = ReceivePort();
return {}; 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; return data;
} }
@@ -104,6 +116,20 @@ dynamic _decodeData(dynamic data, SendPort port,
return JSFunction.fromAddress(ctx, val); 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 = {}; var ret = {};
cache[data] = ret; cache[data] = ret;
for (var entry in data.entries) { for (var entry in data.entries) {
@@ -116,13 +142,13 @@ dynamic _decodeData(dynamic data, SendPort port,
} }
void _runJsIsolate(Map spawnMessage) async { void _runJsIsolate(Map spawnMessage) async {
var qjs = FlutterQjs();
SendPort sendPort = spawnMessage['port']; SendPort sendPort = spawnMessage['port'];
JsMethodHandler methodHandler = spawnMessage['handler']; JsMethodHandler methodHandler = spawnMessage['handler'];
ReceivePort port = ReceivePort(); ReceivePort port = ReceivePort();
sendPort.send(port.sendPort); sendPort.send(port.sendPort);
qjs.setMethodHandler(methodHandler); var qjs = FlutterQjs(
qjs.setModuleHandler((name) { methodHandler: methodHandler,
moduleHandler: (name) {
var ptr = allocate<Pointer<Utf8>>(); var ptr = allocate<Pointer<Utf8>>();
ptr.value = Pointer.fromAddress(0); ptr.value = Pointer.fromAddress(0);
sendPort.send({ sendPort.send({
@@ -139,7 +165,8 @@ void _runJsIsolate(Map spawnMessage) async {
}); });
free(ptr); free(ptr);
return ret; return ret;
}); },
);
qjs.dispatch(); qjs.dispatch();
await for (var msg in port) { await for (var msg in port) {
var data; var data;
@@ -160,6 +187,7 @@ void _runJsIsolate(Map spawnMessage) async {
).invoke(_decodeData(msg['args'], null)); ).invoke(_decodeData(msg['args'], null));
break; break;
case 'close': case 'close':
qjs.port.close();
qjs.close(); qjs.close();
port.close(); port.close();
break; break;
@@ -182,12 +210,15 @@ typedef JsIsolateSpawn = void Function(SendPort sendPort);
class IsolateQjs { class IsolateQjs {
Future<SendPort> _sendPort; Future<SendPort> _sendPort;
JsMethodHandler _methodHandler;
JsAsyncModuleHandler _moduleHandler;
/// Set a handler to manage js call with `channel(method, args)` function. /// Set a handler to manage js call with `channel(method, args)` function.
/// The function must be a top-level function or a static method /// 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() { _ensureEngine() {
if (_sendPort != null) return; if (_sendPort != null) return;
@@ -196,7 +227,7 @@ class IsolateQjs {
_runJsIsolate, _runJsIsolate,
{ {
'port': port.sendPort, 'port': port.sendPort,
'handler': _methodHandler, 'handler': methodHandler,
}, },
errorsAreFatal: true, errorsAreFatal: true,
); );
@@ -210,7 +241,7 @@ class IsolateQjs {
case 'module': case 'module':
var ptr = Pointer<Pointer>.fromAddress(msg['ptr']); var ptr = Pointer<Pointer>.fromAddress(msg['ptr']);
try { try {
ptr.value = Utf8.toUtf8(await _moduleHandler(msg['name'])); ptr.value = Utf8.toUtf8(await moduleHandler(msg['name']));
} catch (e) { } catch (e) {
ptr.value = Pointer.fromAddress(-1); ptr.value = Pointer.fromAddress(-1);
} }
@@ -226,11 +257,6 @@ class IsolateQjs {
_sendPort = completer.future; _sendPort = completer.future;
} }
/// Set a handler to manage js module.
setModuleHandler(JsAsyncModuleHandler handler) {
_moduleHandler = handler;
}
close() { close() {
if (_sendPort == null) return; if (_sendPort == null) return;
_sendPort.then((sendPort) { _sendPort.then((sendPort) {
@@ -253,8 +279,9 @@ class IsolateQjs {
'port': evaluatePort.sendPort, 'port': evaluatePort.sendPort,
}); });
var result = await evaluatePort.first; var result = await evaluatePort.first;
if (result['error'] == null) if (result['error'] == null){
return _decodeData(result['data'], sendPort); return _decodeData(result['data'], sendPort);
}
else else
throw result['error']; throw result['error'];
} }

View File

@@ -130,8 +130,8 @@ Pointer dartToJs(Pointer ctx, dynamic val, {Map<dynamic, dynamic> cache}) {
var ret = jsNewPromiseCapability(ctx, resolvingFunc); var ret = jsNewPromiseCapability(ctx, resolvingFunc);
var res = jsToDart(ctx, resolvingFunc); var res = jsToDart(ctx, resolvingFunc);
var rej = jsToDart(ctx, resolvingFunc2); var rej = jsToDart(ctx, resolvingFunc2);
jsFreeValue(ctx, resolvingFunc); jsFreeValue(ctx, resolvingFunc, free: false);
jsFreeValue(ctx, resolvingFunc2); jsFreeValue(ctx, resolvingFunc2, free: false);
free(resolvingFunc); free(resolvingFunc);
val.then((value) { val.then((value) {
res(value); res(value);
@@ -225,10 +225,12 @@ dynamic jsToDart(Pointer ctx, Pointer val, {Map<int, dynamic> cache}) {
} }
if (jsIsFunction(ctx, val) != 0) { if (jsIsFunction(ctx, val) != 0) {
return JSFunction(ctx, val); return JSFunction(ctx, val);
} else if (jsIsPromise(ctx, val) != 0) {
return runtimeOpaques[jsGetRuntime(ctx)]?.promiseToFuture(val);
} else if (jsIsArray(ctx, val) != 0) { } else if (jsIsArray(ctx, val) != 0) {
Pointer jslength = jsGetPropertyStr(ctx, val, "length"); Pointer jslength = jsGetPropertyStr(ctx, val, "length");
int length = jsToInt64(ctx, jslength); int length = jsToInt64(ctx, jslength);
List<dynamic> ret = List(); List<dynamic> ret = [];
cache[valptr] = ret; cache[valptr] = ret;
for (int i = 0; i < length; ++i) { for (int i = 0; i < length; ++i) {
var jsAtomVal = jsNewInt64(ctx, i); var jsAtomVal = jsNewInt64(ctx, i);
@@ -274,7 +276,8 @@ Pointer jsNewContextWithPromsieWrapper(Pointer rt) {
ctx, ctx,
""" """
(value) => { (value) => {
const __ret = Promise.resolve(value) const __ret = {};
Promise.resolve(value)
.then(v => { .then(v => {
__ret.__value = v; __ret.__value = v;
__ret.__resolved = true; __ret.__resolved = true;
@@ -286,10 +289,10 @@ Pointer jsNewContextWithPromsieWrapper(Pointer rt) {
} }
""", """,
"<future>", "<future>",
JSEvalType.GLOBAL); JSEvalFlag.GLOBAL);
var promiseWrapper = JSRefValue(ctx, jsPromiseWrapper); var promiseWrapper = JSRefValue(ctx, jsPromiseWrapper);
jsFreeValue(ctx, jsPromiseWrapper); jsFreeValue(ctx, jsPromiseWrapper);
runtimeOpaques[rt].promsieToFuture = (promise) { runtimeOpaques[rt].promiseToFuture = (promise) {
var completer = Completer(); var completer = Completer();
var wrapper = promiseWrapper.val; var wrapper = promiseWrapper.val;
if (wrapper == null) if (wrapper == null)

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.1.4 version: 0.2.0
homepage: https://github.com/ekibun/flutter_qjs homepage: https://github.com/ekibun/flutter_qjs
environment: environment:

View File

@@ -5,9 +5,11 @@
* @LastEditors: ekibun * @LastEditors: ekibun
* @LastEditTime: 2020-10-07 00:11:27 * @LastEditTime: 2020-10-07 00:11:27
*/ */
import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:flutter_qjs/ffi.dart';
import 'package:flutter_qjs/flutter_qjs.dart'; 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';
@@ -16,6 +18,30 @@ dynamic myMethodHandler(method, args) {
return 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: "<eval>");
expect(await value[0]('world'), 'hello world!', reason: "js function call");
expect(value[1]['a'], value[1], reason: "recursive object");
expect(value[2], isInstanceOf<Future>(), 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 { void main() async {
test('make.windows', () async { test('make.windows', () async {
final utf8Encoding = Encoding.getByName('utf-8'); final utf8Encoding = Encoding.getByName('utf-8');
@@ -54,27 +80,56 @@ void main() async {
stderr.write(result.stderr); stderr.write(result.stderr);
expect(result.exitCode, 0); expect(result.exitCode, 0);
}, testOn: 'mac-os'); }, testOn: 'mac-os');
test('jsToDart', () async { test('module', () async {
final qjs = IsolateQjs(myMethodHandler); final qjs = FlutterQjs(
qjs.setModuleHandler((name) async { moduleHandler: (name) {
return "export default '${new DateTime.now()}'"; return "export default 'test module'";
}); },
var value = await qjs.evaluate(""" );
const a = {}; qjs.dispatch();
a.a = a; qjs.evaluate('''
import("test").then((module) => channel('channel', [ import handlerData from 'test';
(...args)=>`hello \${args}!`, a, export default {
0.1, true, false, 1, "world", module data: handlerData
])); };
""", name: "<eval>"); ''', name: 'evalModule', evalFlags: JSEvalFlag.MODULE);
expect(value[1]['a'], value[1], reason: "recursive object"); var result = await qjs.evaluate('import("evalModule")');
expect(await value[0]('world'), 'hello world!', reason: "js function call"); expect(result['default']['data'], 'test module', reason: "eval module");
qjs.close(); 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 { test('stack overflow', () async {
final qjs = FlutterQjs(); final qjs = FlutterQjs();
try { try {
await qjs.evaluate("a=()=>a();a();", name: "<eval>"); qjs.evaluate("a=()=>a();a();", name: "<eval>");
} catch (e) { } catch (e) {
expect( expect(
e.toString(), startsWith('Exception: InternalError: stack overflow'), e.toString(), startsWith('Exception: InternalError: stack overflow'),