Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,9 @@ class HermesRuntimeAgentDelegate::Impl final : public RuntimeAgentDelegate {
}

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 HostAgent will, bail out early.
if (req.method.starts_with("Log.") || req.method.starts_with("Network.")) {
// Since we know Hermes doesn't do anything useful with Log or Network
// messages, but our containing HostAgent will, bail out early.
// TODO: We need a way to negotiate this more dynamically with Hermes
// through the API.
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ class HermesRuntimeTargetDelegate::Impl final : public RuntimeTargetDelegate {
return &hermesStackTrace_;
}

const HermesStackTrace& operator*() const {
return hermesStackTrace_;
}

const HermesStackTrace* operator->() const {
return &hermesStackTrace_;
}

private:
HermesStackTrace hermesStackTrace_;
};
Expand Down Expand Up @@ -216,6 +224,16 @@ class HermesRuntimeTargetDelegate::Impl final : public RuntimeTargetDelegate {
return samplingProfileDelegate_->collectSamplingProfile();
}

std::optional<folly::dynamic> serializeStackTrace(
const StackTrace& stackTrace) override {
if (auto* hermesStackTraceWrapper =
dynamic_cast<const HermesStackTraceWrapper*>(&stackTrace)) {
return folly::parseJson(cdpDebugAPI_->serializeStackTraceToJsonStr(
**hermesStackTraceWrapper));
}
return std::nullopt;
}

private:
HermesRuntimeTargetDelegate& delegate_;
std::shared_ptr<HermesRuntime> runtime_;
Expand Down Expand Up @@ -311,6 +329,11 @@ HermesRuntimeTargetDelegate::collectSamplingProfile() {
return impl_->collectSamplingProfile();
}

std::optional<folly::dynamic> HermesRuntimeTargetDelegate::serializeStackTrace(
const StackTrace& stackTrace) {
return impl_->serializeStackTrace(stackTrace);
}

#ifdef HERMES_ENABLE_DEBUGGER
CDPDebugAPI& HermesRuntimeTargetDelegate::getCDPDebugAPI() {
return impl_->getCDPDebugAPI();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ class HermesRuntimeTargetDelegate : public RuntimeTargetDelegate {

tracing::RuntimeSamplingProfile collectSamplingProfile() override;

std::optional<folly::dynamic> serializeStackTrace(
const StackTrace& stackTrace) override;

private:
// We use the private implementation idiom to ensure this class has the same
// layout regardless of whether HERMES_ENABLE_DEBUGGER is defined. The net
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ target_merge_so(jsinspector)
target_include_directories(jsinspector PUBLIC ${REACT_COMMON_DIR})

target_link_libraries(jsinspector
boost
folly_runtime
glog
jsinspector_network
jsinspector_tracing
react_featureflags
runtimeexecutor
reactperflogger
react_utils
)
target_compile_reactnative_options(jsinspector PRIVATE)
if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,10 @@ FallbackRuntimeTargetDelegate::collectSamplingProfile() {
"Sampling Profiler capabilities are not supported for Runtime fallback");
}

std::optional<folly::dynamic>
FallbackRuntimeTargetDelegate::serializeStackTrace(
const StackTrace& /*stackTrace*/) {
return std::nullopt;
}

} // namespace facebook::react::jsinspector_modern
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ class FallbackRuntimeTargetDelegate : public RuntimeTargetDelegate {

tracing::RuntimeSamplingProfile collectSamplingProfile() override;

std::optional<folly::dynamic> serializeStackTrace(
const StackTrace& stackTrace) override;

private:
std::string engineDescription_;
};
Expand Down
19 changes: 19 additions & 0 deletions packages/react-native/ReactCommon/jsinspector-modern/HostAgent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "InstanceAgent.h"

#ifdef REACT_NATIVE_DEBUGGER_ENABLED
#include "InspectorFlags.h"
#include "NetworkIOAgent.h"
#include "SessionState.h"
#include "TracingAgent.h"
Expand Down Expand Up @@ -142,6 +143,24 @@ class HostAgent::Impl final {
.shouldSendOKResponse = true,
};
}
if (InspectorFlags::getInstance().getNetworkInspectionEnabled()) {
if (req.method == "Network.enable") {
sessionState_.isNetworkDomainEnabled = true;

return {
.isFinishedHandlingRequest = false,
.shouldSendOKResponse = true,
};
}
if (req.method == "Network.disable") {
sessionState_.isNetworkDomainEnabled = false;

return {
.isFinishedHandlingRequest = false,
.shouldSendOKResponse = true,
};
}
}

