* Hermes Inspector Integration redesign

* Improving the change file text

* Resolving review comments

* Reverting the unintentional change to enable hermes by default and some formatting fixes

* Fixing x86 builds by ensuring the right calling convention prefix with inspector APIs

* Fixing issues in instance reload

* Fixing last incomplete commit by uploading JSEngine.props

* yarn format

* Some cleanups

* yarn format

* Adding markdown documentation

* Reporting bundle download status to package and resolving review feedbacks

* Fixing build
This commit is contained in:
Anandraj 2021-05-14 14:24:07 -07:00 коммит произвёл GitHub
Родитель 5ebc3efbcf
Коммит 945ccd4ae2
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
23 изменённых файлов: 608 добавлений и 31 удалений

Просмотреть файл

@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Enabling Hermes Inspector",
"packageName": "react-native-windows",
"email": "anandrag@microsoft.com",
"dependentChangeType": "patch"
}

45
docs/inspector.md Normal file
Просмотреть файл

@ -0,0 +1,45 @@
# Hermes Inspector
*React Native for Windows* with *Hermes engine* now supports direct JavaScript runtime inspection using tools such as Chrome or Edge devtools, VSCode debugger, Flipper etc. by implementing an in-process Chrome Debug Protocol server.
Please note that it is fundamentally different from "Remote JS Debugging", which loads the JavaScript bundle into a remote Chrome browser session with duplex communication over IPC channels.
We share the implementation (code and design) with other platforms wherever possible. All the external endpoints, APIs and protocols should be identical to *React Native* environments on other platforms.
Hence, we expect most tooling available on other platforms to just work on Windows. But, as of now, we have tested only with Chrome and Edge devtools.
## Steps to enable direct debugging
1. Initialize React Native Host,
- Turn on `DeveloperSupport`
- Turn on `FastRefresh`
- Turn off `WebDebugger`
- Turn on `Direct Debugging`
2. Ensure Dev-Server is running
3. Start the application
After the app has booted,
4. Navigate to `edge://inspect` in Edge browser or `chrome://inspect` in Chrome browser
5. Enable **Discover network targets** and **configure** the target discovery settings to include `localhost:8081` (or wherever the metro server is running)
6. Within a few seconds "Hermes React Native" should appear on the page as a remote target
7. Click on the **inspect** link to launch the devtools page
8. Click `Ctrl+P` to open source files and set break points
9. Alternatively, you can insert `debugger` statements in source code to break on specific locations
In order to break on locations during boot, you can either
- `debugger` statements during boot sequence will pause the runtime waiting for debugger to connect
- Set break point and refresh the bundle through the Dev Server. The runtime will wait for debugger to attach.
## Steps to enable heap profiling
Follow steps 1-7 from above, and then
1. Click on the "Memory" tab in the inspector
2. Heap snapshots and instrumented allocations with callstacks should be working.
## Enable debugging/profiling on release builds
We keep the inspector turned off on release builds by default. If you want to debug or profile release builds, set the MSBuild property `EnableHermesInspectorInReleaseFlavor` to `'true'` when building the platform.
## Known Issues
1. CPU Sampling profiler current don't work

Просмотреть файл

@ -2,5 +2,5 @@
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.210312.4" targetFramework="native" />
<package id="Microsoft.UI.Xaml" version="2.5.0" targetFramework="native" />
<package id="ReactNative.Hermes.Windows" version="0.7.2-microsoft.5" targetFramework="native" />
<package id="ReactNative.Hermes.Windows" version="0.7.2-microsoft.11" targetFramework="native" />
</packages>

Просмотреть файл

@ -2,5 +2,5 @@
<packages>
<package id="Microsoft.VCRTForwarders.140" version="1.0.2-rc" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.210312.4" targetFramework="native" />
<package id="ReactNative.Hermes.Windows" version="0.7.2-microsoft.5" targetFramework="native" />
<package id="ReactNative.Hermes.Windows" version="0.7.2-microsoft.11" targetFramework="native" />
</packages>

Просмотреть файл

