diff --git a/change/react-native-windows-8c0a3d4a-27a0-4b81-810c-1578749610a8.json b/change/react-native-windows-8c0a3d4a-27a0-4b81-810c-1578749610a8.json
new file mode 100644
index 00000000000..5a87e5bed5f
--- /dev/null
+++ b/change/react-native-windows-8c0a3d4a-27a0-4b81-810c-1578749610a8.json
@@ -0,0 +1,7 @@
+{
+ "type": "prerelease",
+ "comment": "Remove usage of JSDispatcher in various built-in modules",
+ "packageName": "react-native-windows",
+ "email": "30809111+acoates-ms@users.noreply.github.com",
+ "dependentChangeType": "patch"
+}
diff --git a/vnext/Desktop/React.Windows.Desktop.vcxproj b/vnext/Desktop/React.Windows.Desktop.vcxproj
index 52387dbd75f..74b8151709c 100644
--- a/vnext/Desktop/React.Windows.Desktop.vcxproj
+++ b/vnext/Desktop/React.Windows.Desktop.vcxproj
@@ -243,9 +243,6 @@
..\Microsoft.ReactNative\IJSValueWriter.idl
-
- ..\Microsoft.ReactNative\IJSValueWriter.idl
-
..\Microsoft.ReactNative\ReactInstanceSettings.idl
diff --git a/vnext/Folly/Folly.vcxproj b/vnext/Folly/Folly.vcxproj
index c483305b1eb..d98cb6d6b0e 100644
--- a/vnext/Folly/Folly.vcxproj
+++ b/vnext/Folly/Folly.vcxproj
@@ -50,6 +50,7 @@
StaticLibrary
+
diff --git a/vnext/Folly/ThreadNameStub.cpp b/vnext/Folly/ThreadNameStub.cpp
new file mode 100644
index 00000000000..d4216832bb1
--- /dev/null
+++ b/vnext/Folly/ThreadNameStub.cpp
@@ -0,0 +1,10 @@
+#include
+
+// Avoid bringing in a bunch of folly threading just for setThreadName
+namespace folly {
+ bool setThreadName(StringPiece)
+ {
+ return false;
+ }
+ }
+
\ No newline at end of file
diff --git a/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactContextTest.cpp b/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactContextTest.cpp
index 059a6f83ccf..f677674bfaa 100644
--- a/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactContextTest.cpp
+++ b/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactContextTest.cpp
@@ -28,6 +28,10 @@ struct ReactContextStub : implements {
VerifyElseCrashSz(false, "Not implemented");
}
+ CallInvoker CallInvoker() noexcept {
+ VerifyElseCrashSz(false, "Not implemented");
+ }
+
IInspectable JSRuntime() noexcept {
VerifyElseCrashSz(false, "Not implemented");
}
diff --git a/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactModuleBuilderMock.cpp b/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactModuleBuilderMock.cpp
index 38bba3ed362..ca554de7174 100644
--- a/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactModuleBuilderMock.cpp
+++ b/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactModuleBuilderMock.cpp
@@ -69,6 +69,10 @@ void ReactModuleBuilderMock::AddInitializer(InitializerDelegate const &initializ
m_initializers.push_back(initializer);
}
+void ReactModuleBuilderMock::AddJsiInitializer(JsiInitializerDelegate const &initializer) noexcept {
+ m_jsiinitializers.push_back(initializer);
+}
+
void ReactModuleBuilderMock::AddConstantProvider(ConstantProviderDelegate const &constantProvider) noexcept {
m_constantProviders.push_back(constantProvider);
}
diff --git a/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactModuleBuilderMock.h b/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactModuleBuilderMock.h
index a6e4b0d6a46..dae87e5a71f 100644
--- a/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactModuleBuilderMock.h
+++ b/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactModuleBuilderMock.h
@@ -70,6 +70,7 @@ struct ReactModuleBuilderMock {
public: // IReactModuleBuilder
void AddInitializer(InitializerDelegate const &initializer) noexcept;
+ void AddJsiInitializer(JsiInitializerDelegate const &initializer) noexcept;
void AddConstantProvider(ConstantProviderDelegate const &constantProvider) noexcept;
void AddMethod(hstring const &name, MethodReturnType returnType, MethodDelegate const &method) noexcept;
void AddSyncMethod(hstring const &name, SyncMethodDelegate const &method) noexcept;
@@ -100,6 +101,7 @@ struct ReactModuleBuilderMock {
private:
IReactContext m_reactContext{nullptr};
std::vector m_initializers;
+ std::vector m_jsiinitializers;
std::vector m_constantProviders;
std::map> m_methods;
std::map m_syncMethods;
@@ -132,6 +134,10 @@ struct ReactContextMock : implements {
VerifyElseCrashSz(false, "Not implemented");
}
+ CallInvoker CallInvoker() noexcept {
+ VerifyElseCrashSz(false, "Not implemented");
+ }
+
IInspectable JSRuntime() noexcept {
VerifyElseCrashSz(false, "Not implemented");
}
@@ -216,6 +222,7 @@ struct ReactModuleBuilderImpl : implements
+#include "ReactContext.h"
#include "ReactNonAbiValue.h"
#include "winrt/Windows.Foundation.Collections.h"
@@ -14,6 +15,16 @@ using namespace facebook::jsi;
namespace winrt::Microsoft::ReactNative {
+namespace Details {
+// Try to get JSI Runtime for the ReactContext
+// If it is not found, then create it based on context JSI runtime and store it in the context.Properties().
+// The function returns nullptr if the current context does not have JSI runtime.
+// It makes sure that the JSI runtime holder is removed when the instance is unloaded.
+JsiAbiRuntime *TryGetOrCreateContextRuntime(
+ winrt::Microsoft::ReactNative::ReactContext const &context,
+ JsiRuntime const &runtimeHandle) noexcept;
+} // namespace Details
+
// The macro to simplify recording JSI exceptions.
// It looks strange to keep the normal structure of the try/catch in code.
#define JSI_RUNTIME_SET_ERROR(runtime) \
@@ -132,6 +143,52 @@ std::shared_ptr const &JsiHostObjectWrapper::HostObje
return m_hostObject;
}
+//===========================================================================
+// JsiHostObjectGetOrCreateWrapper implementation
+//===========================================================================
+
+JsiHostObjectGetOrCreateWrapper::JsiHostObjectGetOrCreateWrapper(
+ const winrt::Microsoft::ReactNative::IReactContext &context,
+ std::shared_ptr &&hostObject) noexcept
+ : m_hostObject(std::move(hostObject)), m_context(context) {}
+
+JsiValueRef JsiHostObjectGetOrCreateWrapper::GetProperty(JsiRuntime const &runtime, JsiPropertyIdRef const &name) try {
+ JsiAbiRuntime *rt{Details::TryGetOrCreateContextRuntime(m_context, runtime)};
+ JsiAbiRuntime::PropNameIDRef nameRef{name};
+ return JsiAbiRuntime::DetachJsiValueRef(m_hostObject->get(*rt, nameRef));
+} catch (JSI_RUNTIME_SET_ERROR(runtime)) {
+ throw;
+}
+
+void JsiHostObjectGetOrCreateWrapper::SetProperty(
+ JsiRuntime const &runtime,
+ JsiPropertyIdRef const &name,
+ JsiValueRef const &value) try {
+ JsiAbiRuntime *rt{Details::TryGetOrCreateContextRuntime(m_context, runtime)};
+ m_hostObject->set(*rt, JsiAbiRuntime::PropNameIDRef{name}, JsiAbiRuntime::ValueRef(value));
+} catch (JSI_RUNTIME_SET_ERROR(runtime)) {
+ throw;
+}
+
+winrt::Windows::Foundation::Collections::IVector JsiHostObjectGetOrCreateWrapper::GetPropertyIds(
+ JsiRuntime const &runtime) try {
+ JsiAbiRuntime *rt{Details::TryGetOrCreateContextRuntime(m_context, runtime)};
+ auto names = m_hostObject->getPropertyNames(*rt);
+ std::vector result;
+ result.reserve(names.size());
+ for (auto &name : names) {
+ result.push_back(JsiAbiRuntime::DetachJsiPropertyIdRef(std::move(name)));
+ }
+
+ return winrt::single_threaded_vector(std::move(result));
+} catch (JSI_RUNTIME_SET_ERROR(runtime)) {
+ throw;
+}
+
+std::shared_ptr const &JsiHostObjectGetOrCreateWrapper::HostObjectSharedPtr() noexcept {
+ return m_hostObject;
+}
+
//===========================================================================
// JsiHostFunctionWrapper implementation
//===========================================================================
@@ -162,38 +219,41 @@ JsiValueRef JsiHostFunctionWrapper::operator()(
// JsiAbiRuntime implementation
//===========================================================================
-// The tls_jsiAbiRuntimeMap map allows us to associate JsiAbiRuntime with JsiRuntime.
-// The association is thread-specific and DLL-specific.
-// It is thread specific because we want to have the safe access only in JS thread.
-// It is DLL-specific because JsiAbiRuntime is not ABI-safe and each module DLL will
-// have their own JsiAbiRuntime instance.
-static thread_local std::map *tls_jsiAbiRuntimeMap{nullptr};
+// The s_jsiAbiRuntimeMap map allows us to associate JsiAbiRuntime with JsiRuntime.
+// The association is DLL-specific because JsiAbiRuntime is not ABI-safe and each module DLL will have their own
+// JsiAbiRuntime instance.
+static std::map *s_jsiAbiRuntimeMap{nullptr};
+static std::recursive_mutex s_jsiRuntimeMapMutex;
JsiAbiRuntime::JsiAbiRuntime(JsiRuntime const &runtime) noexcept : m_runtime{runtime} {
VerifyElseCrashSz(runtime, "JSI runtime is null");
- VerifyElseCrashSz(
- GetFromJsiRuntime(runtime) == nullptr,
- "We can have only one instance of JsiAbiRuntime for JsiRuntime in the thread.");
- if (!tls_jsiAbiRuntimeMap) {
- tls_jsiAbiRuntimeMap = new std::map();
+
+ {
+ std::lock_guard guard(s_jsiRuntimeMapMutex);
+ VerifyElseCrashSz(
+ GetFromJsiRuntime(runtime) == nullptr, "We can have only one instance of JsiAbiRuntime for a JsiRuntime.");
+
+ if (!s_jsiAbiRuntimeMap) {
+ s_jsiAbiRuntimeMap = new std::map();
+ }
+ s_jsiAbiRuntimeMap->try_emplace(get_abi(runtime), this);
}
- tls_jsiAbiRuntimeMap->try_emplace(get_abi(runtime), this);
}
JsiAbiRuntime::~JsiAbiRuntime() {
- VerifyElseCrashSz(
- GetFromJsiRuntime(m_runtime) != nullptr, "JsiAbiRuntime must be called in the same thread where it was created.");
- tls_jsiAbiRuntimeMap->erase(get_abi(m_runtime));
- if (tls_jsiAbiRuntimeMap->empty()) {
- delete tls_jsiAbiRuntimeMap;
- tls_jsiAbiRuntimeMap = nullptr;
+ std::lock_guard guard(s_jsiRuntimeMapMutex);
+ s_jsiAbiRuntimeMap->erase(get_abi(m_runtime));
+ if (s_jsiAbiRuntimeMap->empty()) {
+ delete s_jsiAbiRuntimeMap;
+ s_jsiAbiRuntimeMap = nullptr;
}
}
/*static*/ JsiAbiRuntime *JsiAbiRuntime::GetFromJsiRuntime(JsiRuntime const &runtime) noexcept {
- if (tls_jsiAbiRuntimeMap && runtime) {
- auto it = tls_jsiAbiRuntimeMap->find(get_abi(runtime));
- if (it != tls_jsiAbiRuntimeMap->end()) {
+ std::lock_guard guard(s_jsiRuntimeMapMutex);
+ if (s_jsiAbiRuntimeMap && runtime) {
+ auto it = s_jsiAbiRuntimeMap->find(get_abi(runtime));
+ if (it != s_jsiAbiRuntimeMap->end()) {
return it->second;
}
}
diff --git a/vnext/Microsoft.ReactNative.Cxx/JSI/JsiAbiApi.h b/vnext/Microsoft.ReactNative.Cxx/JSI/JsiAbiApi.h
index 314bb0f3807..6f3230727a2 100644
--- a/vnext/Microsoft.ReactNative.Cxx/JSI/JsiAbiApi.h
+++ b/vnext/Microsoft.ReactNative.Cxx/JSI/JsiAbiApi.h
@@ -54,6 +54,25 @@ struct JsiHostObjectWrapper : implements {
std::shared_ptr m_hostObject;
};
+// An ABI-safe wrapper for facebook::jsi::HostObject, similar to JsiHostObjectWrapper,
+// but uses GetOrCreate for the AbiRuntime to ensure its created on first use
+// This is important for use with TurboModules, which may not go through the ReactContext.CallInvoker
+struct JsiHostObjectGetOrCreateWrapper : implements {
+ JsiHostObjectGetOrCreateWrapper(
+ const winrt::Microsoft::ReactNative::IReactContext &context,
+ std::shared_ptr &&hostObject) noexcept;
+
+ JsiValueRef GetProperty(JsiRuntime const &runtime, JsiPropertyIdRef const &name);
+ void SetProperty(JsiRuntime const &runtime, JsiPropertyIdRef const &name, JsiValueRef const &value);
+ winrt::Windows::Foundation::Collections::IVector GetPropertyIds(JsiRuntime const &runtime);
+
+ std::shared_ptr const &HostObjectSharedPtr() noexcept;
+
+ private:
+ std::shared_ptr m_hostObject;
+ winrt::Microsoft::ReactNative::IReactContext m_context;
+};
+
// The function object that wraps up the facebook::jsi::HostFunctionType
struct JsiHostFunctionWrapper {
// We only support new and move constructors.
@@ -226,6 +245,7 @@ struct JsiAbiRuntime : facebook::jsi::Runtime {
// Allow access to the helper function
friend struct JsiByteBufferWrapper;
friend struct JsiHostObjectWrapper;
+ friend struct JsiHostObjectGetOrCreateWrapper;
friend struct JsiHostFunctionWrapper;
friend struct AbiJSError;
friend struct AbiJSINativeException;
diff --git a/vnext/Microsoft.ReactNative.Cxx/JSI/JsiApiContext.cpp b/vnext/Microsoft.ReactNative.Cxx/JSI/JsiApiContext.cpp
index aa75c348fcf..46e00403166 100644
--- a/vnext/Microsoft.ReactNative.Cxx/JSI/JsiApiContext.cpp
+++ b/vnext/Microsoft.ReactNative.Cxx/JSI/JsiApiContext.cpp
@@ -10,20 +10,12 @@ extern "C" IMAGE_DOS_HEADER __ImageBase;
namespace winrt::Microsoft::ReactNative {
-// Try to get JSI Runtime for the current JS dispatcher thread.
+namespace Details {
+// Try to get JSI Runtime for the ReactContext
// If it is not found, then create it based on context JSI runtime and store it in the context.Properties().
// The function returns nullptr if the current context does not have JSI runtime.
// It makes sure that the JSI runtime holder is removed when the instance is unloaded.
-facebook::jsi::Runtime *TryGetOrCreateContextRuntime(ReactContext const &context) noexcept {
- ReactDispatcher jsDispatcher = context.JSDispatcher();
- VerifyElseCrashSz(jsDispatcher.HasThreadAccess(), "Must be in JS thread");
-
- // The JSI runtime is not available if we do Web debugging when JS is running in web browser.
- JsiRuntime abiJsiRuntime = context.Handle().JSRuntime().as();
- if (!abiJsiRuntime) {
- return nullptr;
- }
-
+JsiAbiRuntime *TryGetOrCreateContextRuntime(ReactContext const &context, JsiRuntime const &abiJsiRuntime) noexcept {
// See if the JSI runtime was previously created.
JsiAbiRuntime *runtime = JsiAbiRuntime::GetFromJsiRuntime(abiJsiRuntime);
if (!runtime) {
@@ -57,6 +49,48 @@ facebook::jsi::Runtime *TryGetOrCreateContextRuntime(ReactContext const &context
return runtime;
}
+} // namespace Details
+
+facebook::jsi::Runtime *TryGetOrCreateContextRuntime(
+ ReactContext const &context,
+ winrt::Windows::Foundation::IInspectable const &runtimeHandle) noexcept {
+ if (!runtimeHandle) {
+ return nullptr;
+ }
+
+ // The JSI runtime is not available if we do Web debugging when JS is running in web browser.
+ JsiRuntime abiJsiRuntime = runtimeHandle.as();
+ if (!abiJsiRuntime) {
+ return nullptr;
+ }
+
+ return Details::TryGetOrCreateContextRuntime(context, abiJsiRuntime);
+}
+
+// Note: deprecated in favor of TryGetOrCreateContextRuntime with Handle parameter
+facebook::jsi::Runtime *TryGetOrCreateContextRuntime(ReactContext const &context) noexcept {
+#ifdef DEBUG
+ ReactDispatcher jsDispatcher = context.JSDispatcher();
+ VerifyElseCrashSz(jsDispatcher.HasThreadAccess(), "Must be in JS thread");
+#endif
+
+ if (auto runtimeHandle = context.Handle().JSRuntime()) {
+ return TryGetOrCreateContextRuntime(context, runtimeHandle);
+ }
+
+ return nullptr;
+}
+
+// Calls TryGetOrCreateContextRuntime to get JSI runtime.
+// It crashes when TryGetOrCreateContextRuntime returns null.
+[[deprecated]] facebook::jsi::Runtime &GetOrCreateContextRuntime(
+ ReactContext const &context,
+ winrt::Windows::Foundation::IInspectable const &runtimeHandle) noexcept {
+ facebook::jsi::Runtime *runtime = TryGetOrCreateContextRuntime(context, runtimeHandle);
+ VerifyElseCrashSz(runtime, "JSI runtime is not available");
+ return *runtime;
+}
+
// Calls TryGetOrCreateContextRuntime to get JSI runtime.
// It crashes when TryGetOrCreateContextRuntime returns null.
// Note: deprecated in favor of TryGetOrCreateContextRuntime.
diff --git a/vnext/Microsoft.ReactNative.Cxx/JSI/JsiApiContext.h b/vnext/Microsoft.ReactNative.Cxx/JSI/JsiApiContext.h
index caf6ad2bb3d..d0fdc92c848 100644
--- a/vnext/Microsoft.ReactNative.Cxx/JSI/JsiApiContext.h
+++ b/vnext/Microsoft.ReactNative.Cxx/JSI/JsiApiContext.h
@@ -11,10 +11,15 @@
namespace winrt::Microsoft::ReactNative {
+facebook::jsi::Runtime &GetOrCreateContextRuntime(
+ ReactContext const &context,
+ winrt::Windows::Foundation::IInspectable const &runtimeHandle) noexcept;
+
// Try to get JSI Runtime for the current JS dispatcher thread.
// If it is not found, then create it based on context JSI runtime and store it in the context.Properties().
// The function returns nullptr if the current context does not have JSI runtime.
// It makes sure that the JSI runtime holder is removed when the instance is unloaded.
+// Deprecated -- will remove once we remove the JSRuntime property from ReactContext
facebook::jsi::Runtime *TryGetOrCreateContextRuntime(ReactContext const &context) noexcept;
// Calls TryGetOrCreateContextRuntime to get JSI runtime.
@@ -25,6 +30,7 @@ facebook::jsi::Runtime *TryGetOrCreateContextRuntime(ReactContext const &context
// Call provided lambda with the facebook::jsi::Runtime& parameter.
// For example: ExecuteJsi(context, [](facebook::jsi::Runtime& runtime){...})
// The code is executed synchronously if it is already in JSDispatcher, or asynchronously otherwise.
+// New Arch should use ReactContext.CallInvoker instead
template
void ExecuteJsi(ReactContext const &context, TCodeWithRuntime const &code, ReactPromise *callStatus = nullptr) {
ReactDispatcher jsDispatcher = context.JSDispatcher();
diff --git a/vnext/Microsoft.ReactNative.Cxx/NativeModules.h b/vnext/Microsoft.ReactNative.Cxx/NativeModules.h
index 4e2fe9f1e5d..50f8c199899 100644
--- a/vnext/Microsoft.ReactNative.Cxx/NativeModules.h
+++ b/vnext/Microsoft.ReactNative.Cxx/NativeModules.h
@@ -7,6 +7,7 @@
#pragma once
#include
#include
+#include "JSI/JsiApiContext.h"
#include "JSValueReader.h"
#include "JSValueWriter.h"
#include "ModuleRegistration.h"
@@ -500,6 +501,7 @@ template
struct ModuleInitMethodInfo {
using ModuleType = TModule;
using MethodType = void (TModule::*)(ReactContext const &) noexcept;
+ using JsiMethodType = void (TModule::*)(ReactContext const &, facebook::jsi::Runtime &) noexcept;
static InitializerDelegate GetInitializer(void *module, MethodType method) noexcept {
return [module = static_cast(module), method](ReactContext const &reactContext) noexcept {
@@ -508,6 +510,23 @@ struct ModuleInitMethodInfo {
}
};
+template
+struct ModuleJsiInitMethodInfo;
+
+template
+struct ModuleJsiInitMethodInfo {
+ using ModuleType = TModule;
+ using MethodType = void (TModule::*)(ReactContext const &, facebook::jsi::Runtime &) noexcept;
+
+ static JsiInitializerDelegate GetJsiInitializer(void *module, MethodType method) noexcept {
+ return
+ [module = static_cast(module), method](
+ ReactContext const &reactContext, winrt::Windows::Foundation::IInspectable const &runtimeHandle) noexcept {
+ (module->*method)(reactContext, GetOrCreateContextRuntime(reactContext, runtimeHandle));
+ };
+ }
+};
+
// ==== MakeCallbackSignatures =================================================
template
@@ -1041,6 +1060,9 @@ struct ReactModuleBuilder {
for (auto &initializer : m_initializers) {
m_moduleBuilder.AddInitializer(initializer);
}
+ for (auto &initializer : m_jsiinitializers) {
+ m_moduleBuilder.AddJsiInitializer(initializer);
+ }
}
template
@@ -1069,8 +1091,14 @@ struct ReactModuleBuilder {
template
void RegisterInitMethod(TMethod method) noexcept {
- auto initializer = ModuleInitMethodInfo::GetInitializer(m_module, method);
- m_initializers.push_back(std::move(initializer));
+ if constexpr (ModuleMethodInfo::ArgCount == 1) {
+ auto initializer = ModuleInitMethodInfo::GetInitializer(m_module, method);
+ m_initializers.push_back(std::move(initializer));
+ } else {
+ static_assert(ModuleMethodInfo::ArgCount == 2);
+ auto jsiinitializer = ModuleJsiInitMethodInfo::GetJsiInitializer(m_module, method);
+ m_jsiinitializers.push_back(std::move(jsiinitializer));
+ }
}
template
@@ -1129,6 +1157,7 @@ struct ReactModuleBuilder {
std::wstring_view m_moduleName{L""};
std::wstring_view m_eventEmitterName{L""};
std::vector m_initializers;
+ std::vector m_jsiinitializers;
};
struct VerificationResult {
diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactContext.h b/vnext/Microsoft.ReactNative.Cxx/ReactContext.h
index 9023a39b3f1..1c521771e59 100644
--- a/vnext/Microsoft.ReactNative.Cxx/ReactContext.h
+++ b/vnext/Microsoft.ReactNative.Cxx/ReactContext.h
@@ -11,6 +11,7 @@
#if !defined(CORE_ABI) && !defined(__APPLE__)
#include
#endif
+#include
#include
#include "JSValueWriter.h"
#include "ReactNotificationService.h"
@@ -18,6 +19,8 @@
namespace winrt::Microsoft::ReactNative {
+std::shared_ptr MakeAbiCallInvoker(IReactContext const &context) noexcept;
+
// Represents a context of execution for the Native Module.
// It wraps up the IReactContext and adds convenience methods for
// working with C++ types.
@@ -50,6 +53,10 @@ struct ReactContext {
return ReactDispatcher{m_handle.JSDispatcher()};
}
+ std::shared_ptr CallInvoker() const noexcept {
+ return MakeAbiCallInvoker(m_handle);
+ }
+
// Call methodName JS function of module with moduleName.
// args are either function arguments or a single lambda with 'IJSValueWriter const&' argument.
template
diff --git a/vnext/Microsoft.ReactNative.Cxx/TurboModuleProvider.cpp b/vnext/Microsoft.ReactNative.Cxx/TurboModuleProvider.cpp
index bc57f6711ff..bfeb719adaa 100644
--- a/vnext/Microsoft.ReactNative.Cxx/TurboModuleProvider.cpp
+++ b/vnext/Microsoft.ReactNative.Cxx/TurboModuleProvider.cpp
@@ -4,28 +4,26 @@
#include "pch.h"
#include "TurboModuleProvider.h"
+#include "JSI/JsiApiContext.h"
+
namespace winrt::Microsoft::ReactNative {
// CallInvoker implementation based on JSDispatcher.
struct AbiCallInvoker final : facebook::react::CallInvoker {
AbiCallInvoker(IReactContext const &context) : m_context(context) {}
- void invokeAsync(std::function &&func) noexcept override {
- m_context.JSDispatcher().Post([func = std::move(func)]() { func(); });
- }
-
void invokeAsync(facebook::react::CallFunc &&func) noexcept override {
- // If async is allowed to run sync if already on the JS thread, this could be replaced with just the ExecuteJsi call
- m_context.JSDispatcher().Post([context = m_context, func = std::move(func)]() {
- winrt::Microsoft::ReactNative::ExecuteJsi(context, std::move(func));
- });
+ m_context.CallInvoker().InvokeAsync(
+ [context = m_context, func = std::move(func)](const winrt::Windows::Foundation::IInspectable &runtimeHandle) {
+ func(GetOrCreateContextRuntime(context, runtimeHandle));
+ });
}
- virtual void invokeSync(facebook::react::CallFunc &&func) override {
- // Throwing an exception in this method matches the behavior of
- // Instance::JSCallInvoker::invokeSync in react-native\ReactCommon\cxxreact\Instance.cpp
- UNREFERENCED_PARAMETER(func);
- throw std::runtime_error("Synchronous native -> JS calls are currently not supported.");
+ void invokeSync(facebook::react::CallFunc &&func) override {
+ m_context.CallInvoker().InvokeSync(
+ [context = m_context, func = std::move(func)](const winrt::Windows::Foundation::IInspectable &runtimeHandle) {
+ func(GetOrCreateContextRuntime(context, runtimeHandle));
+ });
}
private:
diff --git a/vnext/Microsoft.ReactNative.Cxx/TurboModuleProvider.h b/vnext/Microsoft.ReactNative.Cxx/TurboModuleProvider.h
index 3308662a74c..465b3a2133f 100644
--- a/vnext/Microsoft.ReactNative.Cxx/TurboModuleProvider.h
+++ b/vnext/Microsoft.ReactNative.Cxx/TurboModuleProvider.h
@@ -22,10 +22,9 @@ void AddTurboModuleProvider(IReactPackageBuilder const &packageBuilder, std::wst
IJsiHostObject abiTurboModule{nullptr};
// We expect the initializer to be called immediately for TurboModules
moduleBuilder.AddInitializer([&abiTurboModule](IReactContext const &context) mutable {
- TryGetOrCreateContextRuntime(ReactContext{context}); // Ensure the JSI runtime is created.
- auto callInvoker = MakeAbiCallInvoker(context);
+ auto callInvoker = ReactContext{context}.CallInvoker();
auto turboModule = std::make_shared(callInvoker);
- abiTurboModule = winrt::make(std::move(turboModule));
+ abiTurboModule = winrt::make(context, std::move(turboModule));
});
return abiTurboModule.as();
});
diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/ExecuteJsiTests.cpp b/vnext/Microsoft.ReactNative.IntegrationTests/ExecuteJsiTests.cpp
index e58a23de143..df2bfb761a3 100644
--- a/vnext/Microsoft.ReactNative.IntegrationTests/ExecuteJsiTests.cpp
+++ b/vnext/Microsoft.ReactNative.IntegrationTests/ExecuteJsiTests.cpp
@@ -27,7 +27,8 @@ struct TestExecuteJsiModule {
REACT_METHOD(TestHostFunction, L"testHostFunction")
void TestHostFunction() noexcept {
TestEventService::LogEvent("testHostFunction started", nullptr);
- ExecuteJsi(m_reactContext, [](Runtime &rt) {
+
+ m_reactContext.CallInvoker()->invokeAsync([](Runtime &rt) {
Function hostGreeter = Function::createFromHostFunction(
rt,
PropNameID::forAscii(rt, "hostGreeter"),
@@ -51,7 +52,7 @@ struct TestExecuteJsiModule {
REACT_METHOD(TestHostObject, L"testHostObject")
void TestHostObject() noexcept {
TestEventService::LogEvent("testHostObject started", nullptr);
- ExecuteJsi(m_reactContext, [](Runtime &rt) {
+ m_reactContext.CallInvoker()->invokeAsync([](Runtime &rt) {
class GreeterHostObject : public HostObject {
Value get(Runtime &rt, const PropNameID &) override {
return String::createFromAscii(rt, "Hello");
@@ -80,8 +81,8 @@ struct TestExecuteJsiModule {
// The JSI executed synchronously here because we are in JS thread.
TestEventService::LogEvent("testSameJsiRuntime started", nullptr);
Runtime *jsiRuntime{};
- ExecuteJsi(m_reactContext, [&jsiRuntime](Runtime &rt) { jsiRuntime = &rt; });
- ExecuteJsi(m_reactContext, [&jsiRuntime](Runtime &rt) {
+ m_reactContext.CallInvoker()->invokeAsync([&jsiRuntime](Runtime &rt) { jsiRuntime = &rt; });
+ m_reactContext.CallInvoker()->invokeAsync([&jsiRuntime](Runtime &rt) {
TestCheckEqual(jsiRuntime, &rt);
TestEventService::LogEvent("testSameJsiRuntime completed", nullptr);
});
@@ -97,10 +98,10 @@ struct TestExecuteJsiModule {
[](ReactError const &error) noexcept {
TestEventService::LogEvent("testExecuteJsiPromise promise failed", error.Message.c_str());
});
- ExecuteJsi(
- m_reactContext,
- [](Runtime &) { TestEventService::LogEvent("testExecuteJsiPromise completed", nullptr); },
- &callResult);
+ m_reactContext.CallInvoker()->invokeAsync([callResult](Runtime &) {
+ TestEventService::LogEvent("testExecuteJsiPromise completed", nullptr);
+ callResult.Resolve();
+ });
}
private:
@@ -133,12 +134,12 @@ TEST_CLASS (ExecuteJsiTests) {
TestEventService::ObserveEvents({
TestEvent{"initialize", nullptr},
TestEvent{"testHostFunction started", nullptr},
- TestEvent{"testHostFunction completed", nullptr},
TestEvent{"testHostObject started", nullptr},
- TestEvent{"testHostObject completed", nullptr},
TestEvent{"testSameJsiRuntime started", nullptr},
- TestEvent{"testSameJsiRuntime completed", nullptr},
TestEvent{"testExecuteJsiPromise started", nullptr},
+ TestEvent{"testHostFunction completed", nullptr},
+ TestEvent{"testHostObject completed", nullptr},
+ TestEvent{"testSameJsiRuntime completed", nullptr},
TestEvent{"testExecuteJsiPromise completed", nullptr},
TestEvent{"testExecuteJsiPromise promise succeeded", nullptr},
});
@@ -146,12 +147,12 @@ TEST_CLASS (ExecuteJsiTests) {
TestEventService::ObserveEvents({
TestEvent{"OnInstanceDestroyed started", nullptr},
- TestEvent{"OnInstanceDestroyed promise failed", "No JSI runtime"},
+ TestEvent{"OnInstanceDestroyed promise failed", "Promise destroyed."},
});
}
static void OnInstanceDestroyed(ReactContext const &reactContext) {
- // See that ExecuteJsi failed to execute
+ // See that Jsi failed to execute
TestEventService::LogEvent("OnInstanceDestroyed started", nullptr);
ReactPromise callResult(
@@ -159,10 +160,8 @@ TEST_CLASS (ExecuteJsiTests) {
[](ReactError const &error) noexcept {
TestEventService::LogEvent("OnInstanceDestroyed promise failed", error.Message.c_str());
});
- ExecuteJsi(
- reactContext,
- [](Runtime &) { TestEventService::LogEvent("OnInstanceDestroyed completed", nullptr); },
- &callResult);
+ reactContext.CallInvoker()->invokeAsync(
+ [](Runtime &) { TestEventService::LogEvent("OnInstanceDestroyed completed", nullptr); });
}
};
diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/JsiSimpleTurboModuleTests.cpp b/vnext/Microsoft.ReactNative.IntegrationTests/JsiSimpleTurboModuleTests.cpp
index a698d84bde3..e033b9267fc 100644
--- a/vnext/Microsoft.ReactNative.IntegrationTests/JsiSimpleTurboModuleTests.cpp
+++ b/vnext/Microsoft.ReactNative.IntegrationTests/JsiSimpleTurboModuleTests.cpp
@@ -47,25 +47,37 @@ TEST_CLASS (JsiSimpleTurboModuleTests) {
TEST_METHOD(TestInstanceReload) {
TestEventService::Initialize();
+ std::thread::id jsThreadId{};
+
auto reactNativeHost =
TestReactNativeHostHolder(L"JsiSimpleTurboModuleTests", [&](ReactNativeHost const &host) noexcept {
host.PackageProviders().Append(winrt::make());
// See that all events are raised in JSDispatcher thread.
host.InstanceSettings().InstanceCreated(
- [](IInspectable const & /*sender*/, InstanceCreatedEventArgs const &args) {
+ [&](IInspectable const & /*sender*/, InstanceCreatedEventArgs const &args) {
TestEventService::LogEvent("Instance created event", nullptr);
+ // Save this thread as the js thread
+ jsThreadId = std::this_thread::get_id();
+#if !USE_FABRIC
TestCheck(ReactContext(args.Context()).JSDispatcher().HasThreadAccess());
+#endif
});
host.InstanceSettings().InstanceLoaded(
- [](IInspectable const & /*sender*/, InstanceLoadedEventArgs const &args) {
+ [&](IInspectable const & /*sender*/, InstanceLoadedEventArgs const &args) {
TestEventService::LogEvent("Instance loaded event", nullptr);
+ TestCheck(jsThreadId == std::this_thread::get_id());
+#if !USE_FABRIC
TestCheck(ReactContext(args.Context()).JSDispatcher().HasThreadAccess());
+#endif
});
host.InstanceSettings().InstanceDestroyed(
[&](IInspectable const & /*sender*/, InstanceDestroyedEventArgs const &args) {
TestEventService::LogEvent("Instance destroyed event", nullptr);
+ TestCheck(jsThreadId == std::this_thread::get_id());
+#if !USE_FABRIC
TestCheck(ReactContext(args.Context()).JSDispatcher().HasThreadAccess());
+#endif
});
});
diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/ReactNativeHostTests.cpp b/vnext/Microsoft.ReactNative.IntegrationTests/ReactNativeHostTests.cpp
index 2e5b0b9a7b9..c7ccf3bb689 100644
--- a/vnext/Microsoft.ReactNative.IntegrationTests/ReactNativeHostTests.cpp
+++ b/vnext/Microsoft.ReactNative.IntegrationTests/ReactNativeHostTests.cpp
@@ -131,6 +131,29 @@ TEST_CLASS (ReactNativeHostTests) {
TestEventService::ObserveEvents({TestEvent{"InstanceLoaded::Success", nullptr}});
}
+ TEST_METHOD(LoadInstance_FiresInstanceCreatedHasJsiAccess) {
+ TestEventService::Initialize();
+
+ auto options = TestReactNativeHostHolder::Options{};
+ auto reactNativeHost = TestReactNativeHostHolder(L"ReactNativeHostTests", [](ReactNativeHost const &host) noexcept {
+ host.InstanceSettings().InstanceCreated(
+ [](auto const &, winrt::Microsoft::ReactNative::IInstanceCreatedEventArgs args) noexcept {
+ facebook::jsi::Runtime &rt =
+ winrt::Microsoft::ReactNative::GetOrCreateContextRuntime(args.Context(), args.RuntimeHandle());
+
+ facebook::jsi::Object result(rt);
+ result.setProperty(rt, "const1", facebook::jsi::Value(true));
+ result.setProperty(rt, "const2", facebook::jsi::Value(375));
+ result.setProperty(rt, "const3", facebook::jsi::String::createFromUtf8(rt, "something"));
+ rt.global().setProperty(rt, "setFromInstanceCreated", result);
+
+ TestEventService::LogEvent("InstanceCreated", nullptr);
+ });
+ });
+
+ TestEventService::ObserveEvents({TestEvent{"InstanceCreated", nullptr}});
+ }
+
TEST_METHOD(LoadBundleWithError_FiresInstanceLoaded_Failed) {
TestEventService::Initialize();
@@ -169,7 +192,11 @@ TEST_CLASS (ReactNativeHostTests) {
},
std::move(options));
+#if USE_FABRIC
+ TestEventService::ObserveEvents({TestEvent{"InstanceLoaded::Failed", nullptr}});
+#else
TestEventService::ObserveEvents({TestEvent{"InstanceLoaded::Canceled", nullptr}});
+#endif
}
};
diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/TurboModuleTests.cpp b/vnext/Microsoft.ReactNative.IntegrationTests/TurboModuleTests.cpp
index 2486e8fdba1..287104d97f8 100644
--- a/vnext/Microsoft.ReactNative.IntegrationTests/TurboModuleTests.cpp
+++ b/vnext/Microsoft.ReactNative.IntegrationTests/TurboModuleTests.cpp
@@ -650,7 +650,9 @@ TEST_CLASS (TurboModuleTests) {
TestEventService::Initialize();
TestNotificationService::Initialize();
- auto reactNativeHost = TestReactNativeHostHolder(L"TurboModuleTests", [](ReactNativeHost const &host) noexcept {
+ CallInvoker callInvoker{nullptr};
+
+ auto reactNativeHost = TestReactNativeHostHolder(L"TurboModuleTests", [&](ReactNativeHost const &host) noexcept {
host.PackageProviders().Append(winrt::make());
ReactPropertyBag(host.InstanceSettings().Properties())
.Set(CppTurboModule::TestName, L"JSDispatcherAfterInstanceUnload");
@@ -658,6 +660,10 @@ TEST_CLASS (TurboModuleTests) {
[&](IInspectable const & /*sender*/, InstanceDestroyedEventArgs const & /*args*/) {
TestNotificationService::Set("Instance destroyed event");
});
+ host.InstanceSettings().InstanceCreated(
+ [&](IInspectable const & /*sender*/, InstanceCreatedEventArgs const &args) {
+ callInvoker = args.Context().CallInvoker();
+ });
});
TestEventService::ObserveEvents({
@@ -667,19 +673,17 @@ TEST_CLASS (TurboModuleTests) {
reactNativeHost.Host().UnloadInstance();
TestNotificationService::Wait("Instance destroyed event");
- // JSDispatcher must not process any callbacks
- auto jsDispatcher = reactNativeHost.Host()
- .InstanceSettings()
- .Properties()
- .Get(ReactDispatcherHelper::JSDispatcherProperty())
- .as();
struct CallbackData {
~CallbackData() {
TestNotificationService::Set("CallbackData destroyed");
}
};
bool callbackIsCalled{false};
- jsDispatcher.Post([&callbackIsCalled, data = std::make_shared()] { callbackIsCalled = true; });
+
+ // callInvoker must not process any callbacks
+ callInvoker.InvokeAsync(
+ [&callbackIsCalled, data = std::make_shared()](
+ const winrt::Windows::Foundation::IInspectable & /*runtimeHandle*/) { callbackIsCalled = true; });
TestNotificationService::Wait("CallbackData destroyed");
TestCheck(!callbackIsCalled);
}
diff --git a/vnext/Microsoft.ReactNative.Managed.UnitTests/ReactModuleBuilderMock.cs b/vnext/Microsoft.ReactNative.Managed.UnitTests/ReactModuleBuilderMock.cs
index 87aa0a1e046..53a072a15a8 100644
--- a/vnext/Microsoft.ReactNative.Managed.UnitTests/ReactModuleBuilderMock.cs
+++ b/vnext/Microsoft.ReactNative.Managed.UnitTests/ReactModuleBuilderMock.cs
@@ -18,6 +18,7 @@ namespace Microsoft.ReactNative.Managed.UnitTests
class ReactModuleBuilderMock : IReactModuleBuilder
{
private readonly List m_initializers = new List();
+ private readonly List m_jsiinitializers = new List();
private readonly Dictionary> m_methods =
new Dictionary>();
private readonly Dictionary m_syncMethods =
@@ -55,6 +56,11 @@ public void AddInitializer(InitializerDelegate initializer)
m_initializers.Add(initializer);
}
+ public void AddJsiInitializer(JsiInitializerDelegate initializer)
+ {
+ m_jsiinitializers.Add(initializer);
+ }
+
public void AddConstantProvider(ConstantProviderDelegate constantProvider)
{
m_constantProviders.Add(constantProvider);
@@ -354,7 +360,11 @@ public ReactContextMock(ReactModuleBuilderMock builder)
public IReactDispatcher UIDispatcher => Properties.Get(ReactDispatcherHelper.UIDispatcherProperty) as IReactDispatcher;
+#pragma warning disable 612, 618 // Deprecated
public IReactDispatcher JSDispatcher => Properties.Get(ReactDispatcherHelper.JSDispatcherProperty) as IReactDispatcher;
+#pragma warning restore 612, 618
+
+ public CallInvoker CallInvoker => throw new NotImplementedException();
public Object JSRuntime => throw new NotImplementedException();
diff --git a/vnext/Microsoft.ReactNative.Managed/ReactContext.cs b/vnext/Microsoft.ReactNative.Managed/ReactContext.cs
index ac8c52e9023..4fbf17810b8 100644
--- a/vnext/Microsoft.ReactNative.Managed/ReactContext.cs
+++ b/vnext/Microsoft.ReactNative.Managed/ReactContext.cs
@@ -24,8 +24,10 @@ public struct ReactContext
public ReactDispatcher UIDispatcher => new ReactDispatcher(Handle.UIDispatcher);
+ #pragma warning disable 612, 618 // Deprecated
public ReactDispatcher JSDispatcher => new ReactDispatcher(Handle.JSDispatcher);
-
+ #pragma warning restore 612, 618
+
public LoadingState LoadingState => Handle.LoadingState;
public ReactSettingsSnapshot SettingsSnapshot => new ReactSettingsSnapshot(Handle.SettingsSnapshot);
diff --git a/vnext/Microsoft.ReactNative/CallInvoker.cpp b/vnext/Microsoft.ReactNative/CallInvoker.cpp
new file mode 100644
index 00000000000..7a51a1d30b7
--- /dev/null
+++ b/vnext/Microsoft.ReactNative/CallInvoker.cpp
@@ -0,0 +1,42 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#include "CallInvoker.h"
+
+#include "CallInvoker.g.cpp"
+#include "JsiApi.h"
+
+namespace winrt::Microsoft::ReactNative::implementation {
+
+static const ReactPropertyId &CallInvokerPropertyId() noexcept {
+ static const ReactPropertyId prop{L"ReactNative", L"CallInvoker"};
+ return prop;
+}
+
+CallInvoker::CallInvoker(
+ Mso::React::IReactContext &reactContext,
+ std::shared_ptr callInvoker) noexcept
+ : m_callInvoker(callInvoker), m_context(&reactContext) {}
+
+void CallInvoker::InvokeAsync(CallFunc func) noexcept {
+ m_callInvoker->invokeAsync(
+ [reactContext = m_context, func](facebook::jsi::Runtime & /*runtime*/) { func(reactContext->JsiRuntime()); });
+}
+
+void CallInvoker::InvokeSync(CallFunc func) noexcept {
+ m_callInvoker->invokeSync(
+ [reactContext = m_context, func](facebook::jsi::Runtime & /*runtime*/) { func(reactContext->JsiRuntime()); });
+}
+
+winrt::Microsoft::ReactNative::CallInvoker CallInvoker::FromProperties(
+ const winrt::Microsoft::ReactNative::ReactPropertyBag &properties) noexcept {
+ return properties.Get(CallInvokerPropertyId());
+}
+
+void CallInvoker::SetProperties(
+ const winrt::Microsoft::ReactNative::ReactPropertyBag &properties,
+ winrt::Microsoft::ReactNative::CallInvoker invoker) noexcept {
+ properties.Set(CallInvokerPropertyId(), invoker);
+}
+
+} // namespace winrt::Microsoft::ReactNative::implementation
diff --git a/vnext/Microsoft.ReactNative/CallInvoker.h b/vnext/Microsoft.ReactNative/CallInvoker.h
new file mode 100644
index 00000000000..898b06b945b
--- /dev/null
+++ b/vnext/Microsoft.ReactNative/CallInvoker.h
@@ -0,0 +1,34 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#pragma once
+
+#include
+#include
+#include "ReactHost/React.h"
+#include "winrt/Microsoft.ReactNative.h"
+
+#include "CallInvoker.g.h"
+
+namespace winrt::Microsoft::ReactNative::implementation {
+
+struct CallInvoker : CallInvokerT {
+ CallInvoker(
+ Mso::React::IReactContext &reactContext,
+ std::shared_ptr callInvoker) noexcept;
+
+ void InvokeAsync(CallFunc func) noexcept;
+ void InvokeSync(CallFunc func) noexcept;
+
+ static winrt::Microsoft::ReactNative::CallInvoker FromProperties(
+ const winrt::Microsoft::ReactNative::ReactPropertyBag &properties) noexcept;
+ static void SetProperties(
+ const winrt::Microsoft::ReactNative::ReactPropertyBag &properties,
+ winrt::Microsoft::ReactNative::CallInvoker invoker) noexcept;
+
+ private:
+ Mso::CntPtr m_context;
+ std::shared_ptr m_callInvoker;
+};
+
+} // namespace winrt::Microsoft::ReactNative::implementation
diff --git a/vnext/Microsoft.ReactNative/CallInvokerWriter.cpp b/vnext/Microsoft.ReactNative/CallInvokerWriter.cpp
index d7c1b24ee09..35cbdc98d0f 100644
--- a/vnext/Microsoft.ReactNative/CallInvokerWriter.cpp
+++ b/vnext/Microsoft.ReactNative/CallInvokerWriter.cpp
@@ -8,22 +8,6 @@
namespace winrt::Microsoft::ReactNative {
-// Special IJSValueWriter that does nothing.
-// We use it instead of JsiWriter when JSI runtime is not available anymore.
-struct JSNoopWriter : winrt::implements {
- public: // IJSValueWriter
- void WriteNull() noexcept;
- void WriteBoolean(bool value) noexcept;
- void WriteInt64(int64_t value) noexcept;
- void WriteDouble(double value) noexcept;
- void WriteString(const winrt::hstring &value) noexcept;
- void WriteObjectBegin() noexcept;
- void WritePropertyName(const winrt::hstring &name) noexcept;
- void WriteObjectEnd() noexcept;
- void WriteArrayBegin() noexcept;
- void WriteArrayEnd() noexcept;
-};
-
//===========================================================================
// CallInvokerWriter implementation
//===========================================================================
@@ -145,4 +129,4 @@ void JSNoopWriter::WriteObjectEnd() noexcept {}
void JSNoopWriter::WriteArrayBegin() noexcept {}
void JSNoopWriter::WriteArrayEnd() noexcept {}
-} // namespace winrt::Microsoft::ReactNative
+} // namespace winrt::Microsoft::ReactNative
\ No newline at end of file
diff --git a/vnext/Microsoft.ReactNative/CallInvokerWriter.h b/vnext/Microsoft.ReactNative/CallInvokerWriter.h
index fdbcc96b7b1..f9372105f66 100644
--- a/vnext/Microsoft.ReactNative/CallInvokerWriter.h
+++ b/vnext/Microsoft.ReactNative/CallInvokerWriter.h
@@ -45,4 +45,20 @@ struct CallInvokerWriter : winrt::implements
IJSValueWriter m_writer;
};
-} // namespace winrt::Microsoft::ReactNative
+// Special IJSValueWriter that does nothing.
+// We use it instead of JsiWriter when JSI runtime is not available anymore.
+struct JSNoopWriter : winrt::implements {
+ public: // IJSValueWriter
+ void WriteNull() noexcept;
+ void WriteBoolean(bool value) noexcept;
+ void WriteInt64(int64_t value) noexcept;
+ void WriteDouble(double value) noexcept;
+ void WriteString(const winrt::hstring &value) noexcept;
+ void WriteObjectBegin() noexcept;
+ void WritePropertyName(const winrt::hstring &name) noexcept;
+ void WriteObjectEnd() noexcept;
+ void WriteArrayBegin() noexcept;
+ void WriteArrayEnd() noexcept;
+};
+
+} // namespace winrt::Microsoft::ReactNative
\ No newline at end of file
diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.h b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.h
index be9cfa44b79..899d2ffa6bc 100644
--- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.h
+++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.h
@@ -14,7 +14,6 @@
#include
#include
#include
-#include "Utils/BatchingEventEmitter.h"
namespace winrt {
using namespace Windows::UI;
diff --git a/vnext/Microsoft.ReactNative/Fabric/FabricUIManagerModule.cpp b/vnext/Microsoft.ReactNative/Fabric/FabricUIManagerModule.cpp
index 586926a0621..790fd270389 100644
--- a/vnext/Microsoft.ReactNative/Fabric/FabricUIManagerModule.cpp
+++ b/vnext/Microsoft.ReactNative/Fabric/FabricUIManagerModule.cpp
@@ -470,6 +470,19 @@ void FabricUIManager::Initialize(winrt::Microsoft::ReactNative::ReactContext con
m_context.Properties().Set(FabicUIManagerProperty(), shared_from_this());
+ auto destroyInstanceNotificationId{
+ winrt::Microsoft::ReactNative::ReactNotificationId{
+ L"ReactNative.InstanceSettings", L"InstanceDestroyed"}};
+ reactContext.Notifications().Subscribe(
+ destroyInstanceNotificationId,
+ [reactContext](
+ winrt::Windows::Foundation::IInspectable const & /*sender*/,
+ winrt::Microsoft::ReactNative::ReactNotificationArgs<
+ winrt::Microsoft::ReactNative::InstanceDestroyedEventArgs> const &args) noexcept {
+ reactContext.Properties().Remove(FabicUIManagerProperty());
+ args.Subscription().Unsubscribe(); // Unsubscribe after we handle the notification.
+ });
+
/*
EventBeatManager eventBeatManager = new EventBeatManager(mReactApplicationContext);
UIManagerModule nativeModule =
diff --git a/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/MessageQueueThreadImpl.cpp b/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/MessageQueueThreadImpl.cpp
new file mode 100644
index 00000000000..a23a40422c8
--- /dev/null
+++ b/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/MessageQueueThreadImpl.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+// [Windows] Brought forward from react-native - switch to using react-native/ReactCxxPlatform/react/threading version
+// once we integrate that far
+
+#include "MessageQueueThreadImpl.h"
+
+#include
+
+namespace facebook::react {
+
+void MessageQueueThreadImpl::runOnQueue(std::function &&runnable) {
+ if (!taskDispatchThread_.isRunning()) {
+ return;
+ }
+ taskDispatchThread_.runAsync([runnable = std::move(runnable)]() noexcept { runnable(); });
+}
+
+void MessageQueueThreadImpl::runOnQueueSync(std::function &&runnable) {
+ if (!taskDispatchThread_.isRunning()) {
+ return;
+ }
+ if (taskDispatchThread_.isOnThread()) {
+ runnable();
+ } else {
+ taskDispatchThread_.runSync([runnable = std::move(runnable)]() noexcept { runnable(); });
+ }
+}
+
+void MessageQueueThreadImpl::quitSynchronous() {
+ taskDispatchThread_.quit();
+}
+
+} // namespace facebook::react
diff --git a/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/MessageQueueThreadImpl.h b/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/MessageQueueThreadImpl.h
new file mode 100644
index 00000000000..0f4bdf0f85d
--- /dev/null
+++ b/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/MessageQueueThreadImpl.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+// [Windows] Brought forward from react-native - switch to using react-native/ReactCxxPlatform/react/threading version
+// once we integrate that far
+
+#pragma once
+
+#include
+#include
+#include
+
+namespace facebook::react {
+
+using MessageQueueThreadFactory = std::function()>;
+
+constexpr char MessageQueueThreadFactoryKey[] = "MessageQueueThreadFactoryKey";
+
+/**
+ * MessageQueueThread implementation that uses a TaskDispatchThread for
+ * queueing and threading logic
+ */
+class MessageQueueThreadImpl : public MessageQueueThread {
+ public:
+ MessageQueueThreadImpl() noexcept = default;
+ explicit MessageQueueThreadImpl(int priorityOffset) noexcept : taskDispatchThread_("MessageQueue", priorityOffset) {}
+
+ ~MessageQueueThreadImpl() noexcept override = default;
+
+ /** Add a job to the queue asynchronously */
+ void runOnQueue(std::function &&runnable) override;
+
+ /**
+ * Add a job to the queue synchronously - call won't return until runnable
+ * has completed. Will run immediately if called from the looper thread.
+ * Should only be used for initialization.
+ */
+ void runOnQueueSync(std::function &&runnable) override;
+
+ /**
+ * Stop the message queue thread. Should only be used for cleanup - once it
+ * returns, no further work should run on the queue.
+ */
+ void quitSynchronous() override;
+
+ private:
+ TaskDispatchThread taskDispatchThread_{"MessageQueue"};
+};
+
+} // namespace facebook::react
diff --git a/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/TaskDispatchThread.cpp b/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/TaskDispatchThread.cpp
new file mode 100644
index 00000000000..9c7318103bd
--- /dev/null
+++ b/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/TaskDispatchThread.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+// [Windows] Brought forward from react-native - switch to using react-native/ReactCxxPlatform/react/threading version
+// once we integrate that far
+
+#include "TaskDispatchThread.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#ifdef ANDROID
+#include
+#include
+#endif
+
+namespace facebook::react {
+
+TaskDispatchThread::TaskDispatchThread(std::string threadName, int priorityOffset) noexcept
+ : threadName_(std::move(threadName)) {
+#ifdef ANDROID
+ // Attaches the thread to JVM just in case anything calls out to Java
+ thread_ = std::thread([&]() {
+ facebook::jni::ThreadScope::WithClassLoader([&]() {
+ int result = setpriority(PRIO_PROCESS, static_cast(::syscall(SYS_gettid)), priorityOffset);
+
+ if (result != 0) {
+ LOG(INFO) << " setCurrentThreadPriority failed with pri errno: " << errno;
+ }
+
+ loop();
+ });
+ });
+
+#else
+ thread_ = std::thread(&TaskDispatchThread::loop, this);
+#endif
+}
+
+TaskDispatchThread::~TaskDispatchThread() noexcept {
+ quit();
+}
+
+bool TaskDispatchThread::isOnThread() noexcept {
+ return std::this_thread::get_id() == thread_.get_id();
+}
+
+bool TaskDispatchThread::isRunning() noexcept {
+ return running_;
+}
+
+void TaskDispatchThread::runAsync(TaskFn &&task, std::chrono::milliseconds delayMs) noexcept {
+ if (!running_) {
+ return;
+ }
+ std::lock_guard guard(queueLock_);
+ auto dispatchTime = std::chrono::system_clock::now() + delayMs;
+ queue_.emplace(dispatchTime, std::move(task));
+ loopCv_.notify_one();
+}
+
+void TaskDispatchThread::runSync(TaskFn &&task) noexcept {
+ std::promise promise;
+ runAsync([&]() {
+ if (running_) {
+ task();
+ }
+ promise.set_value();
+ });
+ promise.get_future().wait();
+}
+
+void TaskDispatchThread::quit() noexcept {
+ if (!running_) {
+ return;
+ }
+ running_ = false;
+ loopCv_.notify_one();
+ if (thread_.joinable()) {
+ if (!isOnThread()) {
+ thread_.join();
+ } else {
+ thread_.detach();
+ }
+ }
+}
+
+void TaskDispatchThread::loop() noexcept {
+ if (!threadName_.empty()) {
+ folly::setThreadName(threadName_);
+ }
+ while (running_) {
+ std::unique_lock lock(queueLock_);
+ loopCv_.wait(lock, [&]() { return !running_ || !queue_.empty(); });
+ while (!queue_.empty()) {
+ auto task = queue_.top();
+ auto now = std::chrono::system_clock::now();
+ if (task.dispatchTime > now) {
+ if (running_) {
+ loopCv_.wait_until(lock, task.dispatchTime);
+ } else {
+ // Shutting down, skip all the delayed tasks that are not to be
+ // executed yet
+ queue_.pop();
+ }
+ continue;
+ }
+
+ queue_.pop();
+ lock.unlock();
+ task.fn();
+ lock.lock();
+ }
+ }
+}
+
+} // namespace facebook::react
diff --git a/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/TaskDispatchThread.h b/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/TaskDispatchThread.h
new file mode 100644
index 00000000000..1f161ca6997
--- /dev/null
+++ b/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/TaskDispatchThread.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+// [Windows] Brought forward from react-native - switch to using react-native/ReactCxxPlatform/react/threading version
+// once we integrate that far
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace facebook::react {
+
+/**
+ * Representation of a thread looper which can add tasks to a queue and handle
+ * the synchronization of callers.
+ */
+class TaskDispatchThread {
+ public:
+ using TaskFn = std::function;
+ using TimePoint = std::chrono::time_point;
+
+ TaskDispatchThread(std::string threadName = "", int priorityOffset = 0) noexcept;
+
+ ~TaskDispatchThread() noexcept;
+
+ /** Return true if the current thread is the same as this looper's thread. */
+ bool isOnThread() noexcept;
+
+ /** Return true until TaskDispatchThread.quit() is called */
+ bool isRunning() noexcept;
+
+ /** Add task to the queue and return immediately. */
+ void runAsync(TaskFn &&task, std::chrono::milliseconds delayMs = std::chrono::milliseconds::zero()) noexcept;
+
+ /** Add task to the queue and wait until it has completed. */
+ void runSync(TaskFn &&task) noexcept;
+
+ /** Shut down and clean up the thread. */
+ void quit() noexcept;
+
+ protected:
+ struct Task {
+ TimePoint dispatchTime;
+ TaskFn fn;
+
+ Task(TimePoint dispatchTime, TaskFn &&fn) : dispatchTime(dispatchTime), fn(std::move(fn)) {}
+
+ bool operator<(const Task &other) const {
+ // Have the earliest tasks be at the front of the queue.
+ return dispatchTime > other.dispatchTime;
+ }
+ };
+
+ void loop() noexcept;
+
+ std::mutex queueLock_;
+ std::condition_variable loopCv_;
+ std::priority_queue queue_;
+ std::atomic running_{true};
+ std::string threadName_;
+ std::thread thread_;
+};
+
+} // namespace facebook::react
diff --git a/vnext/Microsoft.ReactNative/IReactContext.cpp b/vnext/Microsoft.ReactNative/IReactContext.cpp
index 8e79b0fa2c7..1e106b61ba5 100644
--- a/vnext/Microsoft.ReactNative/IReactContext.cpp
+++ b/vnext/Microsoft.ReactNative/IReactContext.cpp
@@ -8,6 +8,9 @@
#include "XamlUIService.h"
#endif
+#include "CallInvoker.h"
+#include "Utils/Helpers.h"
+
namespace winrt::Microsoft::ReactNative::implementation {
//=============================================================================
@@ -97,10 +100,24 @@ IReactDispatcher ReactContext::UIDispatcher() noexcept {
}
IReactDispatcher ReactContext::JSDispatcher() noexcept {
+#if defined(DEBUG) && defined(USE_FABRIC)
+ VerifyElseCrashSz(
+ !::Microsoft::ReactNative::IsFabricEnabled(Properties()),
+ "ReactContext.JSRuntime is not supported on new arch, use ReactContext.CallInvoker instead.");
+#endif
return Properties().Get(ReactDispatcherHelper::JSDispatcherProperty()).try_as();
}
+winrt::Microsoft::ReactNative::CallInvoker ReactContext::CallInvoker() noexcept {
+ return winrt::Microsoft::ReactNative::implementation::CallInvoker::FromProperties(ReactPropertyBag(Properties()));
+}
+
winrt::Windows::Foundation::IInspectable ReactContext::JSRuntime() noexcept {
+#if defined(DEBUG) && defined(USE_FABRIC)
+ VerifyElseCrashSz(
+ !::Microsoft::ReactNative::IsFabricEnabled(Properties()),
+ "ReactContext.JSRuntime is not supported on new arch, use ReactContext.CallInvoker instead.");
+#endif
return m_context->JsiRuntime();
}
diff --git a/vnext/Microsoft.ReactNative/IReactContext.h b/vnext/Microsoft.ReactNative/IReactContext.h
index e22e0cf6f03..796f2faa492 100644
--- a/vnext/Microsoft.ReactNative/IReactContext.h
+++ b/vnext/Microsoft.ReactNative/IReactContext.h
@@ -43,6 +43,7 @@ struct ReactContext : winrt::implements {
IReactNotificationService Notifications() noexcept;
IReactDispatcher UIDispatcher() noexcept;
IReactDispatcher JSDispatcher() noexcept;
+ winrt::Microsoft::ReactNative::CallInvoker CallInvoker() noexcept;
IInspectable JSRuntime() noexcept;
LoadingState LoadingState() noexcept;
diff --git a/vnext/Microsoft.ReactNative/IReactContext.idl b/vnext/Microsoft.ReactNative/IReactContext.idl
index 2aebfa210d5..2c497a6a8b4 100644
--- a/vnext/Microsoft.ReactNative/IReactContext.idl
+++ b/vnext/Microsoft.ReactNative/IReactContext.idl
@@ -14,6 +14,18 @@ import "IReactPropertyBag.idl";
namespace Microsoft.ReactNative
{
+ DOC_STRING("Function that acts on a JsiRuntime, provided as the argument to the function. ABI safe version of facebook::react::CallFunc in CallInvoker.h. Most direct usage of this should be avoided by using ReactContext.CallInvoker.")
+ delegate void CallFunc(Object runtime);
+
+ [webhosthidden]
+ [default_interface]
+ DOC_STRING("CallInvoker used to access the jsi runtime. Most direct usage of this should be avoided by using ReactContext.CallInvoker.")
+ runtimeclass CallInvoker
+ {
+ void InvokeAsync(CallFunc func);
+ void InvokeSync(CallFunc func);
+ };
+
DOC_STRING(
"Used to represent the state of the React Native JavaScript instance")
enum LoadingState
@@ -173,6 +185,7 @@ namespace Microsoft.ReactNative
"It is a shortcut for the @ReactDispatcherHelper.UIDispatcherProperty from the @.Properties property bag.")
IReactDispatcher UIDispatcher { get; };
+ [deprecated("Use @IReactContext.CallInvoker instead", deprecate, 1)]
DOC_STRING(
"Gets the JavaScript engine thread dispatcher.\n"
"It is a shortcut for the @ReactDispatcherHelper.JSDispatcherProperty from the @.Properties property bag.")
@@ -182,9 +195,13 @@ namespace Microsoft.ReactNative
"Gets the JavaScript runtime for the running React instance.\n"
"It can be null if Web debugging is used.\n"
"**Note: do not use this property directly. "
- "It is an experimental property that may be removed or changed in a future version.")
+ "It is an experimental property will be removed in a future version.\n"
+ "Deprecated for new Arch: Use @IReactContext.CallInvoker instead.")
Object JSRuntime { get; };
+ DOC_STRING("used to schedule work on the JS runtime. Most direct usage of this should be avoided by using ReactContext.CallInvoker.")
+ CallInvoker CallInvoker { get; };
+
#if !defined(CORE_ABI) && !defined(USE_FABRIC)
[deprecated("Use @XamlUIService.DispatchEvent instead", deprecate, 1)]
DOC_STRING("Deprecated property. Use @XamlUIService.DispatchEvent instead. It will be removed in a future version.")
diff --git a/vnext/Microsoft.ReactNative/IReactDispatcher.idl b/vnext/Microsoft.ReactNative/IReactDispatcher.idl
index b6e522bee53..5db40c06c81 100644
--- a/vnext/Microsoft.ReactNative/IReactDispatcher.idl
+++ b/vnext/Microsoft.ReactNative/IReactDispatcher.idl
@@ -48,6 +48,7 @@ namespace Microsoft.ReactNative
"a specific React instance.")
static IReactPropertyName UIDispatcherProperty { get; };
+ [deprecated("Use @IReactContext.CallInvoker instead", deprecate, 1)]
DOC_STRING(
"Gets name of the `JSDispatcher` property for the @IReactPropertyBag.\n"
"Generally you can use @IReactContext.JSDispatcher to get the value of this property for "
diff --git a/vnext/Microsoft.ReactNative/IReactModuleBuilder.cpp b/vnext/Microsoft.ReactNative/IReactModuleBuilder.cpp
index 06403da41b8..4911757ba98 100644
--- a/vnext/Microsoft.ReactNative/IReactModuleBuilder.cpp
+++ b/vnext/Microsoft.ReactNative/IReactModuleBuilder.cpp
@@ -74,6 +74,10 @@ void ReactModuleBuilder::AddInitializer(InitializerDelegate const &initializer)
m_initializers.push_back(initializer);
}
+void ReactModuleBuilder::AddJsiInitializer(JsiInitializerDelegate const &initializer) noexcept {
+ m_jsiinitializers.push_back(initializer);
+}
+
void ReactModuleBuilder::AddConstantProvider(ConstantProviderDelegate const &constantProvider) noexcept {
m_constantProviders.push_back(constantProvider);
}
diff --git a/vnext/Microsoft.ReactNative/IReactModuleBuilder.h b/vnext/Microsoft.ReactNative/IReactModuleBuilder.h
index 6fba03b3a71..17fdf22ecca 100644
--- a/vnext/Microsoft.ReactNative/IReactModuleBuilder.h
+++ b/vnext/Microsoft.ReactNative/IReactModuleBuilder.h
@@ -12,6 +12,7 @@ struct ReactModuleBuilder : winrt::implements m_initializers;
+ std::vector m_jsiinitializers;
std::vector m_constantProviders;
std::vector m_methods;
};
diff --git a/vnext/Microsoft.ReactNative/IReactModuleBuilder.idl b/vnext/Microsoft.ReactNative/IReactModuleBuilder.idl
index a80ea90880e..d422b3f2514 100644
--- a/vnext/Microsoft.ReactNative/IReactModuleBuilder.idl
+++ b/vnext/Microsoft.ReactNative/IReactModuleBuilder.idl
@@ -15,6 +15,12 @@ namespace Microsoft.ReactNative
"Experimental code uses it to initialize TurboModule `CallInvoker`.")
delegate void InitializerDelegate(IReactContext reactContext);
+ DOC_STRING(
+ "A delegate that sets `reactContext` for a module.\n"
+ "We use it for a stand-alone initialize method, strongly typed JS events and functions.\n"
+ "Experimental code uses it to initialize TurboModule `CallInvoker`.")
+ delegate void JsiInitializerDelegate(IReactContext reactContext, Object runtimeHandle);
+
DOC_STRING("Native method return type.")
enum MethodReturnType
{
@@ -59,6 +65,8 @@ namespace Microsoft.ReactNative
"There can be multiple initializer methods which are called in the order they were registered.")
void AddInitializer(InitializerDelegate initializer);
+ void AddJsiInitializer(JsiInitializerDelegate initializer);
+
DOC_STRING(
"Adds a constant provider method to define constants for the native module. See @ConstantProviderDelegate.")
void AddConstantProvider(ConstantProviderDelegate constantProvider);
diff --git a/vnext/Microsoft.ReactNative/JsiApi.cpp b/vnext/Microsoft.ReactNative/JsiApi.cpp
index eee8ea21a28..d074fff39f7 100644
--- a/vnext/Microsoft.ReactNative/JsiApi.cpp
+++ b/vnext/Microsoft.ReactNative/JsiApi.cpp
@@ -482,8 +482,8 @@ facebook::jsi::JSError const &jsError) { \
jsiRuntime->evaluateJavaScript(jsiPalBuffer, "Form_JSI_API_not_a_real_file");
ReactNative::JsiRuntime abiJsiResult{make(Mso::Copy(jsiRuntimeHolder), Mso::Copy(jsiRuntime))};
std::scoped_lock lock{s_mutex};
- s_jsiRuntimeMap.try_emplace(reinterpret_cast(jsiRuntime.get()), abiJsiResult);
- return abiJsiResult;
+ auto it = s_jsiRuntimeMap.try_emplace(reinterpret_cast(jsiRuntime.get()), abiJsiResult);
+ return it.first->second.get();
}
ReactNative::JsiRuntime JsiRuntime::MakeChakraRuntime() {
diff --git a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj
index 02c9a6d4d98..ef968d81f40 100644
--- a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj
+++ b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj
@@ -201,9 +201,6 @@
IJSValueWriter.idl
-
- IJSValueWriter.idl
-
IReactDispatcher.idl
diff --git a/vnext/Microsoft.ReactNative/Modules/AccessibilityInfoModule.cpp b/vnext/Microsoft.ReactNative/Modules/AccessibilityInfoModule.cpp
index af25b74353d..9e287550eee 100644
--- a/vnext/Microsoft.ReactNative/Modules/AccessibilityInfoModule.cpp
+++ b/vnext/Microsoft.ReactNative/Modules/AccessibilityInfoModule.cpp
@@ -22,12 +22,11 @@ void AccessibilityInfo::Initialize(winrt::Microsoft::ReactNative::ReactContext c
}
void AccessibilityInfo::isReduceMotionEnabled(std::function const &onSuccess) noexcept {
- auto jsDispatcher = m_context.JSDispatcher();
- m_context.UIDispatcher().Post([weakThis = weak_from_this(), jsDispatcher, onSuccess] {
+ m_context.UIDispatcher().Post([weakThis = weak_from_this(), onSuccess] {
if (auto strongThis = weakThis.lock()) {
winrt::Windows::UI::ViewManagement::UISettings uiSettings;
auto animationsEnabled = uiSettings.AnimationsEnabled();
- jsDispatcher.Post([animationsEnabled, onSuccess] { onSuccess(!animationsEnabled); });
+ onSuccess(!animationsEnabled);
}
});
}
diff --git a/vnext/Microsoft.ReactNative/Modules/AlertModule.cpp b/vnext/Microsoft.ReactNative/Modules/AlertModule.cpp
index 60eb8fb5dd1..1b8f8509064 100644
--- a/vnext/Microsoft.ReactNative/Modules/AlertModule.cpp
+++ b/vnext/Microsoft.ReactNative/Modules/AlertModule.cpp
@@ -44,7 +44,6 @@ void Alert::ProcessPendingAlertRequestsXaml() noexcept {
const auto &pendingAlert = pendingAlerts.front();
const auto &args = pendingAlert.args;
const auto &result = pendingAlert.result;
- auto jsDispatcher = m_context.JSDispatcher();
xaml::Controls::ContentDialog dialog{};
xaml::Controls::TextBlock titleTextBlock;
@@ -110,7 +109,7 @@ void Alert::ProcessPendingAlertRequestsXaml() noexcept {
} else if (IsXamlIsland()) {
// We cannot show a ContentDialog in a XAML Island unless it is assigned a
// XamlRoot instance. In such cases, we just treat the alert as dismissed.
- jsDispatcher.Post([result, this] { result(m_constants.dismissed, m_constants.buttonNeutral); });
+ result(m_constants.dismissed, m_constants.buttonNeutral);
pendingAlerts.pop();
ProcessPendingAlertRequests();
return;
@@ -138,19 +137,17 @@ void Alert::ProcessPendingAlertRequestsXaml() noexcept {
const auto hasCloseButton = dialog.CloseButtonText().size() > 0;
auto asyncOp = dialog.ShowAsync();
asyncOp.Completed(
- [hasCloseButton, jsDispatcher, result, this](
+ [hasCloseButton, result, this](
const winrt::IAsyncOperation &asyncOp, winrt::AsyncStatus status) {
switch (asyncOp.GetResults()) {
case xaml::Controls::ContentDialogResult::Primary:
- jsDispatcher.Post([result, this] { result(m_constants.buttonClicked, m_constants.buttonPositive); });
+ result(m_constants.buttonClicked, m_constants.buttonPositive);
break;
case xaml::Controls::ContentDialogResult::Secondary:
- jsDispatcher.Post([result, this] { result(m_constants.buttonClicked, m_constants.buttonNegative); });
+ result(m_constants.buttonClicked, m_constants.buttonNegative);
break;
case xaml::Controls::ContentDialogResult::None:
- jsDispatcher.Post([hasCloseButton, result, this] {
- result(hasCloseButton ? m_constants.buttonClicked : m_constants.dismissed, m_constants.buttonNeutral);
- });
+ result(hasCloseButton ? m_constants.buttonClicked : m_constants.dismissed, m_constants.buttonNeutral);
break;
default:
break;
@@ -164,7 +161,6 @@ void Alert::ProcessPendingAlertRequestsMessageDialog() noexcept {
const auto &pendingAlert = pendingAlerts.front();
const auto &args = pendingAlert.args;
const auto &result = pendingAlert.result;
- auto jsDispatcher = m_context.JSDispatcher();
auto cancelable = args.cancelable.value_or(true);
auto messageDialog = winrt::Windows::UI::Popups::MessageDialog(
@@ -208,11 +204,10 @@ void Alert::ProcessPendingAlertRequestsMessageDialog() noexcept {
auto asyncOp = messageDialog.ShowAsync();
asyncOp.Completed(
- [jsDispatcher, result, this](
+ [result, this](
const winrt::IAsyncOperation &asyncOp, winrt::AsyncStatus status) {
auto uicommand = asyncOp.GetResults();
- jsDispatcher.Post(
- [id = uicommand.Id(), result, this] { result(m_constants.buttonClicked, winrt::unbox_value(id)); });
+ result(m_constants.buttonClicked, winrt::unbox_value(uicommand.Id()));
pendingAlerts.pop();
ProcessPendingAlertRequests();
});
diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/NativeAnimatedModule.cpp b/vnext/Microsoft.ReactNative/Modules/Animated/NativeAnimatedModule.cpp
index 94766cfed17..62a433b65e0 100644
--- a/vnext/Microsoft.ReactNative/Modules/Animated/NativeAnimatedModule.cpp
+++ b/vnext/Microsoft.ReactNative/Modules/Animated/NativeAnimatedModule.cpp
@@ -56,9 +56,7 @@ void NativeAnimatedModule::getValue(double tag, std::function cons
callback = std::move(saveValueCallback)]() {
if (auto pThis = wkThis.lock()) {
pThis->m_nodesManager->GetValue(
- tag, [context = pThis->m_context, callback = std::move(callback)](double value) {
- context.JSDispatcher().Post([callback = std::move(callback), value]() { callback(value); });
- });
+ tag, [context = pThis->m_context, callback = std::move(callback)](double value) { callback(value); });
}
});
}
@@ -131,11 +129,9 @@ void NativeAnimatedModule::startAnimatingNode(
nodeTag,
animationConfig.AsObject(),
[context = pThis->m_context, endCallback = std::move(endCallback)](bool finished) {
- context.JSDispatcher().Post([finished, endCallback = std::move(endCallback)]() {
- ReactNativeSpecs::AnimatedModuleSpec_EndResult result;
- result.finished = finished;
- endCallback(std::move(result));
- });
+ ReactNativeSpecs::AnimatedModuleSpec_EndResult result;
+ result.finished = finished;
+ endCallback(std::move(result));
},
pThis->m_nodesManager);
}
diff --git a/vnext/Microsoft.ReactNative/Modules/AppStateModule.cpp b/vnext/Microsoft.ReactNative/Modules/AppStateModule.cpp
index 185b31c22d4..c6ac8d0b539 100644
--- a/vnext/Microsoft.ReactNative/Modules/AppStateModule.cpp
+++ b/vnext/Microsoft.ReactNative/Modules/AppStateModule.cpp
@@ -96,13 +96,13 @@ void AppState::SetDeactivated(bool deactivated) noexcept {
if (winrt::Microsoft::ReactNative::implementation::QuirkSettings::GetMapWindowDeactivatedToAppStateInactive(
m_context.Properties())) {
m_deactivated = deactivated;
- m_context.JSDispatcher().Post([this]() { AppStateDidChange({GetAppState()}); });
+ AppStateDidChange({GetAppState()});
}
}
void AppState::SetEnteredBackground(bool enteredBackground) noexcept {
m_enteredBackground = enteredBackground;
- m_context.JSDispatcher().Post([this]() { AppStateDidChange({GetAppState()}); });
+ AppStateDidChange({GetAppState()});
}
std::string AppState::GetAppState() noexcept {
diff --git a/vnext/Microsoft.ReactNative/Modules/ClipboardModule.cpp b/vnext/Microsoft.ReactNative/Modules/ClipboardModule.cpp
index 5b155aa3364..b4765792738 100644
--- a/vnext/Microsoft.ReactNative/Modules/ClipboardModule.cpp
+++ b/vnext/Microsoft.ReactNative/Modules/ClipboardModule.cpp
@@ -16,27 +16,25 @@ void Clipboard::Initialize(winrt::Microsoft::ReactNative::ReactContext const &re
}
void Clipboard::getString(React::ReactPromise result) noexcept {
- auto jsDispatcher = m_reactContext.JSDispatcher();
- m_reactContext.UIDispatcher().Post([jsDispatcher, result] {
+ m_reactContext.UIDispatcher().Post([result] {
auto data = DataTransfer::Clipboard::GetContent();
auto asyncOp = data.GetTextAsync();
// unfortunately, lambda captures doesn't work well with winrt::fire_and_forget and co_await here
// call asyncOp.Completed explicitly
- asyncOp.Completed([jsDispatcher, result](const IAsyncOperation &asyncOp, AsyncStatus status) {
+ asyncOp.Completed([result](const IAsyncOperation &asyncOp, AsyncStatus status) {
switch (status) {
case AsyncStatus::Completed: {
auto text = std::wstring(asyncOp.GetResults());
- jsDispatcher.Post(
- [result, text] { result.Resolve(std::string{Microsoft::Common::Unicode::Utf16ToUtf8(text)}); });
+ result.Resolve(std::string{Microsoft::Common::Unicode::Utf16ToUtf8(text)});
break;
}
case AsyncStatus::Canceled: {
- jsDispatcher.Post([result] { result.Reject(React::ReactError()); });
+ result.Reject(React::ReactError());
break;
}
case AsyncStatus::Error: {
auto message = std::wstring(winrt::hresult_error(asyncOp.ErrorCode()).message());
- jsDispatcher.Post([result, message] { result.Reject(message.c_str()); });
+ result.Reject(message.c_str());
break;
}
case AsyncStatus::Started: {
diff --git a/vnext/Microsoft.ReactNative/Modules/ImageViewManagerModule.cpp b/vnext/Microsoft.ReactNative/Modules/ImageViewManagerModule.cpp
index 1eb3e98e638..bf403ea1e49 100644
--- a/vnext/Microsoft.ReactNative/Modules/ImageViewManagerModule.cpp
+++ b/vnext/Microsoft.ReactNative/Modules/ImageViewManagerModule.cpp
@@ -109,14 +109,10 @@ void ImageLoader::getSize(std::string uri, React::ReactPromise{width, height});
- });
+ [result](double width, double height) noexcept {
+ result.Resolve(std::vector{width, height});
},
- [result, context]() noexcept {
- context.JSDispatcher().Post([result = std::move(result)]() noexcept { result.Reject("Failed"); });
- }
+ [result]() noexcept { result.Reject("Failed"); }
#ifdef USE_FABRIC
,
IsFabricEnabled(context.Properties().Handle())
@@ -138,15 +134,10 @@ void ImageLoader::getSizeWithHeaders(
context.Properties().Handle(),
std::move(uri),
std::move(headers),
- [result, context](double width, double height) noexcept {
- context.JSDispatcher().Post([result = std::move(result), width, height]() noexcept {
- result.Resolve(
- Microsoft::ReactNativeSpecs::ImageLoaderIOSSpec_getSizeWithHeaders_returnType{width, height});
- });
+ [result](double width, double height) noexcept {
+ result.Resolve(Microsoft::ReactNativeSpecs::ImageLoaderIOSSpec_getSizeWithHeaders_returnType{width, height});
},
- [result, context]() noexcept {
- context.JSDispatcher().Post([result = std::move(result)]() noexcept { result.Reject("Failed"); });
- }
+ [result]() noexcept { result.Reject("Failed"); }
#ifdef USE_FABRIC
,
IsFabricEnabled(context.Properties().Handle())
diff --git a/vnext/Microsoft.ReactNative/Modules/NativeUIManager.cpp b/vnext/Microsoft.ReactNative/Modules/NativeUIManager.cpp
index 02079637721..0a4d876bba9 100644
--- a/vnext/Microsoft.ReactNative/Modules/NativeUIManager.cpp
+++ b/vnext/Microsoft.ReactNative/Modules/NativeUIManager.cpp
@@ -998,14 +998,14 @@ void NativeUIManager::measure(
auto feView = view.try_as();
if (feView == nullptr) {
- m_context.JSDispatcher().Post([callback = std::move(callback)]() { callback(0, 0, 0, 0, 0, 0); });
+ callback(0, 0, 0, 0, 0, 0);
return;
}
// Retrieve the XAML element for the root view containing this view
auto feRootView = static_cast(shadowRoot).GetView().try_as();
if (feRootView == nullptr) {
- m_context.JSDispatcher().Post([callback = std::move(callback)]() { callback(0, 0, 0, 0, 0, 0); });
+ callback(0, 0, 0, 0, 0, 0);
return;
}
@@ -1015,9 +1015,7 @@ void NativeUIManager::measure(
// this is exactly, but it is not used anyway.
// Either codify this non-use or determine if and how we can send the needed
// data.
- m_context.JSDispatcher().Post([callback = std::move(callback), react = rectInParentCoords]() {
- callback(0, 0, react.Width, react.Height, react.X, react.Y);
- });
+ callback(0, 0, rectInParentCoords.Width, rectInParentCoords.Height, rectInParentCoords.X, rectInParentCoords.Y);
}
void NativeUIManager::measureInWindow(
@@ -1033,14 +1031,11 @@ void NativeUIManager::measureInWindow(
auto windowTransform = view.TransformToVisual(nullptr);
auto positionInWindow = windowTransform.TransformPoint({0, 0});
- m_context.JSDispatcher().Post(
- [callback = std::move(callback), pos = positionInWindow, w = view.ActualWidth(), h = view.ActualHeight()]() {
- callback(pos.X, pos.Y, w, h);
- });
+ callback(positionInWindow.X, positionInWindow.Y, view.ActualWidth(), view.ActualHeight());
return;
}
- m_context.JSDispatcher().Post([callback = std::move(callback)]() { callback(0, 0, 0, 0); });
+ callback(0, 0, 0, 0);
}
void NativeUIManager::measureLayout(
@@ -1060,15 +1055,11 @@ void NativeUIManager::measureLayout(
const auto height = static_cast(targetElement.ActualHeight());
const auto transformedBounds = ancestorTransform.TransformBounds(winrt::Rect(0, 0, width, height));
- m_context.JSDispatcher().Post([callback = std::move(callback), rect = transformedBounds]() {
- callback(rect.X, rect.Y, rect.Width, rect.Height);
- });
+ callback(transformedBounds.X, transformedBounds.Y, transformedBounds.Width, transformedBounds.Height);
} catch (winrt::hresult_error const &e) {
- m_context.JSDispatcher().Post([errorCallback = std::move(errorCallback), msg = e.message()]() {
- auto writer = React::MakeJSValueTreeWriter();
- writer.WriteString(msg);
- errorCallback(React::TakeJSValue(writer));
- });
+ auto writer = React::MakeJSValueTreeWriter();
+ writer.WriteString(e.message());
+ errorCallback(React::TakeJSValue(writer));
}
}
@@ -1082,7 +1073,7 @@ void NativeUIManager::findSubviewIn(
auto rootUIView = view.try_as();
if (rootUIView == nullptr) {
- m_context.JSDispatcher().Post([callback = std::move(callback)]() { callback(0, 0, 0, 0, 0); });
+ callback(0, 0, 0, 0, 0);
return;
}
@@ -1111,14 +1102,12 @@ void NativeUIManager::findSubviewIn(
}
if (foundElement == nullptr) {
- m_context.JSDispatcher().Post([callback = std::move(callback)]() { callback(0, 0, 0, 0, 0); });
+ callback(0, 0, 0, 0, 0);
return;
}
- m_context.JSDispatcher().Post(
- [callback = std::move(callback), foundTag, box = GetRectOfElementInParentCoords(foundElement, rootUIView)]() {
- callback(static_cast(foundTag), box.X, box.Y, box.Width, box.Height);
- });
+ auto box = GetRectOfElementInParentCoords(foundElement, rootUIView);
+ callback(static_cast(foundTag), box.X, box.Y, box.Width, box.Height);
}
void NativeUIManager::focus(int64_t reactTag) {
diff --git a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp
index 057beb22bbe..7941f125f45 100644
--- a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp
+++ b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp
@@ -4,6 +4,7 @@
#include "ReactInstanceWin.h"
#include
+#include
#include
#include
#include
@@ -64,6 +65,7 @@
#include
#include
#include
+#include
#endif
#if !defined(CORE_ABI) && !defined(USE_FABRIC)
@@ -641,15 +643,10 @@ void ReactInstanceWin::InitializeBridgeless() noexcept {
// null moduleProvider since native modules are not supported in bridgeless
LoadModules(devSettings, nullptr, m_options.TurboModuleProvider);
- auto jsDispatchQueue =
- Mso::DispatchQueue::MakeLooperQueue(CreateDispatchQueueSettings(m_reactContext->Notifications()));
- auto jsDispatcher =
- winrt::make(Mso::Copy(jsDispatchQueue));
- m_options.Properties.Set(ReactDispatcherHelper::JSDispatcherProperty(), jsDispatcher);
- m_jsMessageThread.Exchange(std::make_shared(
- jsDispatchQueue, Mso::MakeWeakMemberFunctor(this, &ReactInstanceWin::OnError)));
+ auto jsMessageThread = std::make_shared();
+ m_jsMessageThread.Exchange(jsMessageThread);
- m_jsDispatchQueue.Exchange(std::move(jsDispatchQueue));
+ std::shared_ptr callInvoker;
m_jsMessageThread.Load()->runOnQueueSync([&]() {
SetJSThreadDescription();
@@ -672,11 +669,11 @@ void ReactInstanceWin::InitializeBridgeless() noexcept {
}
m_jsiRuntimeHolder = std::make_shared(
- devSettings, m_jsMessageThread.Load(), CreatePreparedScriptStore());
+ devSettings, jsMessageThread, CreatePreparedScriptStore());
auto jsRuntime = std::make_unique(m_jsiRuntimeHolder);
jsRuntime->getRuntime();
m_bridgelessReactInstance = std::make_unique(
- std::move(jsRuntime), m_jsMessageThread.Load(), timerManager, jsErrorHandlingFunc);
+ std::move(jsRuntime), jsMessageThread, timerManager, jsErrorHandlingFunc);
auto bufferedRuntimeExecutor = m_bridgelessReactInstance->getBufferedRuntimeExecutor();
timerManager->setRuntimeExecutor(bufferedRuntimeExecutor);
@@ -684,6 +681,14 @@ void ReactInstanceWin::InitializeBridgeless() noexcept {
Microsoft::ReactNative::SchedulerSettings::SetRuntimeScheduler(
winrt::Microsoft::ReactNative::ReactPropertyBag(m_options.Properties),
m_bridgelessReactInstance->getRuntimeScheduler());
+
+ callInvoker = std::make_shared(
+ m_bridgelessReactInstance->getRuntimeScheduler());
+
+ winrt::Microsoft::ReactNative::implementation::CallInvoker::SetProperties(
+ ReactPropertyBag(m_options.Properties),
+ winrt::make(
+ *m_reactContext, std::shared_ptr(callInvoker)));
});
m_options.TurboModuleProvider->SetReactContext(
@@ -701,10 +706,8 @@ void ReactInstanceWin::InitializeBridgeless() noexcept {
};
facebook::react::bindNativeLogger(runtime, logger);
- auto turboModuleManager = std::make_shared(
- m_options.TurboModuleProvider,
- std::make_shared(
- m_bridgelessReactInstance->getRuntimeScheduler()));
+ auto turboModuleManager =
+ std::make_shared(m_options.TurboModuleProvider, callInvoker);
auto binding =
[turboModuleManager](const std::string &name) -> std::shared_ptr {
@@ -1087,7 +1090,6 @@ Mso::Future ReactInstanceWin::Destroy() noexcept {
#ifdef USE_FABRIC
if (m_bridgelessReactInstance) {
- auto jsDispatchQueue = m_jsDispatchQueue.Exchange(nullptr);
if (auto jsMessageThread = m_jsMessageThread.Exchange(nullptr)) {
jsMessageThread->runOnQueueSync([&]() noexcept {
{
@@ -1099,9 +1101,6 @@ Mso::Future ReactInstanceWin::Destroy() noexcept {
}
this->m_bridgelessReactInstance = nullptr;
jsMessageThread->quitSynchronous();
- if (jsDispatchQueue) {
- jsDispatchQueue.Shutdown(PendingTaskAction::Complete);
- }
m_whenDestroyed.SetValue();
});
}
@@ -1122,13 +1121,19 @@ ReactInstanceState ReactInstanceWin::State() const noexcept {
void ReactInstanceWin::InitJSMessageThread() noexcept {
m_instance.Exchange(std::make_shared());
+ auto callInvoker = m_instance.Load()->getJSCallInvoker();
auto scheduler = Mso::MakeJSCallInvokerScheduler(
CreateDispatchQueueSettings(m_reactContext->Notifications()),
- m_instance.Load()->getJSCallInvoker(),
+ std::shared_ptr(callInvoker),
Mso::MakeWeakMemberFunctor(this, &ReactInstanceWin::OnError),
Mso::Copy(m_whenDestroyed));
auto jsDispatchQueue = Mso::DispatchQueue::MakeCustomQueue(Mso::CntPtr(scheduler));
+ winrt::Microsoft::ReactNative::implementation::CallInvoker::SetProperties(
+ ReactPropertyBag(m_options.Properties),
+ winrt::make(
+ *m_reactContext, std::shared_ptr(callInvoker)));
+
auto jsDispatcher =
winrt::make(Mso::Copy(jsDispatchQueue));
m_options.Properties.Set(ReactDispatcherHelper::JSDispatcherProperty(), jsDispatcher);
diff --git a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.h b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.h
index 69a00df3de5..36c153df4e9 100644
--- a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.h
+++ b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.h
@@ -187,7 +187,7 @@ class ReactInstanceWin final : public Mso::ActiveObject
mutable std::mutex m_mutex;
// !Bridgeless
- const Mso::ActiveReadableField m_jsDispatchQueue{Queue(), m_mutex};
+ const Mso::ActiveReadableField m_jsDispatchQueue{nullptr, Queue(), m_mutex};
const Mso::ActiveReadableField> m_jsMessageThread{
Queue(),
m_mutex};
diff --git a/vnext/Microsoft.ReactNative/ReactInstanceSettings.cpp b/vnext/Microsoft.ReactNative/ReactInstanceSettings.cpp
index ce0b63b82f4..943def44914 100644
--- a/vnext/Microsoft.ReactNative/ReactInstanceSettings.cpp
+++ b/vnext/Microsoft.ReactNative/ReactInstanceSettings.cpp
@@ -20,6 +20,12 @@ winrt::Microsoft::ReactNative::IReactContext InstanceCreatedEventArgs::Context()
return m_context;
}
+winrt::Windows::Foundation::IInspectable InstanceCreatedEventArgs::RuntimeHandle() noexcept {
+ return winrt::get_self(m_context)
+ ->GetInner()
+ .JsiRuntime();
+}
+
InstanceLoadedEventArgs::InstanceLoadedEventArgs(Mso::CntPtr &&context, bool failed)
: m_context(winrt::make(std::move(context))), m_failed(failed) {}
@@ -31,6 +37,12 @@ winrt::Microsoft::ReactNative::IReactContext InstanceLoadedEventArgs::Context()
return m_context;
}
+winrt::Windows::Foundation::IInspectable InstanceLoadedEventArgs::RuntimeHandle() noexcept {
+ return winrt::get_self(m_context)
+ ->GetInner()
+ .JsiRuntime();
+}
+
InstanceDestroyedEventArgs::InstanceDestroyedEventArgs(Mso::CntPtr &&context)
: m_context(winrt::make(std::move(context))) {}
diff --git a/vnext/Microsoft.ReactNative/ReactInstanceSettings.h b/vnext/Microsoft.ReactNative/ReactInstanceSettings.h
index 042b5cdc896..43740c8a630 100644
--- a/vnext/Microsoft.ReactNative/ReactInstanceSettings.h
+++ b/vnext/Microsoft.ReactNative/ReactInstanceSettings.h
@@ -19,6 +19,7 @@ struct InstanceCreatedEventArgs : InstanceCreatedEventArgsT &&context);
winrt::Microsoft::ReactNative::IReactContext Context() noexcept;
+ winrt::Windows::Foundation::IInspectable RuntimeHandle() noexcept;
private:
winrt::Microsoft::ReactNative::IReactContext m_context;
@@ -29,6 +30,7 @@ struct InstanceLoadedEventArgs : InstanceLoadedEventArgsT &&context, bool failed);
winrt::Microsoft::ReactNative::IReactContext Context() noexcept;
+ winrt::Windows::Foundation::IInspectable RuntimeHandle() noexcept;
bool Failed() noexcept;
private:
diff --git a/vnext/Microsoft.ReactNative/ReactInstanceSettings.idl b/vnext/Microsoft.ReactNative/ReactInstanceSettings.idl
index f3703cdc038..be1cdb8afb8 100644
--- a/vnext/Microsoft.ReactNative/ReactInstanceSettings.idl
+++ b/vnext/Microsoft.ReactNative/ReactInstanceSettings.idl
@@ -45,6 +45,9 @@ namespace Microsoft.ReactNative
{
DOC_STRING("Gets the @IReactContext for the React instance that was just created.")
IReactContext Context { get; };
+
+ DOC_STRING("Provides access to the jsi::Runtime for synchronous access using GetOrCreateContextRuntime")
+ Object RuntimeHandle { get; };
}
[webhosthidden]
@@ -57,6 +60,9 @@ namespace Microsoft.ReactNative
DOC_STRING("Returns `true` if the JavaScript bundle failed to load.")
Boolean Failed { get; };
+
+ DOC_STRING("Provides access to the jsi::Runtime for synchronous access using GetOrCreateContextRuntime")
+ Object RuntimeHandle { get; };
}
[webhosthidden]
diff --git a/vnext/Microsoft.ReactNative/SchedulerSettings.cpp b/vnext/Microsoft.ReactNative/SchedulerSettings.cpp
index 605af5a3829..f4c842ae66c 100644
--- a/vnext/Microsoft.ReactNative/SchedulerSettings.cpp
+++ b/vnext/Microsoft.ReactNative/SchedulerSettings.cpp
@@ -49,17 +49,17 @@ facebook::react::RuntimeExecutor &GetRuntimeExecutor(
}
winrt::Microsoft::ReactNative::ReactPropertyId<
- winrt::Microsoft::ReactNative::ReactNonAbiValue>>
+ winrt::Microsoft::ReactNative::ReactNonAbiValue>>
RuntimeSchedulerProperty() noexcept {
winrt::Microsoft::ReactNative::ReactPropertyId<
- winrt::Microsoft::ReactNative::ReactNonAbiValue>>
+ winrt::Microsoft::ReactNative::ReactNonAbiValue>>
propId{L"ReactNative", L"RuntimeScheduler"};
return propId;
}
void SetRuntimeScheduler(
winrt::Microsoft::ReactNative::ReactPropertyBag properties,
- const std::shared_ptr &runtimeScheduler) noexcept {
+ const std::weak_ptr &runtimeScheduler) noexcept {
properties.Set(RuntimeSchedulerProperty(), runtimeScheduler);
}
@@ -68,7 +68,7 @@ std::shared_ptr RuntimeSchedulerFromPropertie
auto runtimeScheduler = properties.Get(RuntimeSchedulerProperty());
if (!runtimeScheduler)
return nullptr;
- return runtimeScheduler.Value();
+ return runtimeScheduler.Value().lock();
}
} // namespace Microsoft::ReactNative::SchedulerSettings
diff --git a/vnext/Microsoft.ReactNative/SchedulerSettings.h b/vnext/Microsoft.ReactNative/SchedulerSettings.h
index dec7b296438..f92dd98df59 100644
--- a/vnext/Microsoft.ReactNative/SchedulerSettings.h
+++ b/vnext/Microsoft.ReactNative/SchedulerSettings.h
@@ -27,7 +27,7 @@ facebook::react::RuntimeExecutor &GetRuntimeExecutor(
void SetRuntimeScheduler(
winrt::Microsoft::ReactNative::ReactPropertyBag properties,
- const std::shared_ptr &runtimeScheduler) noexcept;
+ const std::weak_ptr &runtimeScheduler) noexcept;
std::shared_ptr RuntimeSchedulerFromProperties(
winrt::Microsoft::ReactNative::ReactPropertyBag properties) noexcept;
diff --git a/vnext/Microsoft.ReactNative/TurboModulesProvider.cpp b/vnext/Microsoft.ReactNative/TurboModulesProvider.cpp
index 294e48b4f55..a89031616e9 100644
--- a/vnext/Microsoft.ReactNative/TurboModulesProvider.cpp
+++ b/vnext/Microsoft.ReactNative/TurboModulesProvider.cpp
@@ -7,6 +7,7 @@
#include "pch.h"
#include "TurboModulesProvider.h"
+#include
#include
#include
#include "CallInvokerWriter.h"
@@ -42,6 +43,14 @@ struct TurboModuleBuilder : winrt::implements(m_reactContext)
+ ->GetInner()
+ .JsiRuntime());
+ }
+
void AddConstantProvider(ConstantProviderDelegate const &constantProvider) noexcept {
EnsureMemberNotSet("getConstants", false);
m_constantProviders.push_back(constantProvider);
@@ -119,6 +128,10 @@ class TurboModuleImpl : public facebook::react::TurboModule {
m_moduleBuilder(winrt::make_self(reactContext)),
m_providedModule(reactModuleProvider(m_moduleBuilder.as())) {
if (auto hostObject = m_providedModule.try_as()) {
+ // Force ABI runtime creation if it hasn't already been created
+ winrt::get_self(m_reactContext)
+ ->GetInner()
+ .JsiRuntime();
m_hostObjectWrapper = std::make_shared(hostObject);
}
}
diff --git a/vnext/PropertySheets/React.Cpp.props b/vnext/PropertySheets/React.Cpp.props
index fe6b132e839..d5ca9ebe3df 100644
--- a/vnext/PropertySheets/React.Cpp.props
+++ b/vnext/PropertySheets/React.Cpp.props
@@ -155,6 +155,7 @@
$(ReactNativeDir)\ReactCommon\react\bridging;
$(ReactNativeDir)\ReactCommon\reactperflogger;
$(ReactNativeDir)\ReactCommon\runtimeexecutor;
+ $(ReactNativeDir)\ReactCxxPlatform;
%(AdditionalIncludeDirectories);
diff --git a/vnext/ReactCommon/TEMP_UntilReactCommonUpdate/jserrorhandler/JsErrorHandler.cpp b/vnext/ReactCommon/TEMP_UntilReactCommonUpdate/jserrorhandler/JsErrorHandler.cpp
new file mode 100644
index 00000000000..fc5ff6cfabc
--- /dev/null
+++ b/vnext/ReactCommon/TEMP_UntilReactCommonUpdate/jserrorhandler/JsErrorHandler.cpp
@@ -0,0 +1,429 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+#include "JsErrorHandler.h"
+#include
+#include
+#include
+#include
+#include
+#include "StackTraceParser.h"
+
+using namespace facebook;
+
+namespace {
+std::string quote(const std::string& view) {
+ return "\"" + view + "\"";
+}
+
+int nextExceptionId() {
+ static int exceptionId = 0;
+ return exceptionId++;
+}
+
+bool isLooselyNull(const jsi::Value& value) {
+ return value.isNull() || value.isUndefined();
+}
+
+bool isEqualTo(
+ jsi::Runtime& runtime,
+ const jsi::Value& value,
+ const std::string& str) {
+ return jsi::Value::strictEquals(
+ runtime, value, jsi::String::createFromUtf8(runtime, str));
+}
+
+std::string stringifyToCpp(jsi::Runtime& runtime, const jsi::Value& value) {
+ return value.toString(runtime).utf8(runtime);
+}
+
+bool isTruthy(jsi::Runtime& runtime, const jsi::Value& value) {
+ auto Boolean = runtime.global().getPropertyAsFunction(runtime, "Boolean");
+ return Boolean.call(runtime, value).getBool();
+}
+
+void objectAssign(
+ jsi::Runtime& runtime,
+ jsi::Object& target,
+ const jsi::Object& value) {
+ auto Object = runtime.global().getPropertyAsObject(runtime, "Object");
+ auto assign = Object.getPropertyAsFunction(runtime, "assign");
+ assign.callWithThis(runtime, Object, target, value);
+}
+
+jsi::Object wrapInErrorIfNecessary(
+ jsi::Runtime& runtime,
+ const jsi::Value& value) {
+ auto Error = runtime.global().getPropertyAsFunction(runtime, "Error");
+ auto isError =
+ value.isObject() && value.asObject(runtime).instanceOf(runtime, Error);
+ auto error = isError
+ ? value.getObject(runtime)
+ : Error.callAsConstructor(runtime, value).getObject(runtime);
+ return error;
+}
+
+class SetFalseOnDestruct {
+ std::shared_ptr _value;
+
+ public:
+ SetFalseOnDestruct(const SetFalseOnDestruct&) = delete;
+ SetFalseOnDestruct& operator=(const SetFalseOnDestruct&) = delete;
+ SetFalseOnDestruct(SetFalseOnDestruct&&) = delete;
+ SetFalseOnDestruct& operator=(SetFalseOnDestruct&&) = delete;
+ explicit SetFalseOnDestruct(std::shared_ptr value)
+ : _value(std::move(value)) {}
+ ~SetFalseOnDestruct() {
+ *_value = false;
+ }
+};
+
+void logErrorWhileReporting(
+ std::string message,
+ jsi::JSError& error,
+ jsi::JSError& originalError) {
+ LOG(ERROR) << "JsErrorHandler::" << message << std::endl
+ << "Js error message: " << error.getMessage() << std::endl
+ << "Original js error message: " << originalError.getMessage()
+ << std::endl;
+}
+
+jsi::Value getBundleMetadata(jsi::Runtime& runtime, jsi::JSError& error) {
+ auto jsGetBundleMetadataValue =
+ runtime.global().getProperty(runtime, "__getBundleMetadata");
+
+ if (!jsGetBundleMetadataValue.isObject() ||
+ !jsGetBundleMetadataValue.asObject(runtime).isFunction(runtime)) {
+ return jsi::Value::null();
+ }
+
+ auto jsGetBundleMetadataValueFn =
+ jsGetBundleMetadataValue.asObject(runtime).asFunction(runtime);
+
+ try {
+ auto bundleMetadataValue = jsGetBundleMetadataValueFn.call(runtime);
+ if (bundleMetadataValue.isObject()) {
+ return bundleMetadataValue;
+ }
+ return bundleMetadataValue;
+ } catch (jsi::JSError& ex) {
+ logErrorWhileReporting(
+ "getBundleMetadata(): Error raised while calling __getBundleMetadata(). Returning null.",
+ ex,
+ error);
+ }
+
+ return jsi::Value::null();
+}
+} // namespace
+
+namespace facebook::react {
+
+template <>
+struct Bridging {
+ static jsi::Value toJs(
+ jsi::Runtime& runtime,
+ const JsErrorHandler::ProcessedError::StackFrame& frame) {
+ auto stackFrame = jsi::Object(runtime);
+ auto file = bridging::toJs(runtime, frame.file, nullptr);
+ auto lineNumber = bridging::toJs(runtime, frame.lineNumber, nullptr);
+ auto column = bridging::toJs(runtime, frame.column, nullptr);
+
+ stackFrame.setProperty(runtime, "file", file);
+ stackFrame.setProperty(runtime, "methodName", frame.methodName);
+ stackFrame.setProperty(runtime, "lineNumber", lineNumber);
+ stackFrame.setProperty(runtime, "column", column);
+ return stackFrame;
+ }
+};
+
+template <>
+struct Bridging {
+ static jsi::Value toJs(
+ jsi::Runtime& runtime,
+ const JsErrorHandler::ProcessedError& error) {
+ auto data = jsi::Object(runtime);
+ data.setProperty(runtime, "message", error.message);
+ data.setProperty(
+ runtime,
+ "originalMessage",
+ bridging::toJs(runtime, error.originalMessage, nullptr));
+ data.setProperty(
+ runtime, "name", bridging::toJs(runtime, error.name, nullptr));
+ data.setProperty(
+ runtime,
+ "componentStack",
+ bridging::toJs(runtime, error.componentStack, nullptr));
+
+ auto stack = jsi::Array(runtime, error.stack.size());
+ for (size_t i = 0; i < error.stack.size(); i++) {
+ auto& frame = error.stack[i];
+ stack.setValueAtIndex(runtime, i, bridging::toJs(runtime, frame));
+ }
+
+ data.setProperty(runtime, "stack", stack);
+ data.setProperty(runtime, "id", error.id);
+ data.setProperty(runtime, "isFatal", error.isFatal);
+ data.setProperty(runtime, "extraData", error.extraData);
+ return data;
+ }
+};
+
+std::ostream& operator<<(
+ std::ostream& os,
+ const JsErrorHandler::ProcessedError::StackFrame& frame) {
+ auto file = frame.file ? quote(*frame.file) : "nil";
+ auto methodName = quote(frame.methodName);
+ auto lineNumber =
+ frame.lineNumber ? std::to_string(*frame.lineNumber) : "nil";
+ auto column = frame.column ? std::to_string(*frame.column) : "nil";
+
+ os << "StackFrame { .file = " << file << ", .methodName = " << methodName
+ << ", .lineNumber = " << lineNumber << ", .column = " << column << " }";
+ return os;
+}
+std::ostream& operator<<(
+ std::ostream& os,
+ const JsErrorHandler::ProcessedError& error) {
+ auto message = quote(error.message);
+ auto originalMessage =
+ error.originalMessage ? quote(*error.originalMessage) : "nil";
+ auto name = error.name ? quote(*error.name) : "nil";
+ auto componentStack =
+ error.componentStack ? quote(*error.componentStack) : "nil";
+ auto id = std::to_string(error.id);
+ auto isFatal = std::to_string(static_cast(error.isFatal));
+ auto extraData = "jsi::Object{ } ";
+
+ os << "ProcessedError {\n"
+ << " .message = " << message << "\n"
+ << " .originalMessage = " << originalMessage << "\n"
+ << " .name = " << name << "\n"
+ << " .componentStack = " << componentStack << "\n"
+ << " .stack = [\n";
+
+ for (const auto& frame : error.stack) {
+ os << " " << frame << ", \n";
+ }
+ os << " ]\n"
+ << " .id = " << id << "\n"
+ << " .isFatal " << isFatal << "\n"
+ << " .extraData = " << extraData << "\n"
+ << "}";
+ return os;
+}
+
+JsErrorHandler::JsErrorHandler(JsErrorHandler::OnJsError onJsError)
+ : _onJsError(std::move(onJsError)),
+ _inErrorHandler(std::make_shared(false)){
+
+ };
+
+JsErrorHandler::~JsErrorHandler() {}
+
+void JsErrorHandler::handleError(
+ jsi::Runtime& runtime,
+ jsi::JSError& error,
+ bool isFatal,
+ bool logToConsole) {
+ // TODO: Current error parsing works and is stable. Can investigate using
+ // REGEX_HERMES to get additional Hermes data, though it requires JS setup
+
+ if (!ReactNativeFeatureFlags::useAlwaysAvailableJSErrorHandling() &&
+ _isRuntimeReady) {
+ try {
+ handleJSError(runtime, error, isFatal);
+ return;
+ } catch (jsi::JSError& ex) {
+ logErrorWhileReporting(
+ "handleError(): Error raised while reporting using js pipeline. Using c++ pipeline instead.",
+ ex,
+ error);
+
+ // Re-try reporting using the c++ pipeline
+ _hasHandledFatalError = false;
+ }
+ }
+
+ handleErrorWithCppPipeline(runtime, error, isFatal, logToConsole);
+}
+
+void JsErrorHandler::handleErrorWithCppPipeline(
+ jsi::Runtime& runtime,
+ jsi::JSError& error,
+ bool isFatal,
+ bool logToConsole) {
+ *_inErrorHandler = true;
+ SetFalseOnDestruct temp{_inErrorHandler};
+
+ auto message = error.getMessage();
+ auto errorObj = wrapInErrorIfNecessary(runtime, error.value());
+ auto componentStackValue = errorObj.getProperty(runtime, "componentStack");
+ if (!isLooselyNull(componentStackValue)) {
+ message += "\n" + stringifyToCpp(runtime, componentStackValue);
+ }
+
+ auto nameValue = errorObj.getProperty(runtime, "name");
+ auto name = (isLooselyNull(nameValue) || isEqualTo(runtime, nameValue, ""))
+ ? std::nullopt
+ : std::optional(stringifyToCpp(runtime, nameValue));
+
+ if (name && !message.starts_with(*name + ": ")) {
+ message = *name + ": " + message;
+ }
+
+ auto jsEngineValue = errorObj.getProperty(runtime, "jsEngine");
+
+ if (!isLooselyNull(jsEngineValue)) {
+ message += ", js engine: " + stringifyToCpp(runtime, jsEngineValue);
+ }
+
+ auto extraDataKey = jsi::PropNameID::forUtf8(runtime, "RN$ErrorExtraDataKey");
+ auto extraDataValue = errorObj.getProperty(runtime, extraDataKey);
+
+ auto extraData = jsi::Object(runtime);
+ if (extraDataValue.isObject()) {
+ objectAssign(runtime, extraData, extraDataValue.asObject(runtime));
+ }
+
+ auto isDEV =
+ isTruthy(runtime, runtime.global().getProperty(runtime, "__DEV__"));
+
+ extraData.setProperty(runtime, "jsEngine", jsEngineValue);
+ extraData.setProperty(runtime, "rawStack", error.getStack());
+ extraData.setProperty(runtime, "__DEV__", isDEV);
+ extraData.setProperty(
+ runtime, "bundleMetadata", getBundleMetadata(runtime, error));
+
+ auto cause = errorObj.getProperty(runtime, "cause");
+ if (cause.isObject()) {
+ auto causeObj = cause.asObject(runtime);
+ // TODO: Consider just forwarding all properties. For now, just forward the
+ // stack properties to maintain symmetry with js pipeline
+ auto stackSymbols = causeObj.getProperty(runtime, "stackSymbols");
+ extraData.setProperty(runtime, "stackSymbols", stackSymbols);
+
+ auto stackReturnAddresses =
+ causeObj.getProperty(runtime, "stackReturnAddresses");
+ extraData.setProperty(
+ runtime, "stackReturnAddresses", stackReturnAddresses);
+
+ auto stackElements = causeObj.getProperty(runtime, "stackElements");
+ extraData.setProperty(runtime, "stackElements", stackElements);
+ }
+
+ auto originalMessage = message == error.getMessage()
+ ? std::nullopt
+ : std::optional(error.getMessage());
+
+ auto componentStack = !componentStackValue.isString()
+ ? std::nullopt
+ : std::optional(componentStackValue.asString(runtime).utf8(runtime));
+
+ auto isHermes = runtime.global().hasProperty(runtime, "HermesInternal");
+ auto stackFrames = StackTraceParser::parse(isHermes, error.getStack());
+
+ auto id = nextExceptionId();
+
+ ProcessedError processedError = {
+ .message =
+ _isRuntimeReady ? message : ("[runtime not ready]: " + message),
+ .originalMessage = originalMessage,
+ .name = name,
+ .componentStack = componentStack,
+ .stack = stackFrames,
+ .id = id,
+ .isFatal = isFatal,
+ .extraData = std::move(extraData),
+ };
+
+ auto data = bridging::toJs(runtime, processedError).asObject(runtime);
+
+ auto isComponentError =
+ isTruthy(runtime, errorObj.getProperty(runtime, "isComponentError"));
+ data.setProperty(runtime, "isComponentError", isComponentError);
+
+ if (logToConsole && runtime.global().hasProperty(runtime, "console")) { // [Windows] Added hasProperty check
+ auto console = runtime.global().getPropertyAsObject(runtime, "console");
+ auto errorFn = console.getPropertyAsFunction(runtime, "error");
+ auto finalMessage =
+ jsi::String::createFromUtf8(runtime, processedError.message);
+ errorFn.callWithThis(runtime, console, finalMessage);
+ }
+
+ std::shared_ptr shouldPreventDefault = std::make_shared(false);
+ auto preventDefault = jsi::Function::createFromHostFunction(
+ runtime,
+ jsi::PropNameID::forAscii(runtime, "preventDefault"),
+ 0,
+ [shouldPreventDefault](
+ jsi::Runtime& /*rt*/,
+ const jsi::Value& /*thisVal*/,
+ const jsi::Value* /*args*/,
+ size_t /*count*/) {
+ *shouldPreventDefault = true;
+ return jsi::Value::undefined();
+ });
+
+ data.setProperty(runtime, "preventDefault", preventDefault);
+
+ for (auto& errorListener : _errorListeners) {
+ try {
+ errorListener(runtime, jsi::Value(runtime, data));
+ } catch (jsi::JSError& ex) {
+ logErrorWhileReporting(
+ "handleErrorWithCppPipeline(): Error raised inside an error listener. Executing next listener.",
+ ex,
+ error);
+ }
+ }
+
+ if (*shouldPreventDefault) {
+ return;
+ }
+
+ auto errorType = errorObj.getProperty(runtime, "type");
+ auto isWarn = isEqualTo(runtime, errorType, "warn");
+
+ if (isFatal || !isWarn) {
+ if (isFatal) {
+ if (_hasHandledFatalError) {
+ return;
+ }
+ _hasHandledFatalError = true;
+ }
+
+ _onJsError(runtime, processedError);
+ }
+}
+
+void JsErrorHandler::registerErrorListener(
+ const std::function& errorListener) {
+ _errorListeners.push_back(errorListener);
+}
+
+bool JsErrorHandler::hasHandledFatalError() {
+ return _hasHandledFatalError;
+}
+
+void JsErrorHandler::setRuntimeReady() {
+ _isRuntimeReady = true;
+}
+
+bool JsErrorHandler::isRuntimeReady() {
+ return _isRuntimeReady;
+}
+
+void JsErrorHandler::notifyOfFatalError() {
+ _hasHandledFatalError = true;
+}
+
+bool JsErrorHandler::inErrorHandler() {
+ return *_inErrorHandler;
+}
+
+} // namespace facebook::react
diff --git a/vnext/Shared/Modules/BlobModule.cpp b/vnext/Shared/Modules/BlobModule.cpp
index d54220cdb61..d0754c15db1 100644
--- a/vnext/Shared/Modules/BlobModule.cpp
+++ b/vnext/Shared/Modules/BlobModule.cpp
@@ -35,28 +35,26 @@ namespace Microsoft::React {
#pragma region BlobTurboModule
-void BlobTurboModule::Initialize(msrn::ReactContext const &reactContext) noexcept {
+void BlobTurboModule::Initialize(msrn::ReactContext const &reactContext, facebook::jsi::Runtime &runtime) noexcept {
m_resource = IBlobResource::Make(reactContext.Properties().Handle());
m_resource->Callbacks().OnError = [&reactContext](string &&errorText) {
Modules::SendEvent(reactContext, L"blobFailed", {errorText});
};
namespace jsi = facebook::jsi;
- msrn::ExecuteJsi(reactContext, [resource = m_resource](jsi::Runtime &runtime) {
- runtime.global().setProperty(
- runtime,
- "__blobCollectorProvider",
- jsi::Function::createFromHostFunction(
- runtime,
- jsi::PropNameID::forAscii(runtime, "__blobCollectorProvider"),
- 1,
- [resource](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) {
- auto blobId = args[0].asString(rt).utf8(rt);
- auto collector = std::make_shared(blobId, resource);
-
- return jsi::Object::createFromHostObject(rt, collector);
- }));
- });
+ runtime.global().setProperty(
+ runtime,
+ "__blobCollectorProvider",
+ jsi::Function::createFromHostFunction(
+ runtime,
+ jsi::PropNameID::forAscii(runtime, "__blobCollectorProvider"),
+ 1,
+ [resource = m_resource](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) {
+ auto blobId = args[0].asString(rt).utf8(rt);
+ auto collector = std::make_shared(blobId, resource);
+
+ return jsi::Object::createFromHostObject(rt, collector);
+ }));
}
ReactNativeSpecs::BlobModuleSpec_Constants BlobTurboModule::GetConstants() noexcept {
diff --git a/vnext/Shared/Modules/BlobModule.h b/vnext/Shared/Modules/BlobModule.h
index 382129d202d..a541c8f7752 100644
--- a/vnext/Shared/Modules/BlobModule.h
+++ b/vnext/Shared/Modules/BlobModule.h
@@ -24,7 +24,9 @@ struct BlobTurboModule {
using ModuleSpec = ReactNativeSpecs::BlobModuleSpec;
REACT_INIT(Initialize)
- void Initialize(winrt::Microsoft::ReactNative::ReactContext const &reactContext) noexcept;
+ void Initialize(
+ winrt::Microsoft::ReactNative::ReactContext const &reactContext,
+ facebook::jsi::Runtime &runtime) noexcept;
REACT_GET_CONSTANTS(GetConstants)
ReactNativeSpecs::BlobModuleSpec_Constants GetConstants() noexcept;
diff --git a/vnext/Shared/Shared.vcxitems b/vnext/Shared/Shared.vcxitems
index 6b4703eb94e..ae3676e070b 100644
--- a/vnext/Shared/Shared.vcxitems
+++ b/vnext/Shared/Shared.vcxitems
@@ -342,6 +342,7 @@
$(MSBuildThisFileDirectory)..\Microsoft.ReactNative\JsiApi.idl
+
true
@@ -672,6 +673,8 @@
+
+
diff --git a/vnext/Shared/Shared.vcxitems.filters b/vnext/Shared/Shared.vcxitems.filters
index 9a6c83c196c..1bc977f8e60 100644
--- a/vnext/Shared/Shared.vcxitems.filters
+++ b/vnext/Shared/Shared.vcxitems.filters
@@ -342,6 +342,7 @@
+