mirror of
https://github.com/wgh136/flutter_qjs.git
synced 2025-09-27 13:27:24 +00:00
wrap dart object to js.
This commit is contained in:
@@ -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.
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
20
cxx/ffi.cpp
20
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)
|
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)
|
||||||
|
Submodule cxx/quickjs updated: 16e640b3dc...9ac874168b
@@ -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
|
||||||
|
@@ -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();
|
||||||
|
@@ -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;
|
||||||
|
@@ -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:
|
||||||
|
@@ -98,19 +98,15 @@ 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) {
|
return "export default '${new DateTime.now()}'";
|
||||||
return "export default '${new DateTime.now()}'";
|
},
|
||||||
},
|
);
|
||||||
);
|
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 {
|
||||||
|
Reference in New Issue
Block a user