setToGlobalObject

This commit is contained in:
ekibun
2021-01-24 16:43:19 +08:00
parent 1589f87194
commit 6b0bab2faf
9 changed files with 228 additions and 142 deletions

View File

@@ -46,7 +46,7 @@ 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 |
@@ -54,35 +54,27 @@ Data conversion between dart and js are implemented as follow:
| Uint8List | ArrayBuffer | | Uint8List | ArrayBuffer |
| List | Array | | List | Array |
| Map | Object | | Map | Object |
| JSFunction(...args) <br> IsolateJSFunction(...args) | function(....args) | | Function | function(....args) |
| Future | Promise | | Future | Promise |
| Object | DartObject | | 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. Method `setToGlobalObject` is presented to set dart object into global object.
For example, you can pass a function implement http in JavaScript with `Dio`:
In constructor, pass handler function to manage JavaScript call. For example, you can use `Dio` to implement http in JavaScript:
```dart ```dart
final engine = FlutterQjs( engine.setToGlobalObject("http", (String url) {
methodHandler: (String method, List arg) { return Dio().get(url).then((response) => response.data);
switch (method) { });
case "http":
return Dio().get(arg[0]).then((response) => response.data);
default:
throw Exception("No such method");
}
},
);
``` ```
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 ```javascript
channel("http", ["http://example.com/"]); http("http://example.com/");
``` ```
### Use modules ### 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 ### 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 ```dart
dynamic methodHandler(String method, List arg) { dynamic methodHandler(String method, List arg) {
@@ -132,6 +126,16 @@ final engine = IsolateQjs(
// not need engine.dispatch(); // 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: 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 ```dart

View File

@@ -39,7 +39,7 @@ extern "C"
{ {
JSRuntime *rt = JS_GetRuntime(ctx); JSRuntime *rt = JS_GetRuntime(ctx);
JSChannel *channel = (JSChannel *)JS_GetRuntimeOpaque(rt); 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) if (str == 0)
return NULL; return NULL;
JSValue func_val = JS_Eval(ctx, str, strlen(str), module_name, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); 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; 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); JSRuntime *rt = JS_GetRuntime(ctx);
JSChannel *channel = (JSChannel *)JS_GetRuntimeOpaque(rt); JSChannel *channel = (JSChannel *)JS_GetRuntimeOpaque(rt);
const char *str = JS_ToCString(ctx, argv[0]); void *data[4];
JS_DupValue(ctx, *(argv + 1)); data[0] = &this_val;
JSValue ret = *(JSValue *)channel(ctx, str, argv + 1); data[1] = &argc;
JS_FreeValue(ctx, *(argv + 1)); data[2] = argv;
JS_FreeCString(ctx, str); data[3] = func_data;
return ret; return *(JSValue *)channel(ctx, JSChannelType_METHON, data);
} }
void js_promise_rejection_tracker(JSContext *ctx, JSValueConst promise, void js_promise_rejection_tracker(JSContext *ctx, JSValueConst promise,
@@ -71,7 +71,7 @@ extern "C"
return; return;
JSRuntime *rt = JS_GetRuntime(ctx); JSRuntime *rt = JS_GetRuntime(ctx);
JSChannel *channel = (JSChannel *)JS_GetRuntimeOpaque(rt); JSChannel *channel = (JSChannel *)JS_GetRuntimeOpaque(rt);
channel(ctx, (char *)ctx, &reason); channel(ctx, JSChannelType_PROMISE_TRACK, &reason);
} }
DLLEXPORT JSRuntime *jsNewRuntime(JSChannel channel) DLLEXPORT JSRuntime *jsNewRuntime(JSChannel channel)
@@ -97,7 +97,9 @@ extern "C"
JSClassID classid = JS_GetClassID(obj); JSClassID classid = JS_GetClassID(obj);
void *opaque = JS_GetOpaque(obj, classid); void *opaque = JS_GetOpaque(obj, classid);
JSChannel *channel = (JSChannel *)JS_GetRuntimeOpaque(rt); 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); int e = JS_NewClass(rt, QJSClassId, &def);
if (e < 0) if (e < 0)
@@ -134,14 +136,19 @@ extern "C"
JS_FreeRuntime(rt); 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) DLLEXPORT JSContext *jsNewContext(JSRuntime *rt)
{ {
JSContext *ctx = JS_NewContext(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; return ctx;
} }

View File

@@ -8,7 +8,14 @@
extern "C" 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); DLLEXPORT JSValue *jsThrowInternalError(JSContext *ctx, char *message);
@@ -30,6 +37,10 @@ extern "C"
DLLEXPORT void jsFreeRuntime(JSRuntime *rt); DLLEXPORT void jsFreeRuntime(JSRuntime *rt);
DLLEXPORT JSValue *jsNewCFunction(JSContext *ctx, JSValue *funcData);
DLLEXPORT JSValue *jsGetGlobalObject(JSContext *ctx);
DLLEXPORT JSContext *jsNewContext(JSRuntime *rt); DLLEXPORT JSContext *jsNewContext(JSRuntime *rt);
DLLEXPORT void jsFreeContext(JSContext *ctx); DLLEXPORT void jsFreeContext(JSContext *ctx);

