wrap dart object to js.

This commit is contained in:
ekibun
2021-01-18 02:02:12 +08:00
parent 8e950c5b14
commit 7de32aac0b
9 changed files with 104 additions and 29 deletions

View File

@@ -6,6 +6,11 @@
* @LastEditTime: 2020-12-02 11:36:40 * @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 ## 0.2.3
* fix compiler error in windows release. * fix compiler error in windows release.

View File

@@ -53,8 +53,9 @@ Data conversion between dart and js are implemented as follow:
| Map | Object | | Map | Object |
| JSFunction(...args) <br> IsolateJSFunction(...args) | function(....args) | | JSFunction(...args) <br> IsolateJSFunction(...args) | function(....args) |
| Future | Promise | | 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 ### Invoke dart function

View File

@@ -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) DLLEXPORT JSValue *jsEval(JSContext *ctx, const char *input, size_t input_len, const char *filename, int32_t eval_flags)
{ {
JS_ResetStackTop(JS_GetRuntime(ctx)); JSRuntime *rt = JS_GetRuntime(ctx);
return new JSValue(JS_Eval(ctx, input, input_len, filename, eval_flags)); 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) DLLEXPORT int32_t jsValueGetTag(JSValue *val)
@@ -283,8 +286,11 @@ extern "C"
DLLEXPORT JSValue *jsCall(JSContext *ctx, JSValueConst *func_obj, JSValueConst *this_obj, DLLEXPORT JSValue *jsCall(JSContext *ctx, JSValueConst *func_obj, JSValueConst *this_obj,
int32_t argc, JSValueConst *argv) int32_t argc, JSValueConst *argv)
{ {
JS_ResetStackTop(JS_GetRuntime(ctx)); JSRuntime *rt = JS_GetRuntime(ctx);
return new JSValue(JS_Call(ctx, *func_obj, *this_obj, argc, argv)); 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) DLLEXPORT int32_t jsIsException(JSValueConst *val)
@@ -299,9 +305,11 @@ extern "C"
DLLEXPORT int32_t jsExecutePendingJob(JSRuntime *rt) DLLEXPORT int32_t jsExecutePendingJob(JSRuntime *rt)
{ {
JS_ResetStackTop(rt); uint8_t *stack_top = JS_SetStackTop(rt, 0);
JSContext *ctx; 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) DLLEXPORT JSValue *jsNewPromiseCapability(JSContext *ctx, JSValue *resolving_funcs)

View File

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

View File

@@ -113,6 +113,7 @@ class RuntimeOpaque {
List<JSRef> ref = []; List<JSRef> ref = [];
ReceivePort port; ReceivePort port;
Future Function(Pointer) promiseToFuture; Future Function(Pointer) promiseToFuture;
Pointer Function(Object) objectWrapper;
} }
final Map<Pointer, RuntimeOpaque> runtimeOpaques = Map(); final Map<Pointer, RuntimeOpaque> runtimeOpaques = Map();

View File

@@ -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 { class JSPromise extends JSRefValue {
Completer completer; Completer completer;
JSPromise(Pointer ctx, Pointer val, this.completer) : super(ctx, val); 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<dynamic, dynamic> cache}) { Pointer dartToJs(Pointer ctx, dynamic val, {Map<dynamic, dynamic> cache}) {
if (val == null) return jsUNDEFINED();
if (val is Future) { if (val is Future) {
var resolvingFunc = allocate<Uint8>(count: sizeOfJSValue * 2); var resolvingFunc = allocate<Uint8>(count: sizeOfJSValue * 2);
var resolvingFunc2 = var resolvingFunc2 =
@@ -195,7 +216,7 @@ Pointer dartToJs(Pointer ctx, dynamic val, {Map<dynamic, dynamic> cache}) {
} }
return ret; return ret;
} }
return jsUNDEFINED(); return runtimeOpaques[jsGetRuntime(ctx)]?.objectWrapper(val) ?? jsUNDEFINED();
} }
dynamic jsToDart(Pointer ctx, Pointer val, {Map<int, dynamic> cache}) { dynamic jsToDart(Pointer ctx, Pointer val, {Map<int, dynamic> cache}) {
@@ -262,6 +283,10 @@ dynamic jsToDart(Pointer ctx, Pointer val, {Map<int, dynamic> cache}) {
} }
jsFree(ctx, ptab.value); jsFree(ctx, ptab.value);
free(ptab); free(ptab);
final objHash = ret["__dart_obj_hash__"];
if (objHash is int) {
return DartObject.fromAddress(ctx, objHash)?.obj ?? ret;
}
return ret; return ret;
} }
break; break;
@@ -272,6 +297,9 @@ dynamic jsToDart(Pointer ctx, Pointer val, {Map<int, dynamic> cache}) {
Pointer jsNewContextWithPromsieWrapper(Pointer rt) { Pointer jsNewContextWithPromsieWrapper(Pointer rt) {
var ctx = jsNewContext(rt); var ctx = jsNewContext(rt);
final runtimeOpaque = runtimeOpaques[rt];
if (runtimeOpaque == null) throw Exception("Runtime has been released!");
var jsPromiseWrapper = jsEval( var jsPromiseWrapper = jsEval(
ctx, ctx,
""" """
@@ -290,17 +318,42 @@ Pointer jsNewContextWithPromsieWrapper(Pointer rt) {
""", """,
"<future>", "<future>",
JSEvalFlag.GLOBAL); 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;
};
""",
"<future>",
JSEvalFlag.GLOBAL);
final promiseWrapper = JSRefValue(ctx, jsPromiseWrapper);
jsFreeValue(ctx, jsPromiseWrapper); jsFreeValue(ctx, jsPromiseWrapper);
runtimeOpaques[rt].promiseToFuture = (promise) { final objectWrapper = JSRefValue(ctx, jsObjectWrapper);
jsFreeValue(ctx, jsObjectWrapper);
runtimeOpaque.promiseToFuture = (promise) {
var completer = Completer(); var completer = Completer();
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!"));
var jsPromise = jsCall(ctx, wrapper, null, [promise]); var jsPromise = jsCall(ctx, wrapper, null, [promise]);
runtimeOpaques[rt].ref.add(JSPromise(ctx, jsPromise, completer)); var wrapPromise = JSPromise(ctx, jsPromise, completer);
jsFreeValue(ctx, jsPromise); 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; return ctx;

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

View File

@@ -98,7 +98,6 @@ void main() async {
qjs.close(); qjs.close();
}); });
test('jsToDart', () async { test('jsToDart', () async {
await runZonedGuarded(() async {
final qjs = FlutterQjs( final qjs = FlutterQjs(
methodHandler: myMethodHandler, methodHandler: myMethodHandler,
moduleHandler: (name) { moduleHandler: (name) {
@@ -108,9 +107,6 @@ void main() async {
qjs.dispatch(); qjs.dispatch();
await testEvaluate(qjs); await testEvaluate(qjs);
qjs.close(); qjs.close();
}, (e, stack) {
if (e is TestFailure) throw e;
});
}); });
test('isolate', () async { test('isolate', () async {
await runZonedGuarded(() async { await runZonedGuarded(() async {
@@ -123,9 +119,20 @@ void main() async {
await testEvaluate(qjs); await testEvaluate(qjs);
qjs.close(); qjs.close();
}, (e, stack) { }, (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: "<eval>");
expect(value, isInstanceOf<FlutterQjs>(), reason: "dart object");
qjs.close();
});
test('stack overflow', () async { test('stack overflow', () async {
final qjs = FlutterQjs(); final qjs = FlutterQjs();
try { try {