Skip to content
Merged
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,8 @@
{
"type": "prerelease",
"comment": "Expose ability for apps to provide their own RedBox implementation",
"packageName": "react-native-windows",
"email": "[email protected]",
"dependentChangeType": "patch",
"date": "2020-05-04T19:54:41.414Z"
}
1 change: 1 addition & 0 deletions packages/playground/windows/playground.sln
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
..\..\..\vnext\JSI\Shared\JSI.Shared.vcxitems*{0cc28589-39e4-4288-b162-97b959f8b843}*SharedItemsImports = 9
..\..\..\vnext\ReactWindowsCore\ReactWindowsCore.vcxitems*{11c084a3-a57c-4296-a679-cac17b603144}*SharedItemsImports = 4
..\..\..\vnext\Shared\Shared.vcxitems*{2049dbe9-8d13-42c9-ae4b-413ae38fffd0}*SharedItemsImports = 9
..\..\..\vnext\Microsoft.ReactNative.SharedManaged\Microsoft.ReactNative.SharedManaged.projitems*{67a1076f-7790-4203-86ea-4402ccb5e782}*SharedItemsImports = 13
..\..\..\vnext\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{6b6aa847-b32f-41ac-9d3b-48a8cdfa8ade}*SharedItemsImports = 4
Expand Down
1 change: 1 addition & 0 deletions packages/scripts/formatFiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ function queryNoOpenFiles() {
if (opened) {
console.error('The following files have incorrect formatting:');
console.error(opened);
console.error('Running `yarn format` from the repo root should fix this.');
process.exit(2);
}
}
Expand Down
11 changes: 11 additions & 0 deletions vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,10 @@
<DependentUpon>ReactInstanceSettings.idl</DependentUpon>
<SubType>Code</SubType>
</ClInclude>
<ClInclude Include="RedBoxHandler.h">
<DependentUpon>RedBoxHandler.idl</DependentUpon>
<SubType>Code</SubType>
</ClInclude>
<ClInclude Include="ReactNativeHost.h">
<DependentUpon>ReactNativeHost.idl</DependentUpon>
<SubType>Code</SubType>
Expand Down Expand Up @@ -503,6 +507,10 @@
<DependentUpon>ReactInstanceSettings.idl</DependentUpon>
<SubType>Code</SubType>
</ClCompile>
<ClCompile Include="RedBoxHandler.cpp">
<DependentUpon>RedBoxHandler.idl</DependentUpon>
<SubType>Code</SubType>
</ClCompile>
<ClCompile Include="ReactNativeHost.cpp">
<DependentUpon>ReactNativeHost.idl</DependentUpon>
<SubType>Code</SubType>
Expand Down Expand Up @@ -560,6 +568,9 @@
<Midl Include="ReactInstanceSettings.idl">
<SubType>Designer</SubType>
</Midl>
<Midl Include="RedBoxHandler.idl">
<SubType>Designer</SubType>
</Midl>
<Midl Include="ReactNativeHost.idl">
<SubType>Designer</SubType>
</Midl>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,7 @@
<Midl Include="IReactModuleBuilder.idl" />
<Midl Include="IReactPackageBuilder.idl" />
<Midl Include="IReactPackageProvider.idl" />
<Midl Include="IRedBoxHandler.idl" />
<Midl Include="IReactPropertyBag.idl" />
<Midl Include="IViewManager.idl" />
<Midl Include="NoExceptionAttribute.idl" />
Expand Down
2 changes: 1 addition & 1 deletion vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ std::shared_ptr<IRedBoxHandler> ReactInstanceWin::GetRedBoxHandler() noexcept {
return m_options.RedBoxHandler;
} else if (m_options.DeveloperSettings.IsDevModeEnabled) {
auto localWkReactHost = m_weakReactHost;
return CreateRedBoxHandler(std::move(localWkReactHost), m_uiMessageThread.LoadWithLock());
return CreateDefaultRedBoxHandler(std::move(localWkReactHost));
} else {
return {};
}
Expand Down
12 changes: 12 additions & 0 deletions vnext/Microsoft.ReactNative/ReactInstanceSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ struct ReactInstanceSettings : ReactInstanceSettingsT<ReactInstanceSettings> {
uint16_t DebuggerPort() noexcept;
void DebuggerPort(uint16_t value) noexcept;

IRedBoxHandler RedBoxHandler() noexcept;
void RedBoxHandler(IRedBoxHandler const &value) noexcept;

private:
IReactPropertyBag m_properties{ReactPropertyBagHelper::CreatePropertyBag()};
hstring m_mainComponentName{};
Expand All @@ -100,6 +103,7 @@ struct ReactInstanceSettings : ReactInstanceSettingsT<ReactInstanceSettings> {
hstring m_debugBundlePath{};
hstring m_bundleRootPath{};
uint16_t m_debuggerPort{9229};
IRedBoxHandler m_redBoxHandler{nullptr};
};

} // namespace winrt::Microsoft::ReactNative::implementation
Expand Down Expand Up @@ -264,4 +268,12 @@ inline void ReactInstanceSettings::DebuggerPort(uint16_t value) noexcept {
m_debuggerPort = value;
}

