fix js memory leak.

This commit is contained in:
ekibun
2020-08-25 20:36:13 +08:00
parent 204f2411e5
commit e0000ac2d6
24 changed files with 180 additions and 63 deletions

View File

@@ -3,8 +3,12 @@
* @Author: ekibun
* @Date: 2020-08-08 08:16:50
* @LastEditors: ekibun
* @LastEditTime: 2020-08-20 14:55:13
* @LastEditTime: 2020-08-25 18:12:51
-->
## 0.0.3
* fix js memory leak.
## 0.0.2
* update example.

View File

@@ -32,7 +32,7 @@ android {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
minSdkVersion 21
minSdkVersion 16
externalNativeBuild {
cmake {
cppFlags "-std=c++17"

View File

@@ -23,7 +23,7 @@ add_library( # Sets the name of the library.
# quickjs
set(QUICK_JS_LIB_DIR ../../../../cxx/quickjs)
file (STRINGS "${QUICK_JS_LIB_DIR}/VERSION" QUICKJS_VERSION)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DCONFIG_VERSION=\\\"${QUICKJS_VERSION}\\\"")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DDUMP_LEAKS -DCONFIG_VERSION=\\\"${QUICKJS_VERSION}\\\"")
target_sources(${JNI_LIB_NAME} PUBLIC
${QUICK_JS_LIB_DIR}/cutils.c
${QUICK_JS_LIB_DIR}/libregexp.c

View File

@@ -3,7 +3,7 @@
* @Author: ekibun
* @Date: 2020-08-16 11:08:23
* @LastEditors: ekibun
* @LastEditTime: 2020-08-20 13:09:08
* @LastEditTime: 2020-08-25 18:06:08
*/
#include <string>
#include <unordered_map>
@@ -45,10 +45,15 @@ namespace qjs
JSValue array = JS_NewArray(ctx);
auto buf = env->GetDoubleArrayElements((jdoubleArray)val, 0);
for (uint32_t i = 0; i < size; i++)
{
auto atom = JS_NewAtomUInt32(ctx, i);
JS_DefinePropertyValue(
ctx, array, JS_NewAtomUInt32(ctx, i),
ctx, array, atom,
JS_NewFloat64(ctx, buf[i]),
JS_PROP_C_W_E);
JS_FreeAtom(ctx, atom);
}
return array;
}
else if (className.compare("java.lang.Boolean") == 0)
@@ -82,10 +87,15 @@ namespace qjs
JSValue array = JS_NewArray(ctx);
cache[val] = array;
for (uint32_t i = 0; i < size; i++)
{
auto atom = JS_NewAtomUInt32(ctx, i);
JS_DefinePropertyValue(
ctx, array, JS_NewAtomUInt32(ctx, i),
javaToJs(ctx, env, env->GetObjectArrayElement(list, i), cache),
JS_PROP_C_W_E);
ctx, array, atom,
javaToJs(ctx, env, env->GetObjectArrayElement(list, i), cache),
JS_PROP_C_W_E);
JS_FreeAtom(ctx, atom);
}
return array;
}
else if (className.compare("java.util.HashMap") == 0)
@@ -119,10 +129,14 @@ namespace qjs
{
// 读取一条数据
jobject entryObj = env->CallObjectMethod(iteratorObj, nextMID);
auto atomvalue = javaToJs(ctx, env, env->CallObjectMethod(entryObj, getKeyMID), cache);
auto atom = JS_ValueToAtom(ctx, atomvalue);
JS_DefinePropertyValue(
ctx, obj, JS_ValueToAtom(ctx, javaToJs(ctx, env, env->CallObjectMethod(entryObj, getKeyMID), cache)),
ctx, obj, atom,
javaToJs(ctx, env, env->CallObjectMethod(entryObj, getValueMID), cache),
JS_PROP_C_W_E);
JS_FreeAtom(ctx, atom);
JS_FreeValue(ctx, atomvalue);
}
return obj;
}
@@ -158,7 +172,7 @@ namespace qjs
if (JS_IsFunction(val.ctx, val.v))
{
std::map<jobject, jobject> retMap;
retMap[env->NewStringUTF("__js_function__")] = jniWrapPrimity<jlong>(env, (int64_t) new JSValue{JS_DupValue(val.ctx, val.v)});
retMap[env->NewStringUTF("__js_function__")] = jniWrapPrimity<jlong>(env, (int64_t) new JSValue{js_add_ref(val)});
return jniWrapMap(env, retMap);
}
else if (JS_IsArray(val.ctx, val.v) > 0)

