diff --git a/CHANGELOG.md b/CHANGELOG.md
index cf714c4..142246e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,11 @@
* @LastEditTime: 2020-12-02 11:36:40
-->
+## 0.2.4
+
+* wrap dart object to js.
+* fix stack overflow when use jsCall nesting.
+
## 0.2.3
* fix compiler error in windows release.
diff --git a/README.md b/README.md
index ca015a1..b238633 100644
--- a/README.md
+++ b/README.md
@@ -53,8 +53,9 @@ Data conversion between dart and js are implemented as follow:
| Map | Object |
| JSFunction(...args)
IsolateJSFunction(...args) | function(....args) |
| Future | Promise |
+| Object | DartObject |
-**notice:** `function` can only be sent from js to dart.
+**notice:** `function` can only be sent from js to dart. `DartObject` can only be used in `moduleHandler`.
### Invoke dart function
diff --git a/cxx/ffi.cpp b/cxx/ffi.cpp
index 1d1ff7e..c9843de 100644
--- a/cxx/ffi.cpp
+++ b/cxx/ffi.cpp
@@ -105,8 +105,11 @@ extern "C"
DLLEXPORT JSValue *jsEval(JSContext *ctx, const char *input, size_t input_len, const char *filename, int32_t eval_flags)
{
- JS_ResetStackTop(JS_GetRuntime(ctx));
- return new JSValue(JS_Eval(ctx, input, input_len, filename, eval_flags));
+ JSRuntime *rt = JS_GetRuntime(ctx);
+ uint8_t *stack_top = JS_SetStackTop(rt, 0);
+ JSValue *ret = new JSValue(JS_Eval(ctx, input, input_len, filename, eval_flags));
+ JS_SetStackTop(rt, stack_top);
+ return ret;
}
DLLEXPORT int32_t jsValueGetTag(JSValue *val)
@@ -283,8 +286,11 @@ extern "C"
DLLEXPORT JSValue *jsCall(JSContext *ctx, JSValueConst *func_obj, JSValueConst *this_obj,
int32_t argc, JSValueConst *argv)
{
- JS_ResetStackTop(JS_GetRuntime(ctx));
- return new JSValue(JS_Call(ctx, *func_obj, *this_obj, argc, argv));
+ JSRuntime *rt = JS_GetRuntime(ctx);
+ uint8_t *stack_top = JS_SetStackTop(rt, 0);
+ JSValue *ret = new JSValue(JS_Call(ctx, *func_obj, *this_obj, argc, argv));
+ JS_SetStackTop(rt, stack_top);
+ return ret;
}
DLLEXPORT int32_t jsIsException(JSValueConst *val)
@@ -299,9 +305,11 @@ extern "C"
DLLEXPORT int32_t jsExecutePendingJob(JSRuntime *rt)
{
- JS_ResetStackTop(rt);
+ uint8_t *stack_top = JS_SetStackTop(rt, 0);
JSContext *ctx;
- return JS_ExecutePendingJob(rt, &ctx);
+ int ret = JS_ExecutePendingJob(rt, &ctx);
+ JS_SetStackTop(rt, stack_top);
+ return ret;
}
DLLEXPORT JSValue *jsNewPromiseCapability(JSContext *ctx, JSValue *resolving_funcs)
diff --git a/cxx/quickjs b/cxx/quickjs
index 16e640b..9ac8741 160000
--- a/cxx/quickjs
+++ b/cxx/quickjs
@@ -1 +1 @@
-Subproject commit 16e640b3dcfe8a4fb1845b9e961b8e42bf56af81
+Subproject commit 9ac874168b360f760459d3af51d33946279cce9b
diff --git a/example/pubspec.lock b/example/pubspec.lock
index fd9c5e9..6752578 100644
--- a/example/pubspec.lock
+++ b/example/pubspec.lock
@@ -82,7 +82,7 @@ packages:
path: ".."
relative: true
source: path
- version: "0.2.3"
+ version: "0.2.4"
flutter_test:
dependency: "direct dev"
description: flutter
diff --git a/lib/ffi.dart b/lib/ffi.dart
index 25c602f..bedb673 100644
--- a/lib/ffi.dart
+++ b/lib/ffi.dart
@@ -113,6 +113,7 @@ class RuntimeOpaque {
List ref = [];
ReceivePort port;
Future Function(Pointer) promiseToFuture;
+ Pointer Function(Object) objectWrapper;
}
final Map runtimeOpaques = Map();
diff --git a/lib/wrapper.dart b/lib/wrapper.dart
index d9b8ff8..ba8e054 100644
--- a/lib/wrapper.dart
+++ b/lib/wrapper.dart
@@ -37,6 +37,26 @@ class JSRefValue implements JSRef {
}
}
+class DartObject implements JSRef {
+ Object obj;
+ Pointer ctx;
+ DartObject(this.ctx, this.obj) {
+ runtimeOpaques[jsGetRuntime(ctx)]?.ref?.add(this);
+ }
+
+ static DartObject fromAddress(Pointer ctx, int val) {
+ return runtimeOpaques[jsGetRuntime(ctx)]?.ref?.firstWhere(
+ (e) => identityHashCode(e) == val,
+ orElse: null,
+ );
+ }
+
+ @override
+ void release() {
+ ctx = null;
+ }
+}
+
class JSPromise extends JSRefValue {
Completer completer;
JSPromise(Pointer ctx, Pointer val, this.completer) : super(ctx, val);
@@ -123,6 +143,7 @@ String parseJSException(Pointer ctx, {Pointer e}) {
}
Pointer dartToJs(Pointer ctx, dynamic val, {Map cache}) {
+ if (val == null) return jsUNDEFINED();
if (val is Future) {
var resolvingFunc = allocate(count: sizeOfJSValue * 2);
var resolvingFunc2 =
@@ -195,7 +216,7 @@ Pointer dartToJs(Pointer ctx, dynamic val, {Map cache}) {
}
return ret;
}
- return jsUNDEFINED();
+ return runtimeOpaques[jsGetRuntime(ctx)]?.objectWrapper(val) ?? jsUNDEFINED();
}
dynamic jsToDart(Pointer ctx, Pointer val, {Map cache}) {
@@ -262,6 +283,10 @@ dynamic jsToDart(Pointer ctx, Pointer val, {Map cache}) {
}
jsFree(ctx, ptab.value);
free(ptab);
+ final objHash = ret["__dart_obj_hash__"];
+ if (objHash is int) {
+ return DartObject.fromAddress(ctx, objHash)?.obj ?? ret;
+ }
return ret;
}
break;
@@ -272,6 +297,9 @@ dynamic jsToDart(Pointer ctx, Pointer val, {Map cache}) {
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,
"""
@@ -290,17 +318,42 @@ Pointer jsNewContextWithPromsieWrapper(Pointer rt) {
""",
"",
JSEvalFlag.GLOBAL);
- var promiseWrapper = JSRefValue(ctx, jsPromiseWrapper);
+ var jsObjectWrapper = jsEval(
+ ctx,
+ """
+ (objHash, type) => {
+ const ret = {
+ "__dart_obj_hash__": objHash,
+ };
+ ret.__proto__.toString = ()=> "" + type;
+ return ret;
+ };
+ """,
+ "",
+ JSEvalFlag.GLOBAL);
+ final promiseWrapper = JSRefValue(ctx, jsPromiseWrapper);
jsFreeValue(ctx, jsPromiseWrapper);
- runtimeOpaques[rt].promiseToFuture = (promise) {
+ final objectWrapper = JSRefValue(ctx, jsObjectWrapper);
+ jsFreeValue(ctx, jsObjectWrapper);
+ runtimeOpaque.promiseToFuture = (promise) {
var completer = Completer();
var wrapper = promiseWrapper.val;
if (wrapper == null)
completer.completeError(Exception("Runtime has been released!"));
var jsPromise = jsCall(ctx, wrapper, null, [promise]);
- runtimeOpaques[rt].ref.add(JSPromise(ctx, jsPromise, completer));
+ var wrapPromise = JSPromise(ctx, jsPromise, completer);
jsFreeValue(ctx, jsPromise);
- return completer.future;
+ return wrapPromise.completer.future;
+ };
+ runtimeOpaque.objectWrapper = (obj) {
+ var jsObjHash = jsNewInt64(ctx, identityHashCode(DartObject(ctx, obj)));
+ var jsTypeString = jsNewString(ctx, obj.toString());
+ var wrapper = objectWrapper.val;
+ if (wrapper == null) throw Exception("Runtime has been released!");
+ var ret = jsCall(ctx, wrapper, null, [jsObjHash, jsTypeString]);
+ jsFreeValue(ctx, jsObjHash);
+ jsFreeValue(ctx, jsTypeString);
+ return ret;
};
return ctx;
diff --git a/pubspec.yaml b/pubspec.yaml
index c5687ac..cc92630 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.2.3
+version: 0.2.4
homepage: https://github.com/ekibun/flutter_qjs
environment:
diff --git a/test/flutter_qjs_test.dart b/test/flutter_qjs_test.dart
index 6ba709b..dbb6292 100644
--- a/test/flutter_qjs_test.dart
+++ b/test/flutter_qjs_test.dart
@@ -98,19 +98,15 @@ void main() async {
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;
- });
+ final qjs = FlutterQjs(
+ methodHandler: myMethodHandler,
+ moduleHandler: (name) {
+ return "export default '${new DateTime.now()}'";
+ },
+ );
+ qjs.dispatch();
+ await testEvaluate(qjs);
+ qjs.close();
});
test('isolate', () async {
await runZonedGuarded(() async {
@@ -123,9 +119,20 @@ void main() async {
await testEvaluate(qjs);
qjs.close();
}, (e, stack) {
- if (e is TestFailure) throw e;
+ if (!e.toString().startsWith("test Promise.reject")) throw e;
});
});
+ test('dart object', () async {
+ final qjs = FlutterQjs(
+ methodHandler: (method, args) {
+ return FlutterQjs();
+ },
+ );
+ qjs.dispatch();
+ var value = await qjs.evaluate("channel('channel', [])", name: "");
+ expect(value, isInstanceOf(), reason: "dart object");
+ qjs.close();
+ });
test('stack overflow', () async {
final qjs = FlutterQjs();
try {