mirror of
https://github.com/wgh136/flutter_qjs.git
synced 2025-09-27 13:27:24 +00:00
reference leak
This commit is contained in:
162
lib/src/engine.dart
Normal file
162
lib/src/engine.dart
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* @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 _rt;
|
||||
Pointer _ctx;
|
||||
|
||||
/// Max stack size for quickjs.
|
||||
final int stackSize;
|
||||
|
||||
/// Message Port for event loop. Close it to stop dispatching event loop.
|
||||
ReceivePort port = ReceivePort();
|
||||
|
||||
/// Handler function to manage js module.
|
||||
_JsModuleHandler moduleHandler;
|
||||
|
||||
/// Handler function to manage js module.
|
||||
_JsHostPromiseRejectionHandler hostPromiseRejectionHandler;
|
||||
|
||||
FlutterQjs({
|
||||
this.moduleHandler,
|
||||
this.stackSize,
|
||||
this.hostPromiseRejectionHandler,
|
||||
});
|
||||
|
||||
_ensureEngine() {
|
||||
if (_rt != null) return;
|
||||
_rt = jsNewRuntime((ctx, type, ptr) {
|
||||
try {
|
||||
switch (type) {
|
||||
case JSChannelType.METHON:
|
||||
final pdata = ptr.cast<Pointer>();
|
||||
final argc = pdata.elementAt(1).value.cast<Int32>().value;
|
||||
List pargs = [];
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
pargs.add(_jsToDart(
|
||||
ctx,
|
||||
Pointer.fromAddress(
|
||||
pdata.elementAt(2).value.address + sizeOfJSValue * i,
|
||||
),
|
||||
));
|
||||
}
|
||||
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 = Utf8.toUtf8(moduleHandler(
|
||||
Utf8.fromUtf8(ptr.cast<Utf8>()),
|
||||
));
|
||||
Future.microtask(() {
|
||||
free(ret);
|
||||
});
|
||||
return ret;
|
||||
case JSChannelType.PROMISE_TRACK:
|
||||
final err = _parseJSException(ctx, ptr);
|
||||
if (hostPromiseRejectionHandler != null) {
|
||||
hostPromiseRejectionHandler(err);
|
||||
} else {
|
||||
print('unhandled promise rejection: $err');
|
||||
}
|
||||
return Pointer.fromAddress(0);
|
||||
case JSChannelType.FREE_OBJECT:
|
||||
Pointer rt = ctx;
|
||||
_DartObject obj = _DartObject.fromAddress(rt, ptr.address);
|
||||
obj?.release();
|
||||
return Pointer.fromAddress(0);
|
||||
}
|
||||
throw JSError('call channel with wrong type');
|
||||
} catch (e) {
|
||||
if (type == JSChannelType.FREE_OBJECT) {
|
||||
print('DartObject release error: $e');
|
||||
return Pointer.fromAddress(0);
|
||||
}
|
||||
if (type == JSChannelType.MODULE) {
|
||||
print('host Promise Rejection Handler error: $e');
|
||||
return Pointer.fromAddress(0);
|
||||
}
|
||||
final throwObj = _dartToJs(ctx, e);
|
||||
final err = jsThrow(ctx, throwObj);
|
||||
jsFreeValue(ctx, throwObj);
|
||||
if (type == JSChannelType.MODULE) {
|
||||
jsFreeValue(ctx, err);
|
||||
return Pointer.fromAddress(0);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
}, port);
|
||||
if (this.stackSize != null && this.stackSize > 0)
|
||||
jsSetMaxStackSize(_rt, this.stackSize);
|
||||
_ctx = jsNewContext(_rt);
|
||||
}
|
||||
|
||||
/// Free Runtime and Context which can be recreate when evaluate again.
|
||||
close() {
|
||||
if (_rt == null) return;
|
||||
final rt = _rt;
|
||||
final ctx = _ctx;
|
||||
_executePendingJob();
|
||||
_rt = null;
|
||||
_ctx = null;
|
||||
jsFreeContext(ctx);
|
||||
try {
|
||||
jsFreeRuntime(rt);
|
||||
} on String catch (e) {
|
||||
throw JSError(e);
|
||||
}
|
||||
}
|
||||
|
||||
void _executePendingJob() {
|
||||
if (_rt == 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 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;
|
||||
}
|
||||
}
|
931
lib/src/ffi.dart
Normal file
931
lib/src/ffi.dart
Normal file
@@ -0,0 +1,931 @@
|
||||
/*
|
||||
* @Description: ffi
|
||||
* @Author: ekibun
|
||||
* @Date: 2020-09-19 10:29:04
|
||||
* @LastEditors: ekibun
|
||||
* @LastEditTime: 2020-12-02 11:14:35
|
||||
*/
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
abstract class JSRef {
|
||||
bool leakable = false;
|
||||
void release();
|
||||
}
|
||||
|
||||
class JSEvalFlag {
|
||||
static const GLOBAL = 0 << 0;
|
||||
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);
|
||||
static const ENUMERABLE = (1 << 2);
|
||||
static const C_W_E = (CONFIGURABLE | WRITABLE | ENUMERABLE);
|
||||
}
|
||||
|
||||
class JSTag {
|
||||
static const FIRST = -11; /* first negative tag */
|
||||
static const BIG_DECIMAL = -11;
|
||||
static const BIG_INT = -10;
|
||||
static const BIG_FLOAT = -9;
|
||||
static const SYMBOL = -8;
|
||||
static const STRING = -7;
|
||||
static const MODULE = -3; /* used internally */
|
||||
static const FUNCTION_BYTECODE = -2; /* used internally */
|
||||
static const OBJECT = -1;
|
||||
|
||||
static const INT = 0;
|
||||
static const BOOL = 1;
|
||||
static const NULL = 2;
|
||||
static const UNDEFINED = 3;
|
||||
static const UNINITIALIZED = 4;
|
||||
static const CATCH_OFFSET = 5;
|
||||
static const EXCEPTION = 6;
|
||||
static const FLOAT64 = 7;
|
||||
}
|
||||
|
||||
final DynamicLibrary _qjsLib = Platform.environment['FLUTTER_TEST'] == 'true'
|
||||
? (Platform.isWindows
|
||||
? DynamicLibrary.open('test/build/Debug/ffiquickjs.dll')
|
||||
: Platform.isMacOS
|
||||
? DynamicLibrary.open('test/build/libffiquickjs.dylib')
|
||||
: DynamicLibrary.open('test/build/libffiquickjs.so'))
|
||||
: (Platform.isWindows
|
||||
? DynamicLibrary.open('flutter_qjs_plugin.dll')
|
||||
: Platform.isAndroid
|
||||
? DynamicLibrary.open('libqjs.so')
|
||||
: DynamicLibrary.process());
|
||||
|
||||
/// DLLEXPORT JSValue *jsThrow(JSContext *ctx, JSValue *obj)
|
||||
final Pointer Function(
|
||||
Pointer ctx,
|
||||
Pointer obj,
|
||||
) jsThrow = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Pointer Function(
|
||||
Pointer,
|
||||
Pointer,
|
||||
)>>('jsThrow')
|
||||
.asFunction();
|
||||
|
||||
/// JSValue *jsEXCEPTION()
|
||||
final Pointer Function() jsEXCEPTION = _qjsLib
|
||||
.lookup<NativeFunction<Pointer Function()>>('jsEXCEPTION')
|
||||
.asFunction();
|
||||
|
||||
/// JSValue *jsUNDEFINED()
|
||||
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<_JSChannelNative>>,
|
||||
) _jsNewRuntime = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Pointer Function(
|
||||
Pointer,
|
||||
)>>('jsNewRuntime')
|
||||
.asFunction();
|
||||
|
||||
class _RuntimeOpaque {
|
||||
_JSChannel _channel;
|
||||
List<JSRef> _ref = [];
|
||||
ReceivePort _port;
|
||||
int _dartObjectClassId;
|
||||
get dartObjectClassId => _dartObjectClassId;
|
||||
|
||||
void addRef(JSRef ref) => _ref.add(ref);
|
||||
|
||||
bool removeRef(JSRef ref) => _ref.remove(ref);
|
||||
|
||||
JSRef getRef(bool Function(JSRef ref) test) {
|
||||
return _ref.firstWhere(test, orElse: () => null);
|
||||
}
|
||||
}
|
||||
|
||||
final Map<Pointer, _RuntimeOpaque> runtimeOpaques = Map();
|
||||
|
||||
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(
|
||||
_JSChannel callback,
|
||||
ReceivePort port,
|
||||
) {
|
||||
final rt = _jsNewRuntime(Pointer.fromFunction(channelDispacher));
|
||||
runtimeOpaques[rt] = _RuntimeOpaque()
|
||||
.._channel = callback
|
||||
.._port = port;
|
||||
return rt;
|
||||
}
|
||||
|
||||
/// DLLEXPORT void jsSetMaxStackSize(JSRuntime *rt, size_t stack_size)
|
||||
final void Function(
|
||||
Pointer,
|
||||
int,
|
||||
) jsSetMaxStackSize = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Void Function(
|
||||
Pointer,
|
||||
IntPtr,
|
||||
)>>('jsSetMaxStackSize')
|
||||
.asFunction();
|
||||
|
||||
/// void jsFreeRuntime(JSRuntime *rt)
|
||||
final void Function(
|
||||
Pointer,
|
||||
) _jsFreeRuntime = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Void Function(
|
||||
Pointer,
|
||||
)>>('jsFreeRuntime')
|
||||
.asFunction();
|
||||
|
||||
void jsFreeRuntime(
|
||||
Pointer rt,
|
||||
) {
|
||||
final referenceleak = <String>[];
|
||||
while (true) {
|
||||
final ref = runtimeOpaques[rt]
|
||||
?._ref
|
||||
?.firstWhere((ref) => ref.leakable, orElse: () => null);
|
||||
if (ref == null) break;
|
||||
ref.release();
|
||||
runtimeOpaques[rt]?._ref?.remove(ref);
|
||||
}
|
||||
while (0 < runtimeOpaques[rt]?._ref?.length ?? 0) {
|
||||
final ref = runtimeOpaques[rt]?._ref?.first;
|
||||
assert(!ref.leakable);
|
||||
referenceleak.add(
|
||||
" ${identityHashCode(ref)}\t${ref.runtimeType.toString()}\t${ref.toString().replaceAll('\n', '\\n')}");
|
||||
ref.release();
|
||||
runtimeOpaques[rt]?._ref?.remove(ref);
|
||||
}
|
||||
_jsFreeRuntime(rt);
|
||||
if (referenceleak.length > 0) {
|
||||
throw ('reference leak:\n ADDR\t TYPE \t PROP\n' +
|
||||
referenceleak.join('\n'));
|
||||
}
|
||||
}
|
||||
|
||||
/// JSValue *jsNewCFunction(JSContext *ctx, JSValue *funcData)
|
||||
final Pointer Function(
|
||||
Pointer ctx,
|
||||
Pointer funcData,
|
||||
) jsNewCFunction = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Pointer Function(
|
||||
Pointer,
|
||||
Pointer,
|
||||
)>>('jsNewCFunction')
|
||||
.asFunction();
|
||||
|
||||
/// JSContext *jsNewContext(JSRuntime *rt)
|
||||
final Pointer Function(
|
||||
Pointer rt,
|
||||
) _jsNewContext = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Pointer Function(
|
||||
Pointer,
|
||||
)>>('jsNewContext')
|
||||
.asFunction();
|
||||
|
||||
Pointer jsNewContext(Pointer rt) {
|
||||
final ctx = _jsNewContext(rt);
|
||||
final runtimeOpaque = runtimeOpaques[rt];
|
||||
if (runtimeOpaque == null) throw Exception('Runtime has been released!');
|
||||
runtimeOpaque._dartObjectClassId = jsNewClass(ctx, 'DartObject');
|
||||
return ctx;
|
||||
}
|
||||
|
||||
/// void jsFreeContext(JSContext *ctx)
|
||||
final void Function(
|
||||
Pointer,
|
||||
) jsFreeContext = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Void Function(
|
||||
Pointer,
|
||||
)>>('jsFreeContext')
|
||||
.asFunction();
|
||||
|
||||
/// JSRuntime *jsGetRuntime(JSContext *ctx)
|
||||
final Pointer Function(
|
||||
Pointer,
|
||||
) jsGetRuntime = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Pointer Function(
|
||||
Pointer,
|
||||
)>>('jsGetRuntime')
|
||||
.asFunction();
|
||||
|
||||
/// JSValue *jsEval(JSContext *ctx, const char *input, size_t input_len, const char *filename, int eval_flags)
|
||||
final Pointer Function(
|
||||
Pointer ctx,
|
||||
Pointer<Utf8> input,
|
||||
int inputLen,
|
||||
Pointer<Utf8> filename,
|
||||
int evalFlags,
|
||||
) _jsEval = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Pointer Function(
|
||||
Pointer,
|
||||
Pointer<Utf8>,
|
||||
IntPtr,
|
||||
Pointer<Utf8>,
|
||||
Int32,
|
||||
)>>('jsEval')
|
||||
.asFunction();
|
||||
|
||||
Pointer jsEval(
|
||||
Pointer ctx,
|
||||
String input,
|
||||
String filename,
|
||||
int evalFlags,
|
||||
) {
|
||||
final utf8input = Utf8.toUtf8(input);
|
||||
final utf8filename = Utf8.toUtf8(filename);
|
||||
final val = _jsEval(
|
||||
ctx,
|
||||
utf8input,
|
||||
Utf8.strlen(utf8input),
|
||||
utf8filename,
|
||||
evalFlags,
|
||||
);
|
||||
free(utf8input);
|
||||
free(utf8filename);
|
||||
runtimeOpaques[jsGetRuntime(ctx)]._port.sendPort.send(#eval);
|
||||
return val;
|
||||
}
|
||||
|
||||
/// DLLEXPORT int32_t jsValueGetTag(JSValue *val)
|
||||
final int Function(
|
||||
Pointer val,
|
||||
) jsValueGetTag = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Int32 Function(
|
||||
Pointer,
|
||||
)>>('jsValueGetTag')
|
||||
.asFunction();
|
||||
|
||||
/// void *jsValueGetPtr(JSValue *val)
|
||||
final Pointer Function(
|
||||
Pointer val,
|
||||
) jsValueGetPtr = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Pointer Function(
|
||||
Pointer,
|
||||
)>>('jsValueGetPtr')
|
||||
.asFunction();
|
||||
|
||||
/// DLLEXPORT bool jsTagIsFloat64(int32_t tag)
|
||||
final int Function(
|
||||
int val,
|
||||
) jsTagIsFloat64 = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Int32 Function(
|
||||
Int32,
|
||||
)>>('jsTagIsFloat64')
|
||||
.asFunction();
|
||||
|
||||
/// JSValue *jsNewBool(JSContext *ctx, int val)
|
||||
final Pointer Function(
|
||||
Pointer ctx,
|
||||
int val,
|
||||
) jsNewBool = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Pointer Function(
|
||||
Pointer,
|
||||
Int32,
|
||||
)>>('jsNewBool')
|
||||
.asFunction();
|
||||
|
||||
/// JSValue *jsNewInt64(JSContext *ctx, int64_t val)
|
||||
final Pointer Function(
|
||||
Pointer ctx,
|
||||
int val,
|
||||
) jsNewInt64 = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Pointer Function(
|
||||
Pointer,
|
||||
Int64,
|
||||
)>>('jsNewInt64')
|
||||
.asFunction();
|
||||
|
||||
/// JSValue *jsNewFloat64(JSContext *ctx, double val)
|
||||
final Pointer Function(
|
||||
Pointer ctx,
|
||||
double val,
|
||||
) jsNewFloat64 = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Pointer Function(
|
||||
Pointer,
|
||||
Double,
|
||||
)>>('jsNewFloat64')
|
||||
.asFunction();
|
||||
|
||||
/// JSValue *jsNewString(JSContext *ctx, const char *str)
|
||||
final Pointer Function(
|
||||
Pointer ctx,
|
||||
Pointer<Utf8> str,
|
||||
) _jsNewString = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Pointer Function(
|
||||
Pointer,
|
||||
Pointer<Utf8>,
|
||||
)>>('jsNewString')
|
||||
.asFunction();
|
||||
|
||||
Pointer jsNewString(
|
||||
Pointer ctx,
|
||||
String str,
|
||||
) {
|
||||
final utf8str = Utf8.toUtf8(str);
|
||||
final jsStr = _jsNewString(ctx, utf8str);
|
||||
free(utf8str);
|
||||
return jsStr;
|
||||
}
|
||||
|
||||
/// JSValue *jsNewArrayBufferCopy(JSContext *ctx, const uint8_t *buf, size_t len)
|
||||
final Pointer Function(
|
||||
Pointer ctx,
|
||||
Pointer<Uint8> buf,
|
||||
int len,
|
||||
) jsNewArrayBufferCopy = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Pointer Function(
|
||||
Pointer,
|
||||
Pointer<Uint8>,
|
||||
IntPtr,
|
||||
)>>('jsNewArrayBufferCopy')
|
||||
.asFunction();
|
||||
|
||||
/// JSValue *jsNewArray(JSContext *ctx)
|
||||
final Pointer Function(
|
||||
Pointer ctx,
|
||||
) jsNewArray = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Pointer Function(
|
||||
Pointer,
|
||||
)>>('jsNewArray')
|
||||
.asFunction();
|
||||
|
||||
/// JSValue *jsNewObject(JSContext *ctx)
|
||||
final Pointer Function(
|
||||
Pointer ctx,
|
||||
) jsNewObject = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Pointer Function(
|
||||
Pointer,
|
||||
)>>('jsNewObject')
|
||||
.asFunction();
|
||||
|
||||
/// void jsFreeValue(JSContext *ctx, JSValue *val, int32_t free)
|
||||
final void Function(
|
||||
Pointer ctx,
|
||||
Pointer val,
|
||||
int free,
|
||||
) _jsFreeValue = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Void Function(
|
||||
Pointer,
|
||||
Pointer,
|
||||
Int32,
|
||||
)>>('jsFreeValue')
|
||||
.asFunction();
|
||||
|
||||
void jsFreeValue(
|
||||
Pointer ctx,
|
||||
Pointer val, {
|
||||
bool free = true,
|
||||
}) {
|
||||
_jsFreeValue(ctx, val, free ? 1 : 0);
|
||||
}
|
||||
|
||||
/// void jsFreeValue(JSRuntime *rt, JSValue *val, int32_t free)
|
||||
final void Function(
|
||||
Pointer rt,
|
||||
Pointer val,
|
||||
int free,
|
||||
) _jsFreeValueRT = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Void Function(
|
||||
Pointer,
|
||||
Pointer,
|
||||
Int32,
|
||||
)>>('jsFreeValueRT')
|
||||
.asFunction();
|
||||
|
||||
void jsFreeValueRT(
|
||||
Pointer rt,
|
||||
Pointer val, {
|
||||
bool free = true,
|
||||
}) {
|
||||
_jsFreeValueRT(rt, val, free ? 1 : 0);
|
||||
}
|
||||
|
||||
/// JSValue *jsDupValue(JSContext *ctx, JSValueConst *v)
|
||||
final Pointer Function(
|
||||
Pointer ctx,
|
||||
Pointer val,
|
||||
) jsDupValue = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Pointer Function(
|
||||
Pointer,
|
||||
Pointer,
|
||||
)>>('jsDupValue')
|
||||
.asFunction();
|
||||
|
||||
/// JSValue *jsDupValueRT(JSRuntime *rt, JSValue *v)
|
||||
final Pointer Function(
|
||||
Pointer rt,
|
||||
Pointer val,
|
||||
) jsDupValueRT = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Pointer Function(
|
||||
Pointer,
|
||||
Pointer,
|
||||
)>>('jsDupValueRT')
|
||||
.asFunction();
|
||||
|
||||
/// int32_t jsToBool(JSContext *ctx, JSValueConst *val)
|
||||
final int Function(
|
||||
Pointer ctx,
|
||||
Pointer val,
|
||||
) jsToBool = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Int32 Function(
|
||||
Pointer,
|
||||
Pointer,
|
||||
)>>('jsToBool')
|
||||
.asFunction();
|
||||
|
||||
/// int64_t jsToFloat64(JSContext *ctx, JSValueConst *val)
|
||||
final int Function(
|
||||
Pointer ctx,
|
||||
Pointer val,
|
||||
) jsToInt64 = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Int64 Function(
|
||||
Pointer,
|
||||
Pointer,
|
||||
)>>('jsToInt64')
|
||||
.asFunction();
|
||||
|
||||
/// double jsToFloat64(JSContext *ctx, JSValueConst *val)
|
||||
final double Function(
|
||||
Pointer ctx,
|
||||
Pointer val,
|
||||
) jsToFloat64 = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Double Function(
|
||||
Pointer,
|
||||
Pointer,
|
||||
)>>('jsToFloat64')
|
||||
.asFunction();
|
||||
|
||||
/// const char *jsToCString(JSContext *ctx, JSValue *val)
|
||||
final Pointer<Utf8> Function(
|
||||
Pointer ctx,
|
||||
Pointer val,
|
||||
) _jsToCString = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Pointer<Utf8> Function(
|
||||
Pointer,
|
||||
Pointer,
|
||||
)>>('jsToCString')
|
||||
.asFunction();
|
||||
|
||||
/// void jsFreeCString(JSContext *ctx, const char *ptr)
|
||||
final void Function(
|
||||
Pointer ctx,
|
||||
Pointer<Utf8> val,
|
||||
) jsFreeCString = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Void Function(
|
||||
Pointer,
|
||||
Pointer<Utf8>,
|
||||
)>>('jsFreeCString')
|
||||
.asFunction();
|
||||
|
||||
String jsToCString(
|
||||
Pointer ctx,
|
||||
Pointer val,
|
||||
) {
|
||||
final ptr = _jsToCString(ctx, val);
|
||||
if (ptr.address == 0) throw Exception('JSValue cannot convert to string');
|
||||
final str = Utf8.fromUtf8(ptr);
|
||||
jsFreeCString(ctx, ptr);
|
||||
return str;
|
||||
}
|
||||
|
||||
/// DLLEXPORT uint32_t jsNewClass(JSContext *ctx, const char *name)
|
||||
final int Function(
|
||||
Pointer ctx,
|
||||
Pointer<Utf8> name,
|
||||
) _jsNewClass = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Uint32 Function(
|
||||
Pointer,
|
||||
Pointer<Utf8>,
|
||||
)>>('jsNewClass')
|
||||
.asFunction();
|
||||
|
||||
int jsNewClass(
|
||||
Pointer ctx,
|
||||
String name,
|
||||
) {
|
||||
final utf8name = Utf8.toUtf8(name);
|
||||
final val = _jsNewClass(
|
||||
ctx,
|
||||
utf8name,
|
||||
);
|
||||
free(utf8name);
|
||||
return val;
|
||||
}
|
||||
|
||||
/// DLLEXPORT JSValue *jsNewObjectClass(JSContext *ctx, uint32_t QJSClassId, void *opaque)
|
||||
final Pointer Function(
|
||||
Pointer ctx,
|
||||
int classId,
|
||||
int opaque,
|
||||
) jsNewObjectClass = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Pointer Function(
|
||||
Pointer,
|
||||
Uint32,
|
||||
IntPtr,
|
||||
)>>('jsNewObjectClass')
|
||||
.asFunction();
|
||||
|
||||
/// DLLEXPORT void *jsGetObjectOpaque(JSValue *obj, uint32_t classid)
|
||||
final int Function(
|
||||
Pointer obj,
|
||||
int classid,
|
||||
) jsGetObjectOpaque = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
IntPtr Function(
|
||||
Pointer,
|
||||
Uint32,
|
||||
)>>('jsGetObjectOpaque')
|
||||
.asFunction();
|
||||
|
||||
/// uint8_t *jsGetArrayBuffer(JSContext *ctx, size_t *psize, JSValueConst *obj)
|
||||
final Pointer<Uint8> Function(
|
||||
Pointer ctx,
|
||||
Pointer<IntPtr> psize,
|
||||
Pointer val,
|
||||
) jsGetArrayBuffer = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Pointer<Uint8> Function(
|
||||
Pointer,
|
||||
Pointer<IntPtr>,
|
||||
Pointer,
|
||||
)>>('jsGetArrayBuffer')
|
||||
.asFunction();
|
||||
|
||||
/// int32_t jsIsFunction(JSContext *ctx, JSValueConst *val)
|
||||
final int Function(
|
||||
Pointer ctx,
|
||||
Pointer val,
|
||||
) jsIsFunction = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Int32 Function(
|
||||
Pointer,
|
||||
Pointer,
|
||||
)>>('jsIsFunction')
|
||||
.asFunction();
|
||||
|
||||
/// int32_t jsIsPromise(JSContext *ctx, JSValueConst *val)
|
||||
final int Function(
|
||||
Pointer ctx,
|
||||
Pointer val,
|
||||
) jsIsPromise = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Int32 Function(
|
||||
Pointer,
|
||||
Pointer,
|
||||
)>>('jsIsPromise')
|
||||
.asFunction();
|
||||
|
||||
/// int32_t jsIsArray(JSContext *ctx, JSValueConst *val)
|
||||
final int Function(
|
||||
Pointer ctx,
|
||||
Pointer val,
|
||||
) jsIsArray = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Int32 Function(
|
||||
Pointer,
|
||||
Pointer,
|
||||
)>>('jsIsArray')
|
||||
.asFunction();
|
||||
|
||||
/// DLLEXPORT int32_t jsIsError(JSContext *ctx, JSValueConst *val);
|
||||
final int Function(
|
||||
Pointer ctx,
|
||||
Pointer val,
|
||||
) jsIsError = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Int32 Function(
|
||||
Pointer,
|
||||
Pointer,
|
||||
)>>('jsIsError')
|
||||
.asFunction();
|
||||
|
||||
/// DLLEXPORT JSValue *jsNewError(JSContext *ctx);
|
||||
final Pointer Function(
|
||||
Pointer ctx,
|
||||
) jsNewError = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Pointer Function(
|
||||
Pointer,
|
||||
)>>('jsNewError')
|
||||
.asFunction();
|
||||
|
||||
/// JSValue *jsGetProperty(JSContext *ctx, JSValueConst *this_obj,
|
||||
/// JSAtom prop)
|
||||
final Pointer Function(
|
||||
Pointer ctx,
|
||||
Pointer thisObj,
|
||||
int prop,
|
||||
) jsGetProperty = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Pointer Function(
|
||||
Pointer,
|
||||
Pointer,
|
||||
Uint32,
|
||||
)>>('jsGetProperty')
|
||||
.asFunction();
|
||||
|
||||
/// int jsDefinePropertyValue(JSContext *ctx, JSValueConst *this_obj,
|
||||
/// JSAtom prop, JSValue *val, int flags)
|
||||
final int Function(
|
||||
Pointer ctx,
|
||||
Pointer thisObj,
|
||||
int prop,
|
||||
Pointer val,
|
||||
int flag,
|
||||
) jsDefinePropertyValue = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Int32 Function(
|
||||
Pointer,
|
||||
Pointer,
|
||||
Uint32,
|
||||
Pointer,
|
||||
Int32,
|
||||
)>>('jsDefinePropertyValue')
|
||||
.asFunction();
|
||||
|
||||
/// void jsFreeAtom(JSContext *ctx, JSAtom v)
|
||||
final Pointer Function(
|
||||
Pointer ctx,
|
||||
int v,
|
||||
) jsFreeAtom = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Pointer Function(
|
||||
Pointer,
|
||||
Uint32,
|
||||
)>>('jsFreeAtom')
|
||||
.asFunction();
|
||||
|
||||
/// JSAtom jsValueToAtom(JSContext *ctx, JSValueConst *val)
|
||||
final int Function(
|
||||
Pointer ctx,
|
||||
Pointer val,
|
||||
) jsValueToAtom = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Uint32 Function(
|
||||
Pointer,
|
||||
Pointer,
|
||||
)>>('jsValueToAtom')
|
||||
.asFunction();
|
||||
|
||||
/// JSValue *jsAtomToValue(JSContext *ctx, JSAtom val)
|
||||
final Pointer Function(
|
||||
Pointer ctx,
|
||||
int val,
|
||||
) jsAtomToValue = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Pointer Function(
|
||||
Pointer,
|
||||
Uint32,
|
||||
)>>('jsAtomToValue')
|
||||
.asFunction();
|
||||
|
||||
/// int jsGetOwnPropertyNames(JSContext *ctx, JSPropertyEnum **ptab,
|
||||
/// uint32_t *plen, JSValueConst *obj, int flags)
|
||||
final int Function(
|
||||
Pointer ctx,
|
||||
Pointer<Pointer> ptab,
|
||||
Pointer<Uint32> plen,
|
||||
Pointer obj,
|
||||
int flags,
|
||||
) jsGetOwnPropertyNames = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Int32 Function(
|
||||
Pointer,
|
||||
Pointer<Pointer>,
|
||||
Pointer<Uint32>,
|
||||
Pointer,
|
||||
Int32,
|
||||
)>>('jsGetOwnPropertyNames')
|
||||
.asFunction();
|
||||
|
||||
/// JSAtom jsPropertyEnumGetAtom(JSPropertyEnum *ptab, int i)
|
||||
final int Function(
|
||||
Pointer ptab,
|
||||
int i,
|
||||
) jsPropertyEnumGetAtom = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Uint32 Function(
|
||||
Pointer,
|
||||
Int32,
|
||||
)>>('jsPropertyEnumGetAtom')
|
||||
.asFunction();
|
||||
|
||||
/// uint32_t sizeOfJSValue()
|
||||
final int Function() _sizeOfJSValue = _qjsLib
|
||||
.lookup<NativeFunction<Uint32 Function()>>('sizeOfJSValue')
|
||||
.asFunction();
|
||||
|
||||
final sizeOfJSValue = _sizeOfJSValue();
|
||||
|
||||
/// void setJSValueList(JSValue *list, int i, JSValue *val)
|
||||
final void Function(
|
||||
Pointer list,
|
||||
int i,
|
||||
Pointer val,
|
||||
) setJSValueList = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Void Function(
|
||||
Pointer,
|
||||
Uint32,
|
||||
Pointer,
|
||||
)>>('setJSValueList')
|
||||
.asFunction();
|
||||
|
||||
/// JSValue *jsCall(JSContext *ctx, JSValueConst *func_obj, JSValueConst *this_obj,
|
||||
/// int argc, JSValueConst *argv)
|
||||
final Pointer Function(
|
||||
Pointer ctx,
|
||||
Pointer funcObj,
|
||||
Pointer thisObj,
|
||||
int argc,
|
||||
Pointer argv,
|
||||
) _jsCall = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Pointer Function(
|
||||
Pointer,
|
||||
Pointer,
|
||||
Pointer,
|
||||
Int32,
|
||||
Pointer,
|
||||
)>>('jsCall')
|
||||
.asFunction();
|
||||
|
||||
Pointer jsCall(
|
||||
Pointer ctx,
|
||||
Pointer funcObj,
|
||||
Pointer thisObj,
|
||||
List<Pointer> argv,
|
||||
) {
|
||||
Pointer jsArgs = allocate<Uint8>(
|
||||
count: argv.length > 0 ? sizeOfJSValue * argv.length : 1,
|
||||
);
|
||||
for (int i = 0; i < argv.length; ++i) {
|
||||
Pointer jsArg = argv[i];
|
||||
setJSValueList(jsArgs, i, jsArg);
|
||||
}
|
||||
Pointer func1 = jsDupValue(ctx, funcObj);
|
||||
Pointer _thisObj = thisObj ?? jsUNDEFINED();
|
||||
Pointer jsRet = _jsCall(ctx, funcObj, _thisObj, argv.length, jsArgs);
|
||||
if (thisObj == null) {
|
||||
jsFreeValue(ctx, _thisObj);
|
||||
}
|
||||
jsFreeValue(ctx, func1);
|
||||
free(jsArgs);
|
||||
runtimeOpaques[jsGetRuntime(ctx)]._port.sendPort.send(#call);
|
||||
return jsRet;
|
||||
}
|
||||
|
||||
/// int jsIsException(JSValueConst *val)
|
||||
final int Function(
|
||||
Pointer val,
|
||||
) jsIsException = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Int32 Function(
|
||||
Pointer,
|
||||
)>>('jsIsException')
|
||||
.asFunction();
|
||||
|
||||
/// JSValue *jsGetException(JSContext *ctx)
|
||||
final Pointer Function(
|
||||
Pointer ctx,
|
||||
) jsGetException = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Pointer Function(
|
||||
Pointer,
|
||||
)>>('jsGetException')
|
||||
.asFunction();
|
||||
|
||||
/// int jsExecutePendingJob(JSRuntime *rt)
|
||||
final int Function(
|
||||
Pointer ctx,
|
||||
) jsExecutePendingJob = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Int32 Function(
|
||||
Pointer,
|
||||
)>>('jsExecutePendingJob')
|
||||
.asFunction();
|
||||
|
||||
/// JSValue *jsNewPromiseCapability(JSContext *ctx, JSValue *resolving_funcs)
|
||||
final Pointer Function(
|
||||
Pointer ctx,
|
||||
Pointer resolvingFuncs,
|
||||
) jsNewPromiseCapability = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Pointer Function(
|
||||
Pointer,
|
||||
Pointer,
|
||||
)>>('jsNewPromiseCapability')
|
||||
.asFunction();
|
||||
|
||||
/// void jsFree(JSContext *ctx, void *ptab)
|
||||
final void Function(
|
||||
Pointer ctx,
|
||||
Pointer ptab,
|
||||
) jsFree = _qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Void Function(
|
||||
Pointer,
|
||||
Pointer,
|
||||
)>>('jsFree')
|
||||
.asFunction();
|
301
lib/src/isolate.dart
Normal file
301
lib/src/isolate.dart
Normal file
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
* @Description: isolate
|
||||
* @Author: ekibun
|
||||
* @Date: 2020-10-02 13:49:03
|
||||
* @LastEditors: ekibun
|
||||
* @LastEditTime: 2020-10-03 22:21:31
|
||||
*/
|
||||
part of '../flutter_qjs.dart';
|
||||
|
||||
typedef dynamic _Decode(Map obj, SendPort port);
|
||||
List<_Decode> _decoders = [
|
||||
JSError._decode,
|
||||
_IsolateJSFunction._decode,
|
||||
_IsolateFunction._decode,
|
||||
_JSFunction._decode,
|
||||
];
|
||||
|
||||
abstract class _IsolateEncodable {
|
||||
Map _encode();
|
||||
}
|
||||
|
||||
dynamic _encodeData(dynamic data, {Map<dynamic, dynamic> cache}) {
|
||||
if (cache == null) cache = Map();
|
||||
if (data is _IsolateEncodable) return data._encode();
|
||||
if (cache.containsKey(data)) return cache[data];
|
||||
if (data is List) {
|
||||
final ret = [];
|
||||
cache[data] = ret;
|
||||
for (int i = 0; i < data.length; ++i) {
|
||||
ret.add(_encodeData(data[i], cache: cache));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
if (data is Map) {
|
||||
final ret = {};
|
||||
cache[data] = ret;
|
||||
for (final entry in data.entries) {
|
||||
ret[_encodeData(entry.key, cache: cache)] =
|
||||
_encodeData(entry.value, cache: cache);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
if (data is Future) {
|
||||
final futurePort = ReceivePort();
|
||||
data.then((value) {
|
||||
futurePort.first.then((port) {
|
||||
futurePort.close();
|
||||
(port as SendPort).send(_encodeData(value));
|
||||
});
|
||||
}, onError: (e) {
|
||||
futurePort.first.then((port) {
|
||||
futurePort.close();
|
||||
(port as SendPort).send({#error: _encodeData(e)});
|
||||
});
|
||||
});
|
||||
return {
|
||||
#jsFuturePort: futurePort.sendPort,
|
||||
};
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
dynamic _decodeData(dynamic data, SendPort port,
|
||||
{Map<dynamic, dynamic> cache}) {
|
||||
if (cache == null) cache = Map();
|
||||
if (cache.containsKey(data)) return cache[data];
|
||||
if (data is List) {
|
||||
final ret = [];
|
||||
cache[data] = ret;
|
||||
for (int i = 0; i < data.length; ++i) {
|
||||
ret.add(_decodeData(data[i], port, cache: cache));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
if (data is Map) {
|
||||
for (final decoder in _decoders) {
|
||||
final decodeObj = decoder(data, port);
|
||||
if (decodeObj != null) return decodeObj;
|
||||
}
|
||||
if (data.containsKey(#jsFuturePort)) {
|
||||
SendPort port = data[#jsFuturePort];
|
||||
final futurePort = ReceivePort();
|
||||
port.send(futurePort.sendPort);
|
||||
final futureCompleter = Completer();
|
||||
futureCompleter.future.catchError((e) {});
|
||||
futurePort.first.then((value) {
|
||||
futurePort.close();
|
||||
if (value is Map && value.containsKey(#error)) {
|
||||
futureCompleter.completeError(_decodeData(value[#error], port));
|
||||
} else {
|
||||
futureCompleter.complete(_decodeData(value, port));
|
||||
}
|
||||
});
|
||||
return futureCompleter.future;
|
||||
}
|
||||
final ret = {};
|
||||
cache[data] = ret;
|
||||
for (final entry in data.entries) {
|
||||
ret[_decodeData(entry.key, port, cache: cache)] =
|
||||
_decodeData(entry.value, port, cache: cache);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
void _runJsIsolate(Map spawnMessage) async {
|
||||
SendPort sendPort = spawnMessage[#port];
|
||||
ReceivePort port = ReceivePort();
|
||||
sendPort.send(port.sendPort);
|
||||
final qjs = FlutterQjs(
|
||||
stackSize: spawnMessage[#stackSize],
|
||||
hostPromiseRejectionHandler: (reason) {
|
||||
sendPort.send({
|
||||
#type: #hostPromiseRejection,
|
||||
#reason: _encodeData(reason),
|
||||
});
|
||||
},
|
||||
moduleHandler: (name) {
|
||||
final ptr = allocate<Pointer<Utf8>>();
|
||||
ptr.value = Pointer.fromAddress(0);
|
||||
sendPort.send({
|
||||
#type: #module,
|
||||
#name: name,
|
||||
#ptr: ptr.address,
|
||||
});
|
||||
while (ptr.value.address == 0) sleep(Duration.zero);
|
||||
if (ptr.value.address == -1) throw JSError('Module Not found');
|
||||
final ret = Utf8.fromUtf8(ptr.value);
|
||||
sendPort.send({
|
||||
#type: #release,
|
||||
#ptr: ptr.value.address,
|
||||
});
|
||||
free(ptr);
|
||||
return ret;
|
||||
},
|
||||
);
|
||||
port.listen((msg) async {
|
||||
var data;
|
||||
SendPort msgPort = msg[#port];
|
||||
try {
|
||||
switch (msg[#type]) {
|
||||
case #evaluate:
|
||||
data = await qjs.evaluate(
|
||||
msg[#command],
|
||||
name: msg[#name],
|
||||
evalFlags: msg[#flag],
|
||||
);
|
||||
break;
|
||||
case #call:
|
||||
data = await _JSFunction.fromAddress(
|
||||
Pointer.fromAddress(msg[#ctx]),
|
||||
Pointer.fromAddress(msg[#val]),
|
||||
).invoke(
|
||||
_decodeData(msg[#args], null),
|
||||
_decodeData(msg[#thisVal], null),
|
||||
);
|
||||
break;
|
||||
case #closeFunction:
|
||||
_JSFunction.fromAddress(
|
||||
Pointer.fromAddress(msg[#ctx]),
|
||||
Pointer.fromAddress(msg[#val]),
|
||||
).release();
|
||||
break;
|
||||
case #close:
|
||||
data = false;
|
||||
qjs.port.close();
|
||||
qjs.close();
|
||||
port.close();
|
||||
data = true;
|
||||
break;
|
||||
}
|
||||
if (msgPort != null) msgPort.send(_encodeData(data));
|
||||
} catch (e) {
|
||||
if (msgPort != null)
|
||||
msgPort.send({
|
||||
#error: _encodeData(e),
|
||||
});
|
||||
}
|
||||
});
|
||||
await qjs.dispatch();
|
||||
}
|
||||
|
||||
typedef _JsAsyncModuleHandler = Future<String> Function(String name);
|
||||
|
||||
class IsolateQjs {
|
||||
Future<SendPort> _sendPort;
|
||||
|
||||
/// Max stack size for quickjs.
|
||||
final int stackSize;
|
||||
|
||||
/// Asynchronously handler to manage js module.
|
||||
_JsAsyncModuleHandler moduleHandler;
|
||||
|
||||
/// Handler function to manage js module.
|
||||
_JsHostPromiseRejectionHandler hostPromiseRejectionHandler;
|
||||
|
||||
/// Quickjs engine runing on isolate thread.
|
||||
///
|
||||
/// 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.moduleHandler,
|
||||
this.stackSize,
|
||||
this.hostPromiseRejectionHandler,
|
||||
});
|
||||
|
||||
_ensureEngine() {
|
||||
if (_sendPort != null) return;
|
||||
ReceivePort port = ReceivePort();
|
||||
Isolate.spawn(
|
||||
_runJsIsolate,
|
||||
{
|
||||
#port: port.sendPort,
|
||||
#stackSize: stackSize,
|
||||
},
|
||||
errorsAreFatal: true,
|
||||
);
|
||||
final completer = Completer<SendPort>();
|
||||
port.listen((msg) async {
|
||||
if (msg is SendPort && !completer.isCompleted) {
|
||||
completer.complete(msg);
|
||||
return;
|
||||
}
|
||||
switch (msg[#type]) {
|
||||
case #hostPromiseRejection:
|
||||
try {
|
||||
final err = _decodeData(msg[#reason], port.sendPort);
|
||||
if (hostPromiseRejectionHandler != null) {
|
||||
hostPromiseRejectionHandler(err);
|
||||
} else {
|
||||
print('unhandled promise rejection: $err');
|
||||
}
|
||||
} catch (e) {
|
||||
print('host Promise Rejection Handler error: $e');
|
||||
}
|
||||
break;
|
||||
case #module:
|
||||
final ptr = Pointer<Pointer>.fromAddress(msg[#ptr]);
|
||||
try {
|
||||
ptr.value = Utf8.toUtf8(await moduleHandler(msg[#name]));
|
||||
} catch (e) {
|
||||
ptr.value = Pointer.fromAddress(-1);
|
||||
}
|
||||
break;
|
||||
case #release:
|
||||
free(Pointer.fromAddress(msg[#ptr]));
|
||||
break;
|
||||
}
|
||||
}, onDone: () {
|
||||
close();
|
||||
if (!completer.isCompleted)
|
||||
completer.completeError(JSError('isolate close'));
|
||||
});
|
||||
_sendPort = completer.future;
|
||||
}
|
||||
|
||||
/// Create isolate function
|
||||
Future<_IsolateFunction> bind(Function func) async {
|
||||
_ensureEngine();
|
||||
return _IsolateFunction._bind(func, await _sendPort);
|
||||
}
|
||||
|
||||
/// Free Runtime and close isolate thread that can be recreate when evaluate again.
|
||||
close() {
|
||||
if (_sendPort == null) return;
|
||||
final ret = _sendPort.then((sendPort) async {
|
||||
final closePort = ReceivePort();
|
||||
sendPort.send({
|
||||
#type: #close,
|
||||
#port: closePort.sendPort,
|
||||
});
|
||||
final result = await closePort.first;
|
||||
closePort.close();
|
||||
if (result is Map && result.containsKey(#error))
|
||||
throw _decodeData(result[#error], sendPort);
|
||||
return _decodeData(result, sendPort);
|
||||
});
|
||||
_sendPort = null;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Evaluate js script.
|
||||
Future<dynamic> evaluate(String command, {String name, int evalFlags}) async {
|
||||
_ensureEngine();
|
||||
final evaluatePort = ReceivePort();
|
||||
final sendPort = await _sendPort;
|
||||
sendPort.send({
|
||||
#type: #evaluate,
|
||||
#command: command,
|
||||
#name: name,
|
||||
#flag: evalFlags,
|
||||
#port: evaluatePort.sendPort,
|
||||
});
|
||||
final result = await evaluatePort.first;
|
||||
evaluatePort.close();
|
||||
if (result is Map && result.containsKey(#error))
|
||||
throw _decodeData(result[#error], sendPort);
|
||||
return _decodeData(result, sendPort);
|
||||
}
|
||||
}
|
342
lib/src/object.dart
Normal file
342
lib/src/object.dart
Normal file
@@ -0,0 +1,342 @@
|
||||
/*
|
||||
* @Description: wrap object
|
||||
* @Author: ekibun
|
||||
* @Date: 2020-10-02 13:49:03
|
||||
* @LastEditors: ekibun
|
||||
* @LastEditTime: 2020-10-03 22:21:31
|
||||
*/
|
||||
part of '../flutter_qjs.dart';
|
||||
|
||||
/// js invokable
|
||||
abstract class JSInvokable extends JSReleasable {
|
||||
dynamic invoke(List args, [dynamic thisVal]);
|
||||
|
||||
static dynamic _wrap(dynamic func) {
|
||||
return func is JSInvokable
|
||||
? func
|
||||
: func is Function
|
||||
? _DartFunction(func)
|
||||
: func;
|
||||
}
|
||||
|
||||
@override
|
||||
noSuchMethod(Invocation invocation) {
|
||||
return invoke(
|
||||
invocation.positionalArguments,
|
||||
invocation.namedArguments[#thisVal],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DartFunction extends JSInvokable {
|
||||
final Function _func;
|
||||
_DartFunction(this._func);
|
||||
|
||||
@override
|
||||
invoke(List args, [thisVal]) {
|
||||
/// wrap this into function
|
||||
final passThis =
|
||||
RegExp('{.*thisVal.*}').hasMatch(_func.runtimeType.toString());
|
||||
return Function.apply(_func, args, passThis ? {#thisVal: thisVal} : null);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _func.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
release() {}
|
||||
}
|
||||
|
||||
/// implement this to capture js object release.
|
||||
abstract class JSReleasable {
|
||||
void release();
|
||||
}
|
||||
|
||||
class _DartObject extends JSRef {
|
||||
@override
|
||||
bool leakable = true;
|
||||
|
||||
Object _obj;
|
||||
Pointer _ctx;
|
||||
_DartObject(this._ctx, this._obj) {
|
||||
runtimeOpaques[jsGetRuntime(_ctx)]?.addRef(this);
|
||||
}
|
||||
|
||||
static _DartObject fromAddress(Pointer rt, int val) {
|
||||
return runtimeOpaques[rt]?.getRef((e) => identityHashCode(e) == val);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
if (_ctx == null) return "DartObject(<released>)";
|
||||
return _obj.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
void release() {
|
||||
if (_ctx == null) return;
|
||||
runtimeOpaques[jsGetRuntime(_ctx)]?.removeRef(this);
|
||||
_ctx = null;
|
||||
if (_obj is JSReleasable) {
|
||||
(_obj as JSReleasable).release();
|
||||
}
|
||||
_obj = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// JS Error wrapper
|
||||
class JSError extends _IsolateEncodable {
|
||||
String message;
|
||||
String stack;
|
||||
JSError(message, [stack]) {
|
||||
if (message is JSError) {
|
||||
this.message = message.message;
|
||||
this.stack = message.stack;
|
||||
} else {
|
||||
this.message = message.toString();
|
||||
this.stack = (stack ?? StackTrace.current).toString();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return stack == null ? message.toString() : "$message\n$stack";
|
||||
}
|
||||
|
||||
static JSError _decode(Map obj, SendPort port) {
|
||||
if (obj.containsKey(#jsError))
|
||||
return JSError(obj[#jsError], obj[#jsErrorStack]);
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Map _encode() {
|
||||
return {
|
||||
#jsError: message,
|
||||
#jsErrorStack: stack,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// JS Object reference
|
||||
/// call [release] to release js object.
|
||||
class _JSObject extends JSRef {
|
||||
Pointer _val;
|
||||
Pointer _ctx;
|
||||
|
||||
/// Create
|
||||
_JSObject(this._ctx, Pointer _val) {
|
||||
Pointer rt = jsGetRuntime(_ctx);
|
||||
this._val = jsDupValue(_ctx, _val);
|
||||
runtimeOpaques[rt]?.addRef(this);
|
||||
}
|
||||
|
||||
static _JSObject fromAddress(Pointer ctx, Pointer val) {
|
||||
Pointer rt = jsGetRuntime(ctx);
|
||||
return runtimeOpaques[rt]?.getRef((e) =>
|
||||
e is _JSObject &&
|
||||
e._val.address == val.address &&
|
||||
e._ctx.address == ctx.address);
|
||||
}
|
||||
|
||||
@override
|
||||
void release() {
|
||||
if (_val == null) return;
|
||||
Pointer rt = jsGetRuntime(_ctx);
|
||||
runtimeOpaques[rt]?.removeRef(this);
|
||||
jsFreeValue(_ctx, _val);
|
||||
_val = null;
|
||||
_ctx = null;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
if (_val == null) return "JSObject(released)";
|
||||
return jsToCString(_ctx, _val);
|
||||
}
|
||||
}
|
||||
|
||||
/// JS function wrapper
|
||||
class _JSFunction extends _JSObject implements JSInvokable, _IsolateEncodable {
|
||||
_JSFunction(Pointer ctx, Pointer val) : super(ctx, val);
|
||||
|
||||
static _JSFunction fromAddress(Pointer ctx, Pointer val) {
|
||||
return _JSObject.fromAddress(ctx, val);
|
||||
}
|
||||
|
||||
@override
|
||||
invoke(List<dynamic> arguments, [dynamic thisVal]) {
|
||||
Pointer jsRet = _invoke(arguments, thisVal);
|
||||
if (jsRet == null) return;
|
||||
bool isException = jsIsException(jsRet) != 0;
|
||||
if (isException) {
|
||||
jsFreeValue(_ctx, jsRet);
|
||||
throw _parseJSException(_ctx);
|
||||
}
|
||||
final ret = _jsToDart(_ctx, jsRet);
|
||||
jsFreeValue(_ctx, jsRet);
|
||||
return ret;
|
||||
}
|
||||
|
||||
Pointer _invoke(List<dynamic> arguments, [dynamic thisVal]) {
|
||||
if (_val == null) return null;
|
||||
List<Pointer> args = arguments
|
||||
.map(
|
||||
(e) => _dartToJs(_ctx, e),
|
||||
)
|
||||
.toList();
|
||||
Pointer jsThis = _dartToJs(_ctx, thisVal);
|
||||
Pointer jsRet = jsCall(_ctx, _val, jsThis, args);
|
||||
jsFreeValue(_ctx, jsThis);
|
||||
for (Pointer jsArg in args) {
|
||||
jsFreeValue(_ctx, jsArg);
|
||||
}
|
||||
return jsRet;
|
||||
}
|
||||
|
||||
static _JSFunction _decode(Map obj, SendPort port) {
|
||||
if (obj.containsKey(#jsFunction) && port == null)
|
||||
return _JSFunction.fromAddress(
|
||||
Pointer.fromAddress(obj[#jsFunctionCtx]),
|
||||
Pointer.fromAddress(obj[#jsFunction]),
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Map _encode() {
|
||||
return {
|
||||
#jsFunction: _val.address,
|
||||
#jsFunctionCtx: _ctx.address,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
noSuchMethod(Invocation invocation) {
|
||||
return invoke(
|
||||
invocation.positionalArguments,
|
||||
invocation.namedArguments[#thisVal],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// JS function wrapper for isolate
|
||||
class _IsolateJSFunction extends JSInvokable implements _IsolateEncodable {
|
||||
int _val;
|
||||
int _ctx;
|
||||
SendPort _port;
|
||||
_IsolateJSFunction(this._ctx, this._val, this._port);
|
||||
|
||||
@override
|
||||
Future invoke(List arguments, [thisVal]) async {
|
||||
if (0 == _val ?? 0) return;
|
||||
final evaluatePort = ReceivePort();
|
||||
_port.send({
|
||||
#type: #call,
|
||||
#ctx: _ctx,
|
||||
#val: _val,
|
||||
#args: _encodeData(arguments),
|
||||
#thisVal: _encodeData(thisVal),
|
||||
#port: evaluatePort.sendPort,
|
||||
});
|
||||
final result = await evaluatePort.first;
|
||||
evaluatePort.close();
|
||||
if (result is Map && result.containsKey(#error))
|
||||
throw _decodeData(result[#error], _port);
|
||||
return _decodeData(result, _port);
|
||||
}
|
||||
|
||||
static _IsolateJSFunction _decode(Map obj, SendPort port) {
|
||||
if (obj.containsKey(#jsFunction) && port != null)
|
||||
return _IsolateJSFunction(obj[#jsFunctionCtx], obj[#jsFunction], port);
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Map _encode() {
|
||||
return {
|
||||
#jsFunction: _val,
|
||||
#jsFunctionCtx: _ctx,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
void release() {
|
||||
if (_port == null) return;
|
||||
_port.send({
|
||||
#type: #closeFunction,
|
||||
#ctx: _ctx,
|
||||
#val: _val,
|
||||
});
|
||||
_port = null;
|
||||
_val = null;
|
||||
_ctx = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Dart function wrapper for isolate
|
||||
class _IsolateFunction extends JSInvokable
|
||||
implements JSReleasable, _IsolateEncodable {
|
||||
SendPort _port;
|
||||
SendPort _func;
|
||||
_IsolateFunction(this._func, this._port);
|
||||
|
||||
static _IsolateFunction _bind(Function func, SendPort port) {
|
||||
final JSInvokable invokable = JSInvokable._wrap(func);
|
||||
final funcPort = ReceivePort();
|
||||
funcPort.listen((msg) async {
|
||||
if (msg == #close) return funcPort.close();
|
||||
SendPort msgPort = msg[#port];
|
||||
try {
|
||||
List args = _decodeData(msg[#args], port);
|
||||
Map thisVal = _decodeData(msg[#thisVal], port);
|
||||
final data = await invokable.invoke(args, thisVal);
|
||||
if (msgPort != null) msgPort.send(_encodeData(data));
|
||||
} catch (e) {
|
||||
if (msgPort != null)
|
||||
msgPort.send({
|
||||
#error: _encodeData(e),
|
||||
});
|
||||
}
|
||||
});
|
||||
return _IsolateFunction(funcPort.sendPort, port);
|
||||
}
|
||||
|
||||
@override
|
||||
Future invoke(List positionalArguments, [thisVal]) async {
|
||||
if (_func == null) return;
|
||||
final evaluatePort = ReceivePort();
|
||||
_func.send({
|
||||
#args: _encodeData(positionalArguments),
|
||||
#thisVal: _encodeData(thisVal),
|
||||
#port: evaluatePort.sendPort,
|
||||
});
|
||||
final result = await evaluatePort.first;
|
||||
evaluatePort.close();
|
||||
if (result is Map && result.containsKey(#error))
|
||||
throw _decodeData(result[#error], _port);
|
||||
return _decodeData(result, _port);
|
||||
}
|
||||
|
||||
static _IsolateFunction _decode(Map obj, SendPort port) {
|
||||
if (obj.containsKey(#jsFunctionPort))
|
||||
return _IsolateFunction(obj[#jsFunctionPort], port);
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Map _encode() {
|
||||
return {
|
||||
#jsFunctionPort: _func,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
void release() {
|
||||
if (_func == null) return;
|
||||
_func.send(#close);
|
||||
_func = null;
|
||||
}
|
||||
}
|
235
lib/src/wrapper.dart
Normal file
235
lib/src/wrapper.dart
Normal file
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* @Description: wrapper
|
||||
* @Author: ekibun
|
||||
* @Date: 2020-09-19 22:07:47
|
||||
* @LastEditors: ekibun
|
||||
* @LastEditTime: 2020-12-02 11:14:03
|
||||
*/
|
||||
part of '../flutter_qjs.dart';
|
||||
|
||||
dynamic _parseJSException(Pointer ctx, [Pointer perr]) {
|
||||
final e = perr ?? jsGetException(ctx);
|
||||
var err;
|
||||
try {
|
||||
err = _jsToDart(ctx, e);
|
||||
} catch (exception) {
|
||||
err = exception;
|
||||
}
|
||||
if (perr == null) jsFreeValue(ctx, e);
|
||||
return err;
|
||||
}
|
||||
|
||||
void _definePropertyValue(
|
||||
Pointer ctx,
|
||||
Pointer obj,
|
||||
dynamic key,
|
||||
dynamic val, {
|
||||
Map<dynamic, dynamic> cache,
|
||||
}) {
|
||||
final jsAtomVal = _dartToJs(ctx, key, cache: cache);
|
||||
final jsAtom = jsValueToAtom(ctx, jsAtomVal);
|
||||
jsDefinePropertyValue(
|
||||
ctx,
|
||||
obj,
|
||||
jsAtom,
|
||||
_dartToJs(ctx, val, cache: cache),
|
||||
JSProp.C_W_E,
|
||||
);
|
||||
jsFreeAtom(ctx, jsAtom);
|
||||
jsFreeValue(ctx, jsAtomVal);
|
||||
}
|
||||
|
||||
Pointer _jsGetPropertyValue(
|
||||
Pointer ctx,
|
||||
Pointer obj,
|
||||
dynamic key, {
|
||||
Map<dynamic, dynamic> cache,
|
||||
}) {
|
||||
final jsAtomVal = _dartToJs(ctx, key, cache: cache);
|
||||
final jsAtom = jsValueToAtom(ctx, jsAtomVal);
|
||||
final jsProp = jsGetProperty(ctx, obj, jsAtom);
|
||||
jsFreeAtom(ctx, jsAtom);
|
||||
jsFreeValue(ctx, jsAtomVal);
|
||||
return jsProp;
|
||||
}
|
||||
|
||||
Pointer _dartToJs(Pointer ctx, dynamic val, {Map<dynamic, dynamic> cache}) {
|
||||
if (val == null) return jsUNDEFINED();
|
||||
if (val is JSError) {
|
||||
final ret = jsNewError(ctx);
|
||||
_definePropertyValue(ctx, ret, "name", "");
|
||||
_definePropertyValue(ctx, ret, "message", val.message);
|
||||
_definePropertyValue(ctx, ret, "stack", val.stack);
|
||||
return ret;
|
||||
}
|
||||
if (val is _JSObject) return jsDupValue(ctx, val._val);
|
||||
if (val is Future) {
|
||||
final resolvingFunc = allocate<Uint8>(count: sizeOfJSValue * 2);
|
||||
final resolvingFunc2 =
|
||||
Pointer.fromAddress(resolvingFunc.address + sizeOfJSValue);
|
||||
final ret = jsNewPromiseCapability(ctx, resolvingFunc);
|
||||
_JSFunction res = _jsToDart(ctx, resolvingFunc)..leakable = true;
|
||||
_JSFunction rej = _jsToDart(ctx, resolvingFunc2)..leakable = true;
|
||||
jsFreeValue(ctx, resolvingFunc, free: false);
|
||||
jsFreeValue(ctx, resolvingFunc2, free: false);
|
||||
free(resolvingFunc);
|
||||
val.then((value) {
|
||||
res.invoke([value]);
|
||||
}, onError: (e) {
|
||||
rej.invoke([e]);
|
||||
}).whenComplete(() {
|
||||
res.release();
|
||||
rej.release();
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
if (cache == null) cache = Map();
|
||||
if (val is bool) return jsNewBool(ctx, val ? 1 : 0);
|
||||
if (val is int) return jsNewInt64(ctx, val);
|
||||
if (val is double) return jsNewFloat64(ctx, val);
|
||||
if (val is String) return jsNewString(ctx, val);
|
||||
if (val is Uint8List) {
|
||||
final ptr = allocate<Uint8>(count: val.length);
|
||||
final byteList = ptr.asTypedList(val.length);
|
||||
byteList.setAll(0, val);
|
||||
final ret = jsNewArrayBufferCopy(ctx, ptr, val.length);
|
||||
free(ptr);
|
||||
return ret;
|
||||
}
|
||||
if (cache.containsKey(val)) {
|
||||
return jsDupValue(ctx, cache[val]);
|
||||
}
|
||||
if (val is List) {
|
||||
Pointer ret = jsNewArray(ctx);
|
||||
cache[val] = ret;
|
||||
for (int i = 0; i < val.length; ++i) {
|
||||
_definePropertyValue(ctx, ret, i, val[i], cache: cache);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
if (val is Map) {
|
||||
Pointer ret = jsNewObject(ctx);
|
||||
cache[val] = ret;
|
||||
for (MapEntry<dynamic, dynamic> entry in val.entries) {
|
||||
_definePropertyValue(ctx, ret, entry.key, entry.value, cache: cache);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
// wrap Function to JSInvokable
|
||||
final valWrap = JSInvokable._wrap(val);
|
||||
int dartObjectClassId =
|
||||
runtimeOpaques[jsGetRuntime(ctx)]?.dartObjectClassId ?? 0;
|
||||
if (dartObjectClassId == 0) return jsUNDEFINED();
|
||||
final dartObject = jsNewObjectClass(
|
||||
ctx,
|
||||
dartObjectClassId,
|
||||
identityHashCode(_DartObject(ctx, valWrap)),
|
||||
);
|
||||
if (valWrap is JSInvokable) {
|
||||
final ret = jsNewCFunction(ctx, dartObject);
|
||||
jsFreeValue(ctx, dartObject);
|
||||
return ret;
|
||||
}
|
||||
return dartObject;
|
||||
}
|
||||
|
||||
dynamic _jsToDart(Pointer ctx, Pointer val, {Map<int, dynamic> cache}) {
|
||||
if (cache == null) cache = Map();
|
||||
int tag = jsValueGetTag(val);
|
||||
if (jsTagIsFloat64(tag) != 0) {
|
||||
return jsToFloat64(ctx, val);
|
||||
}
|
||||
switch (tag) {
|
||||
case JSTag.BOOL:
|
||||
return jsToBool(ctx, val) != 0;
|
||||
case JSTag.INT:
|
||||
return jsToInt64(ctx, val);
|
||||
case JSTag.STRING:
|
||||
return jsToCString(ctx, val);
|
||||
case JSTag.OBJECT:
|
||||
final rt = jsGetRuntime(ctx);
|
||||
final dartObjectClassId = runtimeOpaques[rt].dartObjectClassId;
|
||||
if (dartObjectClassId != 0) {
|
||||
final dartObject = _DartObject.fromAddress(
|
||||
rt, jsGetObjectOpaque(val, dartObjectClassId));
|
||||
if (dartObject != null) return dartObject._obj;
|
||||
}
|
||||
Pointer<IntPtr> psize = allocate<IntPtr>();
|
||||
Pointer<Uint8> buf = jsGetArrayBuffer(ctx, psize, val);
|
||||
int size = psize.value;
|
||||
free(psize);
|
||||
if (buf.address != 0) {
|
||||
return Uint8List.fromList(buf.asTypedList(size));
|
||||
}
|
||||
int valptr = jsValueGetPtr(val).address;
|
||||
if (cache.containsKey(valptr)) {
|
||||
return cache[valptr];
|
||||
}
|
||||
if (jsIsFunction(ctx, val) != 0) {
|
||||
return _JSFunction(ctx, val);
|
||||
} else if (jsIsError(ctx, val) != 0) {
|
||||
final err = jsToCString(ctx, val);
|
||||
final pstack = _jsGetPropertyValue(ctx, val, 'stack');
|
||||
final stack =
|
||||
jsToBool(ctx, pstack) != 0 ? jsToCString(ctx, pstack) : null;
|
||||
jsFreeValue(ctx, pstack);
|
||||
return JSError(err, stack);
|
||||
} else if (jsIsPromise(ctx, val) != 0) {
|
||||
Pointer jsPromiseThen = _jsGetPropertyValue(ctx, val, 'then');
|
||||
_JSFunction promiseThen = _jsToDart(ctx, jsPromiseThen, cache: cache);
|
||||
jsFreeValue(ctx, jsPromiseThen);
|
||||
final completer = Completer();
|
||||
completer.future.catchError((e) {});
|
||||
final jsPromise = _JSObject(ctx, val);
|
||||
final jsRet = promiseThen._invoke([
|
||||
(v) {
|
||||
if (!completer.isCompleted) completer.complete(v);
|
||||
},
|
||||
(e) {
|
||||
if (!completer.isCompleted) completer.completeError(e);
|
||||
},
|
||||
], jsPromise);
|
||||
jsPromise.release();
|
||||
promiseThen.release();
|
||||
bool isException = jsIsException(jsRet) != 0;
|
||||
jsFreeValue(ctx, jsRet);
|
||||
if (isException) throw _parseJSException(ctx);
|
||||
return completer.future;
|
||||
} else if (jsIsArray(ctx, val) != 0) {
|
||||
Pointer jslength = _jsGetPropertyValue(ctx, val, 'length');
|
||||
int length = jsToInt64(ctx, jslength);
|
||||
List<dynamic> ret = [];
|
||||
cache[valptr] = ret;
|
||||
for (int i = 0; i < length; ++i) {
|
||||
final jsProp = _jsGetPropertyValue(ctx, val, i);
|
||||
ret.add(_jsToDart(ctx, jsProp, cache: cache));
|
||||
jsFreeValue(ctx, jsProp);
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
Pointer<Pointer> ptab = allocate<Pointer>();
|
||||
Pointer<Uint32> plen = allocate<Uint32>();
|
||||
if (jsGetOwnPropertyNames(ctx, ptab, plen, val, -1) != 0) return null;
|
||||
int len = plen.value;
|
||||
free(plen);
|
||||
Map<dynamic, dynamic> ret = Map();
|
||||
cache[valptr] = ret;
|
||||
for (int i = 0; i < len; ++i) {
|
||||
final jsAtom = jsPropertyEnumGetAtom(ptab.value, i);
|
||||
final jsAtomValue = jsAtomToValue(ctx, jsAtom);
|
||||
final jsProp = jsGetProperty(ctx, val, jsAtom);
|
||||
ret[_jsToDart(ctx, jsAtomValue, cache: cache)] =
|
||||
_jsToDart(ctx, jsProp, cache: cache);
|
||||
jsFreeValue(ctx, jsAtomValue);
|
||||
jsFreeValue(ctx, jsProp);
|
||||
jsFreeAtom(ctx, jsAtom);
|
||||
}
|
||||
jsFree(ctx, ptab.value);
|
||||
free(ptab);
|
||||
return ret;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return null;
|
||||
}
|
Reference in New Issue
Block a user