This commit is contained in:
ekibun
2021-01-25 14:33:47 +08:00
parent ee110f55e1
commit 6fb2c4776b
12 changed files with 492 additions and 545 deletions

View File

@@ -6,6 +6,11 @@
* @LastEditTime: 2020-12-02 11:36:40 * @LastEditTime: 2020-12-02 11:36:40
--> -->
## 0.3.1
* code clean up.
* fix isolate wrap error.
## 0.3.0 ## 0.3.0
* breakdown change to remove `channel`. * breakdown change to remove `channel`.

View File

@@ -45,20 +45,31 @@ 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 | function(....args) | | Function<br>JSInvokable | function(....args) |
| Future | Promise | | Future | Promise |
| Object | DartObject | | Object | DartObject |
**notice:** Dart function parameter `thisVal` is used to store `this` in js. **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);
```
### Use modules ### Use modules
@@ -119,10 +130,11 @@ try {
Method `close` can destroy quickjs runtime that can be recreated again if you call `evaluate`. Method `close` can destroy quickjs runtime that can 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: **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:
```dart ```dart
await setToGlobalObject("func", await engine.bind(() { await setToGlobalObject("func", await engine.bind(({thisVal}) {
// DO SOMETHING // DO SOMETHING
})) }))
``` ```
@@ -131,8 +143,8 @@ await setToGlobalObject("func", await engine.bind(() {
## Breaking change in v0.3.0 ## Breaking change in v0.3.0
`channel` function is no longer utilized by default. `channel` function is no longer included by default.
Use js function to set to global: Use js function to set dart object globally:
```dart ```dart
final setToGlobalObject = await engine.evaluate("(key, val) => this[key] = val;"); final setToGlobalObject = await engine.evaluate("(key, val) => this[key] = val;");

View File

@@ -1,16 +1,3 @@
# flutter_qjs_example # flutter_qjs_example
Demonstrates how to use the flutter_qjs plugin. Demonstrates how to use the flutter_qjs plugin.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View File

@@ -6,9 +6,7 @@
* @LastEditTime: 2020-12-02 11:28:06 * @LastEditTime: 2020-12-02 11:28:06
*/ */
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'dart:typed_data';
import 'package:dio/dio.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_qjs/isolate.dart'; import 'package:flutter_qjs/isolate.dart';
@@ -44,28 +42,6 @@ class TestPage extends StatefulWidget {
State<StatefulWidget> createState() => _TestPageState(); State<StatefulWidget> createState() => _TestPageState();
} }
dynamic methodHandler(String method, List arg) {
switch (method) {
case "http":
return Dio().get(arg[0]).then((response) => response.data);
case "test":
return arg[0]([
true,
1,
0.5,
"str",
{"key": "val", 0: 1},
Uint8List(2),
Int32List(2),
Int64List(2),
Float64List(2),
Float32List(2)
]);
default:
throw Exception("No such method");
}
}
class _TestPageState extends State<TestPage> { class _TestPageState extends State<TestPage> {
String resp; String resp;
IsolateQjs engine; IsolateQjs engine;
@@ -82,9 +58,6 @@ class _TestPageState extends State<TestPage> {
"js/" + module.replaceFirst(new RegExp(r".js$"), "") + ".js"); "js/" + module.replaceFirst(new RegExp(r".js$"), "") + ".js");
}, },
); );
final setToGlobalObject =
await engine.evaluate("(key, val) => this[key] = val;");
setToGlobalObject("channel", methodHandler);
} }
@override @override

View File

@@ -43,13 +43,6 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.15.0-nullsafety.5" version: "1.15.0-nullsafety.5"
dio:
dependency: "direct main"
description:
name: dio
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.10"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@@ -82,7 +75,7 @@ packages:
path: ".." path: ".."
relative: true relative: true
source: path source: path
version: "0.3.0" version: "0.3.1"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@@ -95,13 +88,6 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "0.6.0" version: "0.6.0"
http_parser:
dependency: transitive
description:
name: http_parser
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.4"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:

View File

@@ -22,7 +22,6 @@ dependencies:
highlight: 0.6.0 highlight: 0.6.0
flutter_highlight: 0.6.0 flutter_highlight: 0.6.0
dio: 3.0.10
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@@ -15,7 +15,6 @@ abstract class JSRef {
void release(); void release();
} }
/// JS_Eval() flags
class JSEvalFlag { class JSEvalFlag {
static const GLOBAL = 0 << 0; static const GLOBAL = 0 << 0;
static const MODULE = 1 << 0; static const MODULE = 1 << 0;
@@ -56,7 +55,7 @@ class JSTag {
static const FLOAT64 = 7; static const FLOAT64 = 7;
} }
final DynamicLibrary qjsLib = Platform.environment['FLUTTER_TEST'] == 'true' final DynamicLibrary _qjsLib = Platform.environment['FLUTTER_TEST'] == 'true'
? (Platform.isWindows ? (Platform.isWindows
? DynamicLibrary.open("test/build/Debug/ffiquickjs.dll") ? DynamicLibrary.open("test/build/Debug/ffiquickjs.dll")
: Platform.isMacOS : Platform.isMacOS
@@ -72,7 +71,7 @@ final DynamicLibrary qjsLib = Platform.environment['FLUTTER_TEST'] == 'true'
final Pointer Function( final Pointer Function(
Pointer ctx, Pointer ctx,
Pointer<Utf8> message, Pointer<Utf8> message,
) _jsThrowInternalError = qjsLib ) _jsThrowInternalError = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Pointer Function( Pointer Function(
@@ -89,12 +88,12 @@ Pointer jsThrowInternalError(Pointer ctx, String message) {
} }
/// JSValue *jsEXCEPTION() /// JSValue *jsEXCEPTION()
final Pointer Function() jsEXCEPTION = qjsLib final Pointer Function() jsEXCEPTION = _qjsLib
.lookup<NativeFunction<Pointer Function()>>("jsEXCEPTION") .lookup<NativeFunction<Pointer Function()>>("jsEXCEPTION")
.asFunction(); .asFunction();
/// JSValue *jsUNDEFINED() /// JSValue *jsUNDEFINED()
final Pointer Function() jsUNDEFINED = qjsLib final Pointer Function() jsUNDEFINED = _qjsLib
.lookup<NativeFunction<Pointer Function()>>("jsUNDEFINED") .lookup<NativeFunction<Pointer Function()>>("jsUNDEFINED")
.asFunction(); .asFunction();
@@ -105,7 +104,7 @@ typedef JSChannelNative = Pointer Function(
/// JSRuntime *jsNewRuntime(JSChannel channel) /// JSRuntime *jsNewRuntime(JSChannel channel)
final Pointer Function( final Pointer Function(
Pointer<NativeFunction<JSChannelNative>>, Pointer<NativeFunction<JSChannelNative>>,
) _jsNewRuntime = qjsLib ) _jsNewRuntime = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Pointer Function( Pointer Function(
@@ -117,7 +116,6 @@ class RuntimeOpaque {
JSChannel channel; JSChannel channel;
List<JSRef> ref = []; List<JSRef> ref = [];
ReceivePort port; ReceivePort port;
Future Function(Pointer) promiseToFuture;
int dartObjectClassId; int dartObjectClassId;
} }
@@ -143,7 +141,7 @@ Pointer jsNewRuntime(
final void Function( final void Function(
Pointer, Pointer,
int, int,
) jsSetMaxStackSize = qjsLib ) jsSetMaxStackSize = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Void Function( Void Function(
@@ -155,7 +153,7 @@ final void Function(
/// void jsFreeRuntime(JSRuntime *rt) /// void jsFreeRuntime(JSRuntime *rt)
final void Function( final void Function(
Pointer, Pointer,
) _jsFreeRuntime = qjsLib ) _jsFreeRuntime = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Void Function( Void Function(
@@ -177,7 +175,7 @@ void jsFreeRuntime(
final Pointer Function( final Pointer Function(
Pointer ctx, Pointer ctx,
Pointer funcData, Pointer funcData,
) jsNewCFunction = qjsLib ) jsNewCFunction = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Pointer Function( Pointer Function(
@@ -189,7 +187,7 @@ final Pointer Function(
/// JSContext *jsNewContext(JSRuntime *rt) /// JSContext *jsNewContext(JSRuntime *rt)
final Pointer Function( final Pointer Function(
Pointer rt, Pointer rt,
) jsNewContext = qjsLib ) _jsNewContext = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Pointer Function( Pointer Function(
@@ -197,10 +195,18 @@ final Pointer Function(
)>>("jsNewContext") )>>("jsNewContext")
.asFunction(); .asFunction();
Pointer jsNewContext(Pointer rt) {
var ctx = _jsNewContext(rt);
final runtimeOpaque = runtimeOpaques[rt];
if (runtimeOpaque == null) throw Exception("Runtime has been released!");
runtimeOpaque.dartObjectClassId = jsNewClass(ctx, "DartObject");
return ctx;
}
/// void jsFreeContext(JSContext *ctx) /// void jsFreeContext(JSContext *ctx)
final void Function( final void Function(
Pointer, Pointer,
) jsFreeContext = qjsLib ) jsFreeContext = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Void Function( Void Function(
@@ -211,7 +217,7 @@ final void Function(
/// JSRuntime *jsGetRuntime(JSContext *ctx) /// JSRuntime *jsGetRuntime(JSContext *ctx)
final Pointer Function( final Pointer Function(
Pointer, Pointer,
) jsGetRuntime = qjsLib ) jsGetRuntime = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Pointer Function( Pointer Function(
@@ -226,7 +232,7 @@ final Pointer Function(
int inputLen, int inputLen,
Pointer<Utf8> filename, Pointer<Utf8> filename,
int evalFlags, int evalFlags,
) _jsEval = qjsLib ) _jsEval = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Pointer Function( Pointer Function(
@@ -262,7 +268,7 @@ Pointer jsEval(
/// DLLEXPORT int32_t jsValueGetTag(JSValue *val) /// DLLEXPORT int32_t jsValueGetTag(JSValue *val)
final int Function( final int Function(
Pointer val, Pointer val,
) jsValueGetTag = qjsLib ) jsValueGetTag = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Int32 Function( Int32 Function(
@@ -273,7 +279,7 @@ final int Function(
/// void *jsValueGetPtr(JSValue *val) /// void *jsValueGetPtr(JSValue *val)
final Pointer Function( final Pointer Function(
Pointer val, Pointer val,
) jsValueGetPtr = qjsLib ) jsValueGetPtr = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Pointer Function( Pointer Function(
@@ -284,7 +290,7 @@ final Pointer Function(
/// DLLEXPORT bool jsTagIsFloat64(int32_t tag) /// DLLEXPORT bool jsTagIsFloat64(int32_t tag)
final int Function( final int Function(
int val, int val,
) jsTagIsFloat64 = qjsLib ) jsTagIsFloat64 = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Int32 Function( Int32 Function(
@@ -296,7 +302,7 @@ final int Function(
final Pointer Function( final Pointer Function(
Pointer ctx, Pointer ctx,
int val, int val,
) jsNewBool = qjsLib ) jsNewBool = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Pointer Function( Pointer Function(
@@ -309,7 +315,7 @@ final Pointer Function(
final Pointer Function( final Pointer Function(
Pointer ctx, Pointer ctx,
int val, int val,
) jsNewInt64 = qjsLib ) jsNewInt64 = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Pointer Function( Pointer Function(
@@ -322,7 +328,7 @@ final Pointer Function(
final Pointer Function( final Pointer Function(
Pointer ctx, Pointer ctx,
double val, double val,
) jsNewFloat64 = qjsLib ) jsNewFloat64 = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Pointer Function( Pointer Function(
@@ -335,7 +341,7 @@ final Pointer Function(
final Pointer Function( final Pointer Function(
Pointer ctx, Pointer ctx,
Pointer<Utf8> str, Pointer<Utf8> str,
) _jsNewString = qjsLib ) _jsNewString = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Pointer Function( Pointer Function(
@@ -357,7 +363,7 @@ final Pointer Function(
Pointer ctx, Pointer ctx,
Pointer<Uint8> buf, Pointer<Uint8> buf,
int len, int len,
) jsNewArrayBufferCopy = qjsLib ) jsNewArrayBufferCopy = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Pointer Function( Pointer Function(
@@ -370,7 +376,7 @@ final Pointer Function(
/// JSValue *jsNewArray(JSContext *ctx) /// JSValue *jsNewArray(JSContext *ctx)
final Pointer Function( final Pointer Function(
Pointer ctx, Pointer ctx,
) jsNewArray = qjsLib ) jsNewArray = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Pointer Function( Pointer Function(
@@ -381,7 +387,7 @@ final Pointer Function(
/// JSValue *jsNewObject(JSContext *ctx) /// JSValue *jsNewObject(JSContext *ctx)
final Pointer Function( final Pointer Function(
Pointer ctx, Pointer ctx,
) jsNewObject = qjsLib ) jsNewObject = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Pointer Function( Pointer Function(
@@ -394,7 +400,7 @@ final void Function(
Pointer ctx, Pointer ctx,
Pointer val, Pointer val,
int free, int free,
) _jsFreeValue = qjsLib ) _jsFreeValue = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Void Function( Void Function(
@@ -417,7 +423,7 @@ final void Function(
Pointer rt, Pointer rt,
Pointer val, Pointer val,
int free, int free,
) _jsFreeValueRT = qjsLib ) _jsFreeValueRT = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Void Function( Void Function(
@@ -439,7 +445,7 @@ void jsFreeValueRT(
final Pointer Function( final Pointer Function(
Pointer ctx, Pointer ctx,
Pointer val, Pointer val,
) jsDupValue = qjsLib ) jsDupValue = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Pointer Function( Pointer Function(
@@ -452,7 +458,7 @@ final Pointer Function(
final Pointer Function( final Pointer Function(
Pointer rt, Pointer rt,
Pointer val, Pointer val,
) jsDupValueRT = qjsLib ) jsDupValueRT = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Pointer Function( Pointer Function(
@@ -465,7 +471,7 @@ final Pointer Function(
final int Function( final int Function(
Pointer ctx, Pointer ctx,
Pointer val, Pointer val,
) jsToBool = qjsLib ) jsToBool = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Int32 Function( Int32 Function(
@@ -478,7 +484,7 @@ final int Function(
final int Function( final int Function(
Pointer ctx, Pointer ctx,
Pointer val, Pointer val,
) jsToInt64 = qjsLib ) jsToInt64 = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Int64 Function( Int64 Function(
@@ -491,7 +497,7 @@ final int Function(
final double Function( final double Function(
Pointer ctx, Pointer ctx,
Pointer val, Pointer val,
) jsToFloat64 = qjsLib ) jsToFloat64 = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Double Function( Double Function(
@@ -504,7 +510,7 @@ final double Function(
final Pointer<Utf8> Function( final Pointer<Utf8> Function(
Pointer ctx, Pointer ctx,
Pointer val, Pointer val,
) _jsToCString = qjsLib ) _jsToCString = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Pointer<Utf8> Function( Pointer<Utf8> Function(
@@ -517,7 +523,7 @@ final Pointer<Utf8> Function(
final void Function( final void Function(
Pointer ctx, Pointer ctx,
Pointer<Utf8> val, Pointer<Utf8> val,
) jsFreeCString = qjsLib ) jsFreeCString = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Void Function( Void Function(
@@ -541,7 +547,7 @@ String jsToCString(
final int Function( final int Function(
Pointer ctx, Pointer ctx,
Pointer<Utf8> name, Pointer<Utf8> name,
) _jsNewClass = qjsLib ) _jsNewClass = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Uint32 Function( Uint32 Function(
@@ -568,7 +574,7 @@ final Pointer Function(
Pointer ctx, Pointer ctx,
int classId, int classId,
int opaque, int opaque,
) jsNewObjectClass = qjsLib ) jsNewObjectClass = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Pointer Function( Pointer Function(
@@ -582,7 +588,7 @@ final Pointer Function(
final int Function( final int Function(
Pointer obj, Pointer obj,
int classid, int classid,
) jsGetObjectOpaque = qjsLib ) jsGetObjectOpaque = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
IntPtr Function( IntPtr Function(
@@ -596,7 +602,7 @@ final Pointer<Uint8> Function(
Pointer ctx, Pointer ctx,
Pointer<IntPtr> psize, Pointer<IntPtr> psize,
Pointer val, Pointer val,
) jsGetArrayBuffer = qjsLib ) jsGetArrayBuffer = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Pointer<Uint8> Function( Pointer<Uint8> Function(
@@ -610,7 +616,7 @@ final Pointer<Uint8> Function(
final int Function( final int Function(
Pointer ctx, Pointer ctx,
Pointer val, Pointer val,
) jsIsFunction = qjsLib ) jsIsFunction = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Int32 Function( Int32 Function(
@@ -623,7 +629,7 @@ final int Function(
final int Function( final int Function(
Pointer ctx, Pointer ctx,
Pointer val, Pointer val,
) jsIsPromise = qjsLib ) jsIsPromise = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Int32 Function( Int32 Function(
@@ -636,7 +642,7 @@ final int Function(
final int Function( final int Function(
Pointer ctx, Pointer ctx,
Pointer val, Pointer val,
) jsIsArray = qjsLib ) jsIsArray = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Int32 Function( Int32 Function(
@@ -651,7 +657,7 @@ final Pointer Function(
Pointer ctx, Pointer ctx,
Pointer thisObj, Pointer thisObj,
int prop, int prop,
) jsGetProperty = qjsLib ) jsGetProperty = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Pointer Function( Pointer Function(
@@ -669,7 +675,7 @@ final int Function(
int prop, int prop,
Pointer val, Pointer val,
int flag, int flag,
) jsDefinePropertyValue = qjsLib ) jsDefinePropertyValue = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Int32 Function( Int32 Function(
@@ -685,7 +691,7 @@ final int Function(
final Pointer Function( final Pointer Function(
Pointer ctx, Pointer ctx,
int v, int v,
) jsFreeAtom = qjsLib ) jsFreeAtom = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Pointer Function( Pointer Function(
@@ -698,7 +704,7 @@ final Pointer Function(
final int Function( final int Function(
Pointer ctx, Pointer ctx,
Pointer val, Pointer val,
) jsValueToAtom = qjsLib ) jsValueToAtom = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Uint32 Function( Uint32 Function(
@@ -711,7 +717,7 @@ final int Function(
final Pointer Function( final Pointer Function(
Pointer ctx, Pointer ctx,
int val, int val,
) jsAtomToValue = qjsLib ) jsAtomToValue = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Pointer Function( Pointer Function(
@@ -728,7 +734,7 @@ final int Function(
Pointer<Uint32> plen, Pointer<Uint32> plen,
Pointer obj, Pointer obj,
int flags, int flags,
) jsGetOwnPropertyNames = qjsLib ) jsGetOwnPropertyNames = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Int32 Function( Int32 Function(
@@ -744,7 +750,7 @@ final int Function(
final int Function( final int Function(
Pointer ptab, Pointer ptab,
int i, int i,
) jsPropertyEnumGetAtom = qjsLib ) jsPropertyEnumGetAtom = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Uint32 Function( Uint32 Function(
@@ -754,7 +760,7 @@ final int Function(
.asFunction(); .asFunction();
/// uint32_t sizeOfJSValue() /// uint32_t sizeOfJSValue()
final int Function() _sizeOfJSValue = qjsLib final int Function() _sizeOfJSValue = _qjsLib
.lookup<NativeFunction<Uint32 Function()>>("sizeOfJSValue") .lookup<NativeFunction<Uint32 Function()>>("sizeOfJSValue")
.asFunction(); .asFunction();
@@ -765,7 +771,7 @@ final void Function(
Pointer list, Pointer list,
int i, int i,
Pointer val, Pointer val,
) setJSValueList = qjsLib ) setJSValueList = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Void Function( Void Function(
@@ -783,7 +789,7 @@ final Pointer Function(
Pointer thisObj, Pointer thisObj,
int argc, int argc,
Pointer argv, Pointer argv,
) _jsCall = qjsLib ) _jsCall = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Pointer Function( Pointer Function(
@@ -823,7 +829,7 @@ Pointer jsCall(
/// int jsIsException(JSValueConst *val) /// int jsIsException(JSValueConst *val)
final int Function( final int Function(
Pointer val, Pointer val,
) jsIsException = qjsLib ) jsIsException = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Int32 Function( Int32 Function(
@@ -834,7 +840,7 @@ final int Function(
/// JSValue *jsGetException(JSContext *ctx) /// JSValue *jsGetException(JSContext *ctx)
final Pointer Function( final Pointer Function(
Pointer ctx, Pointer ctx,
) jsGetException = qjsLib ) jsGetException = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Pointer Function( Pointer Function(
@@ -845,7 +851,7 @@ final Pointer Function(
/// int jsExecutePendingJob(JSRuntime *rt) /// int jsExecutePendingJob(JSRuntime *rt)
final int Function( final int Function(
Pointer ctx, Pointer ctx,
) jsExecutePendingJob = qjsLib ) jsExecutePendingJob = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Int32 Function( Int32 Function(
@@ -857,7 +863,7 @@ final int Function(
final Pointer Function( final Pointer Function(
Pointer ctx, Pointer ctx,
Pointer resolvingFuncs, Pointer resolvingFuncs,
) jsNewPromiseCapability = qjsLib ) jsNewPromiseCapability = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Pointer Function( Pointer Function(
@@ -870,7 +876,7 @@ final Pointer Function(
final void Function( final void Function(
Pointer ctx, Pointer ctx,
Pointer ptab, Pointer ptab,
) jsFree = qjsLib ) jsFree = _qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
Void Function( Void Function(

View File

@@ -10,10 +10,8 @@ import 'dart:ffi';
import 'dart:isolate'; import 'dart:isolate';
import 'package:ffi/ffi.dart'; import 'package:ffi/ffi.dart';
import 'package:flutter_qjs/ffi.dart'; import 'ffi.dart';
import 'package:flutter_qjs/wrapper.dart'; import 'wrapper.dart';
import 'isolate.dart';
/// Handler function to manage js module. /// Handler function to manage js module.
typedef JsModuleHandler = String Function(String name); typedef JsModuleHandler = String Function(String name);
@@ -21,6 +19,7 @@ typedef JsModuleHandler = String Function(String name);
/// Handler to manage unhandled promise rejection. /// Handler to manage unhandled promise rejection.
typedef JsHostPromiseRejectionHandler = void Function(String reason); typedef JsHostPromiseRejectionHandler = void Function(String reason);
/// Quickjs engine for flutter.
class FlutterQjs { class FlutterQjs {
Pointer _rt; Pointer _rt;
Pointer _ctx; Pointer _ctx;
@@ -37,21 +36,12 @@ class FlutterQjs {
/// Handler function to manage js module. /// Handler function to manage js module.
JsHostPromiseRejectionHandler hostPromiseRejectionHandler; JsHostPromiseRejectionHandler hostPromiseRejectionHandler;
/// Quickjs engine for flutter.
///
/// Pass handlers to implement js-dart interaction and resolving modules.
FlutterQjs({ FlutterQjs({
this.moduleHandler, this.moduleHandler,
this.stackSize, this.stackSize,
this.hostPromiseRejectionHandler, this.hostPromiseRejectionHandler,
}); });
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() { _ensureEngine() {
if (_rt != null) return; if (_rt != null) return;
_rt = jsNewRuntime((ctx, type, ptr) { _rt = jsNewRuntime((ctx, type, ptr) {
@@ -69,10 +59,8 @@ class FlutterQjs {
))); )));
} }
final thisVal = jsToDart(ctx, pdata.elementAt(0).value); final thisVal = jsToDart(ctx, pdata.elementAt(0).value);
final func = jsToDart(ctx, pdata.elementAt(3).value); JSInvokable func = jsToDart(ctx, pdata.elementAt(3).value);
final ret = func is QjsInvokable final ret = func.invoke(args, thisVal);
? func.invoke(args, thisVal)
: applyFunction(func, args, thisVal);
return dartToJs(ctx, ret); return dartToJs(ctx, ret);
case JSChannelType.MODULE: case JSChannelType.MODULE:
if (moduleHandler == null) throw Exception("No ModuleHandler"); if (moduleHandler == null) throw Exception("No ModuleHandler");
@@ -122,7 +110,7 @@ class FlutterQjs {
}, port); }, port);
if (this.stackSize != null && this.stackSize > 0) if (this.stackSize != null && this.stackSize > 0)
jsSetMaxStackSize(_rt, this.stackSize); jsSetMaxStackSize(_rt, this.stackSize);
_ctx = jsNewContextWithPromsieWrapper(_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.
@@ -146,18 +134,6 @@ class FlutterQjs {
break; break;
} }
} }
List jsPromises = runtimeOpaques[_rt]
.ref
.where(
(v) => v is JSPromise,
)
.toList();
for (JSPromise jsPromise in jsPromises) {
if (jsPromise.checkResolveReject()) {
jsPromise.release();
runtimeOpaques[_rt].ref.remove(jsPromise);
}
}
} }
} }

View File

@@ -11,205 +11,8 @@ import 'dart:io';
import 'dart:isolate'; import 'dart:isolate';
import 'package:ffi/ffi.dart'; import 'package:ffi/ffi.dart';
import 'package:flutter_qjs/flutter_qjs.dart'; import 'flutter_qjs.dart';
import 'package:flutter_qjs/wrapper.dart'; import 'wrapper.dart';
class IsolateJSFunction implements QjsInvokable {
int val;
int ctx;
SendPort port;
IsolateJSFunction(this.ctx, this.val, this.port);
Future invoke(List arguments, [thisVal]) async {
if (0 == val ?? 0) return;
var evaluatePort = ReceivePort();
port.send({
'type': 'call',
'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
throw result['error'];
}
@override
noSuchMethod(Invocation invocation) {
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],
);
}
}
dynamic _encodeData(dynamic data, {Map<dynamic, dynamic> cache}) {
if (cache == null) cache = Map();
if (cache.containsKey(data)) return cache[data];
if (data is List) {
var ret = [];
cache[data] = ret;
for (int i = 0; i < data.length; ++i) {
ret.add(_encodeData(data[i], cache: cache));
}
return ret;
}
if (data is Map) {
var ret = {};
cache[data] = ret;
for (var entry in data.entries) {
ret[_encodeData(entry.key, cache: cache)] =
_encodeData(entry.value, cache: cache);
}
return ret;
}
if (data is JSFunction) {
return {
'__js_function_ctx': data.ctx.address,
'__js_function_val': data.val.address,
};
}
if (data is IsolateJSFunction) {
return {
'__js_function_ctx': data.ctx,
'__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) {
futurePort.close();
(port as SendPort).send({'data': _encodeData(value)});
});
}, onError: (e, stack) {
futurePort.first.then((port) {
futurePort.close();
(port as SendPort)
.send({'error': e.toString() + "\n" + stack.toString()});
});
});
return {
'__js_future_port': futurePort.sendPort,
};
}
return data;
}
dynamic _decodeData(dynamic data, SendPort port,
{Map<dynamic, dynamic> cache}) {
if (cache == null) cache = Map();
if (cache.containsKey(data)) return cache[data];
if (data is List) {
var ret = [];
cache[data] = ret;
for (int i = 0; i < data.length; ++i) {
ret.add(_decodeData(data[i], port, cache: cache));
}
return ret;
}
if (data is Map) {
if (data.containsKey('__js_function_val')) {
int ctx = data['__js_function_ctx'];
int val = data['__js_function_val'];
if (port != null) {
return IsolateJSFunction(ctx, val, port);
} else {
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 {
futureCompleter.complete(value['data']);
}
});
return futureCompleter.future;
}
var ret = {};
cache[data] = ret;
for (var entry in data.entries) {
ret[_decodeData(entry.key, port, cache: cache)] =
_decodeData(entry.value, port, cache: cache);
}
return ret;
}
return data;
}
void _runJsIsolate(Map spawnMessage) async { void _runJsIsolate(Map spawnMessage) async {
SendPort sendPort = spawnMessage['port']; SendPort sendPort = spawnMessage['port'];
@@ -256,11 +59,11 @@ void _runJsIsolate(Map spawnMessage) async {
break; break;
case 'call': case 'call':
data = JSFunction.fromAddress( data = JSFunction.fromAddress(
msg['ctx'], Pointer.fromAddress(msg['ctx']),
msg['val'], Pointer.fromAddress(msg['val']),
).invoke( ).invoke(
_decodeData(msg['args'], null), decodeData(msg['args'], null),
_decodeData(msg['this'], null), decodeData(msg['this'], null),
); );
break; break;
case 'close': case 'close':
@@ -271,7 +74,7 @@ void _runJsIsolate(Map spawnMessage) async {
} }
if (msgPort != null) if (msgPort != null)
msgPort.send({ msgPort.send({
'data': _encodeData(data), 'data': encodeData(data),
}); });
} catch (e, stack) { } catch (e, stack) {
if (msgPort != null) if (msgPort != null)
@@ -389,10 +192,10 @@ class IsolateQjs {
'flag': evalFlags, 'flag': evalFlags,
'port': evaluatePort.sendPort, 'port': evaluatePort.sendPort,
}); });
var result = await evaluatePort.first; Map result = await evaluatePort.first;
evaluatePort.close(); evaluatePort.close();
if (result['error'] == null) { if (result.containsKey('data')) {
return _decodeData(result['data'], sendPort); return decodeData(result['data'], sendPort);
} else } else
throw result['error']; throw result['error'];
} }

View File

@@ -7,123 +7,20 @@
*/ */
import 'dart:async'; import 'dart:async';
import 'dart:ffi'; import 'dart:ffi';
import 'dart:isolate';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:ffi/ffi.dart'; import 'package:ffi/ffi.dart';
import 'ffi.dart'; import 'ffi.dart';
import 'isolate.dart';
class JSRefValue implements JSRef { abstract class JSInvokable {
Pointer val; dynamic invoke(List args, [dynamic thisVal]);
Pointer ctx;
JSRefValue(this.ctx, Pointer val) {
Pointer rt = jsGetRuntime(ctx);
this.val = jsDupValue(ctx, val);
runtimeOpaques[rt]?.ref?.add(this);
}
JSRefValue.fromAddress(int ctx, int val) { static dynamic wrap(dynamic func) {
this.ctx = Pointer.fromAddress(ctx); return func is JSInvokable
this.val = Pointer.fromAddress(val); ? func
} : func is Function
? _DartFunction(func)
@override : func;
void release() {
if (val != null) {
jsFreeValue(ctx, val);
}
val = null;
ctx = null;
}
}
abstract class QjsReleasable {
void release();
}
abstract class QjsInvokable {
dynamic invoke(List positionalArguments, [dynamic thisVal]);
}
class DartObject implements JSRef {
Object obj;
Pointer ctx;
DartObject(this.ctx, this.obj) {
runtimeOpaques[jsGetRuntime(ctx)]?.ref?.add(this);
}
static DartObject fromAddress(Pointer rt, int val) {
return runtimeOpaques[rt]?.ref?.firstWhere(
(e) => identityHashCode(e) == val,
orElse: () => null,
);
}
@override
void release() {
if (obj is QjsReleasable) (obj as QjsReleasable).release();
obj = null;
ctx = null;
}
}
class JSPromise extends JSRefValue {
Completer completer;
JSPromise(Pointer ctx, Pointer val, this.completer) : super(ctx, val);
@override
void release() {
super.release();
if (!completer.isCompleted) {
completer.completeError("Promise cannot resolve");
}
}
bool checkResolveReject() {
if (val == null || completer.isCompleted) return true;
var status = jsToDart(ctx, val);
if (status["__resolved"] == true) {
completer.complete(status["__value"]);
return true;
}
if (status["__rejected"] == true) {
final err = jsGetPropertyStr(ctx, val, "__error");
completer.completeError(parseJSException(
ctx,
perr: err,
));
jsFreeValue(ctx, err);
return true;
}
return false;
}
}
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, [dynamic thisVal]) {
if (val == null) return;
List<Pointer> args = arguments
.map(
(e) => dartToJs(ctx, e),
)
.toList();
Pointer jsRet = jsCall(ctx, val, dartToJs(ctx, thisVal), args);
for (Pointer jsArg in args) {
jsFreeValue(ctx, jsArg);
}
bool isException = jsIsException(jsRet) != 0;
if (isException) {
jsFreeValue(ctx, jsRet);
throw Exception(parseJSException(ctx));
}
var ret = jsToDart(ctx, jsRet);
jsFreeValue(ctx, jsRet);
return ret;
} }
@override @override
@@ -135,13 +32,316 @@ class JSFunction extends JSRefValue implements QjsInvokable {
} }
} }
Pointer jsGetPropertyStr(Pointer ctx, Pointer val, String prop) { class _DartFunction extends JSInvokable {
var jsAtomVal = jsNewString(ctx, prop); Function _func;
var jsAtom = jsValueToAtom(ctx, jsAtomVal); _DartFunction(this._func);
Pointer jsProp = jsGetProperty(ctx, val, jsAtom);
jsFreeAtom(ctx, jsAtom); @override
jsFreeValue(ctx, jsAtomVal); invoke(List args, [thisVal]) {
return jsProp; /// wrap this into function
final passThis =
RegExp("{.*thisVal.*}").hasMatch(_func.runtimeType.toString());
return Function.apply(_func, args, passThis ? {#thisVal: thisVal} : null);
}
}
abstract class DartReleasable {
void release();
}
class DartObject implements JSRef {
Object _obj;
Pointer _ctx;
DartObject(this._ctx, this._obj) {
runtimeOpaques[jsGetRuntime(_ctx)]?.ref?.add(this);
}
static DartObject fromAddress(Pointer rt, int val) {
return runtimeOpaques[rt]?.ref?.firstWhere(
(e) => identityHashCode(e) == val,
orElse: () => null,
);
}
@override
void release() {
if (_obj is DartReleasable) {
(_obj as DartReleasable).release();
}
_obj = null;
_ctx = null;
}
}
class JSObject implements JSRef {
Pointer _val;
Pointer _ctx;
/// Create
JSObject(this._ctx, Pointer _val) {
Pointer rt = jsGetRuntime(_ctx);
this._val = jsDupValue(_ctx, _val);
runtimeOpaques[rt]?.ref?.add(this);
}
JSObject.fromAddress(Pointer ctx, Pointer val) {
this._ctx = ctx;
this._val = val;
}
@override
void release() {
if (_val != null) {
jsFreeValue(_ctx, _val);
}
_val = null;
_ctx = null;
}
}
class JSFunction extends JSObject implements JSInvokable {
JSFunction(Pointer ctx, Pointer val) : super(ctx, val);
JSFunction.fromAddress(Pointer ctx, Pointer val)
: super.fromAddress(ctx, val);
@override
invoke(List<dynamic> arguments, [dynamic thisVal]) {
Pointer jsRet = _invoke(arguments, thisVal);
if (jsRet == null) return;
bool isException = jsIsException(jsRet) != 0;
if (isException) {
jsFreeValue(_ctx, jsRet);
throw Exception(parseJSException(_ctx));
}
var ret = jsToDart(_ctx, jsRet);
jsFreeValue(_ctx, jsRet);
return ret;
}
Pointer _invoke(List<dynamic> arguments, [dynamic thisVal]) {
if (_val == null) return null;
List<Pointer> args = arguments
.map(
(e) => dartToJs(_ctx, e),
)
.toList();
Pointer jsThis = dartToJs(_ctx, thisVal);
Pointer jsRet = jsCall(_ctx, _val, jsThis, args);
jsFreeValue(_ctx, jsThis);
for (Pointer jsArg in args) {
jsFreeValue(_ctx, jsArg);
}
return jsRet;
}
@override
noSuchMethod(Invocation invocation) {
return invoke(
invocation.positionalArguments,
invocation.namedArguments[#thisVal],
);
}
}
class IsolateJSFunction extends JSInvokable {
int _val;
int _ctx;
SendPort port;
IsolateJSFunction(this._ctx, this._val, this.port);
@override
Future invoke(List arguments, [thisVal]) async {
if (0 == _val ?? 0) return;
var evaluatePort = ReceivePort();
port.send({
'type': 'call',
'ctx': _ctx,
'val': _val,
'args': encodeData(arguments),
'this': encodeData(thisVal),
'port': evaluatePort.sendPort,
});
Map result = await evaluatePort.first;
evaluatePort.close();
if (result.containsKey('data'))
return decodeData(result['data'], port);
else
throw result['error'];
}
}
class IsolateFunction extends JSInvokable implements DartReleasable {
SendPort _port;
SendPort func;
IsolateFunction(this.func, this._port);
static IsolateFunction bind(Function func, SendPort port) {
final JSInvokable invokable = JSInvokable.wrap(func);
final funcPort = ReceivePort();
funcPort.listen((msg) async {
if (msg == "close") return funcPort.close();
var data;
SendPort msgPort = msg['port'];
try {
List args = decodeData(msg['args'], port);
Map thisVal = decodeData(msg['this'], port);
data = await invokable.invoke(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);
}
@override
Future invoke(List positionalArguments, [thisVal]) async {
if (func == null) return;
var evaluatePort = ReceivePort();
func.send({
'args': encodeData(positionalArguments),
'this': encodeData(thisVal),
'port': evaluatePort.sendPort,
});
Map result = await evaluatePort.first;
evaluatePort.close();
if (result.containsKey('data'))
return decodeData(result['data'], _port);
else
throw result['error'];
}
@override
void release() {
if (func == null) return;
func.send("close");
func = null;
}
}
dynamic encodeData(dynamic data, {Map<dynamic, dynamic> cache}) {
if (cache == null) cache = Map();
if (cache.containsKey(data)) return cache[data];
if (data is List) {
var ret = [];
cache[data] = ret;
for (int i = 0; i < data.length; ++i) {
ret.add(encodeData(data[i], cache: cache));
}
return ret;
}
if (data is Map) {
var ret = {};
cache[data] = ret;
for (var entry in data.entries) {
ret[encodeData(entry.key, cache: cache)] =
encodeData(entry.value, cache: cache);
}
return ret;
}
if (data is JSObject) {
return {
'__js_function': data is JSFunction,
'__js_obj_ctx': data._ctx.address,
'__js_obj_val': data._val.address,
};
}
if (data is IsolateJSFunction) {
return {
'__js_obj_ctx': data._ctx,
'__js_obj_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) {
futurePort.close();
(port as SendPort).send({'data': encodeData(value)});
});
}, onError: (e, stack) {
futurePort.first.then((port) {
futurePort.close();
(port as SendPort)
.send({'error': e.toString() + "\n" + stack.toString()});
});
});
return {
'__js_future_port': futurePort.sendPort,
};
}
return data;
}
dynamic decodeData(dynamic data, SendPort port, {Map<dynamic, dynamic> cache}) {
if (cache == null) cache = Map();
if (cache.containsKey(data)) return cache[data];
if (data is List) {
var ret = [];
cache[data] = ret;
for (int i = 0; i < data.length; ++i) {
ret.add(decodeData(data[i], port, cache: cache));
}
return ret;
}
if (data is Map) {
if (data.containsKey('__js_obj_val')) {
int ctx = data['__js_obj_ctx'];
int val = data['__js_obj_val'];
if (data['__js_function'] == false) {
return JSObject.fromAddress(
Pointer.fromAddress(ctx),
Pointer.fromAddress(val),
);
} else if (port != null) {
return IsolateJSFunction(ctx, val, port);
} else {
return JSFunction.fromAddress(
Pointer.fromAddress(ctx),
Pointer.fromAddress(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 {
futureCompleter.complete(value['data']);
}
});
return futureCompleter.future;
}
var ret = {};
cache[data] = ret;
for (var entry in data.entries) {
ret[decodeData(entry.key, port, cache: cache)] =
decodeData(entry.value, port, cache: cache);
}
return ret;
}
return data;
} }
String parseJSException(Pointer ctx, {Pointer perr}) { String parseJSException(Pointer ctx, {Pointer perr}) {
@@ -149,7 +349,7 @@ String parseJSException(Pointer ctx, {Pointer perr}) {
var err = jsToCString(ctx, e); var err = jsToCString(ctx, e);
if (jsValueGetTag(e) == JSTag.OBJECT) { if (jsValueGetTag(e) == JSTag.OBJECT) {
Pointer stack = jsGetPropertyStr(ctx, e, "stack"); Pointer stack = jsGetPropertyValue(ctx, e, "stack");
if (jsToBool(ctx, stack) != 0) { if (jsToBool(ctx, stack) != 0) {
err += '\n' + jsToCString(ctx, stack); err += '\n' + jsToCString(ctx, stack);
} }
@@ -179,8 +379,23 @@ void definePropertyValue(
jsFreeValue(ctx, jsAtomVal); jsFreeValue(ctx, jsAtomVal);
} }
Pointer jsGetPropertyValue(
Pointer ctx,
Pointer obj,
dynamic key, {
Map<dynamic, dynamic> cache,
}) {
var jsAtomVal = dartToJs(ctx, key, cache: cache);
var jsAtom = jsValueToAtom(ctx, jsAtomVal);
var jsProp = jsGetProperty(ctx, obj, jsAtom);
jsFreeAtom(ctx, jsAtom);
jsFreeValue(ctx, jsAtomVal);
return jsProp;
}
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 JSObject) return jsDupValue(ctx, val._val);
if (val is Future) { if (val is Future) {
var resolvingFunc = allocate<Uint8>(count: sizeOfJSValue * 2); var resolvingFunc = allocate<Uint8>(count: sizeOfJSValue * 2);
var resolvingFunc2 = var resolvingFunc2 =
@@ -214,9 +429,6 @@ Pointer dartToJs(Pointer ctx, dynamic val, {Map<dynamic, dynamic> cache}) {
if (cache.containsKey(val)) { if (cache.containsKey(val)) {
return jsDupValue(ctx, cache[val]); return jsDupValue(ctx, cache[val]);
} }
if (val is JSFunction) {
return jsDupValue(ctx, val.val);
}
if (val is List) { if (val is List) {
Pointer ret = jsNewArray(ctx); Pointer ret = jsNewArray(ctx);
cache[val] = ret; cache[val] = ret;
@@ -233,15 +445,17 @@ Pointer dartToJs(Pointer ctx, dynamic val, {Map<dynamic, dynamic> cache}) {
} }
return ret; return ret;
} }
// wrap Function to JSInvokable
final valWrap = JSInvokable.wrap(val);
int dartObjectClassId = int dartObjectClassId =
runtimeOpaques[jsGetRuntime(ctx)]?.dartObjectClassId ?? 0; runtimeOpaques[jsGetRuntime(ctx)]?.dartObjectClassId ?? 0;
if (dartObjectClassId == 0) return jsUNDEFINED(); if (dartObjectClassId == 0) return jsUNDEFINED();
var dartObject = jsNewObjectClass( var dartObject = jsNewObjectClass(
ctx, ctx,
dartObjectClassId, dartObjectClassId,
identityHashCode(DartObject(ctx, val)), identityHashCode(DartObject(ctx, valWrap)),
); );
if (val is Function || val is IsolateFunction) { if (valWrap is JSInvokable) {
final ret = jsNewCFunction(ctx, dartObject); final ret = jsNewCFunction(ctx, dartObject);
jsFreeValue(ctx, dartObject); jsFreeValue(ctx, dartObject);
return ret; return ret;
@@ -268,7 +482,7 @@ dynamic jsToDart(Pointer ctx, Pointer val, {Map<int, dynamic> cache}) {
if (dartObjectClassId != 0) { if (dartObjectClassId != 0) {
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;
} }
Pointer<IntPtr> psize = allocate<IntPtr>(); Pointer<IntPtr> psize = allocate<IntPtr>();
Pointer<Uint8> buf = jsGetArrayBuffer(ctx, psize, val); Pointer<Uint8> buf = jsGetArrayBuffer(ctx, psize, val);
@@ -284,18 +498,30 @@ dynamic jsToDart(Pointer ctx, Pointer val, {Map<int, dynamic> cache}) {
if (jsIsFunction(ctx, val) != 0) { if (jsIsFunction(ctx, val) != 0) {
return JSFunction(ctx, val); return JSFunction(ctx, val);
} else if (jsIsPromise(ctx, val) != 0) { } else if (jsIsPromise(ctx, val) != 0) {
return runtimeOpaques[rt]?.promiseToFuture(val); Pointer jsPromiseThen = jsGetPropertyValue(ctx, val, "then");
JSFunction promiseThen = jsToDart(ctx, jsPromiseThen, cache: cache);
jsFreeValue(ctx, jsPromiseThen);
var completer = Completer();
completer.future.catchError((e) {});
final jsRet = promiseThen._invoke([
(v) {
if (!completer.isCompleted) completer.complete(v);
},
(e) {
if (!completer.isCompleted) completer.completeError(e);
},
], JSObject.fromAddress(ctx, val));
bool isException = jsIsException(jsRet) != 0;
jsFreeValue(ctx, jsRet);
if (isException) throw Exception(parseJSException(ctx));
return completer.future;
} else if (jsIsArray(ctx, val) != 0) { } else if (jsIsArray(ctx, val) != 0) {
Pointer jslength = jsGetPropertyStr(ctx, val, "length"); Pointer jslength = jsGetPropertyValue(ctx, val, "length");
int length = jsToInt64(ctx, jslength); int length = jsToInt64(ctx, jslength);
List<dynamic> ret = []; List<dynamic> ret = [];
cache[valptr] = ret; cache[valptr] = ret;
for (int i = 0; i < length; ++i) { for (int i = 0; i < length; ++i) {
var jsAtomVal = jsNewInt64(ctx, i); var jsProp = jsGetPropertyValue(ctx, val, i);
var jsAtom = jsValueToAtom(ctx, jsAtomVal);
var jsProp = jsGetProperty(ctx, val, jsAtom);
jsFreeAtom(ctx, jsAtom);
jsFreeValue(ctx, jsAtomVal);
ret.add(jsToDart(ctx, jsProp, cache: cache)); ret.add(jsToDart(ctx, jsProp, cache: cache));
jsFreeValue(ctx, jsProp); jsFreeValue(ctx, jsProp);
} }
@@ -327,43 +553,3 @@ dynamic jsToDart(Pointer ctx, Pointer val, {Map<int, dynamic> cache}) {
} }
return null; return null;
} }
Pointer jsNewContextWithPromsieWrapper(Pointer rt) {
var ctx = jsNewContext(rt);
final runtimeOpaque = runtimeOpaques[rt];
if (runtimeOpaque == null) throw Exception("Runtime has been released!");
var jsPromiseWrapper = jsEval(
ctx,
"""
(value) => {
const __ret = {};
Promise.resolve(value)
.then(v => {
__ret.__value = v;
__ret.__resolved = true;
}).catch(e => {
__ret.__error = e;
__ret.__rejected = true;
});
return __ret;
}
""",
"<future>",
JSEvalFlag.GLOBAL);
runtimeOpaque.dartObjectClassId = jsNewClass(ctx, "DartObject");
final promiseWrapper = JSRefValue(ctx, jsPromiseWrapper);
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!"));
var jsPromise = jsCall(ctx, wrapper, null, [promise]);
var wrapPromise = JSPromise(ctx, jsPromise, completer);
jsFreeValue(ctx, jsPromise);
return wrapPromise.completer.future;
};
return ctx;
}

View File

@@ -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.0 version: 0.3.1
homepage: https://github.com/ekibun/flutter_qjs homepage: https://github.com/ekibun/flutter_qjs
environment: environment:

View File

@@ -9,32 +9,46 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:flutter_qjs/ffi.dart';
import 'package:flutter_qjs/flutter_qjs.dart'; import 'package:flutter_qjs/flutter_qjs.dart';
import 'package:flutter_qjs/isolate.dart'; import 'package:flutter_qjs/isolate.dart';
import 'package:flutter_qjs/ffi.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
dynamic myFunction(String args, {String thisVal}) { dynamic myFunction(String args, {thisVal}) {
return [thisVal, args]; return [thisVal, args];
} }
Future testEvaluate(qjs) async { Future testEvaluate(qjs) async {
final testWrap = await qjs.evaluate("(a) => a", name: "<testWrap>"); final testWrap = await qjs.evaluate(
"(a) => a",
name: "<testWrap>",
);
final wrapNull = await testWrap(null);
expect(wrapNull, null, reason: "wrap null");
final primities = [0, 1, 0.1, true, false, "str"]; final primities = [0, 1, 0.1, true, false, "str"];
final wrapPrimities = await testWrap(primities); final wrapPrimities = await testWrap(primities);
for (var i = 0; i < primities.length; i++) { for (var i = 0; i < primities.length; i++) {
expect(wrapPrimities[i], primities[i], reason: "wrap primities"); expect(wrapPrimities[i], primities[i], reason: "wrap primities");
} }
final wrapFunction = await testWrap(testWrap);
final testEqual = await qjs.evaluate(
"(a, b) => a === b",
name: "<testEqual>",
);
expect(await testEqual(wrapFunction, testWrap), true,
reason: "wrap function");
expect(wrapNull, null, reason: "wrap null");
final a = {}; final a = {};
a["a"] = a; a["a"] = a;
final wrapA = await testWrap(a); final wrapA = await testWrap(a);
expect(wrapA['a'], wrapA, reason: "recursive object"); expect(wrapA['a'], wrapA, reason: "recursive object");
final testThis = await qjs.evaluate( final testThis = await qjs.evaluate(
"(func) => func.call('this', 'arg')", "(function (func, arg) { return func.call(this, arg) })",
name: "<testThis>", name: "<testThis>",
); );
final funcRet = await testThis(myFunction); final funcRet = await testThis(myFunction, 'arg', thisVal: {'name': 'this'});
expect(funcRet[0], 'this', reason: "js function this"); expect(funcRet[0]['name'], 'this', reason: "js function this");
expect(funcRet[1], 'arg', reason: "js function argument"); expect(funcRet[1], 'arg', reason: "js function argument");
final promises = await testWrap(await qjs.evaluate( final promises = await testWrap(await qjs.evaluate(
"[Promise.reject('test Promise.reject'), Promise.resolve('test Promise.resolve')]", "[Promise.reject('test Promise.reject'), Promise.resolve('test Promise.resolve')]",