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
|
* @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
|
## 0.3.2
|
||||||
|
|
||||||
* fix Promise reject cannot get Exception string.
|
* 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);
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
```
|
115
README.md
115
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!
|
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
|
## Getting Started
|
||||||
|
|
||||||
### Basic usage
|
### 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
|
```dart
|
||||||
final engine = FlutterQjs(
|
final engine = FlutterQjs(
|
||||||
@@ -25,7 +27,7 @@ final engine = FlutterQjs(
|
|||||||
engine.dispatch();
|
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
|
```dart
|
||||||
try {
|
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
|
```dart
|
||||||
try {
|
try {
|
||||||
@@ -49,41 +51,21 @@ engine = null;
|
|||||||
|
|
||||||
Data conversion between dart and js are implemented as follow:
|
Data conversion between dart and js are implemented as follow:
|
||||||
|
|
||||||
| dart | js |
|
| dart | js |
|
||||||
| ----------------------- | ------------------ |
|
| ---------------------------- | ---------- |
|
||||||
| Bool | boolean |
|
| Bool | boolean |
|
||||||
| Int | number |
|
| Int | number |
|
||||||
| Double | number |
|
| Double | number |
|
||||||
| String | string |
|
| String | string |
|
||||||
| Uint8List | ArrayBuffer |
|
| Uint8List | ArrayBuffer|
|
||||||
| List | Array |
|
| List | Array |
|
||||||
| Map | Object |
|
| Map | Object |
|
||||||
| Function<br>JSInvokable | function(....args) |
|
| Function(arg1, arg2, ..., {thisVal})<br>JSInvokable.invoke(\[arg1, arg2, ...\], thisVal) | function.call(thisVal, arg1, arg2, ...) |
|
||||||
| Future | Promise |
|
| Future | Promise |
|
||||||
| JSError | Error |
|
| JSError | Error |
|
||||||
| Object | DartObject |
|
| Object | DartObject |
|
||||||
|
|
||||||
**notice:** `JSInvokable` does not extend `Function`, but can be used same as `Function`.
|
## Use Modules
|
||||||
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
|
|
||||||
|
|
||||||
ES6 module with `import` function is supported and can be managed in dart with `moduleHandler`:
|
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.
|
**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)
|
To use async function in module handler, try [run on isolate thread](#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:
|
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();
|
// 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
|
```dart
|
||||||
try {
|
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.
|
## Use Dart Function (Breaking change in v0.3.0)
|
||||||
Method `bind` can help to pass instance function to isolate:
|
|
||||||
|
Js script returning function will be converted to `JSInvokable`. **It does not extend `Function`, use `invoke` method to invoke it**:
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
await jsFunc(await engine.bind(({thisVal}) {
|
(func as JSInvokable).invoke([arg1, arg2], thisVal);
|
||||||
// DO SOMETHING
|
|
||||||
}));
|
|
||||||
```
|
```
|
||||||
|
|
||||||
[This example](example/lib/main.dart) contains a complete demonstration on how to use this plugin.
|
**notice:** evaluation returning `JSInvokable` may cause reference leak.
|
||||||
|
You should manually call `free` to release JS reference.
|
||||||
## Breaking change in v0.3.0
|
|
||||||
|
|
||||||
`channel` function is no longer included by default.
|
|
||||||
Use js function to set dart object globally:
|
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
final setToGlobalObject = await engine.evaluate("(key, val) => this[key] = val;");
|
(obj as JSRef).free();
|
||||||
await setToGlobalObject("channel", methodHandler);
|
// 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)
|
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()
|
DLLEXPORT JSValue *jsEXCEPTION()
|
||||||
|
@@ -75,7 +75,7 @@ packages:
|
|||||||
path: ".."
|
path: ".."
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "0.3.2"
|
version: "0.3.3"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@@ -5,7 +5,7 @@ import 'dart:isolate';
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'package:ffi/ffi.dart';
|
import 'package:ffi/ffi.dart';
|
||||||
import 'src/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/engine.dart';
|
||||||
part 'src/isolate.dart';
|
part 'src/isolate.dart';
|
||||||
|
@@ -22,6 +22,35 @@ abstract class JSRef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void destroy();
|
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 {}
|
abstract class JSRefLeakable {}
|
||||||
@@ -188,8 +217,10 @@ void jsFreeRuntime(
|
|||||||
}
|
}
|
||||||
while (0 < runtimeOpaques[rt]?._ref?.length ?? 0) {
|
while (0 < runtimeOpaques[rt]?._ref?.length ?? 0) {
|
||||||
final ref = runtimeOpaques[rt]?._ref?.first;
|
final ref = runtimeOpaques[rt]?._ref?.first;
|
||||||
|
final objStrs = ref.toString().split('\n');
|
||||||
|
final objStr = objStrs.length > 0 ? objStrs[0] + " ..." : objStrs[0];
|
||||||
referenceleak.add(
|
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();
|
ref.destroy();
|
||||||
}
|
}
|
||||||
_jsFreeRuntime(rt);
|
_jsFreeRuntime(rt);
|
||||||
|
@@ -17,11 +17,7 @@ abstract class _IsolateEncodable {
|
|||||||
Map _encode();
|
Map _encode();
|
||||||
}
|
}
|
||||||
|
|
||||||
final List _sendAllowType = [Null, String, int, double, bool, SendPort];
|
|
||||||
|
|
||||||
dynamic _encodeData(dynamic data, {Map<dynamic, dynamic> cache}) {
|
dynamic _encodeData(dynamic data, {Map<dynamic, dynamic> cache}) {
|
||||||
if (data is Function) return data;
|
|
||||||
if (_sendAllowType.contains(data.runtimeType)) return data;
|
|
||||||
if (cache == null) cache = Map();
|
if (cache == null) cache = Map();
|
||||||
if (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();
|
||||||
@@ -59,7 +55,7 @@ dynamic _encodeData(dynamic data, {Map<dynamic, dynamic> cache}) {
|
|||||||
#jsFuturePort: futurePort.sendPort,
|
#jsFuturePort: futurePort.sendPort,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
throw JSError('unsupport type: ${data.runtimeType}\n${data.toString()}');
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
dynamic _decodeData(dynamic data, {Map<dynamic, dynamic> cache}) {
|
dynamic _decodeData(dynamic data, {Map<dynamic, dynamic> cache}) {
|
||||||
|
@@ -24,23 +24,6 @@ class _DartFunction extends JSInvokable {
|
|||||||
final Function _func;
|
final Function _func;
|
||||||
_DartFunction(this._func);
|
_DartFunction(this._func);
|
||||||
|
|
||||||
void _freeRecursive(dynamic obj, [Set cache]) {
|
|
||||||
if (obj == null) return;
|
|
||||||
if (cache == null) cache = Set();
|
|
||||||
if (cache.contains(obj)) return;
|
|
||||||
if (obj is List) {
|
|
||||||
cache.add(obj);
|
|
||||||
obj.forEach((e) => _freeRecursive(e, cache));
|
|
||||||
}
|
|
||||||
if (obj is Map) {
|
|
||||||
cache.add(obj);
|
|
||||||
obj.values.forEach((e) => _freeRecursive(e, cache));
|
|
||||||
}
|
|
||||||
if (obj is JSRef) {
|
|
||||||
obj.free();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
invoke(List args, [thisVal]) {
|
invoke(List args, [thisVal]) {
|
||||||
/// wrap this into function
|
/// wrap this into function
|
||||||
@@ -48,8 +31,8 @@ class _DartFunction extends JSInvokable {
|
|||||||
RegExp('{.*thisVal.*}').hasMatch(_func.runtimeType.toString());
|
RegExp('{.*thisVal.*}').hasMatch(_func.runtimeType.toString());
|
||||||
final ret =
|
final ret =
|
||||||
Function.apply(_func, args, passThis ? {#thisVal: thisVal} : null);
|
Function.apply(_func, args, passThis ? {#thisVal: thisVal} : null);
|
||||||
_freeRecursive(args);
|
JSRef.freeRecursive(args);
|
||||||
_freeRecursive(thisVal);
|
JSRef.freeRecursive(thisVal);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +162,7 @@ class _JSFunction extends _JSObject implements JSInvokable, _IsolateEncodable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Pointer _invoke(List<dynamic> arguments, [dynamic thisVal]) {
|
Pointer _invoke(List<dynamic> arguments, [dynamic thisVal]) {
|
||||||
if (_val == null) return null;
|
if (_val == null) throw JSError("InternalError: JSValue released");
|
||||||
List<Pointer> args = arguments
|
List<Pointer> args = arguments
|
||||||
.map(
|
.map(
|
||||||
(e) => _dartToJs(_ctx, e),
|
(e) => _dartToJs(_ctx, e),
|
||||||
@@ -196,22 +179,26 @@ class _JSFunction extends _JSObject implements JSInvokable, _IsolateEncodable {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Map _encode() {
|
Map _encode() {
|
||||||
final func = IsolateFunction._new(this);
|
return IsolateFunction._new(this)._encode();
|
||||||
final ret = func._encode();
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class _IsolatePortHandler {
|
/// Dart function wrapper for isolate
|
||||||
|
class IsolateFunction extends JSInvokable implements _IsolateEncodable {
|
||||||
int _isolateId;
|
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 ReceivePort _invokeHandler;
|
||||||
static Set<_IsolatePortHandler> _handlers = Set();
|
static Set<IsolateFunction> _handlers = Set();
|
||||||
|
|
||||||
static get _port {
|
static get _handlePort {
|
||||||
if (_invokeHandler == null) {
|
if (_invokeHandler == null) {
|
||||||
_invokeHandler = ReceivePort();
|
_invokeHandler = ReceivePort();
|
||||||
_invokeHandler.listen((msg) async {
|
_invokeHandler.listen((msg) async {
|
||||||
@@ -236,11 +223,11 @@ class _IsolatePort {
|
|||||||
return _invokeHandler.sendPort;
|
return _invokeHandler.sendPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
static _send(SendPort isolate, _IsolatePortHandler handler, msg) async {
|
_send(msg) async {
|
||||||
if (isolate == null) return handler._handle(msg);
|
if (_port == null) return _handle(msg);
|
||||||
final evaluatePort = ReceivePort();
|
final evaluatePort = ReceivePort();
|
||||||
isolate.send({
|
_port.send({
|
||||||
#handler: handler._isolateId,
|
#handler: _isolateId,
|
||||||
#msg: msg,
|
#msg: msg,
|
||||||
#port: evaluatePort.sendPort,
|
#port: evaluatePort.sendPort,
|
||||||
});
|
});
|
||||||
@@ -250,33 +237,11 @@ class _IsolatePort {
|
|||||||
return _decodeData(result);
|
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() {
|
_destroy() {
|
||||||
_IsolatePort._remove(this);
|
_handlers.remove(this);
|
||||||
_invokable?.free();
|
_invokable?.free();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
_handle(msg) async {
|
_handle(msg) async {
|
||||||
switch (msg) {
|
switch (msg) {
|
||||||
case #dup:
|
case #dup:
|
||||||
@@ -284,7 +249,6 @@ class IsolateFunction extends JSInvokable
|
|||||||
return null;
|
return null;
|
||||||
case #free:
|
case #free:
|
||||||
_refCount--;
|
_refCount--;
|
||||||
print("${identityHashCode(this)} ref $_refCount");
|
|
||||||
if (_refCount < 0) _destroy();
|
if (_refCount < 0) _destroy();
|
||||||
return null;
|
return null;
|
||||||
case #destroy:
|
case #destroy:
|
||||||
@@ -300,8 +264,7 @@ class IsolateFunction extends JSInvokable
|
|||||||
Future invoke(List positionalArguments, [thisVal]) async {
|
Future invoke(List positionalArguments, [thisVal]) async {
|
||||||
List dArgs = _encodeData(positionalArguments);
|
List dArgs = _encodeData(positionalArguments);
|
||||||
Map dThisVal = _encodeData(thisVal);
|
Map dThisVal = _encodeData(thisVal);
|
||||||
return _IsolatePort._send(_port, this, {
|
return _send({
|
||||||
#type: #invokeIsolate,
|
|
||||||
#args: dArgs,
|
#args: dArgs,
|
||||||
#thisVal: dThisVal,
|
#thisVal: dThisVal,
|
||||||
});
|
});
|
||||||
@@ -320,7 +283,7 @@ class IsolateFunction extends JSInvokable
|
|||||||
Map _encode() {
|
Map _encode() {
|
||||||
return {
|
return {
|
||||||
#jsFunctionId: _isolateId ?? identityHashCode(this),
|
#jsFunctionId: _isolateId ?? identityHashCode(this),
|
||||||
#jsFunctionPort: _port ?? _IsolatePort._port,
|
#jsFunctionPort: _port ?? IsolateFunction._handlePort,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,16 +291,16 @@ class IsolateFunction extends JSInvokable
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
dup() {
|
dup() {
|
||||||
_IsolatePort._send(_port, this, #dup);
|
_send(#dup);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
free() {
|
free() {
|
||||||
_IsolatePort._send(_port, this, #free);
|
_send(#free);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void destroy() {
|
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}) {
|
Pointer _dartToJs(Pointer ctx, dynamic val, {Map<dynamic, dynamic> 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 Exception) return _dartToJs(ctx, JSError(val));
|
||||||
if (val is JSError) {
|
if (val is JSError) {
|
||||||
final ret = jsNewError(ctx);
|
final ret = jsNewError(ctx);
|
||||||
_definePropertyValue(ctx, ret, "name", "");
|
_definePropertyValue(ctx, ret, "name", "");
|
||||||
@@ -187,9 +189,11 @@ dynamic _jsToDart(Pointer ctx, Pointer val, {Map<int, dynamic> cache}) {
|
|||||||
final jsPromise = _JSObject(ctx, val);
|
final jsPromise = _JSObject(ctx, val);
|
||||||
final jsRet = promiseThen._invoke([
|
final jsRet = promiseThen._invoke([
|
||||||
(v) {
|
(v) {
|
||||||
|
JSRef.dupRecursive(v);
|
||||||
if (!completer.isCompleted) completer.complete(v);
|
if (!completer.isCompleted) completer.complete(v);
|
||||||
},
|
},
|
||||||
(e) {
|
(e) {
|
||||||
|
JSRef.dupRecursive(e);
|
||||||
if (!completer.isCompleted) completer.completeError(e);
|
if (!completer.isCompleted) completer.completeError(e);
|
||||||
},
|
},
|
||||||
], jsPromise);
|
], jsPromise);
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
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.2
|
version: 0.3.3
|
||||||
homepage: https://github.com/ekibun/flutter_qjs
|
homepage: https://github.com/ekibun/flutter_qjs
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
@@ -8,7 +8,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:isolate';
|
|
||||||
|
|
||||||
import 'package:flutter_qjs/flutter_qjs.dart';
|
import 'package:flutter_qjs/flutter_qjs.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
@@ -19,7 +18,7 @@ dynamic myFunction(String args, {thisVal}) {
|
|||||||
|
|
||||||
Future testEvaluate(qjs) async {
|
Future testEvaluate(qjs) async {
|
||||||
JSInvokable wrapFunction = await qjs.evaluate(
|
JSInvokable wrapFunction = await qjs.evaluate(
|
||||||
'(a) => a',
|
'async (a) => a',
|
||||||
name: '<testWrap>',
|
name: '<testWrap>',
|
||||||
);
|
);
|
||||||
dynamic testWrap = await wrapFunction.invoke([wrapFunction]);
|
dynamic testWrap = await wrapFunction.invoke([wrapFunction]);
|
||||||
@@ -68,11 +67,6 @@ Future testEvaluate(qjs) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
test('send', () async {
|
|
||||||
final rec = ReceivePort();
|
|
||||||
rec.close();
|
|
||||||
rec.sendPort.send("3232");
|
|
||||||
});
|
|
||||||
test('make', () async {
|
test('make', () async {
|
||||||
final utf8Encoding = Encoding.getByName('utf-8');
|
final utf8Encoding = Encoding.getByName('utf-8');
|
||||||
var cmakePath = 'cmake';
|
var cmakePath = 'cmake';
|
||||||
@@ -139,25 +133,22 @@ void main() async {
|
|||||||
await testEvaluate(qjs);
|
await testEvaluate(qjs);
|
||||||
await qjs.close();
|
await qjs.close();
|
||||||
});
|
});
|
||||||
test('isolate bind function', () async {
|
test('isolate bind this', () async {
|
||||||
final qjs = IsolateQjs();
|
final qjs = IsolateQjs();
|
||||||
final localVars = [];
|
JSInvokable localVar;
|
||||||
JSInvokable testFunc =
|
JSInvokable setToGlobal = await qjs
|
||||||
await qjs.evaluate('(func)=>func(()=>"ret")', name: '<eval>');
|
.evaluate('(name, func)=>{ this[name] = func }', name: '<eval>');
|
||||||
final func = IsolateFunction.func((args) {
|
final func = IsolateFunction((args) {
|
||||||
localVars.add(args..dup());
|
localVar = args..dup();
|
||||||
return args.invoke([]);
|
return args.invoke([]);
|
||||||
});
|
});
|
||||||
final testFuncRet = await testFunc.invoke([func..dup()]);
|
await setToGlobal.invoke(["test", func..dup()]);
|
||||||
final testFuncRet2 = await testFunc.invoke([func..dup()]);
|
|
||||||
func.free();
|
func.free();
|
||||||
testFunc.free();
|
setToGlobal.free();
|
||||||
for (IsolateFunction vars in localVars) {
|
final testFuncRet = await qjs.evaluate('test(()=>"ret")', name: '<eval>');
|
||||||
expect(await vars.invoke([]), 'ret', reason: 'bind function');
|
expect(await localVar.invoke([]), 'ret', reason: 'bind function');
|
||||||
vars.free();
|
localVar.free();
|
||||||
}
|
|
||||||
expect(testFuncRet, 'ret', reason: 'bind function args return');
|
expect(testFuncRet, 'ret', reason: 'bind function args return');
|
||||||
expect(testFuncRet2, testFuncRet, reason: 'bind function args return2');
|
|
||||||
await qjs.close();
|
await qjs.close();
|
||||||
});
|
});
|
||||||
test('reference leak', () async {
|
test('reference leak', () async {
|
||||||
|
Reference in New Issue
Block a user