diff --git a/src/RJSRealm.mm b/src/RJSRealm.mm index aed02df9..0efcd38b 100644 --- a/src/RJSRealm.mm +++ b/src/RJSRealm.mm @@ -346,7 +346,7 @@ JSValueRef RealmWrite(JSContextRef ctx, JSObjectRef function, JSObjectRef thisOb try { RJSValidateArgumentCount(argumentCount, 1); - JSObjectRef object = RJSValidatedValueToObject(ctx, arguments[0]); + JSObjectRef object = RJSValidatedValueToFunction(ctx, arguments[0]); SharedRealm realm = *RJSGetInternal(thisObject); realm->begin_transaction(); JSObjectCallAsFunction(ctx, object, thisObject, 0, NULL, jsException); @@ -370,6 +370,8 @@ JSValueRef RealmWrite(JSContextRef ctx, JSObjectRef function, JSObjectRef thisOb namespace realm { struct Notification { JSGlobalContextRef ctx; + JSObjectRef realmObject; + JSObjectRef callbackObject; RJSRealmDelegate::NotificationFunction func; }; } @@ -378,22 +380,26 @@ JSValueRef RealmAddNotification(JSContextRef ctx, JSObjectRef function, JSObject try { RJSValidateArgumentCount(argumentCount, 1); - JSObjectRef user_function = RJSValidatedValueToObject(ctx, arguments[0]); + JSObjectRef callback = RJSValidatedValueToFunction(ctx, arguments[0]); SharedRealm realm = *RJSGetInternal(thisObject); JSGlobalContextRef gCtx = JSGlobalContextRetain(JSContextGetGlobalContext(ctx)); + + JSValueProtect(gCtx, thisObject); + JSValueProtect(gCtx, callback); + RJSRealmDelegate::NotificationFunction func = std::make_shared>([=](std::string notification_name) { JSValueRef arguments[2]; arguments[0] = thisObject; arguments[1] = RJSValueForString(gCtx, notification_name); JSValueRef ex = NULL; - JSObjectCallAsFunction(gCtx, user_function, thisObject, 2, arguments, &ex); + JSObjectCallAsFunction(gCtx, callback, thisObject, 2, arguments, &ex); if (ex) { throw RJSException(gCtx, ex); } }); static_cast(realm->m_delegate.get())->add_notification(func); - return RJSWrapObject(ctx, RJSNotificationClass(), new Notification { gCtx, func }); + return RJSWrapObject(ctx, RJSNotificationClass(), new Notification { gCtx, thisObject, callback, func }); } catch (std::exception &exp) { if (jsException) { @@ -420,7 +426,11 @@ JSValueRef RealmClose(JSContextRef ctx, JSObjectRef function, JSObjectRef thisOb void RJSNotificationFinalize(JSObjectRef object) { Notification *notification = RJSGetInternal(object); - JSGlobalContextRelease(notification->ctx); + JSGlobalContextRef ctx = notification->ctx; + + JSValueUnprotect(ctx, notification->callbackObject); + JSValueUnprotect(ctx, notification->realmObject); + JSGlobalContextRelease(ctx); RJSFinalize(object); } diff --git a/src/RJSUtil.hpp b/src/RJSUtil.hpp index 056b93bb..2d15ee9e 100644 --- a/src/RJSUtil.hpp +++ b/src/RJSUtil.hpp @@ -106,6 +106,14 @@ static inline JSObjectRef RJSValidatedValueToObject(JSContextRef ctx, JSValueRef return object; } +static inline JSObjectRef RJSValidatedValueToFunction(JSContextRef ctx, JSValueRef value, const char *message = NULL) { + JSObjectRef object = JSValueToObject(ctx, value, NULL); + if (!object || !JSObjectIsFunction(ctx, object)) { + throw std::runtime_error(message ?: "Value is not a function."); + } + return object; +} + static inline double RJSValidatedValueToNumber(JSContextRef ctx, JSValueRef value) { JSValueRef exception = NULL; double number = JSValueToNumber(ctx, value, &exception);