153 lines
5.8 KiB
C++
153 lines
5.8 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 "RuntimeAgent.h"
|
||
|
#include "SessionState.h"
|
||
|
|
||
|
namespace facebook::react::jsinspector_modern {
|
||
|
|
||
|
RuntimeAgent::RuntimeAgent(
|
||
|
FrontendChannel frontendChannel,
|
||
|
RuntimeTargetController& targetController,
|
||
|
const ExecutionContextDescription& executionContextDescription,
|
||
|
SessionState& sessionState,
|
||
|
std::unique_ptr<RuntimeAgentDelegate> delegate)
|
||
|
: frontendChannel_(std::move(frontendChannel)),
|
||
|
targetController_(targetController),
|
||
|
sessionState_(sessionState),
|
||
|
delegate_(std::move(delegate)),
|
||
|
executionContextDescription_(executionContextDescription) {
|
||
|
for (auto& [name, contextSelectors] : sessionState_.subscribedBindings) {
|
||
|
if (matchesAny(executionContextDescription_, contextSelectors)) {
|
||
|
targetController_.installBindingHandler(name);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (sessionState_.isRuntimeDomainEnabled &&
|
||
|
sessionState_.isLogDomainEnabled) {
|
||
|
targetController_.notifyDebuggerSessionCreated();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool RuntimeAgent::handleRequest(const cdp::PreparsedRequest& req) {
|
||
|
if (req.method == "Runtime.addBinding") {
|
||
|
std::string bindingName = req.params["name"].getString();
|
||
|
|
||
|
ExecutionContextSelector contextSelector = ExecutionContextSelector::all();
|
||
|
|
||
|
// TODO: Eventually, move execution context targeting out of RuntimeAgent.
|
||
|
// Right now, there's only ever one context (Runtime) in a Host, so we can
|
||
|
// handle it here for simplicity, and use session state to propagate
|
||
|
// bindings to the next RuntimeAgent.
|
||
|
if (req.params.count("executionContextId")) {
|
||
|
auto executionContextId = req.params["executionContextId"].getInt();
|
||
|
if (executionContextId < (int64_t)std::numeric_limits<int32_t>::min() ||
|
||
|
executionContextId > (int64_t)std::numeric_limits<int32_t>::max()) {
|
||
|
frontendChannel_(cdp::jsonError(
|
||
|
req.id,
|
||
|
cdp::ErrorCode::InvalidParams,
|
||
|
"Invalid execution context id"));
|
||
|
return true;
|
||
|
}
|
||
|
contextSelector =
|
||
|
ExecutionContextSelector::byId((int32_t)executionContextId);
|
||
|
|
||
|
if (req.params.count("executionContextName")) {
|
||
|
frontendChannel_(cdp::jsonError(
|
||
|
req.id,
|
||
|
cdp::ErrorCode::InvalidParams,
|
||
|
"executionContextName is mutually exclusive with executionContextId"));
|
||
|
return true;
|
||
|
}
|
||
|
} else if (req.params.count("executionContextName")) {
|
||
|
contextSelector = ExecutionContextSelector::byName(
|
||
|
req.params["executionContextName"].getString());
|
||
|
}
|
||
|
if (contextSelector.matches(executionContextDescription_)) {
|
||
|
targetController_.installBindingHandler(bindingName);
|
||
|
}
|
||
|
sessionState_.subscribedBindings[bindingName].insert(contextSelector);
|
||
|
|
||
|
frontendChannel_(cdp::jsonResult(req.id));
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
if (req.method == "Runtime.removeBinding") {
|
||
|
// @cdp Runtime.removeBinding has no targeting by execution context. We
|
||
|
// interpret it to mean "unsubscribe, and stop installing the binding on
|
||
|
// all new contexts". This diverges slightly from V8, which continues
|
||
|
// to install the binding on new contexts after it's "removed", but *only*
|
||
|
// if the subscription is targeted by context name.
|
||
|
sessionState_.subscribedBindings.erase(req.params["name"].getString());
|
||
|
|
||
|
frontendChannel_(cdp::jsonResult(req.id));
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
if (req.method == "Runtime.enable" && sessionState_.isLogDomainEnabled) {
|
||
|
targetController_.notifyDebuggerSessionCreated();
|
||
|
}
|
||
|
if (req.method == "Log.enable" && sessionState_.isRuntimeDomainEnabled) {
|
||
|
targetController_.notifyDebuggerSessionCreated();
|
||
|
}
|
||
|
if (req.method == "Runtime.disable" && sessionState_.isLogDomainEnabled) {
|
||
|
targetController_.notifyDebuggerSessionDestroyed();
|
||
|
}
|
||
|
if (req.method == "Log.disable" && sessionState_.isRuntimeDomainEnabled) {
|
||
|
targetController_.notifyDebuggerSessionDestroyed();
|
||
|
}
|
||
|
if (delegate_) {
|
||
|
return delegate_->handleRequest(req);
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void RuntimeAgent::notifyBindingCalled(
|
||
|
const std::string& bindingName,
|
||
|
const std::string& payload) {
|
||
|
// NOTE: When dispatching @cdp Runtime.bindingCalled notifications, we don't
|
||
|
// re-check whether the session is expecting notifications from the current
|
||
|
// context - only that it's subscribed to that binding name.
|
||
|
// Theoretically, this can result in over-sending notifications from contexts
|
||
|
// that the client no longer cares about, or never cared about to begin with
|
||
|
// (e.g. if the binding handler was installed by a previous session).
|
||
|
//
|
||
|
// React Native intentionally replicates this behavior for the sake of
|
||
|
// bug-for-bug compatibility with Chrome, but clients should probably not rely
|
||
|
// on it.
|
||
|
if (!sessionState_.subscribedBindings.count(bindingName)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
frontendChannel_(cdp::jsonNotification(
|
||
|
"Runtime.bindingCalled",
|
||
|
folly::dynamic::object(
|
||
|
"executionContextId", executionContextDescription_.id)(
|
||
|
"name", bindingName)("payload", payload)));
|
||
|
}
|
||
|
|
||
|
RuntimeAgent::ExportedState RuntimeAgent::getExportedState() {
|
||
|
return {
|
||
|
.delegateState = delegate_ ? delegate_->getExportedState() : nullptr,
|
||
|
};
|
||
|
}
|
||
|
|
||
|
RuntimeAgent::~RuntimeAgent() {
|
||
|
if (sessionState_.isRuntimeDomainEnabled &&
|
||
|
sessionState_.isLogDomainEnabled) {
|
||
|
targetController_.notifyDebuggerSessionDestroyed();
|
||
|
}
|
||
|
|
||
|
// 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
|