diff --git a/change/react-native-windows-5fdef670-daf7-48a0-978f-27aba2e64394.json b/change/react-native-windows-5fdef670-daf7-48a0-978f-27aba2e64394.json new file mode 100644 index 00000000000..c0b0bc74b3b --- /dev/null +++ b/change/react-native-windows-5fdef670-daf7-48a0-978f-27aba2e64394.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Wire up Fusebox InspectorPackagerConnection", + "packageName": "react-native-windows", + "email": "erozell@outlook.com", + "dependentChangeType": "patch" +} diff --git a/packages/playground/windows/playground-win32/Playground-Win32.cpp b/packages/playground/windows/playground-win32/Playground-Win32.cpp index a3c45b43f68..5e068ff2447 100644 --- a/packages/playground/windows/playground-win32/Playground-Win32.cpp +++ b/packages/playground/windows/playground-win32/Playground-Win32.cpp @@ -85,6 +85,7 @@ struct WindowData { winrt::Microsoft::ReactNative::ReactNativeHost Host() noexcept { if (!m_host) { + winrt::Microsoft::ReactNative::QuirkSettings::SetUseFusebox(true); m_host = winrt::Microsoft::ReactNative::ReactNativeHost(); m_host.InstanceSettings(InstanceSettings()); } diff --git a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj index 932722fbf5f..e2683d37b9a 100644 --- a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj +++ b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj @@ -281,6 +281,7 @@ Code + diff --git a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj.filters b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj.filters index ad0c2b22282..e2480686490 100644 --- a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj.filters +++ b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj.filters @@ -313,6 +313,9 @@ ReactHost + + ReactHost + ReactHost diff --git a/vnext/Microsoft.ReactNative/QuirkSettings.cpp b/vnext/Microsoft.ReactNative/QuirkSettings.cpp index 81dddc80a82..82ba20df624 100644 --- a/vnext/Microsoft.ReactNative/QuirkSettings.cpp +++ b/vnext/Microsoft.ReactNative/QuirkSettings.cpp @@ -9,10 +9,26 @@ #include "React.h" #include "ReactPropertyBag.h" +#include +#include + namespace winrt::Microsoft::ReactNative::implementation { QuirkSettings::QuirkSettings() noexcept {} +class QuirkSettingsReactNativeFeatureFlags : public facebook::react::ReactNativeFeatureFlagsDefaults { + public: + QuirkSettingsReactNativeFeatureFlags(bool enableModernCDPRegistry) + : m_enableModernCDPRegistry(enableModernCDPRegistry) {} + + bool inspectorEnableModernCDPRegistry() override { + return m_enableModernCDPRegistry; + } + + private: + bool m_enableModernCDPRegistry; +}; + winrt::Microsoft::ReactNative::ReactPropertyId MatchAndroidAndIOSStretchBehaviorProperty() noexcept { static winrt::Microsoft::ReactNative::ReactPropertyId propId{ L"ReactNative.QuirkSettings", L"MatchAndroidAndIOSyStretchBehavior"}; @@ -137,6 +153,10 @@ winrt::Microsoft::ReactNative::ReactPropertyId IsBridgelessProperty() noex ReactPropertyBag(settings.Properties()).Set(UseRuntimeSchedulerProperty(), value); } +/*static*/ void QuirkSettings::SetUseFusebox(bool value) noexcept { + facebook::react::ReactNativeFeatureFlags::override(std::make_unique(value)); +} + #pragma endregion IDL interface /*static*/ bool QuirkSettings::GetMatchAndroidAndIOSStretchBehavior(ReactPropertyBag properties) noexcept { diff --git a/vnext/Microsoft.ReactNative/QuirkSettings.h b/vnext/Microsoft.ReactNative/QuirkSettings.h index 0c5d73e94e4..75469a420d1 100644 --- a/vnext/Microsoft.ReactNative/QuirkSettings.h +++ b/vnext/Microsoft.ReactNative/QuirkSettings.h @@ -68,6 +68,8 @@ struct QuirkSettings : QuirkSettingsT { winrt::Microsoft::ReactNative::ReactInstanceSettings settings, bool value) noexcept; + static void SetUseFusebox(bool value) noexcept; + #pragma endregion Public API - part of IDL interface }; diff --git a/vnext/Microsoft.ReactNative/QuirkSettings.idl b/vnext/Microsoft.ReactNative/QuirkSettings.idl index a4ad798aced..ed398025afb 100644 --- a/vnext/Microsoft.ReactNative/QuirkSettings.idl +++ b/vnext/Microsoft.ReactNative/QuirkSettings.idl @@ -64,6 +64,11 @@ namespace Microsoft.ReactNative "By default `react-native-windows` will use the new RuntimeScheduler." "Setting this to false will revert the behavior to previous scheduling logic.") static void SetUseRuntimeScheduler(ReactInstanceSettings settings, Boolean value); + + DOC_STRING( + "By default `react-native-windows` uses the legacy inspector packager connection protocol." + "Setting this to true to enable the modern \"Fusebox\" debugging functionality.") + static void SetUseFusebox(Boolean value); } } // namespace Microsoft.ReactNative diff --git a/vnext/Microsoft.ReactNative/ReactHost/DebuggerNotifications.h b/vnext/Microsoft.ReactNative/ReactHost/DebuggerNotifications.h new file mode 100644 index 00000000000..6a083140a34 --- /dev/null +++ b/vnext/Microsoft.ReactNative/ReactHost/DebuggerNotifications.h @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include + +namespace Microsoft::ReactNative { + +struct DebuggerNotifications { + static winrt::Microsoft::ReactNative::IReactPropertyName ShowDebuggerPausedOverlayEventName() noexcept { + static winrt::Microsoft::ReactNative::IReactPropertyName propertyName{ + winrt::Microsoft::ReactNative::ReactPropertyBagHelper::GetName( + winrt::Microsoft::ReactNative::ReactPropertyBagHelper::GetNamespace(L"ReactNative.Debugger"), + L"ShowDebuggerPausedOverlay")}; + return propertyName; + } + + static void OnShowDebuggerPausedOverlay( + winrt::Microsoft::ReactNative::IReactNotificationService const &service, + std::string message, + std::function onResume) { + const winrt::Microsoft::ReactNative::ReactNonAbiValue>> nonAbiValue{ + std::in_place, std::tie(message, onResume)}; + service.SendNotification(ShowDebuggerPausedOverlayEventName(), nullptr, nonAbiValue); + } + + static void OnHideDebuggerPausedOverlay(winrt::Microsoft::ReactNative::IReactNotificationService const &service) { + service.SendNotification(ShowDebuggerPausedOverlayEventName(), nullptr, nullptr); + } + + static winrt::Microsoft::ReactNative::IReactNotificationSubscription SubscribeShowDebuggerPausedOverlay( + winrt::Microsoft::ReactNative::IReactNotificationService const &service, + winrt::Microsoft::ReactNative::IReactDispatcher const &dispatcher, + std::function)> showCallback, + std::function hideCallback) { + return service.Subscribe( + ShowDebuggerPausedOverlayEventName(), + dispatcher, + [showCallback, hideCallback](auto &&, winrt::Microsoft::ReactNative::IReactNotificationArgs const &args) { + if (args.Data()) { + const auto [message, onResume] = args.Data() + .as>>>() + .Value(); + showCallback(message, onResume); + } else { + hideCallback(); + } + }); + } +}; + +} // namespace Microsoft::ReactNative diff --git a/vnext/Microsoft.ReactNative/ReactHost/React.h b/vnext/Microsoft.ReactNative/ReactHost/React.h index 5a56fa70482..de9aae753cd 100644 --- a/vnext/Microsoft.ReactNative/ReactHost/React.h +++ b/vnext/Microsoft.ReactNative/ReactHost/React.h @@ -32,6 +32,10 @@ #include #include +namespace facebook::react::jsinspector_modern { +class HostTarget; +} // namespace facebook::react::jsinspector_modern + namespace Mso::React { // Forward declarations @@ -343,6 +347,9 @@ struct ReactOptions { //! The callback is called when IReactInstance is destroyed and must not be used anymore. //! It is called from the native queue. OnReactInstanceDestroyedCallback OnInstanceDestroyed; + + //! The HostTarget instance for Fusebox + facebook::react::jsinspector_modern::HostTarget *InspectorTarget; }; //! IReactHost manages a ReactNative instance. diff --git a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp index 379b1b2e2f1..1283d156165 100644 --- a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp +++ b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp @@ -291,6 +291,18 @@ ReactInstanceWin::ReactInstanceWin( } ReactInstanceWin::~ReactInstanceWin() noexcept { +#ifdef USE_FABRIC + if (m_bridgelessReactInstance && m_options.InspectorTarget) { + auto messageDispatchQueue = + Mso::React::MessageDispatchQueue(::Microsoft::ReactNative::FuseboxInspectorThread::Instance(), nullptr); + messageDispatchQueue.runOnQueueSync([weakBridgelessReactInstance = std::weak_ptr(m_bridgelessReactInstance)]() { + if (auto bridgelessReactInstance = weakBridgelessReactInstance.lock()) { + bridgelessReactInstance->unregisterFromInspector(); + } + }); + } +#endif + std::scoped_lock lock{s_registryMutex}; auto it = std::find(s_instanceRegistry.begin(), s_instanceRegistry.end(), this); if (it != s_instanceRegistry.end()) { @@ -527,6 +539,8 @@ std::shared_ptr ReactInstanceWin::CreateDevSetting devSettings->useRuntimeScheduler = useRuntimeScheduler; + devSettings->inspectorTarget = m_options.InspectorTarget; + return devSettings; } @@ -611,15 +625,19 @@ void ReactInstanceWin::InitializeBridgeless() noexcept { if (devSettings->useDirectDebugger) { ::Microsoft::ReactNative::GetSharedDevManager()->EnsureHermesInspector( - devSettings->sourceBundleHost, devSettings->sourceBundlePort); + devSettings->sourceBundleHost, devSettings->sourceBundlePort, devSettings->bundleAppId); } m_jsiRuntimeHolder = std::make_shared( devSettings, m_jsMessageThread.Load(), CreateHermesPreparedScriptStore()); auto jsRuntime = std::make_unique(m_jsiRuntimeHolder); jsRuntime->getRuntime(); - m_bridgelessReactInstance = std::make_unique( - std::move(jsRuntime), m_jsMessageThread.Load(), timerManager, jsErrorHandlingFunc); + m_bridgelessReactInstance = std::make_shared( + std::move(jsRuntime), + m_jsMessageThread.Load(), + timerManager, + jsErrorHandlingFunc, + m_options.InpectorTarget); auto bufferedRuntimeExecutor = m_bridgelessReactInstance->getBufferedRuntimeExecutor(); timerManager->setRuntimeExecutor(bufferedRuntimeExecutor); diff --git a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.h b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.h index c043a5ce335..c64d2a41e58 100644 --- a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.h +++ b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.h @@ -204,7 +204,7 @@ class ReactInstanceWin final : public Mso::ActiveObject #ifdef USE_FABRIC // Bridgeless - std::unique_ptr m_bridgelessReactInstance; + std::shared_ptr m_bridgelessReactInstance; #endif std::atomic m_state{ReactInstanceState::Loading}; diff --git a/vnext/Microsoft.ReactNative/ReactNativeHost.cpp b/vnext/Microsoft.ReactNative/ReactNativeHost.cpp index 8fc98d62cc7..8d9c35d7555 100644 --- a/vnext/Microsoft.ReactNative/ReactNativeHost.cpp +++ b/vnext/Microsoft.ReactNative/ReactNativeHost.cpp @@ -5,13 +5,16 @@ #include "ReactNativeHost.h" #include "ReactNativeHost.g.cpp" +#include "FuseboxInspectorThread.h" #include "ReactPackageBuilder.h" #include "RedBox.h" #include "TurboModulesProvider.h" #include +#include #include #include "IReactContext.h" +#include "ReactHost/DebuggerNotifications.h" #include "ReactInstanceSettings.h" #ifdef USE_FABRIC @@ -30,6 +33,39 @@ using namespace xaml::Controls; namespace winrt::Microsoft::ReactNative::implementation { +class FuseboxHostTargetDelegate : public facebook::react::jsinspector_modern::HostTargetDelegate, + public std::enable_shared_from_this { + public: + FuseboxHostTargetDelegate(ReactNativeHost *reactNativeHost) : m_reactNativeHost(reactNativeHost) {} + + void onReload(facebook::react::jsinspector_modern::HostTargetDelegate::PageReloadRequest const &request) override { + m_reactNativeHost->ReloadInstance(); + } + +#ifdef HAS_FUSEBOX_PAUSED_OVERLAY // Remove after syncing past https://github.com/facebook/react-native/pull/44078 + void onSetPausedInDebuggerMessage( + facebook::react::jsinspector_modern::HostTargetDelegate::OverlaySetPausedInDebuggerMessageRequest const &request) + override { + const auto instanceSettings = m_reactNativeHost->InstanceSettings(); + if (instanceSettings) { + if (request.message.has_value()) { + ::Microsoft::ReactNative::DebuggerNotifications::OnShowDebuggerPausedOverlay( + instanceSettings.Notifications(), request.message.value(), [weakThis = weak_from_this()]() { + if (auto strongThis = weakThis.lock()) { + strongThis->m_reactNativeHost->OnDebuggerResume(); + } + }); + } else { + ::Microsoft::ReactNative::DebuggerNotifications::OnHideDebuggerPausedOverlay(instanceSettings.Notifications()); + } + } + } +#endif + + private: + ReactNativeHost *m_reactNativeHost; +}; + ReactNativeHost::ReactNativeHost() noexcept : m_reactHost{Mso::React::MakeReactHost()} { #if _DEBUG facebook::react::InitializeLogging([](facebook::react::RCTLogLevel /*logLevel*/, const char *message) { @@ -37,6 +73,45 @@ ReactNativeHost::ReactNativeHost() noexcept : m_reactHost{Mso::React::MakeReactH OutputDebugStringA(str.c_str()); }); #endif + + auto &inspectorFlags = facebook::react::jsinspector_modern::InspectorFlags::getInstance(); + if (inspectorFlags.getEnableModernCDPRegistry() && !m_inspectorPageId.has_value()) { + m_inspectorHostDelegate = std::make_shared(this); + m_inspectorTarget = facebook::react::jsinspector_modern::HostTarget::create( + *m_inspectorHostDelegate, [](std::function &&callback) { + ::Microsoft::ReactNative::FuseboxInspectorThread::Instance().InvokeElsePost([callback]() { callback(); }); + }); + + std::weak_ptr weakInspectorTarget = m_inspectorTarget; + facebook::react::jsinspector_modern::InspectorTargetCapabilities capabilities; +#ifdef HAS_FUSEBOX_CAPABILITIES // Remove after syncing past https://github.com/facebook/react-native/pull/43689 + capabilities.nativePageReloads = true; + capabilities.prefersFuseboxFrontend = true; +#endif + m_inspectorPageId = facebook::react::jsinspector_modern::getInspectorInstance().addPage( + "React Native Windows (Experimental)", + /* vm */ "", + [weakInspectorTarget](std::unique_ptr remote) + -> std::unique_ptr { + if (const auto inspectorTarget = weakInspectorTarget.lock()) { + facebook::react::jsinspector_modern::HostTarget::SessionMetadata sessionMetadata; + sessionMetadata.integrationName = "React Native Windows (Host)"; + return inspectorTarget->connect(std::move(remote), sessionMetadata); + } + + // This can happen if we're about to be dealloc'd. Reject the connection. + return nullptr; + }, + capabilities); + } +} + +ReactNativeHost::~ReactNativeHost() noexcept { + if (m_inspectorPageId.has_value()) { + facebook::react::jsinspector_modern::getInspectorInstance().removePage(*m_inspectorPageId); + m_inspectorPageId.reset(); + m_inspectorTarget.reset(); + } } /*static*/ ReactNative::ReactNativeHost ReactNativeHost::FromContext( @@ -186,6 +261,7 @@ IAsyncAction ReactNativeHost::ReloadInstance() noexcept { } reactOptions.Identity = jsBundleFile; + reactOptions.InspectorTarget = m_inspectorTarget.get(); return make(m_reactHost->ReloadInstanceWithOptions(std::move(reactOptions))); } @@ -197,4 +273,15 @@ Mso::React::IReactHost *ReactNativeHost::ReactHost() noexcept { return m_reactHost.Get(); } +void ReactNativeHost::OnDebuggerResume() noexcept { +#ifdef HAS_FUSEBOX_PAUSED_OVERLAY // Remove after syncing past https://github.com/facebook/react-native/pull/44078 + ::Microsoft::ReactNative::FuseboxInspectorThread::Instance().InvokeElsePost( + [weakInspectorTarget = std::weak_ptr(m_inspectorTarget)]() { + if (const auto inspectorTarget = weakInspectorTarget.lock()) { + inspectorTarget->sendCommand(facebook::react::jsinspector_modern::HostCommand::DebuggerResume); + } + }); +#endif +} + } // namespace winrt::Microsoft::ReactNative::implementation diff --git a/vnext/Microsoft.ReactNative/ReactNativeHost.h b/vnext/Microsoft.ReactNative/ReactNativeHost.h index d133e8bd883..a78894039a3 100644 --- a/vnext/Microsoft.ReactNative/ReactNativeHost.h +++ b/vnext/Microsoft.ReactNative/ReactNativeHost.h @@ -5,6 +5,7 @@ #include "ReactNativeHost.g.h" +#include #include "NativeModulesProvider.h" #include "ReactHost/React.h" #include "ReactInstanceSettings.h" @@ -16,6 +17,7 @@ namespace winrt::Microsoft::ReactNative::implementation { struct ReactNativeHost : ReactNativeHostT { public: // ReactNativeHost ABI API ReactNativeHost() noexcept; + ~ReactNativeHost() noexcept; static ReactNative::ReactNativeHost FromContext(ReactNative::IReactContext const &reactContext) noexcept; @@ -25,6 +27,7 @@ struct ReactNativeHost : ReactNativeHostT { // property InstanceSettings ReactNative::ReactInstanceSettings InstanceSettings() noexcept; void InstanceSettings(ReactNative::ReactInstanceSettings const &value) noexcept; + void OnDebuggerResume() noexcept; winrt::Windows::Foundation::IAsyncAction LoadInstance() noexcept; winrt::Windows::Foundation::IAsyncAction ReloadInstance() noexcept; @@ -39,6 +42,10 @@ struct ReactNativeHost : ReactNativeHostT { ReactNative::ReactInstanceSettings m_instanceSettings{nullptr}; ReactNative::IReactPackageBuilder m_packageBuilder; + + std::shared_ptr m_inspectorHostDelegate{nullptr}; + std::shared_ptr m_inspectorTarget{nullptr}; + std::optional m_inspectorPageId{std::nullopt}; }; } // namespace winrt::Microsoft::ReactNative::implementation diff --git a/vnext/Microsoft.ReactNative/ReactRootView.cpp b/vnext/Microsoft.ReactNative/ReactRootView.cpp index a697f76aa39..e04b0b813c2 100644 --- a/vnext/Microsoft.ReactNative/ReactRootView.cpp +++ b/vnext/Microsoft.ReactNative/ReactRootView.cpp @@ -5,14 +5,19 @@ #include "ReactRootView.g.cpp" #include +#include #include +#include +#include #include #include #include #include #include +#include "InstanceManager.h" #include "ReactNativeHost.h" #include "ReactViewInstance.h" +#include "Utils/KeyboardUtils.h" #include "XamlUtils.h" #include @@ -34,6 +39,7 @@ ReactRootView::ReactRootView() noexcept : m_uiQueue(Mso::DispatchQueue::GetCurre UpdatePerspective(); Loaded([this](auto &&, auto &&) { ::Microsoft::ReactNative::SetCompositor(::Microsoft::ReactNative::GetCompositor(*this)); + SetupDevToolsShortcut(); }); } @@ -45,6 +51,20 @@ void ReactRootView::ReactNativeHost(ReactNative::ReactNativeHost const &value) n if (m_reactNativeHost != value) { ReactViewHost(nullptr); m_reactNativeHost = value; + const auto weakThis = this->get_weak(); + ::Microsoft::ReactNative::DebuggerNotifications::SubscribeShowDebuggerPausedOverlay( + m_reactNativeHost.InstanceSettings().Notifications(), + m_reactNativeHost.InstanceSettings().UIDispatcher(), + [weakThis](std::string message, std::function onResume) { + if (auto strongThis = weakThis.get()) { + strongThis->ShowDebuggerPausedOverlay(message, onResume); + } + }, + [weakThis]() { + if (auto strongThis = weakThis.get()) { + strongThis->HideDebuggerPausedOverlay(); + } + }); ReloadView(); } } @@ -283,6 +303,65 @@ void ReactRootView::EnsureLoadingUI() noexcept { } } +void ReactRootView::HideDebuggerPausedOverlay() noexcept { + m_isDebuggerPausedOverlayOpen = false; + if (m_debuggerPausedFlyout) { + m_debuggerPausedFlyout.Hide(); + m_debuggerPausedFlyout = nullptr; + } +} + +void ReactRootView::ShowDebuggerPausedOverlay( + const std::string &message, + const std::function &onResume) noexcept { + // Initialize content + const xaml::Controls::Grid contentGrid; + xaml::Controls::ColumnDefinition messageColumnDefinition; + xaml::Controls::ColumnDefinition buttonColumnDefinition; + messageColumnDefinition.MinWidth(60); + buttonColumnDefinition.MinWidth(36); + contentGrid.ColumnDefinitions().Append(messageColumnDefinition); + contentGrid.ColumnDefinitions().Append(buttonColumnDefinition); + xaml::Controls::TextBlock messageBlock; + messageBlock.Text(winrt::to_hstring(message)); + messageBlock.FontWeight(winrt::Windows::UI::Text::FontWeights::SemiBold()); + xaml::Controls::FontIcon resumeGlyph; + resumeGlyph.FontFamily(xaml::Media::FontFamily(L"Segoe MDL2 Assets")); + resumeGlyph.Foreground(xaml::Media::SolidColorBrush(winrt::Colors::Green())); + resumeGlyph.Glyph(L"\uF5B0"); + resumeGlyph.HorizontalAlignment(xaml::HorizontalAlignment::Right); + resumeGlyph.PointerReleased([onResume](auto &&...) { onResume(); }); + xaml::Controls::Grid::SetColumn(resumeGlyph, 1); + contentGrid.Children().Append(messageBlock); + contentGrid.Children().Append(resumeGlyph); + + // Configure flyout + m_isDebuggerPausedOverlayOpen = true; + xaml::Style flyoutStyle( + {XAML_NAMESPACE_STR L".Controls.FlyoutPresenter", winrt::Windows::UI::Xaml::Interop::TypeKind::Metadata}); + flyoutStyle.Setters().Append(winrt::Setter( + xaml::Controls::Control::CornerRadiusProperty(), winrt::box_value(xaml::CornerRadius{12, 12, 12, 12}))); + flyoutStyle.Setters().Append(winrt::Setter( + xaml::Controls::Control::BackgroundProperty(), + winrt::box_value(xaml::Media::SolidColorBrush{FromArgb(255, 255, 255, 193)}))); + flyoutStyle.Setters().Append( + winrt::Setter(xaml::FrameworkElement::MarginProperty(), winrt::box_value(xaml::Thickness{0, 12, 0, 0}))); + m_debuggerPausedFlyout = xaml::Controls::Flyout{}; + m_debuggerPausedFlyout.FlyoutPresenterStyle(flyoutStyle); + m_debuggerPausedFlyout.LightDismissOverlayMode(xaml::Controls::LightDismissOverlayMode::On); + m_debuggerPausedFlyout.Content(contentGrid); + + // Disable light dismiss + m_debuggerPausedFlyout.Closing([weakThis = this->get_weak()](auto &&, const auto &args) { + if (auto strongThis = weakThis.get()) { + args.Cancel(strongThis->m_isDebuggerPausedOverlayOpen); + } + }); + + // Show flyout + m_debuggerPausedFlyout.ShowAt(*this); +} + void ReactRootView::ShowInstanceLoaded() noexcept { if (m_xamlRootView) { ClearLoadingUI(); @@ -481,4 +560,33 @@ void ReactRootView::RemoveChildAt(uint32_t index) { Children().RemoveAt(RNIndexToXamlIndex(index)); } +bool IsCtrlShiftI(winrt::Windows::System::VirtualKey key) noexcept { + return ( + key == winrt::Windows::System::VirtualKey::I && + ::Microsoft::ReactNative::IsModifiedKeyPressed( + winrt::CoreWindow::GetForCurrentThread(), winrt::Windows::System::VirtualKey::Shift) && + ::Microsoft::ReactNative::IsModifiedKeyPressed( + winrt::CoreWindow::GetForCurrentThread(), winrt::Windows::System::VirtualKey::Control)); +} + +void ReactRootView::SetupDevToolsShortcut() noexcept { + if (auto xamlRoot = XamlRoot()) { + if (std::find(m_subscribedDebuggerRoots.begin(), m_subscribedDebuggerRoots.end(), xamlRoot) == + m_subscribedDebuggerRoots.end()) { + if (auto rootContent = xamlRoot.Content()) { + m_subscribedDebuggerRoots.push_back(xamlRoot); + rootContent.KeyDown( + [weakThis = this->get_weak()](const auto & /*sender*/, const xaml::Input::KeyRoutedEventArgs &args) { + if (const auto strongThis = weakThis.get()) { + if (IsCtrlShiftI(args.Key())) { + ::Microsoft::ReactNative::GetSharedDevManager()->OpenDevTools( + winrt::to_string(strongThis->m_reactNativeHost.InstanceSettings().BundleAppId())); + } + }; + }); + } + } + } +} + } // namespace winrt::Microsoft::ReactNative::implementation diff --git a/vnext/Microsoft.ReactNative/ReactRootView.h b/vnext/Microsoft.ReactNative/ReactRootView.h index f35a3c3fd60..876ac7e38cb 100644 --- a/vnext/Microsoft.ReactNative/ReactRootView.h +++ b/vnext/Microsoft.ReactNative/ReactRootView.h @@ -72,6 +72,7 @@ struct ReactRootView : ReactRootViewT, ::Microsoft::ReactNative:: bool m_isPerspectiveEnabled{true}; bool m_isInitialized{false}; bool m_isJSViewAttached{false}; + bool m_isDebuggerPausedOverlayOpen{false}; Mso::DispatchQueue m_uiQueue; int64_t m_rootTag{-1}; std::unique_ptr m_reactOptions; @@ -84,9 +85,11 @@ struct ReactRootView : ReactRootViewT, ::Microsoft::ReactNative:: std::shared_ptr<::Microsoft::ReactNative::PreviewKeyboardEventHandlerOnRoot> m_previewKeyboardEventHandlerOnRoot; xaml::Controls::ContentControl m_focusSafeHarbor{nullptr}; xaml::Controls::ContentControl::LosingFocus_revoker m_focusSafeHarborLosingFocusRevoker{}; + xaml::Controls::Flyout m_debuggerPausedFlyout{nullptr}; winrt::Grid m_greenBoxGrid{nullptr}; winrt::TextBlock m_waitingTextBlock{nullptr}; winrt::SystemNavigationManager::BackRequested_revoker m_backRequestedRevoker{}; + std::vector m_subscribedDebuggerRoots{}; // Visual tree to support safe harbor // this @@ -102,6 +105,8 @@ struct ReactRootView : ReactRootViewT, ::Microsoft::ReactNative:: void UpdateRootViewInternal() noexcept; void ClearLoadingUI() noexcept; void EnsureLoadingUI() noexcept; + void HideDebuggerPausedOverlay() noexcept; + void ShowDebuggerPausedOverlay(const std::string &message, const std::function &onResume) noexcept; void ShowInstanceLoaded() noexcept; void ShowInstanceError() noexcept; void ShowInstanceWaiting() noexcept; @@ -112,6 +117,7 @@ struct ReactRootView : ReactRootViewT, ::Microsoft::ReactNative:: bool OnBackRequested() noexcept; Mso::React::IReactViewHost *ReactViewHost() noexcept; void ReactViewHost(Mso::React::IReactViewHost *viewHost) noexcept; + void SetupDevToolsShortcut() noexcept; }; } // namespace winrt::Microsoft::ReactNative::implementation diff --git a/vnext/ReactCommon/ReactCommon.vcxproj b/vnext/ReactCommon/ReactCommon.vcxproj index b299a156afa..0e17e4ae0b7 100644 --- a/vnext/ReactCommon/ReactCommon.vcxproj +++ b/vnext/ReactCommon/ReactCommon.vcxproj @@ -109,7 +109,9 @@ + + @@ -131,7 +133,9 @@ + + diff --git a/vnext/Shared/DevServerHelper.h b/vnext/Shared/DevServerHelper.h index 384a7619810..55ce1881638 100644 --- a/vnext/Shared/DevServerHelper.h +++ b/vnext/Shared/DevServerHelper.h @@ -76,12 +76,20 @@ class DevServerHelper { const std::string &packagerHost, const uint16_t packagerPort, const std::string &deviceName, - const std::string &packageName) { + const std::string &packageName, + const std::string &deviceId) { return string_format( InspectorDeviceUrlFormat, GetDeviceLocalHost(packagerHost, packagerPort).c_str(), deviceName.c_str(), - packageName.c_str()); + packageName.c_str(), + deviceId.c_str()); + } + + static std::string + get_OpenDebuggerUrl(const std::string &packagerHost, const uint16_t packagerPort, const std::string &deviceId) { + return string_format( + OpenDebuggerUrlFormat, GetDeviceLocalHost(packagerHost, packagerPort).c_str(), deviceId.c_str()); } static constexpr const char DefaultPackagerHost[] = "localhost"; @@ -105,7 +113,8 @@ class DevServerHelper { static constexpr const char PackagerConnectionUrlFormat[] = "ws://%s/message"; static constexpr const char PackagerStatusUrlFormat[] = "http://%s/status"; static constexpr const char PackagerOpenStackFrameUrlFormat[] = "https://%s/open-stack-frame"; - static constexpr const char InspectorDeviceUrlFormat[] = "ws://%s/inspector/device?name=%s&app=%s"; + static constexpr const char InspectorDeviceUrlFormat[] = "ws://%s/inspector/device?name=%s&app=%s&device=%s"; + static constexpr const char OpenDebuggerUrlFormat[] = "http://%s/open-debugger?device=%s"; static constexpr const char PackagerOkStatus[] = "packager-status:running"; const int LongPollFailureDelayMs = 5000; diff --git a/vnext/Shared/DevSettings.h b/vnext/Shared/DevSettings.h index 0d21853a7f2..7cd0af57f25 100644 --- a/vnext/Shared/DevSettings.h +++ b/vnext/Shared/DevSettings.h @@ -23,6 +23,10 @@ struct RuntimeHolderLazyInit; namespace facebook { namespace react { +namespace jsinspector_modern { +class HostTarget; +} // namespace jsinspector_modern + enum class JSIEngineOverride : int32_t { Default = 0, // No JSI, will use the legacy ExecutorFactory Chakra = 1, // Use the JSIExecutorFactory with ChakraRuntime @@ -114,6 +118,9 @@ struct DevSettings { // Enable concurrent mode by installing runtimeScheduler bool useRuntimeScheduler{false}; + + // The HostTarget instance for Fusebox + facebook::react::jsinspector_modern::HostTarget *inspectorTarget; }; } // namespace react diff --git a/vnext/Shared/DevSupportManager.cpp b/vnext/Shared/DevSupportManager.cpp index a6935bb8aa9..9620230cd0a 100644 --- a/vnext/Shared/DevSupportManager.cpp +++ b/vnext/Shared/DevSupportManager.cpp @@ -9,14 +9,19 @@ #include #include +#include "FuseboxInspectorPackagerConnectionDelegate.h" #include "PackagerConnection.h" #include "Unicode.h" #include "Utilities.h" #include +#include #include +#include +#include #include +#include #include #include #include @@ -171,6 +176,49 @@ bool IsIgnorablePollHResult(HRESULT hr) { return hr == WININET_E_INVALID_SERVER_RESPONSE; } +std::string GetDeviceId(const std::string &packageName) { + const auto hash = winrt::Windows::Security::Cryptography::Core::HashAlgorithmProvider::OpenAlgorithm( + winrt::Windows::Security::Cryptography::Core::HashAlgorithmNames::Sha256()) + .CreateHash(); + hash.Append(winrt::Windows::System::Profile::SystemIdentification::GetSystemIdForPublisher().Id()); + winrt::Windows::Storage::Streams::InMemoryRandomAccessStream stream; + winrt::Windows::Storage::Streams::DataWriter writer; + // If an app ID is provided, we will allow reconnection to DevTools. + // Apps must supply a unique app ID to each ReactNativeHost instance settings for this to behave correctly. + if (!bundleAppId.empty()) { + const auto packageNameBuffer = winrt::Windows::Security::Cryptography::CryptographicBuffer::ConvertStringToBinary( + winrt::to_hstring(packageName), winrt::Windows::Security::Cryptography::BinaryStringEncoding::Utf16BE); + hash.Append(packageNameBuffer); + } else { + const auto processId = GetCurrentProcessId(); + std::vector processIdBytes( + reinterpret_cast(&processId), reinterpret_cast(&processId + 1)); + winrt::array_view processIdByteArray(processIdBytes); + const auto processIdBuffer = + winrt::Windows::Security::Cryptography::CryptographicBuffer::CreateFromByteArray(processIdByteArray); + hash.Append(processIdBuffer); + } + const auto hashBuffer = hash.GetValueAndReset(); + const auto hashString = winrt::Windows::Security::Cryptography::CryptographicBuffer::EncodeToHexString(hashBuffer); + return winrt::to_string(hashString); +} + +std::string GetPackageName(const std::string &bundleAppId) { + if (!bundleAppId.empty()) { + return bundleAppId; + } + + std::string packageName{"RNW"}; + wchar_t fullName[PACKAGE_FULL_NAME_MAX_LENGTH]{}; + UINT32 size = ARRAYSIZE(fullName); + if (SUCCEEDED(GetCurrentPackageFullName(&size, fullName))) { + // we are in an unpackaged app + packageName = winrt::to_string(fullName); + } + + return packageName; +} + std::future PollForLiveReload(const std::string &url) { winrt::Windows::Web::Http::HttpClient httpClient; winrt::Windows::Foundation::Uri uri(Microsoft::Common::Unicode::Utf8ToUtf16(url)); @@ -238,30 +286,49 @@ void DevSupportManager::StopPollingLiveReload() { m_cancellation_token = true; } +void DevSupportManager::OpenDevTools(const std::string &bundleAppId) { + winrt::Windows::Web::Http::Filters::HttpBaseProtocolFilter filter; + filter.CacheControl().ReadBehavior(winrt::Windows::Web::Http::Filters::HttpCacheReadBehavior::NoCache); + winrt::Windows::Web::Http::HttpClient httpClient(filter); + // TODO: Use currently configured dev server host + winrt::Windows::Foundation::Uri uri( + Microsoft::Common::Unicode::Utf8ToUtf16(facebook::react::DevServerHelper::get_OpenDebuggerUrl( + std::string{DevServerHelper::DefaultPackagerHost}, + DevServerHelper::DefaultPackagerPort, + GetDeviceId(GetPackageName(bundleAppId))))); + + winrt::Windows::Web::Http::HttpRequestMessage request(winrt::Windows::Web::Http::HttpMethod::Post(), uri); + httpClient.SendRequestAsync(request); +} + void DevSupportManager::EnsureHermesInspector( [[maybe_unused]] const std::string &packagerHost, - [[maybe_unused]] const uint16_t packagerPort) noexcept { + [[maybe_unused]] const uint16_t packagerPort, + [[maybe_unused]] const std::string &bundleAppId) noexcept { static std::once_flag once; - std::call_once(once, [this, &packagerHost, packagerPort]() { - // TODO: should we use the bundleAppId as the app param if available? - std::string packageName("RNW"); - wchar_t fullName[PACKAGE_FULL_NAME_MAX_LENGTH]{}; - UINT32 size = ARRAYSIZE(fullName); - if (SUCCEEDED(GetCurrentPackageFullName(&size, fullName))) { - // we are in an unpackaged app - packageName = winrt::to_string(fullName); - } - + std::call_once(once, [this, &packagerHost, packagerPort, &bundleAppId]() { + std::string packageName = GetPackageName(bundleAppId); std::string deviceName("RNWHost"); auto hostNames = winrt::Windows::Networking::Connectivity::NetworkInformation::GetHostNames(); if (hostNames && hostNames.First() && hostNames.First().Current()) { deviceName = winrt::to_string(hostNames.First().Current().DisplayName()); } - m_inspectorPackagerConnection = std::make_shared( - facebook::react::DevServerHelper::get_InspectorDeviceUrl(packagerHost, packagerPort, deviceName, packageName), - m_BundleStatusProvider); - m_inspectorPackagerConnection->connectAsync(); + const auto deviceId = GetDeviceId(packageName); + auto inspectorUrl = facebook::react::DevServerHelper::get_InspectorDeviceUrl( + packagerHost, packagerPort, deviceName, packageName, deviceId); + auto &inspectorFlags = jsinspector_modern::InspectorFlags::getInstance(); + if (inspectorFlags.getEnableCxxInspectorPackagerConnection()) { + m_fuseboxInspectorPackagerConnection = std::make_unique( + inspectorUrl, + packageName, + std::make_unique()); + m_fuseboxInspectorPackagerConnection->connect(); + } else { + m_inspectorPackagerConnection = + std::make_shared(std::move(inspectorUrl), m_BundleStatusProvider); + m_inspectorPackagerConnection->connectAsync(); + } }); } diff --git a/vnext/Shared/DevSupportManager.h b/vnext/Shared/DevSupportManager.h index 5c33ed1de02..e5aa7bbab40 100644 --- a/vnext/Shared/DevSupportManager.h +++ b/vnext/Shared/DevSupportManager.h @@ -15,6 +15,7 @@ #include #include +#include namespace facebook { namespace react { @@ -48,14 +49,20 @@ class DevSupportManager final : public facebook::react::IDevSupportManager { const uint16_t sourceBundlePort, std::function onChangeCallback) override; virtual void StopPollingLiveReload() override; + virtual void OpenDevTools(const std::string &bundleAppId) override; - virtual void EnsureHermesInspector(const std::string &packagerHost, const uint16_t packagerPort) noexcept override; + virtual void EnsureHermesInspector( + const std::string &packagerHost, + const uint16_t packagerPort, + const std::string &bundleAppId) noexcept override; virtual void UpdateBundleStatus(bool isLastDownloadSuccess, int64_t updateTimestamp) noexcept override; private: std::atomic_bool m_cancellation_token; std::shared_ptr m_inspectorPackagerConnection; + std::unique_ptr + m_fuseboxInspectorPackagerConnection; struct BundleStatusProvider : public InspectorPackagerConnection::IBundleStatusProvider { virtual InspectorPackagerConnection::BundleStatus getBundleStatus() { diff --git a/vnext/Shared/FuseboxInspectorPackagerConnectionDelegate.cpp b/vnext/Shared/FuseboxInspectorPackagerConnectionDelegate.cpp new file mode 100644 index 00000000000..9113a4f24e9 --- /dev/null +++ b/vnext/Shared/FuseboxInspectorPackagerConnectionDelegate.cpp @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "FuseboxInspectorPackagerConnectionDelegate.h" + +#include +#include "FuseboxInspectorThread.h" + +namespace Microsoft::ReactNative { + +FuseboxInspectorPackagerConnectionDelegate::WebSocket::WebSocket( + std::string const &url, + std::weak_ptr delegate) + : m_weakDelegate{delegate} { + std::vector certExceptions; + + m_packagerWebSocketConnection = + std::make_shared(std::move(certExceptions)); + + m_packagerWebSocketConnection->SetOnMessage([delegate](auto &&, const std::string &message, bool isBinary) { + FuseboxInspectorThread::Instance().InvokeElsePost([delegate, message]() { + if (const auto strongDelegate = delegate.lock()) { + strongDelegate->didReceiveMessage(message); + } + }); + }); + m_packagerWebSocketConnection->SetOnError( + [delegate](const Microsoft::React::Networking::IWebSocketResource::Error &error) { + FuseboxInspectorThread::Instance().InvokeElsePost([delegate, error]() { + if (const auto strongDelegate = delegate.lock()) { + strongDelegate->didFailWithError(std::nullopt, error.Message); + } + }); + }); + m_packagerWebSocketConnection->SetOnClose([delegate](auto &&...) { + FuseboxInspectorThread::Instance().InvokeElsePost([delegate]() { + if (const auto strongDelegate = delegate.lock()) { + strongDelegate->didClose(); + } + }); + }); + + Microsoft::React::Networking::IWebSocketResource::Protocols protocols; + Microsoft::React::Networking::IWebSocketResource::Options options; + m_packagerWebSocketConnection->Connect(std::string{url}, protocols, options); +} + +void FuseboxInspectorPackagerConnectionDelegate::WebSocket::send(std::string_view message) { + m_packagerWebSocketConnection->Send(std::string{message}); +} + +FuseboxInspectorPackagerConnectionDelegate::WebSocket::~WebSocket() { + std::string reason{"Explicit close"}; + m_packagerWebSocketConnection->Close( + Microsoft::React::Networking::WinRTWebSocketResource::CloseCode::GoingAway, reason); +} + +std::unique_ptr +FuseboxInspectorPackagerConnectionDelegate::connectWebSocket( + const std::string &url, + std::weak_ptr delegate) { + return std::make_unique(url, delegate); +} + +winrt::fire_and_forget RunWithDelayAsync(std::function callback, std::chrono::milliseconds delayMs) { + co_await winrt::resume_after(delayMs); + FuseboxInspectorThread::Instance().InvokeElsePost([callback]() { callback(); }); +} + +void FuseboxInspectorPackagerConnectionDelegate::scheduleCallback( + std::function callback, + std::chrono::milliseconds delayMs) { + RunWithDelayAsync(callback, delayMs); +} + +} // namespace Microsoft::ReactNative diff --git a/vnext/Shared/FuseboxInspectorPackagerConnectionDelegate.h b/vnext/Shared/FuseboxInspectorPackagerConnectionDelegate.h new file mode 100644 index 00000000000..27ff809f9be --- /dev/null +++ b/vnext/Shared/FuseboxInspectorPackagerConnectionDelegate.h @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include +#include +#include + +namespace Microsoft::ReactNative { + +class FuseboxInspectorPackagerConnectionDelegate final + : public facebook::react::jsinspector_modern::InspectorPackagerConnectionDelegate { + class WebSocket : public facebook::react::jsinspector_modern::IWebSocket { + public: + WebSocket(std::string const &url, std::weak_ptr delegate); + virtual void send(std::string_view message) override; + virtual ~WebSocket() override; + + private: + std::shared_ptr m_packagerWebSocketConnection; + std::weak_ptr m_weakDelegate; + }; + + public: + virtual std::unique_ptr connectWebSocket( + const std::string &url, + std::weak_ptr delegate) override; + + virtual void scheduleCallback(std::function callback, std::chrono::milliseconds delayMs) override; +}; +} // namespace Microsoft::ReactNative diff --git a/vnext/Shared/FuseboxInspectorThread.h b/vnext/Shared/FuseboxInspectorThread.h new file mode 100644 index 00000000000..e33311410f5 --- /dev/null +++ b/vnext/Shared/FuseboxInspectorThread.h @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include + +namespace Microsoft::ReactNative { + +class FuseboxInspectorThread final { + public: + static Mso::DispatchQueue &Instance() { + static Mso::DispatchQueue queue = Mso::DispatchQueue::MakeSerialQueue(); + return queue; + } +}; + +} // namespace Microsoft::ReactNative diff --git a/vnext/Shared/IDevSupportManager.h b/vnext/Shared/IDevSupportManager.h index dd90ff1bfc7..3ce638e260f 100644 --- a/vnext/Shared/IDevSupportManager.h +++ b/vnext/Shared/IDevSupportManager.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include #include @@ -23,8 +24,12 @@ struct IDevSupportManager { const uint16_t sourceBundlePort, std::function onChangeCallback) = 0; virtual void StopPollingLiveReload() = 0; + virtual void OpenDevTools(const std::string &bundleAppId) = 0; - virtual void EnsureHermesInspector(const std::string &packagerHost, const uint16_t packagerPort) noexcept = 0; + virtual void EnsureHermesInspector( + const std::string &packagerHost, + const uint16_t packagerPort, + const std::string &bundleAppId) noexcept = 0; virtual void UpdateBundleStatus(bool isLastDownloadSuccess, int64_t updateTimestamp) noexcept = 0; }; diff --git a/vnext/Shared/OInstance.cpp b/vnext/Shared/OInstance.cpp index ece2c869a9c..f32385af3af 100644 --- a/vnext/Shared/OInstance.cpp +++ b/vnext/Shared/OInstance.cpp @@ -73,6 +73,20 @@ using namespace Microsoft::JSI; using std::make_shared; using winrt::Microsoft::ReactNative::ReactPropertyBagHelper; +namespace facebook::react { +bool shouldStartHermesInspector(DevSettings &devSettings) { + bool isHermes = + ((devSettings.jsiEngineOverride == JSIEngineOverride::Hermes) || + (devSettings.jsiEngineOverride == JSIEngineOverride::Default && devSettings.jsiRuntimeHolder && + devSettings.jsiRuntimeHolder->getRuntimeType() == facebook::react::JSIEngineOverride::Hermes)); + + if (isHermes && devSettings.useDirectDebugger && !devSettings.useWebDebugger) + return true; + else + return false; +} +} // namespace facebook::react + namespace Microsoft::ReactNative { // Note: Based on @@ -105,6 +119,12 @@ void LoadRemoteUrlScript( hermesBytecodeVersion = ::hermes::hbc::BYTECODE_VERSION; #endif + const auto bundlePath = ; + if (facebook::react::shouldStartHermesInspector(*devSettings)) { + devManager->EnsureHermesInspector( + devSettings->sourceBundleHost, devSettings->sourceBundlePort, devSettings->bundleAppId); + } + auto [jsBundleString, success] = GetJavaScriptFromServer( devSettings->sourceBundleHost, devSettings->sourceBundlePort, @@ -329,20 +349,6 @@ void InstanceImpl::SetInError() noexcept { m_isInError = true; } -namespace { -bool shouldStartHermesInspector(DevSettings &devSettings) { - bool isHermes = - ((devSettings.jsiEngineOverride == JSIEngineOverride::Hermes) || - (devSettings.jsiEngineOverride == JSIEngineOverride::Default && devSettings.jsiRuntimeHolder && - devSettings.jsiRuntimeHolder->getRuntimeType() == facebook::react::JSIEngineOverride::Hermes)); - - if (isHermes && devSettings.useDirectDebugger && !devSettings.useWebDebugger) - return true; - else - return false; -} -} // namespace - InstanceImpl::InstanceImpl( std::shared_ptr &&instance, std::string &&jsBundleBasePath, @@ -373,10 +379,6 @@ InstanceImpl::InstanceImpl( facebook::react::tracing::initializeETW(); #endif - if (shouldStartHermesInspector(*m_devSettings)) { - m_devManager->EnsureHermesInspector(m_devSettings->sourceBundleHost, m_devSettings->sourceBundlePort); - } - // Default (common) NativeModules auto modules = GetDefaultNativeModules(nativeQueue); @@ -484,7 +486,8 @@ InstanceImpl::InstanceImpl( } } - m_innerInstance->initializeBridge(std::move(callback), jsef, m_jsThread, m_moduleRegistry); + m_innerInstance->initializeBridge( + std::move(callback), jsef, m_jsThread, m_moduleRegistry, m_devSettings->inspectorTarget); // For RuntimeScheduler to work properly, we need to install TurboModuleManager with RuntimeSchedulerCallbackInvoker. // To be able to do that, we need to be able to call m_innerInstance->getRuntimeExecutor(), which we can only do after @@ -587,6 +590,16 @@ void InstanceImpl::loadBundleInternal(std::string &&jsBundleRelativePath, bool s } InstanceImpl::~InstanceImpl() { + if (m_devSettings->inspectorTarget) { + auto messageDispatchQueue = + Mso::React::MessageDispatchQueue(::Microsoft::ReactNative::FuseboxInspectorThread::Instance(), nullptr); + messageDispatchQueue.runOnQueueSync([weakInnerInstance = std::weak_ptr(m_innerInstance)]() { + if (auto innerInstance = weakInnerInstance.lock()) { + innerInstance->unregisterFromInspector(); + } + }); + } + if (shouldStartHermesInspector(*m_devSettings) && m_devSettings->jsiRuntimeHolder) { m_devSettings->jsiRuntimeHolder->teardown(); } diff --git a/vnext/Shared/Shared.vcxitems b/vnext/Shared/Shared.vcxitems index 47caa4d4318..120b0873510 100644 --- a/vnext/Shared/Shared.vcxitems +++ b/vnext/Shared/Shared.vcxitems @@ -218,6 +218,7 @@ + @@ -412,8 +413,10 @@ + + diff --git a/vnext/Shared/Shared.vcxitems.filters b/vnext/Shared/Shared.vcxitems.filters index babf9f193ac..64d6757edd7 100644 --- a/vnext/Shared/Shared.vcxitems.filters +++ b/vnext/Shared/Shared.vcxitems.filters @@ -94,6 +94,9 @@ Source Files\JSI + + Source Files + Source Files @@ -507,6 +510,9 @@ Header Files + + Header Files + Header Files @@ -610,6 +616,9 @@ Header Files\JSI + + Header Files + Header Files