From ebb831a0c96883bee7b2322392a403e0ca4f11d2 Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Thu, 3 Jul 2025 05:01:57 -0700 Subject: [PATCH] Implement Network.loadingFailed (#52334) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/52334 Adds support for `Network.loadingFailed` in jsinspector-modern and wires up for iOS. Changelog: [Internal] Reviewed By: hoxyq Differential Revision: D77489477 fbshipit-source-id: dc8156979fe49583819019fa4b88b6eb99dea734 --- .../Network/RCTInspectorNetworkReporter.h | 2 +- .../Network/RCTInspectorNetworkReporter.mm | 5 ++- .../Libraries/Network/RCTNetworking.mm | 6 ++- .../jsinspector-modern/network/CdpNetwork.cpp | 12 ++++++ .../jsinspector-modern/network/CdpNetwork.h | 13 ++++++ .../network/NetworkReporter.cpp | 42 ++++++++++++------- .../network/NetworkReporter.h | 17 ++++---- 7 files changed, 72 insertions(+), 25 deletions(-) diff --git a/packages/react-native/Libraries/Network/RCTInspectorNetworkReporter.h b/packages/react-native/Libraries/Network/RCTInspectorNetworkReporter.h index 728ba464db4..e535601613d 100644 --- a/packages/react-native/Libraries/Network/RCTInspectorNetworkReporter.h +++ b/packages/react-native/Libraries/Network/RCTInspectorNetworkReporter.h @@ -57,7 +57,7 @@ * * - Corresponds to `Network.loadingFailed` in CDP. */ -+ (void)reportRequestFailed:(NSNumber *)requestId; ++ (void)reportRequestFailed:(NSNumber *)requestId cancelled:(BOOL)cancelled; /** * Store response body preview. This is an optional reporting method, and is a diff --git a/packages/react-native/Libraries/Network/RCTInspectorNetworkReporter.mm b/packages/react-native/Libraries/Network/RCTInspectorNetworkReporter.mm index f0ccc6f3365..336f7b35bbc 100644 --- a/packages/react-native/Libraries/Network/RCTInspectorNetworkReporter.mm +++ b/packages/react-native/Libraries/Network/RCTInspectorNetworkReporter.mm @@ -109,9 +109,10 @@ static const NSMutableDictionary *responseBuffers #endif } -// TODO(T218584924): Implement and report to NetworkReporter -+ (void)reportRequestFailed:(NSNumber *)requestId ++ (void)reportRequestFailed:(NSNumber *)requestId cancelled:(bool)cancelled { + NetworkReporter::getInstance().reportRequestFailed(requestId.stringValue.UTF8String, cancelled); + #ifdef REACT_NATIVE_DEBUGGER_ENABLED // Debug build: Clear buffer for request if (responseBuffers != nullptr) { diff --git a/packages/react-native/Libraries/Network/RCTNetworking.mm b/packages/react-native/Libraries/Network/RCTNetworking.mm index ae478eb7d22..da9ee34ac0f 100644 --- a/packages/react-native/Libraries/Network/RCTNetworking.mm +++ b/packages/react-native/Libraries/Network/RCTNetworking.mm @@ -437,8 +437,12 @@ RCT_EXPORT_MODULE() [task start]; __weak RCTNetworkTask *weakTask = task; + NSNumber *requestId = [task.requestID copy]; return ^{ [weakTask cancel]; + if (facebook::react::ReactNativeFeatureFlags::enableNetworkEventReporting()) { + [RCTInspectorNetworkReporter reportRequestFailed:requestId cancelled:YES]; + } if (cancellationBlock) { cancellationBlock(); } @@ -672,7 +676,7 @@ RCT_EXPORT_MODULE() if (facebook::react::ReactNativeFeatureFlags::enableNetworkEventReporting()) { if (error != nullptr) { - [RCTInspectorNetworkReporter reportRequestFailed:task.requestID]; + [RCTInspectorNetworkReporter reportRequestFailed:task.requestID cancelled:NO]; } else { [RCTInspectorNetworkReporter reportResponseEnd:task.requestID encodedDataLength:data.length]; } diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/CdpNetwork.cpp b/packages/react-native/ReactCommon/jsinspector-modern/network/CdpNetwork.cpp index 15766f08958..d5fc2599084 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/network/CdpNetwork.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/network/CdpNetwork.cpp @@ -107,6 +107,18 @@ folly::dynamic ResponseReceivedParams::toDynamic() const { return params; } +folly::dynamic LoadingFailedParams::toDynamic() const { + folly::dynamic params = folly::dynamic::object; + + params["requestId"] = requestId; + params["timestamp"] = timestamp; + params["type"] = type; + params["errorText"] = errorText; + params["canceled"] = canceled; + + return params; +} + folly::dynamic LoadingFinishedParams::toDynamic() const { folly::dynamic params = folly::dynamic::object; diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/CdpNetwork.h b/packages/react-native/ReactCommon/jsinspector-modern/network/CdpNetwork.h index 709e2e74922..1240de5f786 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/network/CdpNetwork.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/network/CdpNetwork.h @@ -89,6 +89,19 @@ struct ResponseReceivedParams { folly::dynamic toDynamic() const; }; +/** + * https://chromedevtools.github.io/devtools-protocol/tot/Network/#event-loadingFailed + */ +struct LoadingFailedParams { + std::string requestId; + double timestamp; + std::string type; + std::string errorText; + bool canceled; + + folly::dynamic toDynamic() const; +}; + /** * https://chromedevtools.github.io/devtools-protocol/tot/Network/#event-loadingFinished */ diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.cpp b/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.cpp index 0bcd8be4b79..89bd0b0a11b 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.cpp @@ -151,19 +151,6 @@ void NetworkReporter::reportConnectionTiming(const std::string& requestId) { #endif } -void NetworkReporter::reportRequestFailed( - const std::string& /*requestId*/) const { -#ifdef REACT_NATIVE_DEBUGGER_ENABLED - // Debug build: CDP event handling - if (!isDebuggingEnabledNoSync()) { - return; - } - - // TODO(T218236855) - throw std::runtime_error("Not implemented"); -#endif -} - void NetworkReporter::reportResponseStart( const std::string& requestId, const ResponseInfo& responseInfo, @@ -190,11 +177,14 @@ void NetworkReporter::reportResponseStart( auto response = cdp::network::Response::fromInputParams(responseInfo, encodedDataLength); + auto resourceType = cdp::network::resourceTypeFromMimeType(response.mimeType); + resourceTypeMap_.emplace(requestId, resourceType); + auto params = cdp::network::ResponseReceivedParams{ .requestId = requestId, .loaderId = "", .timestamp = getCurrentUnixTimestampSeconds(), - .type = cdp::network::resourceTypeFromMimeType(response.mimeType), + .type = resourceType, .response = response, .hasExtraInfo = false, }; @@ -273,6 +263,30 @@ void NetworkReporter::reportResponseEnd( #endif } +void NetworkReporter::reportRequestFailed( + const std::string& requestId, + bool cancelled) const { +#ifdef REACT_NATIVE_DEBUGGER_ENABLED + // Debug build: CDP event handling + if (!isDebuggingEnabledNoSync()) { + return; + } + + auto params = cdp::network::LoadingFailedParams{ + .requestId = requestId, + .timestamp = getCurrentUnixTimestampSeconds(), + .type = resourceTypeMap_.find(requestId) != resourceTypeMap_.end() + ? resourceTypeMap_.at(requestId) + : "Other", + .errorText = cancelled ? "net::ERR_ABORTED" : "net::ERR_FAILED", + .canceled = cancelled, + }; + + frontendChannel_( + cdp::jsonNotification("Network.loadingFailed", params.toDynamic())); +#endif +} + void NetworkReporter::storeResponseBody( const std::string& requestId, std::string_view body, diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.h b/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.h index 839985385f1..3cecb86baf5 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.h @@ -113,13 +113,6 @@ class NetworkReporter { */ void reportConnectionTiming(const std::string& requestId); - /** - * Report when a network request has failed. - * - * Corresponds to `Network.loadingFailed` in CDP. - */ - void reportRequestFailed(const std::string& requestId) const; - /** * Report when HTTP response headers have been received, corresponding to * when the first byte of the response is available. @@ -152,6 +145,13 @@ class NetworkReporter { */ void reportResponseEnd(const std::string& requestId, int encodedDataLength); + /** + * Report when a network request has failed. + * + * Corresponds to `Network.loadingFailed` in CDP. + */ + void reportRequestFailed(const std::string& requestId, bool cancelled) const; + /** * Store the fetched response body for a text or image network response. * These may be retrieved by CDP clients to to render a response preview via @@ -193,6 +193,9 @@ class NetworkReporter { std::unordered_map perfTimingsBuffer_{}; std::mutex perfTimingsMutex_; + // Only populated when CDP debugging is enabled. + std::map resourceTypeMap_{}; + // Only populated when CDP debugging is enabled. BoundedRequestBuffer requestBodyBuffer_{}; std::mutex requestBodyMutex_;