mirror of
https://github.com/wgh136/flutter_qjs.git
synced 2025-09-27 13:27:24 +00:00
183 lines
4.9 KiB
Dart
183 lines
4.9 KiB
Dart
/*
|
|
* @Description: quickjs engine
|
|
* @Author: ekibun
|
|
* @Date: 2020-08-08 08:29:09
|
|
* @LastEditors: ekibun
|
|
* @LastEditTime: 2020-10-06 23:47:13
|
|
*/
|
|
part of '../flutter_qjs.dart';
|
|
|
|
/// Handler function to manage js module.
|
|
typedef _JsModuleHandler = String Function(String name);
|
|
|
|
/// Handler to manage unhandled promise rejection.
|
|
typedef _JsHostPromiseRejectionHandler = void Function(dynamic reason);
|
|
|
|
/// Quickjs engine for flutter.
|
|
class FlutterQjs {
|
|
Pointer<JSRuntime>? _rt;
|
|
Pointer<JSContext>? _ctx;
|
|
|
|
/// Max stack size for quickjs.
|
|
final int? stackSize;
|
|
|
|
/// Max stack size for quickjs.
|
|
final int? timeout;
|
|
|
|
/// Max memory for quickjs.
|
|
final int? memoryLimit;
|
|
|
|
/// Message Port for event loop. Close it to stop dispatching event loop.
|
|
ReceivePort port = ReceivePort();
|
|
|
|
/// Handler function to manage js module.
|
|
final _JsModuleHandler? moduleHandler;
|
|
|
|
/// Handler function to manage js module.
|
|
final _JsHostPromiseRejectionHandler? hostPromiseRejectionHandler;
|
|
|
|
FlutterQjs({
|
|
this.moduleHandler,
|
|
this.stackSize,
|
|
this.timeout,
|
|
this.memoryLimit,
|
|
this.hostPromiseRejectionHandler,
|
|
});
|
|
|
|
_ensureEngine() {
|
|
if (_rt != null) return;
|
|
final rt = jsNewRuntime((ctx, type, ptr) {
|
|
try {
|
|
switch (type) {
|
|
case JSChannelType.METHON:
|
|
final pdata = ptr.cast<Pointer<JSValue>>();
|
|
final argc = pdata.elementAt(1).value.cast<Int32>().value;
|
|
final pargs = [];
|
|
for (var i = 0; i < argc; ++i) {
|
|
pargs.add(_jsToDart(
|
|
ctx,
|
|
Pointer.fromAddress(
|
|
pdata.elementAt(2).value.address + sizeOfJSValue * i,
|
|
),
|
|
));
|
|
}
|
|
final JSInvokable func = _jsToDart(
|
|
ctx,
|
|
pdata.elementAt(3).value,
|
|
);
|
|
return _dartToJs(
|
|
ctx,
|
|
func.invoke(
|
|
pargs,
|
|
_jsToDart(ctx, pdata.elementAt(0).value),
|
|
));
|
|
case JSChannelType.MODULE:
|
|
if (moduleHandler == null) throw JSError('No ModuleHandler');
|
|
final ret = moduleHandler!(
|
|
ptr.cast<Utf8>().toDartString(),
|
|
).toNativeUtf8();
|
|
Future.microtask(() {
|
|
malloc.free(ret);
|
|
});
|
|
return ret.cast();
|
|
case JSChannelType.PROMISE_TRACK:
|
|
final err = _parseJSException(ctx, ptr);
|
|
if (hostPromiseRejectionHandler != null) {
|
|
hostPromiseRejectionHandler!(err);
|
|
} else {
|
|
print('unhandled promise rejection: $err');
|
|
}
|
|
return nullptr;
|
|
case JSChannelType.FREE_OBJECT:
|
|
final rt = ctx.cast<JSRuntime>();
|
|
_DartObject.fromAddress(rt, ptr.address)?.free();
|
|
return nullptr;
|
|
}
|
|
throw JSError('call channel with wrong type');
|
|
} catch (e) {
|
|
if (type == JSChannelType.FREE_OBJECT) {
|
|
print('DartObject release error: $e');
|
|
return nullptr;
|
|
}
|
|
if (type == JSChannelType.MODULE) {
|
|
print('host Promise Rejection Handler error: $e');
|
|
return nullptr;
|
|
}
|
|
final throwObj = _dartToJs(ctx, e);
|
|
final err = jsThrow(ctx, throwObj);
|
|
jsFreeValue(ctx, throwObj);
|
|
if (type == JSChannelType.MODULE) {
|
|
jsFreeValue(ctx, err);
|
|
return nullptr;
|
|
}
|
|
return err;
|
|
}
|
|
}, timeout ?? 0, port);
|
|
final stackSize = this.stackSize ?? 0;
|
|
if (stackSize > 0) jsSetMaxStackSize(rt, stackSize);
|
|
final memoryLimit = this.memoryLimit ?? 0;
|
|
if (memoryLimit > 0) jsSetMemoryLimit(rt, memoryLimit);
|
|
_rt = rt;
|
|
_ctx = jsNewContext(rt);
|
|
}
|
|
|
|
/// Free Runtime and Context which can be recreate when evaluate again.
|
|
close() {
|
|
final rt = _rt;
|
|
final ctx = _ctx;
|
|
_rt = null;
|
|
_ctx = null;
|
|
if (ctx != null) jsFreeContext(ctx);
|
|
if (rt == null) return;
|
|
_executePendingJob();
|
|
try {
|
|
jsFreeRuntime(rt);
|
|
} on String catch (e) {
|
|
throw JSError(e);
|
|
}
|
|
}
|
|
|
|
void _executePendingJob() {
|
|
final rt = _rt;
|
|
final ctx = _ctx;
|
|
if (rt == null || ctx == null) return;
|
|
while (true) {
|
|
int err = jsExecutePendingJob(rt);
|
|
if (err <= 0) {
|
|
if (err < 0) print(_parseJSException(ctx));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Dispatch JavaScript Event loop.
|
|
Future<void> dispatch() async {
|
|
await for (final _ in port) {
|
|
_executePendingJob();
|
|
}
|
|
}
|
|
|
|
/// Evaluate js script.
|
|
dynamic evaluate(
|
|
String command, {
|
|
String? name,
|
|
int? evalFlags,
|
|
}) {
|
|
_ensureEngine();
|
|
final ctx = _ctx!;
|
|
final jsval = jsEval(
|
|
ctx,
|
|
command,
|
|
name ?? '<eval>',
|
|
evalFlags ?? JSEvalFlag.GLOBAL,
|
|
);
|
|
if (jsIsException(jsval) != 0) {
|
|
jsFreeValue(ctx, jsval);
|
|
throw _parseJSException(ctx);
|
|
}
|
|
final result = _jsToDart(ctx, jsval);
|
|
jsFreeValue(ctx, jsval);
|
|
return result;
|
|
}
|
|
}
|