mirror of
https://github.com/wgh136/flutter_qjs.git
synced 2025-09-27 05:27:23 +00:00
v0.3.3
This commit is contained in:
@@ -6,6 +6,12 @@
|
||||
* @LastEditTime: 2020-12-02 11:36:40
|
||||
-->
|
||||
|
||||
## 0.3.3
|
||||
|
||||
* remove `JSInvokable.call`.
|
||||
* fix crash when throw error.
|
||||
* add reference count and leak detection.
|
||||
|
||||
## 0.3.2
|
||||
|
||||
* fix Promise reject cannot get Exception string.
|
||||
|
157
README-CN.md
Normal file
157
README-CN.md
Normal file
@@ -0,0 +1,157 @@
|
||||
<!--
|
||||
* @Description:
|
||||
* @Author: ekibun
|
||||
* @Date: 2020-08-08 08:16:50
|
||||
* @LastEditors: ekibun
|
||||
* @LastEditTime: 2020-10-03 00:44:41
|
||||
-->
|
||||
# flutter_qjs
|
||||
|
||||

|
||||

|
||||
|
||||
[English](README.md) | [中文](README-CN.md)
|
||||
|
||||
一个为flutter开发的 `quickjs` 引擎。插件基于 `dart:ffi`,支持除Web以外的所有平台!
|
||||
|
||||
## 基本使用
|
||||
|
||||
首先,创建 `FlutterQjs` 对象。调用 `dispatch` 建立事件循环:
|
||||
|
||||
```dart
|
||||
final engine = FlutterQjs(
|
||||
stackSize: 1024 * 1024, // change stack size here.
|
||||
);
|
||||
engine.dispatch();
|
||||
```
|
||||
|
||||
使用 `evaluate` 方法运行js脚本,方法同步执行,使用 `await` 来获得 `Promise` 结果:
|
||||
|
||||
```dart
|
||||
try {
|
||||
print(engine.evaluate(code ?? ''));
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
```
|
||||
|
||||
使用 `close` 方法销毁 quickjs 实例,其在再次调用 `evaluate` 时将会重建。当不再需要 `FlutterQjs` 对象时,关闭 `port` 参数来结束事件循环。**在 v0.3.3 后增加了引用检查,可能会抛出异常**。
|
||||
|
||||
```dart
|
||||
try {
|
||||
engine.port.close(); // stop dispatch loop
|
||||
engine.close(); // close engine
|
||||
} on JSError catch(e) {
|
||||
print(e); // catch reference leak exception
|
||||
}
|
||||
engine = null;
|
||||
```
|
||||
|
||||
dart 与 js 间数据以如下规则转换:
|
||||
|
||||
| dart | js |
|
||||
| ---------------------------- | ---------- |
|
||||
| Bool | boolean |
|
||||
| Int | number |
|
||||
| Double | number |
|
||||
| String | string |
|
||||
| Uint8List | ArrayBuffer|
|
||||
| List | Array |
|
||||
| Map | Object |
|
||||
| Function(arg1, arg2, ..., {thisVal})<br>JSInvokable.invoke(\[arg1, arg2, ...\], thisVal) | function.call(thisVal, arg1, arg2, ...) |
|
||||
| Future | Promise |
|
||||
| JSError | Error |
|
||||
| Object | DartObject |
|
||||
|
||||
## 使用模块
|
||||
|
||||
插件支持 ES6 模块方法 `import`。使用 `moduleHandler` 来处理模块请求:
|
||||
|
||||
```dart
|
||||
final engine = FlutterQjs(
|
||||
moduleHandler: (String module) {
|
||||
if(module == "hello")
|
||||
return "export default (name) => `hello \${name}!`;";
|
||||
throw Exception("Module Not found");
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
在JavaScript中,`import` 方法用以获取模块:
|
||||
|
||||
```javascript
|
||||
import("hello").then(({default: greet}) => greet("world"));
|
||||
```
|
||||
|
||||
**注:** 模块将只被编译一次. 调用 `FlutterQjs.close` 再 `evaluate` 来重置模块缓存。
|
||||
|
||||
若要使用异步方法来处理模块请求,请参见 [在 isolate 中运行](#在-isolate-中运行)。
|
||||
|
||||
## 在 isolate 中运行
|
||||
|
||||
创建 `IsolateQjs` 对象,设置 `moduleHandler` 来处理模块请求。 现在可以使用异步函数来获得模块字符串,如 `rootBundle.loadString`:
|
||||
|
||||
```dart
|
||||
final engine = IsolateQjs(
|
||||
moduleHandler: (String module) async {
|
||||
return await rootBundle.loadString(
|
||||
"js/" + module.replaceFirst(new RegExp(r".js$"), "") + ".js");
|
||||
},
|
||||
);
|
||||
// not need engine.dispatch();
|
||||
```
|
||||
|
||||
与在主线程运行一样,使用 `evaluate` 方法运行js脚本。在isolate中,所有结果都将异步返回,使用 `await` 来获取结果:
|
||||
|
||||
```dart
|
||||
try {
|
||||
print(await engine.evaluate(code ?? ''));
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
```
|
||||
|
||||
使用 `close` 方法销毁 isolate 线程,其在再次调用 `evaluate` 时将会重建。
|
||||
|
||||
## 调用 Dart 函数
|
||||
|
||||
Js脚本返回函数将被转换为 `JSInvokable`。 **它不能像 `Function` 一样调用,请使用 `invoke` 方法来调用**:
|
||||
|
||||
```dart
|
||||
(func as JSInvokable).invoke([arg1, arg2], thisVal);
|
||||
```
|
||||
|
||||
**注:** 返回 `JSInvokable` 可能造成引用泄漏,需要手动调用 `free` 来释放引用:
|
||||
|
||||
```dart
|
||||
(obj as JSRef).free();
|
||||
// or JSRef.freeRecursive(obj);
|
||||
```
|
||||
|
||||
传递给 `JSInvokable` 的参数将自动释放. 使用 `dup` 来保持引用:
|
||||
|
||||
```dart
|
||||
(obj as JSRef).dup();
|
||||
// or JSRef.dupRecursive(obj);
|
||||
```
|
||||
|
||||
自 v0.3.0 起,dart 函数可以作为参数传递给 `JSInvokable`,且 `channel` 函数不再默认内置。可以使用如下方法将 dart 函数赋值给全局,例如,使用 `Dio` 来为 qjs 提供 http 支持:
|
||||
|
||||
```dart
|
||||
final setToGlobalObject = await engine.evaluate("(key, val) => { this[key] = val; }");
|
||||
await setToGlobalObject.invoke(["http", (String url) {
|
||||
return Dio().get(url).then((response) => response.data);
|
||||
}]);
|
||||
setToGlobalObject.free();
|
||||
```
|
||||
|
||||
在 isolate 模式下,只有顶层和静态函数能作为参数传给 `JSInvokable`,函数将在 isolate 线程中调用。 使用 `IsolateFunction` 来传递局部函数(将在主线程中调用):
|
||||
|
||||
```dart
|
||||
await setToGlobalObject.invoke([
|
||||
"http",
|
||||
IsolateFunction((String url) {
|
||||
return Dio().get(url).then((response) => response.data);
|
||||
}),
|
||||
]);
|
||||
```
|
93
README.md
93
README.md
@@ -10,13 +10,15 @@
|
||||

|
||||

|
||||
|
||||
[English](README.md) | [中文](README-CN.md)
|
||||
|
||||
This plugin is a simple js engine for flutter using the `quickjs` project with `dart:ffi`. Plugin currently supports all the platforms except web!
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Basic usage
|
||||
|
||||
Firstly, create a `FlutterQjs` object, then call `dispatch` to dispatch event loop:
|
||||
Firstly, create a `FlutterQjs` object, then call `dispatch` to establish event loop:
|
||||
|
||||
```dart
|
||||
final engine = FlutterQjs(
|
||||
@@ -25,7 +27,7 @@ final engine = FlutterQjs(
|
||||
engine.dispatch();
|
||||
```
|
||||
|
||||
Use `evaluate` method to run js script, now you can use it synchronously, or use await to resolve `Promise`:
|
||||
Use `evaluate` method to run js script, it runs synchronously, you can use await to resolve `Promise`:
|
||||
|
||||
```dart
|
||||
try {
|
||||
@@ -35,7 +37,7 @@ try {
|
||||
}
|
||||
```
|
||||
|
||||
Method `close` can destroy quickjs runtime that can be recreated again if you call `evaluate`. Parameter `port` should be close to stop `dispatch` loop when you do not need it.
|
||||
Method `close` can destroy quickjs runtime that can be recreated again if you call `evaluate`. Parameter `port` should be close to stop `dispatch` loop when you do not need it. **Reference leak exception will be thrown since v0.3.3**
|
||||
|
||||
```dart
|
||||
try {
|
||||
@@ -50,7 +52,7 @@ engine = null;
|
||||
Data conversion between dart and js are implemented as follow:
|
||||
|
||||
| dart | js |
|
||||
| ----------------------- | ------------------ |
|
||||
| ---------------------------- | ---------- |
|
||||
| Bool | boolean |
|
||||
| Int | number |
|
||||
| Double | number |
|
||||
@@ -58,32 +60,12 @@ Data conversion between dart and js are implemented as follow:
|
||||
| Uint8List | ArrayBuffer|
|
||||
| List | Array |
|
||||
| Map | Object |
|
||||
| Function<br>JSInvokable | function(....args) |
|
||||
| Function(arg1, arg2, ..., {thisVal})<br>JSInvokable.invoke(\[arg1, arg2, ...\], thisVal) | function.call(thisVal, arg1, arg2, ...) |
|
||||
| Future | Promise |
|
||||
| JSError | Error |
|
||||
| Object | DartObject |
|
||||
|
||||
**notice:** `JSInvokable` does not extend `Function`, but can be used same as `Function`.
|
||||
Dart function uses named argument `thisVal` to manage js function `this`:
|
||||
|
||||
```dart
|
||||
func(arg1, arg2, {thisVal});
|
||||
```
|
||||
|
||||
or use `invoke` method to pass list parameters:
|
||||
|
||||
```dart
|
||||
(func as JSInvokable).invoke([arg1, arg2], thisVal);
|
||||
```
|
||||
|
||||
`JSInvokable` returned by evaluation may increase reference of JS object.
|
||||
You should manually call `free` to release JS reference:
|
||||
|
||||
```dart
|
||||
(func as JSInvokable).free();
|
||||
```
|
||||
|
||||
### Use modules
|
||||
## Use Modules
|
||||
|
||||
ES6 module with `import` function is supported and can be managed in dart with `moduleHandler`:
|
||||
|
||||
@@ -105,9 +87,8 @@ import("hello").then(({default: greet}) => greet("world"));
|
||||
|
||||
**notice:** Module handler should be called only once for each module name. To reset the module cache, call `FlutterQjs.close` then `evaluate` again.
|
||||
|
||||
To use async function in module handler, try [Run on isolate thread](#Run-on-isolate-thread)
|
||||
|
||||
### Run on isolate thread
|
||||
To use async function in module handler, try [run on isolate thread](#Run-on-Isolate-Thread)
|
||||
## Run on Isolate Thread
|
||||
|
||||
Create a `IsolateQjs` object, pass handlers to resolving modules. Async function such as `rootBundle.loadString` can be used now to get modules:
|
||||
|
||||
@@ -121,7 +102,7 @@ final engine = IsolateQjs(
|
||||
// not need engine.dispatch();
|
||||
```
|
||||
|
||||
Same as run on main thread, use `evaluate` to run js script. In this way, `Promise` return by `evaluate` will be automatically tracked and return the resolved data:
|
||||
Same as run on main thread, use `evaluate` to run js script. In isolate, everything returns asynchronously, use `await` to get the result:
|
||||
|
||||
```dart
|
||||
try {
|
||||
@@ -131,25 +112,49 @@ try {
|
||||
}
|
||||
```
|
||||
|
||||
Method `close` can destroy quickjs runtime that can be recreated again if you call `evaluate`.
|
||||
Method `close` can destroy isolate thread that will be recreated again if you call `evaluate`.
|
||||
|
||||
**notice:** Make sure arguments passed to `IsolateJSFunction` are avaliable for isolate, such as primities and top level function.
|
||||
Method `bind` can help to pass instance function to isolate:
|
||||
## Use Dart Function (Breaking change in v0.3.0)
|
||||
|
||||
Js script returning function will be converted to `JSInvokable`. **It does not extend `Function`, use `invoke` method to invoke it**:
|
||||
|
||||
```dart
|
||||
await jsFunc(await engine.bind(({thisVal}) {
|
||||
// DO SOMETHING
|
||||
}));
|
||||
(func as JSInvokable).invoke([arg1, arg2], thisVal);
|
||||
```
|
||||
|
||||
[This example](example/lib/main.dart) contains a complete demonstration on how to use this plugin.
|
||||
|
||||
## Breaking change in v0.3.0
|
||||
|
||||
`channel` function is no longer included by default.
|
||||
Use js function to set dart object globally:
|
||||
**notice:** evaluation returning `JSInvokable` may cause reference leak.
|
||||
You should manually call `free` to release JS reference.
|
||||
|
||||
```dart
|
||||
final setToGlobalObject = await engine.evaluate("(key, val) => this[key] = val;");
|
||||
await setToGlobalObject("channel", methodHandler);
|
||||
(obj as JSRef).free();
|
||||
// or JSRef.freeRecursive(obj);
|
||||
```
|
||||
|
||||
Arguments passed into `JSInvokable` will be freed automatically. Use `dup` to keep the reference.
|
||||
|
||||
```dart
|
||||
(obj as JSRef).dup();
|
||||
// or JSRef.dupRecursive(obj);
|
||||
```
|
||||
|
||||
Since v0.3.0, you can pass a function to `JSInvokable` arguments, and `channel` function is no longer included by default. You can use js function to set dart object globally.
|
||||
For example, use `Dio` to implement http in qjs:
|
||||
|
||||
```dart
|
||||
final setToGlobalObject = await engine.evaluate("(key, val) => { this[key] = val; }");
|
||||
await setToGlobalObject.invoke(["http", (String url) {
|
||||
return Dio().get(url).then((response) => response.data);
|
||||
}]);
|
||||
setToGlobalObject.free();
|
||||
```
|
||||
|
||||
In isolate, top level function passed in `JSInvokable` will be invoked in isolate thread. Use `IsolateFunction` to pass a instant function:
|
||||
|
||||
```dart
|
||||
await setToGlobalObject.invoke([
|
||||
"http",
|
||||
IsolateFunction((String url) {
|
||||
return Dio().get(url).then((response) => response.data);
|
||||
}),
|
||||
]);
|
||||
```
|
@@ -15,7 +15,7 @@ extern "C"
|
||||
|
||||
DLLEXPORT JSValue *jsThrow(JSContext *ctx, JSValue *obj)
|
||||
{
|
||||
return new JSValue(JS_Throw(ctx, *obj));
|
||||
return new JSValue(JS_Throw(ctx, JS_DupValue(ctx, *obj)));
|
||||
}
|
||||
|
||||
DLLEXPORT JSValue *jsEXCEPTION()
|
||||
|
@@ -75,7 +75,7 @@ packages:
|
||||
path: ".."
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.3.2"
|
||||
version: "0.3.3"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
|
@@ -5,7 +5,7 @@ import 'dart:isolate';
|
||||
import 'dart:typed_data';
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'src/ffi.dart';
|
||||
export 'src/ffi.dart' show JSEvalFlag;
|
||||
export 'src/ffi.dart' show JSEvalFlag, JSRef;
|
||||
|
||||
part 'src/engine.dart';
|
||||
part 'src/isolate.dart';
|
||||
|
@@ -22,6 +22,35 @@ abstract class JSRef {
|
||||
}
|
||||
|
||||
void destroy();
|
||||
|
||||
static void freeRecursive(dynamic obj) {
|
||||
_callRecursive(obj, (ref) => ref.free());
|
||||
}
|
||||
|
||||
static void dupRecursive(dynamic obj) {
|
||||
_callRecursive(obj, (ref) => ref.dup());
|
||||
}
|
||||
|
||||
static void _callRecursive(
|
||||
dynamic obj,
|
||||
void Function(JSRef) cb, [
|
||||
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) => _callRecursive(e, cb, cache));
|
||||
}
|
||||
if (obj is Map) {
|
||||
cache.add(obj);
|
||||
obj.values.forEach((e) => _callRecursive(e, cb, cache));
|
||||
}
|
||||
if (obj is JSRef) {
|
||||
cb(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class JSRefLeakable {}
|
||||
@@ -188,8 +217,10 @@ void jsFreeRuntime(
|
||||
}
|
||||
while (0 < runtimeOpaques[rt]?._ref?.length ?? 0) {
|
||||
final ref = runtimeOpaques[rt]?._ref?.first;
|
||||
final objStrs = ref.toString().split('\n');
|
||||
final objStr = objStrs.length > 0 ? objStrs[0] + " ..." : objStrs[0];
|
||||
referenceleak.add(
|
||||
" ${identityHashCode(ref)}\t${ref._refCount + 1}\t${ref.runtimeType.toString()}\t${ref.toString().replaceAll('\n', '\\n')}");
|
||||
" ${identityHashCode(ref)}\t${ref._refCount + 1}\t${ref.runtimeType.toString()}\t$objStr");
|
||||
ref.destroy();
|
||||
}
|
||||
_jsFreeRuntime(rt);
|
||||
|
@@ -17,11 +17,7 @@ abstract class _IsolateEncodable {
|
||||
Map _encode();
|
||||
}
|
||||
|
||||
final List _sendAllowType = [Null, String, int, double, bool, SendPort];
|
||||
|
||||
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.containsKey(data)) return cache[data];
|
||||
if (data is _IsolateEncodable) return data._encode();
|
||||
@@ -59,7 +55,7 @@ dynamic _encodeData(dynamic data, {Map<dynamic, dynamic> cache}) {
|
||||
#jsFuturePort: futurePort.sendPort,
|
||||
};
|
||||
}
|
||||
throw JSError('unsupport type: ${data.runtimeType}\n${data.toString()}');
|
||||
return data;
|
||||
}
|
||||
|
||||
dynamic _decodeData(dynamic data, {Map<dynamic, dynamic> cache}) {
|
||||
|
@@ -24,23 +24,6 @@ class _DartFunction extends JSInvokable {
|
||||
final Function _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
|
||||
invoke(List args, [thisVal]) {
|
||||
/// wrap this into function
|
||||
@@ -48,8 +31,8 @@ class _DartFunction extends JSInvokable {
|
||||
RegExp('{.*thisVal.*}').hasMatch(_func.runtimeType.toString());
|
||||
final ret =
|
||||
Function.apply(_func, args, passThis ? {#thisVal: thisVal} : null);
|
||||
_freeRecursive(args);
|
||||
_freeRecursive(thisVal);
|
||||
JSRef.freeRecursive(args);
|
||||
JSRef.freeRecursive(thisVal);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -179,7 +162,7 @@ class _JSFunction extends _JSObject implements JSInvokable, _IsolateEncodable {
|
||||
}
|
||||
|
||||
Pointer _invoke(List<dynamic> arguments, [dynamic thisVal]) {
|
||||
if (_val == null) return null;
|
||||
if (_val == null) throw JSError("InternalError: JSValue released");
|
||||
List<Pointer> args = arguments
|
||||
.map(
|
||||
(e) => _dartToJs(_ctx, e),
|
||||
@@ -196,22 +179,26 @@ class _JSFunction extends _JSObject implements JSInvokable, _IsolateEncodable {
|
||||
|
||||
@override
|
||||
Map _encode() {
|
||||
final func = IsolateFunction._new(this);
|
||||
final ret = func._encode();
|
||||
return ret;
|
||||
return IsolateFunction._new(this)._encode();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _IsolatePortHandler {
|
||||
/// Dart function wrapper for isolate
|
||||
class IsolateFunction extends JSInvokable implements _IsolateEncodable {
|
||||
int _isolateId;
|
||||
dynamic _handle(dynamic);
|
||||
SendPort _port;
|
||||
JSInvokable _invokable;
|
||||
IsolateFunction._fromId(this._isolateId, this._port);
|
||||
|
||||
IsolateFunction._new(this._invokable) {
|
||||
_handlers.add(this);
|
||||
}
|
||||
IsolateFunction(Function func) : this._new(_DartFunction(func));
|
||||
|
||||
class _IsolatePort {
|
||||
static ReceivePort _invokeHandler;
|
||||
static Set<_IsolatePortHandler> _handlers = Set();
|
||||
static Set<IsolateFunction> _handlers = Set();
|
||||
|
||||
static get _port {
|
||||
static get _handlePort {
|
||||
if (_invokeHandler == null) {
|
||||
_invokeHandler = ReceivePort();
|
||||
_invokeHandler.listen((msg) async {
|
||||
@@ -236,11 +223,11 @@ class _IsolatePort {
|
||||
return _invokeHandler.sendPort;
|
||||
}
|
||||
|
||||
static _send(SendPort isolate, _IsolatePortHandler handler, msg) async {
|
||||
if (isolate == null) return handler._handle(msg);
|
||||
_send(msg) async {
|
||||
if (_port == null) return _handle(msg);
|
||||
final evaluatePort = ReceivePort();
|
||||
isolate.send({
|
||||
#handler: handler._isolateId,
|
||||
_port.send({
|
||||
#handler: _isolateId,
|
||||
#msg: msg,
|
||||
#port: evaluatePort.sendPort,
|
||||
});
|
||||
@@ -250,33 +237,11 @@ class _IsolatePort {
|
||||
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);
|
||||
_handlers.remove(this);
|
||||
_invokable?.free();
|
||||
}
|
||||
|
||||
@override
|
||||
_handle(msg) async {
|
||||
switch (msg) {
|
||||
case #dup:
|
||||
@@ -284,7 +249,6 @@ class IsolateFunction extends JSInvokable
|
||||
return null;
|
||||
case #free:
|
||||
_refCount--;
|
||||
print("${identityHashCode(this)} ref $_refCount");
|
||||
if (_refCount < 0) _destroy();
|
||||
return null;
|
||||
case #destroy:
|
||||
@@ -300,8 +264,7 @@ class IsolateFunction extends JSInvokable
|
||||
Future invoke(List positionalArguments, [thisVal]) async {
|
||||
List dArgs = _encodeData(positionalArguments);
|
||||
Map dThisVal = _encodeData(thisVal);
|
||||
return _IsolatePort._send(_port, this, {
|
||||
#type: #invokeIsolate,
|
||||
return _send({
|
||||
#args: dArgs,
|
||||
#thisVal: dThisVal,
|
||||
});
|
||||
@@ -320,7 +283,7 @@ class IsolateFunction extends JSInvokable
|
||||
Map _encode() {
|
||||
return {
|
||||
#jsFunctionId: _isolateId ?? identityHashCode(this),
|
||||
#jsFunctionPort: _port ?? _IsolatePort._port,
|
||||
#jsFunctionPort: _port ?? IsolateFunction._handlePort,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -328,16 +291,16 @@ class IsolateFunction extends JSInvokable
|
||||
|
||||
@override
|
||||
dup() {
|
||||
_IsolatePort._send(_port, this, #dup);
|
||||
_send(#dup);
|
||||
}
|
||||
|
||||
@override
|
||||
free() {
|
||||
_IsolatePort._send(_port, this, #free);
|
||||
_send(#free);
|
||||
}
|
||||
|
||||
@override
|
||||
void destroy() {
|
||||
_IsolatePort._send(_port, this, #destroy);
|
||||
_send(#destroy);
|
||||
}
|
||||
}
|
||||
|
@@ -55,6 +55,8 @@ Pointer _jsGetPropertyValue(
|
||||
|
||||
Pointer _dartToJs(Pointer ctx, dynamic val, {Map<dynamic, dynamic> cache}) {
|
||||
if (val == null) return jsUNDEFINED();
|
||||
if (val is Error) return _dartToJs(ctx, JSError(val, val.stackTrace));
|
||||
if (val is Exception) return _dartToJs(ctx, JSError(val));
|
||||
if (val is JSError) {
|
||||
final ret = jsNewError(ctx);
|
||||
_definePropertyValue(ctx, ret, "name", "");
|
||||
@@ -187,9 +189,11 @@ dynamic _jsToDart(Pointer ctx, Pointer val, {Map<int, dynamic> cache}) {
|
||||
final jsPromise = _JSObject(ctx, val);
|
||||
final jsRet = promiseThen._invoke([
|
||||
(v) {
|
||||
JSRef.dupRecursive(v);
|
||||
if (!completer.isCompleted) completer.complete(v);
|
||||
},
|
||||
(e) {
|
||||
JSRef.dupRecursive(e);
|
||||
if (!completer.isCompleted) completer.completeError(e);
|
||||
},
|
||||
], jsPromise);
|
||||
|
@@ -1,6 +1,6 @@
|
||||
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!
|
||||
version: 0.3.2
|
||||
version: 0.3.3
|
||||
homepage: https://github.com/ekibun/flutter_qjs
|
||||
|
||||
environment:
|
||||
|
@@ -8,7 +8,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:flutter_qjs/flutter_qjs.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
@@ -19,7 +18,7 @@ dynamic myFunction(String args, {thisVal}) {
|
||||
|
||||
Future testEvaluate(qjs) async {
|
||||
JSInvokable wrapFunction = await qjs.evaluate(
|
||||
'(a) => a',
|
||||
'async (a) => a',
|
||||
name: '<testWrap>',
|
||||
);
|
||||
dynamic testWrap = await wrapFunction.invoke([wrapFunction]);
|
||||
@@ -68,11 +67,6 @@ Future testEvaluate(qjs) async {
|
||||
}
|
||||
|
||||
void main() async {
|
||||
test('send', () async {
|
||||
final rec = ReceivePort();
|
||||
rec.close();
|
||||
rec.sendPort.send("3232");
|
||||
});
|
||||
test('make', () async {
|
||||
final utf8Encoding = Encoding.getByName('utf-8');
|
||||
var cmakePath = 'cmake';
|
||||
@@ -139,25 +133,22 @@ void main() async {
|
||||
await testEvaluate(qjs);
|
||||
await qjs.close();
|
||||
});
|
||||
test('isolate bind function', () async {
|
||||
test('isolate bind this', () async {
|
||||
final qjs = IsolateQjs();
|
||||
final localVars = [];
|
||||
JSInvokable testFunc =
|
||||
await qjs.evaluate('(func)=>func(()=>"ret")', name: '<eval>');
|
||||
final func = IsolateFunction.func((args) {
|
||||
localVars.add(args..dup());
|
||||
JSInvokable localVar;
|
||||
JSInvokable setToGlobal = await qjs
|
||||
.evaluate('(name, func)=>{ this[name] = func }', name: '<eval>');
|
||||
final func = IsolateFunction((args) {
|
||||
localVar = args..dup();
|
||||
return args.invoke([]);
|
||||
});
|
||||
final testFuncRet = await testFunc.invoke([func..dup()]);
|
||||
final testFuncRet2 = await testFunc.invoke([func..dup()]);
|
||||
await setToGlobal.invoke(["test", func..dup()]);
|
||||
func.free();
|
||||
testFunc.free();
|
||||
for (IsolateFunction vars in localVars) {
|
||||
expect(await vars.invoke([]), 'ret', reason: 'bind function');
|
||||
vars.free();
|
||||
}
|
||||
setToGlobal.free();
|
||||
final testFuncRet = await qjs.evaluate('test(()=>"ret")', name: '<eval>');
|
||||
expect(await localVar.invoke([]), 'ret', reason: 'bind function');
|
||||
localVar.free();
|
||||
expect(testFuncRet, 'ret', reason: 'bind function args return');
|
||||
expect(testFuncRet2, testFuncRet, reason: 'bind function args return2');
|
||||
await qjs.close();
|
||||
});
|
||||
test('reference leak', () async {
|
||||
|
Reference in New Issue
Block a user