mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/42746 Changelog: [Internal] Formally introduces the concept of "session state" to the modern CDP backend, with the simplest possible implementation: * `PageTargetSession` has a mutable `SessionState` member. * All agents receive the same `SessionState&` in their constructor (with `SessionState`'s lifetime being the caller's responsibility). * It's only legal to read/write to `SessionState` on the thread where requests are handled and Agents are created (the "main" thread). * Agents are expected to play nice and not clobber each other's state in `SessionState`; this is *not* protected with visibility or `const`ness, however. * We'll probably want to come up with some API-level mechanism to control this as the complexity of our agents grows. ## Current use case: `<Domain>.enable` The first use case for session state is to let `PageAgent` manage the `Log.enable` and `Runtime.enable` state for the session. This will allow agents created later in the session (or recreated as part of a reload) to emit Log and Runtime notifications without waiting for additional `enable` messages (that the client is not required to send). We'll likely want to generalise this design to arbitrary domains in some way (e.g. add a top-level domain router that agents register with explicitly?) but I went with the simplest implementation for our current needs. NOTE: The CDP spec doesn't state this explicitly, but it's clear from Chrome's behaviour that a `<Domain>.enable` command is intended to be session-scoped and survive reloads. ## Future use case: Instance/Runtime state persistence The `<Domain>.enable` use case could have been solved with passing *immutable* state to Agents (`const SessionState&`). We make the state mutable in anticipation of `HermesRuntimeAgent` needing to store its own state in the session down the line, which we know is going to be needed in order for breakpoints to survive reloads. Agents that never need to mutate state SHOULD only store this as a const reference. Reviewed By: huntie Differential Revision: D53006916 fbshipit-source-id: a0443c507294faa94efdf25b2f1670129774dc78
154 lines
5.2 KiB
C++
154 lines
5.2 KiB
C++
/*
|
|
* 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 <folly/dynamic.h>
|
|
#include <folly/json.h>
|
|
#include <jsinspector-modern/InstanceAgent.h>
|
|
#include <jsinspector-modern/PageAgent.h>
|
|
#include <jsinspector-modern/PageTarget.h>
|
|
|
|
#include <chrono>
|
|
|
|
using namespace std::chrono;
|
|
using namespace std::literals::string_view_literals;
|
|
|
|
namespace facebook::react::jsinspector_modern {
|
|
|
|
#define ANSI_WEIGHT_BOLD "\x1B[1m"
|
|
#define ANSI_WEIGHT_RESET "\x1B[22m"
|
|
#define ANSI_STYLE_ITALIC "\x1B[3m"
|
|
#define ANSI_STYLE_RESET "\x1B[23m"
|
|
#define ANSI_COLOR_BG_YELLOW "\x1B[48;2;253;247;231m"
|
|
|
|
static constexpr auto kModernCDPBackendNotice =
|
|
ANSI_COLOR_BG_YELLOW ANSI_WEIGHT_BOLD
|
|
"NOTE:" ANSI_WEIGHT_RESET " You are using the " ANSI_STYLE_ITALIC
|
|
"modern" ANSI_STYLE_RESET " CDP backend for React Native (PageTarget)."sv;
|
|
|
|
PageAgent::PageAgent(
|
|
FrontendChannel frontendChannel,
|
|
PageTargetController& targetController,
|
|
PageTarget::SessionMetadata sessionMetadata,
|
|
SessionState& sessionState)
|
|
: frontendChannel_(frontendChannel),
|
|
targetController_(targetController),
|
|
sessionMetadata_(std::move(sessionMetadata)),
|
|
sessionState_(sessionState) {}
|
|
|
|
void PageAgent::handleRequest(const cdp::PreparsedRequest& req) {
|
|
bool shouldSendOKResponse = false;
|
|
bool isFinishedHandlingRequest = false;
|
|
|
|
// Domain enable/disable requests: write to state (because we're the top-level
|
|
// Agent in the Session), trigger any side effects, and decide whether we are
|
|
// finished handling the request (or need to delegate to the InstanceAgent).
|
|
if (req.method == "Log.enable") {
|
|
sessionState_.isLogDomainEnabled = true;
|
|
|
|
// Send a log entry identifying the modern CDP backend.
|
|
sendInfoLogEntry(kModernCDPBackendNotice);
|
|
|
|
// Send a log entry with the integration name.
|
|
if (sessionMetadata_.integrationName) {
|
|
sendInfoLogEntry("Integration: " + *sessionMetadata_.integrationName);
|
|
}
|
|
|
|
shouldSendOKResponse = true;
|
|
isFinishedHandlingRequest = false;
|
|
} else if (req.method == "Log.disable") {
|
|
sessionState_.isLogDomainEnabled = false;
|
|
|
|
shouldSendOKResponse = true;
|
|
isFinishedHandlingRequest = false;
|
|
} else if (req.method == "Runtime.enable") {
|
|
sessionState_.isRuntimeDomainEnabled = true;
|
|
|
|
shouldSendOKResponse = true;
|
|
isFinishedHandlingRequest = false;
|
|
} else if (req.method == "Runtime.disable") {
|
|
sessionState_.isRuntimeDomainEnabled = false;
|
|
|
|
shouldSendOKResponse = true;
|
|
isFinishedHandlingRequest = false;
|
|
}
|
|
// Methods other than domain enables/disables: handle anything we know how
|
|
// to handle, and delegate to the InstanceAgent otherwise.
|
|
else if (req.method == "Page.reload") {
|
|
targetController_.getDelegate().onReload({
|
|
.ignoreCache = req.params.isObject() && req.params.count("ignoreCache")
|
|
? std::optional(req.params.at("ignoreCache").asBool())
|
|
: std::nullopt,
|
|
.scriptToEvaluateOnLoad =
|
|
req.params.isObject() && req.params.count("scriptToEvaluateOnLoad")
|
|
? std::optional(req.params.at("scriptToEvaluateOnLoad").asString())
|
|
: std::nullopt,
|
|
});
|
|
|
|
shouldSendOKResponse = true;
|
|
isFinishedHandlingRequest = true;
|
|
}
|
|
|
|
if (!isFinishedHandlingRequest && instanceAgent_ &&
|
|
instanceAgent_->handleRequest(req)) {
|
|
return;
|
|
}
|
|
|
|
if (shouldSendOKResponse) {
|
|
folly::dynamic res = folly::dynamic::object("id", req.id)(
|
|
"result", folly::dynamic::object());
|
|
std::string json = folly::toJson(res);
|
|
frontendChannel_(json);
|
|
return;
|
|
}
|
|
|
|
folly::dynamic res = folly::dynamic::object("id", req.id)(
|
|
"error",
|
|
folly::dynamic::object("code", -32601)(
|
|
"message", req.method + " not implemented yet"));
|
|
std::string json = folly::toJson(res);
|
|
frontendChannel_(json);
|
|
}
|
|
|
|
void PageAgent::sendInfoLogEntry(std::string_view text) {
|
|
frontendChannel_(
|
|
folly::toJson(folly::dynamic::object("method", "Log.entryAdded")(
|
|
"params",
|
|
folly::dynamic::object(
|
|
"entry",
|
|
folly::dynamic::object(
|
|
"timestamp",
|
|
duration_cast<milliseconds>(
|
|
system_clock::now().time_since_epoch())
|
|
.count())("source", "other")(
|
|
"level", "info")("text", text)))));
|
|
}
|
|
|
|
void PageAgent::setCurrentInstanceAgent(
|
|
std::unique_ptr<InstanceAgent> instanceAgent) {
|
|
auto previousInstanceAgent = std::move(instanceAgent_);
|
|
instanceAgent_ = std::move(instanceAgent);
|
|
if (!sessionState_.isRuntimeDomainEnabled) {
|
|
return;
|
|
}
|
|
if (previousInstanceAgent != nullptr) {
|
|
// TODO: Send Runtime.executionContextDestroyed here - at the moment we
|
|
// expect the runtime to do it for us.
|
|
|
|
// Because we can only have a single instance, we can report all contexts
|
|
// as cleared.
|
|
folly::dynamic contextsCleared =
|
|
folly::dynamic::object("method", "Runtime.executionContextsCleared");
|
|
frontendChannel_(folly::toJson(contextsCleared));
|
|
}
|
|
if (instanceAgent_) {
|
|
// TODO: Send Runtime.executionContextCreated here - at the moment we expect
|
|
// the runtime to do it for us.
|
|
}
|
|
}
|
|
|
|
} // namespace facebook::react::jsinspector_modern
|