mirror of
https://github.com/wgh136/flutter_qjs.git
synced 2025-09-27 05:27:23 +00:00
v0.3.0
This commit is contained in:
11
lib/ffi.dart
11
lib/ffi.dart
@@ -186,17 +186,6 @@ final Pointer Function(
|
||||
)>>("jsNewCFunction")
|
||||
.asFunction();
|
||||
|
||||
/// JSValue *jsGetGlobalObject(JSContext *ctx)
|
||||
final Pointer Function(
|
||||
Pointer ctx,
|
||||
) jsGetGlobalObject = qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Pointer Function(
|
||||
Pointer,
|
||||
)>>("jsGetGlobalObject")
|
||||
.asFunction();
|
||||
|
||||
/// JSContext *jsNewContext(JSRuntime *rt)
|
||||
final Pointer Function(
|
||||
Pointer rt,
|
||||
|
@@ -13,6 +13,8 @@ import 'package:ffi/ffi.dart';
|
||||
import 'package:flutter_qjs/ffi.dart';
|
||||
import 'package:flutter_qjs/wrapper.dart';
|
||||
|
||||
import 'isolate.dart';
|
||||
|
||||
/// Handler function to manage js module.
|
||||
typedef JsModuleHandler = String Function(String name);
|
||||
|
||||
@@ -44,11 +46,10 @@ class FlutterQjs {
|
||||
this.hostPromiseRejectionHandler,
|
||||
});
|
||||
|
||||
setToGlobalObject(dynamic key, dynamic val) {
|
||||
_ensureEngine();
|
||||
final globalObject = jsGetGlobalObject(_ctx);
|
||||
definePropertyValue(_ctx, globalObject, key, val);
|
||||
jsFreeValue(_ctx, globalObject);
|
||||
static applyFunction(Function func, List args, dynamic thisVal) {
|
||||
final passThis =
|
||||
RegExp("{.*thisVal.*}").hasMatch(func.runtimeType.toString());
|
||||
return Function.apply(func, args, passThis ? {#thisVal: thisVal} : null);
|
||||
}
|
||||
|
||||
_ensureEngine() {
|
||||
@@ -68,13 +69,11 @@ class FlutterQjs {
|
||||
)));
|
||||
}
|
||||
final thisVal = jsToDart(ctx, pdata.elementAt(0).value);
|
||||
Function func = jsToDart(ctx, pdata.elementAt(3).value);
|
||||
final passThis =
|
||||
RegExp("{.*thisVal.*}").hasMatch(func.runtimeType.toString());
|
||||
return dartToJs(
|
||||
ctx,
|
||||
Function.apply(func, args, passThis ? {#thisVal: thisVal} : null),
|
||||
);
|
||||
final func = jsToDart(ctx, pdata.elementAt(3).value);
|
||||
final ret = func is QjsInvokable
|
||||
? func.invoke(args, thisVal)
|
||||
: applyFunction(func, args, thisVal);
|
||||
return dartToJs(ctx, ret);
|
||||
case JSChannelType.MODULE:
|
||||
if (moduleHandler == null) throw Exception("No ModuleHandler");
|
||||
var ret = Utf8.toUtf8(moduleHandler(
|
||||
|
128
lib/isolate.dart
128
lib/isolate.dart
@@ -14,13 +14,13 @@ import 'package:ffi/ffi.dart';
|
||||
import 'package:flutter_qjs/flutter_qjs.dart';
|
||||
import 'package:flutter_qjs/wrapper.dart';
|
||||
|
||||
class IsolateJSFunction {
|
||||
class IsolateJSFunction implements QjsInvokable {
|
||||
int val;
|
||||
int ctx;
|
||||
SendPort port;
|
||||
IsolateJSFunction(this.ctx, this.val, this.port);
|
||||
|
||||
Future<dynamic> invoke(List<dynamic> arguments) async {
|
||||
Future invoke(List arguments, [thisVal]) async {
|
||||
if (0 == val ?? 0) return;
|
||||
var evaluatePort = ReceivePort();
|
||||
port.send({
|
||||
@@ -28,9 +28,11 @@ class IsolateJSFunction {
|
||||
'ctx': ctx,
|
||||
'val': val,
|
||||
'args': _encodeData(arguments),
|
||||
'this': _encodeData(thisVal),
|
||||
'port': evaluatePort.sendPort,
|
||||
});
|
||||
var result = await evaluatePort.first;
|
||||
evaluatePort.close();
|
||||
if (result['data'] != null)
|
||||
return _decodeData(result['data'], port);
|
||||
else
|
||||
@@ -39,7 +41,63 @@ class IsolateJSFunction {
|
||||
|
||||
@override
|
||||
noSuchMethod(Invocation invocation) {
|
||||
return invoke(invocation.positionalArguments);
|
||||
return invoke(
|
||||
invocation.positionalArguments,
|
||||
invocation.namedArguments[#thisVal],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class IsolateFunction implements QjsInvokable {
|
||||
SendPort _port;
|
||||
SendPort func;
|
||||
IsolateFunction(this.func, this._port);
|
||||
|
||||
static IsolateFunction bind(Function func, SendPort port) {
|
||||
final funcPort = ReceivePort();
|
||||
funcPort.listen((msg) async {
|
||||
var data;
|
||||
SendPort msgPort = msg['port'];
|
||||
try {
|
||||
List args = _decodeData(msg['args'], port);
|
||||
Map thisVal = _decodeData(msg['this'], port);
|
||||
data = await FlutterQjs.applyFunction(func, args, thisVal);
|
||||
if (msgPort != null)
|
||||
msgPort.send({
|
||||
'data': _encodeData(data),
|
||||
});
|
||||
} catch (e, stack) {
|
||||
if (msgPort != null)
|
||||
msgPort.send({
|
||||
'error': e.toString() + "\n" + stack.toString(),
|
||||
});
|
||||
}
|
||||
});
|
||||
return IsolateFunction(funcPort.sendPort, port);
|
||||
}
|
||||
|
||||
Future invoke(List positionalArguments, [thisVal]) async {
|
||||
if (func == null) return;
|
||||
var evaluatePort = ReceivePort();
|
||||
func.send({
|
||||
'args': _encodeData(positionalArguments),
|
||||
'this': _encodeData(thisVal),
|
||||
'port': evaluatePort.sendPort,
|
||||
});
|
||||
var result = await evaluatePort.first;
|
||||
evaluatePort.close();
|
||||
if (result['data'] != null)
|
||||
return _decodeData(result['data'], _port);
|
||||
else
|
||||
throw result['error'];
|
||||
}
|
||||
|
||||
@override
|
||||
noSuchMethod(Invocation invocation) {
|
||||
return invoke(
|
||||
invocation.positionalArguments,
|
||||
invocation.namedArguments[#thisVal],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,17 +133,24 @@ dynamic _encodeData(dynamic data, {Map<dynamic, dynamic> cache}) {
|
||||
'__js_function_val': data.val,
|
||||
};
|
||||
}
|
||||
if (data is IsolateFunction) {
|
||||
return {
|
||||
'__js_function_port': data.func,
|
||||
};
|
||||
}
|
||||
if (data is Future) {
|
||||
var futurePort = ReceivePort();
|
||||
data.then((value) {
|
||||
futurePort.first.then((port) => {
|
||||
(port as SendPort).send({'data': _encodeData(value)})
|
||||
});
|
||||
futurePort.first.then((port) {
|
||||
futurePort.close();
|
||||
(port as SendPort).send({'data': _encodeData(value)});
|
||||
});
|
||||
}, onError: (e, stack) {
|
||||
futurePort.first.then((port) => {
|
||||
(port as SendPort)
|
||||
.send({'error': e.toString() + "\n" + stack.toString()})
|
||||
});
|
||||
futurePort.first.then((port) {
|
||||
futurePort.close();
|
||||
(port as SendPort)
|
||||
.send({'error': e.toString() + "\n" + stack.toString()});
|
||||
});
|
||||
});
|
||||
return {
|
||||
'__js_future_port': futurePort.sendPort,
|
||||
@@ -116,12 +181,17 @@ dynamic _decodeData(dynamic data, SendPort port,
|
||||
return JSFunction.fromAddress(ctx, val);
|
||||
}
|
||||
}
|
||||
if (data.containsKey('__js_function_port')) {
|
||||
return IsolateFunction(data['__js_function_port'], port);
|
||||
}
|
||||
if (data.containsKey('__js_future_port')) {
|
||||
SendPort port = data['__js_future_port'];
|
||||
var futurePort = ReceivePort();
|
||||
port.send(futurePort.sendPort);
|
||||
var futureCompleter = Completer();
|
||||
futureCompleter.future.catchError((e) {});
|
||||
futurePort.first.then((value) {
|
||||
futurePort.close();
|
||||
if (value['error'] != null) {
|
||||
futureCompleter.completeError(value['error']);
|
||||
} else {
|
||||
@@ -172,8 +242,7 @@ void _runJsIsolate(Map spawnMessage) async {
|
||||
return ret;
|
||||
},
|
||||
);
|
||||
qjs.dispatch();
|
||||
await for (var msg in port) {
|
||||
port.listen((msg) async {
|
||||
var data;
|
||||
SendPort msgPort = msg['port'];
|
||||
try {
|
||||
@@ -189,10 +258,10 @@ void _runJsIsolate(Map spawnMessage) async {
|
||||
data = JSFunction.fromAddress(
|
||||
msg['ctx'],
|
||||
msg['val'],
|
||||
).invoke(_decodeData(msg['args'], null));
|
||||
break;
|
||||
case 'setToGlobalObject':
|
||||
qjs.setToGlobalObject(msg['key'], msg['val']);
|
||||
).invoke(
|
||||
_decodeData(msg['args'], null),
|
||||
_decodeData(msg['this'], null),
|
||||
);
|
||||
break;
|
||||
case 'close':
|
||||
qjs.port.close();
|
||||
@@ -210,7 +279,8 @@ void _runJsIsolate(Map spawnMessage) async {
|
||||
'error': e.toString() + "\n" + stack.toString(),
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
await qjs.dispatch();
|
||||
}
|
||||
|
||||
typedef JsAsyncModuleHandler = Future<String> Function(String name);
|
||||
@@ -290,6 +360,12 @@ class IsolateQjs {
|
||||
_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;
|
||||
@@ -301,23 +377,6 @@ class IsolateQjs {
|
||||
_sendPort = null;
|
||||
}
|
||||
|
||||
setToGlobalObject(dynamic key, dynamic val) async {
|
||||
_ensureEngine();
|
||||
var evaluatePort = ReceivePort();
|
||||
var sendPort = await _sendPort;
|
||||
sendPort.send({
|
||||
'type': 'setToGlobalObject',
|
||||
'key': key,
|
||||
'val': val,
|
||||
'port': evaluatePort.sendPort,
|
||||
});
|
||||
var result = await evaluatePort.first;
|
||||
if (result['error'] == null) {
|
||||
return;
|
||||
} else
|
||||
throw result['error'];
|
||||
}
|
||||
|
||||
/// Evaluate js script.
|
||||
Future<dynamic> evaluate(String command, {String name, int evalFlags}) async {
|
||||
_ensureEngine();
|
||||
@@ -331,6 +390,7 @@ class IsolateQjs {
|
||||
'port': evaluatePort.sendPort,
|
||||
});
|
||||
var result = await evaluatePort.first;
|
||||
evaluatePort.close();
|
||||
if (result['error'] == null) {
|
||||
return _decodeData(result['data'], sendPort);
|
||||
} else
|
||||
|
@@ -12,6 +12,7 @@ import 'dart:typed_data';
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
import 'ffi.dart';
|
||||
import 'isolate.dart';
|
||||
|
||||
class JSRefValue implements JSRef {
|
||||
Pointer val;
|
||||
@@ -37,6 +38,14 @@ class JSRefValue implements JSRef {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class QjsReleasable {
|
||||
void release();
|
||||
}
|
||||
|
||||
abstract class QjsInvokable {
|
||||
dynamic invoke(List positionalArguments, [dynamic thisVal]);
|
||||
}
|
||||
|
||||
class DartObject implements JSRef {
|
||||
Object obj;
|
||||
Pointer ctx;
|
||||
@@ -53,6 +62,7 @@ class DartObject implements JSRef {
|
||||
|
||||
@override
|
||||
void release() {
|
||||
if (obj is QjsReleasable) (obj as QjsReleasable).release();
|
||||
obj = null;
|
||||
ctx = null;
|
||||
}
|
||||
@@ -90,19 +100,19 @@ class JSPromise extends JSRefValue {
|
||||
}
|
||||
}
|
||||
|
||||
class JSFunction extends JSRefValue {
|
||||
class JSFunction extends JSRefValue implements QjsInvokable {
|
||||
JSFunction(Pointer ctx, Pointer val) : super(ctx, val);
|
||||
|
||||
JSFunction.fromAddress(int ctx, int val) : super.fromAddress(ctx, val);
|
||||
|
||||
invoke(List<dynamic> arguments) {
|
||||
invoke(List<dynamic> arguments, [dynamic thisVal]) {
|
||||
if (val == null) return;
|
||||
List<Pointer> args = arguments
|
||||
.map(
|
||||
(e) => dartToJs(ctx, e),
|
||||
)
|
||||
.toList();
|
||||
Pointer jsRet = jsCall(ctx, val, null, args);
|
||||
Pointer jsRet = jsCall(ctx, val, dartToJs(ctx, thisVal), args);
|
||||
for (Pointer jsArg in args) {
|
||||
jsFreeValue(ctx, jsArg);
|
||||
}
|
||||
@@ -118,7 +128,10 @@ class JSFunction extends JSRefValue {
|
||||
|
||||
@override
|
||||
noSuchMethod(Invocation invocation) {
|
||||
return invoke(invocation.positionalArguments);
|
||||
return invoke(
|
||||
invocation.positionalArguments,
|
||||
invocation.namedArguments[#thisVal],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,7 +241,7 @@ Pointer dartToJs(Pointer ctx, dynamic val, {Map<dynamic, dynamic> cache}) {
|
||||
dartObjectClassId,
|
||||
identityHashCode(DartObject(ctx, val)),
|
||||
);
|
||||
if (val is Function) {
|
||||
if (val is Function || val is IsolateFunction) {
|
||||
final ret = jsNewCFunction(ctx, dartObject);
|
||||
jsFreeValue(ctx, dartObject);
|
||||
return ret;
|
||||
@@ -343,6 +356,7 @@ Pointer jsNewContextWithPromsieWrapper(Pointer rt) {
|
||||
jsFreeValue(ctx, jsPromiseWrapper);
|
||||
runtimeOpaque.promiseToFuture = (promise) {
|
||||
var completer = Completer();
|
||||
completer.future.catchError((e) {});
|
||||
var wrapper = promiseWrapper.val;
|
||||
if (wrapper == null)
|
||||
completer.completeError(Exception("Runtime has been released!"));
|
||||
|
Reference in New Issue
Block a user