This commit is contained in:
ekibun
2021-01-27 11:50:25 +08:00
parent 3fdeca56a2
commit 01ea420bd7
7 changed files with 258 additions and 255 deletions

View File

@@ -77,10 +77,10 @@ or use `invoke` method to pass list parameters:
``` ```
`JSInvokable` returned by evaluation may increase reference of JS object. `JSInvokable` returned by evaluation may increase reference of JS object.
You should manually call `release` to release JS reference: You should manually call `free` to release JS reference:
```dart ```dart
(func as JSInvokable).release(); (func as JSInvokable).free();
``` ```
### Use modules ### Use modules

View File

@@ -80,7 +80,7 @@ class FlutterQjs {
case JSChannelType.FREE_OBJECT: case JSChannelType.FREE_OBJECT:
Pointer rt = ctx; Pointer rt = ctx;
_DartObject obj = _DartObject.fromAddress(rt, ptr.address); _DartObject obj = _DartObject.fromAddress(rt, ptr.address);
obj?.release(); obj?.free();
return Pointer.fromAddress(0); return Pointer.fromAddress(0);
} }
throw JSError('call channel with wrong type'); throw JSError('call channel with wrong type');

View File

@@ -11,10 +11,21 @@ import 'dart:isolate';
import 'package:ffi/ffi.dart'; import 'package:ffi/ffi.dart';
abstract class JSRef { abstract class JSRef {
bool leakable = false; int _refCount = 0;
void release(); void dup() {
_refCount++;
}
void free() {
_refCount--;
if (_refCount < 0) destroy();
}
void destroy();
} }
abstract class JSRefLeakable {}
class JSEvalFlag { class JSEvalFlag {
static const GLOBAL = 0 << 0; static const GLOBAL = 0 << 0;
static const MODULE = 1 << 0; static const MODULE = 1 << 0;
@@ -170,22 +181,20 @@ void jsFreeRuntime(
while (true) { while (true) {
final ref = runtimeOpaques[rt] final ref = runtimeOpaques[rt]
?._ref ?._ref
?.firstWhere((ref) => ref.leakable, orElse: () => null); ?.firstWhere((ref) => ref is JSRefLeakable, orElse: () => null);
if (ref == null) break; if (ref == null) break;
ref.release(); ref.destroy();
runtimeOpaques[rt]?._ref?.remove(ref); runtimeOpaques[rt]?._ref?.remove(ref);
} }
while (0 < runtimeOpaques[rt]?._ref?.length ?? 0) { while (0 < runtimeOpaques[rt]?._ref?.length ?? 0) {
final ref = runtimeOpaques[rt]?._ref?.first; final ref = runtimeOpaques[rt]?._ref?.first;
assert(!ref.leakable);
referenceleak.add( referenceleak.add(
" ${identityHashCode(ref)}\t${ref.runtimeType.toString()}\t${ref.toString().replaceAll('\n', '\\n')}"); " ${identityHashCode(ref)}\t${ref._refCount + 1}\t${ref.runtimeType.toString()}\t${ref.toString().replaceAll('\n', '\\n')}");
ref.release(); ref.destroy();
runtimeOpaques[rt]?._ref?.remove(ref);
} }
_jsFreeRuntime(rt); _jsFreeRuntime(rt);
if (referenceleak.length > 0) { if (referenceleak.length > 0) {
throw ('reference leak:\n ADDR\t TYPE \t PROP\n' + throw ('reference leak:\n ADDR\tREF\tTYPE\tPROP\n' +
referenceleak.join('\n')); referenceleak.join('\n'));
} }
} }

View File

@@ -7,22 +7,24 @@
*/ */
part of '../flutter_qjs.dart'; part of '../flutter_qjs.dart';
typedef dynamic _Decode(Map obj, SendPort port); typedef dynamic _Decode(Map obj);
List<_Decode> _decoders = [ List<_Decode> _decoders = [
JSError._decode, JSError._decode,
_IsolateJSFunction._decode, IsolateFunction._decode,
_IsolateFunction._decode,
_JSFunction._decode,
]; ];
abstract class _IsolateEncodable { abstract class _IsolateEncodable {
Map _encode(); Map _encode();
} }
final List _sendAllowType = [Null, String, int, double, bool, SendPort];
dynamic _encodeData(dynamic data, {Map<dynamic, dynamic> cache}) { dynamic _encodeData(dynamic data, {Map<dynamic, dynamic> cache}) {
if (data is Function) return data;
if (_sendAllowType.contains(data.runtimeType)) return data;
if (cache == null) cache = Map(); if (cache == null) cache = Map();
if (data is _IsolateEncodable) return data._encode();
if (cache.containsKey(data)) return cache[data]; if (cache.containsKey(data)) return cache[data];
if (data is _IsolateEncodable) return data._encode();
if (data is List) { if (data is List) {
final ret = []; final ret = [];
cache[data] = ret; cache[data] = ret;
@@ -57,24 +59,23 @@ dynamic _encodeData(dynamic data, {Map<dynamic, dynamic> cache}) {
#jsFuturePort: futurePort.sendPort, #jsFuturePort: futurePort.sendPort,
}; };
} }
return data; throw JSError('unsupport type: ${data.runtimeType}\n${data.toString()}');
} }
dynamic _decodeData(dynamic data, SendPort port, dynamic _decodeData(dynamic data, {Map<dynamic, dynamic> cache}) {
{Map<dynamic, dynamic> cache}) {
if (cache == null) cache = Map(); if (cache == null) cache = Map();
if (cache.containsKey(data)) return cache[data]; if (cache.containsKey(data)) return cache[data];
if (data is List) { if (data is List) {
final ret = []; final ret = [];
cache[data] = ret; cache[data] = ret;
for (int i = 0; i < data.length; ++i) { for (int i = 0; i < data.length; ++i) {
ret.add(_decodeData(data[i], port, cache: cache)); ret.add(_decodeData(data[i], cache: cache));
} }
return ret; return ret;
} }
if (data is Map) { if (data is Map) {
for (final decoder in _decoders) { for (final decoder in _decoders) {
final decodeObj = decoder(data, port); final decodeObj = decoder(data);
if (decodeObj != null) return decodeObj; if (decodeObj != null) return decodeObj;
} }
if (data.containsKey(#jsFuturePort)) { if (data.containsKey(#jsFuturePort)) {
@@ -86,9 +87,9 @@ dynamic _decodeData(dynamic data, SendPort port,
futurePort.first.then((value) { futurePort.first.then((value) {
futurePort.close(); futurePort.close();
if (value is Map && value.containsKey(#error)) { if (value is Map && value.containsKey(#error)) {
futureCompleter.completeError(_decodeData(value[#error], port)); futureCompleter.completeError(_decodeData(value[#error]));
} else { } else {
futureCompleter.complete(_decodeData(value, port)); futureCompleter.complete(_decodeData(value));
} }
}); });
return futureCompleter.future; return futureCompleter.future;
@@ -96,8 +97,8 @@ dynamic _decodeData(dynamic data, SendPort port,
final ret = {}; final ret = {};
cache[data] = ret; cache[data] = ret;
for (final entry in data.entries) { for (final entry in data.entries) {
ret[_decodeData(entry.key, port, cache: cache)] = ret[_decodeData(entry.key, cache: cache)] =
_decodeData(entry.value, port, cache: cache); _decodeData(entry.value, cache: cache);
} }
return ret; return ret;
} }
@@ -147,21 +148,6 @@ void _runJsIsolate(Map spawnMessage) async {
evalFlags: msg[#flag], evalFlags: msg[#flag],
); );
break; 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: case #close:
data = false; data = false;
qjs.port.close(); qjs.port.close();
@@ -225,7 +211,7 @@ class IsolateQjs {
switch (msg[#type]) { switch (msg[#type]) {
case #hostPromiseRejection: case #hostPromiseRejection:
try { try {
final err = _decodeData(msg[#reason], port.sendPort); final err = _decodeData(msg[#reason]);
if (hostPromiseRejectionHandler != null) { if (hostPromiseRejectionHandler != null) {
hostPromiseRejectionHandler(err); hostPromiseRejectionHandler(err);
} else { } else {
@@ -255,12 +241,6 @@ class IsolateQjs {
_sendPort = completer.future; _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. /// Free Runtime and close isolate thread that can be recreate when evaluate again.
close() { close() {
if (_sendPort == null) return; if (_sendPort == null) return;
@@ -273,8 +253,8 @@ class IsolateQjs {
final result = await closePort.first; final result = await closePort.first;
closePort.close(); closePort.close();
if (result is Map && result.containsKey(#error)) if (result is Map && result.containsKey(#error))
throw _decodeData(result[#error], sendPort); throw _decodeData(result[#error]);
return _decodeData(result, sendPort); return _decodeData(result);
}); });
_sendPort = null; _sendPort = null;
return ret; return ret;
@@ -295,7 +275,7 @@ class IsolateQjs {
final result = await evaluatePort.first; final result = await evaluatePort.first;
evaluatePort.close(); evaluatePort.close();
if (result is Map && result.containsKey(#error)) if (result is Map && result.containsKey(#error))
throw _decodeData(result[#error], sendPort); throw _decodeData(result[#error]);
return _decodeData(result, sendPort); return _decodeData(result);
} }
} }

View File

@@ -8,7 +8,7 @@
part of '../flutter_qjs.dart'; part of '../flutter_qjs.dart';
/// js invokable /// js invokable
abstract class JSInvokable extends JSReleasable { abstract class JSInvokable extends JSRef {
dynamic invoke(List args, [dynamic thisVal]); dynamic invoke(List args, [dynamic thisVal]);
static dynamic _wrap(dynamic func) { static dynamic _wrap(dynamic func) {
@@ -18,26 +18,39 @@ abstract class JSInvokable extends JSReleasable {
? _DartFunction(func) ? _DartFunction(func)
: func; : func;
} }
@override
noSuchMethod(Invocation invocation) {
return invoke(
invocation.positionalArguments,
invocation.namedArguments[#thisVal],
);
}
} }
class _DartFunction extends JSInvokable { class _DartFunction extends JSInvokable {
final Function _func; final Function _func;
_DartFunction(this._func); _DartFunction(this._func);
void _freeRecursive(dynamic obj, [Set cache]) {
if (obj == null) return;
if (cache == null) cache = Set();
if (cache.contains(obj)) return;
if (obj is List) {
cache.add(obj);
obj.forEach((e) => _freeRecursive(e, cache));
}
if (obj is Map) {
cache.add(obj);
obj.values.forEach((e) => _freeRecursive(e, cache));
}
if (obj is JSRef) {
obj.free();
}
}
@override @override
invoke(List args, [thisVal]) { invoke(List args, [thisVal]) {
/// wrap this into function /// wrap this into function
final passThis = final passThis =
RegExp('{.*thisVal.*}').hasMatch(_func.runtimeType.toString()); RegExp('{.*thisVal.*}').hasMatch(_func.runtimeType.toString());
return Function.apply(_func, args, passThis ? {#thisVal: thisVal} : null); final ret =
Function.apply(_func, args, passThis ? {#thisVal: thisVal} : null);
_freeRecursive(args);
_freeRecursive(thisVal);
return ret;
} }
@override @override
@@ -46,21 +59,18 @@ class _DartFunction extends JSInvokable {
} }
@override @override
release() {} destroy() {}
} }
/// implement this to capture js object release. /// implement this to capture js object release.
abstract class JSReleasable {
void release();
}
class _DartObject extends JSRef {
@override
bool leakable = true;
class _DartObject extends JSRef implements JSRefLeakable {
Object _obj; Object _obj;
Pointer _ctx; Pointer _ctx;
_DartObject(this._ctx, this._obj) { _DartObject(this._ctx, this._obj) {
if (_obj is JSRef) {
(_obj as JSRef).dup();
}
runtimeOpaques[jsGetRuntime(_ctx)]?.addRef(this); runtimeOpaques[jsGetRuntime(_ctx)]?.addRef(this);
} }
@@ -70,17 +80,17 @@ class _DartObject extends JSRef {
@override @override
String toString() { String toString() {
if (_ctx == null) return "DartObject(<released>)"; if (_ctx == null) return "DartObject(released)";
return _obj.toString(); return _obj.toString();
} }
@override @override
void release() { void destroy() {
if (_ctx == null) return; if (_ctx == null) return;
runtimeOpaques[jsGetRuntime(_ctx)]?.removeRef(this); runtimeOpaques[jsGetRuntime(_ctx)]?.removeRef(this);
_ctx = null; _ctx = null;
if (_obj is JSReleasable) { if (_obj is JSRef) {
(_obj as JSReleasable).release(); (_obj as JSRef).free();
} }
_obj = null; _obj = null;
} }
@@ -105,7 +115,7 @@ class JSError extends _IsolateEncodable {
return stack == null ? message.toString() : "$message\n$stack"; return stack == null ? message.toString() : "$message\n$stack";
} }
static JSError _decode(Map obj, SendPort port) { static JSError _decode(Map obj) {
if (obj.containsKey(#jsError)) if (obj.containsKey(#jsError))
return JSError(obj[#jsError], obj[#jsErrorStack]); return JSError(obj[#jsError], obj[#jsErrorStack]);
return null; return null;
@@ -133,16 +143,8 @@ class _JSObject extends JSRef {
runtimeOpaques[rt]?.addRef(this); 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 @override
void release() { void destroy() {
if (_val == null) return; if (_val == null) return;
Pointer rt = jsGetRuntime(_ctx); Pointer rt = jsGetRuntime(_ctx);
runtimeOpaques[rt]?.removeRef(this); runtimeOpaques[rt]?.removeRef(this);
@@ -162,10 +164,6 @@ class _JSObject extends JSRef {
class _JSFunction extends _JSObject implements JSInvokable, _IsolateEncodable { class _JSFunction extends _JSObject implements JSInvokable, _IsolateEncodable {
_JSFunction(Pointer ctx, Pointer val) : super(ctx, val); _JSFunction(Pointer ctx, Pointer val) : super(ctx, val);
static _JSFunction fromAddress(Pointer ctx, Pointer val) {
return _JSObject.fromAddress(ctx, val);
}
@override @override
invoke(List<dynamic> arguments, [dynamic thisVal]) { invoke(List<dynamic> arguments, [dynamic thisVal]) {
Pointer jsRet = _invoke(arguments, thisVal); Pointer jsRet = _invoke(arguments, thisVal);
@@ -196,11 +194,124 @@ class _JSFunction extends _JSObject implements JSInvokable, _IsolateEncodable {
return jsRet; return jsRet;
} }
static _JSFunction _decode(Map obj, SendPort port) { @override
if (obj.containsKey(#jsFunction) && port == null) Map _encode() {
return _JSFunction.fromAddress( final func = IsolateFunction._new(this);
Pointer.fromAddress(obj[#jsFunctionCtx]), final ret = func._encode();
Pointer.fromAddress(obj[#jsFunction]), return ret;
}
}
abstract class _IsolatePortHandler {
int _isolateId;
dynamic _handle(dynamic);
}
class _IsolatePort {
static ReceivePort _invokeHandler;
static Set<_IsolatePortHandler> _handlers = Set();
static get _port {
if (_invokeHandler == null) {
_invokeHandler = ReceivePort();
_invokeHandler.listen((msg) async {
final msgPort = msg[#port];
try {
final handler = _handlers.firstWhere(
(v) => identityHashCode(v) == msg[#handler],
orElse: () => null,
);
if (handler == null) throw JSError('handler released');
final ret = _encodeData(await handler._handle(msg[#msg]));
if (msgPort != null) msgPort.send(ret);
} catch (e) {
final err = _encodeData(e);
if (msgPort != null)
msgPort.send({
#error: err,
});
}
});
}
return _invokeHandler.sendPort;
}
static _send(SendPort isolate, _IsolatePortHandler handler, msg) async {
if (isolate == null) return handler._handle(msg);
final evaluatePort = ReceivePort();
isolate.send({
#handler: handler._isolateId,
#msg: msg,
#port: evaluatePort.sendPort,
});
final result = await evaluatePort.first;
if (result is Map && result.containsKey(#error))
throw _decodeData(result[#error]);
return _decodeData(result);
}
static _add(_IsolatePortHandler sendport) => _handlers.add(sendport);
static _remove(_IsolatePortHandler sendport) => _handlers.remove(sendport);
}
/// Dart function wrapper for isolate
class IsolateFunction extends JSInvokable
implements _IsolateEncodable, _IsolatePortHandler {
@override
int _isolateId;
SendPort _port;
JSInvokable _invokable;
IsolateFunction._fromId(this._isolateId, this._port);
IsolateFunction._new(this._invokable) {
_IsolatePort._add(this);
}
static IsolateFunction func(Function func) {
return IsolateFunction._new(_DartFunction(func));
}
_destroy() {
_IsolatePort._remove(this);
_invokable?.free();
}
@override
_handle(msg) async {
switch (msg) {
case #dup:
_refCount++;
return null;
case #free:
_refCount--;
print("${identityHashCode(this)} ref $_refCount");
if (_refCount < 0) _destroy();
return null;
case #destroy:
_destroy();
return null;
}
List args = _decodeData(msg[#args]);
Map thisVal = _decodeData(msg[#thisVal]);
return _invokable.invoke(args, thisVal);
}
@override
Future invoke(List positionalArguments, [thisVal]) async {
List dArgs = _encodeData(positionalArguments);
Map dThisVal = _encodeData(thisVal);
return _IsolatePort._send(_port, this, {
#type: #invokeIsolate,
#args: dArgs,
#thisVal: dThisVal,
});
}
static IsolateFunction _decode(Map obj) {
if (obj.containsKey(#jsFunctionPort))
return IsolateFunction._fromId(
obj[#jsFunctionId],
obj[#jsFunctionPort],
); );
return null; return null;
} }
@@ -208,135 +319,25 @@ class _JSFunction extends _JSObject implements JSInvokable, _IsolateEncodable {
@override @override
Map _encode() { Map _encode() {
return { return {
#jsFunction: _val.address, #jsFunctionId: _isolateId ?? identityHashCode(this),
#jsFunctionCtx: _ctx.address, #jsFunctionPort: _port ?? _IsolatePort._port,
}; };
} }
int _refCount = 0;
@override @override
noSuchMethod(Invocation invocation) { dup() {
return invoke( _IsolatePort._send(_port, this, #dup);
invocation.positionalArguments, }
invocation.namedArguments[#thisVal],
); @override
} free() {
} _IsolatePort._send(_port, this, #free);
}
/// JS function wrapper for isolate
class _IsolateJSFunction extends JSInvokable implements _IsolateEncodable { @override
int _val; void destroy() {
int _ctx; _IsolatePort._send(_port, this, #destroy);
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;
} }
} }

View File

@@ -68,18 +68,22 @@ Pointer _dartToJs(Pointer ctx, dynamic val, {Map<dynamic, dynamic> cache}) {
final resolvingFunc2 = final resolvingFunc2 =
Pointer.fromAddress(resolvingFunc.address + sizeOfJSValue); Pointer.fromAddress(resolvingFunc.address + sizeOfJSValue);
final ret = jsNewPromiseCapability(ctx, resolvingFunc); final ret = jsNewPromiseCapability(ctx, resolvingFunc);
_JSFunction res = _jsToDart(ctx, resolvingFunc)..leakable = true; _JSFunction res = _jsToDart(ctx, resolvingFunc);
_JSFunction rej = _jsToDart(ctx, resolvingFunc2)..leakable = true; _JSFunction rej = _jsToDart(ctx, resolvingFunc2);
jsFreeValue(ctx, resolvingFunc, free: false); jsFreeValue(ctx, resolvingFunc, free: false);
jsFreeValue(ctx, resolvingFunc2, free: false); jsFreeValue(ctx, resolvingFunc2, free: false);
free(resolvingFunc); free(resolvingFunc);
_DartObject refRes = _DartObject(ctx, res);
_DartObject refRej = _DartObject(ctx, rej);
res.free();
rej.free();
val.then((value) { val.then((value) {
res.invoke([value]); res.invoke([value]);
}, onError: (e) { }, onError: (e) {
rej.invoke([e]); rej.invoke([e]);
}).whenComplete(() { }).whenComplete(() {
res.release(); refRes.free();
rej.release(); refRej.free();
}); });
return ret; return ret;
} }
@@ -189,8 +193,8 @@ dynamic _jsToDart(Pointer ctx, Pointer val, {Map<int, dynamic> cache}) {
if (!completer.isCompleted) completer.completeError(e); if (!completer.isCompleted) completer.completeError(e);
}, },
], jsPromise); ], jsPromise);
jsPromise.release(); jsPromise.free();
promiseThen.release(); promiseThen.free();
bool isException = jsIsException(jsRet) != 0; bool isException = jsIsException(jsRet) != 0;
jsFreeValue(ctx, jsRet); jsFreeValue(ctx, jsRet);
if (isException) throw _parseJSException(ctx); if (isException) throw _parseJSException(ctx);

View File

@@ -8,6 +8,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:isolate';
import 'package:flutter_qjs/flutter_qjs.dart'; import 'package:flutter_qjs/flutter_qjs.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
@@ -17,48 +18,42 @@ dynamic myFunction(String args, {thisVal}) {
} }
Future testEvaluate(qjs) async { Future testEvaluate(qjs) async {
final testWrap = await qjs.evaluate( JSInvokable wrapFunction = await qjs.evaluate(
'(a) => a', '(a) => a',
name: '<testWrap>', name: '<testWrap>',
); );
final wrapNull = await testWrap(null); dynamic testWrap = await wrapFunction.invoke([wrapFunction]);
final wrapNull = await testWrap.invoke([null]);
expect(wrapNull, null, reason: 'wrap null'); expect(wrapNull, null, reason: 'wrap null');
final primities = [0, 1, 0.1, true, false, 'str']; final primities = [0, 1, 0.1, true, false, 'str'];
final wrapPrimities = await testWrap(primities); final wrapPrimities = await testWrap.invoke([primities]);
for (int i = 0; i < primities.length; i++) { for (int i = 0; i < primities.length; i++) {
expect(wrapPrimities[i], primities[i], reason: 'wrap primities'); expect(wrapPrimities[i], primities[i], reason: 'wrap primities');
} }
final jsError = JSError('test Error'); final jsError = JSError('test Error');
final wrapJsError = await testWrap(jsError); final wrapJsError = await testWrap.invoke([jsError]);
expect(jsError.message, (wrapJsError as JSError).message, expect(jsError.message, (wrapJsError as JSError).message,
reason: 'wrap JSError'); reason: 'wrap JSError');
final wrapFunction = await testWrap(testWrap);
final testEqual = await qjs.evaluate(
'(a, b) => a === b',
name: '<testEqual>',
);
expect(await testEqual(wrapFunction, testWrap), true,
reason: 'wrap function');
wrapFunction.release();
testEqual.release();
expect(wrapNull, null, reason: 'wrap null'); expect(wrapNull, null, reason: 'wrap null');
final a = {}; final a = {};
a['a'] = a; a['a'] = a;
final wrapA = await testWrap(a); final wrapA = await testWrap.invoke([a]);
expect(wrapA['a'], wrapA, reason: 'recursive object'); expect(wrapA['a'], wrapA, reason: 'recursive object');
final testThis = await qjs.evaluate( JSInvokable testThis = await qjs.evaluate(
'(function (func, arg) { return func.call(this, arg) })', '(function (func, arg) { return func.call(this, arg) })',
name: '<testThis>', name: '<testThis>',
); );
final funcRet = await testThis(myFunction, 'arg', thisVal: {'name': 'this'}); final funcRet = await testThis.invoke([myFunction, 'arg'], {'name': 'this'});
testThis.release(); testThis.free();
expect(funcRet[0]['name'], 'this', reason: 'js function this'); expect(funcRet[0]['name'], 'this', reason: 'js function this');
expect(funcRet[1], 'arg', reason: 'js function argument'); expect(funcRet[1], 'arg', reason: 'js function argument');
final promises = await testWrap(await qjs.evaluate( List promises = await testWrap.invoke([
'[Promise.reject("reject"), Promise.resolve("resolve"), new Promise(() => {})]', await qjs.evaluate(
name: '<promises>', '[Promise.reject("reject"), Promise.resolve("resolve"), new Promise(() => {})]',
)); name: '<promises>',
)
]);
for (final promise in promises) for (final promise in promises)
expect(promise, isInstanceOf<Future>(), reason: 'promise object'); expect(promise, isInstanceOf<Future>(), reason: 'promise object');
try { try {
@@ -68,10 +63,16 @@ Future testEvaluate(qjs) async {
expect(e, 'reject', reason: 'promise object reject'); expect(e, 'reject', reason: 'promise object reject');
} }
expect(await promises[1], 'resolve', reason: 'promise object resolve'); expect(await promises[1], 'resolve', reason: 'promise object resolve');
testWrap.release(); testWrap.free();
wrapFunction.free();
} }
void main() async { void main() async {
test('send', () async {
final rec = ReceivePort();
rec.close();
rec.sendPort.send("3232");
});
test('make', () async { test('make', () async {
final utf8Encoding = Encoding.getByName('utf-8'); final utf8Encoding = Encoding.getByName('utf-8');
var cmakePath = 'cmake'; var cmakePath = 'cmake';
@@ -140,15 +141,23 @@ void main() async {
}); });
test('isolate bind function', () async { test('isolate bind function', () async {
final qjs = IsolateQjs(); final qjs = IsolateQjs();
var localVar; final localVars = [];
final testFunc = await qjs.evaluate('(func)=>func("ret")', name: '<eval>'); JSInvokable testFunc =
final testFuncRet = await testFunc(await qjs.bind((args) { await qjs.evaluate('(func)=>func(()=>"ret")', name: '<eval>');
localVar = 'test'; final func = IsolateFunction.func((args) {
return args; localVars.add(args..dup());
})); return args.invoke([]);
testFunc.release(); });
expect(localVar, 'test', reason: 'bind function'); final testFuncRet = await testFunc.invoke([func..dup()]);
final testFuncRet2 = await testFunc.invoke([func..dup()]);
func.free();
testFunc.free();
for (IsolateFunction vars in localVars) {
expect(await vars.invoke([]), 'ret', reason: 'bind function');
vars.free();
}
expect(testFuncRet, 'ret', reason: 'bind function args return'); expect(testFuncRet, 'ret', reason: 'bind function args return');
expect(testFuncRet2, testFuncRet, reason: 'bind function args return2');
await qjs.close(); await qjs.close();
}); });
test('reference leak', () async { test('reference leak', () async {