@ -186,7 +186,7 @@
</Target>
<ImportGroup Label="ExtensionTargets">
<Import Project="..\packages\Microsoft.Windows.CppWinRT.2.0.210312.4\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.210312.4\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\packages\ReactNative.Hermes.Windows.0.7.2-microsoft.5\build\native\ReactNative.Hermes.Windows.targets" Condition="Exists('..\packages\ReactNative.Hermes.Windows.0.7.2-microsoft.5\build\native\ReactNative.Hermes.Windows.targets')" />
<Import Project="$(HermesPackage)\build\native\ReactNative.Hermes.Windows.targets" Condition="Exists('$(HermesPackage)\build\native\ReactNative.Hermes.Windows.targets')" />
<Import Project="..\packages\$(WinUIPackageName).$(WinUIPackageVersion)\build\native\$(WinUIPackageName).targets" Condition="Exists('..\packages\$(WinUIPackageName).$(WinUIPackageVersion)\build\native\$(WinUIPackageName).targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
@ -195,7 +195,7 @@
</PropertyGroup>
<Error Condition="!Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.210312.4\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.CppWinRT.2.0.210312.4\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.210312.4\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.CppWinRT.2.0.210312.4\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\packages\ReactNative.Hermes.Windows.0.7.2-microsoft.5\build\native\ReactNative.Hermes.Windows.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(HermesPackage)\build\native\ReactNative.Hermes.Windows.targets'))" />
<Error Condition="!Exists('$(HermesPackage)\build\native\ReactNative.Hermes.Windows.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(HermesPackage)\build\native\ReactNative.Hermes.Windows.targets'))" />
<Error Condition="!Exists('..\packages\$(WinUIPackageName).$(WinUIPackageVersion)\build\native\$(WinUIPackageName).targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\$(WinUIPackageName).$(WinUIPackageVersion)\build\native\$(WinUIPackageName).targets'))" />
</Target>
</Project>

Просмотреть файл

@ -2,5 +2,5 @@
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.210312.4" targetFramework="native"/>
<package id="Microsoft.UI.Xaml" version="2.5.0" targetFramework="native"/>
<package id="ReactNative.Hermes.Windows" version="0.7.2-microsoft.5" targetFramework="native"/>
<package id="ReactNative.Hermes.Windows" version="0.7.2-microsoft.11" targetFramework="native"/>
</packages>

Просмотреть файл