View File

@@ -76,13 +76,13 @@ class _TestPageState extends State<TestPage> {
_ensureEngine() { _ensureEngine() {
if (engine != null) return; if (engine != null) return;
engine = IsolateQjs( engine = IsolateQjs(
methodHandler: methodHandler,
moduleHandler: (String module) async { moduleHandler: (String module) async {
if (module == "test") return "export default '${new DateTime.now()}'"; if (module == "test") return "export default '${new DateTime.now()}'";
return await rootBundle.loadString( return await rootBundle.loadString(
"js/" + module.replaceFirst(new RegExp(r".js$"), "") + ".js"); "js/" + module.replaceFirst(new RegExp(r".js$"), "") + ".js");
}, },
); );
engine.setToGlobalObject("channel", methodHandler);
} }
@override @override

View File

@@ -21,6 +21,13 @@ class JSEvalFlag {
static const MODULE = 1 << 0; 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 { class JSProp {
static const CONFIGURABLE = (1 << 0); static const CONFIGURABLE = (1 << 0);
static const WRITABLE = (1 << 1); static const WRITABLE = (1 << 1);
@@ -91,11 +98,13 @@ final Pointer Function() jsUNDEFINED = qjsLib
.lookup<NativeFunction<Pointer Function()>>("jsUNDEFINED") .lookup<NativeFunction<Pointer Function()>>("jsUNDEFINED")
.asFunction(); .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) /// JSRuntime *jsNewRuntime(JSChannel channel)
final Pointer Function( final Pointer Function(
Pointer< Pointer<NativeFunction<JSChannelNative>>,
NativeFunction<
Pointer Function(Pointer ctx, Pointer method, Pointer argv)>>,
) _jsNewRuntime = qjsLib ) _jsNewRuntime = qjsLib
.lookup< .lookup<
NativeFunction< NativeFunction<
@@ -104,8 +113,6 @@ final Pointer Function(
)>>("jsNewRuntime") )>>("jsNewRuntime")
.asFunction(); .asFunction();
typedef JSChannel = Pointer Function(Pointer ctx, Pointer method, Pointer argv);
class RuntimeOpaque { class RuntimeOpaque {
JSChannel channel; JSChannel channel;
List<JSRef> ref = []; List<JSRef> ref = [];
@@ -116,9 +123,9 @@ class RuntimeOpaque {
final Map<Pointer, RuntimeOpaque> runtimeOpaques = Map(); final Map<Pointer, RuntimeOpaque> runtimeOpaques = Map();
Pointer channelDispacher(Pointer ctx, Pointer method, Pointer argv) { Pointer channelDispacher(Pointer ctx, int type, Pointer argv) {
Pointer rt = method.address == 0 ? ctx : jsGetRuntime(ctx); Pointer rt = type == JSChannelType.FREE_OBJECT ? ctx : jsGetRuntime(ctx);
return runtimeOpaques[rt]?.channel(ctx, method, argv); return runtimeOpaques[rt]?.channel(ctx, type, argv);
} }
Pointer jsNewRuntime( Pointer jsNewRuntime(
@@ -166,6 +173,30 @@ void jsFreeRuntime(
_jsFreeRuntime(rt); _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) /// JSContext *jsNewContext(JSRuntime *rt)
final Pointer Function( final Pointer Function(
Pointer rt, Pointer rt,

View File

@@ -13,9 +13,6 @@ import 'package:ffi/ffi.dart';
import 'package:flutter_qjs/ffi.dart'; import 'package:flutter_qjs/ffi.dart';
import 'package:flutter_qjs/wrapper.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. /// Handler function to manage js module.
typedef JsModuleHandler = String Function(String name); typedef JsModuleHandler = String Function(String name);
@@ -32,9 +29,6 @@ class FlutterQjs {
/// Message Port for event loop. Close it to stop dispatching event loop. /// Message Port for event loop. Close it to stop dispatching event loop.
ReceivePort port = ReceivePort(); ReceivePort port = ReceivePort();
/// Handler function to manage js call with `channel(method, [...args])` function.
JsMethodHandler methodHandler;
/// Handler function to manage js module. /// Handler function to manage js module.
JsModuleHandler moduleHandler; JsModuleHandler moduleHandler;
@@ -44,55 +38,75 @@ class FlutterQjs {
/// Quickjs engine for flutter. /// Quickjs engine for flutter.
/// ///
/// Pass handlers to implement js-dart interaction and resolving modules. /// Pass handlers to implement js-dart interaction and resolving modules.
FlutterQjs( FlutterQjs({
{this.methodHandler,
this.moduleHandler, this.moduleHandler,
this.stackSize, 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() { _ensureEngine() {
if (_rt != null) return; if (_rt != null) return;
_rt = jsNewRuntime((ctx, method, argv) { _rt = jsNewRuntime((ctx, type, ptr) {
try { try {
if (method.address == 0) { switch (type) {
Pointer rt = ctx; case JSChannelType.METHON:
DartObject obj = DartObject.fromAddress(rt, argv.address); final pdata = ptr.cast<Pointer>();
obj?.release(); final argc = pdata.elementAt(1).value.cast<Int32>().value;
runtimeOpaques[rt]?.ref?.remove(obj); List args = [];
return Pointer.fromAddress(0); for (var i = 0; i < argc; i++) {
args.add(jsToDart(
ctx,
Pointer.fromAddress(
pdata.elementAt(2).value.address + sizeOfJSValue * i,
)));
} }
if (argv.address != 0) { final thisVal = jsToDart(ctx, pdata.elementAt(0).value);
if (method.address == ctx.address) { Function func = jsToDart(ctx, pdata.elementAt(3).value);
final errStr = parseJSException(ctx, perr: argv); 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) { if (hostPromiseRejectionHandler != null) {
hostPromiseRejectionHandler(errStr); hostPromiseRejectionHandler(errStr);
} else { } else {
print("unhandled promise rejection: $errStr"); print("unhandled promise rejection: $errStr");
} }
return Pointer.fromAddress(0); 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"); throw Exception("call channel with wrong type");
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;
} catch (e, stack) { } catch (e, stack) {
final errStr = e.toString() + "\n" + stack.toString(); final errStr = e.toString() + "\n" + stack.toString();
if (method.address == 0) { if (type == JSChannelType.FREE_OBJECT) {
print("DartObject release error: " + errStr); print("DartObject release error: " + errStr);
return Pointer.fromAddress(0); return Pointer.fromAddress(0);
} }
if (method.address == ctx.address) { if (type == JSChannelType.MODULE) {
print("host Promise Rejection Handler error: " + errStr); print("host Promise Rejection Handler error: " + errStr);
return Pointer.fromAddress(0); return Pointer.fromAddress(0);
} }
@@ -100,7 +114,7 @@ class FlutterQjs {
ctx, ctx,
errStr, errStr,
); );
if (argv.address == 0) { if (type == JSChannelType.MODULE) {
jsFreeValue(ctx, err); jsFreeValue(ctx, err);
return Pointer.fromAddress(0); return Pointer.fromAddress(0);
} }

View File

@@ -153,7 +153,6 @@ void _runJsIsolate(Map spawnMessage) async {
'reason': reason, 'reason': reason,
}); });
}, },
methodHandler: spawnMessage['handler'],
moduleHandler: (name) { moduleHandler: (name) {
var ptr = allocate<Pointer<Utf8>>(); var ptr = allocate<Pointer<Utf8>>();
ptr.value = Pointer.fromAddress(0); ptr.value = Pointer.fromAddress(0);
@@ -192,6 +191,9 @@ void _runJsIsolate(Map spawnMessage) async {
msg['val'], msg['val'],
).invoke(_decodeData(msg['args'], null)); ).invoke(_decodeData(msg['args'], null));
break; break;
case 'setToGlobalObject':
qjs.setToGlobalObject(msg['key'], msg['val']);
break;
case 'close': case 'close':
qjs.port.close(); qjs.port.close();
qjs.close(); qjs.close();
@@ -220,10 +222,6 @@ class IsolateQjs {
/// Max stack size for quickjs. /// Max stack size for quickjs.
final int stackSize; 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. /// Asynchronously handler to manage js module.
JsAsyncModuleHandler moduleHandler; JsAsyncModuleHandler moduleHandler;
@@ -235,7 +233,6 @@ class IsolateQjs {
/// Pass handlers to implement js-dart interaction and resolving modules. The `methodHandler` is /// 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**. /// used in isolate, so **the handler function must be a top-level function or a static method**.
IsolateQjs({ IsolateQjs({
this.methodHandler,
this.moduleHandler, this.moduleHandler,
this.stackSize, this.stackSize,
this.hostPromiseRejectionHandler, this.hostPromiseRejectionHandler,
@@ -248,7 +245,6 @@ class IsolateQjs {
_runJsIsolate, _runJsIsolate,
{ {
'port': port.sendPort, 'port': port.sendPort,
'handler': methodHandler,
'stackSize': stackSize, 'stackSize': stackSize,
}, },
errorsAreFatal: true, errorsAreFatal: true,
@@ -305,6 +301,23 @@ class IsolateQjs {
_sendPort = null; _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. /// Evaluate js script.
Future<dynamic> evaluate(String command, {String name, int evalFlags}) async { Future<dynamic> evaluate(String command, {String name, int evalFlags}) async {
_ensureEngine(); _ensureEngine();

View File

@@ -146,6 +146,26 @@ String parseJSException(Pointer ctx, {Pointer perr}) {
return err; 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}) { Pointer dartToJs(Pointer ctx, dynamic val, {Map<dynamic, dynamic> cache}) {
if (val == null) return jsUNDEFINED(); if (val == null) return jsUNDEFINED();
if (val is Future) { if (val is Future) {
@@ -188,17 +208,7 @@ Pointer dartToJs(Pointer ctx, dynamic val, {Map<dynamic, dynamic> cache}) {
Pointer ret = jsNewArray(ctx); Pointer ret = jsNewArray(ctx);
cache[val] = ret; cache[val] = ret;
for (int i = 0; i < val.length; ++i) { for (int i = 0; i < val.length; ++i) {
var jsAtomVal = jsNewInt64(ctx, i); definePropertyValue(ctx, ret, i, val[i], cache: cache);
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);
} }
return ret; return ret;
} }
@@ -206,28 +216,24 @@ Pointer dartToJs(Pointer ctx, dynamic val, {Map<dynamic, dynamic> cache}) {
Pointer ret = jsNewObject(ctx); Pointer ret = jsNewObject(ctx);
cache[val] = ret; cache[val] = ret;
for (MapEntry<dynamic, dynamic> entry in val.entries) { for (MapEntry<dynamic, dynamic> entry in val.entries) {
var jsAtomVal = dartToJs(ctx, entry.key, cache: cache); definePropertyValue(ctx, ret, entry.key, entry.value, 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);
} }
return ret; return ret;
} }
int dartObjectClassId = int dartObjectClassId =
runtimeOpaques[jsGetRuntime(ctx)]?.dartObjectClassId ?? 0; runtimeOpaques[jsGetRuntime(ctx)]?.dartObjectClassId ?? 0;
if (dartObjectClassId == 0) return jsUNDEFINED(); if (dartObjectClassId == 0) return jsUNDEFINED();
return jsNewObjectClass( var dartObject = jsNewObjectClass(
ctx, ctx,
dartObjectClassId, dartObjectClassId,
identityHashCode(DartObject(ctx, val)), 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}) { dynamic jsToDart(Pointer ctx, Pointer val, {Map<int, dynamic> cache}) {

View File

@@ -14,31 +14,32 @@ import 'package:flutter_qjs/flutter_qjs.dart';
import 'package:flutter_qjs/isolate.dart'; import 'package:flutter_qjs/isolate.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
dynamic myMethodHandler(method, args) { dynamic myMethodHandler(List args, {String thisVal}) {
return args; return [thisVal, ...args];
} }
Future testEvaluate(qjs) async { Future testEvaluate(qjs) async {
var value = await qjs.evaluate(""" final value = await qjs.evaluate("""
const a = {}; const a = {};
a.a = a; a.a = a;
import('test').then((module) => channel('channel', [ import('test').then((module) => channel.call('this', [
(...args)=>`hello \${args}!`, a, (...args)=>`hello \${args}!`, a,
Promise.reject('test Promise.reject'), Promise.resolve('test Promise.resolve'), Promise.reject('test Promise.reject'), Promise.resolve('test Promise.resolve'),
0.1, true, false, 1, "world", module 0.1, true, false, 1, "world", module
])); ]));
""", name: "<eval>"); """, name: "<eval>");
expect(await value[0]('world'), 'hello world!', reason: "js function call"); expect(value[0], 'this', reason: "js function this");
expect(value[1]['a'], value[1], reason: "recursive object"); expect(await value[1]('world'), 'hello world!', reason: "js function call");
expect(value[2], isInstanceOf<Future>(), reason: "promise object"); expect(value[2]['a'], value[2], reason: "recursive object");
expect(value[3], isInstanceOf<Future>(), reason: "promise object");
try { try {
await value[2]; await value[3];
throw 'Future not reject'; throw 'Future not reject';
} catch (e) { } catch (e) {
expect(e, startsWith('test Promise.reject\n'), expect(e, startsWith('test Promise.reject\n'),
reason: "promise object reject"); reason: "promise object reject");
} }
expect(await value[3], 'test Promise.resolve', expect(await value[4], 'test Promise.resolve',
reason: "promise object resolve"); reason: "promise object resolve");
} }
@@ -96,12 +97,12 @@ void main() async {
}); });
test('jsToDart', () async { test('jsToDart', () async {
final qjs = FlutterQjs( final qjs = FlutterQjs(
methodHandler: myMethodHandler,
moduleHandler: (name) { moduleHandler: (name) {
return "export default '${new DateTime.now()}'"; return "export default '${new DateTime.now()}'";
}, },
hostPromiseRejectionHandler: (_) {}, hostPromiseRejectionHandler: (_) {},
); );
qjs.setToGlobalObject("channel", myMethodHandler);
qjs.dispatch(); qjs.dispatch();
await testEvaluate(qjs); await testEvaluate(qjs);
qjs.close(); qjs.close();
@@ -109,12 +110,12 @@ void main() async {
test('isolate', () async { test('isolate', () async {
await runZonedGuarded(() async { await runZonedGuarded(() async {
final qjs = IsolateQjs( final qjs = IsolateQjs(
methodHandler: myMethodHandler,
moduleHandler: (name) async { moduleHandler: (name) async {
return "export default '${new DateTime.now()}'"; return "export default '${new DateTime.now()}'";
}, },
hostPromiseRejectionHandler: (_) {}, hostPromiseRejectionHandler: (_) {},
); );
await qjs.setToGlobalObject("channel", myMethodHandler);
await testEvaluate(qjs); await testEvaluate(qjs);
qjs.close(); qjs.close();
}, (e, stack) { }, (e, stack) {
@@ -122,13 +123,12 @@ void main() async {
}); });
}); });
test('dart object', () async { test('dart object', () async {
final qjs = FlutterQjs( final qjs = FlutterQjs();
methodHandler: (method, args) { qjs.setToGlobalObject("channel", () {
return FlutterQjs(); return FlutterQjs();
}, });
);
qjs.dispatch(); 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"); expect(value, isInstanceOf<FlutterQjs>(), reason: "dart object");
qjs.close(); qjs.close();
}); });