nullsafety

This commit is contained in:
ekibun
2021-02-22 16:15:46 +08:00
parent 8fb26b20af
commit e87f50956a
9 changed files with 176 additions and 140 deletions

View File

@@ -6,6 +6,15 @@
* @LastEditTime: 2020-12-02 11:36:40 * @LastEditTime: 2020-12-02 11:36:40
--> -->
## 0.3.6
* upgrade ffi to 1.0.0.
* nullsafety.
## 0.3.5
* downgrade ffi to 0.1.3.
## 0.3.4 ## 0.3.4
* upgrade ffi to 1.0.0. * upgrade ffi to 1.0.0.

View File

@@ -75,7 +75,7 @@ packages:
path: ".." path: ".."
relative: true relative: true
source: path source: path
version: "0.3.4" version: "0.3.6"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter

View File

@@ -15,20 +15,20 @@ typedef _JsHostPromiseRejectionHandler = void Function(dynamic reason);
/// Quickjs engine for flutter. /// Quickjs engine for flutter.
class FlutterQjs { class FlutterQjs {
Pointer<JSRuntime> _rt; Pointer<JSRuntime>? _rt;
Pointer<JSContext> _ctx; Pointer<JSContext>? _ctx;
/// Max stack size for quickjs. /// Max stack size for quickjs.
final int stackSize; final int? stackSize;
/// Message Port for event loop. Close it to stop dispatching event loop. /// Message Port for event loop. Close it to stop dispatching event loop.
ReceivePort port = ReceivePort(); ReceivePort port = ReceivePort();
/// Handler function to manage js module. /// Handler function to manage js module.
_JsModuleHandler moduleHandler; final _JsModuleHandler? moduleHandler;
/// Handler function to manage js module. /// Handler function to manage js module.
_JsHostPromiseRejectionHandler hostPromiseRejectionHandler; final _JsHostPromiseRejectionHandler? hostPromiseRejectionHandler;
FlutterQjs({ FlutterQjs({
this.moduleHandler, this.moduleHandler,
@@ -38,7 +38,7 @@ class FlutterQjs {
_ensureEngine() { _ensureEngine() {
if (_rt != null) return; if (_rt != null) return;
_rt = jsNewRuntime((ctx, type, ptr) { final rt = jsNewRuntime((ctx, type, ptr) {
try { try {
switch (type) { switch (type) {
case JSChannelType.METHON: case JSChannelType.METHON:
@@ -65,7 +65,7 @@ class FlutterQjs {
)); ));
case JSChannelType.MODULE: case JSChannelType.MODULE:
if (moduleHandler == null) throw JSError('No ModuleHandler'); if (moduleHandler == null) throw JSError('No ModuleHandler');
final ret = moduleHandler( final ret = moduleHandler!(
ptr.cast<Utf8>().toDartString(), ptr.cast<Utf8>().toDartString(),
).toNativeUtf8(); ).toNativeUtf8();
Future.microtask(() { Future.microtask(() {
@@ -75,15 +75,14 @@ class FlutterQjs {
case JSChannelType.PROMISE_TRACK: case JSChannelType.PROMISE_TRACK:
final err = _parseJSException(ctx, ptr); final err = _parseJSException(ctx, ptr);
if (hostPromiseRejectionHandler != null) { if (hostPromiseRejectionHandler != null) {
hostPromiseRejectionHandler(err); hostPromiseRejectionHandler!(err);
} else { } else {
print('unhandled promise rejection: $err'); print('unhandled promise rejection: $err');
} }
return nullptr; return nullptr;
case JSChannelType.FREE_OBJECT: case JSChannelType.FREE_OBJECT:
final rt = ctx.cast<JSRuntime>(); final rt = ctx.cast<JSRuntime>();
_DartObject obj = _DartObject.fromAddress(rt, ptr.address); _DartObject.fromAddress(rt, ptr.address)?.free();
obj?.free();
return nullptr; return nullptr;
} }
throw JSError('call channel with wrong type'); throw JSError('call channel with wrong type');
@@ -106,20 +105,21 @@ class FlutterQjs {
return err; return err;
} }
}, port); }, port);
if (this.stackSize != null && this.stackSize > 0) final stackSize = this.stackSize ?? 0;
jsSetMaxStackSize(_rt, this.stackSize); if (stackSize > 0) jsSetMaxStackSize(rt, stackSize);
_ctx = jsNewContext(_rt); _rt = rt;
_ctx = jsNewContext(rt);
} }
/// Free Runtime and Context which can be recreate when evaluate again. /// Free Runtime and Context which can be recreate when evaluate again.
close() { close() {
if (_rt == null) return;
final rt = _rt; final rt = _rt;
final ctx = _ctx; final ctx = _ctx;
_executePendingJob();
_rt = null; _rt = null;
_ctx = null; _ctx = null;
jsFreeContext(ctx); if (ctx != null) jsFreeContext(ctx);
if (rt == null) return;
_executePendingJob();
try { try {
jsFreeRuntime(rt); jsFreeRuntime(rt);
} on String catch (e) { } on String catch (e) {
@@ -128,11 +128,13 @@ class FlutterQjs {
} }
void _executePendingJob() { void _executePendingJob() {
if (_rt == null) return; final rt = _rt;
final ctx = _ctx;
if (rt == null || ctx == null) return;
while (true) { while (true) {
int err = jsExecutePendingJob(_rt); int err = jsExecutePendingJob(rt);
if (err <= 0) { if (err <= 0) {
if (err < 0) print(_parseJSException(_ctx)); if (err < 0) print(_parseJSException(ctx));
break; break;
} }
} }
@@ -146,20 +148,25 @@ class FlutterQjs {
} }
/// Evaluate js script. /// Evaluate js script.
dynamic evaluate(String command, {String name, int evalFlags}) { dynamic evaluate(
String command, {
String? name,
int? evalFlags,
}) {
_ensureEngine(); _ensureEngine();
final ctx = _ctx!;
final jsval = jsEval( final jsval = jsEval(
_ctx, ctx,
command, command,
name ?? '<eval>', name ?? '<eval>',
evalFlags ?? JSEvalFlag.GLOBAL, evalFlags ?? JSEvalFlag.GLOBAL,
); );
if (jsIsException(jsval) != 0) { if (jsIsException(jsval) != 0) {
jsFreeValue(_ctx, jsval); jsFreeValue(ctx, jsval);
throw _parseJSException(_ctx); throw _parseJSException(ctx);
} }
final result = _jsToDart(_ctx, jsval); final result = _jsToDart(ctx, jsval);
jsFreeValue(_ctx, jsval); jsFreeValue(ctx, jsval);
return result; return result;
} }
} }

View File

@@ -10,6 +10,16 @@ import 'dart:io';
import 'dart:isolate'; import 'dart:isolate';
import 'package:ffi/ffi.dart'; import 'package:ffi/ffi.dart';
extension ListFirstWhere<T> on Iterable<T> {
T? firstWhereOrNull(bool Function(T) test) {
try {
return firstWhere(test);
} on StateError {
return null;
}
}
}
abstract class JSRef { abstract class JSRef {
int _refCount = 0; int _refCount = 0;
void dup() { void dup() {
@@ -34,7 +44,7 @@ abstract class JSRef {
static void _callRecursive( static void _callRecursive(
dynamic obj, dynamic obj,
void Function(JSRef) cb, [ void Function(JSRef) cb, [
Set cache, Set? cache,
]) { ]) {
if (obj == null) return; if (obj == null) return;
if (cache == null) cache = Set(); if (cache == null) cache = Set();
@@ -155,24 +165,26 @@ final Pointer<JSRuntime> Function(
.asFunction(); .asFunction();
class _RuntimeOpaque { class _RuntimeOpaque {
_JSChannel _channel; final _JSChannel _channel;
List<JSRef> _ref = []; List<JSRef> _ref = [];
ReceivePort _port; final ReceivePort _port;
int _dartObjectClassId; int? _dartObjectClassId;
get dartObjectClassId => _dartObjectClassId; _RuntimeOpaque(this._channel, this._port);
int? get dartObjectClassId => _dartObjectClassId;
void addRef(JSRef ref) => _ref.add(ref); void addRef(JSRef ref) => _ref.add(ref);
bool removeRef(JSRef ref) => _ref.remove(ref); bool removeRef(JSRef ref) => _ref.remove(ref);
JSRef getRef(bool Function(JSRef ref) test) { JSRef? getRef(bool Function(JSRef ref) test) {
return _ref.firstWhere(test, orElse: () => null); return _ref.firstWhereOrNull(test);
} }
} }
final Map<Pointer<JSRuntime>, _RuntimeOpaque> runtimeOpaques = Map(); final Map<Pointer<JSRuntime>, _RuntimeOpaque> runtimeOpaques = Map();
Pointer<JSValue> channelDispacher( Pointer<JSValue>? channelDispacher(
Pointer<JSContext> ctx, Pointer<JSContext> ctx,
int type, int type,
Pointer<JSValue> argv, Pointer<JSValue> argv,
@@ -188,9 +200,7 @@ Pointer<JSRuntime> jsNewRuntime(
ReceivePort port, ReceivePort port,
) { ) {
final rt = _jsNewRuntime(Pointer.fromFunction(channelDispacher)); final rt = _jsNewRuntime(Pointer.fromFunction(channelDispacher));
runtimeOpaques[rt] = _RuntimeOpaque() runtimeOpaques[rt] = _RuntimeOpaque(callback, port);
.._channel = callback
.._port = port;
return rt; return rt;
} }
@@ -222,22 +232,23 @@ void jsFreeRuntime(
Pointer<JSRuntime> rt, Pointer<JSRuntime> rt,
) { ) {
final referenceleak = <String>[]; final referenceleak = <String>[];
final opaque = runtimeOpaques[rt];
if (opaque != null) {
while (true) { while (true) {
final ref = runtimeOpaques[rt] final ref = opaque._ref.firstWhereOrNull((ref) => ref is JSRefLeakable);
?._ref
?.firstWhere((ref) => ref is JSRefLeakable, orElse: () => null);
if (ref == null) break; if (ref == null) break;
ref.destroy(); ref.destroy();
runtimeOpaques[rt]?._ref?.remove(ref); runtimeOpaques[rt]?._ref.remove(ref);
} }
while (0 < runtimeOpaques[rt]?._ref?.length ?? 0) { while (opaque._ref.isNotEmpty) {
final ref = runtimeOpaques[rt]?._ref?.first; final ref = opaque._ref.first;
final objStrs = ref.toString().split('\n'); final objStrs = ref.toString().split('\n');
final objStr = objStrs.length > 0 ? objStrs[0] + " ..." : objStrs[0]; final objStr = objStrs.length > 0 ? objStrs[0] + " ..." : objStrs[0];
referenceleak.add( referenceleak.add(
" ${identityHashCode(ref)}\t${ref._refCount + 1}\t${ref.runtimeType.toString()}\t$objStr"); " ${identityHashCode(ref)}\t${ref._refCount + 1}\t${ref.runtimeType.toString()}\t$objStr");
ref.destroy(); ref.destroy();
} }
}
_jsFreeRuntime(rt); _jsFreeRuntime(rt);
if (referenceleak.length > 0) { if (referenceleak.length > 0) {
throw ('reference leak:\n ADDR\tREF\tTYPE\tPROP\n' + throw ('reference leak:\n ADDR\tREF\tTYPE\tPROP\n' +
@@ -335,7 +346,7 @@ Pointer<JSValue> jsEval(
); );
malloc.free(utf8input); malloc.free(utf8input);
malloc.free(utf8filename); malloc.free(utf8filename);
runtimeOpaques[jsGetRuntime(ctx)]._port.sendPort.send(#eval); runtimeOpaques[jsGetRuntime(ctx)]?._port.sendPort.send(#eval);
return val; return val;
} }
@@ -915,14 +926,11 @@ Pointer<JSValue> jsCall(
setJSValueList(jsArgs, i, jsArg); setJSValueList(jsArgs, i, jsArg);
} }
final func1 = jsDupValue(ctx, funcObj); final func1 = jsDupValue(ctx, funcObj);
final _thisObj = thisObj ?? jsUNDEFINED(); final _thisObj = thisObj;
final jsRet = _jsCall(ctx, funcObj, _thisObj, argv.length, jsArgs); final jsRet = _jsCall(ctx, funcObj, _thisObj, argv.length, jsArgs);
if (thisObj == null) {
jsFreeValue(ctx, _thisObj);
}
jsFreeValue(ctx, func1); jsFreeValue(ctx, func1);
malloc.free(jsArgs); malloc.free(jsArgs);
runtimeOpaques[jsGetRuntime(ctx)]._port.sendPort.send(#call); runtimeOpaques[jsGetRuntime(ctx)]?._port.sendPort.send(#call);
return jsRet; return jsRet;
} }

View File

@@ -17,7 +17,7 @@ abstract class _IsolateEncodable {
Map _encode(); Map _encode();
} }
dynamic _encodeData(dynamic data, {Map<dynamic, dynamic> cache}) { dynamic _encodeData(dynamic data, {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 _IsolateEncodable) return data._encode(); if (data is _IsolateEncodable) return data._encode();
@@ -58,7 +58,7 @@ dynamic _encodeData(dynamic data, {Map<dynamic, dynamic> cache}) {
return data; return data;
} }
dynamic _decodeData(dynamic data, {Map<dynamic, dynamic> cache}) { dynamic _decodeData(dynamic data, {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) {
@@ -132,7 +132,7 @@ void _runJsIsolate(Map spawnMessage) async {
); );
port.listen((msg) async { port.listen((msg) async {
var data; var data;
SendPort msgPort = msg[#port]; SendPort? msgPort = msg[#port];
try { try {
switch (msg[#type]) { switch (msg[#type]) {
case #evaluate: case #evaluate:
@@ -164,16 +164,16 @@ void _runJsIsolate(Map spawnMessage) async {
typedef _JsAsyncModuleHandler = Future<String> Function(String name); typedef _JsAsyncModuleHandler = Future<String> Function(String name);
class IsolateQjs { class IsolateQjs {
Future<SendPort> _sendPort; Future<SendPort>? _sendPort;
/// Max stack size for quickjs. /// Max stack size for quickjs.
final int stackSize; final int? stackSize;
/// Asynchronously handler to manage js module. /// Asynchronously handler to manage js module.
_JsAsyncModuleHandler moduleHandler; final _JsAsyncModuleHandler? moduleHandler;
/// Handler function to manage js module. /// Handler function to manage js module.
_JsHostPromiseRejectionHandler hostPromiseRejectionHandler; final _JsHostPromiseRejectionHandler? hostPromiseRejectionHandler;
/// Quickjs engine runing on isolate thread. /// Quickjs engine runing on isolate thread.
/// ///
@@ -207,7 +207,7 @@ class IsolateQjs {
try { try {
final err = _decodeData(msg[#reason]); final err = _decodeData(msg[#reason]);
if (hostPromiseRejectionHandler != null) { if (hostPromiseRejectionHandler != null) {
hostPromiseRejectionHandler(err); hostPromiseRejectionHandler!(err);
} else { } else {
print('unhandled promise rejection: $err'); print('unhandled promise rejection: $err');
} }
@@ -218,7 +218,7 @@ class IsolateQjs {
case #module: case #module:
final ptr = Pointer<Pointer>.fromAddress(msg[#ptr]); final ptr = Pointer<Pointer>.fromAddress(msg[#ptr]);
try { try {
ptr.value = (await moduleHandler(msg[#name])).toNativeUtf8(); ptr.value = (await moduleHandler!(msg[#name])).toNativeUtf8();
} catch (e) { } catch (e) {
ptr.value = Pointer.fromAddress(-1); ptr.value = Pointer.fromAddress(-1);
} }
@@ -234,8 +234,10 @@ class IsolateQjs {
/// 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; final sendPort = _sendPort;
final ret = _sendPort.then((sendPort) async { _sendPort = null;
if (sendPort == null) return;
final ret = sendPort.then((sendPort) async {
final closePort = ReceivePort(); final closePort = ReceivePort();
sendPort.send({ sendPort.send({
#type: #close, #type: #close,
@@ -247,15 +249,18 @@ class IsolateQjs {
throw _decodeData(result[#error]); throw _decodeData(result[#error]);
return _decodeData(result); return _decodeData(result);
}); });
_sendPort = null;
return ret; return ret;
} }
/// Evaluate js script. /// Evaluate js script.
Future<dynamic> evaluate(String command, {String name, int evalFlags}) async { Future<dynamic> evaluate(
String command, {
String? name,
int? evalFlags,
}) async {
_ensureEngine(); _ensureEngine();
final evaluatePort = ReceivePort(); final evaluatePort = ReceivePort();
final sendPort = await _sendPort; final sendPort = await _sendPort!;
sendPort.send({ sendPort.send({
#type: #evaluate, #type: #evaluate,
#command: command, #command: command,

View File

@@ -48,17 +48,18 @@ class _DartFunction extends JSInvokable {
/// implement this to capture js object release. /// implement this to capture js object release.
class _DartObject extends JSRef implements JSRefLeakable { class _DartObject extends JSRef implements JSRefLeakable {
Object _obj; Object? _obj;
Pointer<JSContext> _ctx; Pointer<JSContext>? _ctx;
_DartObject(this._ctx, this._obj) { _DartObject(Pointer<JSContext> ctx, dynamic obj) {
if (_obj is JSRef) { _ctx = ctx;
(_obj as JSRef).dup(); _obj = obj;
} if (obj is JSRef) obj.dup();
runtimeOpaques[jsGetRuntime(_ctx)]?.addRef(this); runtimeOpaques[jsGetRuntime(ctx)]?.addRef(this);
} }
static _DartObject fromAddress(Pointer<JSRuntime> rt, int val) { static _DartObject? fromAddress(Pointer<JSRuntime> rt, int val) {
return runtimeOpaques[rt]?.getRef((e) => identityHashCode(e) == val); return runtimeOpaques[rt]?.getRef((e) => identityHashCode(e) == val)
as _DartObject?;
} }
@override @override
@@ -69,20 +70,20 @@ class _DartObject extends JSRef implements JSRefLeakable {
@override @override
void destroy() { void destroy() {
if (_ctx == null) return; final ctx = _ctx;
runtimeOpaques[jsGetRuntime(_ctx)]?.removeRef(this); final obj = _obj;
_ctx = null; _ctx = null;
if (_obj is JSRef) {
(_obj as JSRef).free();
}
_obj = null; _obj = null;
if (ctx == null) return;
runtimeOpaques[jsGetRuntime(ctx)]?.removeRef(this);
if (obj is JSRef) obj.free();
} }
} }
/// JS Error wrapper /// JS Error wrapper
class JSError extends _IsolateEncodable { class JSError extends _IsolateEncodable {
String message; late String message;
String stack; late String stack;
JSError(message, [stack]) { JSError(message, [stack]) {
if (message is JSError) { if (message is JSError) {
this.message = message.message; this.message = message.message;
@@ -95,10 +96,10 @@ class JSError extends _IsolateEncodable {
@override @override
String toString() { String toString() {
return stack == null ? message.toString() : "$message\n$stack"; return stack.isEmpty ? message.toString() : "$message\n$stack";
} }
static JSError _decode(Map obj) { 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;
@@ -116,30 +117,33 @@ class JSError extends _IsolateEncodable {
/// JS Object reference /// JS Object reference
/// call [release] to release js object. /// call [release] to release js object.
class _JSObject extends JSRef { class _JSObject extends JSRef {
Pointer<JSValue> _val; Pointer<JSValue>? _val;
Pointer<JSContext> _ctx; Pointer<JSContext>? _ctx;
/// Create /// Create
_JSObject(this._ctx, Pointer<JSValue> _val) { _JSObject(Pointer<JSContext> ctx, Pointer<JSValue> val) {
final rt = jsGetRuntime(_ctx); this._ctx = ctx;
this._val = jsDupValue(_ctx, _val); final rt = jsGetRuntime(ctx);
this._val = jsDupValue(ctx, val);
runtimeOpaques[rt]?.addRef(this); runtimeOpaques[rt]?.addRef(this);
} }
@override @override
void destroy() { void destroy() {
if (_val == null) return; final ctx = _ctx;
final rt = jsGetRuntime(_ctx); final val = _val;
runtimeOpaques[rt]?.removeRef(this);
jsFreeValue(_ctx, _val);
_val = null; _val = null;
_ctx = null; _ctx = null;
if (ctx == null || val == null) return;
final rt = jsGetRuntime(ctx);
runtimeOpaques[rt]?.removeRef(this);
jsFreeValue(ctx, val);
} }
@override @override
String toString() { String toString() {
if (_val == null) return "JSObject(released)"; if (_ctx == null || _val == null) return "JSObject(released)";
return jsToCString(_ctx, _val); return jsToCString(_ctx!, _val!);
} }
} }
@@ -150,29 +154,32 @@ class _JSFunction extends _JSObject implements JSInvokable, _IsolateEncodable {
@override @override
invoke(List<dynamic> arguments, [dynamic thisVal]) { invoke(List<dynamic> arguments, [dynamic thisVal]) {
final jsRet = _invoke(arguments, thisVal); final jsRet = _invoke(arguments, thisVal);
if (jsRet == null) return; final ctx = _ctx!;
bool isException = jsIsException(jsRet) != 0; bool isException = jsIsException(jsRet) != 0;
if (isException) { if (isException) {
jsFreeValue(_ctx, jsRet); jsFreeValue(ctx, jsRet);
throw _parseJSException(_ctx); throw _parseJSException(ctx);
} }
final ret = _jsToDart(_ctx, jsRet); final ret = _jsToDart(ctx, jsRet);
jsFreeValue(_ctx, jsRet); jsFreeValue(ctx, jsRet);
return ret; return ret;
} }
Pointer<JSValue> _invoke(List<dynamic> arguments, [dynamic thisVal]) { Pointer<JSValue> _invoke(List<dynamic> arguments, [dynamic thisVal]) {
if (_val == null) throw JSError("InternalError: JSValue released"); final ctx = _ctx;
final val = _val;
if (ctx == null || val == null)
throw JSError("InternalError: JSValue released");
final args = arguments final args = arguments
.map( .map(
(e) => _dartToJs(_ctx, e), (e) => _dartToJs(ctx, e),
) )
.toList(); .toList();
final jsThis = _dartToJs(_ctx, thisVal); final jsThis = _dartToJs(ctx, thisVal);
final jsRet = jsCall(_ctx, _val, jsThis, args); final jsRet = jsCall(ctx, val, jsThis, args);
jsFreeValue(_ctx, jsThis); jsFreeValue(ctx, jsThis);
for (final jsArg in args) { for (final jsArg in args) {
jsFreeValue(_ctx, jsArg); jsFreeValue(ctx, jsArg);
} }
return jsRet; return jsRet;
} }
@@ -185,9 +192,9 @@ class _JSFunction extends _JSObject implements JSInvokable, _IsolateEncodable {
/// Dart function wrapper for isolate /// Dart function wrapper for isolate
class IsolateFunction extends JSInvokable implements _IsolateEncodable { class IsolateFunction extends JSInvokable implements _IsolateEncodable {
int _isolateId; int? _isolateId;
SendPort _port; SendPort? _port;
JSInvokable _invokable; JSInvokable? _invokable;
IsolateFunction._fromId(this._isolateId, this._port); IsolateFunction._fromId(this._isolateId, this._port);
IsolateFunction._new(this._invokable) { IsolateFunction._new(this._invokable) {
@@ -195,18 +202,17 @@ class IsolateFunction extends JSInvokable implements _IsolateEncodable {
} }
IsolateFunction(Function func) : this._new(_DartFunction(func)); IsolateFunction(Function func) : this._new(_DartFunction(func));
static ReceivePort _invokeHandler; static ReceivePort? _invokeHandler;
static Set<IsolateFunction> _handlers = Set(); static Set<IsolateFunction> _handlers = Set();
static get _handlePort { static get _handlePort {
if (_invokeHandler == null) { if (_invokeHandler == null) {
_invokeHandler = ReceivePort(); _invokeHandler = ReceivePort();
_invokeHandler.listen((msg) async { _invokeHandler!.listen((msg) async {
final msgPort = msg[#port]; final msgPort = msg[#port];
try { try {
final handler = _handlers.firstWhere( final handler = _handlers.firstWhereOrNull(
(v) => identityHashCode(v) == msg[#handler], (v) => identityHashCode(v) == msg[#handler],
orElse: () => null,
); );
if (handler == null) throw JSError('handler released'); if (handler == null) throw JSError('handler released');
final ret = _encodeData(await handler._handle(msg[#msg])); final ret = _encodeData(await handler._handle(msg[#msg]));
@@ -220,13 +226,14 @@ class IsolateFunction extends JSInvokable implements _IsolateEncodable {
} }
}); });
} }
return _invokeHandler.sendPort; return _invokeHandler!.sendPort;
} }
_send(msg) async { _send(msg) async {
if (_port == null) return _handle(msg); final port = _port;
if (port == null) return _handle(msg);
final evaluatePort = ReceivePort(); final evaluatePort = ReceivePort();
_port.send({ port.send({
#handler: _isolateId, #handler: _isolateId,
#msg: msg, #msg: msg,
#port: evaluatePort.sendPort, #port: evaluatePort.sendPort,
@@ -240,6 +247,7 @@ class IsolateFunction extends JSInvokable implements _IsolateEncodable {
_destroy() { _destroy() {
_handlers.remove(this); _handlers.remove(this);
_invokable?.free(); _invokable?.free();
_invokable = null;
} }
_handle(msg) async { _handle(msg) async {
@@ -257,7 +265,7 @@ class IsolateFunction extends JSInvokable implements _IsolateEncodable {
} }
final List args = _decodeData(msg[#args]); final List args = _decodeData(msg[#args]);
final thisVal = _decodeData(msg[#thisVal]); final thisVal = _decodeData(msg[#thisVal]);
return _invokable.invoke(args, thisVal); return _invokable?.invoke(args, thisVal);
} }
@override @override
@@ -270,7 +278,7 @@ class IsolateFunction extends JSInvokable implements _IsolateEncodable {
}); });
} }
static IsolateFunction _decode(Map obj) { static IsolateFunction? _decode(Map obj) {
if (obj.containsKey(#jsFunctionPort)) if (obj.containsKey(#jsFunctionPort))
return IsolateFunction._fromId( return IsolateFunction._fromId(
obj[#jsFunctionId], obj[#jsFunctionId],

View File

@@ -7,7 +7,7 @@
*/ */
part of '../flutter_qjs.dart'; part of '../flutter_qjs.dart';
dynamic _parseJSException(Pointer<JSContext> ctx, [Pointer<JSValue> perr]) { dynamic _parseJSException(Pointer<JSContext> ctx, [Pointer<JSValue>? perr]) {
final e = perr ?? jsGetException(ctx); final e = perr ?? jsGetException(ctx);
var err; var err;
try { try {
@@ -24,7 +24,7 @@ void _definePropertyValue(
Pointer<JSValue> obj, Pointer<JSValue> obj,
dynamic key, dynamic key,
dynamic val, { dynamic val, {
Map<dynamic, Pointer<JSValue>> cache, Map<dynamic, Pointer<JSValue>>? cache,
}) { }) {
final jsAtomVal = _dartToJs(ctx, key, cache: cache); final jsAtomVal = _dartToJs(ctx, key, cache: cache);
final jsAtom = jsValueToAtom(ctx, jsAtomVal); final jsAtom = jsValueToAtom(ctx, jsAtomVal);
@@ -43,7 +43,7 @@ Pointer<JSValue> _jsGetPropertyValue(
Pointer<JSContext> ctx, Pointer<JSContext> ctx,
Pointer<JSValue> obj, Pointer<JSValue> obj,
dynamic key, { dynamic key, {
Map<dynamic, dynamic> cache, Map<dynamic, Pointer<JSValue>>? cache,
}) { }) {
final jsAtomVal = _dartToJs(ctx, key, cache: cache); final jsAtomVal = _dartToJs(ctx, key, cache: cache);
final jsAtom = jsValueToAtom(ctx, jsAtomVal); final jsAtom = jsValueToAtom(ctx, jsAtomVal);
@@ -54,7 +54,7 @@ Pointer<JSValue> _jsGetPropertyValue(
} }
Pointer<JSValue> _dartToJs(Pointer<JSContext> ctx, dynamic val, Pointer<JSValue> _dartToJs(Pointer<JSContext> ctx, dynamic val,
{Map<dynamic, Pointer<JSValue>> cache}) { {Map<dynamic, Pointer<JSValue>>? cache}) {
if (val == null) return jsUNDEFINED(); if (val == null) return jsUNDEFINED();
if (val is Error) return _dartToJs(ctx, JSError(val, val.stackTrace)); if (val is Error) return _dartToJs(ctx, JSError(val, val.stackTrace));
if (val is Exception) return _dartToJs(ctx, JSError(val)); if (val is Exception) return _dartToJs(ctx, JSError(val));
@@ -65,7 +65,7 @@ Pointer<JSValue> _dartToJs(Pointer<JSContext> ctx, dynamic val,
_definePropertyValue(ctx, ret, "stack", val.stack); _definePropertyValue(ctx, ret, "stack", val.stack);
return ret; return ret;
} }
if (val is _JSObject) return jsDupValue(ctx, val._val); if (val is _JSObject) return jsDupValue(ctx, val._val!);
if (val is Future) { if (val is Future) {
final resolvingFunc = malloc<Uint8>(sizeOfJSValue * 2).cast<JSValue>(); final resolvingFunc = malloc<Uint8>(sizeOfJSValue * 2).cast<JSValue>();
final resolvingFunc2 = final resolvingFunc2 =
@@ -104,7 +104,7 @@ Pointer<JSValue> _dartToJs(Pointer<JSContext> ctx, dynamic val,
return ret; return ret;
} }
if (cache.containsKey(val)) { if (cache.containsKey(val)) {
return jsDupValue(ctx, cache[val]); return jsDupValue(ctx, cache[val]!);
} }
if (val is List) { if (val is List) {
final ret = jsNewArray(ctx); final ret = jsNewArray(ctx);
@@ -141,7 +141,7 @@ Pointer<JSValue> _dartToJs(Pointer<JSContext> ctx, dynamic val,
} }
dynamic _jsToDart(Pointer<JSContext> ctx, Pointer<JSValue> val, dynamic _jsToDart(Pointer<JSContext> ctx, Pointer<JSValue> val,
{Map<int, dynamic> cache}) { {Map<int, dynamic>? cache}) {
if (cache == null) cache = Map(); if (cache == null) cache = Map();
final tag = jsValueGetTag(val); final tag = jsValueGetTag(val);
if (jsTagIsFloat64(tag) != 0) { if (jsTagIsFloat64(tag) != 0) {
@@ -156,8 +156,8 @@ dynamic _jsToDart(Pointer<JSContext> ctx, Pointer<JSValue> val,
return jsToCString(ctx, val); return jsToCString(ctx, val);
case JSTag.OBJECT: case JSTag.OBJECT:
final rt = jsGetRuntime(ctx); final rt = jsGetRuntime(ctx);
final dartObjectClassId = runtimeOpaques[rt].dartObjectClassId; final dartObjectClassId = runtimeOpaques[rt]?.dartObjectClassId;
if (dartObjectClassId != 0) { if (dartObjectClassId != null) {
final dartObject = _DartObject.fromAddress( final dartObject = _DartObject.fromAddress(
rt, jsGetObjectOpaque(val, dartObjectClassId)); rt, jsGetObjectOpaque(val, dartObjectClassId));
if (dartObject != null) return dartObject._obj; if (dartObject != null) return dartObject._obj;
@@ -243,7 +243,6 @@ dynamic _jsToDart(Pointer<JSContext> ctx, Pointer<JSValue> val,
malloc.free(ptab); malloc.free(ptab);
return ret; return ret;
} }
break;
default: default:
} }
return null; return null;

View File

@@ -1,11 +1,11 @@
name: flutter_qjs name: flutter_qjs
description: This plugin is a simple js engine for flutter using the `quickjs` project. Plugin currently supports all the platforms except web! description: This plugin is a simple js engine for flutter using the `quickjs` project. Plugin currently supports all the platforms except web!
version: 0.3.4 version: 0.3.6
homepage: https://github.com/ekibun/flutter_qjs homepage: https://github.com/ekibun/flutter_qjs
environment: environment:
sdk: ">=2.7.0 <3.0.0" sdk: ">=2.12.0-0 <3.0.0"
flutter: ">=1.20.0 <2.0.0" flutter: ">=1.20.0"
dependencies: dependencies:
flutter: flutter:

View File

@@ -135,7 +135,7 @@ void main() async {
}); });
test('isolate bind this', () async { test('isolate bind this', () async {
final qjs = IsolateQjs(); final qjs = IsolateQjs();
JSInvokable localVar; JSInvokable? localVar;
JSInvokable setToGlobal = await qjs JSInvokable setToGlobal = await qjs
.evaluate('(name, func)=>{ this[name] = func }', name: '<eval>'); .evaluate('(name, func)=>{ this[name] = func }', name: '<eval>');
final func = IsolateFunction((args) { final func = IsolateFunction((args) {
@@ -146,8 +146,8 @@ void main() async {
func.free(); func.free();
setToGlobal.free(); setToGlobal.free();
final testFuncRet = await qjs.evaluate('test(()=>"ret")', name: '<eval>'); final testFuncRet = await qjs.evaluate('test(()=>"ret")', name: '<eval>');
expect(await localVar.invoke([]), 'ret', reason: 'bind function'); expect(await localVar?.invoke([]), 'ret', reason: 'bind function');
localVar.free(); localVar?.free();
expect(testFuncRet, 'ret', reason: 'bind function args return'); expect(testFuncRet, 'ret', reason: 'bind function args return');
await qjs.close(); await qjs.close();
}); });