Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Offline support for Network page #8332

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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 @@ -14,6 +14,7 @@ import '../../shared/config_specific/logger/allowed_error.dart';
import '../../shared/globals.dart';
import '../../shared/http/http_request_data.dart';
import '../../shared/http/http_service.dart' as http_service;
import '../../shared/offline_data.dart';
import '../../shared/primitives/utils.dart';
import '../../shared/ui/filter.dart';
import '../../shared/ui/search.dart';
Expand Down Expand Up @@ -49,6 +50,7 @@ class NetworkController extends DisposableController
with
SearchControllerMixin<NetworkRequest>,
FilterControllerMixin<NetworkRequest>,
OfflineScreenControllerMixin,
AutoDisposeControllerMixin {
NetworkController() {
_networkService = NetworkService(this);
Expand Down Expand Up @@ -386,6 +388,25 @@ class NetworkController extends DisposableController
serviceConnection.errorBadgeManager.incrementBadgeCount(NetworkScreen.id);
}
}

@override
OfflineScreenData prepareOfflineScreenData() {
final requests =
filteredData.value.whereType<DartIOHttpRequestData>().toList();
return OfflineScreenData(
screenId: NetworkScreen.id,
data: convertRequestsToMap(requests),
);
}
}

Map<String, Object> convertRequestsToMap(
List<DartIOHttpRequestData>? requests,
) {
if (requests == null) return {};
return {
'requests': requests.map((request) => request.toJson()).toList(),
};
}

/// Class for managing the set of all current sockets, and
Expand Down
52 changes: 39 additions & 13 deletions packages/devtools_app/lib/src/screens/network/network_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -123,19 +123,45 @@ class _NetworkScreenBodyState extends State<NetworkScreenBody>
void didChangeDependencies() {
super.didChangeDependencies();
if (!initController()) return;
unawaited(controller.startRecording());

cancelListeners();
try {
if (offlineDataController.showingOfflineData.value == true) {
loadOfflineData(offlineDataController.offlineDataJson);
}
cancelListeners();
if (!offlineDataController.showingOfflineData.value) {
unawaited(controller.startRecording());
debugPrint('started recording');
addAutoDisposeListener(
serviceConnection.serviceManager.isolateManager.mainIsolate,
() {
if (serviceConnection
.serviceManager.isolateManager.mainIsolate.value !=
null) {
unawaited(controller.startRecording());
}
},
);
}
} catch (ex) {
debugPrint('caught ex $ex');
}
}

addAutoDisposeListener(
serviceConnection.serviceManager.isolateManager.mainIsolate,
() {
if (serviceConnection.serviceManager.isolateManager.mainIsolate.value !=
null) {
unawaited(controller.startRecording());
}
},
);
void loadOfflineData(Map<String, dynamic> offlineData) {
final requestsMap = (offlineData['network']
as Map<String, dynamic>)['requests'] as List<dynamic>;
final requests = requestsMap
.map(
(e) => DartIOHttpRequestData.fromJson(
e as Map<String, dynamic>,
null,
null,
),
)
.toList();
controller.filteredData
..clear()
..addAll(requests);
}