View File

@@ -3,9 +3,10 @@
* @Author: ekibun
* @Date: 2020-08-09 18:16:11
* @LastEditors: ekibun
* @LastEditTime: 2020-08-16 19:00:06
* @LastEditTime: 2020-08-25 16:00:46
*/
#include "java_js_wrapper.hpp"
#include "android/log.h"
JNIEnv *getEnv(JavaVM *gJvm)
{
@@ -109,7 +110,7 @@ Java_soko_ekibun_flutter_1qjs_JniBridge_close(
jobject thiz,
jlong engine)
{
delete (qjs::Engine *)engine;
delete ((qjs::Engine *)engine);
}
extern "C" JNIEXPORT void JNICALL
@@ -136,8 +137,8 @@ Java_soko_ekibun_flutter_1qjs_JniBridge_call(
callargs[i] = qjs::javaToJs(ctx.ctx, env, env->GetObjectArrayElement(array, i));
}
jvm->DetachCurrentThread();
qjs::JSValue ret = JS_Call(ctx.ctx, *function, ctx.global(), (int)argscount, callargs);
qjs::JS_FreeValue(ctx.ctx, *function);
qjs::JSValue ret = qjs::call_handler(ctx.ctx, *function, (int)argscount, callargs);
delete[] callargs;
if (qjs::JS_IsException(ret))
throw qjs::exception{};
return qjs::Value{ctx.ctx, ret};

View File

@@ -21,7 +21,7 @@ class FlutterQjsPlugin: FlutterPlugin, MethodCallHandler {
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
applicationContext = flutterPluginBinding.applicationContext
val channel = MethodChannel(flutterPluginBinding.binaryMessenger, "soko.ekibun.flutter_qjs")
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "soko.ekibun.flutter_qjs")
channel.setMethodCallHandler(this)
channelwrapper = MethodChannelWrapper(handler, channel)
}
@@ -37,13 +37,13 @@ class FlutterQjsPlugin: FlutterPlugin, MethodCallHandler {
val name: String = call.argument<String>("name")!!
JniBridge.instance.evaluate(engine, script, name, ResultWrapper(handler, result))
} else if (call.method == "call") {
println(call.arguments<Map<*, *>>());
val engine: Long = call.argument<Long>("engine")!!
val function: Long = call.argument<Long>("function")!!
val args: List<Any> = call.argument<List<Any>>("arguments")!!
JniBridge.instance.call(engine, function, args, ResultWrapper(handler, result))
} else if (call.method == "close") {
val engine: Long = call.argument<Long>("engine")!!
val engine: Long = call.arguments<Long>()
println(engine)
JniBridge.instance.close(engine)
result.success(null)
} else {

View File

@@ -3,7 +3,7 @@
* @Author: ekibun
* @Date: 2020-08-07 13:55:52
* @LastEditors: ekibun
* @LastEditTime: 2020-08-20 13:09:52
* @LastEditTime: 2020-08-25 16:07:29
*/
#pragma once
#include "quickjs/quickjspp.hpp"
@@ -17,7 +17,7 @@ namespace std
{
size_t operator()(const qjs::Value &key) const
{
return (size_t) JS_VALUE_GET_PTR(key.v);
return (size_t)JS_VALUE_GET_PTR(key.v);
}
};
} // namespace std
@@ -46,12 +46,30 @@ namespace qjs
JSValue reject;
} JSOSFuture;
typedef struct
{
struct list_head link;
JSValue ref;
} JSOSRef;
typedef struct JSThreadState
{
struct list_head os_future; /* list of JSOSFuture.link */
struct list_head os_future;
struct list_head os_ref;
DartChannel channel;
} JSThreadState;
JSValue js_add_ref(Value val)
{
JSRuntime *rt = JS_GetRuntime(val.ctx);
JSThreadState *ts = (JSThreadState *)JS_GetRuntimeOpaque(rt);
JSOSRef *th;
th = (JSOSRef *)js_mallocz(val.ctx, sizeof(*th));
th->ref = JS_DupValue(val.ctx, val.v);
list_add_tail(&th->link, &ts->os_ref);
return th->ref;
}
static JSValue js_add_future(Value resolve, Value reject, std::promise<JSFutureReturn> *promise)
{
JSRuntime *rt = JS_GetRuntime(resolve.ctx);
@@ -91,6 +109,21 @@ namespace qjs
return js_add_future(resolve, reject, ts->channel(name, args));
}
static void unlink_ref(JSRuntime *rt, JSOSRef *th)
{
if (th->link.prev)
{
list_del(&th->link);
th->link.prev = th->link.next = NULL;
}
}
static void free_ref(JSRuntime *rt, JSOSRef *th)
{
JS_FreeValueRT(rt, th->ref);
js_free_rt(rt, th);
}
static void unlink_future(JSRuntime *rt, JSOSFuture *th)
{
if (th->link.prev)
@@ -117,6 +150,7 @@ namespace qjs
}
memset(ts, 0, sizeof(*ts));
init_list_head(&ts->os_future);
init_list_head(&ts->os_ref);
ts->channel = channel;
JS_SetRuntimeOpaque(rt, ts);
@@ -135,12 +169,18 @@ namespace qjs
unlink_future(rt, th);
free_future(rt, th);
}
list_for_each_safe(el, el1, &ts->os_ref)
{
JSOSRef *th = list_entry(el, JSOSRef, link);
unlink_ref(rt, th);
free_ref(rt, th);
}
ts->channel = nullptr;
free(ts);
JS_SetRuntimeOpaque(rt, NULL); /* fail safe */
}
static void call_handler(JSContext *ctx, JSValueConst func, int count, JSValue *argv)
static JSValue call_handler(JSContext *ctx, JSValueConst func, int count, JSValue *argv)
{
JSValue ret, func1;
/* 'func' might be destroyed when calling itself (if it frees the
@@ -148,9 +188,9 @@ namespace qjs
func1 = JS_DupValue(ctx, func);
ret = JS_Call(ctx, func1, JS_UNDEFINED, count, argv);
JS_FreeValue(ctx, func1);
if (JS_IsException(ret))
throw exception{};
JS_FreeValue(ctx, ret);
for (int i = 0; i < count; ++i)
JS_FreeValue(ctx, argv[i]);
return ret;
}
static int js_dart_poll(JSContext *ctx)
@@ -182,9 +222,10 @@ namespace qjs
th->reject = JS_UNDEFINED;
unlink_future(rt, th);
free_future(rt, th);
call_handler(ctx, argv.count < 0 ? reject : resolve, abs(argv.count), argv.argv);
for (int i = 0; i < abs(argv.count); ++i)
JS_FreeValue(ctx, argv.argv[i]);
JSValue ret = call_handler(ctx, argv.count < 0 ? reject : resolve, abs(argv.count), argv.argv);
if (qjs::JS_IsException(ret))
throw qjs::exception{};
JS_FreeValue(ctx, ret);
JS_FreeValue(ctx, resolve);
JS_FreeValue(ctx, reject);
delete argv.argv;

View File

@@ -37,6 +37,12 @@
#include <malloc.h>
#endif
#if defined(__ANDROID__)
#include <android/log.h>
#undef printf
#define printf(...) __android_log_print(ANDROID_LOG_INFO, "qjs", __VA_ARGS__)
#endif
#ifdef _MSC_VER
#include <WinSock2.h>

View File

@@ -39,7 +39,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "soko.ekibun.example"
minSdkVersion 21
minSdkVersion 16
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName

View File

@@ -3,7 +3,7 @@
* @Author: ekibun
* @Date: 2020-08-08 08:16:51
* @LastEditors: ekibun
* @LastEditTime: 2020-08-20 14:42:10
* @LastEditTime: 2020-08-24 22:26:03
*/
import 'package:flutter/material.dart';
import 'dart:typed_data';
@@ -109,7 +109,7 @@ class _TestPageState extends State<TestPage> {
FlatButton(
child: Text("close engine"),
onPressed: () async {
if (engine != null) return;
if (engine == null) return;
await engine.destroy();
engine = null;
}),

View File

@@ -75,7 +75,7 @@ packages:
path: ".."
relative: true
source: path
version: "0.0.2"
version: "0.0.3"
flutter_test:
dependency: "direct dev"
description: flutter

View File

@@ -1 +1 @@
5
6

View File

@@ -34,8 +34,8 @@ add_dependencies(flutter flutter_assemble)
# === Wrapper ===
list(APPEND CPP_WRAPPER_SOURCES_CORE
"engine_method_result.cc"
"standard_codec.cc"
"core_implementations.cc"
"standard_codec.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_PLUGIN

View File

@@ -1,5 +1,7 @@
#include "flutter_window.h"
#include <optional>
#include "flutter/generated_plugin_registrant.h"
FlutterWindow::FlutterWindow(RunLoop* run_loop,
@@ -34,3 +36,19 @@ void FlutterWindow::OnDestroy() {
Win32Window::OnDestroy();
}
LRESULT
FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
// Give Flutter, including plugins, an opporutunity to handle window messages.
if (flutter_controller_) {
std::optional<LRESULT> result =
flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
lparam);
if (result) {
return *result;
}
}
return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
}

View File

@@ -4,11 +4,11 @@
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <memory>
#include "run_loop.h"
#include "win32_window.h"
#include <memory>
// A window that does nothing but host a Flutter view.
class FlutterWindow : public Win32Window {
public:
@@ -22,6 +22,8 @@ class FlutterWindow : public Win32Window {
// Win32Window:
bool OnCreate() override;
void OnDestroy() override;
LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
LPARAM const lparam) noexcept override;
private:
// The run loop driving events for this window.

View File

@@ -154,13 +154,6 @@ Win32Window::MessageHandler(HWND hwnd,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
auto window =
reinterpret_cast<Win32Window*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
if (window == nullptr) {
return 0;
}
switch (message) {
case WM_DESTROY:
window_handle_ = nullptr;

View File

@@ -38,14 +38,14 @@ class FlutterJs {
destroy() async {
if (_engine != null) {
await _FlutterJs.instance._channel
.invokeMethod("close", {"engine": _engine});
.invokeMethod("close", _engine);
_engine = null;
}
}
/// Evaluate js script.
Future<dynamic> evaluate(String command, String name) async {
_ensureEngine();
await _ensureEngine();
var arguments = {"engine": _engine, "script": command, "name": name};
return _FlutterJs.instance._wrapFunctionArguments(
await _FlutterJs.instance._channel.invokeMethod("evaluate", arguments),
@@ -62,7 +62,6 @@ class _FlutterJs {
Map<dynamic, JsMethodHandler>();
_FlutterJs._internal() {
_channel.setMethodCallHandler((call) async {
print(call.arguments);
var engine = call.arguments["engine"];
var args = call.arguments["args"];
if (methodHandlers[engine] == null) return call.noSuchMethod(null);

View File

@@ -10,6 +10,7 @@ add_library(libquickjs SHARED
)
project(libquickjs LANGUAGES C)
target_compile_options(libquickjs PRIVATE "-DCONFIG_VERSION=\"${QUICKJS_VERSION}\"")
target_compile_options(libquickjs PRIVATE "-DDUMP_LEAKS")
set(PROJECT_NAME "flutter_qjs")
project(${PROJECT_NAME} LANGUAGES CXX)

View File

@@ -3,7 +3,7 @@
* @Author: ekibun
* @Date: 2020-08-14 21:45:02
* @LastEditors: ekibun
* @LastEditTime: 2020-08-20 13:09:21
* @LastEditTime: 2020-08-25 18:11:19
*/
#include "../cxx/js_engine.hpp"
#include <flutter_linux/flutter_linux.h>
@@ -37,9 +37,14 @@ namespace qjs
auto size = (uint32_t)fl_value_get_length(val);
JSValue array = JS_NewArray(ctx);
for (uint32_t i = 0; i < size; ++i)
{
auto atom = JS_NewAtomUInt32(ctx, i);
JS_DefinePropertyValue(
ctx, array, JS_NewAtomUInt32(ctx, i), JS_NewFloat64(ctx, buf[i]),
ctx, array, atom, JS_NewFloat64(ctx, buf[i]),
JS_PROP_C_W_E);
JS_FreeAtom(ctx, atom);
}
return array;
}
case FL_VALUE_TYPE_LIST:
@@ -47,10 +52,14 @@ namespace qjs
auto size = (uint32_t)fl_value_get_length(val);
JSValue array = JS_NewArray(ctx);
for (uint32_t i = 0; i < size; ++i)
{
auto atom = JS_NewAtomUInt32(ctx, i);
JS_DefinePropertyValue(
ctx, array, JS_NewAtomUInt32(ctx, i),
ctx, array, atom,
dartToJs(ctx, fl_value_get_list_value(val, i)),
JS_PROP_C_W_E);
JS_FreeAtom(ctx, atom);
}
return array;
}
case FL_VALUE_TYPE_MAP:
@@ -58,11 +67,16 @@ namespace qjs
auto size = (uint32_t)fl_value_get_length(val);
JSValue obj = JS_NewObject(ctx);
for (uint32_t i = 0; i < size; ++i)
{
auto atomvalue = dartToJs(ctx, fl_value_get_map_key(val, i));
auto atom = JS_ValueToAtom(ctx, atomvalue);
JS_DefinePropertyValue(
ctx, obj,
JS_ValueToAtom(ctx, dartToJs(ctx, fl_value_get_map_key(val, i))),
ctx, obj, atom,
dartToJs(ctx, fl_value_get_map_value(val, i)),
JS_PROP_C_W_E);
JS_FreeAtom(ctx, atom);
JS_FreeValue(ctx, atomvalue);
}
return obj;
}
default:
@@ -93,7 +107,7 @@ namespace qjs
if (JS_IsFunction(val.ctx, val.v))
{
FlValue *retMap = fl_value_new_map();
fl_value_set_string_take(retMap, "__js_function__", fl_value_new_int((int64_t) new JSValue{JS_DupValue(val.ctx, val.v)}));
fl_value_set_string_take(retMap, "__js_function__", fl_value_new_int((int64_t) new JSValue{js_add_ref(val)}));
return retMap;
}
else if (JS_IsArray(val.ctx, val.v) > 0)

View File

@@ -3,7 +3,7 @@
* @Author: ekibun
* @Date: 2020-08-17 21:37:11
* @LastEditors: ekibun
* @LastEditTime: 2020-08-18 23:22:08
* @LastEditTime: 2020-08-25 16:07:02
*/
#include "include/flutter_qjs/flutter_qjs_plugin.h"
@@ -112,8 +112,8 @@ static void flutter_qjs_plugin_handle_method_call(
{
callargs[i] = qjs::dartToJs(ctx.ctx, fl_value_get_list_value(arguments, i));
}
qjs::JSValue ret = JS_Call(ctx.ctx, *function, qjs::JSValue{qjs::JSValueUnion{0}, qjs::JS_TAG_UNDEFINED}, (int)argscount, callargs);
qjs::JS_FreeValue(ctx.ctx, *function);
qjs::JSValue ret = qjs::call_handler(ctx.ctx, *function, (int)argscount, callargs);
delete[] callargs;
if (qjs::JS_IsException(ret))
throw qjs::exception{};
return qjs::Value{ctx.ctx, ret};
@@ -128,6 +128,13 @@ static void flutter_qjs_plugin_handle_method_call(
g_object_unref(pmethod_call);
}});
}
else if (strcmp(method, "close") == 0)
{
qjs::Engine *engine = (qjs::Engine *)fl_value_get_int(fl_method_call_get_args(method_call));
delete engine;
g_autoptr(FlMethodResponse) response = FL_METHOD_RESPONSE(fl_method_success_response_new(fl_value_new_null()));
fl_method_call_respond(method_call, response, nullptr);
}
else
{
g_autoptr(FlMethodResponse) response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());

View File

@@ -1,6 +1,6 @@
name: flutter_qjs
description: This plugin is a simple js engine for flutter using the `quickjs` project. Plugin currently supports Windows, Linux, and Android.
version: 0.0.2
version: 0.0.3
homepage: https://github.com/ekibun/flutter_qjs
environment:

View File

@@ -11,6 +11,7 @@ add_library(libquickjs STATIC
)
project(libquickjs LANGUAGES C)
target_compile_options(libquickjs PRIVATE "-DCONFIG_VERSION=\"${QUICKJS_VERSION}\"")
target_compile_options(libquickjs PRIVATE "-DDUMP_LEAKS")
set(PROJECT_NAME "flutter_qjs")
project(${PROJECT_NAME} LANGUAGES CXX)

View File

@@ -3,7 +3,7 @@
* @Author: ekibun
* @Date: 2020-08-14 21:45:02
* @LastEditors: ekibun
* @LastEditTime: 2020-08-20 13:06:24
* @LastEditTime: 2020-08-25 18:08:45
*/
#include "../cxx/js_engine.hpp"
#include <flutter/standard_method_codec.h>
@@ -46,9 +46,13 @@ namespace qjs
JSValue array = JS_NewArray(ctx);
auto size = (uint32_t)buf.size();
for (uint32_t i = 0; i < size; ++i)
{
auto atom = JS_NewAtomUInt32(ctx, i);
JS_DefinePropertyValue(
ctx, array, JS_NewAtomUInt32(ctx, i), JS_NewFloat64(ctx, buf[i]),
ctx, array, atom, JS_NewFloat64(ctx, buf[i]),
JS_PROP_C_W_E);
JS_FreeAtom(ctx, atom);
}
return array;
}
if (std::holds_alternative<flutter::EncodableList>(val))
@@ -57,9 +61,14 @@ namespace qjs
JSValue array = JS_NewArray(ctx);
auto size = (uint32_t)list.size();
for (uint32_t i = 0; i < size; i++)
{
auto atom = JS_NewAtomUInt32(ctx, i);
JS_DefinePropertyValue(
ctx, array, JS_NewAtomUInt32(ctx, i), dartToJs(ctx, list[i]),
ctx, array, atom, dartToJs(ctx, list[i]),
JS_PROP_C_W_E);
JS_FreeAtom(ctx, atom);
}
return array;
}
if (std::holds_alternative<flutter::EncodableMap>(val))
@@ -67,9 +76,16 @@ namespace qjs
auto map = std::get<flutter::EncodableMap>(val);
JSValue obj = JS_NewObject(ctx);
for (auto iter = map.begin(); iter != map.end(); ++iter)
{
auto atomvalue = dartToJs(ctx, iter->first);
auto atom = JS_ValueToAtom(ctx, atomvalue);
JS_DefinePropertyValue(
ctx, obj, JS_ValueToAtom(ctx, dartToJs(ctx, iter->first)), dartToJs(ctx, iter->second),
ctx, obj, atom, dartToJs(ctx, iter->second),
JS_PROP_C_W_E);
JS_FreeAtom(ctx, atom);
JS_FreeValue(ctx, atomvalue);
}
return obj;
}
return JS_UNDEFINED;
@@ -100,7 +116,7 @@ namespace qjs
if (JS_IsFunction(val.ctx, val.v))
{
flutter::EncodableMap retMap;
retMap[std::string("__js_function__")] = (int64_t) new JSValue{JS_DupValue(val.ctx, val.v)};
retMap[std::string("__js_function__")] = (int64_t) new JSValue{js_add_ref(val)};
return retMap;
}
else if (JS_IsArray(val.ctx, val.v) > 0)

View File

@@ -151,8 +151,8 @@ namespace
{
callargs[i] = qjs::dartToJs(ctx.ctx, arguments[i]);
}
qjs::JSValue ret = JS_Call(ctx.ctx, *function, ctx.global().v, (int)argscount, callargs);
qjs::JS_FreeValue(ctx.ctx, *function);
qjs::JSValue ret = qjs::call_handler(ctx.ctx, *function, (int)argscount, callargs);
delete[] callargs;
if (qjs::JS_IsException(ret))
throw qjs::exception{};
return qjs::Value{ctx.ctx, ret};