Create wrapper class for Hermes CDPAgent + CDPDebugAPI (#43352)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/43352

## Context

We are migrating to the new Hermes `CDPAgent` and `CDPDebugAPI` APIs in the modern CDP server (previously `HermesCDPHandler`).

## This diff

Adds the `HermesRuntimeAgentDelegateNew` class to provide a swap-in replacement for the existing `HermesRuntimeAgentDelegate` when we enable this via an incoming feature flag.

Changelog: [Internal]

Reviewed By: motiz88

Differential Revision: D53810356

fbshipit-source-id: c63684252230a747ecf0bd8cbb6f4e22052ed9bf
This commit is contained in:
Alex Hunt
2024-03-08 07:29:22 -08:00
committed by Facebook GitHub Bot
parent 52bd8418d8
commit 166fdc5125
7 changed files with 262 additions and 1 deletions
+1 -1
View File
@@ -36,7 +36,7 @@ jobs:
uses: actions/cache@v3
with:
path: packages/rn-tester/Pods
key: v1-${{ runner.os }}-RNTesterPods-${{ hashFiles('packages/rn-tester/Podfile.lock') }}-${{ hashFiles('packages/rn-tester/Podfile') }}-${{ hashFiles('tmp/hermes/hermesversion') }}
key: v2-${{ runner.os }}-RNTesterPods-${{ hashFiles('packages/rn-tester/Podfile.lock') }}-${{ hashFiles('packages/rn-tester/Podfile') }}-${{ hashFiles('tmp/hermes/hermesversion') }}
- name: Pod Install
run: |
cd packages/rn-tester
@@ -0,0 +1,132 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "HermesRuntimeAgentDelegateNew.h"
// If HERMES_ENABLE_DEBUGGER isn't defined, we can't access any Hermes
// CDP headers or types.
#ifdef HERMES_ENABLE_DEBUGGER
#include <hermes/AsyncDebuggerAPI.h>
#include <hermes/cdp/CDPAgent.h>
#include <hermes/inspector/RuntimeAdapter.h>
#else // HERMES_ENABLE_DEBUGGER
#include <jsinspector-modern/FallbackRuntimeAgentDelegate.h>
#endif // HERMES_ENABLE_DEBUGGER
#include <hermes/hermes.h>
#include <jsinspector-modern/ReactCdp.h>
using namespace facebook::hermes;
namespace facebook::react::jsinspector_modern {
#ifdef HERMES_ENABLE_DEBUGGER
class HermesRuntimeAgentDelegateNew::Impl final : public RuntimeAgentDelegate {
public:
Impl(
FrontendChannel frontendChannel,
SessionState& /*unused*/,
std::unique_ptr<RuntimeAgentDelegate::ExportedState>
previouslyExportedState,
const ExecutionContextDescription& executionContextDescription,
HermesRuntime& runtime,
HermesRuntimeTargetDelegate& runtimeTargetDelegate,
const RuntimeExecutor& runtimeExecutor)
: hermes_(hermes::cdp::CDPAgent::create(
executionContextDescription.id,
runtimeTargetDelegate.getCDPDebugAPI(),
// RuntimeTask takes a HermesRuntime whereas our RuntimeExecutor
// takes a jsi::Runtime.
[runtimeExecutor,
&runtime](facebook::hermes::debugger::RuntimeTask fn) {
runtimeExecutor(
[&runtime, fn = std::move(fn)](auto&) { fn(runtime); });
},
std::move(frontendChannel))) {
// TODO(T178858701): Pass previouslyExportedState to CDPAgent
(void)previouslyExportedState;
}
/**
* Handle a CDP request. The response will be sent over the provided
* \c FrontendChannel synchronously or asynchronously.
* \param req The parsed request.
* \returns true if this agent has responded, or will respond asynchronously,
* to the request (with either a success or error message). False if the
* agent expects another agent to respond to the request instead.
*/
bool handleRequest(const cdp::PreparsedRequest& req) override {
// TODO: Change to string::starts_with when we're on C++20.
if (req.method.rfind("Log.", 0) == 0) {
// Since we know Hermes doesn't do anything useful with Log messages, but
// our containing PageAgent will, just bail out early.
// TODO: We need a way to negotiate this more dynamically with Hermes
// through the API.
return false;
}
// Forward everything else to Hermes's CDPAgent.
hermes_->handleCommand(req.toJson());
// Let the call know that this request is handled (i.e. it is Hermes's
// responsibility to respond with either success or an error).
return true;
}
private:
std::unique_ptr<hermes::cdp::CDPAgent> hermes_;
};
#else // !HERMES_ENABLE_DEBUGGER
/**
* A stub for HermesRuntimeAgentDelegateNew when Hermes is compiled without
* debugging support.
*/
class HermesRuntimeAgentDelegateNew::Impl final
: public FallbackRuntimeAgentDelegate {
public:
Impl(
FrontendChannel frontendChannel,
SessionState& sessionState,
std::unique_ptr<RuntimeAgentDelegate::ExportedState>,
const ExecutionContextDescription&,
HermesRuntime& runtime,
HermesRuntimeTargetDelegate& runtimeTargetDelegate,
RuntimeExecutor)
: FallbackRuntimeAgentDelegate(
std::move(frontendChannel),
sessionState,
runtime.description()) {}
};
#endif // HERMES_ENABLE_DEBUGGER
HermesRuntimeAgentDelegateNew::HermesRuntimeAgentDelegateNew(
FrontendChannel frontendChannel,
SessionState& sessionState,
std::unique_ptr<RuntimeAgentDelegate::ExportedState>
previouslyExportedState,
const ExecutionContextDescription& executionContextDescription,
HermesRuntime& runtime,
HermesRuntimeTargetDelegate& runtimeTargetDelegate,
RuntimeExecutor runtimeExecutor)
: impl_(std::make_unique<Impl>(
std::move(frontendChannel),
sessionState,
std::move(previouslyExportedState),
executionContextDescription,
runtime,
runtimeTargetDelegate,
std::move(runtimeExecutor))) {}
bool HermesRuntimeAgentDelegateNew::handleRequest(
const cdp::PreparsedRequest& req) {
return impl_->handleRequest(req);
}
} // namespace facebook::react::jsinspector_modern
@@ -0,0 +1,75 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include "HermesRuntimeTargetDelegate.h"
#include <ReactCommon/RuntimeExecutor.h>
#include <hermes/hermes.h>
#include <jsinspector-modern/ReactCdp.h>
namespace facebook::react::jsinspector_modern {
/**
* A RuntimeAgentDelegate that handles requests from the Chrome DevTools
* Protocol for an instance of Hermes, using the new CDPAgent API.
*/
class HermesRuntimeAgentDelegateNew : public RuntimeAgentDelegate {
public:
/**
* \param frontendChannel A channel used to send responses and events to the
* frontend.
* \param sessionState The state of the current CDP session. This will only
* be accessed on the main thread (during the constructor, in handleRequest,
* etc).
* \param previouslyExportedState The exported state from a previous instance
* of RuntimeAgentDelegate (NOT necessarily HermesRuntimeAgentDelegateNew).
* This may be nullptr, and if not nullptr it may be of any concrete type that
* implements RuntimeAgentDelegate::ExportedState.
* \param executionContextDescription A description of the execution context
* represented by this runtime. This is used for disambiguating the
* source/destination of CDP messages when there are multiple runtimes
* (concurrently or over the life of a Page).
* \param runtime The HermesRuntime that this agent is attached to. The caller
* is responsible for keeping this object alive for the duration of the
* \c HermesRuntimeAgentDelegateNew lifetime.
* \param runtimeTargetDelegate The \c HermesRuntimeTargetDelegate object
* object for the passed runtime.
* \param runtimeExecutor A callback for scheduling work on the JS thread.
* \c runtimeExecutor may drop scheduled work if the runtime is destroyed
* first.
*/
HermesRuntimeAgentDelegateNew(
FrontendChannel frontendChannel,
SessionState& sessionState,
std::unique_ptr<RuntimeAgentDelegate::ExportedState>
previouslyExportedState,
const ExecutionContextDescription& executionContextDescription,
hermes::HermesRuntime& runtime,
HermesRuntimeTargetDelegate& runtimeTargetDelegate,
RuntimeExecutor runtimeExecutor);
/**
* Handle a CDP request. The response will be sent over the provided
* \c FrontendChannel synchronously or asynchronously.
* \param req The parsed request.
* \returns true if this agent has responded, or will respond asynchronously,
* to the request (with either a success or error message). False if the
* agent expects another agent to respond to the request instead.
*/
bool handleRequest(const cdp::PreparsedRequest& req) override;
private:
// We use the private implementation idiom to keep HERMES_ENABLE_DEBUGGER
// checks out of the header.
class Impl;
const std::unique_ptr<Impl> impl_;
};
} // namespace facebook::react::jsinspector_modern
@@ -8,6 +8,12 @@
#include "HermesRuntimeTargetDelegate.h"
#include "HermesRuntimeAgentDelegate.h"
#ifdef HERMES_ENABLE_DEBUGGER
#include <hermes/cdp/CDPDebugAPI.h>
using namespace facebook::hermes::cdp;
#endif // HERMES_ENABLE_DEBUGGER
#include <utility>
using namespace facebook::hermes;
@@ -16,8 +22,18 @@ namespace facebook::react::jsinspector_modern {
class HermesRuntimeTargetDelegate::Impl : public RuntimeTargetDelegate {
public:
#ifdef HERMES_ENABLE_DEBUGGER
explicit Impl(std::shared_ptr<HermesRuntime> hermesRuntime)
: runtime_(std::move(hermesRuntime)),
cdpDebugAPI_(CDPDebugAPI::create(*runtime_)) {}
CDPDebugAPI& getCDPDebugAPI() {
return *cdpDebugAPI_;
}
#else
explicit Impl(std::shared_ptr<HermesRuntime> hermesRuntime)
: runtime_(std::move(hermesRuntime)) {}
#endif
// RuntimeTargetDelegate methods
@@ -39,6 +55,10 @@ class HermesRuntimeTargetDelegate::Impl : public RuntimeTargetDelegate {
private:
std::shared_ptr<HermesRuntime> runtime_;
#ifdef HERMES_ENABLE_DEBUGGER
const std::unique_ptr<CDPDebugAPI> cdpDebugAPI_;
#endif
};
HermesRuntimeTargetDelegate::HermesRuntimeTargetDelegate(
@@ -63,4 +83,10 @@ HermesRuntimeTargetDelegate::createAgentDelegate(
std::move(runtimeExecutor));
}
#ifdef HERMES_ENABLE_DEBUGGER
CDPDebugAPI& HermesRuntimeTargetDelegate::getCDPDebugAPI() {
return impl_->getCDPDebugAPI();
}
#endif
} // namespace facebook::react::jsinspector_modern
@@ -13,6 +13,10 @@
#include <hermes/hermes.h>
#include <jsinspector-modern/ReactCdp.h>
#ifdef HERMES_ENABLE_DEBUGGER
#include <hermes/cdp/CDPDebugAPI.h>
#endif
#include <memory>
namespace facebook::react::jsinspector_modern {
@@ -42,8 +46,20 @@ class HermesRuntimeTargetDelegate : public RuntimeTargetDelegate {
RuntimeExecutor runtimeExecutor) override;
private:
// We use the private implementation idiom to ensure this class has the same
// layout regardless of whether HERMES_ENABLE_DEBUGGER is defined. The net
// effect is that callers can include HermesRuntimeTargetDelegate.h without
// setting HERMES_ENABLE_DEBUGGER one way or the other.
class Impl;
// Callers within this library may set HERMES_ENABLE_DEBUGGER to see this extra
// API.
#ifdef HERMES_ENABLE_DEBUGGER
friend class HermesRuntimeAgentDelegateNew;
hermes::cdp::CDPDebugAPI& getCDPDebugAPI();
#endif
std::unique_ptr<Impl> impl_;
};
@@ -79,6 +79,12 @@ Pod::Spec.new do |spec|
ss.header_dir = 'hermes'
end
spec.subspec 'cdp' do |ss|
ss.source_files = ''
ss.public_header_files = 'API/hermes/cdp/*.h'
ss.header_dir = 'hermes/cdp'
end
spec.subspec 'inspector' do |ss|
ss.source_files = ''
ss.public_header_files = 'API/hermes/inspector/*.h'
@@ -135,6 +135,9 @@ function build_apple_framework {
mkdir -p destroot/include/hermes
cp API/hermes/*.h destroot/include/hermes
mkdir -p destroot/include/hermes/cdp
cp API/hermes/cdp/*.h destroot/include/hermes/cdp
mkdir -p destroot/include/hermes/inspector
cp API/hermes/inspector/*.h destroot/include/hermes/inspector
@@ -162,6 +165,9 @@ function prepare_dest_root_for_ci {
mkdir -p destroot/include/hermes
cp API/hermes/*.h destroot/include/hermes
mkdir -p destroot/include/hermes/cdp
cp API/hermes/cdp/*.h destroot/include/hermes/cdp
mkdir -p destroot/include/hermes/inspector
cp API/hermes/inspector/*.h destroot/include/hermes/inspector