Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wire up Fusebox in react-native-windows #13008

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Wire up Fusebox InspectorPackagerConnection",
"packageName": "react-native-windows",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@
<SubType>Code</SubType>
</ClInclude>
<ClInclude Include="ReactHost\AsyncActionQueue.h" />
<ClInclude Include="ReactHost\DebuggerNotifications.h" />
<ClInclude Include="ReactHost\InstanceFactory.h" />
<ClInclude Include="ReactHost\IReactInstanceInternal.h" />
<ClInclude Include="ReactHost\JSBundle.h" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,9 @@
<ClInclude Include="ReactHost\AsyncActionQueue.h">
<Filter>ReactHost</Filter>
</ClInclude>
<ClInclude Include="ReactHost\DebuggerNotifications.h">
<Filter>ReactHost</Filter>
</ClInclude>
<ClInclude Include="ReactHost\InstanceFactory.h">
<Filter>ReactHost</Filter>
</ClInclude>
Expand Down
20 changes: 20 additions & 0 deletions vnext/Microsoft.ReactNative/QuirkSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,26 @@
#include "React.h"
#include "ReactPropertyBag.h"

#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/featureflags/ReactNativeFeatureFlagsDefaults.h>

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<bool> MatchAndroidAndIOSStretchBehaviorProperty() noexcept {
static winrt::Microsoft::ReactNative::ReactPropertyId<bool> propId{
L"ReactNative.QuirkSettings", L"MatchAndroidAndIOSyStretchBehavior"};
Expand Down Expand Up @@ -137,6 +153,10 @@ winrt::Microsoft::ReactNative::ReactPropertyId<bool> IsBridgelessProperty() noex
ReactPropertyBag(settings.Properties()).Set(UseRuntimeSchedulerProperty(), value);
}

/*static*/ void QuirkSettings::SetUseFusebox(bool value) noexcept {
facebook::react::ReactNativeFeatureFlags::override(std::make_unique<QuirkSettingsReactNativeFeatureFlags>(value));
}

#pragma endregion IDL interface

/*static*/ bool QuirkSettings::GetMatchAndroidAndIOSStretchBehavior(ReactPropertyBag properties) noexcept {
Expand Down
2 changes: 2 additions & 0 deletions vnext/Microsoft.ReactNative/QuirkSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ struct QuirkSettings : QuirkSettingsT<QuirkSettings> {
winrt::Microsoft::ReactNative::ReactInstanceSettings settings,
bool value) noexcept;

static void SetUseFusebox(bool value) noexcept;

#pragma endregion Public API - part of IDL interface
};

Expand Down
5 changes: 5 additions & 0 deletions vnext/Microsoft.ReactNative/QuirkSettings.idl
Original file line number Diff line number Diff line change
Expand Up @@ -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
54 changes: 54 additions & 0 deletions vnext/Microsoft.ReactNative/ReactHost/DebuggerNotifications.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#pragma once

#include <winrt/Microsoft.ReactNative.h>

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<void()> onResume) {
const winrt::Microsoft::ReactNative::ReactNonAbiValue<std::tuple<std::string, std::function<void()>>> 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<void(std::string, std::function<void()>)> showCallback,
std::function<void()> 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<winrt::Microsoft::ReactNative::ReactNonAbiValue<
std::tuple<std::string, std::function<void()>>>>()
.Value();
showCallback(message, onResume);
} else {
hideCallback();
}
});
}
};

} // namespace Microsoft::ReactNative
7 changes: 7 additions & 0 deletions vnext/Microsoft.ReactNative/ReactHost/React.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
#include <ViewManagerProvider.h>
#include <winrt/Microsoft.ReactNative.h>

namespace facebook::react::jsinspector_modern {
class HostTarget;
} // namespace facebook::react::jsinspector_modern

