mirror of
https://github.com/wgh136/flutter_qjs.git
synced 2025-09-27 05:27:23 +00:00
setToGlobalObject
This commit is contained in:
44
README.md
44
README.md
@@ -46,7 +46,7 @@ engine = null;
|
||||
Data conversion between dart and js are implemented as follow:
|
||||
|
||||
| dart | js |
|
||||
| --------------------------------------------------- | ------------------ |
|
||||
| --------- | ------------------ |
|
||||
| Bool | boolean |
|
||||
| Int | number |
|
||||
| Double | number |
|
||||
@@ -54,35 +54,27 @@ Data conversion between dart and js are implemented as follow:
|
||||
| Uint8List | ArrayBuffer |
|
||||
| List | Array |
|
||||
| Map | Object |
|
||||
| JSFunction(...args) <br> IsolateJSFunction(...args) | function(....args) |
|
||||
| Function | function(....args) |
|
||||
| Future | Promise |
|
||||
| Object | DartObject |
|
||||
|
||||
**notice:** `function` can only be sent from js to dart. `DartObject` can only be used in `moduleHandler`.
|
||||
**notice:** Dart function parameter `thisVal` is used to store `this` in js.
|
||||
|
||||
### Invoke dart function
|
||||
### Set into global object
|
||||
|
||||
A global JavaScript function `channel` is presented to invoke dart function.
|
||||
|
||||
In constructor, pass handler function to manage JavaScript call. For example, you can use `Dio` to implement http in JavaScript:
|
||||
Method `setToGlobalObject` is presented to set dart object into global object.
|
||||
For example, you can pass a function implement http in JavaScript with `Dio`:
|
||||
|
||||
```dart
|
||||
final engine = FlutterQjs(
|
||||
methodHandler: (String method, List arg) {
|
||||
switch (method) {
|
||||
case "http":
|
||||
return Dio().get(arg[0]).then((response) => response.data);
|
||||
default:
|
||||
throw Exception("No such method");
|
||||
}
|
||||
},
|
||||
);
|
||||
engine.setToGlobalObject("http", (String url) {
|
||||
return Dio().get(url).then((response) => response.data);
|
||||
});
|
||||
```
|
||||
|
||||
then, in java script you can use channel function to invoke `methodHandler`, make sure the second parameter is a list:
|
||||
then, in java script you can use `http` function to invoke dart function:
|
||||
|
||||
```javascript
|
||||
channel("http", ["http://example.com/"]);
|
||||
http("http://example.com/");
|
||||
```
|
||||
|
||||
### Use modules
|
||||
@@ -111,7 +103,9 @@ To use async function in module handler, try [Run on isolate thread](#Run-on-iso
|
||||
|
||||
### Run on isolate thread
|
||||
|
||||
Create a `IsolateQjs` object, pass handlers to implement js-dart interaction and resolving modules. The `methodHandler` is used in isolate, so **the handler function must be a top-level function or a static method**. 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:
|
||||
|
||||
The `methodHandler` is used in isolate, so **the handler function must be a top-level function or a static method**.
|
||||
|
||||
```dart
|
||||
dynamic methodHandler(String method, List arg) {
|
||||
@@ -132,6 +126,16 @@ final engine = IsolateQjs(
|
||||
// not need engine.dispatch();
|
||||
```
|
||||
|
||||
Method `setToGlobalObject` is still here to set dart object into global object. Use `await` to make sure it is finished.
|
||||
**Make sure the object that can pass through isolate**, For example, a top level function:
|
||||
|
||||
```dart
|
||||
dynamic http(String url) {
|
||||
return Dio().get(url).then((response) => response.data);
|
||||
}
|
||||
await engine.setToGlobalObject("http", http);
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```dart
|
||||
|
37
cxx/ffi.cpp
37
cxx/ffi.cpp
@@ -39,7 +39,7 @@ extern "C"
|
||||
{
|
||||
JSRuntime *rt = JS_GetRuntime(ctx);
|
||||
JSChannel *channel = (JSChannel *)JS_GetRuntimeOpaque(rt);
|
||||
const char *str = (char *)channel(ctx, module_name, nullptr);
|
||||
const char *str = (char *)channel(ctx, JSChannelType_MODULE, (void *)module_name);
|
||||
if (str == 0)
|
||||
return NULL;
|
||||
JSValue func_val = JS_Eval(ctx, str, strlen(str), module_name, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
|
||||
@@ -51,16 +51,16 @@ extern "C"
|
||||
return m;
|
||||
}
|
||||
|
||||
JSValue js_channel(JSContext *ctx, JSValueConst this_val, int32_t argc, JSValueConst *argv)
|
||||
JSValue js_channel(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic, JSValue *func_data)
|
||||
{
|
||||
JSRuntime *rt = JS_GetRuntime(ctx);
|
||||
JSChannel *channel = (JSChannel *)JS_GetRuntimeOpaque(rt);
|
||||
const char *str = JS_ToCString(ctx, argv[0]);
|
||||
JS_DupValue(ctx, *(argv + 1));
|
||||
JSValue ret = *(JSValue *)channel(ctx, str, argv + 1);
|
||||
JS_FreeValue(ctx, *(argv + 1));
|
||||
JS_FreeCString(ctx, str);
|
||||
return ret;
|
||||
void *data[4];
|
||||
data[0] = &this_val;
|
||||
data[1] = &argc;
|
||||
data[2] = argv;
|
||||
data[3] = func_data;
|
||||
return *(JSValue *)channel(ctx, JSChannelType_METHON, data);
|
||||
}
|
||||
|
||||
void js_promise_rejection_tracker(JSContext *ctx, JSValueConst promise,
|
||||
@@ -71,7 +71,7 @@ extern "C"
|
||||
return;
|
||||
JSRuntime *rt = JS_GetRuntime(ctx);
|
||||
JSChannel *channel = (JSChannel *)JS_GetRuntimeOpaque(rt);
|
||||
channel(ctx, (char *)ctx, &reason);
|
||||
channel(ctx, JSChannelType_PROMISE_TRACK, &reason);
|
||||
}
|
||||
|
||||
DLLEXPORT JSRuntime *jsNewRuntime(JSChannel channel)
|
||||
@@ -97,7 +97,9 @@ extern "C"
|
||||
JSClassID classid = JS_GetClassID(obj);
|
||||
void *opaque = JS_GetOpaque(obj, classid);
|
||||
JSChannel *channel = (JSChannel *)JS_GetRuntimeOpaque(rt);
|
||||
channel((JSContext *)rt, nullptr, opaque);
|
||||
if (channel == nullptr)
|
||||
return;
|
||||
channel((JSContext *)rt, JSChannelType_FREE_OBJECT, opaque);
|
||||
}};
|
||||
int e = JS_NewClass(rt, QJSClassId, &def);
|
||||
if (e < 0)
|
||||
@@ -134,14 +136,19 @@ extern "C"
|
||||
JS_FreeRuntime(rt);
|
||||
}
|
||||
|
||||
DLLEXPORT JSValue *jsNewCFunction(JSContext *ctx, JSValue *funcData)
|
||||
{
|
||||
return new JSValue(JS_NewCFunctionData(ctx, js_channel, 0, 0, 1, funcData));
|
||||
}
|
||||
|
||||
DLLEXPORT JSValue *jsGetGlobalObject(JSContext *ctx)
|
||||
{
|
||||
return new JSValue(JS_GetGlobalObject(ctx));
|
||||
}
|
||||
|
||||
DLLEXPORT JSContext *jsNewContext(JSRuntime *rt)
|
||||
{
|
||||
JSContext *ctx = JS_NewContext(rt);
|
||||
JSAtom atom = JS_NewAtom(ctx, "channel");
|
||||
JSValue globalObject = JS_GetGlobalObject(ctx);
|
||||
JS_SetProperty(ctx, globalObject, atom, JS_NewCFunction(ctx, js_channel, "channel", 2));
|
||||
JS_FreeValue(ctx, globalObject);
|
||||
JS_FreeAtom(ctx, atom);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
|
13
cxx/ffi.h
13
cxx/ffi.h
@@ -8,7 +8,14 @@
|
||||
|
||||
extern "C"
|
||||
{
|
||||
typedef void *JSChannel(JSContext *ctx, const char *method, void *argv);
|
||||
enum JSChannelType {
|
||||
JSChannelType_METHON = 0,
|
||||
JSChannelType_MODULE = 1,
|
||||
JSChannelType_PROMISE_TRACK = 2,
|
||||
JSChannelType_FREE_OBJECT = 3,
|
||||
};
|
||||
|
||||
typedef void *JSChannel(JSContext *ctx, size_t type, void *argv);
|
||||
|
||||
DLLEXPORT JSValue *jsThrowInternalError(JSContext *ctx, char *message);
|
||||
|
||||
@@ -30,6 +37,10 @@ extern "C"
|
||||
|
||||
DLLEXPORT void jsFreeRuntime(JSRuntime *rt);
|
||||
|
||||
DLLEXPORT JSValue *jsNewCFunction(JSContext *ctx, JSValue *funcData);
|
||||
|
||||
DLLEXPORT JSValue *jsGetGlobalObject(JSContext *ctx);
|
||||
|
||||
DLLEXPORT JSContext *jsNewContext(JSRuntime *rt);
|
||||
|
||||
DLLEXPORT void jsFreeContext(JSContext *ctx);
|
||||
|
@@ -76,13 +76,13 @@ class _TestPageState extends State<TestPage> {
|
||||
_ensureEngine() {
|
||||
if (engine != null) return;
|
||||
engine = IsolateQjs(
|
||||
methodHandler: methodHandler,
|
||||
moduleHandler: (String module) async {
|
||||
if (module == "test") return "export default '${new DateTime.now()}'";
|
||||
return await rootBundle.loadString(
|
||||
"js/" + module.replaceFirst(new RegExp(r".js$"), "") + ".js");
|
||||
},
|
||||
);
|
||||
engine.setToGlobalObject("channel", methodHandler);
|
||||
}
|
||||
|
||||
@override
|
||||
|
47
lib/ffi.dart
47
lib/ffi.dart
@@ -21,6 +21,13 @@ class JSEvalFlag {
|
||||
static const MODULE = 1 << 0;
|
||||
}
|
||||
|
||||
class JSChannelType {
|
||||
static const METHON = 0;
|
||||
static const MODULE = 1;
|
||||
static const PROMISE_TRACK = 2;
|
||||
static const FREE_OBJECT = 3;
|
||||
}
|
||||
|
||||
class JSProp {
|
||||
static const CONFIGURABLE = (1 << 0);
|
||||
static const WRITABLE = (1 << 1);
|
||||
@@ -91,11 +98,13 @@ final Pointer Function() jsUNDEFINED = qjsLib
|
||||
.lookup<NativeFunction<Pointer Function()>>("jsUNDEFINED")
|
||||
.asFunction();
|
||||
|
||||
typedef JSChannel = Pointer Function(Pointer ctx, int method, Pointer argv);
|
||||
typedef JSChannelNative = Pointer Function(
|
||||
Pointer ctx, IntPtr method, Pointer argv);
|
||||
|
||||
/// JSRuntime *jsNewRuntime(JSChannel channel)
|
||||
final Pointer Function(
|
||||
Pointer<
|
||||
NativeFunction<
|
||||
Pointer Function(Pointer ctx, Pointer method, Pointer argv)>>,
|
||||
Pointer<NativeFunction<JSChannelNative>>,
|
||||
) _jsNewRuntime = qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
@@ -104,8 +113,6 @@ final Pointer Function(
|
||||
)>>("jsNewRuntime")
|
||||
.asFunction();
|
||||
|
||||
typedef JSChannel = Pointer Function(Pointer ctx, Pointer method, Pointer argv);
|
||||
|
||||
class RuntimeOpaque {
|
||||
JSChannel channel;
|
||||
List<JSRef> ref = [];
|
||||
@@ -116,9 +123,9 @@ class RuntimeOpaque {
|
||||
|
||||
final Map<Pointer, RuntimeOpaque> runtimeOpaques = Map();
|
||||
|
||||
Pointer channelDispacher(Pointer ctx, Pointer method, Pointer argv) {
|
||||
Pointer rt = method.address == 0 ? ctx : jsGetRuntime(ctx);
|
||||
return runtimeOpaques[rt]?.channel(ctx, method, argv);
|
||||
Pointer channelDispacher(Pointer ctx, int type, Pointer argv) {
|
||||
Pointer rt = type == JSChannelType.FREE_OBJECT ? ctx : jsGetRuntime(ctx);
|
||||
return runtimeOpaques[rt]?.channel(ctx, type, argv);
|
||||
}
|
||||
|
||||
Pointer jsNewRuntime(
|
||||
@@ -166,6 +173,30 @@ void jsFreeRuntime(
|
||||
_jsFreeRuntime(rt);
|
||||
}
|
||||
|
||||
/// JSValue *jsNewCFunction(JSContext *ctx, JSValue *funcData)
|
||||
final Pointer Function(
|
||||
Pointer ctx,
|
||||
Pointer funcData,
|
||||
) jsNewCFunction = qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Pointer Function(
|
||||
Pointer,
|
||||
Pointer,
|
||||
)>>("jsNewCFunction")
|
||||
.asFunction();
|
||||
|
||||
/// JSValue *jsGetGlobalObject(JSContext *ctx)
|
||||
final Pointer Function(
|
||||
Pointer ctx,
|
||||
) jsGetGlobalObject = qjsLib
|
||||
.lookup<
|
||||
NativeFunction<
|
||||
Pointer Function(
|
||||
Pointer,
|
||||
)>>("jsGetGlobalObject")
|
||||
.asFunction();
|
||||
|
||||
/// JSContext *jsNewContext(JSRuntime *rt)
|
||||
final Pointer Function(
|
||||
Pointer rt,
|
||||
|
@@ -13,9 +13,6 @@ import 'package:ffi/ffi.dart';
|
||||
import 'package:flutter_qjs/ffi.dart';
|
||||
import 'package:flutter_qjs/wrapper.dart';
|
||||
|
||||
/// Handler function to manage js call.
|
||||
typedef JsMethodHandler = dynamic Function(String method, List args);
|
||||
|
||||
/// Handler function to manage js module.
|
||||
typedef JsModuleHandler = String Function(String name);
|
||||
|
||||
@@ -32,9 +29,6 @@ class FlutterQjs {
|
||||
/// Message Port for event loop. Close it to stop dispatching event loop.
|
||||
ReceivePort port = ReceivePort();
|
||||
|
||||
/// Handler function to manage js call with `channel(method, [...args])` function.
|
||||
JsMethodHandler methodHandler;
|
||||
|
||||
/// Handler function to manage js module.
|
||||
JsModuleHandler moduleHandler;
|
||||
|
||||
@@ -44,55 +38,75 @@ class FlutterQjs {
|
||||
/// Quickjs engine for flutter.
|
||||
///
|
||||
/// Pass handlers to implement js-dart interaction and resolving modules.
|
||||
FlutterQjs(
|
||||
{this.methodHandler,
|
||||
FlutterQjs({
|
||||
this.moduleHandler,
|
||||
this.stackSize,
|
||||
this.hostPromiseRejectionHandler});
|
||||
this.hostPromiseRejectionHandler,
|
||||
});
|
||||
|
||||
setToGlobalObject(dynamic key, dynamic val) {
|
||||
_ensureEngine();
|
||||
final globalObject = jsGetGlobalObject(_ctx);
|
||||
definePropertyValue(_ctx, globalObject, key, val);
|
||||
jsFreeValue(_ctx, globalObject);
|
||||
}
|
||||
|
||||
_ensureEngine() {
|
||||
if (_rt != null) return;
|
||||
_rt = jsNewRuntime((ctx, method, argv) {
|
||||
_rt = jsNewRuntime((ctx, type, ptr) {
|
||||
try {
|
||||
if (method.address == 0) {
|
||||
Pointer rt = ctx;
|
||||
DartObject obj = DartObject.fromAddress(rt, argv.address);
|
||||
obj?.release();
|
||||
runtimeOpaques[rt]?.ref?.remove(obj);
|
||||
return Pointer.fromAddress(0);
|
||||
switch (type) {
|
||||
case JSChannelType.METHON:
|
||||
final pdata = ptr.cast<Pointer>();
|
||||
final argc = pdata.elementAt(1).value.cast<Int32>().value;
|
||||
List args = [];
|
||||
for (var i = 0; i < argc; i++) {
|
||||
args.add(jsToDart(
|
||||
ctx,
|
||||
Pointer.fromAddress(
|
||||
pdata.elementAt(2).value.address + sizeOfJSValue * i,
|
||||
)));
|
||||
}
|
||||
if (argv.address != 0) {
|
||||
if (method.address == ctx.address) {
|
||||
final errStr = parseJSException(ctx, perr: argv);
|
||||
final thisVal = jsToDart(ctx, pdata.elementAt(0).value);
|
||||
Function func = jsToDart(ctx, pdata.elementAt(3).value);
|
||||
final passThis =
|
||||
RegExp("{.*thisVal.*}").hasMatch(func.runtimeType.toString());
|
||||
return dartToJs(
|
||||
ctx,
|
||||
Function.apply(func, args, passThis ? {#thisVal: thisVal} : null),
|
||||
);
|
||||
case JSChannelType.MODULE:
|
||||
if (moduleHandler == null) throw Exception("No ModuleHandler");
|
||||
var ret = Utf8.toUtf8(moduleHandler(
|
||||
Utf8.fromUtf8(ptr.cast<Utf8>()),
|
||||
));
|
||||
Future.microtask(() {
|
||||
free(ret);
|
||||
});
|
||||
return ret;
|
||||
case JSChannelType.PROMISE_TRACK:
|
||||
final errStr = parseJSException(ctx, perr: ptr);
|
||||
if (hostPromiseRejectionHandler != null) {
|
||||
hostPromiseRejectionHandler(errStr);
|
||||
} else {
|
||||
print("unhandled promise rejection: $errStr");
|
||||
}
|
||||
return Pointer.fromAddress(0);
|
||||
case JSChannelType.FREE_OBJECT:
|
||||
Pointer rt = ctx;
|
||||
DartObject obj = DartObject.fromAddress(rt, ptr.address);
|
||||
obj?.release();
|
||||
runtimeOpaques[rt]?.ref?.remove(obj);
|
||||
return Pointer.fromAddress(0);
|
||||
}
|
||||
if (methodHandler == null) throw Exception("No MethodHandler");
|
||||
return dartToJs(
|
||||
ctx,
|
||||
methodHandler(
|
||||
Utf8.fromUtf8(method.cast<Utf8>()),
|
||||
jsToDart(ctx, argv),
|
||||
));
|
||||
}
|
||||
if (moduleHandler == null) throw Exception("No ModuleHandler");
|
||||
var ret =
|
||||
Utf8.toUtf8(moduleHandler(Utf8.fromUtf8(method.cast<Utf8>())));
|
||||
Future.microtask(() {
|
||||
free(ret);
|
||||
});
|
||||
return ret;
|
||||
throw Exception("call channel with wrong type");
|
||||
} catch (e, stack) {
|
||||
final errStr = e.toString() + "\n" + stack.toString();
|
||||
if (method.address == 0) {
|
||||
if (type == JSChannelType.FREE_OBJECT) {
|
||||
print("DartObject release error: " + errStr);
|
||||
return Pointer.fromAddress(0);
|
||||
}
|
||||
if (method.address == ctx.address) {
|
||||
if (type == JSChannelType.MODULE) {
|
||||
print("host Promise Rejection Handler error: " + errStr);
|
||||
return Pointer.fromAddress(0);
|
||||
}
|
||||
@@ -100,7 +114,7 @@ class FlutterQjs {
|
||||
ctx,
|
||||
errStr,
|
||||
);
|
||||
if (argv.address == 0) {
|
||||
if (type == JSChannelType.MODULE) {
|
||||
jsFreeValue(ctx, err);
|
||||
return Pointer.fromAddress(0);
|
||||
}
|
||||
|
@@ -153,7 +153,6 @@ void _runJsIsolate(Map spawnMessage) async {
|
||||
'reason': reason,
|
||||
});
|
||||
},
|
||||
methodHandler: spawnMessage['handler'],
|
||||
moduleHandler: (name) {
|
||||
var ptr = allocate<Pointer<Utf8>>();
|
||||
ptr.value = Pointer.fromAddress(0);
|
||||
@@ -192,6 +191,9 @@ void _runJsIsolate(Map spawnMessage) async {
|
||||
msg['val'],
|
||||
).invoke(_decodeData(msg['args'], null));
|
||||
break;
|
||||
case 'setToGlobalObject':
|
||||
qjs.setToGlobalObject(msg['key'], msg['val']);
|
||||
break;
|
||||
case 'close':
|
||||
qjs.port.close();
|
||||
qjs.close();
|
||||
@@ -220,10 +222,6 @@ class IsolateQjs {
|
||||
/// Max stack size for quickjs.
|
||||
final int stackSize;
|
||||
|
||||
/// Handler to manage js call with `channel(method, [...args])` function.
|
||||
/// The function must be a top-level function or a static method.
|
||||
JsMethodHandler methodHandler;
|
||||
|
||||
/// Asynchronously handler to manage js module.
|
||||
JsAsyncModuleHandler moduleHandler;
|
||||
|
||||
@@ -235,7 +233,6 @@ class IsolateQjs {
|
||||
/// Pass handlers to implement js-dart interaction and resolving modules. The `methodHandler` is
|
||||
/// used in isolate, so **the handler function must be a top-level function or a static method**.
|
||||
IsolateQjs({
|
||||
this.methodHandler,
|
||||
this.moduleHandler,
|
||||
this.stackSize,
|
||||
this.hostPromiseRejectionHandler,
|
||||
@@ -248,7 +245,6 @@ class IsolateQjs {
|
||||
_runJsIsolate,
|
||||
{
|
||||
'port': port.sendPort,
|
||||
'handler': methodHandler,
|
||||
'stackSize': stackSize,
|
||||
},
|
||||
errorsAreFatal: true,
|
||||
@@ -305,6 +301,23 @@ class IsolateQjs {
|
||||
_sendPort = null;
|
||||
}
|
||||
|
||||
setToGlobalObject(dynamic key, dynamic val) async {
|
||||
_ensureEngine();
|
||||
var evaluatePort = ReceivePort();
|
||||
var sendPort = await _sendPort;
|
||||
sendPort.send({
|
||||
'type': 'setToGlobalObject',
|
||||
'key': key,
|
||||
'val': val,
|
||||
'port': evaluatePort.sendPort,
|
||||
});
|
||||
var result = await evaluatePort.first;
|
||||
if (result['error'] == null) {
|
||||
return;
|
||||
} else
|
||||
throw result['error'];
|
||||
}
|
||||
|
||||
/// Evaluate js script.
|
||||
Future<dynamic> evaluate(String command, {String name, int evalFlags}) async {
|
||||
_ensureEngine();
|
||||
|
@@ -146,6 +146,26 @@ String parseJSException(Pointer ctx, {Pointer perr}) {
|
||||
return err;
|
||||
}
|
||||
|
||||
void definePropertyValue(
|
||||
Pointer ctx,
|
||||
Pointer obj,
|
||||
dynamic key,
|
||||
dynamic val, {
|
||||
Map<dynamic, dynamic> cache,
|
||||
}) {
|
||||
var jsAtomVal = dartToJs(ctx, key, cache: cache);
|
||||
var jsAtom = jsValueToAtom(ctx, jsAtomVal);
|
||||
jsDefinePropertyValue(
|
||||
ctx,
|
||||
obj,
|
||||
jsAtom,
|
||||
dartToJs(ctx, val, cache: cache),
|
||||
JSProp.C_W_E,
|
||||
);
|
||||
jsFreeAtom(ctx, jsAtom);
|
||||
jsFreeValue(ctx, jsAtomVal);
|
||||
}
|
||||
|
||||
Pointer dartToJs(Pointer ctx, dynamic val, {Map<dynamic, dynamic> cache}) {
|
||||
if (val == null) return jsUNDEFINED();
|
||||
if (val is Future) {
|
||||
@@ -188,17 +208,7 @@ Pointer dartToJs(Pointer ctx, dynamic val, {Map<dynamic, dynamic> cache}) {
|
||||
Pointer ret = jsNewArray(ctx);
|
||||
cache[val] = ret;
|
||||
for (int i = 0; i < val.length; ++i) {
|
||||
var jsAtomVal = jsNewInt64(ctx, i);
|
||||
var jsAtom = jsValueToAtom(ctx, jsAtomVal);
|
||||
jsDefinePropertyValue(
|
||||
ctx,
|
||||
ret,
|
||||
jsAtom,
|
||||
dartToJs(ctx, val[i], cache: cache),
|
||||
JSProp.C_W_E,
|
||||
);
|
||||
jsFreeAtom(ctx, jsAtom);
|
||||
jsFreeValue(ctx, jsAtomVal);
|
||||
definePropertyValue(ctx, ret, i, val[i], cache: cache);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -206,28 +216,24 @@ Pointer dartToJs(Pointer ctx, dynamic val, {Map<dynamic, dynamic> cache}) {
|
||||
Pointer ret = jsNewObject(ctx);
|
||||
cache[val] = ret;
|
||||
for (MapEntry<dynamic, dynamic> entry in val.entries) {
|
||||
var jsAtomVal = dartToJs(ctx, entry.key, cache: cache);
|
||||
var jsAtom = jsValueToAtom(ctx, jsAtomVal);
|
||||
jsDefinePropertyValue(
|
||||
ctx,
|
||||
ret,
|
||||
jsAtom,
|
||||
dartToJs(ctx, entry.value, cache: cache),
|
||||
JSProp.C_W_E,
|
||||
);
|
||||
jsFreeAtom(ctx, jsAtom);
|
||||
jsFreeValue(ctx, jsAtomVal);
|
||||
definePropertyValue(ctx, ret, entry.key, entry.value, cache: cache);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
int dartObjectClassId =
|
||||
runtimeOpaques[jsGetRuntime(ctx)]?.dartObjectClassId ?? 0;
|
||||
if (dartObjectClassId == 0) return jsUNDEFINED();
|
||||
return jsNewObjectClass(
|
||||
var dartObject = jsNewObjectClass(
|
||||
ctx,
|
||||
dartObjectClassId,
|
||||
identityHashCode(DartObject(ctx, val)),
|
||||
);
|
||||
if (val is Function) {
|
||||
final ret = jsNewCFunction(ctx, dartObject);
|
||||
jsFreeValue(ctx, dartObject);
|
||||
return ret;
|
||||
}
|
||||
return dartObject;
|
||||
}
|
||||
|
||||
dynamic jsToDart(Pointer ctx, Pointer val, {Map<int, dynamic> cache}) {
|
||||
|
@@ -14,31 +14,32 @@ import 'package:flutter_qjs/flutter_qjs.dart';
|
||||
import 'package:flutter_qjs/isolate.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
dynamic myMethodHandler(method, args) {
|
||||
return args;
|
||||
dynamic myMethodHandler(List args, {String thisVal}) {
|
||||
return [thisVal, ...args];
|
||||
}
|
||||
|
||||
Future testEvaluate(qjs) async {
|
||||
var value = await qjs.evaluate("""
|
||||
final value = await qjs.evaluate("""
|
||||
const a = {};
|
||||
a.a = a;
|
||||
import('test').then((module) => channel('channel', [
|
||||
import('test').then((module) => channel.call('this', [
|
||||
(...args)=>`hello \${args}!`, a,
|
||||
Promise.reject('test Promise.reject'), Promise.resolve('test Promise.resolve'),
|
||||
0.1, true, false, 1, "world", module
|
||||
]));
|
||||
""", name: "<eval>");
|
||||
expect(await value[0]('world'), 'hello world!', reason: "js function call");
|
||||
expect(value[1]['a'], value[1], reason: "recursive object");
|
||||
expect(value[2], isInstanceOf<Future>(), reason: "promise object");
|
||||
expect(value[0], 'this', reason: "js function this");
|
||||
expect(await value[1]('world'), 'hello world!', reason: "js function call");
|
||||
expect(value[2]['a'], value[2], reason: "recursive object");
|
||||
expect(value[3], isInstanceOf<Future>(), reason: "promise object");
|
||||
try {
|
||||
await value[2];
|
||||
await value[3];
|
||||
throw 'Future not reject';
|
||||
} catch (e) {
|
||||
expect(e, startsWith('test Promise.reject\n'),
|
||||
reason: "promise object reject");
|
||||
}
|
||||
expect(await value[3], 'test Promise.resolve',
|
||||
expect(await value[4], 'test Promise.resolve',
|
||||
reason: "promise object resolve");
|
||||
}
|
||||
|
||||
@@ -96,12 +97,12 @@ void main() async {
|
||||
});
|
||||
test('jsToDart', () async {
|
||||
final qjs = FlutterQjs(
|
||||
methodHandler: myMethodHandler,
|
||||
moduleHandler: (name) {
|
||||
return "export default '${new DateTime.now()}'";
|
||||
},
|
||||
hostPromiseRejectionHandler: (_) {},
|
||||
);
|
||||
qjs.setToGlobalObject("channel", myMethodHandler);
|
||||
qjs.dispatch();
|
||||
await testEvaluate(qjs);
|
||||
qjs.close();
|
||||
@@ -109,12 +110,12 @@ void main() async {
|
||||
test('isolate', () async {
|
||||
await runZonedGuarded(() async {
|
||||
final qjs = IsolateQjs(
|
||||
methodHandler: myMethodHandler,
|
||||
moduleHandler: (name) async {
|
||||
return "export default '${new DateTime.now()}'";
|
||||
},
|
||||
hostPromiseRejectionHandler: (_) {},
|
||||
);
|
||||
await qjs.setToGlobalObject("channel", myMethodHandler);
|
||||
await testEvaluate(qjs);
|
||||
qjs.close();
|
||||
}, (e, stack) {
|
||||
@@ -122,13 +123,12 @@ void main() async {
|
||||
});
|
||||
});
|
||||
test('dart object', () async {
|
||||
final qjs = FlutterQjs(
|
||||
methodHandler: (method, args) {
|
||||
final qjs = FlutterQjs();
|
||||
qjs.setToGlobalObject("channel", () {
|
||||
return FlutterQjs();
|
||||
},
|
||||
);
|
||||
});
|
||||
qjs.dispatch();
|
||||
var value = await qjs.evaluate("channel('channel', [])", name: "<eval>");
|
||||
var value = await qjs.evaluate("channel()", name: "<eval>");
|
||||
expect(value, isInstanceOf<FlutterQjs>(), reason: "dart object");
|
||||
qjs.close();
|
||||
});
|
||||
|
Reference in New Issue
Block a user