From c354be77642610d77f0d12784e28b0e6b99f81da Mon Sep 17 00:00:00 2001 From: Andrew Coates <30809111+acoates-ms@users.noreply.github.com> Date: Wed, 7 May 2025 13:21:08 -0700 Subject: [PATCH 01/17] Remove usage of JSDispatcher in various built-in modules --- .../Modules/AccessibilityInfoModule.cpp | 5 +-- .../Modules/AlertModule.cpp | 19 ++++------ .../Modules/Animated/NativeAnimatedModule.cpp | 10 ++--- .../Modules/AppStateModule.cpp | 4 +- .../Modules/ClipboardModule.cpp | 12 +++--- .../Modules/ImageViewManagerModule.cpp | 22 +++++------ .../Modules/NativeUIManager.cpp | 37 +++++++------------ 7 files changed, 42 insertions(+), 67 deletions(-) diff --git a/vnext/Microsoft.ReactNative/Modules/AccessibilityInfoModule.cpp b/vnext/Microsoft.ReactNative/Modules/AccessibilityInfoModule.cpp index af25b74353d..0e7e8ae0270 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..d0971c50033 100644 --- a/vnext/Microsoft.ReactNative/Modules/Animated/NativeAnimatedModule.cpp +++ b/vnext/Microsoft.ReactNative/Modules/Animated/NativeAnimatedModule.cpp @@ -57,7 +57,7 @@ void NativeAnimatedModule::getValue(double tag, std::function cons 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); }); + callback(value); }); } }); @@ -131,11 +131,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..9690f505078 100644 --- a/vnext/Microsoft.ReactNative/Modules/ImageViewManagerModule.cpp +++ b/vnext/Microsoft.ReactNative/Modules/ImageViewManagerModule.cpp @@ -109,13 +109,11 @@ 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 , @@ -138,14 +136,12 @@ 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 , 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) { From a706f6e855b6b48dd084dc6fe859fc95fcf5b8bf Mon Sep 17 00:00:00 2001 From: Andrew Coates <30809111+acoates-ms@users.noreply.github.com> Date: Wed, 7 May 2025 13:21:15 -0700 Subject: [PATCH 02/17] Change files --- ...ative-windows-8c0a3d4a-27a0-4b81-810c-1578749610a8.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/react-native-windows-8c0a3d4a-27a0-4b81-810c-1578749610a8.json 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" +} From 513672de9dcb497621fe015d49ee6576e64e4afd Mon Sep 17 00:00:00 2001 From: Andrew Coates <30809111+acoates-ms@users.noreply.github.com> Date: Wed, 7 May 2025 13:43:38 -0700 Subject: [PATCH 03/17] format --- .../Modules/AccessibilityInfoModule.cpp | 2 +- .../Modules/Animated/NativeAnimatedModule.cpp | 4 +--- .../Modules/ImageViewManagerModule.cpp | 11 +++-------- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/vnext/Microsoft.ReactNative/Modules/AccessibilityInfoModule.cpp b/vnext/Microsoft.ReactNative/Modules/AccessibilityInfoModule.cpp index 0e7e8ae0270..9e287550eee 100644 --- a/vnext/Microsoft.ReactNative/Modules/AccessibilityInfoModule.cpp +++ b/vnext/Microsoft.ReactNative/Modules/AccessibilityInfoModule.cpp @@ -26,7 +26,7 @@ void AccessibilityInfo::isReduceMotionEnabled(std::function const &o if (auto strongThis = weakThis.lock()) { winrt::Windows::UI::ViewManagement::UISettings uiSettings; auto animationsEnabled = uiSettings.AnimationsEnabled(); - onSuccess(!animationsEnabled);; + onSuccess(!animationsEnabled); } }); } diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/NativeAnimatedModule.cpp b/vnext/Microsoft.ReactNative/Modules/Animated/NativeAnimatedModule.cpp index d0971c50033..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) { - callback(value); - }); + tag, [context = pThis->m_context, callback = std::move(callback)](double value) { callback(value); }); } }); } diff --git a/vnext/Microsoft.ReactNative/Modules/ImageViewManagerModule.cpp b/vnext/Microsoft.ReactNative/Modules/ImageViewManagerModule.cpp index 9690f505078..bf403ea1e49 100644 --- a/vnext/Microsoft.ReactNative/Modules/ImageViewManagerModule.cpp +++ b/vnext/Microsoft.ReactNative/Modules/ImageViewManagerModule.cpp @@ -112,9 +112,7 @@ void ImageLoader::getSize(std::string uri, React::ReactPromise{width, height}); }, - [result]() noexcept { - result.Reject("Failed"); - } + [result]() noexcept { result.Reject("Failed"); } #ifdef USE_FABRIC , IsFabricEnabled(context.Properties().Handle()) @@ -137,12 +135,9 @@ void ImageLoader::getSizeWithHeaders( std::move(uri), std::move(headers), [result](double width, double height) noexcept { - result.Resolve( - Microsoft::ReactNativeSpecs::ImageLoaderIOSSpec_getSizeWithHeaders_returnType{width, height}); + result.Resolve(Microsoft::ReactNativeSpecs::ImageLoaderIOSSpec_getSizeWithHeaders_returnType{width, height}); }, - [result]() noexcept { - result.Reject("Failed"); - } + [result]() noexcept { result.Reject("Failed"); } #ifdef USE_FABRIC , IsFabricEnabled(context.Properties().Handle()) From 0285a3172adf75052706b6ac4ecce347fc64f2ca Mon Sep 17 00:00:00 2001 From: Andrew Coates <30809111+acoates-ms@users.noreply.github.com> Date: Wed, 7 May 2025 15:43:40 -0700 Subject: [PATCH 04/17] Implement public JSDispatcher on top of CallInvoker --- vnext/Desktop/React.Windows.Desktop.vcxproj | 2 +- .../CallInvokerDispatcher.cpp | 24 ++++ .../CallInvokerDispatcher.h | 21 ++++ ...vokerWriter.cpp => JSDispatcherWriter.cpp} | 114 +++++++++--------- ...llInvokerWriter.h => JSDispatcherWriter.h} | 17 ++- .../Microsoft.ReactNative.vcxproj | 2 +- .../ReactHost/ReactInstanceWin.cpp | 12 +- .../TurboModulesProvider.cpp | 24 ++-- vnext/Shared/Shared.vcxitems | 5 +- 9 files changed, 136 insertions(+), 85 deletions(-) create mode 100644 vnext/Microsoft.ReactNative/CallInvokerDispatcher.cpp create mode 100644 vnext/Microsoft.ReactNative/CallInvokerDispatcher.h rename vnext/Microsoft.ReactNative/{CallInvokerWriter.cpp => JSDispatcherWriter.cpp} (51%) rename vnext/Microsoft.ReactNative/{CallInvokerWriter.h => JSDispatcherWriter.h} (69%) diff --git a/vnext/Desktop/React.Windows.Desktop.vcxproj b/vnext/Desktop/React.Windows.Desktop.vcxproj index 52387dbd75f..f8717bc7976 100644 --- a/vnext/Desktop/React.Windows.Desktop.vcxproj +++ b/vnext/Desktop/React.Windows.Desktop.vcxproj @@ -243,7 +243,7 @@ ..\Microsoft.ReactNative\IJSValueWriter.idl - + ..\Microsoft.ReactNative\IJSValueWriter.idl diff --git a/vnext/Microsoft.ReactNative/CallInvokerDispatcher.cpp b/vnext/Microsoft.ReactNative/CallInvokerDispatcher.cpp new file mode 100644 index 00000000000..5582ae55e21 --- /dev/null +++ b/vnext/Microsoft.ReactNative/CallInvokerDispatcher.cpp @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "CallInvokerDispatcher.h" + +namespace Microsoft::ReactNative { + + CallInvokerDispatcher::CallInvokerDispatcher(std::shared_ptr &&callInvoker) noexcept + : m_callInvoker(callInvoker) + {} + + bool CallInvokerDispatcher::HasThreadAccess() noexcept { + return m_threadId == std::this_thread::get_id(); + } + + void CallInvokerDispatcher::Post(winrt::Microsoft::ReactNative::ReactDispatcherCallback const &callback) noexcept { + m_callInvoker->invokeAsync([this, callback = std::move(callback)](facebook::jsi::Runtime&) { + m_threadId = std::this_thread::get_id(); + callback(); + m_threadId = std::thread::id{}; + }); + } + +} diff --git a/vnext/Microsoft.ReactNative/CallInvokerDispatcher.h b/vnext/Microsoft.ReactNative/CallInvokerDispatcher.h new file mode 100644 index 00000000000..3b3cc015852 --- /dev/null +++ b/vnext/Microsoft.ReactNative/CallInvokerDispatcher.h @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once +#include +#include + +namespace Microsoft::ReactNative { + +struct CallInvokerDispatcher : winrt::implements { + CallInvokerDispatcher(std::shared_ptr &&callInvoker) noexcept; + + bool HasThreadAccess() noexcept; + void Post(winrt::Microsoft::ReactNative::ReactDispatcherCallback const &callback) noexcept; + + private: + std::atomic m_threadId{}; + std::shared_ptr m_callInvoker; +}; + +} diff --git a/vnext/Microsoft.ReactNative/CallInvokerWriter.cpp b/vnext/Microsoft.ReactNative/JSDispatcherWriter.cpp similarity index 51% rename from vnext/Microsoft.ReactNative/CallInvokerWriter.cpp rename to vnext/Microsoft.ReactNative/JSDispatcherWriter.cpp index d7c1b24ee09..5a974edeeed 100644 --- a/vnext/Microsoft.ReactNative/CallInvokerWriter.cpp +++ b/vnext/Microsoft.ReactNative/JSDispatcherWriter.cpp @@ -2,7 +2,7 @@ // Licensed under the MIT License. #include "pch.h" -#include "CallInvokerWriter.h" +#include "JSDispatcherWriter.h" #include #include @@ -25,108 +25,106 @@ struct JSNoopWriter : winrt::implements { }; //=========================================================================== -// CallInvokerWriter implementation +// JSDispatcherWriter implementation //=========================================================================== -CallInvokerWriter::CallInvokerWriter( - const std::shared_ptr &jsInvoker, +JSDispatcherWriter::JSDispatcherWriter( + IReactDispatcher const &jsDispatcher, std::weak_ptr jsiRuntimeHolder) noexcept - : m_callInvoker(jsInvoker), m_jsiRuntimeHolder(std::move(jsiRuntimeHolder)) {} + : m_jsDispatcher(jsDispatcher), m_jsiRuntimeHolder(std::move(jsiRuntimeHolder)) {} -CallInvokerWriter::~CallInvokerWriter() { + JSDispatcherWriter::~JSDispatcherWriter() { if (auto jsiRuntimeHolder = m_jsiRuntimeHolder.lock()) { jsiRuntimeHolder->allowRelease(); } } -void CallInvokerWriter::WithResultArgs( +void JSDispatcherWriter::WithResultArgs( Mso::Functor handler) noexcept { - /* - if (m_jsDispatcher.HasThreadAccess()) { - VerifyElseCrash(!m_dynamicWriter); - if (auto jsiRuntimeHolder = m_jsiRuntimeHolder.lock()) { - const facebook::jsi::Value *args{nullptr}; - size_t argCount{0}; - m_jsiWriter->AccessResultAsArgs(args, argCount); - handler(jsiRuntimeHolder->Runtime(), args, argCount); - m_jsiWriter = nullptr; - } - } else { - */ - VerifyElseCrash(!m_jsiWriter); - folly::dynamic dynValue = m_dynamicWriter->TakeValue(); - VerifyElseCrash(dynValue.isArray()); - m_callInvoker->invokeAsync( - [handler, dynValue = std::move(dynValue), weakJsiRuntimeHolder = m_jsiRuntimeHolder, self = get_strong()]( - facebook::jsi::Runtime &runtime) { - std::vector args; - args.reserve(dynValue.size()); - for (auto const &item : dynValue) { - args.emplace_back(facebook::jsi::valueFromDynamic(runtime, item)); - } - handler(runtime, args.data(), args.size()); - }); - //} + if (m_jsDispatcher.HasThreadAccess()) { + VerifyElseCrash(!m_dynamicWriter); + if (auto jsiRuntimeHolder = m_jsiRuntimeHolder.lock()) { + const facebook::jsi::Value *args{nullptr}; + size_t argCount{0}; + m_jsiWriter->AccessResultAsArgs(args, argCount); + handler(jsiRuntimeHolder->Runtime(), args, argCount); + m_jsiWriter = nullptr; + } + } else { + VerifyElseCrash(!m_jsiWriter); + folly::dynamic dynValue = m_dynamicWriter->TakeValue(); + VerifyElseCrash(dynValue.isArray()); + m_jsDispatcher.Post( + [handler, dynValue = std::move(dynValue), weakJsiRuntimeHolder = m_jsiRuntimeHolder, self = get_strong()]() { + if (auto jsiRuntimeHolder = weakJsiRuntimeHolder.lock()) { + std::vector args; + args.reserve(dynValue.size()); + auto &runtime = jsiRuntimeHolder->Runtime(); + for (auto const &item : dynValue) { + args.emplace_back(facebook::jsi::valueFromDynamic(runtime, item)); + } + handler(runtime, args.data(), args.size()); + } + }); + } } -void CallInvokerWriter::WriteNull() noexcept { +void JSDispatcherWriter::WriteNull() noexcept { GetWriter().WriteNull(); } -void CallInvokerWriter::WriteBoolean(bool value) noexcept { +void JSDispatcherWriter::WriteBoolean(bool value) noexcept { GetWriter().WriteBoolean(value); } -void CallInvokerWriter::WriteInt64(int64_t value) noexcept { +void JSDispatcherWriter::WriteInt64(int64_t value) noexcept { GetWriter().WriteInt64(value); } -void CallInvokerWriter::WriteDouble(double value) noexcept { +void JSDispatcherWriter::WriteDouble(double value) noexcept { GetWriter().WriteDouble(value); } -void CallInvokerWriter::WriteString(const winrt::hstring &value) noexcept { +void JSDispatcherWriter::WriteString(const winrt::hstring &value) noexcept { GetWriter().WriteString(value); } -void CallInvokerWriter::WriteObjectBegin() noexcept { +void JSDispatcherWriter::WriteObjectBegin() noexcept { GetWriter().WriteObjectBegin(); } -void CallInvokerWriter::WritePropertyName(const winrt::hstring &name) noexcept { +void JSDispatcherWriter::WritePropertyName(const winrt::hstring &name) noexcept { GetWriter().WritePropertyName(name); } -void CallInvokerWriter::WriteObjectEnd() noexcept { +void JSDispatcherWriter::WriteObjectEnd() noexcept { GetWriter().WriteObjectEnd(); } -void CallInvokerWriter::WriteArrayBegin() noexcept { +void JSDispatcherWriter::WriteArrayBegin() noexcept { GetWriter().WriteArrayBegin(); } -void CallInvokerWriter::WriteArrayEnd() noexcept { +void JSDispatcherWriter::WriteArrayEnd() noexcept { GetWriter().WriteArrayEnd(); } -IJSValueWriter CallInvokerWriter::GetWriter() noexcept { +IJSValueWriter JSDispatcherWriter::GetWriter() noexcept { if (!m_writer) { - /* - if (m_jsDispatcher.HasThreadAccess()) { - if (auto jsiRuntimeHolder = m_jsiRuntimeHolder.lock()) { - m_jsiWriter = winrt::make_self(jsiRuntimeHolder->Runtime()); - m_writer = m_jsiWriter.as(); - } else { - m_writer = winrt::make(); - } - } else { - */ - m_dynamicWriter = winrt::make_self(); - m_writer = m_dynamicWriter.as(); - //} + if (m_jsDispatcher.HasThreadAccess()) { + if (auto jsiRuntimeHolder = m_jsiRuntimeHolder.lock()) { + m_jsiWriter = winrt::make_self(jsiRuntimeHolder->Runtime()); + m_writer = m_jsiWriter.as(); + } else { + m_writer = winrt::make(); + } + } else { + m_dynamicWriter = winrt::make_self(); + m_writer = m_dynamicWriter.as(); + } } - Debug(VerifyElseCrash(m_dynamicWriter != nullptr /* || m_jsDispatcher.HasThreadAccess()*/)); + Debug(VerifyElseCrash(m_dynamicWriter != nullptr || m_jsDispatcher.HasThreadAccess())); return m_writer; } diff --git a/vnext/Microsoft.ReactNative/CallInvokerWriter.h b/vnext/Microsoft.ReactNative/JSDispatcherWriter.h similarity index 69% rename from vnext/Microsoft.ReactNative/CallInvokerWriter.h rename to vnext/Microsoft.ReactNative/JSDispatcherWriter.h index fdbcc96b7b1..50b2cb1c107 100644 --- a/vnext/Microsoft.ReactNative/CallInvokerWriter.h +++ b/vnext/Microsoft.ReactNative/JSDispatcherWriter.h @@ -3,7 +3,6 @@ #pragma once #include -#include #include #include "DynamicWriter.h" #include "JsiWriter.h" @@ -11,13 +10,13 @@ namespace winrt::Microsoft::ReactNative { -// IJSValueWriter to ensure that JsiWriter is always used from a RuntimeExecutor. -// In case if writing is done outside of RuntimeExecutor, it uses DynamicWriter to create -// folly::dynamic which then is written to JsiWriter in RuntimeExecutor. -struct CallInvokerWriter : winrt::implements { - ~CallInvokerWriter(); - CallInvokerWriter( - const std::shared_ptr &jsInvoker, +// IJSValueWriter to ensure that JsiWriter is always used from JSDispatcher. +// In case if writing is done outside of JSDispatcher, it uses DynamicWriter to create +// folly::dynamic which then is written to JsiWriter in JSDispatcher. +struct JSDispatcherWriter : winrt::implements { + ~JSDispatcherWriter(); + JSDispatcherWriter( + IReactDispatcher const &jsDispatcher, std::weak_ptr jsiRuntimeHolder) noexcept; void WithResultArgs(Mso::Functor handler) noexcept; @@ -38,7 +37,7 @@ struct CallInvokerWriter : winrt::implements IJSValueWriter GetWriter() noexcept; private: - const std::shared_ptr m_callInvoker; + IReactDispatcher m_jsDispatcher; std::weak_ptr m_jsiRuntimeHolder; winrt::com_ptr m_dynamicWriter; winrt::com_ptr m_jsiWriter; diff --git a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj index 02c9a6d4d98..937850c9d4d 100644 --- a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj +++ b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj @@ -201,7 +201,7 @@ IJSValueWriter.idl - + IJSValueWriter.idl diff --git a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp index 057beb22bbe..0d0c594cdf5 100644 --- a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp +++ b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp @@ -64,6 +64,7 @@ #include #include #include +#include "CallInvokerDispatcher.h" #endif #if !defined(CORE_ABI) && !defined(USE_FABRIC) @@ -645,12 +646,13 @@ void ReactInstanceWin::InitializeBridgeless() noexcept { 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))); m_jsDispatchQueue.Exchange(std::move(jsDispatchQueue)); + std::shared_ptr callInvoker; + m_jsMessageThread.Load()->runOnQueueSync([&]() { SetJSThreadDescription(); auto timerRegistry = @@ -684,6 +686,11 @@ 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()); + + m_options.Properties.Set(ReactDispatcherHelper::JSDispatcherProperty(), winrt::make(std::shared_ptr(callInvoker))); }); m_options.TurboModuleProvider->SetReactContext( @@ -703,8 +710,7 @@ void ReactInstanceWin::InitializeBridgeless() noexcept { auto turboModuleManager = std::make_shared( m_options.TurboModuleProvider, - std::make_shared( - m_bridgelessReactInstance->getRuntimeScheduler())); + callInvoker); auto binding = [turboModuleManager](const std::string &name) -> std::shared_ptr { diff --git a/vnext/Microsoft.ReactNative/TurboModulesProvider.cpp b/vnext/Microsoft.ReactNative/TurboModulesProvider.cpp index 294e48b4f55..24292e540cc 100644 --- a/vnext/Microsoft.ReactNative/TurboModulesProvider.cpp +++ b/vnext/Microsoft.ReactNative/TurboModulesProvider.cpp @@ -9,7 +9,7 @@ #include "TurboModulesProvider.h" #include #include -#include "CallInvokerWriter.h" +#include "JSDispatcherWriter.h" #include "JSValueWriter.h" #include "JsiApi.h" #include "JsiReader.h" @@ -202,7 +202,7 @@ class TurboModuleImpl : public facebook::react::TurboModule { runtime, propName, 0, - [jsInvoker = jsInvoker_, + [jsDispatcher = m_reactContext.JSDispatcher(), method = methodInfo.Method, longLivedObjectCollection = m_longLivedObjectCollection]( facebook::jsi::Runtime &rt, @@ -214,7 +214,7 @@ class TurboModuleImpl : public facebook::react::TurboModule { auto jsiRuntimeHolder = LongLivedJsiRuntime::CreateWeak(strongLongLivedObjectCollection, rt); method( winrt::make(rt, args, argCount - 1), - winrt::make(jsInvoker, jsiRuntimeHolder), + winrt::make(jsDispatcher, jsiRuntimeHolder), MakeCallback(rt, strongLongLivedObjectCollection, args[argCount - 1]), nullptr); } @@ -225,7 +225,7 @@ class TurboModuleImpl : public facebook::react::TurboModule { runtime, propName, 0, - [jsInvoker = jsInvoker_, + [jsDispatcher = m_reactContext.JSDispatcher(), method = methodInfo.Method, longLivedObjectCollection = m_longLivedObjectCollection]( facebook::jsi::Runtime &rt, @@ -242,9 +242,9 @@ class TurboModuleImpl : public facebook::react::TurboModule { method( winrt::make(rt, args, argCount - 2), - winrt::make(jsInvoker, jsiRuntimeHolder), + winrt::make(jsDispatcher, jsiRuntimeHolder), [weakCallback1, weakCallback2, jsiRuntimeHolder](const IJSValueWriter &writer) noexcept { - writer.as()->WithResultArgs( + writer.as()->WithResultArgs( [weakCallback1, weakCallback2, jsiRuntimeHolder]( facebook::jsi::Runtime &rt, facebook::jsi::Value const *args, size_t count) { if (auto callback1 = weakCallback1.lock()) { @@ -260,7 +260,7 @@ class TurboModuleImpl : public facebook::react::TurboModule { }); }, [weakCallback1, weakCallback2, jsiRuntimeHolder](const IJSValueWriter &writer) noexcept { - writer.as()->WithResultArgs( + writer.as()->WithResultArgs( [weakCallback1, weakCallback2, jsiRuntimeHolder]( facebook::jsi::Runtime &rt, facebook::jsi::Value const *args, size_t count) { if (auto callback2 = weakCallback2.lock()) { @@ -283,7 +283,7 @@ class TurboModuleImpl : public facebook::react::TurboModule { runtime, propName, 0, - [jsInvoker = jsInvoker_, + [jsDispatcher = m_reactContext.JSDispatcher(), method = methodInfo.Method, longLivedObjectCollection = m_longLivedObjectCollection]( facebook::jsi::Runtime &rt, @@ -293,7 +293,7 @@ class TurboModuleImpl : public facebook::react::TurboModule { if (auto strongLongLivedObjectCollection = longLivedObjectCollection.lock()) { auto jsiRuntimeHolder = LongLivedJsiRuntime::CreateWeak(strongLongLivedObjectCollection, rt); auto argReader = winrt::make(rt, args, count); - auto argWriter = winrt::make(jsInvoker, jsiRuntimeHolder); + auto argWriter = winrt::make(jsDispatcher, jsiRuntimeHolder); return facebook::react::createPromiseAsJSIValue( rt, [method, argReader, argWriter, strongLongLivedObjectCollection, jsiRuntimeHolder]( @@ -306,7 +306,7 @@ class TurboModuleImpl : public facebook::react::TurboModule { argReader, argWriter, [weakResolve, weakReject, jsiRuntimeHolder](const IJSValueWriter &writer) { - writer.as()->WithResultArgs( + writer.as()->WithResultArgs( [weakResolve, weakReject, jsiRuntimeHolder]( facebook::jsi::Runtime &runtime, facebook::jsi::Value const *args, @@ -325,7 +325,7 @@ class TurboModuleImpl : public facebook::react::TurboModule { }); }, [weakResolve, weakReject, jsiRuntimeHolder](const IJSValueWriter &writer) { - writer.as()->WithResultArgs( + writer.as()->WithResultArgs( [weakResolve, weakReject, jsiRuntimeHolder]( facebook::jsi::Runtime &runtime, facebook::jsi::Value const *args, @@ -438,7 +438,7 @@ class TurboModuleImpl : public facebook::react::TurboModule { auto weakCallback = LongLivedJsiFunction::CreateWeak(longLivedObjectCollection, rt, callback.getObject(rt).getFunction(rt)); return [weakCallback = std::move(weakCallback)](const IJSValueWriter &writer) noexcept { - writer.as()->WithResultArgs( + writer.as()->WithResultArgs( [weakCallback](facebook::jsi::Runtime &rt, facebook::jsi::Value const *args, size_t count) { if (auto callback = weakCallback.lock()) { callback->Value().call(rt, args, count); diff --git a/vnext/Shared/Shared.vcxitems b/vnext/Shared/Shared.vcxitems index 6b4703eb94e..0cc9d93cb63 100644 --- a/vnext/Shared/Shared.vcxitems +++ b/vnext/Shared/Shared.vcxitems @@ -401,7 +401,10 @@ $(MSBuildThisFileDirectory)..\Microsoft.ReactNative\IReactDispatcher.idl - + + $(MSBuildThisFileDirectory)..\Microsoft.ReactNative\IReactDispatcher.idl + + $(MSBuildThisFileDirectory)..\Microsoft.ReactNative\IJSValueWriter.idl From cb9c0eacc1b881f457545a0498f40f80036ea6a2 Mon Sep 17 00:00:00 2001 From: Andrew Coates <30809111+acoates-ms@users.noreply.github.com> Date: Wed, 7 May 2025 16:20:43 -0700 Subject: [PATCH 05/17] format --- .../CallInvokerDispatcher.cpp | 27 +++++++++---------- .../CallInvokerDispatcher.h | 11 ++++---- .../JSDispatcherWriter.cpp | 2 +- .../ReactHost/ReactInstanceWin.cpp | 12 +++++---- 4 files changed, 27 insertions(+), 25 deletions(-) diff --git a/vnext/Microsoft.ReactNative/CallInvokerDispatcher.cpp b/vnext/Microsoft.ReactNative/CallInvokerDispatcher.cpp index 5582ae55e21..e237c4259f7 100644 --- a/vnext/Microsoft.ReactNative/CallInvokerDispatcher.cpp +++ b/vnext/Microsoft.ReactNative/CallInvokerDispatcher.cpp @@ -5,20 +5,19 @@ namespace Microsoft::ReactNative { - CallInvokerDispatcher::CallInvokerDispatcher(std::shared_ptr &&callInvoker) noexcept - : m_callInvoker(callInvoker) - {} +CallInvokerDispatcher::CallInvokerDispatcher(std::shared_ptr &&callInvoker) noexcept + : m_callInvoker(callInvoker) {} - bool CallInvokerDispatcher::HasThreadAccess() noexcept { - return m_threadId == std::this_thread::get_id(); - } - - void CallInvokerDispatcher::Post(winrt::Microsoft::ReactNative::ReactDispatcherCallback const &callback) noexcept { - m_callInvoker->invokeAsync([this, callback = std::move(callback)](facebook::jsi::Runtime&) { - m_threadId = std::this_thread::get_id(); - callback(); - m_threadId = std::thread::id{}; - }); - } +bool CallInvokerDispatcher::HasThreadAccess() noexcept { + return m_threadId == std::this_thread::get_id(); +} +void CallInvokerDispatcher::Post(winrt::Microsoft::ReactNative::ReactDispatcherCallback const &callback) noexcept { + m_callInvoker->invokeAsync([this, callback = std::move(callback)](facebook::jsi::Runtime &) { + m_threadId = std::this_thread::get_id(); + callback(); + m_threadId = std::thread::id{}; + }); } + +} // namespace Microsoft::ReactNative diff --git a/vnext/Microsoft.ReactNative/CallInvokerDispatcher.h b/vnext/Microsoft.ReactNative/CallInvokerDispatcher.h index 3b3cc015852..f1767862107 100644 --- a/vnext/Microsoft.ReactNative/CallInvokerDispatcher.h +++ b/vnext/Microsoft.ReactNative/CallInvokerDispatcher.h @@ -2,20 +2,21 @@ // Licensed under the MIT License. #pragma once -#include #include +#include namespace Microsoft::ReactNative { -struct CallInvokerDispatcher : winrt::implements { +struct CallInvokerDispatcher + : winrt::implements { CallInvokerDispatcher(std::shared_ptr &&callInvoker) noexcept; bool HasThreadAccess() noexcept; void Post(winrt::Microsoft::ReactNative::ReactDispatcherCallback const &callback) noexcept; private: - std::atomic m_threadId{}; - std::shared_ptr m_callInvoker; + std::atomic m_threadId{}; + std::shared_ptr m_callInvoker; }; -} +} // namespace Microsoft::ReactNative diff --git a/vnext/Microsoft.ReactNative/JSDispatcherWriter.cpp b/vnext/Microsoft.ReactNative/JSDispatcherWriter.cpp index 5a974edeeed..264600596ef 100644 --- a/vnext/Microsoft.ReactNative/JSDispatcherWriter.cpp +++ b/vnext/Microsoft.ReactNative/JSDispatcherWriter.cpp @@ -33,7 +33,7 @@ JSDispatcherWriter::JSDispatcherWriter( std::weak_ptr jsiRuntimeHolder) noexcept : m_jsDispatcher(jsDispatcher), m_jsiRuntimeHolder(std::move(jsiRuntimeHolder)) {} - JSDispatcherWriter::~JSDispatcherWriter() { +JSDispatcherWriter::~JSDispatcherWriter() { if (auto jsiRuntimeHolder = m_jsiRuntimeHolder.lock()) { jsiRuntimeHolder->allowRelease(); } diff --git a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp index 0d0c594cdf5..b15407159dc 100644 --- a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp +++ b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp @@ -688,9 +688,12 @@ void ReactInstanceWin::InitializeBridgeless() noexcept { m_bridgelessReactInstance->getRuntimeScheduler()); callInvoker = std::make_shared( - m_bridgelessReactInstance->getRuntimeScheduler()); + m_bridgelessReactInstance->getRuntimeScheduler()); - m_options.Properties.Set(ReactDispatcherHelper::JSDispatcherProperty(), winrt::make(std::shared_ptr(callInvoker))); + m_options.Properties.Set( + ReactDispatcherHelper::JSDispatcherProperty(), + winrt::make( + std::shared_ptr(callInvoker))); }); m_options.TurboModuleProvider->SetReactContext( @@ -708,9 +711,8 @@ void ReactInstanceWin::InitializeBridgeless() noexcept { }; facebook::react::bindNativeLogger(runtime, logger); - auto turboModuleManager = std::make_shared( - m_options.TurboModuleProvider, - callInvoker); + auto turboModuleManager = + std::make_shared(m_options.TurboModuleProvider, callInvoker); auto binding = [turboModuleManager](const std::string &name) -> std::shared_ptr { From eb90dd32b9c6e7b24815f401b272e5ac37a3a3bc Mon Sep 17 00:00:00 2001 From: Andrew Coates <30809111+acoates-ms@users.noreply.github.com> Date: Mon, 12 May 2025 12:23:35 -0700 Subject: [PATCH 06/17] Add ReactContext.CallInvoker and JsiInitializers --- vnext/Folly/Folly.vcxproj | 1 + vnext/Folly/ThreadNameStub.cpp | 10 + .../ReactModuleBuilderMock.cpp | 4 + .../ReactModuleBuilderMock.h | 6 + .../JSI/JsiAbiApi.cpp | 102 ++++- .../Microsoft.ReactNative.Cxx/JSI/JsiAbiApi.h | 19 + .../JSI/JsiApiContext.cpp | 67 ++- .../JSI/JsiApiContext.h | 6 + .../Microsoft.ReactNative.Cxx/NativeModules.h | 35 +- .../Microsoft.ReactNative.Cxx/ReactContext.h | 7 + .../TurboModuleProvider.cpp | 24 +- .../TurboModuleProvider.h | 7 +- .../ExecuteJsiTests.cpp | 33 +- .../JsiSimpleTurboModuleTests.cpp | 21 +- .../ReactNativeHostTests.cpp | 4 + .../TurboModuleTests.cpp | 18 +- vnext/Microsoft.ReactNative/CallInvoker.cpp | 40 ++ vnext/Microsoft.ReactNative/CallInvoker.h | 28 ++ .../Composition/CompositionEventHandler.h | 1 - .../Fabric/FabricUIManagerModule.cpp | 12 + .../threading/MessageQueueThreadImpl.cpp | 40 ++ .../react/threading/MessageQueueThreadImpl.h | 55 +++ .../react/threading/TaskDispatchThread.cpp | 133 ++++++ .../react/threading/TaskDispatchThread.h | 78 ++++ vnext/Microsoft.ReactNative/IReactContext.cpp | 6 + vnext/Microsoft.ReactNative/IReactContext.h | 1 + vnext/Microsoft.ReactNative/IReactContext.idl | 15 + .../IReactModuleBuilder.cpp | 4 + .../IReactModuleBuilder.h | 2 + .../IReactModuleBuilder.idl | 8 + vnext/Microsoft.ReactNative/JsiApi.cpp | 4 +- .../ReactHost/ReactInstanceWin.cpp | 31 +- .../SchedulerSettings.cpp | 8 +- .../Microsoft.ReactNative/SchedulerSettings.h | 2 +- .../TurboModulesProvider.cpp | 12 +- vnext/PropertySheets/React.Cpp.props | 1 + .../jserrorhandler/JsErrorHandler.cpp | 429 ++++++++++++++++++ vnext/Shared/Modules/BlobModule.cpp | 30 +- vnext/Shared/Modules/BlobModule.h | 2 +- vnext/Shared/Shared.vcxitems | 3 + 40 files changed, 1201 insertions(+), 108 deletions(-) create mode 100644 vnext/Folly/ThreadNameStub.cpp create mode 100644 vnext/Microsoft.ReactNative/CallInvoker.cpp create mode 100644 vnext/Microsoft.ReactNative/CallInvoker.h create mode 100644 vnext/Microsoft.ReactNative/Fabric/platform/react/threading/MessageQueueThreadImpl.cpp create mode 100644 vnext/Microsoft.ReactNative/Fabric/platform/react/threading/MessageQueueThreadImpl.h create mode 100644 vnext/Microsoft.ReactNative/Fabric/platform/react/threading/TaskDispatchThread.cpp create mode 100644 vnext/Microsoft.ReactNative/Fabric/platform/react/threading/TaskDispatchThread.h create mode 100644 vnext/ReactCommon/TEMP_UntilReactCommonUpdate/jserrorhandler/JsErrorHandler.cpp 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/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..ba1e2f941b7 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; @@ -216,6 +217,7 @@ struct ReactModuleBuilderImpl : implements #include "ReactNonAbiValue.h" #include "winrt/Windows.Foundation.Collections.h" +#include "ReactContext.h" using namespace facebook::jsi; @@ -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; +} + // 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,53 @@ 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 //=========================================================================== @@ -164,36 +222,42 @@ JsiValueRef JsiHostFunctionWrapper::operator()( // 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}; +// It is thread specific because we want to have the safe access only in JS thread. /// TODO - make no longer thread +// specific It 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); + VerifyElseCrash(GetFromJsiRuntime(m_runtime) != nullptr); + 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..6b778ffc6be 100644 --- a/vnext/Microsoft.ReactNative.Cxx/JSI/JsiAbiApi.h +++ b/vnext/Microsoft.ReactNative.Cxx/JSI/JsiAbiApi.h @@ -54,6 +54,24 @@ 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 +244,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..142484cc65d 100644 --- a/vnext/Microsoft.ReactNative.Cxx/JSI/JsiApiContext.cpp +++ b/vnext/Microsoft.ReactNative.Cxx/JSI/JsiApiContext.cpp @@ -4,26 +4,22 @@ #include "pch.h" #include "JsiApiContext.h" +#include + // Use __ImageBase to get current DLL handle. // http://blogs.msdn.com/oldnewthing/archive/2004/10/25/247180.aspx 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 +53,55 @@ facebook::jsi::Runtime *TryGetOrCreateContextRuntime(ReactContext const &context return runtime; } +} + +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 +// TODO move this check into ReactContext.JSRuntime(). + VerifyElseCrashSz( + !context.Properties().Get(winrt::Microsoft::ReactNative::ReactPropertyId< + winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext>{ + L"ReactNative.Composition", L"CompositionContext"}), + "ExecuteJsi/TryGetOrCreateContextRuntime not supported on new arch, use ReactContext.CallInvoker instead."); + + 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..f6da0eb8463 100644 --- a/vnext/Microsoft.ReactNative.Cxx/NativeModules.h +++ b/vnext/Microsoft.ReactNative.Cxx/NativeModules.h @@ -13,6 +13,7 @@ #include "ReactContext.h" #include "ReactNonAbiValue.h" #include "ReactPromise.h" +#include "JSI/JsiApiContext.h" #include #include @@ -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,24 @@ 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 +1061,9 @@ struct ReactModuleBuilder { for (auto &initializer : m_initializers) { m_moduleBuilder.AddInitializer(initializer); } + for (auto &initializer : m_jsiinitializers) { + m_moduleBuilder.AddJsiInitializer(initializer); + } } template @@ -1069,8 +1092,15 @@ 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 +1159,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..8995f8d66b6 100644 --- a/vnext/Microsoft.ReactNative.Cxx/TurboModuleProvider.h +++ b/vnext/Microsoft.ReactNative.Cxx/TurboModuleProvider.h @@ -22,10 +22,11 @@ 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)); + // TODO test back with JsiHostObjectWrapper and remove JsiHostObjectGetOrCreateWrapper if not needed + // Repalce above moduleBuilder.AddInitializer to moduleBuilder.AddJsiInitializer then we dont need JsiHostObjectGetOrCreateWrapper + 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..82e8d2030dd 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..39ad1ce5d99 100644 --- a/vnext/Microsoft.ReactNative.IntegrationTests/JsiSimpleTurboModuleTests.cpp +++ b/vnext/Microsoft.ReactNative.IntegrationTests/JsiSimpleTurboModuleTests.cpp @@ -47,26 +47,41 @@ 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); +#if USE_FABRIC + // Save this thread as the js thread + jsThreadId = std::this_thread::get_id(); +#else 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); +#if USE_FABRIC + TestCheck(jsThreadId == std::this_thread::get_id()); +#else TestCheck(ReactContext(args.Context()).JSDispatcher().HasThreadAccess()); +#endif }); host.InstanceSettings().InstanceDestroyed( [&](IInspectable const & /*sender*/, InstanceDestroyedEventArgs const &args) { TestEventService::LogEvent("Instance destroyed event", nullptr); +#if USE_FABRIC + TestCheck(jsThreadId == std::this_thread::get_id()); +#else TestCheck(ReactContext(args.Context()).JSDispatcher().HasThreadAccess()); - }); +#endif + }); }); TestEventService::ObserveEvents({ diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/ReactNativeHostTests.cpp b/vnext/Microsoft.ReactNative.IntegrationTests/ReactNativeHostTests.cpp index 2e5b0b9a7b9..87c24581a88 100644 --- a/vnext/Microsoft.ReactNative.IntegrationTests/ReactNativeHostTests.cpp +++ b/vnext/Microsoft.ReactNative.IntegrationTests/ReactNativeHostTests.cpp @@ -169,7 +169,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..b7a6661570e 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,6 +673,8 @@ TEST_CLASS (TurboModuleTests) { reactNativeHost.Host().UnloadInstance(); TestNotificationService::Wait("Instance destroyed event"); + + // JSDispatcher must not process any callbacks auto jsDispatcher = reactNativeHost.Host() .InstanceSettings() @@ -679,7 +687,15 @@ TEST_CLASS (TurboModuleTests) { } }; bool callbackIsCalled{false}; + +#if USE_FABRIC + callInvoker.InvokeAsync([&callbackIsCalled, data = std::make_shared()](const winrt::Windows::Foundation::IInspectable& /*runtimeHandle*/) + { + callbackIsCalled = true; + }); +#else jsDispatcher.Post([&callbackIsCalled, data = std::make_shared()] { callbackIsCalled = true; }); +#endif TestNotificationService::Wait("CallbackData destroyed"); TestCheck(!callbackIsCalled); } diff --git a/vnext/Microsoft.ReactNative/CallInvoker.cpp b/vnext/Microsoft.ReactNative/CallInvoker.cpp new file mode 100644 index 00000000000..18e648813c4 --- /dev/null +++ b/vnext/Microsoft.ReactNative/CallInvoker.cpp @@ -0,0 +1,40 @@ +// 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); +} + +} diff --git a/vnext/Microsoft.ReactNative/CallInvoker.h b/vnext/Microsoft.ReactNative/CallInvoker.h new file mode 100644 index 00000000000..facb3d56351 --- /dev/null +++ b/vnext/Microsoft.ReactNative/CallInvoker.h @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include "ReactHost/React.h" +#include "winrt/Microsoft.ReactNative.h" +#include + +#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; +}; + +} 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..761a0b28bbb 100644 --- a/vnext/Microsoft.ReactNative/Fabric/FabricUIManagerModule.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/FabricUIManagerModule.cpp @@ -470,6 +470,18 @@ 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 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..838543242aa --- /dev/null +++ b/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/MessageQueueThreadImpl.cpp @@ -0,0 +1,40 @@ +/* + * 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..9332fa8130c --- /dev/null +++ b/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/MessageQueueThreadImpl.h @@ -0,0 +1,55 @@ +/* + * 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..fcdc91a34a7 --- /dev/null +++ b/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/TaskDispatchThread.cpp @@ -0,0 +1,133 @@ +/* + * 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..9478dea3af5 --- /dev/null +++ b/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/TaskDispatchThread.h @@ -0,0 +1,78 @@ +/* + * 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..a84e047ec13 100644 --- a/vnext/Microsoft.ReactNative/IReactContext.cpp +++ b/vnext/Microsoft.ReactNative/IReactContext.cpp @@ -8,6 +8,8 @@ #include "XamlUIService.h" #endif +#include "CallInvoker.h" + namespace winrt::Microsoft::ReactNative::implementation { //============================================================================= @@ -100,6 +102,10 @@ IReactDispatcher ReactContext::JSDispatcher() noexcept { 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 { 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..83c0e20a173 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 @@ -185,6 +197,9 @@ namespace Microsoft.ReactNative "It is an experimental property that may be removed or changed in a future version.") 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/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/ReactHost/ReactInstanceWin.cpp b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp index b15407159dc..f3740655dd1 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 @@ -61,6 +62,7 @@ #include #include #include +#include #include #include #include @@ -642,14 +644,8 @@ 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_jsMessageThread.Exchange(std::make_shared( - jsDispatchQueue, Mso::MakeWeakMemberFunctor(this, &ReactInstanceWin::OnError))); - - m_jsDispatchQueue.Exchange(std::move(jsDispatchQueue)); + auto jsMessageThread = std::make_shared(); + m_jsMessageThread.Exchange(jsMessageThread); std::shared_ptr callInvoker; @@ -674,11 +670,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); @@ -690,6 +686,11 @@ void ReactInstanceWin::InitializeBridgeless() noexcept { 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.Properties.Set( ReactDispatcherHelper::JSDispatcherProperty(), winrt::make( @@ -1095,7 +1096,7 @@ Mso::Future ReactInstanceWin::Destroy() noexcept { #ifdef USE_FABRIC if (m_bridgelessReactInstance) { - auto jsDispatchQueue = m_jsDispatchQueue.Exchange(nullptr); + auto jsDispatchQueue = m_jsDispatchQueue.Exchange(nullptr); // TODO should be null for bridgeless if (auto jsMessageThread = m_jsMessageThread.Exchange(nullptr)) { jsMessageThread->runOnQueueSync([&]() noexcept { { @@ -1130,13 +1131,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/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 24292e540cc..559ebf23e73 100644 --- a/vnext/Microsoft.ReactNative/TurboModulesProvider.cpp +++ b/vnext/Microsoft.ReactNative/TurboModulesProvider.cpp @@ -13,6 +13,7 @@ #include "JSValueWriter.h" #include "JsiApi.h" #include "JsiReader.h" +#include #include "JsiWriter.h" #ifdef __APPLE__ #include "Crash.h" @@ -38,8 +39,12 @@ struct TurboModuleBuilder : winrt::implements(m_reactContext)->GetInner().JsiRuntime()); } void AddConstantProvider(ConstantProviderDelegate const &constantProvider) noexcept { @@ -119,6 +124,9 @@ 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..ac6a0706972 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..856c5e66075 100644 --- a/vnext/Shared/Modules/BlobModule.h +++ b/vnext/Shared/Modules/BlobModule.h @@ -24,7 +24,7 @@ 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 0cc9d93cb63..d6c46c79e7a 100644 --- a/vnext/Shared/Shared.vcxitems +++ b/vnext/Shared/Shared.vcxitems @@ -342,6 +342,7 @@ $(MSBuildThisFileDirectory)..\Microsoft.ReactNative\JsiApi.idl + true @@ -675,6 +676,8 @@ + + From 6d7297739f1b5fc382a62ade6185ed02ffbbd465 Mon Sep 17 00:00:00 2001 From: Andrew Coates <30809111+acoates-ms@users.noreply.github.com> Date: Mon, 12 May 2025 12:43:03 -0700 Subject: [PATCH 07/17] format --- .../JSI/JsiAbiApi.cpp | 27 +++++++------ .../Microsoft.ReactNative.Cxx/JSI/JsiAbiApi.h | 5 ++- .../JSI/JsiApiContext.cpp | 26 ++++++------- .../Microsoft.ReactNative.Cxx/NativeModules.h | 24 ++++++------ .../TurboModuleProvider.h | 3 +- .../ExecuteJsiTests.cpp | 6 +-- .../JsiSimpleTurboModuleTests.cpp | 4 +- .../ReactNativeHostTests.cpp | 2 +- .../TurboModuleTests.cpp | 19 ++++------ vnext/Microsoft.ReactNative/CallInvoker.cpp | 38 ++++++++++--------- vnext/Microsoft.ReactNative/CallInvoker.h | 32 +++++++++------- .../Fabric/FabricUIManagerModule.cpp | 21 +++++----- .../threading/MessageQueueThreadImpl.cpp | 13 +++---- .../react/threading/MessageQueueThreadImpl.h | 15 ++++---- .../react/threading/TaskDispatchThread.cpp | 23 ++++------- .../react/threading/TaskDispatchThread.h | 21 ++++------ .../ReactHost/ReactInstanceWin.cpp | 4 +- .../TurboModulesProvider.cpp | 19 ++++++---- 18 files changed, 148 insertions(+), 154 deletions(-) diff --git a/vnext/Microsoft.ReactNative.Cxx/JSI/JsiAbiApi.cpp b/vnext/Microsoft.ReactNative.Cxx/JSI/JsiAbiApi.cpp index 7fd6513e9e2..efc4c71babc 100644 --- a/vnext/Microsoft.ReactNative.Cxx/JSI/JsiAbiApi.cpp +++ b/vnext/Microsoft.ReactNative.Cxx/JSI/JsiAbiApi.cpp @@ -4,9 +4,9 @@ #include "pch.h" #include "JsiAbiApi.h" #include +#include "ReactContext.h" #include "ReactNonAbiValue.h" #include "winrt/Windows.Foundation.Collections.h" -#include "ReactContext.h" using namespace facebook::jsi; @@ -16,14 +16,14 @@ 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; -} +// 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. @@ -143,14 +143,14 @@ 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) {} +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)}; @@ -189,7 +189,6 @@ std::shared_ptr const &JsiHostObjectGetOrCreateWrappe return m_hostObject; } - //=========================================================================== // JsiHostFunctionWrapper implementation //=========================================================================== diff --git a/vnext/Microsoft.ReactNative.Cxx/JSI/JsiAbiApi.h b/vnext/Microsoft.ReactNative.Cxx/JSI/JsiAbiApi.h index 6b778ffc6be..6f3230727a2 100644 --- a/vnext/Microsoft.ReactNative.Cxx/JSI/JsiAbiApi.h +++ b/vnext/Microsoft.ReactNative.Cxx/JSI/JsiAbiApi.h @@ -58,7 +58,9 @@ struct JsiHostObjectWrapper : implements { // 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; + 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); @@ -71,7 +73,6 @@ struct JsiHostObjectGetOrCreateWrapper : implements{ + winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext>{ L"ReactNative.Composition", L"CompositionContext"}), "ExecuteJsi/TryGetOrCreateContextRuntime not supported on new arch, use ReactContext.CallInvoker instead."); @@ -89,14 +86,15 @@ facebook::jsi::Runtime *TryGetOrCreateContextRuntime(ReactContext const &context 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 { +[[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; diff --git a/vnext/Microsoft.ReactNative.Cxx/NativeModules.h b/vnext/Microsoft.ReactNative.Cxx/NativeModules.h index f6da0eb8463..50f8c199899 100644 --- a/vnext/Microsoft.ReactNative.Cxx/NativeModules.h +++ b/vnext/Microsoft.ReactNative.Cxx/NativeModules.h @@ -7,13 +7,13 @@ #pragma once #include #include +#include "JSI/JsiApiContext.h" #include "JSValueReader.h" #include "JSValueWriter.h" #include "ModuleRegistration.h" #include "ReactContext.h" #include "ReactNonAbiValue.h" #include "ReactPromise.h" -#include "JSI/JsiApiContext.h" #include #include @@ -501,7 +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; + 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 { @@ -514,19 +514,18 @@ template struct ModuleJsiInitMethodInfo; template -struct ModuleJsiInitMethodInfo -{ +struct ModuleJsiInitMethodInfo { using ModuleType = TModule; - using MethodType = void (TModule::*)(ReactContext const&, facebook::jsi::Runtime&) noexcept; + 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)); - }; + 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 ================================================= @@ -1092,7 +1091,6 @@ struct ReactModuleBuilder { template void RegisterInitMethod(TMethod method) noexcept { - if constexpr (ModuleMethodInfo::ArgCount == 1) { auto initializer = ModuleInitMethodInfo::GetInitializer(m_module, method); m_initializers.push_back(std::move(initializer)); diff --git a/vnext/Microsoft.ReactNative.Cxx/TurboModuleProvider.h b/vnext/Microsoft.ReactNative.Cxx/TurboModuleProvider.h index 8995f8d66b6..4c9cc00ba02 100644 --- a/vnext/Microsoft.ReactNative.Cxx/TurboModuleProvider.h +++ b/vnext/Microsoft.ReactNative.Cxx/TurboModuleProvider.h @@ -25,7 +25,8 @@ void AddTurboModuleProvider(IReactPackageBuilder const &packageBuilder, std::wst auto callInvoker = ReactContext{context}.CallInvoker(); auto turboModule = std::make_shared(callInvoker); // TODO test back with JsiHostObjectWrapper and remove JsiHostObjectGetOrCreateWrapper if not needed - // Repalce above moduleBuilder.AddInitializer to moduleBuilder.AddJsiInitializer then we dont need JsiHostObjectGetOrCreateWrapper + // Repalce above moduleBuilder.AddInitializer to moduleBuilder.AddJsiInitializer then we dont need + // JsiHostObjectGetOrCreateWrapper 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 82e8d2030dd..df2bfb761a3 100644 --- a/vnext/Microsoft.ReactNative.IntegrationTests/ExecuteJsiTests.cpp +++ b/vnext/Microsoft.ReactNative.IntegrationTests/ExecuteJsiTests.cpp @@ -98,9 +98,9 @@ struct TestExecuteJsiModule { [](ReactError const &error) noexcept { TestEventService::LogEvent("testExecuteJsiPromise promise failed", error.Message.c_str()); }); - m_reactContext.CallInvoker()->invokeAsync( - [callResult](Runtime &) { TestEventService::LogEvent("testExecuteJsiPromise completed", nullptr); - callResult.Resolve(); + m_reactContext.CallInvoker()->invokeAsync([callResult](Runtime &) { + TestEventService::LogEvent("testExecuteJsiPromise completed", nullptr); + callResult.Resolve(); }); } diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/JsiSimpleTurboModuleTests.cpp b/vnext/Microsoft.ReactNative.IntegrationTests/JsiSimpleTurboModuleTests.cpp index 39ad1ce5d99..bb5d3eb0465 100644 --- a/vnext/Microsoft.ReactNative.IntegrationTests/JsiSimpleTurboModuleTests.cpp +++ b/vnext/Microsoft.ReactNative.IntegrationTests/JsiSimpleTurboModuleTests.cpp @@ -47,7 +47,7 @@ TEST_CLASS (JsiSimpleTurboModuleTests) { TEST_METHOD(TestInstanceReload) { TestEventService::Initialize(); - std::thread::id jsThreadId {}; + std::thread::id jsThreadId{}; auto reactNativeHost = TestReactNativeHostHolder(L"JsiSimpleTurboModuleTests", [&](ReactNativeHost const &host) noexcept { @@ -81,7 +81,7 @@ TEST_CLASS (JsiSimpleTurboModuleTests) { #else TestCheck(ReactContext(args.Context()).JSDispatcher().HasThreadAccess()); #endif - }); + }); }); TestEventService::ObserveEvents({ diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/ReactNativeHostTests.cpp b/vnext/Microsoft.ReactNative.IntegrationTests/ReactNativeHostTests.cpp index 87c24581a88..a9cfcff6199 100644 --- a/vnext/Microsoft.ReactNative.IntegrationTests/ReactNativeHostTests.cpp +++ b/vnext/Microsoft.ReactNative.IntegrationTests/ReactNativeHostTests.cpp @@ -170,7 +170,7 @@ TEST_CLASS (ReactNativeHostTests) { std::move(options)); #if USE_FABRIC - TestEventService::ObserveEvents({ TestEvent{"InstanceLoaded::Failed", nullptr} }); + 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 b7a6661570e..cf233740575 100644 --- a/vnext/Microsoft.ReactNative.IntegrationTests/TurboModuleTests.cpp +++ b/vnext/Microsoft.ReactNative.IntegrationTests/TurboModuleTests.cpp @@ -650,7 +650,7 @@ TEST_CLASS (TurboModuleTests) { TestEventService::Initialize(); TestNotificationService::Initialize(); - CallInvoker callInvoker { nullptr }; + CallInvoker callInvoker{nullptr}; auto reactNativeHost = TestReactNativeHostHolder(L"TurboModuleTests", [&](ReactNativeHost const &host) noexcept { host.PackageProviders().Append(winrt::make()); @@ -660,10 +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(); - }); + host.InstanceSettings().InstanceCreated( + [&](IInspectable const & /*sender*/, InstanceCreatedEventArgs const &args) { + callInvoker = args.Context().CallInvoker(); + }); }); TestEventService::ObserveEvents({ @@ -673,8 +673,6 @@ TEST_CLASS (TurboModuleTests) { reactNativeHost.Host().UnloadInstance(); TestNotificationService::Wait("Instance destroyed event"); - - // JSDispatcher must not process any callbacks auto jsDispatcher = reactNativeHost.Host() .InstanceSettings() @@ -689,10 +687,9 @@ TEST_CLASS (TurboModuleTests) { bool callbackIsCalled{false}; #if USE_FABRIC - callInvoker.InvokeAsync([&callbackIsCalled, data = std::make_shared()](const winrt::Windows::Foundation::IInspectable& /*runtimeHandle*/) - { - callbackIsCalled = true; - }); + callInvoker.InvokeAsync( + [&callbackIsCalled, data = std::make_shared()]( + const winrt::Windows::Foundation::IInspectable & /*runtimeHandle*/) { callbackIsCalled = true; }); #else jsDispatcher.Post([&callbackIsCalled, data = std::make_shared()] { callbackIsCalled = true; }); #endif diff --git a/vnext/Microsoft.ReactNative/CallInvoker.cpp b/vnext/Microsoft.ReactNative/CallInvoker.cpp index 18e648813c4..7a51a1d30b7 100644 --- a/vnext/Microsoft.ReactNative/CallInvoker.cpp +++ b/vnext/Microsoft.ReactNative/CallInvoker.cpp @@ -9,32 +9,34 @@ namespace winrt::Microsoft::ReactNative::implementation { static const ReactPropertyId &CallInvokerPropertyId() noexcept { - static const ReactPropertyId prop{L"ReactNative", L"CallInvoker"}; - return prop; + 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) {} +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::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()); - }); +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()); +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); +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 index facb3d56351..898b06b945b 100644 --- a/vnext/Microsoft.ReactNative/CallInvoker.h +++ b/vnext/Microsoft.ReactNative/CallInvoker.h @@ -3,26 +3,32 @@ #pragma once +#include +#include #include "ReactHost/React.h" #include "winrt/Microsoft.ReactNative.h" -#include #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; + 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/Fabric/FabricUIManagerModule.cpp b/vnext/Microsoft.ReactNative/Fabric/FabricUIManagerModule.cpp index 761a0b28bbb..790fd270389 100644 --- a/vnext/Microsoft.ReactNative/Fabric/FabricUIManagerModule.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/FabricUIManagerModule.cpp @@ -470,17 +470,18 @@ 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"} }; + 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 const& args) noexcept - { - reactContext.Properties().Remove(FabicUIManagerProperty()); - args.Subscription().Unsubscribe(); // Unsubscribe after we handle the notification. - }); + 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); diff --git a/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/MessageQueueThreadImpl.cpp b/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/MessageQueueThreadImpl.cpp index 838543242aa..a23a40422c8 100644 --- a/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/MessageQueueThreadImpl.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/MessageQueueThreadImpl.cpp @@ -5,7 +5,8 @@ * 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 +// [Windows] Brought forward from react-native - switch to using react-native/ReactCxxPlatform/react/threading version +// once we integrate that far #include "MessageQueueThreadImpl.h" @@ -13,23 +14,21 @@ namespace facebook::react { -void MessageQueueThreadImpl::runOnQueue(std::function&& runnable) { +void MessageQueueThreadImpl::runOnQueue(std::function &&runnable) { if (!taskDispatchThread_.isRunning()) { return; } - taskDispatchThread_.runAsync( - [runnable = std::move(runnable)]() noexcept { runnable(); }); + taskDispatchThread_.runAsync([runnable = std::move(runnable)]() noexcept { runnable(); }); } -void MessageQueueThreadImpl::runOnQueueSync(std::function&& runnable) { +void MessageQueueThreadImpl::runOnQueueSync(std::function &&runnable) { if (!taskDispatchThread_.isRunning()) { return; } if (taskDispatchThread_.isOnThread()) { runnable(); } else { - taskDispatchThread_.runSync( - [runnable = std::move(runnable)]() noexcept { runnable(); }); + taskDispatchThread_.runSync([runnable = std::move(runnable)]() noexcept { runnable(); }); } } diff --git a/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/MessageQueueThreadImpl.h b/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/MessageQueueThreadImpl.h index 9332fa8130c..0f4bdf0f85d 100644 --- a/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/MessageQueueThreadImpl.h +++ b/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/MessageQueueThreadImpl.h @@ -5,8 +5,9 @@ * 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 - +// [Windows] Brought forward from react-native - switch to using react-native/ReactCxxPlatform/react/threading version +// once we integrate that far + #pragma once #include @@ -15,8 +16,7 @@ namespace facebook::react { -using MessageQueueThreadFactory = - std::function()>; +using MessageQueueThreadFactory = std::function()>; constexpr char MessageQueueThreadFactoryKey[] = "MessageQueueThreadFactoryKey"; @@ -27,20 +27,19 @@ constexpr char MessageQueueThreadFactoryKey[] = "MessageQueueThreadFactoryKey"; class MessageQueueThreadImpl : public MessageQueueThread { public: MessageQueueThreadImpl() noexcept = default; - explicit MessageQueueThreadImpl(int priorityOffset) noexcept - : taskDispatchThread_("MessageQueue", priorityOffset) {} + 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; + 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; + void runOnQueueSync(std::function &&runnable) override; /** * Stop the message queue thread. Should only be used for cleanup - once it diff --git a/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/TaskDispatchThread.cpp b/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/TaskDispatchThread.cpp index fcdc91a34a7..9c7318103bd 100644 --- a/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/TaskDispatchThread.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/TaskDispatchThread.cpp @@ -5,8 +5,9 @@ * 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 - +// [Windows] Brought forward from react-native - switch to using react-native/ReactCxxPlatform/react/threading version +// once we integrate that far + #include "TaskDispatchThread.h" #include @@ -24,22 +25,16 @@ namespace facebook::react { -TaskDispatchThread::TaskDispatchThread( - std::string threadName, - int priorityOffset) noexcept +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); + int result = setpriority(PRIO_PROCESS, static_cast(::syscall(SYS_gettid)), priorityOffset); if (result != 0) { - LOG(INFO) << " setCurrentThreadPriority failed with pri errno: " - << errno; + LOG(INFO) << " setCurrentThreadPriority failed with pri errno: " << errno; } loop(); @@ -63,9 +58,7 @@ bool TaskDispatchThread::isRunning() noexcept { return running_; } -void TaskDispatchThread::runAsync( - TaskFn&& task, - std::chrono::milliseconds delayMs) noexcept { +void TaskDispatchThread::runAsync(TaskFn &&task, std::chrono::milliseconds delayMs) noexcept { if (!running_) { return; } @@ -75,7 +68,7 @@ void TaskDispatchThread::runAsync( loopCv_.notify_one(); } -void TaskDispatchThread::runSync(TaskFn&& task) noexcept { +void TaskDispatchThread::runSync(TaskFn &&task) noexcept { std::promise promise; runAsync([&]() { if (running_) { diff --git a/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/TaskDispatchThread.h b/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/TaskDispatchThread.h index 9478dea3af5..1f161ca6997 100644 --- a/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/TaskDispatchThread.h +++ b/vnext/Microsoft.ReactNative/Fabric/platform/react/threading/TaskDispatchThread.h @@ -5,8 +5,9 @@ * 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 - +// [Windows] Brought forward from react-native - switch to using react-native/ReactCxxPlatform/react/threading version +// once we integrate that far + #pragma once #include @@ -27,9 +28,7 @@ class TaskDispatchThread { using TaskFn = std::function; using TimePoint = std::chrono::time_point; - TaskDispatchThread( - std::string threadName = "", - int priorityOffset = 0) noexcept; + TaskDispatchThread(std::string threadName = "", int priorityOffset = 0) noexcept; ~TaskDispatchThread() noexcept; @@ -40,13 +39,10 @@ class TaskDispatchThread { bool isRunning() noexcept; /** Add task to the queue and return immediately. */ - void runAsync( - TaskFn&& task, - std::chrono::milliseconds delayMs = - std::chrono::milliseconds::zero()) noexcept; + 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; + void runSync(TaskFn &&task) noexcept; /** Shut down and clean up the thread. */ void quit() noexcept; @@ -56,10 +52,9 @@ class TaskDispatchThread { TimePoint dispatchTime; TaskFn fn; - Task(TimePoint dispatchTime, TaskFn&& fn) - : dispatchTime(dispatchTime), fn(std::move(fn)) {} + Task(TimePoint dispatchTime, TaskFn &&fn) : dispatchTime(dispatchTime), fn(std::move(fn)) {} - bool operator<(const Task& other) const { + bool operator<(const Task &other) const { // Have the earliest tasks be at the front of the queue. return dispatchTime > other.dispatchTime; } diff --git a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp index f3740655dd1..8d791035239 100644 --- a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp +++ b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp @@ -62,10 +62,10 @@ #include #include #include -#include #include #include #include +#include #include "CallInvokerDispatcher.h" #endif @@ -1134,7 +1134,7 @@ void ReactInstanceWin::InitJSMessageThread() noexcept { auto callInvoker = m_instance.Load()->getJSCallInvoker(); auto scheduler = Mso::MakeJSCallInvokerScheduler( CreateDispatchQueueSettings(m_reactContext->Notifications()), - std::shared_ptr(callInvoker), + std::shared_ptr(callInvoker), Mso::MakeWeakMemberFunctor(this, &ReactInstanceWin::OnError), Mso::Copy(m_whenDestroyed)); auto jsDispatchQueue = Mso::DispatchQueue::MakeCustomQueue(Mso::CntPtr(scheduler)); diff --git a/vnext/Microsoft.ReactNative/TurboModulesProvider.cpp b/vnext/Microsoft.ReactNative/TurboModulesProvider.cpp index 559ebf23e73..8f8e6081d5d 100644 --- a/vnext/Microsoft.ReactNative/TurboModulesProvider.cpp +++ b/vnext/Microsoft.ReactNative/TurboModulesProvider.cpp @@ -7,13 +7,13 @@ #include "pch.h" #include "TurboModulesProvider.h" +#include #include #include #include "JSDispatcherWriter.h" #include "JSValueWriter.h" #include "JsiApi.h" #include "JsiReader.h" -#include #include "JsiWriter.h" #ifdef __APPLE__ #include "Crash.h" @@ -39,12 +39,16 @@ struct TurboModuleBuilder : winrt::implements(m_reactContext)->GetInner().JsiRuntime()); + initializer( + m_reactContext, + winrt::get_self(m_reactContext) + ->GetInner() + .JsiRuntime()); } void AddConstantProvider(ConstantProviderDelegate const &constantProvider) noexcept { @@ -124,9 +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(); + winrt::get_self(m_reactContext) + ->GetInner() + .JsiRuntime(); m_hostObjectWrapper = std::make_shared(hostObject); } } From 8c0bac9870cb198ba79c41a8e21ab2a684dbaf5d Mon Sep 17 00:00:00 2001 From: Andrew Coates <30809111+acoates-ms@users.noreply.github.com> Date: Mon, 12 May 2025 12:43:14 -0700 Subject: [PATCH 08/17] format --- vnext/Shared/Modules/BlobModule.cpp | 2 +- vnext/Shared/Modules/BlobModule.h | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/vnext/Shared/Modules/BlobModule.cpp b/vnext/Shared/Modules/BlobModule.cpp index ac6a0706972..d0754c15db1 100644 --- a/vnext/Shared/Modules/BlobModule.cpp +++ b/vnext/Shared/Modules/BlobModule.cpp @@ -35,7 +35,7 @@ namespace Microsoft::React { #pragma region BlobTurboModule -void BlobTurboModule::Initialize(msrn::ReactContext const &reactContext, facebook::jsi::Runtime& runtime) 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}); diff --git a/vnext/Shared/Modules/BlobModule.h b/vnext/Shared/Modules/BlobModule.h index 856c5e66075..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, facebook::jsi::Runtime& runtime) noexcept; + void Initialize( + winrt::Microsoft::ReactNative::ReactContext const &reactContext, + facebook::jsi::Runtime &runtime) noexcept; REACT_GET_CONSTANTS(GetConstants) ReactNativeSpecs::BlobModuleSpec_Constants GetConstants() noexcept; From 25619a9304958d2b3356b0d80e0257cfe8ef270c Mon Sep 17 00:00:00 2001 From: Andrew Coates <30809111+acoates-ms@users.noreply.github.com> Date: Tue, 13 May 2025 08:23:07 -0700 Subject: [PATCH 09/17] build fix --- vnext/Microsoft.ReactNative.Cxx/JSI/JsiApiContext.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vnext/Microsoft.ReactNative.Cxx/JSI/JsiApiContext.cpp b/vnext/Microsoft.ReactNative.Cxx/JSI/JsiApiContext.cpp index 4c2f6606dbf..c30107259f3 100644 --- a/vnext/Microsoft.ReactNative.Cxx/JSI/JsiApiContext.cpp +++ b/vnext/Microsoft.ReactNative.Cxx/JSI/JsiApiContext.cpp @@ -4,7 +4,9 @@ #include "pch.h" #include "JsiApiContext.h" +#if RNW_NEW_ARCH #include +#endif // Use __ImageBase to get current DLL handle. // http://blogs.msdn.com/oldnewthing/archive/2004/10/25/247180.aspx @@ -72,6 +74,7 @@ facebook::jsi::Runtime *TryGetOrCreateContextRuntime( // Note: deprecated in favor of TryGetOrCreateContextRuntime with Handle parameter facebook::jsi::Runtime *TryGetOrCreateContextRuntime(ReactContext const &context) noexcept { #ifdef DEBUG +#ifdef RNW_NEW_ARCH // TODO move this check into ReactContext.JSRuntime(). VerifyElseCrashSz( !context.Properties().Get(winrt::Microsoft::ReactNative::ReactPropertyId< @@ -81,6 +84,7 @@ facebook::jsi::Runtime *TryGetOrCreateContextRuntime(ReactContext const &context ReactDispatcher jsDispatcher = context.JSDispatcher(); VerifyElseCrashSz(jsDispatcher.HasThreadAccess(), "Must be in JS thread"); +#endif #endif if (auto runtimeHandle = context.Handle().JSRuntime()) { From 579ee4772a87cc8f67c0392f856fddf6d149f89d Mon Sep 17 00:00:00 2001 From: Andrew Coates <30809111+acoates-ms@users.noreply.github.com> Date: Tue, 13 May 2025 09:40:40 -0700 Subject: [PATCH 10/17] build fixes --- .../ReactModuleBuilderMock.cs | 8 ++ .../CallInvokerWriter.cpp | 132 ++++++++++++++++++ .../Microsoft.ReactNative/CallInvokerWriter.h | 64 +++++++++ vnext/Microsoft.ReactNative/IReactContext.cpp | 11 ++ .../JSDispatcherWriter.cpp | 32 +---- .../TurboModulesProvider.cpp | 24 ++-- vnext/Shared/Shared.vcxitems | 3 + 7 files changed, 231 insertions(+), 43 deletions(-) create mode 100644 vnext/Microsoft.ReactNative/CallInvokerWriter.cpp create mode 100644 vnext/Microsoft.ReactNative/CallInvokerWriter.h diff --git a/vnext/Microsoft.ReactNative.Managed.UnitTests/ReactModuleBuilderMock.cs b/vnext/Microsoft.ReactNative.Managed.UnitTests/ReactModuleBuilderMock.cs index 87aa0a1e046..7f8cd2c840c 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); @@ -356,6 +362,8 @@ public ReactContextMock(ReactModuleBuilderMock builder) public IReactDispatcher JSDispatcher => Properties.Get(ReactDispatcherHelper.JSDispatcherProperty) as IReactDispatcher; + public CallInvoker CallInvoker => throw new NotImplementedException(); + public Object JSRuntime => throw new NotImplementedException(); public void DispatchEvent(FrameworkElement view, string eventName, JSValueArgWriter eventDataArgWriter) diff --git a/vnext/Microsoft.ReactNative/CallInvokerWriter.cpp b/vnext/Microsoft.ReactNative/CallInvokerWriter.cpp new file mode 100644 index 00000000000..35cbdc98d0f --- /dev/null +++ b/vnext/Microsoft.ReactNative/CallInvokerWriter.cpp @@ -0,0 +1,132 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" +#include "CallInvokerWriter.h" +#include +#include + +namespace winrt::Microsoft::ReactNative { + +//=========================================================================== +// CallInvokerWriter implementation +//=========================================================================== + +CallInvokerWriter::CallInvokerWriter( + const std::shared_ptr &jsInvoker, + std::weak_ptr jsiRuntimeHolder) noexcept + : m_callInvoker(jsInvoker), m_jsiRuntimeHolder(std::move(jsiRuntimeHolder)) {} + +CallInvokerWriter::~CallInvokerWriter() { + if (auto jsiRuntimeHolder = m_jsiRuntimeHolder.lock()) { + jsiRuntimeHolder->allowRelease(); + } +} + +void CallInvokerWriter::WithResultArgs( + Mso::Functor + handler) noexcept { + /* + if (m_jsDispatcher.HasThreadAccess()) { + VerifyElseCrash(!m_dynamicWriter); + if (auto jsiRuntimeHolder = m_jsiRuntimeHolder.lock()) { + const facebook::jsi::Value *args{nullptr}; + size_t argCount{0}; + m_jsiWriter->AccessResultAsArgs(args, argCount); + handler(jsiRuntimeHolder->Runtime(), args, argCount); + m_jsiWriter = nullptr; + } + } else { + */ + VerifyElseCrash(!m_jsiWriter); + folly::dynamic dynValue = m_dynamicWriter->TakeValue(); + VerifyElseCrash(dynValue.isArray()); + m_callInvoker->invokeAsync( + [handler, dynValue = std::move(dynValue), weakJsiRuntimeHolder = m_jsiRuntimeHolder, self = get_strong()]( + facebook::jsi::Runtime &runtime) { + std::vector args; + args.reserve(dynValue.size()); + for (auto const &item : dynValue) { + args.emplace_back(facebook::jsi::valueFromDynamic(runtime, item)); + } + handler(runtime, args.data(), args.size()); + }); + //} +} + +void CallInvokerWriter::WriteNull() noexcept { + GetWriter().WriteNull(); +} + +void CallInvokerWriter::WriteBoolean(bool value) noexcept { + GetWriter().WriteBoolean(value); +} + +void CallInvokerWriter::WriteInt64(int64_t value) noexcept { + GetWriter().WriteInt64(value); +} + +void CallInvokerWriter::WriteDouble(double value) noexcept { + GetWriter().WriteDouble(value); +} + +void CallInvokerWriter::WriteString(const winrt::hstring &value) noexcept { + GetWriter().WriteString(value); +} + +void CallInvokerWriter::WriteObjectBegin() noexcept { + GetWriter().WriteObjectBegin(); +} + +void CallInvokerWriter::WritePropertyName(const winrt::hstring &name) noexcept { + GetWriter().WritePropertyName(name); +} + +void CallInvokerWriter::WriteObjectEnd() noexcept { + GetWriter().WriteObjectEnd(); +} + +void CallInvokerWriter::WriteArrayBegin() noexcept { + GetWriter().WriteArrayBegin(); +} + +void CallInvokerWriter::WriteArrayEnd() noexcept { + GetWriter().WriteArrayEnd(); +} + +IJSValueWriter CallInvokerWriter::GetWriter() noexcept { + if (!m_writer) { + /* + if (m_jsDispatcher.HasThreadAccess()) { + if (auto jsiRuntimeHolder = m_jsiRuntimeHolder.lock()) { + m_jsiWriter = winrt::make_self(jsiRuntimeHolder->Runtime()); + m_writer = m_jsiWriter.as(); + } else { + m_writer = winrt::make(); + } + } else { + */ + m_dynamicWriter = winrt::make_self(); + m_writer = m_dynamicWriter.as(); + //} + } + Debug(VerifyElseCrash(m_dynamicWriter != nullptr /* || m_jsDispatcher.HasThreadAccess()*/)); + return m_writer; +} + +//=========================================================================== +// JSNoopWriter implementation +//=========================================================================== + +void JSNoopWriter::WriteNull() noexcept {} +void JSNoopWriter::WriteBoolean(bool /*value*/) noexcept {} +void JSNoopWriter::WriteInt64(int64_t /*value*/) noexcept {} +void JSNoopWriter::WriteDouble(double /*value*/) noexcept {} +void JSNoopWriter::WriteString(const winrt::hstring & /*value*/) noexcept {} +void JSNoopWriter::WriteObjectBegin() noexcept {} +void JSNoopWriter::WritePropertyName(const winrt::hstring & /*name*/) noexcept {} +void JSNoopWriter::WriteObjectEnd() noexcept {} +void JSNoopWriter::WriteArrayBegin() noexcept {} +void JSNoopWriter::WriteArrayEnd() noexcept {} + +} // namespace winrt::Microsoft::ReactNative \ No newline at end of file diff --git a/vnext/Microsoft.ReactNative/CallInvokerWriter.h b/vnext/Microsoft.ReactNative/CallInvokerWriter.h new file mode 100644 index 00000000000..913efa27b67 --- /dev/null +++ b/vnext/Microsoft.ReactNative/CallInvokerWriter.h @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once + +#include +#include +#include +#include "DynamicWriter.h" +#include "JsiWriter.h" +#include "winrt/Microsoft.ReactNative.h" + +namespace winrt::Microsoft::ReactNative { + +// IJSValueWriter to ensure that JsiWriter is always used from a RuntimeExecutor. +// In case if writing is done outside of RuntimeExecutor, it uses DynamicWriter to create +// folly::dynamic which then is written to JsiWriter in RuntimeExecutor. +struct CallInvokerWriter : winrt::implements { + ~CallInvokerWriter(); + CallInvokerWriter( + const std::shared_ptr &jsInvoker, + std::weak_ptr jsiRuntimeHolder) noexcept; + void WithResultArgs(Mso::Functor + handler) noexcept; + + 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; + + private: + IJSValueWriter GetWriter() noexcept; + + private: + const std::shared_ptr m_callInvoker; + std::weak_ptr m_jsiRuntimeHolder; + winrt::com_ptr m_dynamicWriter; + winrt::com_ptr m_jsiWriter; + IJSValueWriter m_writer; +}; + +// 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/IReactContext.cpp b/vnext/Microsoft.ReactNative/IReactContext.cpp index a84e047ec13..1e106b61ba5 100644 --- a/vnext/Microsoft.ReactNative/IReactContext.cpp +++ b/vnext/Microsoft.ReactNative/IReactContext.cpp @@ -9,6 +9,7 @@ #endif #include "CallInvoker.h" +#include "Utils/Helpers.h" namespace winrt::Microsoft::ReactNative::implementation { @@ -99,6 +100,11 @@ 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(); } @@ -107,6 +113,11 @@ winrt::Microsoft::ReactNative::CallInvoker ReactContext::CallInvoker() noexcept } 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/JSDispatcherWriter.cpp b/vnext/Microsoft.ReactNative/JSDispatcherWriter.cpp index 264600596ef..f91005dc370 100644 --- a/vnext/Microsoft.ReactNative/JSDispatcherWriter.cpp +++ b/vnext/Microsoft.ReactNative/JSDispatcherWriter.cpp @@ -5,25 +5,10 @@ #include "JSDispatcherWriter.h" #include #include +#include "CallInvokerWriter.h" 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; -}; - //=========================================================================== // JSDispatcherWriter implementation //=========================================================================== @@ -128,19 +113,4 @@ IJSValueWriter JSDispatcherWriter::GetWriter() noexcept { return m_writer; } -//=========================================================================== -// JSNoopWriter implementation -//=========================================================================== - -void JSNoopWriter::WriteNull() noexcept {} -void JSNoopWriter::WriteBoolean(bool /*value*/) noexcept {} -void JSNoopWriter::WriteInt64(int64_t /*value*/) noexcept {} -void JSNoopWriter::WriteDouble(double /*value*/) noexcept {} -void JSNoopWriter::WriteString(const winrt::hstring & /*value*/) noexcept {} -void JSNoopWriter::WriteObjectBegin() noexcept {} -void JSNoopWriter::WritePropertyName(const winrt::hstring & /*name*/) noexcept {} -void JSNoopWriter::WriteObjectEnd() noexcept {} -void JSNoopWriter::WriteArrayBegin() noexcept {} -void JSNoopWriter::WriteArrayEnd() noexcept {} - } // namespace winrt::Microsoft::ReactNative diff --git a/vnext/Microsoft.ReactNative/TurboModulesProvider.cpp b/vnext/Microsoft.ReactNative/TurboModulesProvider.cpp index 8f8e6081d5d..a89031616e9 100644 --- a/vnext/Microsoft.ReactNative/TurboModulesProvider.cpp +++ b/vnext/Microsoft.ReactNative/TurboModulesProvider.cpp @@ -10,7 +10,7 @@ #include #include #include -#include "JSDispatcherWriter.h" +#include "CallInvokerWriter.h" #include "JSValueWriter.h" #include "JsiApi.h" #include "JsiReader.h" @@ -215,7 +215,7 @@ class TurboModuleImpl : public facebook::react::TurboModule { runtime, propName, 0, - [jsDispatcher = m_reactContext.JSDispatcher(), + [jsInvoker = jsInvoker_, method = methodInfo.Method, longLivedObjectCollection = m_longLivedObjectCollection]( facebook::jsi::Runtime &rt, @@ -227,7 +227,7 @@ class TurboModuleImpl : public facebook::react::TurboModule { auto jsiRuntimeHolder = LongLivedJsiRuntime::CreateWeak(strongLongLivedObjectCollection, rt); method( winrt::make(rt, args, argCount - 1), - winrt::make(jsDispatcher, jsiRuntimeHolder), + winrt::make(jsInvoker, jsiRuntimeHolder), MakeCallback(rt, strongLongLivedObjectCollection, args[argCount - 1]), nullptr); } @@ -238,7 +238,7 @@ class TurboModuleImpl : public facebook::react::TurboModule { runtime, propName, 0, - [jsDispatcher = m_reactContext.JSDispatcher(), + [jsInvoker = jsInvoker_, method = methodInfo.Method, longLivedObjectCollection = m_longLivedObjectCollection]( facebook::jsi::Runtime &rt, @@ -255,9 +255,9 @@ class TurboModuleImpl : public facebook::react::TurboModule { method( winrt::make(rt, args, argCount - 2), - winrt::make(jsDispatcher, jsiRuntimeHolder), + winrt::make(jsInvoker, jsiRuntimeHolder), [weakCallback1, weakCallback2, jsiRuntimeHolder](const IJSValueWriter &writer) noexcept { - writer.as()->WithResultArgs( + writer.as()->WithResultArgs( [weakCallback1, weakCallback2, jsiRuntimeHolder]( facebook::jsi::Runtime &rt, facebook::jsi::Value const *args, size_t count) { if (auto callback1 = weakCallback1.lock()) { @@ -273,7 +273,7 @@ class TurboModuleImpl : public facebook::react::TurboModule { }); }, [weakCallback1, weakCallback2, jsiRuntimeHolder](const IJSValueWriter &writer) noexcept { - writer.as()->WithResultArgs( + writer.as()->WithResultArgs( [weakCallback1, weakCallback2, jsiRuntimeHolder]( facebook::jsi::Runtime &rt, facebook::jsi::Value const *args, size_t count) { if (auto callback2 = weakCallback2.lock()) { @@ -296,7 +296,7 @@ class TurboModuleImpl : public facebook::react::TurboModule { runtime, propName, 0, - [jsDispatcher = m_reactContext.JSDispatcher(), + [jsInvoker = jsInvoker_, method = methodInfo.Method, longLivedObjectCollection = m_longLivedObjectCollection]( facebook::jsi::Runtime &rt, @@ -306,7 +306,7 @@ class TurboModuleImpl : public facebook::react::TurboModule { if (auto strongLongLivedObjectCollection = longLivedObjectCollection.lock()) { auto jsiRuntimeHolder = LongLivedJsiRuntime::CreateWeak(strongLongLivedObjectCollection, rt); auto argReader = winrt::make(rt, args, count); - auto argWriter = winrt::make(jsDispatcher, jsiRuntimeHolder); + auto argWriter = winrt::make(jsInvoker, jsiRuntimeHolder); return facebook::react::createPromiseAsJSIValue( rt, [method, argReader, argWriter, strongLongLivedObjectCollection, jsiRuntimeHolder]( @@ -319,7 +319,7 @@ class TurboModuleImpl : public facebook::react::TurboModule { argReader, argWriter, [weakResolve, weakReject, jsiRuntimeHolder](const IJSValueWriter &writer) { - writer.as()->WithResultArgs( + writer.as()->WithResultArgs( [weakResolve, weakReject, jsiRuntimeHolder]( facebook::jsi::Runtime &runtime, facebook::jsi::Value const *args, @@ -338,7 +338,7 @@ class TurboModuleImpl : public facebook::react::TurboModule { }); }, [weakResolve, weakReject, jsiRuntimeHolder](const IJSValueWriter &writer) { - writer.as()->WithResultArgs( + writer.as()->WithResultArgs( [weakResolve, weakReject, jsiRuntimeHolder]( facebook::jsi::Runtime &runtime, facebook::jsi::Value const *args, @@ -451,7 +451,7 @@ class TurboModuleImpl : public facebook::react::TurboModule { auto weakCallback = LongLivedJsiFunction::CreateWeak(longLivedObjectCollection, rt, callback.getObject(rt).getFunction(rt)); return [weakCallback = std::move(weakCallback)](const IJSValueWriter &writer) noexcept { - writer.as()->WithResultArgs( + writer.as()->WithResultArgs( [weakCallback](facebook::jsi::Runtime &rt, facebook::jsi::Value const *args, size_t count) { if (auto callback = weakCallback.lock()) { callback->Value().call(rt, args, count); diff --git a/vnext/Shared/Shared.vcxitems b/vnext/Shared/Shared.vcxitems index d6c46c79e7a..1ca164d4b42 100644 --- a/vnext/Shared/Shared.vcxitems +++ b/vnext/Shared/Shared.vcxitems @@ -405,6 +405,9 @@ $(MSBuildThisFileDirectory)..\Microsoft.ReactNative\IReactDispatcher.idl + + $(MSBuildThisFileDirectory)..\Microsoft.ReactNative\IJSValueWriter.idl + $(MSBuildThisFileDirectory)..\Microsoft.ReactNative\IJSValueWriter.idl From 138023b37c6bbb95b26b9652f41f207e9745d730 Mon Sep 17 00:00:00 2001 From: Andrew Coates <30809111+acoates-ms@users.noreply.github.com> Date: Tue, 13 May 2025 11:18:28 -0700 Subject: [PATCH 11/17] Cleanup --- .../JSI/JsiAbiApi.cpp | 6 ++--- .../TurboModuleProvider.h | 3 --- .../TurboModuleTests.cpp | 11 +-------- .../CallInvokerDispatcher.cpp | 23 ------------------- .../CallInvokerDispatcher.h | 22 ------------------ .../Microsoft.ReactNative/CallInvokerWriter.h | 22 +++++++++--------- .../ReactHost/ReactInstanceWin.cpp | 10 -------- .../ReactHost/ReactInstanceWin.h | 2 +- vnext/Shared/Shared.vcxitems | 3 --- 9 files changed, 15 insertions(+), 87 deletions(-) delete mode 100644 vnext/Microsoft.ReactNative/CallInvokerDispatcher.cpp delete mode 100644 vnext/Microsoft.ReactNative/CallInvokerDispatcher.h diff --git a/vnext/Microsoft.ReactNative.Cxx/JSI/JsiAbiApi.cpp b/vnext/Microsoft.ReactNative.Cxx/JSI/JsiAbiApi.cpp index efc4c71babc..d9a8d9f91b3 100644 --- a/vnext/Microsoft.ReactNative.Cxx/JSI/JsiAbiApi.cpp +++ b/vnext/Microsoft.ReactNative.Cxx/JSI/JsiAbiApi.cpp @@ -219,10 +219,8 @@ 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. /// TODO - make no longer thread -// specific It is DLL-specific because JsiAbiRuntime is not ABI-safe and each module DLL will have their own +// 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; diff --git a/vnext/Microsoft.ReactNative.Cxx/TurboModuleProvider.h b/vnext/Microsoft.ReactNative.Cxx/TurboModuleProvider.h index 4c9cc00ba02..465b3a2133f 100644 --- a/vnext/Microsoft.ReactNative.Cxx/TurboModuleProvider.h +++ b/vnext/Microsoft.ReactNative.Cxx/TurboModuleProvider.h @@ -24,9 +24,6 @@ void AddTurboModuleProvider(IReactPackageBuilder const &packageBuilder, std::wst moduleBuilder.AddInitializer([&abiTurboModule](IReactContext const &context) mutable { auto callInvoker = ReactContext{context}.CallInvoker(); auto turboModule = std::make_shared(callInvoker); - // TODO test back with JsiHostObjectWrapper and remove JsiHostObjectGetOrCreateWrapper if not needed - // Repalce above moduleBuilder.AddInitializer to moduleBuilder.AddJsiInitializer then we dont need - // JsiHostObjectGetOrCreateWrapper abiTurboModule = winrt::make(context, std::move(turboModule)); }); return abiTurboModule.as(); diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/TurboModuleTests.cpp b/vnext/Microsoft.ReactNative.IntegrationTests/TurboModuleTests.cpp index cf233740575..287104d97f8 100644 --- a/vnext/Microsoft.ReactNative.IntegrationTests/TurboModuleTests.cpp +++ b/vnext/Microsoft.ReactNative.IntegrationTests/TurboModuleTests.cpp @@ -673,12 +673,6 @@ 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"); @@ -686,13 +680,10 @@ TEST_CLASS (TurboModuleTests) { }; bool callbackIsCalled{false}; -#if USE_FABRIC + // callInvoker must not process any callbacks callInvoker.InvokeAsync( [&callbackIsCalled, data = std::make_shared()]( const winrt::Windows::Foundation::IInspectable & /*runtimeHandle*/) { callbackIsCalled = true; }); -#else - jsDispatcher.Post([&callbackIsCalled, data = std::make_shared()] { callbackIsCalled = true; }); -#endif TestNotificationService::Wait("CallbackData destroyed"); TestCheck(!callbackIsCalled); } diff --git a/vnext/Microsoft.ReactNative/CallInvokerDispatcher.cpp b/vnext/Microsoft.ReactNative/CallInvokerDispatcher.cpp deleted file mode 100644 index e237c4259f7..00000000000 --- a/vnext/Microsoft.ReactNative/CallInvokerDispatcher.cpp +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "CallInvokerDispatcher.h" - -namespace Microsoft::ReactNative { - -CallInvokerDispatcher::CallInvokerDispatcher(std::shared_ptr &&callInvoker) noexcept - : m_callInvoker(callInvoker) {} - -bool CallInvokerDispatcher::HasThreadAccess() noexcept { - return m_threadId == std::this_thread::get_id(); -} - -void CallInvokerDispatcher::Post(winrt::Microsoft::ReactNative::ReactDispatcherCallback const &callback) noexcept { - m_callInvoker->invokeAsync([this, callback = std::move(callback)](facebook::jsi::Runtime &) { - m_threadId = std::this_thread::get_id(); - callback(); - m_threadId = std::thread::id{}; - }); -} - -} // namespace Microsoft::ReactNative diff --git a/vnext/Microsoft.ReactNative/CallInvokerDispatcher.h b/vnext/Microsoft.ReactNative/CallInvokerDispatcher.h deleted file mode 100644 index f1767862107..00000000000 --- a/vnext/Microsoft.ReactNative/CallInvokerDispatcher.h +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once -#include -#include - -namespace Microsoft::ReactNative { - -struct CallInvokerDispatcher - : winrt::implements { - CallInvokerDispatcher(std::shared_ptr &&callInvoker) noexcept; - - bool HasThreadAccess() noexcept; - void Post(winrt::Microsoft::ReactNative::ReactDispatcherCallback const &callback) noexcept; - - private: - std::atomic m_threadId{}; - std::shared_ptr m_callInvoker; -}; - -} // namespace Microsoft::ReactNative diff --git a/vnext/Microsoft.ReactNative/CallInvokerWriter.h b/vnext/Microsoft.ReactNative/CallInvokerWriter.h index 913efa27b67..f9372105f66 100644 --- a/vnext/Microsoft.ReactNative/CallInvokerWriter.h +++ b/vnext/Microsoft.ReactNative/CallInvokerWriter.h @@ -48,17 +48,17 @@ struct CallInvokerWriter : winrt::implements // 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; + 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/ReactHost/ReactInstanceWin.cpp b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp index 8d791035239..7941f125f45 100644 --- a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp +++ b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp @@ -66,7 +66,6 @@ #include #include #include -#include "CallInvokerDispatcher.h" #endif #if !defined(CORE_ABI) && !defined(USE_FABRIC) @@ -690,11 +689,6 @@ void ReactInstanceWin::InitializeBridgeless() noexcept { ReactPropertyBag(m_options.Properties), winrt::make( *m_reactContext, std::shared_ptr(callInvoker))); - - m_options.Properties.Set( - ReactDispatcherHelper::JSDispatcherProperty(), - winrt::make( - std::shared_ptr(callInvoker))); }); m_options.TurboModuleProvider->SetReactContext( @@ -1096,7 +1090,6 @@ Mso::Future ReactInstanceWin::Destroy() noexcept { #ifdef USE_FABRIC if (m_bridgelessReactInstance) { - auto jsDispatchQueue = m_jsDispatchQueue.Exchange(nullptr); // TODO should be null for bridgeless if (auto jsMessageThread = m_jsMessageThread.Exchange(nullptr)) { jsMessageThread->runOnQueueSync([&]() noexcept { { @@ -1108,9 +1101,6 @@ Mso::Future ReactInstanceWin::Destroy() noexcept { } this->m_bridgelessReactInstance = nullptr; jsMessageThread->quitSynchronous(); - if (jsDispatchQueue) { - jsDispatchQueue.Shutdown(PendingTaskAction::Complete); - } m_whenDestroyed.SetValue(); }); } 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/Shared/Shared.vcxitems b/vnext/Shared/Shared.vcxitems index 1ca164d4b42..17b099075fe 100644 --- a/vnext/Shared/Shared.vcxitems +++ b/vnext/Shared/Shared.vcxitems @@ -402,9 +402,6 @@ $(MSBuildThisFileDirectory)..\Microsoft.ReactNative\IReactDispatcher.idl - - $(MSBuildThisFileDirectory)..\Microsoft.ReactNative\IReactDispatcher.idl - $(MSBuildThisFileDirectory)..\Microsoft.ReactNative\IJSValueWriter.idl From 4721c2c4aa98a5e771b8016bd497880fce783cf0 Mon Sep 17 00:00:00 2001 From: Andrew Coates <30809111+acoates-ms@users.noreply.github.com> Date: Tue, 13 May 2025 12:13:02 -0700 Subject: [PATCH 12/17] Add runtime handle to instance created and instance loaded events --- .../ReactContextTest.cpp | 4 ++++ .../ReactModuleBuilderMock.h | 5 ++++ .../JsiSimpleTurboModuleTests.cpp | 9 +++----- .../ReactNativeHostTests.cpp | 23 +++++++++++++++++++ vnext/Microsoft.ReactNative/IReactContext.idl | 4 +++- .../IReactDispatcher.idl | 1 + .../ReactInstanceSettings.cpp | 12 ++++++++++ .../ReactInstanceSettings.h | 2 ++ .../ReactInstanceSettings.idl | 6 +++++ 9 files changed, 59 insertions(+), 7 deletions(-) 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.h b/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactModuleBuilderMock.h index ba1e2f941b7..dae87e5a71f 100644 --- a/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactModuleBuilderMock.h +++ b/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactModuleBuilderMock.h @@ -101,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; @@ -133,6 +134,10 @@ struct ReactContextMock : 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.IntegrationTests/JsiSimpleTurboModuleTests.cpp b/vnext/Microsoft.ReactNative.IntegrationTests/JsiSimpleTurboModuleTests.cpp index bb5d3eb0465..e033b9267fc 100644 --- a/vnext/Microsoft.ReactNative.IntegrationTests/JsiSimpleTurboModuleTests.cpp +++ b/vnext/Microsoft.ReactNative.IntegrationTests/JsiSimpleTurboModuleTests.cpp @@ -57,28 +57,25 @@ TEST_CLASS (JsiSimpleTurboModuleTests) { host.InstanceSettings().InstanceCreated( [&](IInspectable const & /*sender*/, InstanceCreatedEventArgs const &args) { TestEventService::LogEvent("Instance created event", nullptr); -#if USE_FABRIC // Save this thread as the js thread jsThreadId = std::this_thread::get_id(); -#else +#if !USE_FABRIC TestCheck(ReactContext(args.Context()).JSDispatcher().HasThreadAccess()); #endif }); host.InstanceSettings().InstanceLoaded( [&](IInspectable const & /*sender*/, InstanceLoadedEventArgs const &args) { TestEventService::LogEvent("Instance loaded event", nullptr); -#if USE_FABRIC TestCheck(jsThreadId == std::this_thread::get_id()); -#else +#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); -#if USE_FABRIC TestCheck(jsThreadId == std::this_thread::get_id()); -#else +#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 a9cfcff6199..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(); diff --git a/vnext/Microsoft.ReactNative/IReactContext.idl b/vnext/Microsoft.ReactNative/IReactContext.idl index 83c0e20a173..2c497a6a8b4 100644 --- a/vnext/Microsoft.ReactNative/IReactContext.idl +++ b/vnext/Microsoft.ReactNative/IReactContext.idl @@ -185,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.") @@ -194,7 +195,8 @@ 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.") 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/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] From 5744638cd880adecdff2a589e314ea473ddd6c3c Mon Sep 17 00:00:00 2001 From: Andrew Coates <30809111+acoates-ms@users.noreply.github.com> Date: Tue, 13 May 2025 14:36:21 -0700 Subject: [PATCH 13/17] fix --- vnext/Microsoft.ReactNative.Cxx/JSI/JsiApiContext.cpp | 2 +- vnext/Shared/Shared.vcxitems.filters | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/vnext/Microsoft.ReactNative.Cxx/JSI/JsiApiContext.cpp b/vnext/Microsoft.ReactNative.Cxx/JSI/JsiApiContext.cpp index c30107259f3..a14f02ecbdd 100644 --- a/vnext/Microsoft.ReactNative.Cxx/JSI/JsiApiContext.cpp +++ b/vnext/Microsoft.ReactNative.Cxx/JSI/JsiApiContext.cpp @@ -4,7 +4,7 @@ #include "pch.h" #include "JsiApiContext.h" -#if RNW_NEW_ARCH +#ifdef RNW_NEW_ARCH #include #endif 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 @@ + From 2da78e83c205d300f81162f4b72f1d0474dc8fdc Mon Sep 17 00:00:00 2001 From: Andrew Coates <30809111+acoates-ms@users.noreply.github.com> Date: Tue, 13 May 2025 15:25:27 -0700 Subject: [PATCH 14/17] fix --- vnext/Microsoft.ReactNative.Managed/ReactContext.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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); From 4490085bbbe3c69ccee866b9fa54dab71b2c56f9 Mon Sep 17 00:00:00 2001 From: Andrew Coates <30809111+acoates-ms@users.noreply.github.com> Date: Tue, 13 May 2025 16:21:33 -0700 Subject: [PATCH 15/17] fix --- .../Microsoft.ReactNative.Cxx/JSI/JsiApiContext.cpp | 13 ------------- .../ReactModuleBuilderMock.cs | 2 ++ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/vnext/Microsoft.ReactNative.Cxx/JSI/JsiApiContext.cpp b/vnext/Microsoft.ReactNative.Cxx/JSI/JsiApiContext.cpp index a14f02ecbdd..46e00403166 100644 --- a/vnext/Microsoft.ReactNative.Cxx/JSI/JsiApiContext.cpp +++ b/vnext/Microsoft.ReactNative.Cxx/JSI/JsiApiContext.cpp @@ -4,10 +4,6 @@ #include "pch.h" #include "JsiApiContext.h" -#ifdef RNW_NEW_ARCH -#include -#endif - // Use __ImageBase to get current DLL handle. // http://blogs.msdn.com/oldnewthing/archive/2004/10/25/247180.aspx extern "C" IMAGE_DOS_HEADER __ImageBase; @@ -74,17 +70,8 @@ facebook::jsi::Runtime *TryGetOrCreateContextRuntime( // Note: deprecated in favor of TryGetOrCreateContextRuntime with Handle parameter facebook::jsi::Runtime *TryGetOrCreateContextRuntime(ReactContext const &context) noexcept { #ifdef DEBUG -#ifdef RNW_NEW_ARCH - // TODO move this check into ReactContext.JSRuntime(). - VerifyElseCrashSz( - !context.Properties().Get(winrt::Microsoft::ReactNative::ReactPropertyId< - winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext>{ - L"ReactNative.Composition", L"CompositionContext"}), - "ExecuteJsi/TryGetOrCreateContextRuntime not supported on new arch, use ReactContext.CallInvoker instead."); - ReactDispatcher jsDispatcher = context.JSDispatcher(); VerifyElseCrashSz(jsDispatcher.HasThreadAccess(), "Must be in JS thread"); -#endif #endif if (auto runtimeHandle = context.Handle().JSRuntime()) { diff --git a/vnext/Microsoft.ReactNative.Managed.UnitTests/ReactModuleBuilderMock.cs b/vnext/Microsoft.ReactNative.Managed.UnitTests/ReactModuleBuilderMock.cs index 7f8cd2c840c..53a072a15a8 100644 --- a/vnext/Microsoft.ReactNative.Managed.UnitTests/ReactModuleBuilderMock.cs +++ b/vnext/Microsoft.ReactNative.Managed.UnitTests/ReactModuleBuilderMock.cs @@ -360,7 +360,9 @@ 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(); From 5e4716d357591452f0f51deb1dbbbac4c3920368 Mon Sep 17 00:00:00 2001 From: Andrew Coates <30809111+acoates-ms@users.noreply.github.com> Date: Tue, 13 May 2025 17:04:25 -0700 Subject: [PATCH 16/17] fix --- vnext/Microsoft.ReactNative.Cxx/JSI/JsiAbiApi.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/vnext/Microsoft.ReactNative.Cxx/JSI/JsiAbiApi.cpp b/vnext/Microsoft.ReactNative.Cxx/JSI/JsiAbiApi.cpp index d9a8d9f91b3..82f699225cc 100644 --- a/vnext/Microsoft.ReactNative.Cxx/JSI/JsiAbiApi.cpp +++ b/vnext/Microsoft.ReactNative.Cxx/JSI/JsiAbiApi.cpp @@ -242,7 +242,6 @@ JsiAbiRuntime::JsiAbiRuntime(JsiRuntime const &runtime) noexcept : m_runtime{run JsiAbiRuntime::~JsiAbiRuntime() { std::lock_guard guard(s_jsiRuntimeMapMutex); - VerifyElseCrash(GetFromJsiRuntime(m_runtime) != nullptr); s_jsiAbiRuntimeMap->erase(get_abi(m_runtime)); if (s_jsiAbiRuntimeMap->empty()) { delete s_jsiAbiRuntimeMap; From 95e30a5f9a54fb27db993bbb57de13badc1100aa Mon Sep 17 00:00:00 2001 From: Andrew Coates <30809111+acoates-ms@users.noreply.github.com> Date: Tue, 13 May 2025 17:08:52 -0700 Subject: [PATCH 17/17] Remove unused JSDispatcherWriter --- vnext/Desktop/React.Windows.Desktop.vcxproj | 3 - .../JSDispatcherWriter.cpp | 116 ------------------ .../JSDispatcherWriter.h | 47 ------- .../Microsoft.ReactNative.vcxproj | 3 - vnext/Shared/Shared.vcxitems | 3 - 5 files changed, 172 deletions(-) delete mode 100644 vnext/Microsoft.ReactNative/JSDispatcherWriter.cpp delete mode 100644 vnext/Microsoft.ReactNative/JSDispatcherWriter.h diff --git a/vnext/Desktop/React.Windows.Desktop.vcxproj b/vnext/Desktop/React.Windows.Desktop.vcxproj index f8717bc7976..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/Microsoft.ReactNative/JSDispatcherWriter.cpp b/vnext/Microsoft.ReactNative/JSDispatcherWriter.cpp deleted file mode 100644 index f91005dc370..00000000000 --- a/vnext/Microsoft.ReactNative/JSDispatcherWriter.cpp +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "pch.h" -#include "JSDispatcherWriter.h" -#include -#include -#include "CallInvokerWriter.h" - -namespace winrt::Microsoft::ReactNative { - -//=========================================================================== -// JSDispatcherWriter implementation -//=========================================================================== - -JSDispatcherWriter::JSDispatcherWriter( - IReactDispatcher const &jsDispatcher, - std::weak_ptr jsiRuntimeHolder) noexcept - : m_jsDispatcher(jsDispatcher), m_jsiRuntimeHolder(std::move(jsiRuntimeHolder)) {} - -JSDispatcherWriter::~JSDispatcherWriter() { - if (auto jsiRuntimeHolder = m_jsiRuntimeHolder.lock()) { - jsiRuntimeHolder->allowRelease(); - } -} - -void JSDispatcherWriter::WithResultArgs( - Mso::Functor - handler) noexcept { - if (m_jsDispatcher.HasThreadAccess()) { - VerifyElseCrash(!m_dynamicWriter); - if (auto jsiRuntimeHolder = m_jsiRuntimeHolder.lock()) { - const facebook::jsi::Value *args{nullptr}; - size_t argCount{0}; - m_jsiWriter->AccessResultAsArgs(args, argCount); - handler(jsiRuntimeHolder->Runtime(), args, argCount); - m_jsiWriter = nullptr; - } - } else { - VerifyElseCrash(!m_jsiWriter); - folly::dynamic dynValue = m_dynamicWriter->TakeValue(); - VerifyElseCrash(dynValue.isArray()); - m_jsDispatcher.Post( - [handler, dynValue = std::move(dynValue), weakJsiRuntimeHolder = m_jsiRuntimeHolder, self = get_strong()]() { - if (auto jsiRuntimeHolder = weakJsiRuntimeHolder.lock()) { - std::vector args; - args.reserve(dynValue.size()); - auto &runtime = jsiRuntimeHolder->Runtime(); - for (auto const &item : dynValue) { - args.emplace_back(facebook::jsi::valueFromDynamic(runtime, item)); - } - handler(runtime, args.data(), args.size()); - } - }); - } -} - -void JSDispatcherWriter::WriteNull() noexcept { - GetWriter().WriteNull(); -} - -void JSDispatcherWriter::WriteBoolean(bool value) noexcept { - GetWriter().WriteBoolean(value); -} - -void JSDispatcherWriter::WriteInt64(int64_t value) noexcept { - GetWriter().WriteInt64(value); -} - -void JSDispatcherWriter::WriteDouble(double value) noexcept { - GetWriter().WriteDouble(value); -} - -void JSDispatcherWriter::WriteString(const winrt::hstring &value) noexcept { - GetWriter().WriteString(value); -} - -void JSDispatcherWriter::WriteObjectBegin() noexcept { - GetWriter().WriteObjectBegin(); -} - -void JSDispatcherWriter::WritePropertyName(const winrt::hstring &name) noexcept { - GetWriter().WritePropertyName(name); -} - -void JSDispatcherWriter::WriteObjectEnd() noexcept { - GetWriter().WriteObjectEnd(); -} - -void JSDispatcherWriter::WriteArrayBegin() noexcept { - GetWriter().WriteArrayBegin(); -} - -void JSDispatcherWriter::WriteArrayEnd() noexcept { - GetWriter().WriteArrayEnd(); -} - -IJSValueWriter JSDispatcherWriter::GetWriter() noexcept { - if (!m_writer) { - if (m_jsDispatcher.HasThreadAccess()) { - if (auto jsiRuntimeHolder = m_jsiRuntimeHolder.lock()) { - m_jsiWriter = winrt::make_self(jsiRuntimeHolder->Runtime()); - m_writer = m_jsiWriter.as(); - } else { - m_writer = winrt::make(); - } - } else { - m_dynamicWriter = winrt::make_self(); - m_writer = m_dynamicWriter.as(); - } - } - Debug(VerifyElseCrash(m_dynamicWriter != nullptr || m_jsDispatcher.HasThreadAccess())); - return m_writer; -} - -} // namespace winrt::Microsoft::ReactNative diff --git a/vnext/Microsoft.ReactNative/JSDispatcherWriter.h b/vnext/Microsoft.ReactNative/JSDispatcherWriter.h deleted file mode 100644 index 50b2cb1c107..00000000000 --- a/vnext/Microsoft.ReactNative/JSDispatcherWriter.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once - -#include -#include -#include "DynamicWriter.h" -#include "JsiWriter.h" -#include "winrt/Microsoft.ReactNative.h" - -namespace winrt::Microsoft::ReactNative { - -// IJSValueWriter to ensure that JsiWriter is always used from JSDispatcher. -// In case if writing is done outside of JSDispatcher, it uses DynamicWriter to create -// folly::dynamic which then is written to JsiWriter in JSDispatcher. -struct JSDispatcherWriter : winrt::implements { - ~JSDispatcherWriter(); - JSDispatcherWriter( - IReactDispatcher const &jsDispatcher, - std::weak_ptr jsiRuntimeHolder) noexcept; - void WithResultArgs(Mso::Functor - handler) noexcept; - - 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; - - private: - IJSValueWriter GetWriter() noexcept; - - private: - IReactDispatcher m_jsDispatcher; - std::weak_ptr m_jsiRuntimeHolder; - winrt::com_ptr m_dynamicWriter; - winrt::com_ptr m_jsiWriter; - IJSValueWriter m_writer; -}; - -} // namespace winrt::Microsoft::ReactNative diff --git a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj index 937850c9d4d..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/Shared/Shared.vcxitems b/vnext/Shared/Shared.vcxitems index 17b099075fe..ae3676e070b 100644 --- a/vnext/Shared/Shared.vcxitems +++ b/vnext/Shared/Shared.vcxitems @@ -405,9 +405,6 @@ $(MSBuildThisFileDirectory)..\Microsoft.ReactNative\IJSValueWriter.idl - - $(MSBuildThisFileDirectory)..\Microsoft.ReactNative\IJSValueWriter.idl - $(MSBuildThisFileDirectory)..\Microsoft.ReactNative\Timer.idl