namespace Mso::React {

// Forward declarations
Expand Down Expand Up @@ -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.
Expand Down
24 changes: 21 additions & 3 deletions vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,18 @@ ReactInstanceWin::ReactInstanceWin(
}

ReactInstanceWin::~ReactInstanceWin() noexcept {
#ifdef USE_FABRIC
if (m_bridgelessReactInstance && m_options.InspectorTarget) {
auto messageDispatchQueue =
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using MessageDispatchQueue to get the runOnQueueSync function on an Mso::DispatchQueue. We need to ensure the ReactInstance stays alive for as long as it takes to unregister the inspector.

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()) {
Expand Down Expand Up @@ -527,6 +539,8 @@ std::shared_ptr<facebook::react::DevSettings> ReactInstanceWin::CreateDevSetting

devSettings->useRuntimeScheduler = useRuntimeScheduler;

devSettings->inspectorTarget = m_options.InspectorTarget;

return devSettings;
}

Expand Down Expand Up @@ -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<Microsoft::ReactNative::HermesRuntimeHolder>(
devSettings, m_jsMessageThread.Load(), CreateHermesPreparedScriptStore());
auto jsRuntime = std::make_unique<Microsoft::ReactNative::HermesJSRuntime>(m_jsiRuntimeHolder);
jsRuntime->getRuntime();
m_bridgelessReactInstance = std::make_unique<facebook::react::ReactInstance>(
std::move(jsRuntime), m_jsMessageThread.Load(), timerManager, jsErrorHandlingFunc);
m_bridgelessReactInstance = std::make_shared<facebook::react::ReactInstance>(
std::move(jsRuntime),
m_jsMessageThread.Load(),
timerManager,
jsErrorHandlingFunc,
m_options.InpectorTarget);

auto bufferedRuntimeExecutor = m_bridgelessReactInstance->getBufferedRuntimeExecutor();
timerManager->setRuntimeExecutor(bufferedRuntimeExecutor);
Expand Down
2 changes: 1 addition & 1 deletion vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.h
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ class ReactInstanceWin final : public Mso::ActiveObject<IReactInstanceInternal>

#ifdef USE_FABRIC
// Bridgeless
std::unique_ptr<facebook::react::ReactInstance> m_bridgelessReactInstance;
std::shared_ptr<facebook::react::ReactInstance> m_bridgelessReactInstance;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

converted to shared_ptr to provide weak_ptr to host target de-init

#endif

std::atomic<ReactInstanceState> m_state{ReactInstanceState::Loading};
Expand Down
87 changes: 87 additions & 0 deletions vnext/Microsoft.ReactNative/ReactNativeHost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@
#include "ReactNativeHost.h"
#include "ReactNativeHost.g.cpp"

#include "FuseboxInspectorThread.h"
#include "ReactPackageBuilder.h"
#include "RedBox.h"
#include "TurboModulesProvider.h"

#include <future/futureWinRT.h>
#include <jsinspector-modern/InspectorFlags.h>
#include <winrt/Windows.Foundation.Collections.h>
#include "IReactContext.h"
#include "ReactHost/DebuggerNotifications.h"
#include "ReactInstanceSettings.h"

#ifdef USE_FABRIC
Expand All @@ -30,13 +33,85 @@ using namespace xaml::Controls;

namespace winrt::Microsoft::ReactNative::implementation {

class FuseboxHostTargetDelegate : public facebook::react::jsinspector_modern::HostTargetDelegate,
public std::enable_shared_from_this<FuseboxHostTargetDelegate> {
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) {
std::string str = std::string("ReactNative:") + message;
OutputDebugStringA(str.c_str());
});
#endif

auto &inspectorFlags = facebook::react::jsinspector_modern::InspectorFlags::getInstance();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should all be lazy init'd on the first reload, otherwise you have to set the feature flag before calling the ReactNativeHost ctor, rather than just before attaching the ReactNativeHost to the ReactRootView.

if (inspectorFlags.getEnableModernCDPRegistry() && !m_inspectorPageId.has_value()) {
m_inspectorHostDelegate = std::make_shared<FuseboxHostTargetDelegate>(this);
m_inspectorTarget = facebook::react::jsinspector_modern::HostTarget::create(
*m_inspectorHostDelegate, [](std::function<void()> &&callback) {
::Microsoft::ReactNative::FuseboxInspectorThread::Instance().InvokeElsePost([callback]() { callback(); });
});

std::weak_ptr<facebook::react::jsinspector_modern::HostTarget> 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<facebook::react::jsinspector_modern::IRemoteConnection> remote)
-> std::unique_ptr<facebook::react::jsinspector_modern::ILocalConnection> {
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(
Expand Down Expand Up @@ -186,6 +261,7 @@ IAsyncAction ReactNativeHost::ReloadInstance() noexcept {
}

reactOptions.Identity = jsBundleFile;
reactOptions.InspectorTarget = m_inspectorTarget.get();
return make<Mso::AsyncActionFutureAdapter>(m_reactHost->ReloadInstanceWithOptions(std::move(reactOptions)));
}

Expand All @@ -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
7 changes: 7 additions & 0 deletions vnext/Microsoft.ReactNative/ReactNativeHost.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "ReactNativeHost.g.h"

#include <jsinspector-modern/HostTarget.h>
#include "NativeModulesProvider.h"
#include "ReactHost/React.h"
#include "ReactInstanceSettings.h"
Expand All @@ -16,6 +17,7 @@ namespace winrt::Microsoft::ReactNative::implementation {
struct ReactNativeHost : ReactNativeHostT<ReactNativeHost> {
public: // ReactNativeHost ABI API
ReactNativeHost() noexcept;
~ReactNativeHost() noexcept;

static ReactNative::ReactNativeHost FromContext(ReactNative::IReactContext const &reactContext) noexcept;

Expand All @@ -25,6 +27,7 @@ struct ReactNativeHost : ReactNativeHostT<ReactNativeHost> {
// 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;
Expand All @@ -39,6 +42,10 @@ struct ReactNativeHost : ReactNativeHostT<ReactNativeHost> {

ReactNative::ReactInstanceSettings m_instanceSettings{nullptr};
ReactNative::IReactPackageBuilder m_packageBuilder;

std::shared_ptr<facebook::react::jsinspector_modern::HostTargetDelegate> m_inspectorHostDelegate{nullptr};
std::shared_ptr<facebook::react::jsinspector_modern::HostTarget> m_inspectorTarget{nullptr};
std::optional<int32_t> m_inspectorPageId{std::nullopt};
};

} // namespace winrt::Microsoft::ReactNative::implementation
Expand Down
Loading
Loading