From 2a2e27915b32e628d4e8adc64eae0e74cc658a61 Mon Sep 17 00:00:00 2001 From: Moti Zilberman Date: Fri, 3 Oct 2025 04:11:15 -0700 Subject: [PATCH 1/3] Reduce log spam from naggy mocks in tests (#54047) Summary: TSIA Changelog: [Internal] Differential Revision: D83825727 --- .../jsinspector-modern/tests/InspectorMocks.h | 10 ++++++++++ .../tests/InspectorPackagerConnectionTest.cpp | 2 ++ .../tests/ReactInstanceIntegrationTest.h | 4 ++-- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tests/InspectorMocks.h b/packages/react-native/ReactCommon/jsinspector-modern/tests/InspectorMocks.h index 8a7bd893fcf39c..906804c8dc9f7c 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tests/InspectorMocks.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tests/InspectorMocks.h @@ -97,6 +97,7 @@ class MockInspectorPackagerConnectionDelegate executor_.add(callback); } })); + EXPECT_CALL(*this, scheduleCallback(_, _)).Times(AnyNumber()); } // InspectorPackagerConnectionDelegate methods @@ -168,6 +169,15 @@ class MockRuntimeTargetDelegate : public RuntimeTargetDelegate { collectSamplingProfile, (), (override)); + + inline MockRuntimeTargetDelegate() { + using namespace testing; + + // Silence "uninteresting mock function call" warnings for methods that + // don't have side effects. + + EXPECT_CALL(*this, supportsConsole()).Times(AnyNumber()); + } }; class MockRuntimeAgentDelegate : public RuntimeAgentDelegate { diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tests/InspectorPackagerConnectionTest.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tests/InspectorPackagerConnectionTest.cpp index f61eca78251c40..ca73058026cc60 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tests/InspectorPackagerConnectionTest.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tests/InspectorPackagerConnectionTest.cpp @@ -49,6 +49,8 @@ class InspectorPackagerConnectionTestBase : public testing::Test { socket->getDelegate().didOpen(); return std::move(socket); }); + EXPECT_CALL(*packagerConnectionDelegate(), connectWebSocket(_, _)) + .Times(AnyNumber()); } void TearDown() override { diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tests/ReactInstanceIntegrationTest.h b/packages/react-native/ReactCommon/jsinspector-modern/tests/ReactInstanceIntegrationTest.h index c2e482d88e9392..0448d2aaef38e4 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tests/ReactInstanceIntegrationTest.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tests/ReactInstanceIntegrationTest.h @@ -50,7 +50,7 @@ class ReactInstanceIntegrationTest std::shared_ptr messageQueueThread; std::shared_ptr errorHandler; - MockRemoteConnection& getRemoteConnection() { + NiceMock& getRemoteConnection() { EXPECT_EQ(mockRemoteConnections_.objectsVended(), 1); auto rawPtr = mockRemoteConnections_[0]; assert(rawPtr); @@ -64,7 +64,7 @@ class ReactInstanceIntegrationTest size_t id_ = 1; bool verbose_ = false; std::optional pageId_; - UniquePtrFactory mockRemoteConnections_; + UniquePtrFactory> mockRemoteConnections_; std::unique_ptr clientToVM_; folly::QueuedImmediateExecutor immediateExecutor_; MockHostTargetDelegate hostTargetDelegate_; From 1dd85ed378e9c4c432fefd7f90476d98bb49187e Mon Sep 17 00:00:00 2001 From: Moti Zilberman Date: Fri, 3 Oct 2025 04:11:15 -0700 Subject: [PATCH 2/3] Add basic test for NetworkReporter and CDP Network domain (#54049) Summary: Changelog: [Internal] Adds `JsiIntegrationTest`-based tests for the C++ layer responsible for handling the CDP `Network` domain. The tests use `NetworkReporter` to mimic the behaviour of a React Native platform that is generating network events. NOTE: There are **significant problems** with the `NetworkReporter` / `NetworkHandler` singleton design: cross-Host and cross-Instance data pollution/corruption, inability to inspect multiple Hosts at once, inconsistency between the singleton's state and the CDP session's state, etc. The singletons similarly lack any kind of mechanism for test isolation (e.g. methods for clearing state between tests). As we iteratively fix these problems, the tests will require updating. Reviewed By: huntie Differential Revision: D83746505 --- .../tests/JsiIntegrationTest.h | 5 +- .../tests/NetworkReporterTest.cpp | 466 ++++++++++++++++++ ...JsiIntegrationTestGenericEngineAdapter.cpp | 5 - .../JsiIntegrationTestHermesEngineAdapter.cpp | 5 - .../utils/InspectorFlagOverridesGuard.cpp | 12 + .../tests/utils/InspectorFlagOverridesGuard.h | 1 + 6 files changed, 481 insertions(+), 13 deletions(-) create mode 100644 packages/react-native/ReactCommon/jsinspector-modern/tests/NetworkReporterTest.cpp diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tests/JsiIntegrationTest.h b/packages/react-native/ReactCommon/jsinspector-modern/tests/JsiIntegrationTest.h index 2a3db0b78b3934..af32eedb896dd0 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tests/JsiIntegrationTest.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tests/JsiIntegrationTest.h @@ -47,9 +47,8 @@ class JsiIntegrationPortableTestBase : public ::testing::Test, protected: Executor executor_; - JsiIntegrationPortableTestBase() - : inspectorFlagsGuard_{EngineAdapter::getInspectorFlagOverrides()}, - engineAdapter_{executor_} {} + JsiIntegrationPortableTestBase(InspectorFlagOverrides overrides = {}) + : inspectorFlagsGuard_(overrides), engineAdapter_{executor_} {} void SetUp() override { // NOTE: Using SetUp() so we can call virtual methods like diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tests/NetworkReporterTest.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tests/NetworkReporterTest.cpp new file mode 100644 index 00000000000000..27776a32f3b87f --- /dev/null +++ b/packages/react-native/ReactCommon/jsinspector-modern/tests/NetworkReporterTest.cpp @@ -0,0 +1,466 @@ +/* + * 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 "JsiIntegrationTest.h" +#include "engines/JsiIntegrationTestHermesEngineAdapter.h" + +#include +#include +#include +#include + +using namespace ::testing; + +namespace facebook::react::jsinspector_modern { + +namespace { + +struct Params { + bool enableNetworkEventReporting; +}; + +} // namespace + +/** + * A test fixture for the way the internal NetworkReporter API interacts with + * the CDP Network domain. + */ +class NetworkReporterTest : public JsiIntegrationPortableTestBase< + JsiIntegrationTestHermesEngineAdapter, + folly::QueuedImmediateExecutor>, + public WithParamInterface { + protected: + NetworkReporterTest() + : JsiIntegrationPortableTestBase({ + .networkInspectionEnabled = true, + .enableNetworkEventReporting = + GetParam().enableNetworkEventReporting, + }) {} + + void SetUp() override { + JsiIntegrationPortableTestBase::SetUp(); + connect(); + } + + private: +}; + +TEST_P(NetworkReporterTest, testNetworkEnableDisable) { + InSequence s; + + EXPECT_FALSE(NetworkReporter::getInstance().isDebuggingEnabled()); + this->expectMessageFromPage(JsonEq(R"({ + "id": 1, + "result": {} + })")); + this->toPage_->sendMessage(R"({ + "id": 1, + "method": "Network.enable" + })"); + + EXPECT_TRUE(NetworkReporter::getInstance().isDebuggingEnabled()); + + this->expectMessageFromPage(JsonEq(R"({ + "id": 2, + "result": {} + })")); + this->toPage_->sendMessage(R"({ + "id": 2, + "method": "Network.disable" + })"); + + EXPECT_FALSE(NetworkReporter::getInstance().isDebuggingEnabled()); +} + +TEST_P(NetworkReporterTest, testGetMissingResponseBody) { + InSequence s; + this->expectMessageFromPage(JsonEq(R"({ + "id": 1, + "result": {} + })")); + this->toPage_->sendMessage(R"({ + "id": 1, + "method": "Network.enable" + })"); + this->expectMessageFromPage(JsonParsed(AllOf( + AtJsonPtr("/error/code", (int)cdp::ErrorCode::InternalError), + AtJsonPtr("/id", 2)))); + this->toPage_->sendMessage(R"({ + "id": 2, + "method": "Network.getResponseBody", + "params": { + "requestId": "1234567890-no-such-request" + } + })"); + this->expectMessageFromPage(JsonEq(R"({ + "id": 3, + "result": {} + })")); + this->toPage_->sendMessage(R"({ + "id": 3, + "method": "Network.disable" + })"); +} + +TEST_P(NetworkReporterTest, testRequestWillBeSentWithRedirect) { + InSequence s; + this->expectMessageFromPage(JsonEq(R"({ + "id": 1, + "result": {} + })")); + this->toPage_->sendMessage(R"({ + "id": 1, + "method": "Network.enable" + })"); + + this->expectMessageFromPage(JsonParsed(AllOf( + AtJsonPtr("/method", "Network.requestWillBeSent"), + AtJsonPtr("/params/requestId", "test-request-1"), + AtJsonPtr("/params/loaderId", ""), + AtJsonPtr("/params/documentURL", "mobile"), + AtJsonPtr("/params/request/url", "https://example.com/redirected"), + AtJsonPtr("/params/request/method", "POST"), + AtJsonPtr("/params/request/headers/Content-Type", "application/json"), + AtJsonPtr("/params/timestamp", Gt(0)), + AtJsonPtr("/params/wallTime", Gt(0)), + AtJsonPtr("/params/initiator/type", "script"), + AtJsonPtr("/params/redirectHasExtraInfo", true), + AtJsonPtr("/params/redirectResponse", Not(IsEmpty())), + AtJsonPtr("/params/redirectResponse/url", "https://example.com/original"), + AtJsonPtr("/params/redirectResponse/status", 302), + AtJsonPtr( + "/params/redirectResponse/headers/Location", + "https://example.com/redirected")))); + + RequestInfo requestInfo; + requestInfo.url = "https://example.com/redirected"; + requestInfo.httpMethod = "POST"; + requestInfo.headers = Headers{{"Content-Type", "application/json"}}; + + ResponseInfo redirectResponse; + redirectResponse.url = "https://example.com/original"; + redirectResponse.statusCode = 302; + redirectResponse.headers = + Headers{{"Location", "https://example.com/redirected"}}; + + NetworkReporter::getInstance().reportRequestStart( + "test-request-1", requestInfo, 1024, redirectResponse); + + this->expectMessageFromPage(JsonEq(R"({ + "id": 2, + "result": {} + })")); + this->toPage_->sendMessage(R"({ + "id": 2, + "method": "Network.disable" + })"); +} + +TEST_P(NetworkReporterTest, testRequestWillBeSentExtraInfoParameters) { + InSequence s; + this->expectMessageFromPage(JsonEq(R"({ + "id": 1, + "result": {} + })")); + this->toPage_->sendMessage(R"({ + "id": 1, + "method": "Network.enable" + })"); + + this->expectMessageFromPage(JsonParsed(AllOf( + AtJsonPtr("/method", "Network.requestWillBeSentExtraInfo"), + AtJsonPtr("/params/requestId", "test-extra-info"), + AtJsonPtr("/params/headers/User-Agent", "TestAgent"), + AtJsonPtr("/params/headers/Accept-Language", "en-US"), + AtJsonPtr("/params/connectTiming/requestTime", Gt(0))))); + + Headers extraHeaders = { + {"User-Agent", "TestAgent"}, {"Accept-Language", "en-US"}}; + + NetworkReporter::getInstance().reportConnectionTiming( + "test-extra-info", extraHeaders); + + this->expectMessageFromPage(JsonEq(R"({ + "id": 2, + "result": {} + })")); + this->toPage_->sendMessage(R"({ + "id": 2, + "method": "Network.disable" + })"); +} + +TEST_P(NetworkReporterTest, testLoadingFailedCancelled) { + InSequence s; + this->expectMessageFromPage(JsonEq(R"({ + "id": 1, + "result": {} + })")); + this->toPage_->sendMessage(R"({ + "id": 1, + "method": "Network.enable" + })"); + + this->expectMessageFromPage(JsonParsed(AllOf( + AtJsonPtr("/method", "Network.loadingFailed"), + AtJsonPtr("/params/requestId", "test-request-1"), + AtJsonPtr("/params/timestamp", Gt(0)), + AtJsonPtr("/params/type", "Other"), + AtJsonPtr("/params/errorText", "net::ERR_ABORTED"), + AtJsonPtr("/params/canceled", true)))); + + NetworkReporter::getInstance().reportRequestFailed("test-request-1", true); + + this->expectMessageFromPage(JsonEq(R"({ + "id": 2, + "result": {} + })")); + this->toPage_->sendMessage(R"({ + "id": 2, + "method": "Network.disable" + })"); +} + +TEST_P(NetworkReporterTest, testLoadingFailedError) { + InSequence s; + this->expectMessageFromPage(JsonEq(R"({ + "id": 1, + "result": {} + })")); + this->toPage_->sendMessage(R"({ + "id": 1, + "method": "Network.enable" + })"); + + this->expectMessageFromPage(JsonParsed(AllOf( + AtJsonPtr("/method", "Network.loadingFailed"), + AtJsonPtr("/params/requestId", "test-request-1"), + AtJsonPtr("/params/timestamp", Gt(0)), + AtJsonPtr("/params/type", "Other"), + AtJsonPtr("/params/errorText", "net::ERR_FAILED"), + AtJsonPtr("/params/canceled", false)))); + + NetworkReporter::getInstance().reportRequestFailed("test-request-1", false); + + this->expectMessageFromPage(JsonEq(R"({ + "id": 2, + "result": {} + })")); + this->toPage_->sendMessage(R"({ + "id": 2, + "method": "Network.disable" + })"); +} + +TEST_P(NetworkReporterTest, testCompleteNetworkFlow) { + InSequence s; + this->expectMessageFromPage(JsonEq(R"({ + "id": 1, + "result": {} + })")); + this->toPage_->sendMessage(R"({ + "id": 1, + "method": "Network.enable" + })"); + + const std::string requestId = "complete-flow-request"; + + // Step 1: Request will be sent + this->expectMessageFromPage(JsonParsed(AllOf( + AtJsonPtr("/method", "Network.requestWillBeSent"), + AtJsonPtr("/params/requestId", requestId), + AtJsonPtr("/params/loaderId", ""), + AtJsonPtr("/params/documentURL", "mobile"), + AtJsonPtr("/params/request/url", "https://api.example.com/users"), + AtJsonPtr("/params/request/method", "GET"), + AtJsonPtr("/params/request/headers/Accept", "application/json"), + AtJsonPtr("/params/timestamp", Gt(0)), + AtJsonPtr("/params/wallTime", Gt(0)), + AtJsonPtr("/params/initiator/type", "script"), + AtJsonPtr("/params/redirectHasExtraInfo", false)))); + + RequestInfo requestInfo; + requestInfo.url = "https://api.example.com/users"; + requestInfo.httpMethod = "GET"; + requestInfo.headers = Headers{{"Accept", "application/json"}}; + + NetworkReporter::getInstance().reportRequestStart( + requestId, requestInfo, 0, std::nullopt); + + // Step 2: Connection timing + this->expectMessageFromPage(JsonParsed(AllOf( + AtJsonPtr("/method", "Network.requestWillBeSentExtraInfo"), + AtJsonPtr("/params/requestId", requestId), + AtJsonPtr("/params/headers/Accept", "application/json"), + AtJsonPtr("/params/connectTiming/requestTime", Gt(0))))); + + NetworkReporter::getInstance().reportConnectionTiming( + requestId, requestInfo.headers); + + // Step 3: Response received + this->expectMessageFromPage(JsonParsed(AllOf( + AtJsonPtr("/method", "Network.responseReceived"), + AtJsonPtr("/params/requestId", requestId), + AtJsonPtr("/params/loaderId", ""), + AtJsonPtr("/params/timestamp", Gt(0)), + AtJsonPtr("/params/type", "XHR"), + AtJsonPtr("/params/response/url", "https://api.example.com/users"), + AtJsonPtr("/params/response/status", 200), + AtJsonPtr("/params/response/statusText", "OK"), + AtJsonPtr("/params/response/headers/Content-Type", "application/json"), + AtJsonPtr("/params/response/headers/Content-Length", "1024"), + AtJsonPtr("/params/response/mimeType", "application/json"), + AtJsonPtr("/params/response/encodedDataLength", 1024), + AtJsonPtr("/params/hasExtraInfo", false)))); + + ResponseInfo responseInfo; + responseInfo.url = "https://api.example.com/users"; + responseInfo.statusCode = 200; + responseInfo.headers = + Headers{{"Content-Type", "application/json"}, {"Content-Length", "1024"}}; + + NetworkReporter::getInstance().reportResponseStart( + requestId, responseInfo, 1024); + + // Step 4: Data received (multiple chunks) + this->expectMessageFromPage(JsonParsed(AllOf( + AtJsonPtr("/method", "Network.dataReceived"), + AtJsonPtr("/params/requestId", requestId), + AtJsonPtr("/params/timestamp", Gt(0)), + AtJsonPtr("/params/dataLength", 512), + AtJsonPtr("/params/encodedDataLength", 512)))); + + NetworkReporter::getInstance().reportDataReceived(requestId, 512, 512); + + this->expectMessageFromPage(JsonParsed(AllOf( + AtJsonPtr("/method", "Network.dataReceived"), + AtJsonPtr("/params/requestId", requestId), + AtJsonPtr("/params/timestamp", Gt(0)), + AtJsonPtr("/params/dataLength", 512), + AtJsonPtr("/params/encodedDataLength", 512)))); + + NetworkReporter::getInstance().reportDataReceived(requestId, 512, 512); + + // Step 5: Loading finished + this->expectMessageFromPage(JsonParsed(AllOf( + AtJsonPtr("/method", "Network.loadingFinished"), + AtJsonPtr("/params/requestId", requestId), + AtJsonPtr("/params/timestamp", Gt(0)), + AtJsonPtr("/params/encodedDataLength", 1024)))); + + NetworkReporter::getInstance().reportResponseEnd(requestId, 1024); + + // Store and retrieve response body + NetworkReporter::getInstance().storeResponseBody( + requestId, R"({"users": [{"id": 1, "name": "John"}]})", false); + + this->expectMessageFromPage(JsonEq(R"({ + "id": 2, + "result": { + "body": "{\"users\": [{\"id\": 1, \"name\": \"John\"}]}", + "base64Encoded": false + } + })")); + this->toPage_->sendMessage(fmt::format( + R"({{ + "id": 2, + "method": "Network.getResponseBody", + "params": {{ + "requestId": {0} + }} + }})", + folly::toJson(requestId))); + + this->expectMessageFromPage(JsonEq(R"({ + "id": 3, + "result": {} + })")); + this->toPage_->sendMessage(R"({ + "id": 3, + "method": "Network.disable" + })"); +} + +TEST_P(NetworkReporterTest, testGetResponseBodyWithBase64) { + InSequence s; + this->expectMessageFromPage(JsonEq(R"({ + "id": 1, + "result": {} + })")); + this->toPage_->sendMessage(R"({ + "id": 1, + "method": "Network.enable" + })"); + + const std::string requestId = "base64-response-test"; + + // Store base64-encoded response body + NetworkReporter::getInstance().storeResponseBody( + requestId, "SGVsbG8gV29ybGQ=", true); + + this->expectMessageFromPage(JsonEq(R"({ + "id": 2, + "result": { + "body": "SGVsbG8gV29ybGQ=", + "base64Encoded": true + } + })")); + this->toPage_->sendMessage(fmt::format( + R"({{ + "id": 2, + "method": "Network.getResponseBody", + "params": {{ + "requestId": {0} + }} + }})", + folly::toJson(requestId))); + + this->expectMessageFromPage(JsonEq(R"({ + "id": 3, + "result": {} + })")); + this->toPage_->sendMessage(R"({ + "id": 3, + "method": "Network.disable" + })"); +} + +TEST_P(NetworkReporterTest, testNetworkEventsWhenDisabled) { + EXPECT_FALSE(NetworkReporter::getInstance().isDebuggingEnabled()); + + // NOTE: The test will automatically fail if any unexpected CDP messages are + // received as a result of the following calls. + + RequestInfo requestInfo; + requestInfo.url = "https://example.com/disabled"; + requestInfo.httpMethod = "GET"; + + NetworkReporter::getInstance().reportRequestStart( + "disabled-request", requestInfo, 0, std::nullopt); + + ResponseInfo responseInfo; + responseInfo.url = "https://example.com/disabled"; + responseInfo.statusCode = 200; + + NetworkReporter::getInstance().reportConnectionTiming("disabled-request", {}); + NetworkReporter::getInstance().reportResponseStart( + "disabled-request", responseInfo, 1024); + NetworkReporter::getInstance().reportDataReceived( + "disabled-request", 512, 512); + NetworkReporter::getInstance().reportResponseEnd("disabled-request", 1024); + NetworkReporter::getInstance().reportRequestFailed("disabled-request", false); +} + +static const auto paramValues = testing::Values( + Params{.enableNetworkEventReporting = true}, + Params{ + .enableNetworkEventReporting = false, + }); + +INSTANTIATE_TEST_SUITE_P(NetworkReporterTest, NetworkReporterTest, paramValues); + +} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tests/engines/JsiIntegrationTestGenericEngineAdapter.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tests/engines/JsiIntegrationTestGenericEngineAdapter.cpp index ea6208d23c4855..fc8474eeeca74b 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tests/engines/JsiIntegrationTestGenericEngineAdapter.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tests/engines/JsiIntegrationTestGenericEngineAdapter.cpp @@ -19,11 +19,6 @@ JsiIntegrationTestGenericEngineAdapter::JsiIntegrationTestGenericEngineAdapter( runtimeTargetDelegate_{ "Generic engine (" + runtime_->description() + ")"} {} -/* static */ InspectorFlagOverrides -JsiIntegrationTestGenericEngineAdapter::getInspectorFlagOverrides() noexcept { - return {}; -} - RuntimeTargetDelegate& JsiIntegrationTestGenericEngineAdapter::getRuntimeTargetDelegate() { return runtimeTargetDelegate_; diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tests/engines/JsiIntegrationTestHermesEngineAdapter.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tests/engines/JsiIntegrationTestHermesEngineAdapter.cpp index ca6d48684bde1e..ad2b3e663bfc5d 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tests/engines/JsiIntegrationTestHermesEngineAdapter.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tests/engines/JsiIntegrationTestHermesEngineAdapter.cpp @@ -19,11 +19,6 @@ JsiIntegrationTestHermesEngineAdapter::JsiIntegrationTestHermesEngineAdapter( jsExecutor_{jsExecutor}, runtimeTargetDelegate_{runtime_} {} -/* static */ InspectorFlagOverrides -JsiIntegrationTestHermesEngineAdapter::getInspectorFlagOverrides() noexcept { - return {}; -} - RuntimeTargetDelegate& JsiIntegrationTestHermesEngineAdapter::getRuntimeTargetDelegate() { return runtimeTargetDelegate_; diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tests/utils/InspectorFlagOverridesGuard.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tests/utils/InspectorFlagOverridesGuard.cpp index 76aaf2ead4f27a..837301c934a0c5 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tests/utils/InspectorFlagOverridesGuard.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tests/utils/InspectorFlagOverridesGuard.cpp @@ -36,6 +36,18 @@ class ReactNativeFeatureFlagsOverrides ReactNativeFeatureFlagsDefaults::fuseboxNetworkInspectionEnabled()); } + bool enableBridgelessArchitecture() override { + // NOTE: Network support is gated by (enableBridgelessArchitecture && + // fuseboxNetworkInspectionEnabled). + return overrides_.networkInspectionEnabled.value_or( + ReactNativeFeatureFlagsDefaults::enableBridgelessArchitecture()); + } + + bool enableNetworkEventReporting() override { + return overrides_.enableNetworkEventReporting.value_or( + ReactNativeFeatureFlagsDefaults::enableNetworkEventReporting()); + } + private: InspectorFlagOverrides overrides_; }; diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tests/utils/InspectorFlagOverridesGuard.h b/packages/react-native/ReactCommon/jsinspector-modern/tests/utils/InspectorFlagOverridesGuard.h index b711ffdf0897d6..d86201c15c70db 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tests/utils/InspectorFlagOverridesGuard.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tests/utils/InspectorFlagOverridesGuard.h @@ -21,6 +21,7 @@ struct InspectorFlagOverrides { // the implementation file. std::optional fuseboxEnabledRelease; std::optional networkInspectionEnabled; + std::optional enableNetworkEventReporting; }; /** From b6a81a22d0c5eeaec63d15a12076ca10ec5e55c6 Mon Sep 17 00:00:00 2001 From: Moti Zilberman Date: Fri, 3 Oct 2025 04:11:15 -0700 Subject: [PATCH 3/3] Add RuntimeTargetDelegate::serializeStackTrace API (#54048) Summary: Changelog: [Internal] Adds an engine-agnostic mechanism for serialising a previously captured stack trace as a CDP [`Runtime.StackTrace`](https://cdpstatus.reactnative.dev/devtools-protocol/tot/Runtime#type-StackTrace). This complements the existing `RuntimeTargetDelegate::captureStackTrace` method, which returns an opaque, engine-specific representation of a stack trace. This can be used as a building block for implementing higher-level CDP message types like [`Network.Initiator`](https://cdpstatus.reactnative.dev/devtools-protocol/tot/Network#type-Initiator) within React Native, while keeping the underlying stack trace representation private to each engine. Differential Revision: D83754142 --- .../chrome/HermesRuntimeTargetDelegate.cpp | 23 +++++++++++++++++++ .../chrome/HermesRuntimeTargetDelegate.h | 3 +++ .../FallbackRuntimeTargetDelegate.cpp | 6 +++++ .../FallbackRuntimeTargetDelegate.h | 3 +++ .../jsinspector-modern/RuntimeTarget.h | 8 +++++++ .../jsinspector-modern/tests/InspectorMocks.h | 5 ++++ 6 files changed, 48 insertions(+) diff --git a/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeTargetDelegate.cpp b/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeTargetDelegate.cpp index 4398431e61b176..09489561f06565 100644 --- a/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeTargetDelegate.cpp +++ b/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeTargetDelegate.cpp @@ -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_; }; @@ -216,6 +224,16 @@ class HermesRuntimeTargetDelegate::Impl final : public RuntimeTargetDelegate { return samplingProfileDelegate_->collectSamplingProfile(); } + std::optional serializeStackTrace( + const StackTrace& stackTrace) override { + if (auto* hermesStackTraceWrapper = + dynamic_cast(&stackTrace)) { + return folly::parseJson(cdpDebugAPI_->serializeStackTraceToJsonStr( + **hermesStackTraceWrapper)); + } + return std::nullopt; + } + private: HermesRuntimeTargetDelegate& delegate_; std::shared_ptr runtime_; @@ -311,6 +329,11 @@ HermesRuntimeTargetDelegate::collectSamplingProfile() { return impl_->collectSamplingProfile(); } +std::optional HermesRuntimeTargetDelegate::serializeStackTrace( + const StackTrace& stackTrace) { + return impl_->serializeStackTrace(stackTrace); +} + #ifdef HERMES_ENABLE_DEBUGGER CDPDebugAPI& HermesRuntimeTargetDelegate::getCDPDebugAPI() { return impl_->getCDPDebugAPI(); diff --git a/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeTargetDelegate.h b/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeTargetDelegate.h index 94612ba28c219c..63662d9df270af 100644 --- a/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeTargetDelegate.h +++ b/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeTargetDelegate.h @@ -60,6 +60,9 @@ class HermesRuntimeTargetDelegate : public RuntimeTargetDelegate { tracing::RuntimeSamplingProfile collectSamplingProfile() override; + std::optional 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 diff --git a/packages/react-native/ReactCommon/jsinspector-modern/FallbackRuntimeTargetDelegate.cpp b/packages/react-native/ReactCommon/jsinspector-modern/FallbackRuntimeTargetDelegate.cpp index 0198ba13959aa5..b4607cf3c6dd92 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/FallbackRuntimeTargetDelegate.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/FallbackRuntimeTargetDelegate.cpp @@ -58,4 +58,10 @@ FallbackRuntimeTargetDelegate::collectSamplingProfile() { "Sampling Profiler capabilities are not supported for Runtime fallback"); } +std::optional +FallbackRuntimeTargetDelegate::serializeStackTrace( + const StackTrace& /*stackTrace*/) { + return std::nullopt; +} + } // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactCommon/jsinspector-modern/FallbackRuntimeTargetDelegate.h b/packages/react-native/ReactCommon/jsinspector-modern/FallbackRuntimeTargetDelegate.h index 9c2a6a43ae3066..9507eb2379d4ba 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/FallbackRuntimeTargetDelegate.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/FallbackRuntimeTargetDelegate.h @@ -46,6 +46,9 @@ class FallbackRuntimeTargetDelegate : public RuntimeTargetDelegate { tracing::RuntimeSamplingProfile collectSamplingProfile() override; + std::optional serializeStackTrace( + const StackTrace& stackTrace) override; + private: std::string engineDescription_; }; diff --git a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h index 1dd9b8ef398485..202e4e43a197e1 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h @@ -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 serializeStackTrace( + const StackTrace& stackTrace) = 0; }; /** diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tests/InspectorMocks.h b/packages/react-native/ReactCommon/jsinspector-modern/tests/InspectorMocks.h index 906804c8dc9f7c..212f7510d5e098 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tests/InspectorMocks.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tests/InspectorMocks.h @@ -169,6 +169,11 @@ class MockRuntimeTargetDelegate : public RuntimeTargetDelegate { collectSamplingProfile, (), (override)); + MOCK_METHOD( + std::optional, + serializeStackTrace, + (const StackTrace& stackTrace), + (override)); inline MockRuntimeTargetDelegate() { using namespace testing;