inline IRedBoxHandler ReactInstanceSettings::RedBoxHandler() noexcept {
return m_redBoxHandler;
}

inline void ReactInstanceSettings::RedBoxHandler(IRedBoxHandler const &value) noexcept {
m_redBoxHandler = value;
}

} // namespace winrt::Microsoft::ReactNative::implementation
2 changes: 2 additions & 0 deletions vnext/Microsoft.ReactNative/ReactInstanceSettings.idl
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import "RedBoxHandler.idl";
import "IReactPropertyBag.idl";

namespace Microsoft.ReactNative {
Expand Down Expand Up @@ -29,5 +30,6 @@ namespace Microsoft.ReactNative {
String DebugBundlePath { get; set; };
String BundleRootPath { get; set; };
UInt16 DebuggerPort { get; set; };
Microsoft.ReactNative.IRedBoxHandler RedBoxHandler { get; set; };
Copy link
Member

Choose a reason for hiding this comment

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

Microsoft.ReactNative. [](start = 4, length = 22)

Not needed

}
}
6 changes: 6 additions & 0 deletions vnext/Microsoft.ReactNative/ReactNativeHost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "ReactNativeHost.g.cpp"

#include "ReactPackageBuilder.h"
#include "RedBox.h"

using namespace winrt;
using namespace Windows::Foundation::Collections;
Expand Down Expand Up @@ -78,6 +79,10 @@ void ReactNativeHost::ReloadInstance() noexcept {
legacySettings.UseWebDebugger = m_instanceSettings.UseWebDebugger();
legacySettings.DebuggerPort = m_instanceSettings.DebuggerPort();

if (m_instanceSettings.RedBoxHandler()) {
legacySettings.RedBoxHandler = std::move(Mso::React::CreateRedBoxHandler(m_instanceSettings.RedBoxHandler()));
}

Mso::React::ReactOptions reactOptions{};
reactOptions.Properties = m_instanceSettings.Properties();
reactOptions.DeveloperSettings.IsDevModeEnabled = legacySettings.EnableDeveloperMenu;
Expand All @@ -91,6 +96,7 @@ void ReactNativeHost::ReloadInstance() noexcept {
reactOptions.DeveloperSettings.DebugHost = legacySettings.DebugHost;
reactOptions.BundleRootPath = legacySettings.BundleRootPath;
reactOptions.DeveloperSettings.DebuggerPort = legacySettings.DebuggerPort;
reactOptions.RedBoxHandler = legacySettings.RedBoxHandler;

reactOptions.LegacySettings = std::move(legacySettings);

Expand Down
162 changes: 123 additions & 39 deletions vnext/Microsoft.ReactNative/RedBox.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "RedBox.h"
#include <boost/algorithm/string.hpp>
#include <functional/functor.h>
#include <winrt/Windows.ApplicationModel.Core.h>
#include <winrt/Windows.Data.Json.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.UI.Core.h>
Expand All @@ -18,10 +19,10 @@ namespace Mso::React {

struct RedBox : public std::enable_shared_from_this<RedBox> {
RedBox(
Mso::WeakPtr<IReactHost> weakReactHost,
const Mso::WeakPtr<IReactHost> &weakReactHost,
Mso::Functor<void(uint32_t)> &&onClosedCallback,
ErrorInfo &&errorInfo) noexcept
: m_weakReactHost(std::move(weakReactHost)),
: m_weakReactHost(weakReactHost),
m_onClosedCallback(std::move(onClosedCallback)),
m_errorInfo(std::move(errorInfo)) {}

Expand Down Expand Up @@ -356,26 +357,22 @@ struct RedBox : public std::enable_shared_from_this<RedBox> {
/*
* This class is implemented such that the methods on IRedBoxHandler are thread safe.
*/
struct RedBoxHandler : public std::enable_shared_from_this<RedBoxHandler>, IRedBoxHandler {
RedBoxHandler(
Mso::WeakPtr<Mso::React::IReactHost> weakReactHost,
std::shared_ptr<facebook::react::MessageQueueThread> uiMessageQueue) noexcept
: m_weakReactHost(std::move(weakReactHost)), m_wkUIMessageQueue(std::move(uiMessageQueue)) {}
struct DefaultRedBoxHandler : public std::enable_shared_from_this<DefaultRedBoxHandler>, IRedBoxHandler {
DefaultRedBoxHandler(Mso::WeakPtr<Mso::React::IReactHost> &&weakReactHost) noexcept
: m_weakReactHost(std::move(weakReactHost)) {}

~RedBoxHandler() {
~DefaultRedBoxHandler() {
// Hide any currently showing redboxes
std::vector<std::shared_ptr<RedBox>> redboxes;
{
std::scoped_lock lock{m_lockRedBox};
std::swap(m_redboxes, redboxes);
}
if (auto uiQueue = m_wkUIMessageQueue.lock()) {
uiQueue->runOnQueue([redboxes = std::move(redboxes)]() {
for (const auto redbox : redboxes) {
redbox->Dismiss();
}
});
}
Mso::DispatchQueue::MainUIQueue().Post([redboxes = std::move(redboxes)]() {
for (const auto redbox : redboxes) {
redbox->Dismiss();
}
});
}

virtual void showNewError(ErrorInfo &&info, ErrorType /*exceptionType*/) override {
Expand All @@ -394,7 +391,7 @@ struct RedBoxHandler : public std::enable_shared_from_this<RedBoxHandler>, IRedB
showTopJSError();
}

virtual bool isDevSupportEnabled() override {
virtual bool isDevSupportEnabled() const override {
if (auto reactHost = m_weakReactHost.GetStrongPtr()) {
return reactHost->Options().DeveloperSettings.IsDevModeEnabled;
}
Expand All @@ -414,28 +411,26 @@ struct RedBoxHandler : public std::enable_shared_from_this<RedBoxHandler>, IRedB
}
}
if (redbox) {
if (auto uiQueue = m_wkUIMessageQueue.lock()) {
uiQueue->runOnQueue([redboxCaptured = std::move(redbox), errorInfo = std::move(info)]() {
redboxCaptured->UpdateError(std::move(errorInfo));
});
}
Mso::DispatchQueue::MainUIQueue().Post([redboxCaptured = std::move(redbox), errorInfo = std::move(info)]() {
redboxCaptured->UpdateError(std::move(errorInfo));
});
}
}

virtual void dismissRedbox() override {
if (auto uiQueue = m_wkUIMessageQueue.lock()) {
uiQueue->runOnQueue([&]() {
std::scoped_lock lock{m_lockRedBox};
if (!m_redboxes.empty())
m_redboxes[0]->Dismiss();
});
}
Mso::DispatchQueue::MainUIQueue().Post([wkthis = std::weak_ptr(shared_from_this())]() {
if (auto pthis = wkthis.lock()) {
std::scoped_lock lock{pthis->m_lockRedBox};
if (!pthis->m_redboxes.empty())
pthis->m_redboxes[0]->Dismiss();
}
});
}

private:
// When notified by a redbox that its been dismisssed
void onDismissedCallback(uint32_t id) noexcept {
// Save a local ref, so if we are removing the last redbox which could hold the last ref to the RedBoxHandler
// Save a local ref, so if we are removing the last redbox which could hold the last ref to the DefaultRedBoxHandler
// We ensure that we exit the mutex before the handler is destroyed.
std::shared_ptr<RedBox> redbox;
{
Expand Down Expand Up @@ -463,25 +458,114 @@ struct RedBoxHandler : public std::enable_shared_from_this<RedBoxHandler>, IRedB
}
}

if (auto uiQueue = m_wkUIMessageQueue.lock()) {
if (m_showingRedBox || !redbox) // Only show one redbox at a time
return;
m_showingRedBox = true;
uiQueue->runOnQueue([redboxCaptured = std::move(redbox)]() { redboxCaptured->ShowNewJSError(); });
}
if (m_showingRedBox || !redbox) // Only show one redbox at a time
return;
m_showingRedBox = true;

Mso::DispatchQueue::MainUIQueue().Post(
Copy link
Member

Choose a reason for hiding this comment

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

I'm curious how Mso determines which one is the MainUIQueue - i.e. how is this going to work in xaml islands?
It might make more sense to have the react host / instance expose the dispatch queue for that island

Copy link
Member

Choose a reason for hiding this comment

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

Yes, this is the plan. Currently we use the MainView CoreDispatcher and it fails for the XAML islands.

[redboxCaptured = std::move(redbox)]() { redboxCaptured->ShowNewJSError(); });
}

bool m_showingRedBox = false; // Access from UI Thread only
std::mutex m_lockRedBox;
std::vector<std::shared_ptr<RedBox>> m_redboxes; // Protected by m_lockRedBox
const Mso::WeakPtr<IReactHost> m_weakReactHost;
const std::weak_ptr<facebook::react::MessageQueueThread> m_wkUIMessageQueue;
};

struct RedBoxErrorFrameInfo
: public winrt::implements<RedBoxErrorFrameInfo, winrt::Microsoft::ReactNative::IRedBoxErrorFrameInfo> {
RedBoxErrorFrameInfo(Mso::React::ErrorFrameInfo &&errorFrameInfo) : m_frame(std::move(errorFrameInfo)) {}

winrt::hstring File() const noexcept {
return ::Microsoft::Common::Unicode::Utf8ToUtf16(m_frame.File).c_str();
}

winrt::hstring Method() const noexcept {
return ::Microsoft::Common::Unicode::Utf8ToUtf16(m_frame.Method).c_str();
}

uint32_t Line() const noexcept {
return m_frame.Line;
}

uint32_t Column() const noexcept {
return m_frame.Column;
}

private:
Mso::React::ErrorFrameInfo m_frame;
};

struct RedBoxErrorInfo : public winrt::implements<RedBoxErrorInfo, winrt::Microsoft::ReactNative::IRedBoxErrorInfo> {
RedBoxErrorInfo(Mso::React::ErrorInfo &&errorInfo) : m_errorInfo(std::move(errorInfo)) {}

winrt::hstring Message() const noexcept {
return ::Microsoft::Common::Unicode::Utf8ToUtf16(m_errorInfo.Message).c_str();
}

uint32_t Id() const noexcept {
return m_errorInfo.Id;
}

winrt::Windows::Foundation::Collections::IVectorView<winrt::Microsoft::ReactNative::IRedBoxErrorFrameInfo>
Callstack() noexcept {
if (!m_callstack) {
m_callstack = winrt::single_threaded_vector<winrt::Microsoft::ReactNative::IRedBoxErrorFrameInfo>();
for (auto frame : m_errorInfo.Callstack) {
m_callstack.Append(winrt::make<RedBoxErrorFrameInfo>(std::move(frame)));
}
}

return m_callstack.GetView();
}

private:
winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::ReactNative::IRedBoxErrorFrameInfo> m_callstack{
nullptr};

Mso::React::ErrorInfo m_errorInfo;
};

struct RedBoxHandler : public Mso::React::IRedBoxHandler {
RedBoxHandler(winrt::Microsoft::ReactNative::IRedBoxHandler const &redBoxHandler) : m_redBoxHandler(redBoxHandler) {}

static_assert(
static_cast<uint32_t>(Mso::React::ErrorType::JSFatal) ==
static_cast<uint32_t>(winrt::Microsoft::ReactNative::RedBoxErrorType::JavaScriptFatal));
static_assert(
static_cast<uint32_t>(Mso::React::ErrorType::JSSoft) ==
static_cast<uint32_t>(winrt::Microsoft::ReactNative::RedBoxErrorType::JavaScriptSoft));
static_assert(
static_cast<uint32_t>(Mso::React::ErrorType::Native) ==
static_cast<uint32_t>(winrt::Microsoft::ReactNative::RedBoxErrorType::Native));

virtual void showNewError(Mso::React::ErrorInfo &&info, Mso::React::ErrorType errorType) override {
m_redBoxHandler.ShowNewError(
winrt::make<RedBoxErrorInfo>(std::move(info)),
static_cast<winrt::Microsoft::ReactNative::RedBoxErrorType>(static_cast<uint32_t>(errorType)));
}
virtual bool isDevSupportEnabled() const override {
return m_redBoxHandler.IsDevSupportEnabled();
}
virtual void updateError(Mso::React::ErrorInfo &&info) override {
m_redBoxHandler.UpdateError(winrt::make<RedBoxErrorInfo>(std::move(info)));
}

virtual void dismissRedbox() override {
m_redBoxHandler.DismissRedBox();
}

private:
winrt::Microsoft::ReactNative::IRedBoxHandler m_redBoxHandler;
};

std::shared_ptr<IRedBoxHandler> CreateRedBoxHandler(
Mso::WeakPtr<IReactHost> &&weakReactHost,
std::shared_ptr<facebook::react::MessageQueueThread> &&uiMessageQueue) noexcept {
return std::make_shared<RedBoxHandler>(std::move(weakReactHost), std::move(uiMessageQueue));
winrt::Microsoft::ReactNative::IRedBoxHandler const &redBoxHandler) noexcept {
return std::make_shared<RedBoxHandler>(redBoxHandler);
}

std::shared_ptr<IRedBoxHandler> CreateDefaultRedBoxHandler(Mso::WeakPtr<IReactHost> &&weakReactHost) noexcept {
return std::make_shared<DefaultRedBoxHandler>(std::move(weakReactHost));
}

} // namespace Mso::React
7 changes: 4 additions & 3 deletions vnext/Microsoft.ReactNative/RedBox.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
// Licensed under the MIT License.
#pragma once
#include <DevSettings.h>
#include "IRedBoxHandler.h"
#include "ReactHost/React.h"
#include "RedBoxHandler.h"

namespace Mso::React {

std::shared_ptr<IRedBoxHandler> CreateRedBoxHandler(
Mso::WeakPtr<IReactHost> &&weakReactHost,
std::shared_ptr<facebook::react::MessageQueueThread> &&uiMessageQueue) noexcept;
winrt::Microsoft::ReactNative::IRedBoxHandler const &redBoxHandler) noexcept;

std::shared_ptr<IRedBoxHandler> CreateDefaultRedBoxHandler(Mso::WeakPtr<IReactHost> &&weakReactHost) noexcept;

} // namespace Mso::React
Loading