@override
Expand Down Expand Up @@ -187,7 +213,7 @@ class _NetworkProfilerControlsState extends State<_NetworkProfilerControls>
@override
void initState() {
super.initState();

addAutoDisposeListener(offlineDataController.showingOfflineData);
_recording = widget.controller.recordingNotifier.value;
addAutoDisposeListener(widget.controller.recordingNotifier, () {
setState(() {
Expand Down
8 changes: 8 additions & 0 deletions packages/devtools_app/lib/src/shared/feature_flags.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ bool get enableBeta => enableExperiments || !isExternalBuild;
const _kMemoryDisconnectExperience =
bool.fromEnvironment('memory_disconnect_experience', defaultValue: true);

const _kNetworkOfflineExperiment =
bool.fromEnvironment('network_disconnect_experience', defaultValue: true);

// It is ok to have enum-like static only classes.
// ignore: avoid_classes_with_only_static_members
/// Flags to hide features under construction.
Expand All @@ -62,6 +65,11 @@ abstract class FeatureFlags {
/// https://github.com/flutter/devtools/issues/5606
static const memoryDisconnectExperience = _kMemoryDisconnectExperience;

/// Flag to enable offline data on network screen.
///
/// https://github.com/flutter/devtools/issues/3806
static const networkOffline = _kNetworkOfflineExperiment;

/// Flag to enable save/load for the Memory screen.
///
/// https://github.com/flutter/devtools/issues/8019
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,105 @@ class DartIOHttpRequestData extends NetworkRequest {
.._responseBody = responseContent?['text'].toString()
.._requestBody = requestPostData?['text'].toString();
}
//TODO go through all parameters, to check if they are correctly added.
Map<String, Object>? toJson() {
try {
return {
'startedDateTime': startTimestamp.toIso8601String(),
'time': duration?.inMilliseconds ?? 0,
'request': {
'method': method,
'url': uri,
'httpVersion':
'HTTP/2.0', // Assuming HTTP/1.1, can adjust dynamically if needed
'cookies': requestCookies.map((cookie) => cookie.toString()).toList(),
'headers': requestHeaders,
'queryString': queryParameters?.entries
.map((entry) => {'name': entry.key, 'value': entry.value})
.toList(),
'postData': {
'mimeType': contentType,
'text': requestBody ?? 'None',
},
//TODO check headersSize calculation
'headersSize': requestHeaders != null
? requestHeaders!.length * 40
: 0,
'bodySize': requestBody?.length ?? 0,
'connectionInfo': {
'remoteAddress': (general['connectionInfo']
as Map<String, Object?>?)?['remoteAddress'],
'localPort': (general['connectionInfo']
as Map<String, Object?>?)?['localPort'],
},
'contentLength': general['contentLength'] ?? 0,
'followRedirects': _request.request?.followRedirects ?? true,
'maxRedirects': _request.request?.maxRedirects ?? 5,
'persistentConnection':
_request.request?.persistentConnection ?? true,
'proxyDetails': {
'proxy':
(general['proxyDetails'] as Map<String, Object?>?)?['proxy'],
'type': (general['proxyDetails'] as Map<String, Object?>?)?['type'],
},
'error': general['error'],
},
'response': {
'status': status ?? 'Error',
'statusCode': _request.response!.statusCode,
'statusText': '',
//TODO : get http version/ add in constants
'httpVersion': 'HTTP/2.0',
'cookies':
responseCookies.map((cookie) => cookie.toString()).toList(),
'headers': responseHeaders,
'content': {
'size': responseBody?.length ?? 0,
'mimeType':
contentType ?? 'json',
'text': responseBody ?? '',
},
'redirects': _request.response!.redirects,
//TODO : add redirectURL
'redirectURL': '',
'headersSize':
responseHeaders != null ? responseHeaders!.length * 40 : 0,
'bodySize': encodedResponse?.length ?? 0,
},
'cache': {},
'timings': {
'blocked': -1,
'dns': -1,
'connect': -1,
'send': 1,
'wait': duration?.inMilliseconds ?? 0,
'receive': 1,
'ssl': -1,
},
'connection': (general['connectionInfo']
as Map<String, Object?>?)?['connectionId'] ??
'525659655', // sample connection ID
'comment': '',
'isolateId': _request.isolateId,
'type': '@HttpProfileRequest',
'method': method,
'uri': uri,
'id': id,
'startTime': startTimestamp.microsecondsSinceEpoch,
'events': instantEvents
.map(
(event) => {
'timestamp': event.timestamp.microsecondsSinceEpoch,
'event': event.name,
},
)
.toList(),
};
} catch (ex) {
_log.shout('Error in toJson: $ex');
}
return null;
}

static const _connectionInfoKey = 'connectionInfo';
static const _contentTypeKey = 'content-type';
Expand Down
2 changes: 2 additions & 0 deletions packages/devtools_app/lib/src/shared/screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ enum ScreenMetaData {
iconAsset: 'icons/app_bar/network.png',
requiresDartVm: true,
tutorialVideoTimestamp: '?t=547',
// ignore: avoid_redundant_argument_values, false positive
worksWithOfflineData: FeatureFlags.networkOffline,
),
logging(
'logging',
Expand Down
Loading