From e0000ac2d68493d4a6c60d2517aafd9ee1f99d32 Mon Sep 17 00:00:00 2001 From: ekibun Date: Tue, 25 Aug 2020 20:36:13 +0800 Subject: [PATCH] fix js memory leak. --- CHANGELOG.md | 6 +- android/build.gradle | 2 +- android/src/main/jni/CMakeLists.txt | 2 +- android/src/main/jni/java_js_wrapper.hpp | 28 ++++++--- android/src/main/jni/native-lib.cpp | 9 +-- .../ekibun/flutter_qjs/FlutterQjsPlugin.kt | 6 +- cxx/js_dart_promise.hpp | 61 ++++++++++++++++--- cxx/quickjs/quickjs.c | 6 ++ example/android/app/build.gradle | 2 +- example/lib/main.dart | 4 +- example/pubspec.lock | 2 +- example/windows/flutter/.template_version | 2 +- example/windows/flutter/CMakeLists.txt | 4 +- example/windows/runner/flutter_window.cpp | 18 ++++++ example/windows/runner/flutter_window.h | 6 +- example/windows/runner/win32_window.cpp | 7 --- lib/flutter_qjs.dart | 5 +- linux/CMakeLists.txt | 1 + linux/dart_js_wrapper.hpp | 26 ++++++-- linux/flutter_qjs_plugin.cc | 13 +++- pubspec.yaml | 2 +- windows/CMakeLists.txt | 1 + windows/dart_js_wrapper.hpp | 26 ++++++-- windows/flutter_qjs_plugin.cpp | 4 +- 24 files changed, 180 insertions(+), 63 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fee4d5..58b5fcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/android/build.gradle b/android/build.gradle index 7d22609..cb0ede2 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -32,7 +32,7 @@ android { main.java.srcDirs += 'src/main/kotlin' } defaultConfig { - minSdkVersion 21 + minSdkVersion 16 externalNativeBuild { cmake { cppFlags "-std=c++17" diff --git a/android/src/main/jni/CMakeLists.txt b/android/src/main/jni/CMakeLists.txt index c7ce02f..137e5a0 100644 --- a/android/src/main/jni/CMakeLists.txt +++ b/android/src/main/jni/CMakeLists.txt @@ -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 diff --git a/android/src/main/jni/java_js_wrapper.hpp b/android/src/main/jni/java_js_wrapper.hpp index 02ea3c6..0becc97 100644 --- a/android/src/main/jni/java_js_wrapper.hpp +++ b/android/src/main/jni/java_js_wrapper.hpp @@ -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 #include @@ -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 retMap; - retMap[env->NewStringUTF("__js_function__")] = jniWrapPrimity(env, (int64_t) new JSValue{JS_DupValue(val.ctx, val.v)}); + retMap[env->NewStringUTF("__js_function__")] = jniWrapPrimity(env, (int64_t) new JSValue{js_add_ref(val)}); return jniWrapMap(env, retMap); } else if (JS_IsArray(val.ctx, val.v) > 0) diff --git a/android/src/main/jni/native-lib.cpp b/android/src/main/jni/native-lib.cpp index 7cb91a9..42c3be6 100644 --- a/android/src/main/jni/native-lib.cpp +++ b/android/src/main/jni/native-lib.cpp @@ -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}; diff --git a/android/src/main/kotlin/soko/ekibun/flutter_qjs/FlutterQjsPlugin.kt b/android/src/main/kotlin/soko/ekibun/flutter_qjs/FlutterQjsPlugin.kt index 096708f..da55b5c 100644 --- a/android/src/main/kotlin/soko/ekibun/flutter_qjs/FlutterQjsPlugin.kt +++ b/android/src/main/kotlin/soko/ekibun/flutter_qjs/FlutterQjsPlugin.kt @@ -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("name")!! JniBridge.instance.evaluate(engine, script, name, ResultWrapper(handler, result)) } else if (call.method == "call") { - println(call.arguments>()); val engine: Long = call.argument("engine")!! val function: Long = call.argument("function")!! val args: List = call.argument>("arguments")!! JniBridge.instance.call(engine, function, args, ResultWrapper(handler, result)) } else if (call.method == "close") { - val engine: Long = call.argument("engine")!! + val engine: Long = call.arguments() + println(engine) JniBridge.instance.close(engine) result.success(null) } else { diff --git a/cxx/js_dart_promise.hpp b/cxx/js_dart_promise.hpp index d96c373..672d8fc 100644 --- a/cxx/js_dart_promise.hpp +++ b/cxx/js_dart_promise.hpp @@ -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 *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; diff --git a/cxx/quickjs/quickjs.c b/cxx/quickjs/quickjs.c index a2a7a2b..441e2c5 100644 --- a/cxx/quickjs/quickjs.c +++ b/cxx/quickjs/quickjs.c @@ -37,6 +37,12 @@ #include #endif +#if defined(__ANDROID__) +#include +#undef printf +#define printf(...) __android_log_print(ANDROID_LOG_INFO, "qjs", __VA_ARGS__) +#endif + #ifdef _MSC_VER #include diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 79016cd..8917303 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -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 diff --git a/example/lib/main.dart b/example/lib/main.dart index ed8c715..8a29df7 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -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 { FlatButton( child: Text("close engine"), onPressed: () async { - if (engine != null) return; + if (engine == null) return; await engine.destroy(); engine = null; }), diff --git a/example/pubspec.lock b/example/pubspec.lock index 20671eb..053897a 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -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 diff --git a/example/windows/flutter/.template_version b/example/windows/flutter/.template_version index 7ed6ff8..1e8b314 100644 --- a/example/windows/flutter/.template_version +++ b/example/windows/flutter/.template_version @@ -1 +1 @@ -5 +6 diff --git a/example/windows/flutter/CMakeLists.txt b/example/windows/flutter/CMakeLists.txt index ff47d32..c7a8c76 100644 --- a/example/windows/flutter/CMakeLists.txt +++ b/example/windows/flutter/CMakeLists.txt @@ -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 diff --git a/example/windows/runner/flutter_window.cpp b/example/windows/runner/flutter_window.cpp index f3661dc..87823d5 100644 --- a/example/windows/runner/flutter_window.cpp +++ b/example/windows/runner/flutter_window.cpp @@ -1,5 +1,7 @@ #include "flutter_window.h" +#include + #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 result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/example/windows/runner/flutter_window.h b/example/windows/runner/flutter_window.h index 7f3162f..7fcd130 100644 --- a/example/windows/runner/flutter_window.h +++ b/example/windows/runner/flutter_window.h @@ -4,11 +4,11 @@ #include #include +#include + #include "run_loop.h" #include "win32_window.h" -#include - // 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. diff --git a/example/windows/runner/win32_window.cpp b/example/windows/runner/win32_window.cpp index e7607ef..19fb9e0 100644 --- a/example/windows/runner/win32_window.cpp +++ b/example/windows/runner/win32_window.cpp @@ -154,13 +154,6 @@ Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { - auto window = - reinterpret_cast(GetWindowLongPtr(hwnd, GWLP_USERDATA)); - - if (window == nullptr) { - return 0; - } - switch (message) { case WM_DESTROY: window_handle_ = nullptr; diff --git a/lib/flutter_qjs.dart b/lib/flutter_qjs.dart index ed7f03a..01a5cc7 100644 --- a/lib/flutter_qjs.dart +++ b/lib/flutter_qjs.dart @@ -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 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(); _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); diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index 133f45e..805d99b 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -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) diff --git a/linux/dart_js_wrapper.hpp b/linux/dart_js_wrapper.hpp index 75e48e4..8af5517 100644 --- a/linux/dart_js_wrapper.hpp +++ b/linux/dart_js_wrapper.hpp @@ -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 @@ -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) diff --git a/linux/flutter_qjs_plugin.cc b/linux/flutter_qjs_plugin.cc index ccb3377..3c80979 100644 --- a/linux/flutter_qjs_plugin.cc +++ b/linux/flutter_qjs_plugin.cc @@ -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()); diff --git a/pubspec.yaml b/pubspec.yaml index efa57d5..badaf17 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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: diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 164091c..cb7fa15 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -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) diff --git a/windows/dart_js_wrapper.hpp b/windows/dart_js_wrapper.hpp index da45ef9..990d004 100644 --- a/windows/dart_js_wrapper.hpp +++ b/windows/dart_js_wrapper.hpp @@ -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 @@ -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(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(val)) @@ -67,9 +76,16 @@ namespace qjs auto map = std::get(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) diff --git a/windows/flutter_qjs_plugin.cpp b/windows/flutter_qjs_plugin.cpp index 22f60b9..058775e 100644 --- a/windows/flutter_qjs_plugin.cpp +++ b/windows/flutter_qjs_plugin.cpp @@ -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};