// Methods other than domain enables/disables: handle anything we know how
// to handle, and delegate to the InstanceAgent otherwise. (In some special
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,15 +280,15 @@ bool NetworkIOAgent::handleRequest(
if (req.method == "Network.enable") {
networkHandler.setFrontendChannel(frontendChannel_);
networkHandler.enable();
frontendChannel_(cdp::jsonResult(req.id));
return true;
// NOTE: Domain enable/disable responses are sent by HostAgent.
return false;
}

// @cdp Network.disable support is experimental.
if (req.method == "Network.disable") {
networkHandler.disable();
frontendChannel_(cdp::jsonResult(req.id));
return true;
// NOTE: Domain enable/disable responses are sent by HostAgent.
return false;
}

// @cdp Network.getResponseBody support is experimental.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Pod::Spec.new do |s|

resolve_use_frameworks(s, module_name: module_name)

add_dependency(s, "boost")
add_dependency(s, "React-oscompat") # Needed for USE_FRAMEWORKS=dynamic
s.dependency "React-featureflags"
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
Expand All @@ -55,7 +56,7 @@ Pod::Spec.new do |s|
add_dependency(s, "React-jsinspectortracing", :framework_name => 'jsinspector_moderntracing')
s.dependency "React-perflogger", version
add_dependency(s, "React-oscompat")

add_dependency(s, "React-utils", :additional_framework_paths => ["react/utils/platform/ios"])
if use_hermes()
s.dependency "hermes-engine"
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,12 @@ RuntimeTarget::RuntimeTarget(
void RuntimeTarget::installGlobals() {
// NOTE: RuntimeTarget::installConsoleHandler is in RuntimeTargetConsole.cpp
installConsoleHandler();
// NOTE: RuntimeTarget::installDebuggerSessionObserver is in
// RuntimeTargetDebuggerSessionObserver.cpp
installDebuggerSessionObserver();
// NOTE: RuntimeTarget::installNetworkReporterAPI is in
// RuntimeTargetNetwork.cpp
installNetworkReporterAPI();
}

std::shared_ptr<RuntimeAgent> RuntimeTarget::createAgent(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,14 @@ class RuntimeTargetDelegate {
* Return recorded sampling profile for the previous sampling session.
*/
virtual tracing::RuntimeSamplingProfile collectSamplingProfile() = 0;

/**
* \returns a JSON representation of the given stack trace, conforming to the
* @cdp Runtime.StackTrace type, if the runtime supports it. Otherwise,
* returns std::nullopt.
*/
virtual std::optional<folly::dynamic> serializeStackTrace(
const StackTrace& stackTrace) = 0;
};

/**
Expand Down Expand Up @@ -291,6 +299,12 @@ class JSINSPECTOR_EXPORT RuntimeTarget
*/
void installDebuggerSessionObserver();

/**
* Installs the private __NETWORK_REPORTER__ object on the Runtime's
* global object.
*/
void installNetworkReporterAPI();

/**
* Propagates the debugger session state change to the JavaScript via calling
* onStatusChange on __DEBUGGER_SESSION_OBSERVER__.
Expand All @@ -303,6 +317,12 @@ class JSINSPECTOR_EXPORT RuntimeTarget
*/
void emitDebuggerSessionDestroyed();

/**
* \returns a globally unique ID for a network request.
* May be called from any thread as long as the RuntimeTarget is valid.
*/
std::string createNetworkRequestId();

// Necessary to allow RuntimeAgent to access RuntimeTarget's internals in a
// controlled way (i.e. only RuntimeTargetController gets friend access, while
// RuntimeAgent itself doesn't).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -525,30 +525,6 @@ void RuntimeTarget::installConsoleHandler() {
auto console = objectCreate(runtime, std::move(consolePrototype));
auto state = std::make_shared<ConsoleState>();

/**
* An executor that runs synchronously and provides a safe reference to our
* RuntimeTargetDelegate for use on the JS thread.
* \see RuntimeTargetDelegate for information on which methods are safe to
* call on the JS thread.
* \warning The callback will not run if the RuntimeTarget has been
* destroyed.
*/
auto delegateExecutorSync =
[selfWeak,
selfExecutor](std::invocable<RuntimeTargetDelegate&> auto func) {
if (auto self = selfWeak.lock()) {
// Q: Why is it safe to use self->delegate_ here?
// A: Because the caller of InspectorTarget::registerRuntime
// is explicitly required to guarantee that the delegate not
// only outlives the target, but also outlives all JS code
// execution that occurs on the JS thread.
func(self->delegate_);
// To ensure we never destroy `self` on the JS thread, send
// our shared_ptr back to the inspector thread.
selfExecutor([self = std::move(self)](auto&) { (void)self; });
}
};

/**
* Install a console method with the given name and body. The body receives
* the usual JSI host function parameters plus a ConsoleState reference, a
Expand All @@ -569,20 +545,26 @@ void RuntimeTarget::installConsoleHandler() {
forwardToOriginalConsole(
originalConsole,
methodName,
[body = std::move(body), state, delegateExecutorSync](
[body = std::move(body), state, selfWeak](
jsi::Runtime& runtime,
const jsi::Value& /*thisVal*/,
const jsi::Value* args,
size_t count) {
auto timestampMs = getTimestampMs();
delegateExecutorSync([&](auto& runtimeTargetDelegate) {
auto stackTrace = runtimeTargetDelegate.captureStackTrace(
tryExecuteSync(selfWeak, [&](auto& self) {
// Q: Why is it safe to use self->delegate_ here?
// A: Because the caller of
// InspectorTarget::registerRuntime is explicitly required
// to guarantee that the delegate not only outlives the
// target, but also outlives all JS code execution that
// occurs on the JS thread.
auto stackTrace = self.delegate_.captureStackTrace(
runtime, /* framesToSkip */ 1);
body(
runtime,
args,
count,
runtimeTargetDelegate,
self.delegate_,
*state,
timestampMs,
std::move(stackTrace));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* 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 <jsinspector-modern/InspectorFlags.h>
#include <jsinspector-modern/RuntimeTarget.h>
#include <jsinspector-modern/network/NetworkHandler.h>

#include <boost/uuid/random_generator.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_io.hpp>

using namespace facebook::jsi;
using namespace std::string_literals;

namespace facebook::react::jsinspector_modern {

namespace {

/**
* JS `Object.create()`
*/
Object objectCreate(Runtime& runtime, Value prototype) {
auto objectGlobal = runtime.global().getPropertyAsObject(runtime, "Object");
auto createFn = objectGlobal.getPropertyAsFunction(runtime, "create");
return createFn.callWithThis(runtime, objectGlobal, prototype)
.getObject(runtime);
}

/**
* JS `Object.freeze
*/
Object objectFreeze(Runtime& runtime, Object object) {
auto objectGlobal = runtime.global().getPropertyAsObject(runtime, "Object");
auto freezeFn = objectGlobal.getPropertyAsFunction(runtime, "freeze");
return freezeFn.callWithThis(runtime, objectGlobal, object)
.getObject(runtime);
}

} // namespace

void RuntimeTarget::installNetworkReporterAPI() {
if (!InspectorFlags::getInstance().getNetworkInspectionEnabled()) {
return;
}
auto jsiCreateDevToolsRequestId = [selfWeak = weak_from_this()](
Runtime& runtime,
const Value& /*thisVal*/,
const Value* /*args*/,
size_t /*count*/) -> Value {
std::optional<std::string> devToolsRequestId;
tryExecuteSync(selfWeak, [&](RuntimeTarget& self) {
devToolsRequestId = self.createNetworkRequestId();
// Q: Why is it safe to use self.delegate_ here?
// A: Because the caller of InspectorTarget::registerRuntime
// is explicitly required to guarantee that the delegate not
// only outlives the target, but also outlives all JS code
// execution that occurs on the JS thread.
auto stackTrace = self.delegate_.captureStackTrace(runtime);
// TODO(moti): Instead of checking the singleton state,
// directly check whether the current target has a session
// with the Network domain enabled.
if (NetworkHandler::getInstance().isEnabled()) {
auto cdpStackTrace = self.delegate_.serializeStackTrace(*stackTrace);
if (cdpStackTrace) {
NetworkHandler::getInstance().recordRequestInitiatorStack(
*devToolsRequestId, std::move(*cdpStackTrace));
}
}
});
if (!devToolsRequestId) {
throw JSError(runtime, "React Native Runtime is shutting down");
}
return String::createFromUtf8(runtime, *devToolsRequestId);
};

jsExecutor_([selfWeak = weak_from_this(),
selfExecutor = executorFromThis(),
jsiCreateDevToolsRequestId =
std::move(jsiCreateDevToolsRequestId)](Runtime& runtime) {
auto globalObj = runtime.global();
auto networkReporterApi = objectCreate(runtime, nullptr);
networkReporterApi.setProperty(
runtime,
"createDevToolsRequestId",
Function::createFromHostFunction(
runtime,
PropNameID::forAscii(runtime, "createDevToolsRequestId"),
0,
jsiCreateDevToolsRequestId));
networkReporterApi = objectFreeze(runtime, std::move(networkReporterApi));
globalObj.setProperty(runtime, "__NETWORK_REPORTER__", networkReporterApi);
});
}

std::string RuntimeTarget::createNetworkRequestId() {
return boost::uuids::to_string(boost::uuids::random_generator()());
}

} // namespace facebook::react::jsinspector_modern
Loading
Loading