mirror of
https://github.com/wgh136/flutter_qjs.git
synced 2025-09-27 05:27:23 +00:00
nullsafety
This commit is contained in:
@@ -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.
|
||||||
|
@@ -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
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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,21 +232,22 @@ void jsFreeRuntime(
|
|||||||
Pointer<JSRuntime> rt,
|
Pointer<JSRuntime> rt,
|
||||||
) {
|
) {
|
||||||
final referenceleak = <String>[];
|
final referenceleak = <String>[];
|
||||||
while (true) {
|
final opaque = runtimeOpaques[rt];
|
||||||
final ref = runtimeOpaques[rt]
|
if (opaque != null) {
|
||||||
?._ref
|
while (true) {
|
||||||
?.firstWhere((ref) => ref is JSRefLeakable, orElse: () => null);
|
final ref = opaque._ref.firstWhereOrNull((ref) => ref is JSRefLeakable);
|
||||||
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) {
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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,
|
||||||
|
@@ -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],
|
||||||
|
@@ -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;
|
||||||
|
@@ -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:
|
||||||
|
@@ -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();
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user