@ -401,7 +401,8 @@ void ReactInstanceWin::Initialize() noexcept {
switch (m_options.JsiEngine) {
case JSIEngine::Hermes:
#if defined(USE_HERMES)
devSettings->jsiRuntimeHolder = std::make_shared<facebook::react::HermesRuntimeHolder>();
devSettings->jsiRuntimeHolder =
std::make_shared<facebook::react::HermesRuntimeHolder>(devSettings, m_jsMessageThread.Load());
devSettings->inlineSourceMap = false;
break;
#endif

Просмотреть файл

@ -7,6 +7,6 @@
<package id="Microsoft.UI.Xaml" version="2.5.0" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.210312.4" targetFramework="native" />
<package id="Microsoft.WinUI" version="3.0.0-preview4.210210.4" targetFramework="native" />
<package id="ReactNative.Hermes.Windows" version="0.7.2-microsoft.5" targetFramework="native" />
<package id="ReactNative.Hermes.Windows" version="0.7.2-microsoft.11" targetFramework="native" />
<!-- package id="ReactNative.V8Jsi.Windows.UWP" version="0.64.8" targetFramework="native" / -->
</packages>

Просмотреть файл

@ -3,10 +3,11 @@
<PropertyGroup>
<UseHermes Condition="'$(UseHermes)' == ''">false</UseHermes>
<HermesVersion Condition="'$(HermesVersion)' == ''">0.7.2-microsoft.5</HermesVersion>
<HermesVersion Condition="'$(HermesVersion)' == ''">0.7.2-microsoft.11</HermesVersion>
<HermesPackage Condition="'$(HermesPackage)' == ''">$(SolutionDir)packages\ReactNative.Hermes.Windows.$(HermesVersion)</HermesPackage>
<!-- TODO: Can we automatically distinguish between uwp and win32 here? -->
<HermesArch Condition="'$(HermesArch)' == ''">uwp</HermesArch>
<EnableHermesInspectorInReleaseFlavor Condition="'$(EnableHermesInspectorInReleaseFlavor)' == ''">false</EnableHermesInspectorInReleaseFlavor>
<UseV8 Condition="'$(UseV8)' == ''">false</UseV8>
<V8Version Condition="'$(V8Version)' == ''">0.64.8</V8Version>

Просмотреть файл

@ -10,6 +10,7 @@
<ApplicationTypeRevision>10.0</ApplicationTypeRevision>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="$(SolutionDir)\ExperimentalFeatures.props" Condition="Exists('$(SolutionDir)\ExperimentalFeatures.props')" />
<Import Project="$(ReactNativeWindowsDir)PropertySheets\React.Cpp.props" />
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|ARM64">
@ -85,6 +86,7 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(SolutionDir)packages\boost.1.72.0.0\build\boost.targets" Condition="Exists('$(SolutionDir)packages\boost.1.72.0.0\build\boost.targets')" />
<Import Project="$(HermesPackage)\build\native\ReactNative.Hermes.Windows.targets" Condition="Exists('$(HermesPackage)\build\native\ReactNative.Hermes.Windows.targets') AND '$(UseHermes)' == 'true'" />
</ImportGroup>
<ItemGroup>
<ClInclude Include="$(ReactNativeDir)\ReactCommon\jsi\jsi\instrumentation.h" />

Просмотреть файл

@ -59,6 +59,18 @@ class DevServerHelper {
PackagerOpenStackFrameUrlFormat, GetDeviceLocalHost(sourceBundleHost, sourceBundlePort).c_str());
}
static std::string get_InspectorDeviceUrl(
const std::string &packagerHost,
const uint16_t packagerPort,
const std::string &deviceName,
const std::string &packageName) {
return string_format(
InspectorDeviceUrlFormat,
GetDeviceLocalHost(packagerHost, packagerPort).c_str(),
deviceName.c_str(),
packageName.c_str());
}
static constexpr const char DefaultPackagerHost[] = "localhost";
static const uint16_t DefaultPackagerPort = 8081;
@ -79,6 +91,7 @@ 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 PackagerOkStatus[] = "packager-status:running";
const int LongPollFailureDelayMs = 5000;

Просмотреть файл

@ -21,6 +21,11 @@
#include <winrt/Windows.Web.Http.Headers.h>
#include <winrt/Windows.Web.Http.h>
#ifdef HERMES_ENABLE_DEBUGGER
#include <winrt/Windows.ApplicationModel.Activation.h>
#include <winrt/Windows.Networking.Connectivity.h>
#endif
#pragma warning(push)
#pragma warning(disable : 4068 4251 4101 4804 4309)
#include <cxxreact/JSExecutor.h>
@ -232,6 +237,43 @@ void DevSupportManager::StopPollingLiveReload() {
m_cancellation_token = true;
}
void DevSupportManager::StartInspector(
[[maybe_unused]] const std::string &packagerHost,
[[maybe_unused]] const uint16_t packagerPort) noexcept {
#ifdef HERMES_ENABLE_DEBUGGER
std::string packageName("RNW");
if (auto currentPackage = winrt::Windows::ApplicationModel::Package::Current()) {
packageName = winrt::to_string(currentPackage.DisplayName());
}
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<InspectorPackagerConnection>(
facebook::react::DevServerHelper::get_InspectorDeviceUrl(packagerHost, packagerPort, deviceName, packageName),
m_BundleStatusProvider);
m_inspectorPackagerConnection->connectAsync();
#endif
}
void DevSupportManager::StopInspector() noexcept {
#ifdef HERMES_ENABLE_DEBUGGER
if (m_inspectorPackagerConnection) {
m_inspectorPackagerConnection->disconnectAsync();
m_inspectorPackagerConnection = nullptr;
}
#endif
}
void DevSupportManager::UpdateBundleStatus(bool isLastDownloadSucess, int64_t updateTimestamp) noexcept {
#ifdef HERMES_ENABLE_DEBUGGER
m_BundleStatusProvider->updateBundleStatus(isLastDownloadSucess, updateTimestamp);
#endif
}
std::pair<std::string, bool> GetJavaScriptFromServer(
const std::string &sourceBundleHost,
const uint16_t sourceBundlePort,

Просмотреть файл

@ -14,6 +14,10 @@
#include <memory>
#include <string>
#if defined(HERMES_ENABLE_DEBUGGER)
#include <InspectorPackagerConnection.h>
#endif
namespace facebook {
namespace react {
struct DevSettings;
@ -43,8 +47,31 @@ class DevSupportManager final : public facebook::react::IDevSupportManager {
std::function<void()> onChangeCallback) override;
virtual void StopPollingLiveReload() override;
virtual void StartInspector(const std::string &packagerHost, const uint16_t packagerPort) noexcept override;
virtual void StopInspector() noexcept override;
virtual void UpdateBundleStatus(bool isLastDownloadSucess, int64_t updateTimestamp) noexcept override;
private:
std::atomic_bool m_cancellation_token;
#if defined(HERMES_ENABLE_DEBUGGER)
std::shared_ptr<InspectorPackagerConnection> m_inspectorPackagerConnection;
struct BundleStatusProvider : public InspectorPackagerConnection::IBundleStatusProvider {
virtual InspectorPackagerConnection::BundleStatus getBundleStatus() {
return m_bundleStatus;
}
void updateBundleStatus(bool isLastDownloadSucess, int64_t updateTimestamp) {
m_bundleStatus.m_isLastDownloadSucess = isLastDownloadSucess;
m_bundleStatus.m_updateTimestamp = updateTimestamp;
}
private:
InspectorPackagerConnection::BundleStatus m_bundleStatus;
};
std::shared_ptr<BundleStatusProvider> m_BundleStatusProvider = std::make_shared<BundleStatusProvider>();
#endif
};
} // namespace Microsoft::ReactNative

Просмотреть файл

@ -3,31 +3,108 @@
#include "pch.h"
#include <hermes/hermes.h>
#include <memory>
#include <mutex>
#include <JSI/decorator.h>
#include <cxxreact/MessageQueueThread.h>
#include <cxxreact/SystraceSection.h>
#include <hermes/hermes.h>
#include "HermesRuntimeHolder.h"
#if defined(HERMES_ENABLE_DEBUGGER)
#include <hermes/inspector/chrome/Registration.h>
#endif
using namespace facebook;
namespace facebook {
namespace react {
std::shared_ptr<jsi::Runtime> HermesRuntimeHolder::getRuntime() noexcept {
std::call_once(once_flag_, [this]() { initRuntime(); });
namespace {
if (!runtime_)
std::terminate();
// ChakraJsiRuntime is not thread safe as of now.
if (own_thread_id_ != std::this_thread::get_id())
std::terminate();
return runtime_;
std::unique_ptr<facebook::hermes::HermesRuntime> makeHermesRuntimeSystraced(
const ::hermes::vm::RuntimeConfig &runtimeConfig) {
SystraceSection s("HermesExecutorFactory::makeHermesRuntimeSystraced");
return hermes::makeHermesRuntime(runtimeConfig);
}
#ifdef HERMES_ENABLE_DEBUGGER
class HermesExecutorRuntimeAdapter final : public facebook::hermes::inspector::RuntimeAdapter {
public:
HermesExecutorRuntimeAdapter(
std::shared_ptr<jsi::Runtime> runtime,
facebook::hermes::HermesRuntime &hermesRuntime,
std::shared_ptr<MessageQueueThread> thread)
: m_runtime(runtime), m_hermesRuntime(hermesRuntime), m_thread(std::move(thread)) {}
virtual ~HermesExecutorRuntimeAdapter() = default;
jsi::Runtime &getRuntime() override {
return *m_runtime;
}
facebook::hermes::debugger::Debugger &getDebugger() override {
return m_hermesRuntime.getDebugger();
}
void tickleJs() override {
// The queue will ensure that runtime_ is still valid when this
// gets invoked.
m_thread->runOnQueue([&runtime = m_runtime]() {
auto func = runtime->global().getPropertyAsFunction(*runtime, "__tickleJs");
func.call(*runtime);
});
}
private:
std::shared_ptr<jsi::Runtime> m_runtime;
facebook::hermes::HermesRuntime &m_hermesRuntime;
std::shared_ptr<MessageQueueThread> m_thread;
};
#endif
} // namespace
std::shared_ptr<jsi::Runtime> HermesRuntimeHolder::getRuntime() noexcept {
std::call_once(m_once_flag, [this]() { initRuntime(); });
if (!m_runtime)
std::terminate();
// Make sure that the runtime instance is not consumed from multiple threads.
if (m_own_thread_id != std::this_thread::get_id())
std::terminate();
return m_runtime;
}
HermesRuntimeHolder::HermesRuntimeHolder(
std::shared_ptr<facebook::react::DevSettings> devSettings,
std::shared_ptr<facebook::react::MessageQueueThread> jsQueue) noexcept
: m_devSettings(std::move(devSettings)), m_jsQueue(std::move(jsQueue)) {}
void HermesRuntimeHolder::initRuntime() noexcept {
runtime_ = facebook::hermes::makeHermesRuntime();
own_thread_id_ = std::this_thread::get_id();
auto runtimeConfig = ::hermes::vm::RuntimeConfig();
auto hermesRuntime = makeHermesRuntimeSystraced(runtimeConfig);
facebook::hermes::HermesRuntime &hermesRuntimeRef = *hermesRuntime;
m_runtime = std::move(hermesRuntime);
m_own_thread_id = std::this_thread::get_id();
#ifdef HERMES_ENABLE_DEBUGGER
if (m_devSettings->useDirectDebugger) {
auto adapter = std::make_unique<HermesExecutorRuntimeAdapter>(m_runtime, hermesRuntimeRef, m_jsQueue);
facebook::hermes::inspector::chrome::enableDebugging(std::move(adapter), "Hermes React Native");
}
#endif
// Add js engine information to Error.prototype so in error reporting we
// can send this information.
auto errorPrototype =
m_runtime->global().getPropertyAsObject(*m_runtime, "Error").getPropertyAsObject(*m_runtime, "prototype");
errorPrototype.setProperty(*m_runtime, "jsEngine", "hermes");
}
} // namespace react

Просмотреть файл

@ -7,6 +7,8 @@
#include <jsi/jsi.h>
#include <thread>
#include <DevSettings.h>
namespace facebook {
namespace react {
@ -14,12 +16,19 @@ class HermesRuntimeHolder : public facebook::jsi::RuntimeHolderLazyInit {
public:
std::shared_ptr<facebook::jsi::Runtime> getRuntime() noexcept override;
HermesRuntimeHolder(
std::shared_ptr<facebook::react::DevSettings> devSettings,
std::shared_ptr<facebook::react::MessageQueueThread> jsQueue) noexcept;
private:
void initRuntime() noexcept;
std::shared_ptr<facebook::jsi::Runtime> runtime_;
std::shared_ptr<facebook::jsi::Runtime> m_runtime;
std::once_flag once_flag_;
std::thread::id own_thread_id_;
std::once_flag m_once_flag;
std::thread::id m_own_thread_id;
std::shared_ptr<facebook::react::DevSettings> m_devSettings;
std::shared_ptr<facebook::react::MessageQueueThread> m_jsQueue;
};
} // namespace react

Просмотреть файл

@ -21,6 +21,10 @@ struct IDevSupportManager {
const uint16_t sourceBundlePort,
std::function<void()> onChangeCallback) = 0;
virtual void StopPollingLiveReload() = 0;
virtual void StartInspector(const std::string &packagerHost, const uint16_t packagerPort) noexcept = 0;
virtual void StopInspector() noexcept = 0;
virtual void UpdateBundleStatus(bool isLastDownloadSucess, int64_t updateTimestamp) noexcept = 0;
};
std::shared_ptr<IDevSupportManager> CreateDevSupportManager();

Просмотреть файл

@ -0,0 +1,240 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#include "pch.h"
#ifdef HERMES_ENABLE_DEBUGGER
#include <folly/json.h>
#include <tracing/tracing.h>
#include "InspectorPackagerConnection.h"
namespace facebook {
namespace react {
IDestructible::~IDestructible() {}
} // namespace react
} // namespace facebook
namespace Microsoft::ReactNative {
namespace {
struct InspectorProtocol {
static constexpr std::string_view Message_PAGEID = "pageId";
static constexpr std::string_view Message_PAYLOAD = "payload";
static constexpr std::string_view Message_eventName_wrappedEvent = "wrappedEvent";
static constexpr std::string_view Message_eventName_getPages = "getPages";
static constexpr std::string_view Message_eventName_connect = "connect";
static constexpr std::string_view Message_eventName_disconnect = "disconnect";
static constexpr std::string_view Message_EVENT = "event";
enum class EventType { GetPages, WrappedEvent, Connect, Disconnect };
static EventType getEventType(const folly::dynamic &messageFromPackager) {
std::string event = messageFromPackager.at(InspectorProtocol::Message_EVENT).getString();
if (event == Message_eventName_getPages) {
return EventType::GetPages;
}
if (event == Message_eventName_wrappedEvent) {
return EventType::WrappedEvent;
}
if (event == Message_eventName_connect) {
return EventType::Connect;
}
if (event == Message_eventName_disconnect) {
return EventType::Disconnect;
}
assert(false && "Unknown event!");
std::abort();
}
static folly::dynamic constructResponseForPackager(EventType eventType, folly::dynamic &&payload) {
folly::dynamic response = folly::dynamic::object;
switch (eventType) {
case EventType::GetPages:
response[InspectorProtocol::Message_EVENT] = InspectorProtocol::Message_eventName_getPages;
break;
case EventType::WrappedEvent:
response[InspectorProtocol::Message_EVENT] = InspectorProtocol::Message_eventName_wrappedEvent;
break;
case EventType::Connect:
response[InspectorProtocol::Message_EVENT] = InspectorProtocol::Message_eventName_connect;
break;
case EventType::Disconnect:
response[InspectorProtocol::Message_EVENT] = InspectorProtocol::Message_eventName_disconnect;
break;
default:
assert(false && "Unknown event Type.");
std::abort();
}
response[InspectorProtocol::Message_PAYLOAD] = std::move(payload);
return response;
}
static folly::dynamic constructGetPagesResponsePayloadForPackager(
const std::vector<facebook::react::InspectorPage> &pages,
InspectorPackagerConnection::BundleStatus bundleStatus) {
folly::dynamic payload = folly::dynamic::array;
for (const facebook::react::InspectorPage &page : pages) {
folly::dynamic pageDyn = folly::dynamic::object;
pageDyn["id"] = page.id;
pageDyn["title"] = page.title;
pageDyn["vm"] = page.vm;
pageDyn["isLastBundleDownloadSuccess"] = bundleStatus.m_isLastDownloadSucess;
pageDyn["bundleUpdateTimestamp"] = bundleStatus.m_updateTimestamp;
payload.push_back(pageDyn);
}
return payload;
}
static folly::dynamic constructVMResponsePayloadForPackager(int64_t pageId, std::string &&messageFromVM) {
folly::dynamic payload = folly::dynamic::object;
payload[InspectorProtocol::Message_eventName_wrappedEvent] = messageFromVM;
payload[InspectorProtocol::Message_PAGEID] = pageId;
return payload;
}
static folly::dynamic constructVMResponsePayloadOnDisconnectForPackager(int64_t pageId) {
folly::dynamic payload = folly::dynamic::object;
payload[InspectorProtocol::Message_PAGEID] = pageId;
return payload;
}
};
} // namespace
RemoteConnection::RemoteConnection(int64_t pageId, const InspectorPackagerConnection &packagerConnection)
: m_packagerConnection(packagerConnection), m_pageId(pageId) {}
void RemoteConnection::onMessage(std::string message) {
folly::dynamic response = InspectorProtocol::constructResponseForPackager(
InspectorProtocol::EventType::WrappedEvent,
InspectorProtocol::constructVMResponsePayloadForPackager(m_pageId, std::move(message)));
std::string responsestr = folly::toJson(response);
m_packagerConnection.sendMessageToPackager(std::move(responsestr));
}
void RemoteConnection::onDisconnect() {
folly::dynamic response = InspectorProtocol::constructResponseForPackager(
InspectorProtocol::EventType::Disconnect,
InspectorProtocol::constructVMResponsePayloadOnDisconnectForPackager(m_pageId));
std::string responsestr = folly::toJson(response);
m_packagerConnection.sendMessageToPackager(std::move(responsestr));
}
winrt::fire_and_forget InspectorPackagerConnection::sendMessageToPackagerAsync(std::string &&message) const {
std::string message_(std::move(message));
co_await winrt::resume_background();
m_packagerWebSocketConnection->Send(std::move(message_));
co_return;
}
void InspectorPackagerConnection::sendMessageToPackager(std::string &&message) const {
sendMessageToPackagerAsync(std::move(message));
}
void InspectorPackagerConnection::sendMessageToVM(int64_t pageId, std::string &&message) {
m_localConnections[pageId]->sendMessage(std::move(message));
}
InspectorPackagerConnection::InspectorPackagerConnection(
std::string &&url,
std::shared_ptr<IBundleStatusProvider> bundleStatusProvider)
: m_url(std::move(url)), m_bundleStatusProvider(std::move(bundleStatusProvider)) {}
winrt::fire_and_forget InspectorPackagerConnection::disconnectAsync() {
co_await winrt::resume_background();
std::string reason("Explicit close");
m_packagerWebSocketConnection->Close(Microsoft::React::WinRTWebSocketResource::CloseCode::GoingAway, reason);
co_return;
}
winrt::fire_and_forget InspectorPackagerConnection::connectAsync() {
co_await winrt::resume_background();
std::vector<winrt::Windows::Security::Cryptography::Certificates::ChainValidationResult> certExceptions;
m_packagerWebSocketConnection =
std::make_shared<Microsoft::React::WinRTWebSocketResource>(m_url, std::move(certExceptions));
m_packagerWebSocketConnection->SetOnError([](const Microsoft::React::IWebSocketResource::Error &err) {
facebook::react::tracing::error(err.Message.c_str());
});
m_packagerWebSocketConnection->SetOnConnect(
[]() { facebook::react::tracing::log("Inspector: Websocket connection succeeded."); });
m_packagerWebSocketConnection->SetOnMessage([self = shared_from_this()](
size_t /*length*/, const std::string &message, bool isBinary) {
assert(!isBinary && "We don't expect any binary messages !");
folly::dynamic messageDyn = folly::parseJson(message);
InspectorProtocol::EventType eventType = InspectorProtocol::getEventType(messageDyn);
switch (eventType) {
case InspectorProtocol::EventType::GetPages: {
std::vector<facebook::react::InspectorPage> inspetorPages = facebook::react::getInspectorInstance().getPages();
folly::dynamic response = InspectorProtocol::constructResponseForPackager(
InspectorProtocol::EventType::GetPages,
InspectorProtocol::constructGetPagesResponsePayloadForPackager(
inspetorPages, self->m_bundleStatusProvider->getBundleStatus()));
std::string responsestr = folly::toJson(response);
self->sendMessageToPackager(std::move(responsestr));
} break;
case InspectorProtocol::EventType::WrappedEvent: {
folly::dynamic payload = messageDyn[InspectorProtocol::Message_PAYLOAD];
int64_t pageId = payload[InspectorProtocol::Message_PAGEID].asInt();
if (self->m_localConnections.find(pageId) == self->m_localConnections.end()) {
break;
}
std::string wrappedEvent = payload[InspectorProtocol::Message_eventName_wrappedEvent].getString();
self->sendMessageToVM(pageId, std::move(wrappedEvent));
} break;
case InspectorProtocol::EventType::Connect: {
folly::dynamic payload = messageDyn[InspectorProtocol::Message_PAYLOAD];
int64_t pageId = payload[InspectorProtocol::Message_PAGEID].asInt();
if (self->m_localConnections.find(pageId) != self->m_localConnections.end()) {
break;
}
self->m_localConnections[pageId] = facebook::react::getInspectorInstance().connect(
static_cast<int>(pageId), std::make_unique<RemoteConnection>(pageId, *self));
} break;
case InspectorProtocol::EventType::Disconnect: {
folly::dynamic payload = messageDyn[InspectorProtocol::Message_PAYLOAD];
int64_t pageId = payload[InspectorProtocol::Message_PAGEID].asInt();
if (self->m_localConnections.find(pageId) != self->m_localConnections.end()) {
self->m_localConnections[pageId]->disconnect();
self->m_localConnections.erase(pageId);
}
} break;
}
});
Microsoft::React::IWebSocketResource::Protocols protocols;
Microsoft::React::IWebSocketResource::Options options;
m_packagerWebSocketConnection->Connect(protocols, options);
co_return;
}
} // namespace Microsoft::ReactNative
#endif

Просмотреть файл

@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma once
#include <WinRTWebSocketResource.h>
#include <jsinspector/InspectorInterfaces.h>
namespace Microsoft::ReactNative {
class InspectorPackagerConnection final : public std::enable_shared_from_this<InspectorPackagerConnection> {
public:
winrt::fire_and_forget connectAsync();
winrt::fire_and_forget disconnectAsync();
class BundleStatus {
public:
bool m_isLastDownloadSucess;
int64_t m_updateTimestamp = -1;
BundleStatus(bool isLastDownloadSucess, long updateTimestamp)
: m_isLastDownloadSucess(isLastDownloadSucess), m_updateTimestamp(updateTimestamp) {}
BundleStatus() : m_isLastDownloadSucess(false), m_updateTimestamp(-1) {}
};
struct IBundleStatusProvider {
virtual BundleStatus getBundleStatus() = 0;
};
InspectorPackagerConnection(std::string &&url, std::shared_ptr<IBundleStatusProvider> bundleStatusProvider);
private:
friend class RemoteConnection;
winrt::fire_and_forget sendMessageToPackagerAsync(std::string &&message) const;
void sendMessageToPackager(std::string &&message) const;
// Note:: VM side Inspector processes the messages asynchronousely in a sequential executor with dedicated thread.
// Hence, we don't bother invoking the inspector asynchronously.
void sendMessageToVM(int64_t pageId, std::string &&message);
std::unordered_map<int64_t, std::unique_ptr<facebook::react::ILocalConnection>> m_localConnections;
std::shared_ptr<Microsoft::React::WinRTWebSocketResource> m_packagerWebSocketConnection;
std::shared_ptr<IBundleStatusProvider> m_bundleStatusProvider;
std::string m_url;
};
class RemoteConnection final : public facebook::react::IRemoteConnection {
public:
RemoteConnection(int64_t pageId, const InspectorPackagerConnection &packagerConnection);
void onMessage(std::string message) override;
void onDisconnect() override;
private:
int64_t m_pageId;
const InspectorPackagerConnection &m_packagerConnection;
};
} // namespace Microsoft::ReactNative

Просмотреть файл

@ -53,14 +53,9 @@
#include <ReactCommon/TurboModuleBinding.h>
#include "ChakraRuntimeHolder.h"
#include <tracing/tracing.h>
namespace fs = std::filesystem;
// forward declaration.
namespace facebook::react::tracing {
void initializeETW();
void initializeJSHooks(facebook::jsi::Runtime &runtime);
} // namespace facebook::react::tracing
namespace {
#if (defined(_MSC_VER) && !defined(WINRT))
@ -297,6 +292,10 @@ InstanceImpl::InstanceImpl(
facebook::react::tracing::initializeETW();
#endif
if (m_devSettings->useDirectDebugger && !m_devSettings->useWebDebugger) {
m_devManager->StartInspector(m_devSettings->sourceBundleHost, m_devSettings->sourceBundlePort);
}
// Default (common) NativeModules
auto modules = GetDefaultNativeModules(nativeQueue);
@ -345,7 +344,7 @@ InstanceImpl::InstanceImpl(
switch (m_devSettings->jsiEngineOverride) {
case JSIEngineOverride::Hermes:
#if defined(USE_HERMES)
m_devSettings->jsiRuntimeHolder = std::make_shared<HermesRuntimeHolder>();
m_devSettings->jsiRuntimeHolder = std::make_shared<HermesRuntimeHolder>(m_devSettings, m_jsThread);
m_devSettings->inlineSourceMap = false;
break;
#else
@ -433,10 +432,16 @@ void InstanceImpl::loadBundleInternal(std::string &&jsBundleRelativePath, bool s
m_devSettings->inlineSourceMap);
if (!success) {
m_devManager->UpdateBundleStatus(false, -1);
m_devSettings->errorCallback(jsBundleString);
return;
}
int64_t currentTimeInMilliSeconds =
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono ::system_clock::now().time_since_epoch())
.count();
m_devManager->UpdateBundleStatus(true, currentTimeInMilliSeconds);
auto bundleUrl = DevServerHelper::get_BundleUrl(
m_devSettings->sourceBundleHost,
m_devSettings->sourceBundlePort,
@ -484,6 +489,7 @@ void InstanceImpl::loadBundleInternal(std::string &&jsBundleRelativePath, bool s
}
InstanceImpl::~InstanceImpl() {
m_devManager->StopInspector();
m_nativeQueue->quitSynchronous();
}

Просмотреть файл

@ -29,6 +29,9 @@
<ClCompile Include="$(MSBuildThisFileDirectory)HermesRuntimeHolder.cpp">
<ExcludedFromBuild Condition="'$(UseHermes)' != 'true'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="$(MSBuildThisFileDirectory)InspectorPackagerConnection.cpp">
<ExcludedFromBuild Condition="'$(UseHermes)' != 'true'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="$(MSBuildThisFileDirectory)InstanceManager.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)JSBigAbiString.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)JSI\ChakraApi.cpp" />
@ -91,6 +94,7 @@
<ClInclude Include="$(MSBuildThisFileDirectory)DevSettings.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)Executors\WebSocketJSExecutor.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)HermesRuntimeHolder.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)InspectorPackagerConnection.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)IDevSupportManager.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)IHttpResource.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)InstanceManager.h" />
@ -120,6 +124,7 @@
<ClInclude Include="$(MSBuildThisFileDirectory)Threading\MessageQueueThreadFactory.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)Tracing.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)tracing\fbsystrace.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)tracing\tracing.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)TurboModuleManager.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)TurboModuleRegistry.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)Utils.h" />

Просмотреть файл

@ -124,6 +124,9 @@
<ClCompile Include="$(MSBuildThisFileDirectory)JSI\ChakraJsiRuntime_edgemode.cpp">
<Filter>Source Files\JSI</Filter>
</ClCompile>
<ClCompile Include="$(MSBuildThisFileDirectory)InspectorPackagerConnection.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Filter Include="Source Files">
@ -363,6 +366,12 @@
<ClInclude Include="$(MSBuildThisFileDirectory)JSI\ChakraCoreRuntime.h">
<Filter>Header Files\JSI</Filter>
</ClInclude>
<ClInclude Include="$(MSBuildThisFileDirectory)InspectorPackagerConnection.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="$(MSBuildThisFileDirectory)tracing\tracing.h">
<Filter>Header Files\tracing</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)tracing\rnw.wprp">

Просмотреть файл

@ -5,6 +5,7 @@
#include <TraceLoggingProvider.h>
#include <jsi/jsi.h>
#include <winmeta.h>
#include "tracing/fbsystrace.h"
#include <array>
@ -383,6 +384,16 @@ void initializeETW() {
}
}
void log(const char *msg) {
TraceLoggingWrite(
g_hTraceLoggingProvider, "Trace", TraceLoggingLevel(WINEVENT_LEVEL_INFO), TraceLoggingString(msg, "message"));
}
void error(const char *msg) {
TraceLoggingWrite(
g_hTraceLoggingProvider, "Trace", TraceLoggingLevel(WINEVENT_LEVEL_ERROR), TraceLoggingString(msg, "message"));
}
} // namespace tracing
} // namespace react
} // namespace facebook

Просмотреть файл

@ -0,0 +1,20 @@
#pragma once
// forward declaration.
namespace facebook {
namespace jsi {
class Runtime;
}
} // namespace facebook
namespace facebook {
namespace react {
namespace tracing {
void initializeETW();
void initializeJSHooks(facebook::jsi::Runtime &runtime);
void log(const char *msg);
void error(const char *msg);
} // namespace tracing
} // namespace react
} // namespace facebook