Line data Source code
1 : /*
2 : * @Description: quickjs engine
3 : * @Author: ekibun
4 : * @Date: 2020-08-08 08:29:09
5 : * @LastEditors: ekibun
6 : * @LastEditTime: 2020-10-06 23:47:13
7 : */
8 : part of '../flutter_qjs.dart';
9 :
10 : /// Handler function to manage js module.
11 : typedef _JsModuleHandler = String Function(String name);
12 :
13 : /// Handler to manage unhandled promise rejection.
14 : typedef _JsHostPromiseRejectionHandler = void Function(dynamic reason);
15 :
16 : /// Quickjs engine for flutter.
17 : class FlutterQjs {
18 : Pointer<JSRuntime>? _rt;
19 : Pointer<JSContext>? _ctx;
20 :
21 : /// Max stack size for quickjs.
22 : final int? stackSize;
23 :
24 : /// Message Port for event loop. Close it to stop dispatching event loop.
25 : ReceivePort port = ReceivePort();
26 :
27 : /// Handler function to manage js module.
28 : final _JsModuleHandler? moduleHandler;
29 :
30 : /// Handler function to manage js module.
31 : final _JsHostPromiseRejectionHandler? hostPromiseRejectionHandler;
32 :
33 3 : FlutterQjs({
34 : this.moduleHandler,
35 : this.stackSize,
36 : this.hostPromiseRejectionHandler,
37 : });
38 :
39 3 : _ensureEngine() {
40 3 : if (_rt != null) return;
41 6 : final rt = jsNewRuntime((ctx, type, ptr) {
42 : try {
43 : switch (type) {
44 3 : case JSChannelType.METHON:
45 3 : final pdata = ptr.cast<Pointer<JSValue>>();
46 9 : final argc = pdata.elementAt(1).value.cast<Int32>().value;
47 3 : final pargs = [];
48 6 : for (var i = 0; i < argc; ++i) {
49 6 : pargs.add(_jsToDart(
50 : ctx,
51 3 : Pointer.fromAddress(
52 15 : pdata.elementAt(2).value.address + sizeOfJSValue * i,
53 : ),
54 : ));
55 : }
56 3 : final JSInvokable func = _jsToDart(
57 : ctx,
58 3 : pdata.elementAt(3).value,
59 : );
60 3 : return _dartToJs(
61 : ctx,
62 3 : func.invoke(
63 : pargs,
64 6 : _jsToDart(ctx, pdata.elementAt(0).value),
65 : ));
66 3 : case JSChannelType.MODULE:
67 1 : if (moduleHandler == null) throw JSError('No ModuleHandler');
68 2 : final ret = moduleHandler!(
69 2 : ptr.cast<Utf8>().toDartString(),
70 1 : ).toNativeUtf8();
71 2 : Future.microtask(() {
72 1 : malloc.free(ret);
73 : });
74 1 : return ret.cast();
75 3 : case JSChannelType.PROMISE_TRACK:
76 2 : final err = _parseJSException(ctx, ptr);
77 2 : if (hostPromiseRejectionHandler != null) {
78 4 : hostPromiseRejectionHandler!(err);
79 : } else {
80 0 : print('unhandled promise rejection: $err');
81 : }
82 2 : return nullptr;
83 3 : case JSChannelType.FREE_OBJECT:
84 3 : final rt = ctx.cast<JSRuntime>();
85 9 : _DartObject.fromAddress(rt, ptr.address)?.free();
86 3 : return nullptr;
87 : }
88 0 : throw JSError('call channel with wrong type');
89 : } catch (e) {
90 0 : if (type == JSChannelType.FREE_OBJECT) {
91 0 : print('DartObject release error: $e');
92 0 : return nullptr;
93 : }
94 0 : if (type == JSChannelType.MODULE) {
95 0 : print('host Promise Rejection Handler error: $e');
96 0 : return nullptr;
97 : }
98 0 : final throwObj = _dartToJs(ctx, e);
99 0 : final err = jsThrow(ctx, throwObj);
100 0 : jsFreeValue(ctx, throwObj);
101 0 : if (type == JSChannelType.MODULE) {
102 0 : jsFreeValue(ctx, err);
103 0 : return nullptr;
104 : }
105 : return err;
106 : }
107 3 : }, port);
108 3 : final stackSize = this.stackSize ?? 0;
109 3 : if (stackSize > 0) jsSetMaxStackSize(rt, stackSize);
110 3 : _rt = rt;
111 6 : _ctx = jsNewContext(rt);
112 : }
113 :
114 : /// Free Runtime and Context which can be recreate when evaluate again.
115 3 : close() {
116 3 : final rt = _rt;
117 3 : final ctx = _ctx;
118 3 : _rt = null;
119 3 : _ctx = null;
120 6 : if (ctx != null) jsFreeContext(ctx);
121 : if (rt == null) return;
122 3 : _executePendingJob();
123 : try {
124 3 : jsFreeRuntime(rt);
125 1 : } on String catch (e) {
126 1 : throw JSError(e);
127 : }
128 : }
129 :
130 3 : void _executePendingJob() {
131 3 : final rt = _rt;
132 3 : final ctx = _ctx;
133 : if (rt == null || ctx == null) return;
134 : while (true) {
135 6 : int err = jsExecutePendingJob(rt);
136 3 : if (err <= 0) {
137 3 : if (err < 0) print(_parseJSException(ctx));
138 : break;
139 : }
140 : }
141 : }
142 :
143 : /// Dispatch JavaScript Event loop.
144 3 : Future<void> dispatch() async {
145 9 : await for (final _ in port) {
146 3 : _executePendingJob();
147 : }
148 : }
149 :
150 : /// Evaluate js script.
151 3 : dynamic evaluate(
152 : String command, {
153 : String? name,
154 : int? evalFlags,
155 : }) {
156 3 : _ensureEngine();
157 3 : final ctx = _ctx!;
158 3 : final jsval = jsEval(
159 : ctx,
160 : command,
161 : name ?? '<eval>',
162 : evalFlags ?? JSEvalFlag.GLOBAL,
163 : );
164 9 : if (jsIsException(jsval) != 0) {
165 1 : jsFreeValue(ctx, jsval);
166 1 : throw _parseJSException(ctx);
167 : }
168 3 : final result = _jsToDart(ctx, jsval);
169 3 : jsFreeValue(ctx, jsval);
170 : return result;
171 : }
172 : }
|