diff --git a/packages/devtools_app/lib/src/screens/network/network_request_inspector_views.dart b/packages/devtools_app/lib/src/screens/network/network_request_inspector_views.dart index 831b3e52052..a09706d0002 100644 --- a/packages/devtools_app/lib/src/screens/network/network_request_inspector_views.dart +++ b/packages/devtools_app/lib/src/screens/network/network_request_inspector_views.dart @@ -721,7 +721,9 @@ class NetworkRequestOverviewView extends StatelessWidget { Widget _buildHttpTimeGraph() { final data = this.data as DartIOHttpRequestData; - if (data.duration == null || data.instantEvents.isEmpty) { + if (data.duration == null || + data.duration!.inMicroseconds == 0 || + data.instantEvents.isEmpty) { return Container( key: httpTimingGraphKey, height: 18.0, diff --git a/packages/devtools_app/lib/src/shared/http/http_request_data.dart b/packages/devtools_app/lib/src/shared/http/http_request_data.dart index 6347ffdbf8d..ef897df3684 100644 --- a/packages/devtools_app/lib/src/shared/http/http_request_data.dart +++ b/packages/devtools_app/lib/src/shared/http/http_request_data.dart @@ -140,9 +140,27 @@ class DartIOHttpRequestData extends NetworkRequest { @override Duration? get duration { - if (inProgress || !isValid) return null; - // Timestamps are in microseconds - return _endTime!.difference(_request.startTime); + if (_hasError) { + final start = _request.startTime; + final end = _request.endTime; + return end != null ? end.difference(start) : null; + } + + // Cancelled request + if (_request.isRequestComplete && + !_request.isResponseComplete && + _request.response == null) { + return Duration.zero; + } + + final start = _request.startTime; + final end = _request.response?.endTime ?? _request.endTime; + + if (end != null) { + return end.difference(start); + } + + return null; } /// Whether the request is safe to display in the UI. @@ -227,11 +245,19 @@ class DartIOHttpRequestData extends NetworkRequest { return connectionInfo != null ? connectionInfo[_localPortKey] : null; } - /// True if the HTTP request hasn't completed yet, determined by the lack of - /// an end time in the response data. @override - bool get inProgress => - _hasError ? !_request.isRequestComplete : !_request.isResponseComplete; + bool get inProgress { + // Detect cancelled requests (request finished but response never arrived) + if (_request.isRequestComplete && + !_request.isResponseComplete && + _request.response == null) { + return false; + } + + return _hasError + ? !_request.isRequestComplete + : !_request.isResponseComplete; + } /// All instant events logged to the timeline for this HTTP request. List get instantEvents { @@ -301,8 +327,17 @@ class DartIOHttpRequestData extends NetworkRequest { DateTime get startTimestamp => _request.startTime; @override - String? get status => - _hasError ? 'Error' : _request.response?.statusCode.toString(); + String? get status { + if (_hasError) return 'Error'; + + if (_request.isRequestComplete && + !_request.isResponseComplete && + _request.response == null) { + return 'Cancelled'; + } + + return _request.response?.statusCode.toString(); + } @override String get uri => _request.uri.toString(); diff --git a/packages/devtools_app/lib/src/shared/primitives/custom_pointer_scroll_view.dart b/packages/devtools_app/lib/src/shared/primitives/custom_pointer_scroll_view.dart index f6cc1b52de7..eb0e45f5d2a 100644 --- a/packages/devtools_app/lib/src/shared/primitives/custom_pointer_scroll_view.dart +++ b/packages/devtools_app/lib/src/shared/primitives/custom_pointer_scroll_view.dart @@ -31,7 +31,7 @@ abstract class CustomPointerScrollView extends BoxScrollView { super.physics, super.shrinkWrap, super.padding, - super.scrollCacheExtent, + super.cacheExtent, super.semanticChildCount, super.dragStartBehavior, this.customPointerSignalHandler, diff --git a/packages/devtools_app/test/screens/network/network_model_test.dart b/packages/devtools_app/test/screens/network/network_model_test.dart index 8b1459e7a80..d4cb297b022 100644 --- a/packages/devtools_app/test/screens/network/network_model_test.dart +++ b/packages/devtools_app/test/screens/network/network_model_test.dart @@ -505,5 +505,16 @@ void main() { expect(httpPatch.didFail, false); expect(httpWsHandshake.didFail, false); }); + + test('cancelled request should not remain pending', () { + final cancelledRequest = DartIOHttpRequestData( + HttpProfileRequest.parse(httpGetPendingJson)!, + requestFullDataFromVmService: false, + ); + + expect(cancelledRequest.inProgress, false); + expect(cancelledRequest.status, 'Cancelled'); + expect(cancelledRequest.duration, Duration.zero); + }); }); } diff --git a/packages/devtools_app/test/screens/network/network_table_test.dart b/packages/devtools_app/test/screens/network/network_table_test.dart index e9191c3b97f..e62069e02d7 100644 --- a/packages/devtools_app/test/screens/network/network_table_test.dart +++ b/packages/devtools_app/test/screens/network/network_table_test.dart @@ -77,7 +77,7 @@ void main() { expect(column.getDisplayValue(getRequest), httpGet.status); final pendingRequest = findRequestById('7'); - expect(column.getDisplayValue(pendingRequest), '--'); + expect(column.getDisplayValue(pendingRequest), 'Cancelled'); }); test('TypeColumn for http request', () { @@ -94,16 +94,16 @@ void main() { expect(column.getDisplayValue(getRequest), '811 ms'); final pendingRequest = findRequestById('7'); - expect(column.getDisplayValue(pendingRequest), 'Pending'); + expect(column.getDisplayValue(pendingRequest), '0 μs'); }); test('TimestampColumn', () { final column = TimestampColumn(); final getRequest = findRequestById('1'); - // The hours field may be unreliable since it depends on the timezone the - // test is running in. - expect(column.getDisplayValue(getRequest), contains(':45:26.279')); + // The hours and minutes field may be unreliable since it depends on the + // timezone the test is running in (e.g. UTC vs IST). + expect(column.getDisplayValue(getRequest), contains('26.279')); }); }); }