Save exported state across RuntimeAgent instances (#43098)

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

Changelog: [Internal]

Wraps Hermes's `CDPHandler::getState()` API in an engine-agnostic abstraction (`RuntimeAgentDelegate::getExportedState`).

An Agent's lifetime ends when its Target is destroyed, but it can occasionally be useful to persist some state for the "next" Target+Agent of the same type (in the same session) to read.

`RuntimeAgentDelegate` is polymorphic and can't just write arbitrary data to SessionState. Instead, it can now *export* a state object that we'll store and pass to the next `RuntimeTargetDelegate::createAgentDelegate` call.

Reviewed By: huntie

Differential Revision: D53919696

fbshipit-source-id: a8e9b921bc8fc2d195c5dddea9537e6ead3d0358
This commit is contained in:
Moti Zilberman
2024-02-20 02:21:31 -08:00
committed by Facebook GitHub Bot
parent 01d704dd45
commit e6995583d3
21 changed files with 141 additions and 6 deletions
@@ -39,6 +39,7 @@ std::unique_ptr<jsinspector_modern::RuntimeAgentDelegate>
JSExecutor::createAgentDelegate(
jsinspector_modern::FrontendChannel frontendChannel,
jsinspector_modern::SessionState& sessionState,
std::unique_ptr<jsinspector_modern::RuntimeAgentDelegate::ExportedState>,
const jsinspector_modern::ExecutionContextDescription&
executionContextDescription) {
(void)executionContextDescription;
@@ -147,6 +147,8 @@ class RN_EXPORT JSExecutor : public jsinspector_modern::RuntimeTargetDelegate {
createAgentDelegate(
jsinspector_modern::FrontendChannel frontendChannel,
jsinspector_modern::SessionState& sessionState,
std::unique_ptr<jsinspector_modern::RuntimeAgentDelegate::ExportedState>
previouslyExportedState,
const jsinspector_modern::ExecutionContextDescription&
executionContextDescription) override;
};
@@ -261,6 +261,8 @@ std::unique_ptr<jsinspector_modern::RuntimeAgentDelegate>
HermesExecutor::createAgentDelegate(
jsinspector_modern::FrontendChannel frontendChannel,
jsinspector_modern::SessionState& sessionState,
std::unique_ptr<jsinspector_modern::RuntimeAgentDelegate::ExportedState>
previouslyExportedState,
const jsinspector_modern::ExecutionContextDescription&
executionContextDescription) {
std::shared_ptr<HermesRuntime> hermesRuntimeShared(runtime_, &hermesRuntime_);
@@ -268,6 +270,7 @@ HermesExecutor::createAgentDelegate(
new jsinspector_modern::HermesRuntimeAgentDelegate(
frontendChannel,
sessionState,
std::move(previouslyExportedState),
executionContextDescription,
hermesRuntimeShared,
[jsQueueWeak = std::weak_ptr(jsQueue_),
@@ -58,6 +58,8 @@ class HermesExecutor : public JSIExecutor {
createAgentDelegate(
jsinspector_modern::FrontendChannel frontendChannel,
jsinspector_modern::SessionState& sessionState,
std::unique_ptr<jsinspector_modern::RuntimeAgentDelegate::ExportedState>
previouslyExportedState,
const jsinspector_modern::ExecutionContextDescription&
executionContextDescription) override;
@@ -67,6 +67,26 @@ class HermesRuntimeAgentDelegate::Impl final : public RuntimeAgentDelegate {
using HermesCDPHandler = hermes::inspector_modern::chrome::CDPHandler;
using HermesExecutionContextDescription =
hermes::inspector_modern::chrome::CDPHandlerExecutionContextDescription;
using HermesState = hermes::inspector_modern::chrome::State;
struct HermesStateWrapper : public ExportedState {
explicit HermesStateWrapper(std::unique_ptr<HermesState> state)
: state_(std::move(state)) {}
static std::unique_ptr<HermesState> unwrapDestructively(
ExportedState* wrapper) {
if (!wrapper) {
return nullptr;
}
if (auto* typedWrapper = dynamic_cast<HermesStateWrapper*>(wrapper)) {
return std::move(typedWrapper->state_);
}
return nullptr;
}
private:
std::unique_ptr<HermesState> state_;
};
public:
/**
@@ -75,6 +95,10 @@ class HermesRuntimeAgentDelegate::Impl final : public RuntimeAgentDelegate {
* \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 HermesRuntimeAgentDelegate). 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
@@ -87,6 +111,8 @@ class HermesRuntimeAgentDelegate::Impl final : public RuntimeAgentDelegate {
Impl(
FrontendChannel frontendChannel,
SessionState& sessionState,
std::unique_ptr<RuntimeAgentDelegate::ExportedState>
previouslyExportedState,
const ExecutionContextDescription& executionContextDescription,
std::shared_ptr<hermes::HermesRuntime> runtime,
RuntimeExecutor runtimeExecutor)
@@ -96,7 +122,9 @@ class HermesRuntimeAgentDelegate::Impl final : public RuntimeAgentDelegate {
runtimeExecutor),
/* waitForDebugger */ false,
/* enableConsoleAPICapturing */ false,
/* state */ nullptr,
/* state */
HermesStateWrapper::unwrapDestructively(
previouslyExportedState.get()),
{.isRuntimeDomainEnabled = sessionState.isRuntimeDomainEnabled},
HermesExecutionContextDescription{
.id = executionContextDescription.id,
@@ -139,6 +167,10 @@ class HermesRuntimeAgentDelegate::Impl final : public RuntimeAgentDelegate {
return true;
}
virtual std::unique_ptr<ExportedState> getExportedState() override {
return std::make_unique<HermesStateWrapper>(hermes_->getState());
}
private:
std::shared_ptr<HermesCDPHandler> hermes_;
};
@@ -155,6 +187,7 @@ class HermesRuntimeAgentDelegate::Impl final
Impl(
FrontendChannel frontendChannel,
SessionState& sessionState,
std::unique_ptr<RuntimeAgentDelegate::ExportedState>,
const ExecutionContextDescription&,
std::shared_ptr<hermes::HermesRuntime> runtime,
RuntimeExecutor)
@@ -169,12 +202,15 @@ class HermesRuntimeAgentDelegate::Impl final
HermesRuntimeAgentDelegate::HermesRuntimeAgentDelegate(
FrontendChannel frontendChannel,
SessionState& sessionState,
std::unique_ptr<RuntimeAgentDelegate::ExportedState>
previouslyExportedState,
const ExecutionContextDescription& executionContextDescription,
std::shared_ptr<hermes::HermesRuntime> runtime,
RuntimeExecutor runtimeExecutor)
: impl_(std::make_unique<Impl>(
std::move(frontendChannel),
sessionState,
std::move(previouslyExportedState),
executionContextDescription,
std::move(runtime),
std::move(runtimeExecutor))) {}
@@ -184,4 +220,9 @@ bool HermesRuntimeAgentDelegate::handleRequest(
return impl_->handleRequest(req);
}
std::unique_ptr<HermesRuntimeAgentDelegate::ExportedState>
HermesRuntimeAgentDelegate::getExportedState() {
return impl_->getExportedState();
}
} // namespace facebook::react::jsinspector_modern
@@ -26,6 +26,10 @@ class HermesRuntimeAgentDelegate : public RuntimeAgentDelegate {
* \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 HermesRuntimeAgentDelegate). 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
@@ -38,6 +42,8 @@ class HermesRuntimeAgentDelegate : public RuntimeAgentDelegate {
HermesRuntimeAgentDelegate(
FrontendChannel frontendChannel,
SessionState& sessionState,
std::unique_ptr<RuntimeAgentDelegate::ExportedState>
previouslyExportedState,
const ExecutionContextDescription& executionContextDescription,
std::shared_ptr<hermes::HermesRuntime> runtime,
RuntimeExecutor runtimeExecutor);
@@ -52,6 +58,8 @@ class HermesRuntimeAgentDelegate : public RuntimeAgentDelegate {
*/
bool handleRequest(const cdp::PreparsedRequest& req) override;
virtual std::unique_ptr<ExportedState> getExportedState() override;
private:
// We use the private implementation idiom to keep HERMES_ENABLE_DEBUGGER
// checks out of the header.
@@ -6,6 +6,7 @@
*/
#include "RuntimeAgent.h"
#include "SessionState.h"
namespace facebook::react::jsinspector_modern {
@@ -120,4 +121,18 @@ void RuntimeAgent::notifyBindingCalled(
"name", bindingName)("payload", payload))));
}
RuntimeAgent::ExportedState RuntimeAgent::getExportedState() {
return {
.delegateState = delegate_ ? delegate_->getExportedState() : nullptr,
};
}
RuntimeAgent::~RuntimeAgent() {
// TODO: Eventually, there may be more than one Runtime per Page, and we'll
// need to store multiple agent states here accordingly. For now let's do
// the simple thing and assume (as we do elsewhere) that only one Runtime
// per Page can exist at a time.
sessionState_.lastRuntimeAgentExportedState = getExportedState();
}
} // namespace facebook::react::jsinspector_modern
@@ -10,13 +10,13 @@
#include "InspectorInterfaces.h"
#include "RuntimeAgentDelegate.h"
#include "RuntimeTarget.h"
#include "SessionState.h"
#include <jsinspector-modern/Parsing.h>
namespace facebook::react::jsinspector_modern {
class RuntimeTargetController;
struct SessionState;
/**
* An Agent that handles requests from the Chrome DevTools Protocol
@@ -48,6 +48,8 @@ class RuntimeAgent final {
SessionState& sessionState,
std::unique_ptr<RuntimeAgentDelegate> delegate);
~RuntimeAgent();
/**
* Handle a CDP request. The response will be sent over the provided
* \c FrontendChannel synchronously or asynchronously. Performs any
@@ -69,6 +71,17 @@ class RuntimeAgent final {
const std::string& bindingName,
const std::string& payload);
struct ExportedState {
std::unique_ptr<RuntimeAgentDelegate::ExportedState> delegateState;
};
/**
* Export the RuntimeAgent's state, if available. This will be called
* shortly before the RuntimeAgent is destroyed to preserve state that may be
* needed when constructin a new RuntimeAgent.
*/
ExportedState getExportedState();
private:
FrontendChannel frontendChannel_;
RuntimeTargetController& targetController_;
@@ -19,6 +19,11 @@ namespace facebook::react::jsinspector_modern {
*/
class RuntimeAgentDelegate {
public:
class ExportedState {
public:
virtual ~ExportedState() = default;
};
virtual ~RuntimeAgentDelegate() = default;
/**
@@ -30,6 +35,17 @@ class RuntimeAgentDelegate {
* agent expects another agent to respond to the request instead.
*/
virtual bool handleRequest(const cdp::PreparsedRequest& req) = 0;
/**
* Export RuntimeAgentDelegate-specific state that should persist across
* consecutive RuntimeTargets in this session.
* If the RuntimeTarget is destroyed and later logically replaced by a new
* one (e.g. as part of an Instance reload), the state returned here will be
* passed to \ref RuntimeTargetDelegate::createAgentDelegate.
*/
inline virtual std::unique_ptr<ExportedState> getExportedState() {
return std::make_unique<ExportedState>();
}
};
} // namespace facebook::react::jsinspector_modern
@@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
#include "SessionState.h"
#include <jsinspector-modern/RuntimeTarget.h>
using namespace facebook::jsi;
@@ -33,13 +35,18 @@ RuntimeTarget::RuntimeTarget(
std::shared_ptr<RuntimeAgent> RuntimeTarget::createAgent(
FrontendChannel channel,
SessionState& sessionState) {
auto runtimeAgentState =
std::move(sessionState.lastRuntimeAgentExportedState);
auto runtimeAgent = std::make_shared<RuntimeAgent>(
channel,
controller_,
executionContextDescription_,
sessionState,
delegate_.createAgentDelegate(
channel, sessionState, executionContextDescription_));
channel,
sessionState,
std::move(runtimeAgentState.delegateState),
executionContextDescription_));
agents_.insert(runtimeAgent);
return runtimeAgent;
}
@@ -13,7 +13,6 @@
#include "InspectorInterfaces.h"
#include "RuntimeAgent.h"
#include "ScopedExecutor.h"
#include "SessionState.h"
#include "WeakList.h"
#include <memory>
@@ -35,6 +34,7 @@ namespace facebook::react::jsinspector_modern {
class RuntimeAgent;
class RuntimeAgentDelegate;
class RuntimeTarget;
struct SessionState;
/**
* Receives events from a RuntimeTarget. This is a shared interface that
@@ -47,6 +47,8 @@ class RuntimeTargetDelegate {
virtual std::unique_ptr<RuntimeAgentDelegate> createAgentDelegate(
FrontendChannel channel,
SessionState& sessionState,
std::unique_ptr<RuntimeAgentDelegate::ExportedState>
previouslyExportedState,
const ExecutionContextDescription& executionContextDescription) = 0;
};
@@ -8,6 +8,7 @@
#pragma once
#include "ExecutionContext.h"
#include "RuntimeAgent.h"
#include <string>
#include <string_view>
@@ -34,6 +35,12 @@ struct SessionState {
std::unordered_map<std::string, ExecutionContextSelectorSet>
subscribedBindings;
/**
* Stores the state object exported from the last main RuntimeAgent, if any,
* before it was destroyed.
*/
RuntimeAgent::ExportedState lastRuntimeAgentExportedState;
// Here, we will eventually allow RuntimeAgents to store their own arbitrary
// state (e.g. some sort of K/V storage of folly::dynamic?)
@@ -131,6 +131,8 @@ class MockRuntimeTargetDelegate : public RuntimeTargetDelegate {
createAgentDelegate,
(FrontendChannel channel,
SessionState& sessionState,
std::unique_ptr<RuntimeAgentDelegate::ExportedState>
previouslyExportedState,
const ExecutionContextDescription&),
(override));
};
@@ -140,6 +142,7 @@ class MockRuntimeAgentDelegate : public RuntimeAgentDelegate {
inline MockRuntimeAgentDelegate(
FrontendChannel frontendChannel,
SessionState& sessionState,
std::unique_ptr<RuntimeAgentDelegate::ExportedState>,
const ExecutionContextDescription& executionContextDescription)
: frontendChannel(std::move(frontendChannel)),
sessionState(sessionState),
@@ -31,10 +31,11 @@ class PageTargetTest : public Test {
protected:
PageTargetTest() {
EXPECT_CALL(runtimeTargetDelegate_, createAgentDelegate(_, _, _))
EXPECT_CALL(runtimeTargetDelegate_, createAgentDelegate(_, _, _, _))
.WillRepeatedly(runtimeAgentDelegates_.lazily_make_unique<
FrontendChannel,
SessionState&,
std::unique_ptr<RuntimeAgentDelegate::ExportedState>,
const ExecutionContextDescription&>());
}
@@ -444,7 +445,7 @@ TEST_F(PageTargetProtocolTest, MessageRoutingWhileNoRuntimeAgentDelegate) {
TEST_F(PageTargetProtocolTest, InstanceWithNullRuntimeAgentDelegate) {
InSequence s;
EXPECT_CALL(runtimeTargetDelegate_, createAgentDelegate(_, _, _))
EXPECT_CALL(runtimeTargetDelegate_, createAgentDelegate(_, _, _, _))
.WillRepeatedly(ReturnNull());
auto& instanceTarget = page_->registerInstance(instanceTargetDelegate_);
@@ -24,6 +24,7 @@ std::unique_ptr<RuntimeAgentDelegate>
JsiIntegrationTestGenericEngineAdapter::createAgentDelegate(
FrontendChannel frontendChannel,
SessionState& sessionState,
std::unique_ptr<RuntimeAgentDelegate::ExportedState>,
const ExecutionContextDescription&) {
return std::unique_ptr<jsinspector_modern::RuntimeAgentDelegate>(
new FallbackRuntimeAgentDelegate(
@@ -28,6 +28,8 @@ class JsiIntegrationTestGenericEngineAdapter : public RuntimeTargetDelegate {
virtual std::unique_ptr<RuntimeAgentDelegate> createAgentDelegate(
FrontendChannel frontendChannel,
SessionState& sessionState,
std::unique_ptr<RuntimeAgentDelegate::ExportedState>
previouslyExportedState,
const ExecutionContextDescription& executionContextDescription) override;
jsi::Runtime& getRuntime() const noexcept;
@@ -23,11 +23,14 @@ std::unique_ptr<RuntimeAgentDelegate>
JsiIntegrationTestHermesEngineAdapter::createAgentDelegate(
FrontendChannel frontendChannel,
SessionState& sessionState,
std::unique_ptr<RuntimeAgentDelegate::ExportedState>
previouslyExportedState,
const ExecutionContextDescription& executionContextDescription) {
return std::unique_ptr<jsinspector_modern::RuntimeAgentDelegate>(
new HermesRuntimeAgentDelegate(
frontendChannel,
sessionState,
std::move(previouslyExportedState),
executionContextDescription,
runtime_,
getRuntimeExecutor()));
@@ -28,6 +28,8 @@ class JsiIntegrationTestHermesEngineAdapter : public RuntimeTargetDelegate {
virtual std::unique_ptr<RuntimeAgentDelegate> createAgentDelegate(
FrontendChannel frontendChannel,
SessionState& sessionState,
std::unique_ptr<RuntimeAgentDelegate::ExportedState>
previouslyExportedState,
const ExecutionContextDescription& executionContextDescription) override;
jsi::Runtime& getRuntime() const noexcept;
@@ -22,6 +22,7 @@ std::unique_ptr<jsinspector_modern::RuntimeAgentDelegate>
JSIRuntimeHolder::createAgentDelegate(
jsinspector_modern::FrontendChannel frontendChannel,
jsinspector_modern::SessionState& sessionState,
std::unique_ptr<jsinspector_modern::RuntimeAgentDelegate::ExportedState>,
const jsinspector_modern::ExecutionContextDescription&
executionContextDescription) {
(void)executionContextDescription;
@@ -44,6 +44,8 @@ class JSIRuntimeHolder : public JSRuntime {
std::unique_ptr<jsinspector_modern::RuntimeAgentDelegate> createAgentDelegate(
jsinspector_modern::FrontendChannel frontendChannel,
jsinspector_modern::SessionState& sessionState,
std::unique_ptr<jsinspector_modern::RuntimeAgentDelegate::ExportedState>
previouslyExportedState,
const jsinspector_modern::ExecutionContextDescription&
executionContextDescription) override;
@@ -107,12 +107,15 @@ class HermesJSRuntime : public JSRuntime {
std::unique_ptr<jsinspector_modern::RuntimeAgentDelegate> createAgentDelegate(
jsinspector_modern::FrontendChannel frontendChannel,
jsinspector_modern::SessionState& sessionState,
std::unique_ptr<jsinspector_modern::RuntimeAgentDelegate::ExportedState>
previouslyExportedState,
const jsinspector_modern::ExecutionContextDescription&
executionContextDescription) override {
return std::unique_ptr<jsinspector_modern::RuntimeAgentDelegate>(
new jsinspector_modern::HermesRuntimeAgentDelegate(
frontendChannel,
sessionState,
std::move(previouslyExportedState),
executionContextDescription,
runtime_,
[msgQueueThreadWeak = std::weak_ptr(msgQueueThread_),