Skip to content

Commit

Permalink
Expose ability for apps to provide their own RedBox implementation (#…
Browse files Browse the repository at this point in the history
…4786)

* Expose ability for apps to provide their own RedBox implementation

* Change files

* minor changes

* Code review feedback

* minor fix

* format fixes

* minor change

* minor fix

* minor fix

* minor fix
  • Loading branch information
acoates-ms authored and NickGerleman committed May 14, 2020
1 parent e73a55e commit c6c71f7
Show file tree
Hide file tree
Showing 20 changed files with 309 additions and 50 deletions.
@@ -0,0 +1,8 @@
{
"type": "prerelease",
"comment": "Expose ability for apps to provide their own RedBox implementation",
"packageName": "react-native-windows",
"email": "acoates@microsoft.com",
"dependentChangeType": "patch",
"date": "2020-05-04T19:54:41.414Z"
}
1 change: 1 addition & 0 deletions packages/scripts/formatFiles.js
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
Expand Up @@ -316,6 +316,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 @@ -476,6 +480,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 @@ -532,6 +540,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
Expand Up @@ -694,6 +694,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
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
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
@@ -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; };
}
}
6 changes: 6 additions & 0 deletions vnext/Microsoft.ReactNative/ReactNativeHost.cpp
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
Expand Up @@ -3,6 +3,7 @@
#include "pch.h"
#include "RedBox.h"
#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 @@ -24,10 +25,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 @@ -352,26 +353,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 @@ -390,7 +387,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 @@ -410,28 +407,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 @@ -459,25 +454,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(
[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
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

0 comments on commit c6c71f7

Please sign in to comment.