Skip to content

Commit 91c7b41

Browse files
committed
support page refresh from vsCode
1 parent 8f7c645 commit 91c7b41

File tree

2 files changed

+114
-50
lines changed

2 files changed

+114
-50
lines changed

dwds/lib/src/handlers/dev_handler.dart

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -341,10 +341,13 @@ class DevHandler {
341341
'https://github.com/dart-lang/webdev/issues/new.',
342342
);
343343
}
344-
appConnection = await _handleConnectRequest(
345-
message,
346-
injectedConnection,
347-
);
344+
appConnection =
345+
useWebSocketConnection
346+
? await _handleWebSocketConnectRequest(
347+
message,
348+
injectedConnection,
349+
)
350+
: await _handleConnectRequest(message, injectedConnection);
348351
} else {
349352
final connection = appConnection;
350353
if (connection == null) {
@@ -616,11 +619,6 @@ class DevHandler {
616619
ConnectRequest message,
617620
SocketConnection sseConnection,
618621
) async {
619-
if (useWebSocketConnection) {
620-
return _handleWebSocketConnectRequest(message, sseConnection);
621-
}
622-
623-
// Original Chrome logic from dart-lang/webdev
624622
// After a page refresh, reconnect to the same app services if they
625623
// were previously launched and create the new isolate.
626624
final services = _servicesByAppId[message.appId];

dwds/lib/src/services/web_socket_proxy_service.dart

Lines changed: 107 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import 'package:dwds/src/events.dart';
1616
import 'package:dwds/src/utilities/dart_uri.dart';
1717
import 'package:dwds/src/utilities/shared.dart';
1818
import 'package:logging/logging.dart';
19-
// Ensure RPCError and RPCErrorKind are available for error handling
2019
import 'package:pub_semver/pub_semver.dart' as semver;
2120
import 'package:vm_service/vm_service.dart' as vm_service;
2221
import 'package:vm_service/vm_service.dart';
@@ -85,8 +84,6 @@ class _ServiceExtensionTracker {
8584
}
8685

8786
/// WebSocket-based VM service proxy for web debugging.
88-
///
89-
/// Provides hot reload and service extension support via WebSocket communication.
9087
class WebSocketProxyService implements VmServiceInterface {
9188
final _logger = Logger('WebSocketProxyService');
9289

@@ -102,7 +99,7 @@ class WebSocketProxyService implements VmServiceInterface {
10299
final SendClientRequest sendClientRequest;
103100

104101
/// App connection for this service.
105-
final AppConnection appConnection;
102+
AppConnection appConnection;
106103

107104
/// Active hot reload trackers by request ID.
108105
final Map<String, _HotReloadTracker> _pendingHotReloads = {};
@@ -118,6 +115,26 @@ class WebSocketProxyService implements VmServiceInterface {
118115
_pauseIsolatesOnStartFlag: false,
119116
};
120117

118+
/// Stream controller for resume events after restart.
119+
final _resumeAfterRestartEventsController =
120+
StreamController<String>.broadcast();
121+
122+
/// Stream of resume events after restart.
123+
Stream<String> get resumeAfterRestartEventsStream =>
124+
_resumeAfterRestartEventsController.stream;
125+
126+
/// Whether there's a pending restart.
127+
bool get hasPendingRestart => _resumeAfterRestartEventsController.hasListener;
128+
129+
/// Whether isolates should pause on start.
130+
bool get pauseIsolatesOnStart =>
131+
_currentVmServiceFlags[_pauseIsolatesOnStartFlag] ?? false;
132+
133+
/// Counter for generating unique isolate IDs across page refreshes
134+
static int _globalIsolateIdCounter = 0;
135+
136+
bool get _isIsolateRunning => _isolateRunning;
137+
121138
/// Root VM instance.
122139
final vm_service.VM _vm;
123140

@@ -131,12 +148,19 @@ class WebSocketProxyService implements VmServiceInterface {
131148
vm_service.Event? _currentPauseEvent;
132149
bool _hasResumed = false;
133150

134-
bool get _isIsolateRunning => _isolateRunning;
135-
136151
/// Creates a new isolate for WebSocket debugging.
137152
Future<void> createIsolate([AppConnection? appConnectionOverride]) async {
138153
final appConn = appConnectionOverride ?? appConnection;
139154

155+
// Update the app connection reference if a new one is provided
156+
if (appConnectionOverride != null) {
157+
_logger.fine(
158+
'Updating appConnection reference from '
159+
'instanceId: ${appConnection.request.instanceId} to instanceId: ${appConnectionOverride.request.instanceId}',
160+
);
161+
appConnection = appConnectionOverride;
162+
}
163+
140164
// Clean up existing isolate
141165
if (_isIsolateRunning) {
142166
destroyIsolate();
@@ -149,11 +173,12 @@ class WebSocketProxyService implements VmServiceInterface {
149173
destroyIsolate();
150174
});
151175

152-
// Create isolate reference
176+
// Create isolate reference with unique ID that changes on each page refresh
177+
final isolateId = '${++_globalIsolateIdCounter}';
153178
final isolateRef = vm_service.IsolateRef(
154-
id: '1',
179+
id: isolateId,
155180
name: 'main()',
156-
number: '1',
181+
number: isolateId,
157182
isSystemIsolate: false,
158183
);
159184

@@ -766,21 +791,6 @@ class WebSocketProxyService implements VmServiceInterface {
766791
return UriList(uris: uris.map(DartUri.toResolvedUri).toList());
767792
}
768793

769-
/// Stream controller for resume events after restart.
770-
final _resumeAfterRestartEventsController =
771-
StreamController<String>.broadcast();
772-
773-
/// Stream of resume events after restart.
774-
Stream<String> get resumeAfterRestartEventsStream =>
775-
_resumeAfterRestartEventsController.stream;
776-
777-
/// Whether there's a pending restart.
778-
bool get hasPendingRestart => _resumeAfterRestartEventsController.hasListener;
779-
780-
/// Whether isolates should pause on start.
781-
bool get pauseIsolatesOnStart =>
782-
_currentVmServiceFlags[_pauseIsolatesOnStartFlag] ?? false;
783-
784794
/// Resumes execution of the isolate.
785795
@override
786796
Future<Success> resume(String isolateId, {String? step, int? frameIndex}) =>
@@ -794,31 +804,87 @@ class WebSocketProxyService implements VmServiceInterface {
794804
String? step,
795805
int? frameIndex,
796806
}) async {
797-
// Trigger runMain if this is the first resume
798-
if (!_hasResumed && _currentPauseEvent != null) {
807+
if (hasPendingRestart && !_resumeAfterRestartEventsController.isClosed) {
808+
_resumeAfterRestartEventsController.add(isolateId);
809+
} else {
799810
appConnection.runMain();
800811
}
812+
return Success();
813+
}
801814

802-
// Prevent multiple resume calls
803-
if (_hasResumed && _currentPauseEvent == null) {
804-
return Success();
805-
}
815+
@override
816+
Future<UriList> lookupPackageUris(String isolateId, List<String> uris) =>
817+
wrapInErrorHandlerAsync(
818+
'lookupPackageUris',
819+
() => _lookupPackageUris(isolateId, uris),
820+
);
806821

807-
_currentPauseEvent = null;
808-
_hasResumed = true;
822+
Future<UriList> _lookupPackageUris(
823+
String isolateId,
824+
List<String> uris,
825+
) async {
826+
await isInitialized;
827+
return UriList(uris: uris.map(DartUri.toPackageUri).toList());
828+
}
809829

810-
// Send resume event
811-
if (_isolateRef != null) {
812-
final resumeEvent = vm_service.Event(
813-
kind: vm_service.EventKind.kResume,
814-
timestamp: DateTime.now().millisecondsSinceEpoch,
815-
isolate: _isolateRef!,
816-
);
830+
@override
831+
Future<Success> registerService(String service, String alias) {
832+
return _rpcNotSupportedFuture('registerService');
833+
}
817834

818-
_streamNotify(vm_service.EventStreams.kDebug, resumeEvent);
835+
@override
836+
Future<FlagList> getFlagList() =>
837+
wrapInErrorHandlerAsync('getFlagList', _getFlagList);
838+
839+
Future<FlagList> _getFlagList() async {
840+
// Return basic flag list for WebSocket mode
841+
return FlagList(
842+
flags: [
843+
Flag(
844+
name: _pauseIsolatesOnStartFlag,
845+
comment: 'If enabled, isolates are paused on start',
846+
valueAsString: pauseIsolatesOnStart.toString(),
847+
),
848+
],
849+
);
850+
}
851+
852+
@override
853+
Future<vm_service.Stack> getStack(
854+
String isolateId, {
855+
String? idZoneId,
856+
int? limit,
857+
}) => wrapInErrorHandlerAsync(
858+
'getStack',
859+
() => _getStack(isolateId, idZoneId: idZoneId, limit: limit),
860+
);
861+
862+
Future<vm_service.Stack> _getStack(
863+
String isolateId, {
864+
String? idZoneId,
865+
int? limit,
866+
}) async {
867+
if (!_isIsolateRunning || _isolateRef == null) {
868+
throw vm_service.RPCError(
869+
'getStack',
870+
vm_service.RPCErrorKind.kInvalidParams.code,
871+
'No running isolate found for id: $isolateId',
872+
);
873+
}
874+
if (_isolateRef!.id != isolateId) {
875+
throw vm_service.RPCError(
876+
'getStack',
877+
vm_service.RPCErrorKind.kInvalidParams.code,
878+
'Isolate with id $isolateId not found.',
879+
);
819880
}
820881

821-
return Success();
882+
// Return empty stack since we're in WebSocket mode without Chrome debugging
883+
return vm_service.Stack(
884+
frames: [],
885+
asyncCausalFrames: [],
886+
awaiterFrames: [],
887+
);
822888
}
823889

824890
@override

0 commit comments

Comments
 (0)