mirror of
https://github.com/wgh136/flutter_qjs.git
synced 2025-09-27 05:27:23 +00:00
host promise rejection
This commit is contained in:
24
cxx/ffi.cpp
24
cxx/ffi.cpp
@@ -63,10 +63,22 @@ extern "C"
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void js_promise_rejection_tracker(JSContext *ctx, JSValueConst promise,
|
||||||
|
JSValueConst reason,
|
||||||
|
JS_BOOL is_handled, void *opaque)
|
||||||
|
{
|
||||||
|
if (is_handled)
|
||||||
|
return;
|
||||||
|
JSRuntime *rt = JS_GetRuntime(ctx);
|
||||||
|
JSChannel *channel = (JSChannel *)JS_GetRuntimeOpaque(rt);
|
||||||
|
channel(ctx, (char *)ctx, &reason);
|
||||||
|
}
|
||||||
|
|
||||||
DLLEXPORT JSRuntime *jsNewRuntime(JSChannel channel)
|
DLLEXPORT JSRuntime *jsNewRuntime(JSChannel channel)
|
||||||
{
|
{
|
||||||
JSRuntime *rt = JS_NewRuntime();
|
JSRuntime *rt = JS_NewRuntime();
|
||||||
JS_SetRuntimeOpaque(rt, (void *)channel);
|
JS_SetRuntimeOpaque(rt, (void *)channel);
|
||||||
|
JS_SetHostPromiseRejectionTracker(rt, js_promise_rejection_tracker, nullptr);
|
||||||
JS_SetModuleLoaderFunc(rt, nullptr, js_module_loader, nullptr);
|
JS_SetModuleLoaderFunc(rt, nullptr, js_module_loader, nullptr);
|
||||||
return rt;
|
return rt;
|
||||||
}
|
}
|
||||||
@@ -83,10 +95,9 @@ extern "C"
|
|||||||
// destructor
|
// destructor
|
||||||
[](JSRuntime *rt, JSValue obj) noexcept {
|
[](JSRuntime *rt, JSValue obj) noexcept {
|
||||||
JSClassID classid = JS_GetClassID(obj);
|
JSClassID classid = JS_GetClassID(obj);
|
||||||
ObjectOpaque *opaque = (ObjectOpaque *)JS_GetOpaque(obj, classid);
|
void *opaque = JS_GetOpaque(obj, classid);
|
||||||
JSChannel *channel = (JSChannel *)JS_GetRuntimeOpaque(rt);
|
JSChannel *channel = (JSChannel *)JS_GetRuntimeOpaque(rt);
|
||||||
channel((JSContext *)rt, nullptr, (void *)opaque->opaque);
|
channel((JSContext *)rt, nullptr, opaque);
|
||||||
delete opaque;
|
|
||||||
}};
|
}};
|
||||||
int e = JS_NewClass(rt, QJSClassId, &def);
|
int e = JS_NewClass(rt, QJSClassId, &def);
|
||||||
if (e < 0)
|
if (e < 0)
|
||||||
@@ -100,9 +111,7 @@ extern "C"
|
|||||||
|
|
||||||
DLLEXPORT void *jsGetObjectOpaque(JSValue *obj, uint32_t classid)
|
DLLEXPORT void *jsGetObjectOpaque(JSValue *obj, uint32_t classid)
|
||||||
{
|
{
|
||||||
ObjectOpaque *opaque = (ObjectOpaque *)JS_GetOpaque(*obj, classid);
|
return JS_GetOpaque(*obj, classid);
|
||||||
if(opaque == nullptr) return nullptr;
|
|
||||||
return opaque->opaque;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DLLEXPORT JSValue *jsNewObjectClass(JSContext *ctx, uint32_t QJSClassId, void *opaque)
|
DLLEXPORT JSValue *jsNewObjectClass(JSContext *ctx, uint32_t QJSClassId, void *opaque)
|
||||||
@@ -110,8 +119,7 @@ extern "C"
|
|||||||
auto jsobj = new JSValue(JS_NewObjectClass(ctx, QJSClassId));
|
auto jsobj = new JSValue(JS_NewObjectClass(ctx, QJSClassId));
|
||||||
if (JS_IsException(*jsobj))
|
if (JS_IsException(*jsobj))
|
||||||
return jsobj;
|
return jsobj;
|
||||||
ObjectOpaque *objOpaque = new ObjectOpaque{ctx, opaque};
|
JS_SetOpaque(*jsobj, opaque);
|
||||||
JS_SetOpaque(*jsobj, objOpaque);
|
|
||||||
return jsobj;
|
return jsobj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -8,12 +8,6 @@
|
|||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
{
|
{
|
||||||
struct ObjectOpaque
|
|
||||||
{
|
|
||||||
JSContext *ctx;
|
|
||||||
void *opaque;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef void *JSChannel(JSContext *ctx, const char *method, void *argv);
|
typedef void *JSChannel(JSContext *ctx, const char *method, void *argv);
|
||||||
|
|
||||||
DLLEXPORT JSValue *jsThrowInternalError(JSContext *ctx, char *message);
|
DLLEXPORT JSValue *jsThrowInternalError(JSContext *ctx, char *message);
|
||||||
|
@@ -19,6 +19,9 @@ typedef JsMethodHandler = dynamic Function(String method, List args);
|
|||||||
/// Handler function to manage js module.
|
/// Handler function to manage js module.
|
||||||
typedef JsModuleHandler = String Function(String name);
|
typedef JsModuleHandler = String Function(String name);
|
||||||
|
|
||||||
|
/// Handler to manage unhandled promise rejection.
|
||||||
|
typedef JsHostPromiseRejectionHandler = void Function(String reason);
|
||||||
|
|
||||||
class FlutterQjs {
|
class FlutterQjs {
|
||||||
Pointer _rt;
|
Pointer _rt;
|
||||||
Pointer _ctx;
|
Pointer _ctx;
|
||||||
@@ -35,10 +38,17 @@ class FlutterQjs {
|
|||||||
/// Handler function to manage js module.
|
/// Handler function to manage js module.
|
||||||
JsModuleHandler moduleHandler;
|
JsModuleHandler moduleHandler;
|
||||||
|
|
||||||
|
/// Handler function to manage js module.
|
||||||
|
JsHostPromiseRejectionHandler hostPromiseRejectionHandler;
|
||||||
|
|
||||||
/// Quickjs engine for flutter.
|
/// Quickjs engine for flutter.
|
||||||
///
|
///
|
||||||
/// Pass handlers to implement js-dart interaction and resolving modules.
|
/// Pass handlers to implement js-dart interaction and resolving modules.
|
||||||
FlutterQjs({this.methodHandler, this.moduleHandler, this.stackSize});
|
FlutterQjs(
|
||||||
|
{this.methodHandler,
|
||||||
|
this.moduleHandler,
|
||||||
|
this.stackSize,
|
||||||
|
this.hostPromiseRejectionHandler});
|
||||||
|
|
||||||
_ensureEngine() {
|
_ensureEngine() {
|
||||||
if (_rt != null) return;
|
if (_rt != null) return;
|
||||||
@@ -51,14 +61,22 @@ class FlutterQjs {
|
|||||||
runtimeOpaques[rt]?.ref?.remove(obj);
|
runtimeOpaques[rt]?.ref?.remove(obj);
|
||||||
return Pointer.fromAddress(0);
|
return Pointer.fromAddress(0);
|
||||||
}
|
}
|
||||||
if (argv.address != 0 && method.address != 0) {
|
if (argv.address != 0) {
|
||||||
|
if (method.address == ctx.address) {
|
||||||
|
final errStr = parseJSException(ctx, perr: argv);
|
||||||
|
if (hostPromiseRejectionHandler != null) {
|
||||||
|
hostPromiseRejectionHandler(errStr);
|
||||||
|
} else {
|
||||||
|
print("unhandled promise rejection: $errStr");
|
||||||
|
}
|
||||||
|
return Pointer.fromAddress(0);
|
||||||
|
}
|
||||||
if (methodHandler == null) throw Exception("No MethodHandler");
|
if (methodHandler == null) throw Exception("No MethodHandler");
|
||||||
var argvs = jsToDart(ctx, argv);
|
|
||||||
return dartToJs(
|
return dartToJs(
|
||||||
ctx,
|
ctx,
|
||||||
methodHandler(
|
methodHandler(
|
||||||
Utf8.fromUtf8(method.cast<Utf8>()),
|
Utf8.fromUtf8(method.cast<Utf8>()),
|
||||||
argvs,
|
jsToDart(ctx, argv),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if (moduleHandler == null) throw Exception("No ModuleHandler");
|
if (moduleHandler == null) throw Exception("No ModuleHandler");
|
||||||
@@ -69,11 +87,20 @@ class FlutterQjs {
|
|||||||
});
|
});
|
||||||
return ret;
|
return ret;
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
|
final errStr = e.toString() + "\n" + stack.toString();
|
||||||
|
if (method.address == 0) {
|
||||||
|
print("DartObject release error: " + errStr);
|
||||||
|
return Pointer.fromAddress(0);
|
||||||
|
}
|
||||||
|
if (method.address == ctx.address) {
|
||||||
|
print("host Promise Rejection Handler error: " + errStr);
|
||||||
|
return Pointer.fromAddress(0);
|
||||||
|
}
|
||||||
var err = jsThrowInternalError(
|
var err = jsThrowInternalError(
|
||||||
ctx,
|
ctx,
|
||||||
e.toString() + "\n" + stack.toString(),
|
errStr,
|
||||||
);
|
);
|
||||||
if (method.address == 0) {
|
if (argv.address == 0) {
|
||||||
jsFreeValue(ctx, err);
|
jsFreeValue(ctx, err);
|
||||||
return Pointer.fromAddress(0);
|
return Pointer.fromAddress(0);
|
||||||
}
|
}
|
||||||
|
@@ -143,12 +143,17 @@ dynamic _decodeData(dynamic data, SendPort port,
|
|||||||
|
|
||||||
void _runJsIsolate(Map spawnMessage) async {
|
void _runJsIsolate(Map spawnMessage) async {
|
||||||
SendPort sendPort = spawnMessage['port'];
|
SendPort sendPort = spawnMessage['port'];
|
||||||
JsMethodHandler methodHandler = spawnMessage['handler'];
|
|
||||||
ReceivePort port = ReceivePort();
|
ReceivePort port = ReceivePort();
|
||||||
sendPort.send(port.sendPort);
|
sendPort.send(port.sendPort);
|
||||||
var qjs = FlutterQjs(
|
var qjs = FlutterQjs(
|
||||||
stackSize: spawnMessage['stackSize'],
|
stackSize: spawnMessage['stackSize'],
|
||||||
methodHandler: methodHandler,
|
hostPromiseRejectionHandler: (reason) {
|
||||||
|
sendPort.send({
|
||||||
|
'type': 'hostPromiseRejection',
|
||||||
|
'reason': reason,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methodHandler: spawnMessage['handler'],
|
||||||
moduleHandler: (name) {
|
moduleHandler: (name) {
|
||||||
var ptr = allocate<Pointer<Utf8>>();
|
var ptr = allocate<Pointer<Utf8>>();
|
||||||
ptr.value = Pointer.fromAddress(0);
|
ptr.value = Pointer.fromAddress(0);
|
||||||
@@ -222,11 +227,19 @@ class IsolateQjs {
|
|||||||
/// Asynchronously handler to manage js module.
|
/// Asynchronously handler to manage js module.
|
||||||
JsAsyncModuleHandler moduleHandler;
|
JsAsyncModuleHandler moduleHandler;
|
||||||
|
|
||||||
|
/// Handler function to manage js module.
|
||||||
|
JsHostPromiseRejectionHandler hostPromiseRejectionHandler;
|
||||||
|
|
||||||
/// Quickjs engine runing on isolate thread.
|
/// Quickjs engine runing on isolate thread.
|
||||||
///
|
///
|
||||||
/// Pass handlers to implement js-dart interaction and resolving modules. The `methodHandler` is
|
/// 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**.
|
/// used in isolate, so **the handler function must be a top-level function or a static method**.
|
||||||
IsolateQjs({this.methodHandler, this.moduleHandler, this.stackSize});
|
IsolateQjs({
|
||||||
|
this.methodHandler,
|
||||||
|
this.moduleHandler,
|
||||||
|
this.stackSize,
|
||||||
|
this.hostPromiseRejectionHandler,
|
||||||
|
});
|
||||||
|
|
||||||
_ensureEngine() {
|
_ensureEngine() {
|
||||||
if (_sendPort != null) return;
|
if (_sendPort != null) return;
|
||||||
@@ -247,6 +260,21 @@ class IsolateQjs {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (msg['type']) {
|
switch (msg['type']) {
|
||||||
|
case 'hostPromiseRejection':
|
||||||
|
try {
|
||||||
|
final errStr = msg['reason'];
|
||||||
|
if (hostPromiseRejectionHandler != null) {
|
||||||
|
hostPromiseRejectionHandler(errStr);
|
||||||
|
} else {
|
||||||
|
print("unhandled promise rejection: $errStr");
|
||||||
|
}
|
||||||
|
} catch (e, stack) {
|
||||||
|
print("host Promise Rejection Handler error: " +
|
||||||
|
e.toString() +
|
||||||
|
"\n" +
|
||||||
|
stack.toString());
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'module':
|
case 'module':
|
||||||
var ptr = Pointer<Pointer>.fromAddress(msg['ptr']);
|
var ptr = Pointer<Pointer>.fromAddress(msg['ptr']);
|
||||||
try {
|
try {
|
||||||
|
@@ -78,10 +78,12 @@ class JSPromise extends JSRefValue {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (status["__rejected"] == true) {
|
if (status["__rejected"] == true) {
|
||||||
|
final err = jsGetPropertyStr(ctx, val, "__error");
|
||||||
completer.completeError(parseJSException(
|
completer.completeError(parseJSException(
|
||||||
ctx,
|
ctx,
|
||||||
e: jsGetPropertyStr(ctx, val, "__error"),
|
perr: err,
|
||||||
));
|
));
|
||||||
|
jsFreeValue(ctx, err);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -129,8 +131,9 @@ Pointer jsGetPropertyStr(Pointer ctx, Pointer val, String prop) {
|
|||||||
return jsProp;
|
return jsProp;
|
||||||
}
|
}
|
||||||
|
|
||||||
String parseJSException(Pointer ctx, {Pointer e}) {
|
String parseJSException(Pointer ctx, {Pointer perr}) {
|
||||||
e = e ?? jsGetException(ctx);
|
final e = perr ?? jsGetException(ctx);
|
||||||
|
|
||||||
var err = jsToCString(ctx, e);
|
var err = jsToCString(ctx, e);
|
||||||
if (jsValueGetTag(e) == JSTag.OBJECT) {
|
if (jsValueGetTag(e) == JSTag.OBJECT) {
|
||||||
Pointer stack = jsGetPropertyStr(ctx, e, "stack");
|
Pointer stack = jsGetPropertyStr(ctx, e, "stack");
|
||||||
@@ -139,7 +142,7 @@ String parseJSException(Pointer ctx, {Pointer e}) {
|
|||||||
}
|
}
|
||||||
jsFreeValue(ctx, stack);
|
jsFreeValue(ctx, stack);
|
||||||
}
|
}
|
||||||
jsFreeValue(ctx, e);
|
if (perr == null) jsFreeValue(ctx, e);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -100,6 +100,7 @@ void main() async {
|
|||||||
moduleHandler: (name) {
|
moduleHandler: (name) {
|
||||||
return "export default '${new DateTime.now()}'";
|
return "export default '${new DateTime.now()}'";
|
||||||
},
|
},
|
||||||
|
hostPromiseRejectionHandler: (_) {},
|
||||||
);
|
);
|
||||||
qjs.dispatch();
|
qjs.dispatch();
|
||||||
await testEvaluate(qjs);
|
await testEvaluate(qjs);
|
||||||
@@ -112,6 +113,7 @@ void main() async {
|
|||||||
moduleHandler: (name) async {
|
moduleHandler: (name) async {
|
||||||
return "export default '${new DateTime.now()}'";
|
return "export default '${new DateTime.now()}'";
|
||||||
},
|
},
|
||||||
|
hostPromiseRejectionHandler: (_) {},
|
||||||
);
|
);
|
||||||
await testEvaluate(qjs);
|
await testEvaluate(qjs);
|
||||||
qjs.close();
|
qjs.close();
|
||||||
@@ -141,4 +143,22 @@ void main() async {
|
|||||||
}
|
}
|
||||||
qjs.close();
|
qjs.close();
|
||||||
});
|
});
|
||||||
|
test('host promise rejection', () async {
|
||||||
|
final completer = Completer();
|
||||||
|
final qjs = FlutterQjs(
|
||||||
|
hostPromiseRejectionHandler: (reason) {
|
||||||
|
completer.complete(reason);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
qjs.dispatch();
|
||||||
|
qjs.evaluate(
|
||||||
|
"(() => { Promise.resolve().then(() => { throw 'unhandle' }) })()",
|
||||||
|
name: "<eval>");
|
||||||
|
Future.delayed(Duration(seconds: 10)).then((value) {
|
||||||
|
if (!completer.isCompleted) completer.completeError("not host reject");
|
||||||
|
});
|
||||||
|
expect(await completer.future, "unhandle",
|
||||||
|
reason: "host promise rejection");
|
||||||
|
qjs.close();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user