diff --git a/react-native/ios/RealmReact/RealmReact.mm b/react-native/ios/RealmReact/RealmReact.mm index 4bcc1dee97..fe898031a6 100644 --- a/react-native/ios/RealmReact/RealmReact.mm +++ b/react-native/ios/RealmReact/RealmReact.mm @@ -22,6 +22,7 @@ #import #import +#include #import #import @@ -51,6 +52,7 @@ - (JSContext *)context; @interface RCTBridge (Realm_RCTCxxBridge) - (JSGlobalContextRef)jsContextRef; - (void *)runtime; +- (std::shared_ptr)jsCallInvoker; @end extern "C" JSGlobalContextRef RealmReactGetJSGlobalContextForExecutor(id executor, bool create) { @@ -83,6 +85,7 @@ @interface RealmReact () @implementation RealmReact { NSMutableDictionary *_eventHandlers; + bool waitingForFlush; #if DEBUG GCDWebServer *_webServer; @@ -108,6 +111,7 @@ - (instancetype)init { self = [super init]; if (self) { _eventHandlers = [[NSMutableDictionary alloc] init]; + waitingForFlush = false; } return self; } @@ -274,7 +278,7 @@ - (void)dealloc { typedef JSGlobalContextRef (^JSContextRefExtractor)(); -void _initializeOnJSThread(JSContextRefExtractor jsContextExtractor) { +void _initializeOnJSThread(JSContextRefExtractor jsContextExtractor, std::function flushUiQueue) { // Make sure the previous JS thread is completely finished before continuing. static __weak NSThread *s_currentJSThread; while (s_currentJSThread && !s_currentJSThread.finished) { @@ -282,7 +286,7 @@ void _initializeOnJSThread(JSContextRefExtractor jsContextExtractor) { } s_currentJSThread = [NSThread currentThread]; - RJSInitializeInContext(jsContextExtractor()); + RJSInitializeInContext(jsContextExtractor(), flushUiQueue); } - (void)setBridge:(RCTBridge *)bridge { @@ -310,7 +314,7 @@ - (void)setBridge:(RCTBridge *)bridge { if (!self || !bridge) { return; } - + _initializeOnJSThread(^{ // RN < 0.58 has a private method that returns the js context if ([bridge respondsToSelector:@selector(jsContextRef)]) { @@ -325,6 +329,17 @@ - (void)setBridge:(RCTBridge *)bridge { JSGlobalContextRef ctx_; }; return static_cast(bridge.runtime)->ctx_; + }, ^{ + if (!waitingForFlush) { + waitingForFlush = true; + // Calling jsCallInvokver->invokeAsync restuls in React Native flushing any pending UI + // updates after we have called into JS from C++ + // + // TODO add a bit more info + [bridge jsCallInvoker]->invokeAsync([&](){ + waitingForFlush = false; + }); + } }); } queue:RCTJSThread]; } else { // React Native 0.44 and older @@ -341,6 +356,8 @@ - (void)setBridge:(RCTBridge *)bridge { _initializeOnJSThread(^ { return RealmReactGetJSGlobalContextForExecutor(executor, true); + }, [&]() { + // jsCallInvoker does not exist on older RN }); }]; } diff --git a/src/jsc/jsc_class.hpp b/src/jsc/jsc_class.hpp index 984bf55454..8d9d633794 100644 --- a/src/jsc/jsc_class.hpp +++ b/src/jsc/jsc_class.hpp @@ -19,6 +19,7 @@ #pragma once #include "jsc_types.hpp" +#include "jsc_function.hpp" #include "js_class.hpp" #include "js_util.hpp" @@ -41,7 +42,7 @@ extern js::Protected FunctionPrototype; extern js::Protected RealmObjectClassConstructor; extern js::Protected RealmObjectClassConstructorPrototype; -static inline void jsc_class_init(JSContextRef ctx, JSObjectRef globalObject) +static inline void jsc_class_init(JSContextRef ctx, JSObjectRef globalObject, std::function flushUiQueue) { // handle ReactNative app refresh by reseting the cached constructor values if (RealmObjectClassConstructor) { @@ -63,6 +64,8 @@ static inline void jsc_class_init(JSContextRef ctx, JSObjectRef globalObject) JSObjectRef globalFunction = jsc::Value::to_object(ctx, value); value = jsc::Object::get_property(ctx, globalFunction, "prototype"); FunctionPrototype = js::Protected(ctx, Value::to_object(ctx, value)); + + js::flush_ui_queue = flushUiQueue; } template diff --git a/src/jsc/jsc_function.hpp b/src/jsc/jsc_function.hpp index d2f99d5c1e..d920009eac 100644 --- a/src/jsc/jsc_function.hpp +++ b/src/jsc/jsc_function.hpp @@ -23,12 +23,18 @@ namespace realm { namespace js { +// TODO +extern std::function flush_ui_queue; + template <> inline JSValueRef jsc::Function::call(JSContextRef ctx, const JSObjectRef& function, const JSObjectRef& this_object, size_t argc, const JSValueRef arguments[]) { JSValueRef exception = nullptr; JSValueRef result = JSObjectCallAsFunction(ctx, function, this_object, argc, arguments, &exception); + + flush_ui_queue(); + if (exception) { throw jsc::Exception(ctx, exception); } @@ -48,6 +54,9 @@ inline JSObjectRef jsc::Function::construct(JSContextRef ctx, const JSObjectRef& { JSValueRef exception = nullptr; JSObjectRef result = JSObjectCallAsConstructor(ctx, function, argc, arguments, &exception); + + flush_ui_queue(); + if (exception) { throw jsc::Exception(ctx, exception); } diff --git a/src/jsc/jsc_init.cpp b/src/jsc/jsc_init.cpp index ba290616b7..63d3070f98 100644 --- a/src/jsc/jsc_init.cpp +++ b/src/jsc/jsc_init.cpp @@ -31,6 +31,10 @@ js::Protected FunctionPrototype; js::Protected RealmObjectClassConstructor; js::Protected RealmObjectClassConstructorPrototype; } // namespace jsc + +namespace js { +std::function flush_ui_queue; +} // namespace js } // namespace realm extern "C" { @@ -43,13 +47,13 @@ JSObjectRef RJSConstructorCreate(JSContextRef ctx) return js::RealmClass::create_constructor(ctx); } -void RJSInitializeInContext(JSContextRef ctx) +void RJSInitializeInContext(JSContextRef ctx, std::function flush_ui_queue) { static const jsc::String realm_string = "Realm"; JSObjectRef global_object = JSContextGetGlobalObject(ctx); - jsc_class_init(ctx, global_object); + jsc_class_init(ctx, global_object, flush_ui_queue); JSObjectRef realm_constructor = RJSConstructorCreate(ctx); diff --git a/src/jsc/jsc_init.h b/src/jsc/jsc_init.h index 8521abef9d..8d88e4d462 100644 --- a/src/jsc/jsc_init.h +++ b/src/jsc/jsc_init.h @@ -19,13 +19,14 @@ #pragma once #include +#include #ifdef __cplusplus extern "C" { #endif JSObjectRef RJSConstructorCreate(JSContextRef ctx); -void RJSInitializeInContext(JSContextRef ctx); +void RJSInitializeInContext(JSContextRef ctx, std::function send_dummy_event); void RJSInvalidateCaches(); #ifdef __cplusplus diff --git a/src/jsc/rpc.cpp b/src/jsc/rpc.cpp index 9940ea3011..1b963a5d89 100644 --- a/src/jsc/rpc.cpp +++ b/src/jsc/rpc.cpp @@ -387,7 +387,7 @@ RPCServerImpl::RPCServerImpl() } m_requests["/create_session"] = [this](const json dict) { - RJSInitializeInContext(m_context); + RJSInitializeInContext(m_context, []() {}); jsc::String realm_string = "Realm"; JSObjectRef realm_constructor =