mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Create HermesRuntimeAgent (#42747)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/42747 Changelog: [Internal] Implements a `RuntimeAgent` (D51231326) for Hermes for the modern CDP backend, based on the `CDPHandler` API that Hermes exposes currently. ## A note on `console` We unfortunately have to disable `console` interception (D51234334 / equivalently D52971652) because `CDPHandler`'s current implementation is not aligned with the Agent concept: * Agents are only created once a session has started, but the `console` interceptor needs to be injected at VM startup. * Agents should not clobber each other's shared state (nor consume excessive resources per Agent), but each `CDPHandler` would install its own independent `console` interceptor if enabled. We will enable CDP `console` support in the modern backend in future work. This will require either some additional plumbing in RN (e.g. to safely access JSI from an Agent/Target) or some additional work in Hermes. ## Conditional compilation based on `HERMES_ENABLE_DEBUGGER` `HermesRuntimeAgent.cpp` compiles both with and without `-DHERMES_ENABLE_DEBUGGER`, which is the flag Hermes uses to control the availability of `CDPHandler` (and its containing Buck library). If the debugger is not enabled, `HermesRuntimeAgent` reduces to a `FallbackRuntimeAgent`. In either case, no Hermes debugger headers leak into `HermesRuntimeAgent.h`, so callers don't need to check `#ifdef HERMES_ENABLE_DEBUGGER`, and the overall CDP backend infra is not gated on whether the Hermes debugger is compiled in. Reviewed By: huntie Differential Revision: D51234333 fbshipit-source-id: ccbca443560308c5edba4b9689501d01059fdd94
This commit is contained in:
committed by
Facebook GitHub Bot
parent
4e6eba7a2d
commit
cc34acefab
@@ -42,7 +42,7 @@ Pod::Spec.new do |s|
|
||||
s.header_dir = "reacthermes"
|
||||
s.dependency "React-cxxreact", version
|
||||
s.dependency "React-jsiexecutor", version
|
||||
s.dependency "React-jsinspector", version
|
||||
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
|
||||
s.dependency "React-perflogger", version
|
||||
s.dependency "RCT-Folly", folly_version
|
||||
s.dependency "DoubleConversion"
|
||||
@@ -50,4 +50,5 @@ Pod::Spec.new do |s|
|
||||
s.dependency "glog"
|
||||
s.dependency "hermes-engine"
|
||||
s.dependency "React-jsi"
|
||||
s.dependency "React-runtimeexecutor"
|
||||
end
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <jsi/decorator.h>
|
||||
#include <jsinspector-modern/InspectorFlags.h>
|
||||
|
||||
#include <hermes/inspector-modern/chrome/HermesRuntimeAgent.h>
|
||||
#include <hermes/inspector-modern/chrome/Registration.h>
|
||||
#include <hermes/inspector/RuntimeAdapter.h>
|
||||
|
||||
@@ -230,7 +231,12 @@ std::unique_ptr<JSExecutor> HermesExecutorFactory::createJSExecutor(
|
||||
errorPrototype.setProperty(*decoratedRuntime, "jsEngine", "hermes");
|
||||
|
||||
return std::make_unique<HermesExecutor>(
|
||||
decoratedRuntime, delegate, jsQueue, timeoutInvoker_, runtimeInstaller_);
|
||||
decoratedRuntime,
|
||||
delegate,
|
||||
jsQueue,
|
||||
timeoutInvoker_,
|
||||
runtimeInstaller_,
|
||||
hermesRuntimeRef);
|
||||
}
|
||||
|
||||
::hermes::vm::RuntimeConfig HermesExecutorFactory::defaultRuntimeConfig() {
|
||||
@@ -244,7 +250,37 @@ HermesExecutor::HermesExecutor(
|
||||
std::shared_ptr<ExecutorDelegate> delegate,
|
||||
std::shared_ptr<MessageQueueThread> jsQueue,
|
||||
const JSIScopedTimeoutInvoker& timeoutInvoker,
|
||||
RuntimeInstaller runtimeInstaller)
|
||||
: JSIExecutor(runtime, delegate, timeoutInvoker, runtimeInstaller) {}
|
||||
RuntimeInstaller runtimeInstaller,
|
||||
HermesRuntime& hermesRuntime)
|
||||
: JSIExecutor(runtime, delegate, timeoutInvoker, runtimeInstaller),
|
||||
jsQueue_(jsQueue),
|
||||
runtime_(runtime),
|
||||
hermesRuntime_(hermesRuntime) {}
|
||||
|
||||
std::unique_ptr<jsinspector_modern::RuntimeAgent>
|
||||
HermesExecutor::createRuntimeAgent(
|
||||
jsinspector_modern::FrontendChannel frontendChannel,
|
||||
jsinspector_modern::SessionState& sessionState) {
|
||||
std::shared_ptr<HermesRuntime> hermesRuntimeShared(runtime_, &hermesRuntime_);
|
||||
return std::unique_ptr<jsinspector_modern::RuntimeAgent>(
|
||||
new jsinspector_modern::HermesRuntimeAgent(
|
||||
frontendChannel,
|
||||
sessionState,
|
||||
hermesRuntimeShared,
|
||||
[jsQueueWeak = std::weak_ptr(jsQueue_),
|
||||
runtimeWeak = std::weak_ptr(runtime_)](auto fn) {
|
||||
auto jsQueue = jsQueueWeak.lock();
|
||||
if (!jsQueue) {
|
||||
return;
|
||||
}
|
||||
jsQueue->runOnQueue([runtimeWeak, fn]() {
|
||||
auto runtime = runtimeWeak.lock();
|
||||
if (!runtime) {
|
||||
return;
|
||||
}
|
||||
fn(*runtime);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
@@ -51,10 +51,18 @@ class HermesExecutor : public JSIExecutor {
|
||||
std::shared_ptr<ExecutorDelegate> delegate,
|
||||
std::shared_ptr<MessageQueueThread> jsQueue,
|
||||
const JSIScopedTimeoutInvoker& timeoutInvoker,
|
||||
RuntimeInstaller runtimeInstaller);
|
||||
RuntimeInstaller runtimeInstaller,
|
||||
hermes::HermesRuntime& hermesRuntime);
|
||||
|
||||
virtual std::unique_ptr<jsinspector_modern::RuntimeAgent> createRuntimeAgent(
|
||||
jsinspector_modern::FrontendChannel frontendChannel,
|
||||
jsinspector_modern::SessionState& sessionState) override;
|
||||
|
||||
private:
|
||||
JSIScopedTimeoutInvoker timeoutInvoker_;
|
||||
std::shared_ptr<MessageQueueThread> jsQueue_;
|
||||
std::shared_ptr<jsi::Runtime> runtime_;
|
||||
hermes::HermesRuntime& hermesRuntime_;
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
@@ -33,4 +33,5 @@ target_link_libraries(hermes_inspector_modern
|
||||
fb
|
||||
glog
|
||||
hermes-engine::libhermes
|
||||
jsi)
|
||||
jsi
|
||||
runtimeexecutor)
|
||||
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* 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 "HermesRuntimeAgent.h"
|
||||
|
||||
// If HERMES_ENABLE_DEBUGGER isn't defined, we can't access any Hermes
|
||||
// CDPHandler headers or types.
|
||||
|
||||
#ifdef HERMES_ENABLE_DEBUGGER
|
||||
#include <hermes/inspector/RuntimeAdapter.h>
|
||||
#include <hermes/inspector/chrome/CDPHandler.h>
|
||||
#else // HERMES_ENABLE_DEBUGGER
|
||||
#include <jsinspector-modern/FallbackRuntimeAgent.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
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* An implementation of the Hermes RuntimeAdapter interface (part of
|
||||
* Hermes's CDPHandler API) for use within a React Native RuntimeAgent.
|
||||
*/
|
||||
class HermesRuntimeAgentAdapter
|
||||
: public hermes::inspector_modern::RuntimeAdapter {
|
||||
public:
|
||||
HermesRuntimeAgentAdapter(
|
||||
std::shared_ptr<hermes::HermesRuntime> runtime,
|
||||
RuntimeExecutor runtimeExecutor)
|
||||
: runtime_(runtime), runtimeExecutor_(runtimeExecutor) {}
|
||||
|
||||
HermesRuntime& getRuntime() override {
|
||||
return *runtime_;
|
||||
}
|
||||
|
||||
void tickleJs() override {
|
||||
runtimeExecutor_([](jsi::Runtime& runtime) {
|
||||
jsi::Function func =
|
||||
runtime.global().getPropertyAsFunction(runtime, "__tickleJs");
|
||||
func.call(runtime);
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<hermes::HermesRuntime> runtime_;
|
||||
RuntimeExecutor runtimeExecutor_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* A RuntimeAgent that handles requests from the Chrome DevTools Protocol for
|
||||
* an instance of Hermes.
|
||||
*/
|
||||
class HermesRuntimeAgent::Impl final : public RuntimeAgent {
|
||||
using HermesCDPHandler = hermes::inspector_modern::chrome::CDPHandler;
|
||||
|
||||
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 runtime The HermesRuntime that this agent is attached to.
|
||||
* \param runtimeExecutor A callback for scheduling work on the JS thread.
|
||||
* \c runtimeExecutor may drop scheduled work if the runtime is destroyed
|
||||
* first.
|
||||
*/
|
||||
Impl(
|
||||
FrontendChannel frontendChannel,
|
||||
SessionState& sessionState,
|
||||
std::shared_ptr<hermes::HermesRuntime> runtime,
|
||||
RuntimeExecutor runtimeExecutor)
|
||||
: hermes_(HermesCDPHandler::create(
|
||||
std::make_unique<HermesRuntimeAgentAdapter>(
|
||||
runtime,
|
||||
runtimeExecutor),
|
||||
/* waitForDebugger */ false,
|
||||
/* enableConsoleAPICapturing */ false,
|
||||
/* state */ nullptr,
|
||||
{.isRuntimeDomainEnabled = sessionState.isRuntimeDomainEnabled})) {
|
||||
hermes_->registerCallbacks(
|
||||
/* msgCallback */
|
||||
[frontendChannel =
|
||||
std::move(frontendChannel)](const std::string& messageFromHermes) {
|
||||
frontendChannel(messageFromHermes);
|
||||
;
|
||||
},
|
||||
/* onUnregister */
|
||||
[]() {});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 CDPHandler.
|
||||
hermes_->handle(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::shared_ptr<HermesCDPHandler> hermes_;
|
||||
};
|
||||
|
||||
#else // !HERMES_ENABLE_DEBUGGER
|
||||
|
||||
/**
|
||||
* A stub for HermesRuntimeAgent when Hermes is compiled without debugging
|
||||
* support.
|
||||
*/
|
||||
class HermesRuntimeAgent::Impl final : public FallbackRuntimeAgent {
|
||||
public:
|
||||
Impl(
|
||||
FrontendChannel frontendChannel,
|
||||
SessionState& sessionState,
|
||||
std::shared_ptr<hermes::HermesRuntime> runtime,
|
||||
RuntimeExecutor)
|
||||
: FallbackRuntimeAgent(
|
||||
std::move(frontendChannel),
|
||||
sessionState,
|
||||
runtime->description()) {}
|
||||
};
|
||||
|
||||
#endif // HERMES_ENABLE_DEBUGGER
|
||||
|
||||
HermesRuntimeAgent::HermesRuntimeAgent(
|
||||
FrontendChannel frontendChannel,
|
||||
SessionState& sessionState,
|
||||
std::shared_ptr<hermes::HermesRuntime> runtime,
|
||||
RuntimeExecutor runtimeExecutor)
|
||||
: impl_(std::make_unique<Impl>(
|
||||
std::move(frontendChannel),
|
||||
sessionState,
|
||||
std::move(runtime),
|
||||
std::move(runtimeExecutor))) {}
|
||||
|
||||
bool HermesRuntimeAgent::handleRequest(const cdp::PreparsedRequest& req) {
|
||||
return impl_->handleRequest(req);
|
||||
}
|
||||
|
||||
} // namespace facebook::react::jsinspector_modern
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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 <ReactCommon/RuntimeExecutor.h>
|
||||
|
||||
#include <hermes/hermes.h>
|
||||
#include <jsinspector-modern/ReactCdp.h>
|
||||
|
||||
namespace facebook::react::jsinspector_modern {
|
||||
|
||||
/**
|
||||
* A RuntimeAgent that handles requests from the Chrome DevTools Protocol for
|
||||
* an instance of Hermes.
|
||||
*/
|
||||
class HermesRuntimeAgent : public RuntimeAgent {
|
||||
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 runtime The HermesRuntime that this agent is attached to.
|
||||
* \param runtimeExecutor A callback for scheduling work on the JS thread.
|
||||
* \c runtimeExecutor may drop scheduled work if the runtime is destroyed
|
||||
* first.
|
||||
*/
|
||||
HermesRuntimeAgent(
|
||||
FrontendChannel frontendChannel,
|
||||
SessionState& sessionState,
|
||||
std::shared_ptr<hermes::HermesRuntime> runtime,
|
||||
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
|
||||
@@ -19,4 +19,14 @@ PreparsedRequest preparse(std::string_view message) {
|
||||
.params = parsed.count("params") ? parsed["params"] : nullptr};
|
||||
}
|
||||
|
||||
std::string PreparsedRequest::toJson() const {
|
||||
folly::dynamic obj = folly::dynamic::object;
|
||||
obj["id"] = id;
|
||||
obj["method"] = method;
|
||||
if (params != nullptr) {
|
||||
obj["params"] = params;
|
||||
}
|
||||
return folly::toJson(obj);
|
||||
}
|
||||
|
||||
} // namespace facebook::react::jsinspector_modern::cdp
|
||||
|
||||
@@ -43,6 +43,8 @@ struct PreparsedRequest {
|
||||
inline bool operator==(const PreparsedRequest& rhs) const {
|
||||
return id == rhs.id && method == rhs.method && params == rhs.params;
|
||||
}
|
||||
|
||||
std::string toJson() const;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -51,8 +51,10 @@ Pod::Spec.new do |s|
|
||||
s.dependency "React-jsi"
|
||||
s.dependency "React-RuntimeCore"
|
||||
s.dependency "React-featureflags"
|
||||
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
|
||||
|
||||
if ENV["USE_HERMES"] == nil || ENV["USE_HERMES"] == "1"
|
||||
s.dependency "React-hermes"
|
||||
s.dependency "hermes-engine"
|
||||
else
|
||||
s.dependency "React-jsc"
|
||||
|
||||
@@ -24,6 +24,7 @@ target_link_libraries(bridgelesshermes
|
||||
hermes_executor_common
|
||||
bridgeless
|
||||
react_featureflags
|
||||
jsinspector
|
||||
)
|
||||
|
||||
if(${CMAKE_BUILD_TYPE} MATCHES Debug)
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "HermesInstance.h"
|
||||
|
||||
#include <hermes/inspector-modern/chrome/HermesRuntimeAgent.h>
|
||||
#include <jsi/jsilib.h>
|
||||
#include <jsinspector-modern/InspectorFlags.h>
|
||||
#include <react/featureflags/ReactNativeFeatureFlags.h>
|
||||
@@ -91,6 +92,47 @@ class DecoratedRuntime : public jsi::RuntimeDecorator<jsi::Runtime> {
|
||||
|
||||
#endif
|
||||
|
||||
class HermesJSRuntime : public JSRuntime {
|
||||
public:
|
||||
HermesJSRuntime(
|
||||
std::unique_ptr<HermesRuntime> runtime,
|
||||
std::shared_ptr<MessageQueueThread> msgQueueThread)
|
||||
: runtime_(std::move(runtime)),
|
||||
msgQueueThread_(std::move(msgQueueThread)) {}
|
||||
|
||||
jsi::Runtime& getRuntime() noexcept override {
|
||||
return *runtime_;
|
||||
}
|
||||
|
||||
std::unique_ptr<jsinspector_modern::RuntimeAgent> createInspectorAgent(
|
||||
jsinspector_modern::FrontendChannel frontendChannel,
|
||||
jsinspector_modern::SessionState& sessionState) override {
|
||||
return std::unique_ptr<jsinspector_modern::RuntimeAgent>(
|
||||
new jsinspector_modern::HermesRuntimeAgent(
|
||||
frontendChannel,
|
||||
sessionState,
|
||||
runtime_,
|
||||
[msgQueueThreadWeak = std::weak_ptr(msgQueueThread_),
|
||||
runtimeWeak = std::weak_ptr(runtime_)](auto fn) {
|
||||
auto msgQueueThread = msgQueueThreadWeak.lock();
|
||||
if (!msgQueueThread) {
|
||||
return;
|
||||
}
|
||||
msgQueueThread->runOnQueue([runtimeWeak, fn]() {
|
||||
auto runtime = runtimeWeak.lock();
|
||||
if (!runtime) {
|
||||
return;
|
||||
}
|
||||
fn(*runtime);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<HermesRuntime> runtime_;
|
||||
std::shared_ptr<MessageQueueThread> msgQueueThread_;
|
||||
};
|
||||
|
||||
std::unique_ptr<JSRuntime> HermesInstance::createJSRuntime(
|
||||
std::shared_ptr<const ReactNativeConfig> reactNativeConfig,
|
||||
std::shared_ptr<::hermes::vm::CrashManager> cm,
|
||||
@@ -142,7 +184,8 @@ std::unique_ptr<JSRuntime> HermesInstance::createJSRuntime(
|
||||
}
|
||||
#endif
|
||||
|
||||
return std::make_unique<JSIRuntimeHolder>(std::move(hermesRuntime));
|
||||
return std::make_unique<HermesJSRuntime>(
|
||||
std::move(hermesRuntime), std::move(msgQueueThread));
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
Reference in New Issue
Block a user