setToGlobalObject

This commit is contained in:
ekibun
2021-01-24 16:43:19 +08:00
parent 1589f87194
commit 6b0bab2faf
9 changed files with 228 additions and 142 deletions

View File

@@ -21,6 +21,13 @@ class JSEvalFlag {
static const MODULE = 1 << 0;
}
class JSChannelType {
static const METHON = 0;
static const MODULE = 1;
static const PROMISE_TRACK = 2;
static const FREE_OBJECT = 3;
}
class JSProp {
static const CONFIGURABLE = (1 << 0);
static const WRITABLE = (1 << 1);
@@ -91,11 +98,13 @@ final Pointer Function() jsUNDEFINED = qjsLib
.lookup<NativeFunction<Pointer Function()>>("jsUNDEFINED")
.asFunction();
typedef JSChannel = Pointer Function(Pointer ctx, int method, Pointer argv);
typedef JSChannelNative = Pointer Function(
Pointer ctx, IntPtr method, Pointer argv);
/// JSRuntime *jsNewRuntime(JSChannel channel)
final Pointer Function(
Pointer<
NativeFunction<
Pointer Function(Pointer ctx, Pointer method, Pointer argv)>>,
Pointer<NativeFunction<JSChannelNative>>,
) _jsNewRuntime = qjsLib
.lookup<
NativeFunction<
@@ -104,8 +113,6 @@ final Pointer Function(
)>>("jsNewRuntime")
.asFunction();
typedef JSChannel = Pointer Function(Pointer ctx, Pointer method, Pointer argv);
class RuntimeOpaque {
JSChannel channel;
List<JSRef> ref = [];
@@ -116,9 +123,9 @@ class RuntimeOpaque {
final Map<Pointer, RuntimeOpaque> runtimeOpaques = Map();
Pointer channelDispacher(Pointer ctx, Pointer method, Pointer argv) {
Pointer rt = method.address == 0 ? ctx : jsGetRuntime(ctx);
return runtimeOpaques[rt]?.channel(ctx, method, argv);
Pointer channelDispacher(Pointer ctx, int type, Pointer argv) {
Pointer rt = type == JSChannelType.FREE_OBJECT ? ctx : jsGetRuntime(ctx);
return runtimeOpaques[rt]?.channel(ctx, type, argv);
}
Pointer jsNewRuntime(
@@ -166,6 +173,30 @@ void jsFreeRuntime(
_jsFreeRuntime(rt);
}
/// JSValue *jsNewCFunction(JSContext *ctx, JSValue *funcData)
final Pointer Function(
Pointer ctx,
Pointer funcData,
) jsNewCFunction = qjsLib
.lookup<
NativeFunction<
Pointer Function(
Pointer,
Pointer,
)>>("jsNewCFunction")
.asFunction();
/// JSValue *jsGetGlobalObject(JSContext *ctx)
final Pointer Function(
Pointer ctx,
) jsGetGlobalObject = qjsLib
.lookup<
NativeFunction<
Pointer Function(
Pointer,
)>>("jsGetGlobalObject")
.asFunction();
/// JSContext *jsNewContext(JSRuntime *rt)
final Pointer Function(
Pointer rt,

View File

@@ -13,9 +13,6 @@ import 'package:ffi/ffi.dart';
import 'package:flutter_qjs/ffi.dart';
import 'package:flutter_qjs/wrapper.dart';
/// Handler function to manage js call.
typedef JsMethodHandler = dynamic Function(String method, List args);
/// Handler function to manage js module.
typedef JsModuleHandler = String Function(String name);
@@ -32,9 +29,6 @@ class FlutterQjs {
/// Message Port for event loop. Close it to stop dispatching event loop.
ReceivePort port = ReceivePort();
/// Handler function to manage js call with `channel(method, [...args])` function.
JsMethodHandler methodHandler;
/// Handler function to manage js module.
JsModuleHandler moduleHandler;
@@ -44,55 +38,75 @@ class FlutterQjs {
/// Quickjs engine for flutter.
///
/// Pass handlers to implement js-dart interaction and resolving modules.
FlutterQjs(
{this.methodHandler,
this.moduleHandler,
this.stackSize,
this.hostPromiseRejectionHandler});
FlutterQjs({
this.moduleHandler,
this.stackSize,
this.hostPromiseRejectionHandler,
});
setToGlobalObject(dynamic key, dynamic val) {
_ensureEngine();
final globalObject = jsGetGlobalObject(_ctx);
definePropertyValue(_ctx, globalObject, key, val);
jsFreeValue(_ctx, globalObject);
}
_ensureEngine() {
if (_rt != null) return;
_rt = jsNewRuntime((ctx, method, argv) {
_rt = jsNewRuntime((ctx, type, ptr) {
try {
if (method.address == 0) {
Pointer rt = ctx;
DartObject obj = DartObject.fromAddress(rt, argv.address);
obj?.release();
runtimeOpaques[rt]?.ref?.remove(obj);
return Pointer.fromAddress(0);
}
if (argv.address != 0) {
if (method.address == ctx.address) {
final errStr = parseJSException(ctx, perr: argv);
switch (type) {
case JSChannelType.METHON:
final pdata = ptr.cast<Pointer>();
final argc = pdata.elementAt(1).value.cast<Int32>().value;
List args = [];
for (var i = 0; i < argc; i++) {
args.add(jsToDart(
ctx,
Pointer.fromAddress(
pdata.elementAt(2).value.address + sizeOfJSValue * i,
)));
}
final thisVal = jsToDart(ctx, pdata.elementAt(0).value);
Function func = jsToDart(ctx, pdata.elementAt(3).value);
final passThis =
RegExp("{.*thisVal.*}").hasMatch(func.runtimeType.toString());
return dartToJs(
ctx,
Function.apply(func, args, passThis ? {#thisVal: thisVal} : null),
);
case JSChannelType.MODULE:
if (moduleHandler == null) throw Exception("No ModuleHandler");
var ret = Utf8.toUtf8(moduleHandler(
Utf8.fromUtf8(ptr.cast<Utf8>()),
));
Future.microtask(() {
free(ret);
});
return ret;
case JSChannelType.PROMISE_TRACK:
final errStr = parseJSException(ctx, perr: ptr);
if (hostPromiseRejectionHandler != null) {
hostPromiseRejectionHandler(errStr);
} else {
print("unhandled promise rejection: $errStr");
}
return Pointer.fromAddress(0);
}
if (methodHandler == null) throw Exception("No MethodHandler");
return dartToJs(
ctx,
methodHandler(
Utf8.fromUtf8(method.cast<Utf8>()),
jsToDart(ctx, argv),
));
case JSChannelType.FREE_OBJECT:
Pointer rt = ctx;
DartObject obj = DartObject.fromAddress(rt, ptr.address);
obj?.release();
runtimeOpaques[rt]?.ref?.remove(obj);
return Pointer.fromAddress(0);
}
if (moduleHandler == null) throw Exception("No ModuleHandler");
var ret =
Utf8.toUtf8(moduleHandler(Utf8.fromUtf8(method.cast<Utf8>())));
Future.microtask(() {
free(ret);
});
return ret;
throw Exception("call channel with wrong type");
} catch (e, stack) {
final errStr = e.toString() + "\n" + stack.toString();
if (method.address == 0) {
if (type == JSChannelType.FREE_OBJECT) {
print("DartObject release error: " + errStr);
return Pointer.fromAddress(0);
}
if (method.address == ctx.address) {
if (type == JSChannelType.MODULE) {
print("host Promise Rejection Handler error: " + errStr);
return Pointer.fromAddress(0);
}
@@ -100,7 +114,7 @@ class FlutterQjs {
ctx,
errStr,
);
if (argv.address == 0) {
if (type == JSChannelType.MODULE) {
jsFreeValue(ctx, err);
return Pointer.fromAddress(0);
}

View File

@@ -153,7 +153,6 @@ void _runJsIsolate(Map spawnMessage) async {
'reason': reason,
});
},
methodHandler: spawnMessage['handler'],
moduleHandler: (name) {
var ptr = allocate<Pointer<Utf8>>();
ptr.value = Pointer.fromAddress(0);
@@ -192,6 +191,9 @@ void _runJsIsolate(Map spawnMessage) async {
msg['val'],
).invoke(_decodeData(msg['args'], null));
break;
case 'setToGlobalObject':
qjs.setToGlobalObject(msg['key'], msg['val']);
break;
case 'close':
qjs.port.close();
qjs.close();
@@ -220,10 +222,6 @@ class IsolateQjs {
/// Max stack size for quickjs.
final int stackSize;
/// Handler to manage js call with `channel(method, [...args])` function.
/// The function must be a top-level function or a static method.
JsMethodHandler methodHandler;
/// Asynchronously handler to manage js module.
JsAsyncModuleHandler moduleHandler;
@@ -235,7 +233,6 @@ class IsolateQjs {
/// 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**.
IsolateQjs({
this.methodHandler,
this.moduleHandler,
this.stackSize,
this.hostPromiseRejectionHandler,
@@ -248,7 +245,6 @@ class IsolateQjs {
_runJsIsolate,
{
'port': port.sendPort,
'handler': methodHandler,
'stackSize': stackSize,
},
errorsAreFatal: true,
@@ -305,6 +301,23 @@ class IsolateQjs {
_sendPort = null;
}
setToGlobalObject(dynamic key, dynamic val) async {
_ensureEngine();
var evaluatePort = ReceivePort();
var sendPort = await _sendPort;
sendPort.send({
'type': 'setToGlobalObject',
'key': key,
'val': val,
'port': evaluatePort.sendPort,
});
var result = await evaluatePort.first;
if (result['error'] == null) {
return;
} else
throw result['error'];
}
/// Evaluate js script.
Future<dynamic> evaluate(String command, {String name, int evalFlags}) async {
_ensureEngine();

View File

@@ -146,6 +146,26 @@ String parseJSException(Pointer ctx, {Pointer perr}) {
return err;
}
void definePropertyValue(
Pointer ctx,
Pointer obj,
dynamic key,
dynamic val, {
Map<dynamic, dynamic> cache,
}) {
var jsAtomVal = dartToJs(ctx, key, cache: cache);
var jsAtom = jsValueToAtom(ctx, jsAtomVal);
jsDefinePropertyValue(
ctx,
obj,
jsAtom,
dartToJs(ctx, val, cache: cache),
JSProp.C_W_E,
);
jsFreeAtom(ctx, jsAtom);
jsFreeValue(ctx, jsAtomVal);
}
Pointer dartToJs(Pointer ctx, dynamic val, {Map<dynamic, dynamic> cache}) {
if (val == null) return jsUNDEFINED();
if (val is Future) {
@@ -188,17 +208,7 @@ Pointer dartToJs(Pointer ctx, dynamic val, {Map<dynamic, dynamic> cache}) {
Pointer ret = jsNewArray(ctx);
cache[val] = ret;
for (int i = 0; i < val.length; ++i) {
var jsAtomVal = jsNewInt64(ctx, i);
var jsAtom = jsValueToAtom(ctx, jsAtomVal);
jsDefinePropertyValue(
ctx,
ret,
jsAtom,
dartToJs(ctx, val[i], cache: cache),
JSProp.C_W_E,
);
jsFreeAtom(ctx, jsAtom);
jsFreeValue(ctx, jsAtomVal);
definePropertyValue(ctx, ret, i, val[i], cache: cache);
}
return ret;
}
@@ -206,28 +216,24 @@ Pointer dartToJs(Pointer ctx, dynamic val, {Map<dynamic, dynamic> cache}) {
Pointer ret = jsNewObject(ctx);
cache[val] = ret;
for (MapEntry<dynamic, dynamic> entry in val.entries) {
var jsAtomVal = dartToJs(ctx, entry.key, cache: cache);
var jsAtom = jsValueToAtom(ctx, jsAtomVal);
jsDefinePropertyValue(
ctx,
ret,
jsAtom,
dartToJs(ctx, entry.value, cache: cache),
JSProp.C_W_E,
);
jsFreeAtom(ctx, jsAtom);
jsFreeValue(ctx, jsAtomVal);
definePropertyValue(ctx, ret, entry.key, entry.value, cache: cache);
}
return ret;
}
int dartObjectClassId =
runtimeOpaques[jsGetRuntime(ctx)]?.dartObjectClassId ?? 0;
if (dartObjectClassId == 0) return jsUNDEFINED();
return jsNewObjectClass(
var dartObject = jsNewObjectClass(
ctx,
dartObjectClassId,
identityHashCode(DartObject(ctx, val)),
);
if (val is Function) {
final ret = jsNewCFunction(ctx, dartObject);
jsFreeValue(ctx, dartObject);
return ret;
}
return dartObject;
}
dynamic jsToDart(Pointer ctx, Pointer val, {Map<int, dynamic> cache}) {