diff --git a/dwds/CHANGELOG.md b/dwds/CHANGELOG.md index 897abde13..05b66af7f 100644 --- a/dwds/CHANGELOG.md +++ b/dwds/CHANGELOG.md @@ -1,6 +1,7 @@ ## 24.1.0-wip - Fix bug where debugging clients are not aware of service extensions when connecting to a new web app. - [#2388](https://github.com/dart-lang/webdev/pull/2388) +- Respect the value of `pause_isolates_on_start` during page-refreshes. - [#2431](https://github.com/dart-lang/webdev/pull/2431) ## 24.0.0 diff --git a/dwds/lib/dart_web_debug_service.dart b/dwds/lib/dart_web_debug_service.dart index 45aedc6df..cd78ab82e 100644 --- a/dwds/lib/dart_web_debug_service.dart +++ b/dwds/lib/dart_web_debug_service.dart @@ -139,7 +139,4 @@ class Dwds { debugSettings.enableDebugging, ); } - - bool shouldPauseIsolatesOnStart(String appId) => - _devHandler.shouldPauseIsolatesOnStart(appId); } diff --git a/dwds/lib/src/connections/app_connection.dart b/dwds/lib/src/connections/app_connection.dart index 754d51516..8268d674d 100644 --- a/dwds/lib/src/connections/app_connection.dart +++ b/dwds/lib/src/connections/app_connection.dart @@ -18,8 +18,9 @@ class AppConnection { final _startedCompleter = Completer(); final _doneCompleter = Completer(); final SocketConnection _connection; + final Future _readyToRunMain; - AppConnection(this.request, this._connection) { + AppConnection(this.request, this._connection, this._readyToRunMain) { safeUnawaited(_connection.sink.done.then((v) => _doneCompleter.complete())); } @@ -34,6 +35,12 @@ class AppConnection { if (_startedCompleter.isCompleted) { throw StateError('Main has already started.'); } + + safeUnawaited(_runMain()); + } + + Future _runMain() async { + await _readyToRunMain; _connection.sink.add(jsonEncode(serializers.serialize(RunRequest()))); _startedCompleter.complete(); } diff --git a/dwds/lib/src/dwds_vm_client.dart b/dwds/lib/src/dwds_vm_client.dart index 5c1e9dce8..1235ba6b3 100644 --- a/dwds/lib/src/dwds_vm_client.dart +++ b/dwds/lib/src/dwds_vm_client.dart @@ -428,7 +428,7 @@ void _waitForResumeEventToRunMain( final issuedReadyToRunMainCompleter = Completer(); final resumeEventsSubscription = - chromeProxyService.resumeAfterHotRestartEventsStream.listen((_) async { + chromeProxyService.resumeAfterRestartEventsStream.listen((_) async { await chromeProxyService.inspector.jsEvaluate('\$dartReadyToRunMain();'); if (!issuedReadyToRunMainCompleter.isCompleted) { issuedReadyToRunMainCompleter.complete(); diff --git a/dwds/lib/src/handlers/dev_handler.dart b/dwds/lib/src/handlers/dev_handler.dart index 13cbb01bb..e5d4bdf7e 100644 --- a/dwds/lib/src/handlers/dev_handler.dart +++ b/dwds/lib/src/handlers/dev_handler.dart @@ -121,9 +121,6 @@ class DevHandler { _servicesByAppId.clear(); }(); - bool shouldPauseIsolatesOnStart(String appId) => - _servicesByAppId[appId]?.chromeProxyService.pauseIsolatesOnStart ?? false; - void _emitBuildResults(BuildResult result) { if (result.status != BuildStatus.succeeded) return; for (var injectedConnection in _injectedConnections) { @@ -441,7 +438,12 @@ class DevHandler { // were previously launched and create the new isolate. final services = _servicesByAppId[message.appId]; final existingConnection = _appConnectionByAppId[message.appId]; - final connection = AppConnection(message, sseConnection); + // Completer to indicate when the app's main() method is ready to be run. + // Its future is passed to the AppConnection so that it can be awaited on + // before running the app's main() method: + final readyToRunMainCompleter = Completer(); + final connection = + AppConnection(message, sseConnection, readyToRunMainCompleter.future); // We can take over a connection if there is no connectedInstanceId (this // means the client completely disconnected), or if the existing @@ -460,13 +462,53 @@ class DevHandler { // Reconnect to existing service. services.connectedInstanceId = message.instanceId; + + if (services.chromeProxyService.pauseIsolatesOnStart) { + // If the pause-isolates-on-start flag is set, we need to wait for + // the resume event to run the app's main() method. + _waitForResumeEventToRunMain( + services.chromeProxyService.resumeAfterRestartEventsStream, + readyToRunMainCompleter, + ); + } else { + // Otherwise, we can run the app's main() method immediately. + readyToRunMainCompleter.complete(); + } + await services.chromeProxyService.createIsolate(connection); + } else { + // If this is the initial app connection, we can run the app's main() + // method immediately. + readyToRunMainCompleter.complete(); } _appConnectionByAppId[message.appId] = connection; _connectedApps.add(connection); return connection; } + /// Waits for a resume event to trigger the app's main() method. + /// + /// The [readyToRunMainCompleter]'s future will be passed to the + /// [AppConnection] so that it can be awaited on before running the app's + /// main() method. + void _waitForResumeEventToRunMain( + Stream resumeEventsStream, + Completer readyToRunMainCompleter, + ) { + final resumeEventsSubscription = resumeEventsStream.listen((_) { + readyToRunMainCompleter.complete(); + if (!readyToRunMainCompleter.isCompleted) { + readyToRunMainCompleter.complete(); + } + }); + + safeUnawaited( + readyToRunMainCompleter.future.then((_) { + resumeEventsSubscription.cancel(); + }), + ); + } + void _handleIsolateExit(AppConnection appConnection) { _servicesByAppId[appConnection.request.appId] ?.chromeProxyService diff --git a/dwds/lib/src/services/chrome_proxy_service.dart b/dwds/lib/src/services/chrome_proxy_service.dart index 519e98118..7b552bc93 100644 --- a/dwds/lib/src/services/chrome_proxy_service.dart +++ b/dwds/lib/src/services/chrome_proxy_service.dart @@ -99,18 +99,18 @@ class ChromeProxyService implements VmServiceInterface { /// This value can be updated at runtime via [setFlag]. bool get pauseIsolatesOnStart => _pauseIsolatesOnStart; - final _resumeAfterHotRestartEventsController = + final _resumeAfterRestartEventsController = StreamController.broadcast(); /// A global stream of resume events. /// /// The values in the stream are the isolates IDs for the resume event. /// - /// IMPORTANT: This should only be listened to during a hot-restart. The - /// debugger ignores any resume events as long as there is a subscriber to - /// this stream. - Stream get resumeAfterHotRestartEventsStream => - _resumeAfterHotRestartEventsController.stream; + /// IMPORTANT: This should only be listened to during a hot-restart or page + /// refresh. The debugger ignores any resume events as long as there is a + /// subscriber to this stream. + Stream get resumeAfterRestartEventsStream => + _resumeAfterRestartEventsController.stream; final _logger = Logger('ChromeProxyService'); @@ -1147,8 +1147,8 @@ ${globalToolConfiguration.loadStrategy.loadModuleSnippet}("dart_sdk").developer. }) async { // If there is a subscriber listening for a resume event after hot-restart, // then add the event to the stream and skip processing it. - if (_resumeAfterHotRestartEventsController.hasListener) { - _resumeAfterHotRestartEventsController.add(isolateId); + if (_resumeAfterRestartEventsController.hasListener) { + _resumeAfterRestartEventsController.add(isolateId); return Success(); } if (inspector.appConnection.isStarted) { diff --git a/dwds/test/chrome_proxy_service_test.dart b/dwds/test/chrome_proxy_service_test.dart index ba70c1262..44c3fd52c 100644 --- a/dwds/test/chrome_proxy_service_test.dart +++ b/dwds/test/chrome_proxy_service_test.dart @@ -2070,9 +2070,8 @@ void main() { service.setFlag('pause_isolates_on_start', 'true'), completion(_isSuccess), ); - final appId = context.appConnection.request.appId; expect( - context.dwds!.shouldPauseIsolatesOnStart(appId), + context.service.pauseIsolatesOnStart, equals(true), ); }); @@ -2083,9 +2082,8 @@ void main() { service.setFlag('pause_isolates_on_start', 'false'), completion(_isSuccess), ); - final appId = context.appConnection.request.appId; expect( - context.dwds!.shouldPauseIsolatesOnStart(appId), + context.service.pauseIsolatesOnStart, equals(false), ); }); diff --git a/dwds/test/reload_test.dart b/dwds/test/reload_test.dart index fb5f17e23..e9b23712d 100644 --- a/dwds/test/reload_test.dart +++ b/dwds/test/reload_test.dart @@ -531,7 +531,8 @@ void main() { undoEdit(); }); - test('does not run app until there is a resume event', () async { + test('after hot-restart, does not run app until there is a resume event', + () async { await makeEditAndWaitForRebuild(); final eventsDone = expectLater( @@ -563,6 +564,36 @@ void main() { final sourceAfterResume = await context.webDriver.pageSource; expect(sourceAfterResume.contains(newString), isTrue); }); + + test('after page refresh, does not run app until there is a resume event', + () async { + await makeEditAndWaitForRebuild(); + + await context.webDriver.driver.refresh(); + + final eventsDone = expectLater( + client.onIsolateEvent, + emitsThrough( + emitsInOrder([ + _hasKind(EventKind.kIsolateExit), + _hasKind(EventKind.kIsolateStart), + _hasKind(EventKind.kIsolateRunnable), + ]), + ), + ); + + await eventsDone; + + final sourceBeforeResume = await context.webDriver.pageSource; + expect(sourceBeforeResume.contains(newString), isFalse); + + final vm = await client.getVM(); + final isolateId = vm.isolates!.first.id!; + await client.resume(isolateId); + + final sourceAfterResume = await context.webDriver.pageSource; + expect(sourceAfterResume.contains(newString), isTrue); + }); }); }