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