Files
react-native/packages/react-native/ReactCommon/jsinspector-modern/PageTarget.cpp
T
Moti Zilberman 7886abd243 Add stub page target implementation to jsinspector-modern (#42397)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/42397

Changelog: [Internal]

Adds a stub `PageTarget` class to serve as the entry point to the modern CDP backend in React Native.

The primary method exposed by `PageTarget` is `connect()` which is designed to fit directly as a connection callback passed to `InspectorPackagerConnection::addPage()`. This constructs a `PageTargetSession` containing a `PageAgent` where the actual CDP message handling/routing will occur. For now, `PageAgent` implements no CDP methods, and always responds with a "not implemented" error.

Basic unit tests are included, though we might want to migrate to a more integration-style test suite (with fewer mocks and real bindings to RN) once we've implemented more of the protocol.

## What is a Page

In Chrome's implementation of CDP, a Page represents a single browser tab. A Chrome DevTools session connects to one Page at a time (though it can potentially inspect multiple JavaScript contexts owned by that Page, such as those found in frames and workers).

In our system, a Page will correspond 1:1 to React Native's concept of a *Host* (implemented as `RCTHost`, `RCTBridge`, `ReactHostImpl` or `ReactInstanceManager`, depending on the platform). In all cases, the Host is the object that has a stable identity across reloads, and manages the lifetime of an *Instance* where the JSVM and other application state lives. There can be multiple Hosts in a React Native process, though this is somewhat unusual; those would be treated as independent "tabs" from the perspective of the debugger.

NOTE: The concepts of Target, Session and Agent are new (to this codebase) and are *broadly* inspired by the [corresponding Chromium / V8 concepts](https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/public/devtools_protocol/#Agents_Targets-and-Sessions), though some details differ.

## Next steps

Each core platform implementation in React Native (iOS Bridgeless, iOS Bridge, Android Bridgeless, Android Bridge), as well as out-of-tree platforms that want to support the new debugger, will need to create and register a `PageTarget` instance. We'll do this piecemeal in subsequent diffs.

We'll also gradually add APIs and logic to `PageTarget` / `PageAgent` to allow us to implement some "interesting" CDP methods - some of them directly (e.g. handling reload commands) and others by dispatching to nested agents (e.g. a JS debugging agent powered by Hermes).

Reviewed By: huntie

Differential Revision: D50936932

fbshipit-source-id: ebe5856d7badb361d4971dd9aabeb9982f8aed1b
2024-01-19 12:26:15 -08:00

86 lines
2.6 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 "PageTarget.h"
#include "InspectorUtilities.h"
#include "PageAgent.h"
#include "Parsing.h"
#include <folly/dynamic.h>
#include <folly/json.h>
#include <memory>
namespace facebook::react::jsinspector_modern {
namespace {
/**
* A Session connected to a PageTarget, passing CDP messages to and from a
* PageAgent which it owns.
*/
class PageTargetSession {
public:
explicit PageTargetSession(std::unique_ptr<IRemoteConnection> remote)
: remote_(std::make_shared<RAIIRemoteConnection>(std::move(remote))),
frontendChannel_(
[remoteWeak = std::weak_ptr(remote_)](std::string_view message) {
if (auto remote = remoteWeak.lock()) {
remote->onMessage(std::string(message));
}
}),
pageAgent_(frontendChannel_) {}
/**
* Called by CallbackLocalConnection to send a message to this Session's
* Agent.
*/
void operator()(std::string message) {
cdp::PreparsedRequest request;
// Messages may be invalid JSON, or have unexpected types.
try {
request = cdp::preparse(message);
} catch (const cdp::ParseError& e) {
frontendChannel_(folly::toJson(folly::dynamic::object("id", nullptr)(
"error",
folly::dynamic::object("code", -32700)("message", e.what()))));
return;
} catch (const cdp::TypeError& e) {
frontendChannel_(folly::toJson(folly::dynamic::object("id", nullptr)(
"error",
folly::dynamic::object("code", -32600)("message", e.what()))));
return;
}
// Catch exceptions that may arise from accessing dynamic params during
// request handling.
try {
pageAgent_.handleRequest(request);
} catch (const cdp::TypeError& e) {
frontendChannel_(folly::toJson(folly::dynamic::object("id", request.id)(
"error",
folly::dynamic::object("code", -32600)("message", e.what()))));
return;
}
}
private:
// Owned by this instance, but shared (weakly) with the frontend channel
std::shared_ptr<RAIIRemoteConnection> remote_;
FrontendChannel frontendChannel_;
PageAgent pageAgent_;
};
} // namespace
std::unique_ptr<ILocalConnection> PageTarget::connect(
std::unique_ptr<IRemoteConnection> connectionToFrontend) {
return std::make_unique<CallbackLocalConnection>(
PageTargetSession(std::move(connectionToFrontend)));
}
} // namespace facebook::react::jsinspector_modern