From 17c92b7ba68ea609f4eb3405211d019c9dbc4d27 Mon Sep 17 00:00:00 2001 From: "auto-submit[bot]" <98614782+auto-submit[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 22:15:48 +0000 Subject: [PATCH] =?UTF-8?q?Reverts=20"Reapply=20"Make=20device=20debuggabl?= =?UTF-8?q?e=20if=20useDwdsWebSocketConnection=20is=20true=20=E2=80=A6=20(?= =?UTF-8?q?#173551)"=20(#173568)"=20(#173587)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reverts: flutter/flutter#173568 Initiated by: bkonyi Reason for reverting: Chrome isn't configured for the test configuration and is causing failures. Original PR Author: bkonyi Reviewed By: {jyameo, matanlurey} This change reverts the following previous change: This reverts commit e2a347b14a18729dd24c2cf3240ef22562195e2d. Previously reverted due to `*_chrome_dev_mode` tests failing on all platforms. Co-authored-by: auto-submit[bot] --- .../bin/tasks/linux_chrome_dev_mode.dart | 2 +- .../bin/tasks/macos_chrome_dev_mode.dart | 2 +- .../bin/tasks/windows_chrome_dev_mode.dart | 2 +- dev/devicelab/lib/framework/browser.dart | 14 +- dev/devicelab/lib/framework/devices.dart | 92 ------ dev/devicelab/lib/tasks/perf_tests.dart | 9 - .../lib/tasks/web_dev_mode_tests.dart | 275 ++++++++++-------- .../lib/src/isolated/resident_web_runner.dart | 215 +++++++------- packages/flutter_tools/pubspec.yaml | 4 +- .../resident_web_runner_test.dart | 32 +- .../hot_reload_websocket_test.dart | 193 ------------ .../test/integration.shard/test_driver.dart | 13 +- 12 files changed, 279 insertions(+), 574 deletions(-) delete mode 100644 packages/flutter_tools/test/integration.shard/hot_reload_websocket_test.dart diff --git a/dev/devicelab/bin/tasks/linux_chrome_dev_mode.dart b/dev/devicelab/bin/tasks/linux_chrome_dev_mode.dart index e8553eec831..da3e6226463 100644 --- a/dev/devicelab/bin/tasks/linux_chrome_dev_mode.dart +++ b/dev/devicelab/bin/tasks/linux_chrome_dev_mode.dart @@ -6,5 +6,5 @@ import 'package:flutter_devicelab/framework/framework.dart'; import 'package:flutter_devicelab/tasks/web_dev_mode_tests.dart'; Future main() async { - await task(createWebDevModeTest()); + await task(createWebDevModeTest(WebDevice.webServer, false)); } diff --git a/dev/devicelab/bin/tasks/macos_chrome_dev_mode.dart b/dev/devicelab/bin/tasks/macos_chrome_dev_mode.dart index e8553eec831..da3e6226463 100644 --- a/dev/devicelab/bin/tasks/macos_chrome_dev_mode.dart +++ b/dev/devicelab/bin/tasks/macos_chrome_dev_mode.dart @@ -6,5 +6,5 @@ import 'package:flutter_devicelab/framework/framework.dart'; import 'package:flutter_devicelab/tasks/web_dev_mode_tests.dart'; Future main() async { - await task(createWebDevModeTest()); + await task(createWebDevModeTest(WebDevice.webServer, false)); } diff --git a/dev/devicelab/bin/tasks/windows_chrome_dev_mode.dart b/dev/devicelab/bin/tasks/windows_chrome_dev_mode.dart index e8553eec831..da3e6226463 100644 --- a/dev/devicelab/bin/tasks/windows_chrome_dev_mode.dart +++ b/dev/devicelab/bin/tasks/windows_chrome_dev_mode.dart @@ -6,5 +6,5 @@ import 'package:flutter_devicelab/framework/framework.dart'; import 'package:flutter_devicelab/tasks/web_dev_mode_tests.dart'; Future main() async { - await task(createWebDevModeTest()); + await task(createWebDevModeTest(WebDevice.webServer, false)); } diff --git a/dev/devicelab/lib/framework/browser.dart b/dev/devicelab/lib/framework/browser.dart index 95a8d61b535..20181e1c5f9 100644 --- a/dev/devicelab/lib/framework/browser.dart +++ b/dev/devicelab/lib/framework/browser.dart @@ -26,7 +26,6 @@ class ChromeOptions { this.headless, this.debugPort, this.enableWasmGC = false, - this.silent = false, }); /// If not null passed as `--user-data-dir`. @@ -58,9 +57,6 @@ class ChromeOptions { /// Whether to enable experimental WasmGC flags final bool enableWasmGC; - - /// Disables Chrome stdio outputs. - final bool silent; } /// A function called when the Chrome process encounters an error. @@ -123,7 +119,6 @@ class Chrome { final io.Process chromeProcess = await _spawnChromiumProcess( _findSystemChromeExecutable(), args, - silent: options.silent, workingDirectory: workingDirectory, ); @@ -615,7 +610,6 @@ const String _kGlibcError = 'Inconsistency detected by ld.so'; Future _spawnChromiumProcess( String executable, List args, { - required bool silent, String? workingDirectory, }) async { // Keep attempting to launch the browser until one of: @@ -629,9 +623,7 @@ Future _spawnChromiumProcess( ); process.stdout.transform(utf8.decoder).transform(const LineSplitter()).listen((String line) { - if (!silent) { - print('[CHROME STDOUT]: $line'); - } + print('[CHROME STDOUT]: $line'); }); // Wait until the DevTools are listening before trying to connect. This is @@ -641,9 +633,7 @@ Future _spawnChromiumProcess( .transform(utf8.decoder) .transform(const LineSplitter()) .map((String line) { - if (!silent) { - print('[CHROME STDERR]:$line'); - } + print('[CHROME STDERR]:$line'); if (line.contains(_kGlibcError)) { hitGlibcBug = true; } diff --git a/dev/devicelab/lib/framework/devices.dart b/dev/devicelab/lib/framework/devices.dart index e43e52f4123..0077ab1b7ee 100644 --- a/dev/devicelab/lib/framework/devices.dart +++ b/dev/devicelab/lib/framework/devices.dart @@ -56,7 +56,6 @@ enum DeviceOperatingSystem { ios, linux, macos, - webServer, windows, } @@ -81,8 +80,6 @@ abstract class DeviceDiscovery { return LinuxDeviceDiscovery(); case DeviceOperatingSystem.macos: return MacosDeviceDiscovery(); - case DeviceOperatingSystem.webServer: - return WebServerDeviceDiscovery(); case DeviceOperatingSystem.windows: return WindowsDeviceDiscovery(); case DeviceOperatingSystem.fake: @@ -434,40 +431,6 @@ class MacosDeviceDiscovery implements DeviceDiscovery { Future get workingDevice async => _device; } -class WebServerDeviceDiscovery implements DeviceDiscovery { - factory WebServerDeviceDiscovery() { - return _instance ??= WebServerDeviceDiscovery._(); - } - - WebServerDeviceDiscovery._(); - - static WebServerDeviceDiscovery? _instance; - - static const WebServerDevice _device = WebServerDevice(); - - @override - Future> checkDevices() async { - return {}; - } - - @override - Future chooseWorkingDevice() async {} - - @override - Future chooseWorkingDeviceById(String deviceId) async {} - - @override - Future> discoverDevices() async { - return ['web-server']; - } - - @override - Future performPreflightTasks() async {} - - @override - Future get workingDevice async => _device; -} - class WindowsDeviceDiscovery implements DeviceDiscovery { factory WindowsDeviceDiscovery() { return _instance ??= WindowsDeviceDiscovery._(); @@ -1297,61 +1260,6 @@ class MacosDevice extends Device { Future awaitDevice() async {} } -class WebServerDevice extends Device { - const WebServerDevice(); - - @override - String get deviceId => 'web-server'; - - @override - Future> getMemoryStats(String packageName) async { - return {}; - } - - @override - Future home() async {} - - @override - Future isAsleep() async { - return false; - } - - @override - Future isAwake() async { - return true; - } - - @override - Stream get logcat => const Stream.empty(); - - @override - Future clearLogs() async {} - - @override - Future reboot() async {} - - @override - Future sendToSleep() async {} - - @override - Future stop(String packageName) async {} - - @override - Future tap(int x, int y) async {} - - @override - Future togglePower() async {} - - @override - Future unlock() async {} - - @override - Future wakeUp() async {} - - @override - Future awaitDevice() async {} -} - class WindowsDevice extends Device { const WindowsDevice(); diff --git a/dev/devicelab/lib/tasks/perf_tests.dart b/dev/devicelab/lib/tasks/perf_tests.dart index cf746b2538c..a38e177c074 100644 --- a/dev/devicelab/lib/tasks/perf_tests.dart +++ b/dev/devicelab/lib/tasks/perf_tests.dart @@ -1002,8 +1002,6 @@ class StartupTest { ); final String buildRoot = path.join(testDirectory, 'build'); applicationBinaryPath = _findDarwinAppInBuildDirectory(buildRoot); - case DeviceOperatingSystem.webServer: - break; case DeviceOperatingSystem.windows: await flutter( 'build', @@ -1155,7 +1153,6 @@ class DevtoolsStartupTest { case DeviceOperatingSystem.fuchsia: case DeviceOperatingSystem.linux: case DeviceOperatingSystem.macos: - case DeviceOperatingSystem.webServer: case DeviceOperatingSystem.windows: break; } @@ -1496,7 +1493,6 @@ class PerfTest { case DeviceOperatingSystem.fuchsia: case DeviceOperatingSystem.linux: case DeviceOperatingSystem.macos: - case DeviceOperatingSystem.webServer: case DeviceOperatingSystem.windows: recordGPU = false; } @@ -2015,8 +2011,6 @@ class CompileTest { throw Exception('Unsupported option for Fuchsia devices'); case DeviceOperatingSystem.linux: throw Exception('Unsupported option for Linux devices'); - case DeviceOperatingSystem.webServer: - throw Exception('Unsupported option for Web Server devices'); case DeviceOperatingSystem.windows: unawaited(stderr.flush()); options.insert(0, 'windows'); @@ -2084,8 +2078,6 @@ class CompileTest { case DeviceOperatingSystem.macos: unawaited(stderr.flush()); options.insert(0, 'macos'); - case DeviceOperatingSystem.webServer: - throw Exception('Unsupported option for Web Server devices'); case DeviceOperatingSystem.windows: unawaited(stderr.flush()); options.insert(0, 'windows'); @@ -2118,7 +2110,6 @@ class CompileTest { case DeviceOperatingSystem.fake: case DeviceOperatingSystem.fuchsia: case DeviceOperatingSystem.linux: - case DeviceOperatingSystem.webServer: case DeviceOperatingSystem.windows: throw Exception('Called ${CompileTest.getSizesFromDarwinApp} with $operatingSystem.'); } diff --git a/dev/devicelab/lib/tasks/web_dev_mode_tests.dart b/dev/devicelab/lib/tasks/web_dev_mode_tests.dart index 1f05cf4519f..2dbb7121b67 100644 --- a/dev/devicelab/lib/tasks/web_dev_mode_tests.dart +++ b/dev/devicelab/lib/tasks/web_dev_mode_tests.dart @@ -9,8 +9,6 @@ import 'dart:io'; import 'package:path/path.dart' as path; import 'package:yaml_edit/yaml_edit.dart'; -import '../framework/browser.dart'; -import '../framework/devices.dart'; import '../framework/framework.dart'; import '../framework/task_result.dart'; import '../framework/utils.dart'; @@ -33,123 +31,25 @@ const String kFirstRecompileTime = 'FirstRecompileTime'; const String kSecondStartupTime = 'SecondStartupTime'; const String kSecondRestartTime = 'SecondRestartTime'; -const String kWebServerDevice = 'web-server'; - -final RegExp servedAtPattern = RegExp('is being served at (.*)'); -int hotRestartCount = 0; - -Future> launch({required bool isFirstRun}) async { - final List options = [ - '--hot', - '-d', - kWebServerDevice, - '--verbose', - '--resident', - '--target=lib/main.dart', - ]; - final Process process = await startFlutter('run', options: options); - - final List measurements = []; - - final Completer stdoutDone = Completer(); - final Completer stderrDone = Completer(); - final Completer waitForService = Completer(); - final Stopwatch sw = Stopwatch()..start(); - Chrome? chrome; - bool restarted = false; - process - ..stdout - .transform(utf8.decoder) - .transform(const LineSplitter()) - .listen( - (String line) async { - if (line.contains(servedAtPattern)) { - final String url = servedAtPattern.firstMatch(line)!.group(1)!; - chrome = await Chrome.launch( - ChromeOptions(url: url, headless: true, silent: true, debugPort: 10000), - onError: (String e) {}, - ); - } - if (line.contains('DevHandler: Debug service listening on')) { - waitForService.complete(); - } - // non-dwds builds do not know when the browser is loaded so keep trying - // until this succeeds. - if (line.contains('Ignoring terminal input')) { - unawaited( - Future.delayed(const Duration(seconds: 1)).then((void _) { - process.stdin.write(restarted ? 'q' : 'r'); - }), - ); - return; - } - if (line.contains('Hot restart')) { - unawaited( - waitForService.future.then((_) { - // measure clean start-up time. - sw.stop(); - measurements.add(sw.elapsedMilliseconds); - sw - ..reset() - ..start(); - process.stdin.write('r'); - }), - ); - return; - } - if (line.contains('Reloaded application')) { - if (hotRestartCount == 0) { - assert(isFirstRun); - measurements.add(sw.elapsedMilliseconds); - // Update the file and reload again. - final File appDartSource = file( - path.join(_editedFlutterGalleryDir.path, 'lib/gallery/app.dart'), - ); - appDartSource.writeAsStringSync( - appDartSource.readAsStringSync().replaceFirst( - "'Flutter Gallery'", - "'Updated Flutter Gallery'", - ), - ); - sw - ..reset() - ..start(); - process.stdin.writeln('r'); - ++hotRestartCount; - } else { - restarted = true; - measurements.add(sw.elapsedMilliseconds); - // Quit after second hot restart. - process.stdin.writeln('q'); - } - } - print('stdout: $line'); - }, - onDone: () { - stdoutDone.complete(); - }, - ) - ..stderr - .transform(utf8.decoder) - .transform(const LineSplitter()) - .listen( - (String line) { - print('stderr: $line'); - }, - onDone: () { - stderrDone.complete(); - }, - ); - - await Future.wait(>[stdoutDone.future, stderrDone.future]); - await process.exitCode; - chrome?.stop(); - return measurements; +abstract class WebDevice { + static const String chrome = 'chrome'; + static const String webServer = 'web-server'; } -TaskFunction createWebDevModeTest() { - deviceOperatingSystem = DeviceOperatingSystem.webServer; +TaskFunction createWebDevModeTest(String webDevice, bool enableIncrementalCompiler) { return () async { + final List options = [ + '--hot', + '-d', + webDevice, + '--verbose', + '--resident', + '--target=lib/main.dart', + ]; + int hotRestartCount = 0; + final String expectedMessage = webDevice == WebDevice.webServer + ? 'Recompile complete' + : 'Reloaded application'; final Map measurements = {}; await inDirectory(flutterDirectory, () async { rmTree(_editedFlutterGalleryDir); @@ -166,20 +66,139 @@ TaskFunction createWebDevModeTest() { ).writeAsStringSync(yamlEditor.toString()); await inDirectory(_editedFlutterGalleryDir, () async { - await flutter('packages', options: ['get']); - final List firstMeasurements = await launch(isFirstRun: true); - measurements.addAll({ - kInitialStartupTime: firstMeasurements[0], - kFirstRecompileTime: firstMeasurements[1], - kFirstRestartTime: firstMeasurements[2], - }); + { + await flutter('packages', options: ['get']); + final Process process = await startFlutter('run', options: options); + + final Completer stdoutDone = Completer(); + final Completer stderrDone = Completer(); + final Stopwatch sw = Stopwatch()..start(); + bool restarted = false; + process.stdout + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen( + (String line) { + // non-dwds builds do not know when the browser is loaded so keep trying + // until this succeeds. + if (line.contains('Ignoring terminal input')) { + Future.delayed(const Duration(seconds: 1)).then((void _) { + process.stdin.write(restarted ? 'q' : 'r'); + }); + return; + } + if (line.contains('Hot restart')) { + // measure clean start-up time. + sw.stop(); + measurements[kInitialStartupTime] = sw.elapsedMilliseconds; + sw + ..reset() + ..start(); + process.stdin.write('r'); + return; + } + if (line.contains(expectedMessage)) { + if (hotRestartCount == 0) { + measurements[kFirstRestartTime] = sw.elapsedMilliseconds; + // Update the file and reload again. + final File appDartSource = file( + path.join(_editedFlutterGalleryDir.path, 'lib/gallery/app.dart'), + ); + appDartSource.writeAsStringSync( + appDartSource.readAsStringSync().replaceFirst( + "'Flutter Gallery'", + "'Updated Flutter Gallery'", + ), + ); + sw + ..reset() + ..start(); + process.stdin.writeln('r'); + ++hotRestartCount; + } else { + restarted = true; + measurements[kFirstRecompileTime] = sw.elapsedMilliseconds; + // Quit after second hot restart. + process.stdin.writeln('q'); + } + } + print('stdout: $line'); + }, + onDone: () { + stdoutDone.complete(); + }, + ); + process.stderr + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen( + (String line) { + print('stderr: $line'); + }, + onDone: () { + stderrDone.complete(); + }, + ); + + await Future.wait(>[stdoutDone.future, stderrDone.future]); + await process.exitCode; + } + // Start `flutter run` again to make sure it loads from the previous // state. dev compilers loads up from previously compiled JavaScript. - final List secondMeasurements = await launch(isFirstRun: false); - measurements.addAll({ - kSecondStartupTime: secondMeasurements[0], - kSecondRestartTime: secondMeasurements[1], - }); + { + final Stopwatch sw = Stopwatch()..start(); + final Process process = await startFlutter('run', options: options); + final Completer stdoutDone = Completer(); + final Completer stderrDone = Completer(); + bool restarted = false; + process.stdout + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen( + (String line) { + // non-dwds builds do not know when the browser is loaded so keep trying + // until this succeeds. + if (line.contains('Ignoring terminal input')) { + Future.delayed(const Duration(seconds: 1)).then((void _) { + process.stdin.write(restarted ? 'q' : 'r'); + }); + return; + } + if (line.contains('Hot restart')) { + measurements[kSecondStartupTime] = sw.elapsedMilliseconds; + sw + ..reset() + ..start(); + process.stdin.write('r'); + return; + } + if (line.contains(expectedMessage)) { + restarted = true; + measurements[kSecondRestartTime] = sw.elapsedMilliseconds; + process.stdin.writeln('q'); + } + print('stdout: $line'); + }, + onDone: () { + stdoutDone.complete(); + }, + ); + process.stderr + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen( + (String line) { + print('stderr: $line'); + }, + onDone: () { + stderrDone.complete(); + }, + ); + + await Future.wait(>[stdoutDone.future, stderrDone.future]); + await process.exitCode; + } }); }); if (hotRestartCount != 1) { diff --git a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart index 1eeda8d8415..25173ae16ca 100644 --- a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart +++ b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart @@ -152,22 +152,21 @@ class ResidentWebRunner extends ResidentRunner { // Only non-wasm debug builds of the web support the service protocol. @override - late final bool supportsServiceProtocol = - !debuggingOptions.webUseWasm && isRunningDebug && _deviceIsDebuggable; + bool get supportsServiceProtocol => + !debuggingOptions.webUseWasm && isRunningDebug && deviceIsDebuggable; - /// Device is debuggable if not a WebServer device, or if running with - /// --start-paused or using DWDS WebSocket connection (WebServer device). - late final bool _deviceIsDebuggable = - device!.device is! WebServerDevice || - debuggingOptions.startPaused || - useDwdsWebSocketConnection; + @override + bool get debuggingEnabled => isRunningDebug && deviceIsDebuggable; - late final useDwdsWebSocketConnection = device!.device is! ChromiumDevice; + /// WebServer device is debuggable when running with --start-paused. + bool get deviceIsDebuggable => device!.device is! WebServerDevice || debuggingOptions.startPaused; @override // Web uses a different plugin registry. bool get generateDartPluginRegistry => false; + bool get _enableDwds => debuggingEnabled; + @override bool get reloadIsRestart => debuggingOptions.webUseWasm || @@ -287,6 +286,24 @@ class ResidentWebRunner extends ResidentRunner { ? WebExpressionCompiler(device!.generator!, fileSystem: _fileSystem) : null; + // Retrieve connected web devices, excluding the web server device. + final List? devices = await globals.deviceManager?.getAllDevices(); + final nonWebServerConnectedDeviceIds = { + for (final Device d in devices!.where( + (Device d) => + d.platformType == PlatformType.web && + d.isConnected && + d.id != WebServerDevice.kWebServerDeviceId, + )) + d.id, + }; + + // Use Chrome-based connection only if we have a connected ChromiumDevice + // Otherwise, use DWDS WebSocket connection + final bool useDwdsWebSocketConnection = + !(_chromiumLauncher != null && + nonWebServerConnectedDeviceIds.contains(device!.device!.id)); + device!.devFS = WebDevFS( webDevServerConfig: updatedConfig, packagesFilePath: packagesFilePath, @@ -295,7 +312,7 @@ class ResidentWebRunner extends ResidentRunner { useSseForDebugBackend: debuggingOptions.webUseSseForDebugBackend, useSseForInjectedClient: debuggingOptions.webUseSseForInjectedClient, buildInfo: debuggingOptions.buildInfo, - enableDwds: supportsServiceProtocol, + enableDwds: _enableDwds, enableDds: debuggingOptions.enableDds, entrypoint: _fileSystem.file(target).uri, expressionCompiler: expressionCompiler, @@ -489,7 +506,7 @@ class ResidentWebRunner extends ResidentRunner { Duration? reloadDuration; Duration? reassembleDuration; try { - if (!_deviceIsDebuggable) { + if (!deviceIsDebuggable) { _logger.printStatus('Recompile complete. Page requires refresh.'); } else if (isRunningDebug) { if (fullRestart) { @@ -558,7 +575,7 @@ class ResidentWebRunner extends ResidentRunner { } // Don't track restart times for dart2js builds or web-server devices. - if (debuggingOptions.buildInfo.isDebug && _deviceIsDebuggable) { + if (debuggingOptions.buildInfo.isDebug && deviceIsDebuggable) { if (fullRestart) { _analytics.send( Event.timing( @@ -773,102 +790,86 @@ class ResidentWebRunner extends ResidentRunner { Uri? websocketUri; if (supportsServiceProtocol) { assert(connectDebug != null); - unawaited( - connectDebug!.then((connectionResult) async { - _connectionResult = connectionResult; - unawaited(_connectionResult!.debugConnection!.onDone.whenComplete(_cleanupAndExit)); + _connectionResult = await connectDebug; + unawaited(_connectionResult!.debugConnection!.onDone.whenComplete(_cleanupAndExit)); - void onLogEvent(vmservice.Event event) { - final String message = processVmServiceMessage(event); - _logger.printStatus(message); - } + void onLogEvent(vmservice.Event event) { + final String message = processVmServiceMessage(event); + _logger.printStatus(message); + } - // This flag is needed to manage breakpoints properly. - if (debuggingOptions.startPaused && debuggingOptions.debuggingEnabled) { - try { - final vmservice.Response result = await _vmService.service.setFlag( - 'pause_isolates_on_start', - 'true', - ); - if (result is! vmservice.Success) { - _logger.printError('setFlag failure: $result'); - } - } on Exception catch (e) { - _logger.printError( - 'Failed to set pause_isolates_on_start=true, proceeding. ' - 'Error: $e', - ); - } - } - - _stdOutSub = _vmService.service.onStdoutEvent.listen(onLogEvent); - _stdErrSub = _vmService.service.onStderrEvent.listen(onLogEvent); - _serviceSub = _vmService.service.onServiceEvent.listen(_onServiceEvent); - try { - await _vmService.service.streamListen(vmservice.EventStreams.kStdout); - } on vmservice.RPCError { - // It is safe to ignore this error because we expect an error to be - // thrown if we're already subscribed. - } - try { - await _vmService.service.streamListen(vmservice.EventStreams.kStderr); - } on vmservice.RPCError { - // It is safe to ignore this error because we expect an error to be - // thrown if we're already subscribed. - } - try { - await _vmService.service.streamListen(vmservice.EventStreams.kService); - } on vmservice.RPCError { - // It is safe to ignore this error because we expect an error to be - // thrown if we're already subscribed. - } - try { - await _vmService.service.streamListen(vmservice.EventStreams.kIsolate); - } on vmservice.RPCError { - // It is safe to ignore this error because we expect an error to be - // thrown if we're not already subscribed. - } - await setUpVmService( - reloadSources: (String isolateId, {bool? force, bool? pause}) async { - await restart(pause: pause); - }, - device: device!.device, - flutterProject: flutterProject, - printStructuredErrorLogMethod: printStructuredErrorLog, - vmService: _vmService.service, + // This flag is needed to manage breakpoints properly. + if (debuggingOptions.startPaused && debuggingOptions.debuggingEnabled) { + try { + final vmservice.Response result = await _vmService.service.setFlag( + 'pause_isolates_on_start', + 'true', ); - - websocketUri = Uri.parse(_connectionResult!.debugConnection!.uri); - device!.vmService = _vmService; - - // Run main immediately if the app is not started paused or if there - // is no debugger attached. Otherwise, runMain when a resume event - // is received. - if (!debuggingOptions.startPaused || !supportsServiceProtocol) { - _connectionResult!.appConnection!.runMain(); - } else { - late StreamSubscription resumeSub; - resumeSub = _vmService.service.onDebugEvent.listen((vmservice.Event event) { - if (event.type == vmservice.EventKind.kResume) { - _connectionResult!.appConnection!.runMain(); - resumeSub.cancel(); - } - }); + if (result is! vmservice.Success) { + _logger.printError('setFlag failure: $result'); } + } on Exception catch (e) { + _logger.printError( + 'Failed to set pause_isolates_on_start=true, proceeding. ' + 'Error: $e', + ); + } + } - if (websocketUri != null) { - if (debuggingOptions.vmserviceOutFile != null) { - _fileSystem.file(debuggingOptions.vmserviceOutFile) - ..createSync(recursive: true) - ..writeAsStringSync(websocketUri.toString()); - } - _logger.printStatus('Debug service listening on $websocketUri'); - } - connectionInfoCompleter?.complete(DebugConnectionInfo(wsUri: websocketUri)); - }), + _stdOutSub = _vmService.service.onStdoutEvent.listen(onLogEvent); + _stdErrSub = _vmService.service.onStderrEvent.listen(onLogEvent); + _serviceSub = _vmService.service.onServiceEvent.listen(_onServiceEvent); + try { + await _vmService.service.streamListen(vmservice.EventStreams.kStdout); + } on vmservice.RPCError { + // It is safe to ignore this error because we expect an error to be + // thrown if we're already subscribed. + } + try { + await _vmService.service.streamListen(vmservice.EventStreams.kStderr); + } on vmservice.RPCError { + // It is safe to ignore this error because we expect an error to be + // thrown if we're already subscribed. + } + try { + await _vmService.service.streamListen(vmservice.EventStreams.kService); + } on vmservice.RPCError { + // It is safe to ignore this error because we expect an error to be + // thrown if we're already subscribed. + } + try { + await _vmService.service.streamListen(vmservice.EventStreams.kIsolate); + } on vmservice.RPCError { + // It is safe to ignore this error because we expect an error to be + // thrown if we're not already subscribed. + } + await setUpVmService( + reloadSources: (String isolateId, {bool? force, bool? pause}) async { + await restart(pause: pause); + }, + device: device!.device, + flutterProject: flutterProject, + printStructuredErrorLogMethod: printStructuredErrorLog, + vmService: _vmService.service, ); - } else { - connectionInfoCompleter?.complete(DebugConnectionInfo()); + + websocketUri = Uri.parse(_connectionResult!.debugConnection!.uri); + device!.vmService = _vmService; + + // Run main immediately if the app is not started paused or if there + // is no debugger attached. Otherwise, runMain when a resume event + // is received. + if (!debuggingOptions.startPaused || !supportsServiceProtocol) { + _connectionResult!.appConnection!.runMain(); + } else { + late StreamSubscription resumeSub; + resumeSub = _vmService.service.onDebugEvent.listen((vmservice.Event event) { + if (event.type == vmservice.EventKind.kResume) { + _connectionResult!.appConnection!.runMain(); + resumeSub.cancel(); + } + }); + } } // TODO(bkonyi): remove when ready to serve DevTools from DDS. if (debuggingOptions.enableDevTools) { @@ -880,8 +881,16 @@ class ResidentWebRunner extends ResidentRunner { ), ); } - + if (websocketUri != null) { + if (debuggingOptions.vmserviceOutFile != null) { + _fileSystem.file(debuggingOptions.vmserviceOutFile) + ..createSync(recursive: true) + ..writeAsStringSync(websocketUri.toString()); + } + _logger.printStatus('Debug service listening on $websocketUri'); + } appStartedCompleter?.complete(); + connectionInfoCompleter?.complete(DebugConnectionInfo(wsUri: websocketUri)); if (stayResident) { await waitForAppToFinish(); } else { diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml index 14485a315a2..fa69ee5b1e1 100644 --- a/packages/flutter_tools/pubspec.yaml +++ b/packages/flutter_tools/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: archive: 3.6.1 args: 2.7.0 dds: 5.0.3 - dwds: 24.4.1 + dwds: 24.4.0 code_builder: 4.10.1 collection: 1.19.1 completion: 1.0.2 @@ -126,4 +126,4 @@ dev_dependencies: dartdoc: # Exclude this package from the hosted API docs. nodoc: true -# PUBSPEC CHECKSUM: 932agm +# PUBSPEC CHECKSUM: kav93d diff --git a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart index 4b9e0c58f52..9895d198945 100644 --- a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart @@ -147,7 +147,7 @@ name: my_app } testUsingContext( - 'runner with web server device supports debugging without --start-paused', + 'runner with web server device does not support debugging without --start-paused', () { final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice); flutterDevice.device = WebServerDevice(logger: BufferLogger.test()); @@ -165,7 +165,7 @@ name: my_app systemClock: globals.systemClock, ); - expect(profileResidentWebRunner.debuggingEnabled, true); + expect(profileResidentWebRunner.debuggingEnabled, false); flutterDevice.device = chromeDevice; @@ -1104,12 +1104,7 @@ name: my_app logger: logger, systemClock: SystemClock.fixed(DateTime(2001)), ); - fakeVmServiceHost = FakeVmServiceHost( - requests: [ - ...kAttachExpectations, - const FakeVmServiceRequest(method: 'hotRestart'), - ], - ); + fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations); setupMocks(); flutterDevice.device = webServerDevice; webDevFS.report = UpdateFSReport(success: true); @@ -1122,16 +1117,8 @@ name: my_app expect(logger.statusText, contains('Restarted application in')); expect(result.code, 0); - expect( - fakeAnalytics.sentEvents, - contains( - Event.timing( - workflow: 'hot', - variableName: 'web-incremental-restart', - elapsedMilliseconds: 0, - ), - ), - ); + // web-server device does not send restart analytics + expect(fakeAnalytics.sentEvents, isEmpty); }, overrides: { Analytics: () => fakeAnalytics, @@ -1594,7 +1581,7 @@ name: my_app 'Sends unlaunched app.webLaunchUrl event for Web Server device', () async { final logger = BufferLogger.test(); - fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList()); + fakeVmServiceHost = FakeVmServiceHost(requests: []); setupMocks(); flutterDevice.device = WebServerDevice(logger: logger); webDevFS.baseUri = Uri.parse('http://localhost:8765/app/'); @@ -1678,9 +1665,7 @@ flutter: mainLibName: 'my_app', packages: {'path_provider_linux': '../../path_provider_linux'}, ); - final connectionInfoCompleter = Completer(); - expect(await residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter), 0); - await connectionInfoCompleter.future; + expect(await residentWebRunner.run(), 0); final File generatedLocalizationsFile = globals.fs .directory('lib') .childDirectory('l10n') @@ -2070,9 +2055,6 @@ class FakeWebDevFS extends Fake implements WebDevFS { @override PackageConfig? lastPackageConfig = PackageConfig.empty; - @override - var useDwdsWebSocketConnection = false; - @override Future create() async { return baseUri; diff --git a/packages/flutter_tools/test/integration.shard/hot_reload_websocket_test.dart b/packages/flutter_tools/test/integration.shard/hot_reload_websocket_test.dart deleted file mode 100644 index c28ca8361ab..00000000000 --- a/packages/flutter_tools/test/integration.shard/hot_reload_websocket_test.dart +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -@Tags(['flutter-test-driver']) -library; - -import 'dart:async'; -import 'dart:io' as io; - -import 'package:file/file.dart'; -import 'package:flutter_tools/src/web/chrome.dart'; -import 'package:flutter_tools/src/web/web_device.dart' show WebServerDevice; - -import '../src/common.dart'; -import 'test_data/hot_reload_project.dart'; -import 'test_driver.dart'; -import 'test_utils.dart'; -import 'transition_test_utils.dart'; - -void main() { - testAll(); -} - -void testAll({List additionalCommandArgs = const []}) { - group('WebSocket DWDS connection' - '${additionalCommandArgs.isEmpty ? '' : ' with args: $additionalCommandArgs'}', () { - // Test configuration constants - const debugUrlTimeout = Duration(seconds: 20); - const appStartTimeout = Duration(seconds: 15); - const hotReloadTimeout = Duration(seconds: 10); - - late Directory tempDir; - final project = HotReloadProject(); - late FlutterRunTestDriver flutter; - - setUp(() async { - tempDir = createResolvedTempDirectorySync('hot_reload_websocket_test.'); - await project.setUpIn(tempDir); - flutter = FlutterRunTestDriver(tempDir); - }); - - tearDown(() async { - await flutter.stop(); - tryToDelete(tempDir); - }); - - testWithoutContext( - 'hot reload with headless Chrome WebSocket connection', - () async { - debugPrint('Starting WebSocket DWDS test with headless Chrome...'); - - // Set up listening for app output before starting - final stdout = StringBuffer(); - final sawDebugUrl = Completer(); - final StreamSubscription subscription = flutter.stdout.listen((String e) { - stdout.writeln(e); - // Extract the debug connection URL - if (e.contains('Waiting for connection from Dart debug extension at http://')) { - final debugUrlPattern = RegExp( - r'Waiting for connection from Dart debug extension at (http://[^\s]+)', - ); - final Match? match = debugUrlPattern.firstMatch(e); - if (match != null && !sawDebugUrl.isCompleted) { - sawDebugUrl.complete(match.group(1)!); - } - } - }); - - io.Process? chromeProcess; - try { - // Step 1: Start Flutter app with web-server device (will wait for debug connection) - debugPrint('Step 1: Starting Flutter app with web-server device...'); - // Start the app but don't wait for it to complete - it won't complete until Chrome connects - final Future appStartFuture = runFlutterWithWebServerDevice( - flutter, - additionalCommandArgs: [...additionalCommandArgs, '--no-web-resources-cdn'], - ); - - // Step 2: Wait for DWDS debug URL to be available - debugPrint('Step 2: Waiting for DWDS debug service URL...'); - final String debugUrl = await sawDebugUrl.future.timeout( - debugUrlTimeout, - onTimeout: () { - throw Exception('DWDS debug URL not found - app may not have started correctly'); - }, - ); - debugPrint('✓ DWDS debug service available at: $debugUrl'); - - // Step 3: Launch headless Chrome to connect to DWDS - debugPrint('Step 3: Launching headless Chrome to connect to DWDS...'); - chromeProcess = await _launchHeadlessChrome(debugUrl); - debugPrint('✓ Headless Chrome launched and connecting to DWDS'); - - // Step 4: Wait for app to start (Chrome connection established) - debugPrint('Step 4: Waiting for Flutter app to start after Chrome connection...'); - await appStartFuture.timeout( - appStartTimeout, - onTimeout: () { - throw Exception('App startup did not complete after Chrome connection'); - }, - ); - debugPrint('✓ Flutter app started successfully with WebSocket connection'); - - // Step 5: Test hot reload functionality - debugPrint('Step 5: Testing hot reload with WebSocket connection...'); - await flutter.hotReload().timeout( - hotReloadTimeout, - onTimeout: () { - throw Exception('Hot reload timed out'); - }, - ); - - // Give some time for logs to capture - await Future.delayed(const Duration(seconds: 2)); - - final output = stdout.toString(); - expect(output, contains('Reloaded'), reason: 'Hot reload should complete successfully'); - debugPrint('✓ Hot reload completed successfully with WebSocket connection'); - - // Verify the correct infrastructure was used - expect( - output, - contains('Waiting for connection from Dart debug extension'), - reason: 'Should wait for debug connection (WebSocket infrastructure)', - ); - expect(output, contains('web-server'), reason: 'Should use web-server device'); - - debugPrint('✓ WebSocket DWDS test completed successfully'); - debugPrint('✓ Verified: web-server device + DWDS + WebSocket connection + hot reload'); - } finally { - await _cleanupResources(chromeProcess, subscription); - } - }, - skip: !platform.isMacOS, // Skip on non-macOS platforms where Chrome paths may differ - ); - }); -} - -/// Launches headless Chrome with the given debug URL. -/// Uses findChromeExecutable to locate Chrome on the current platform. -Future _launchHeadlessChrome(String debugUrl) async { - const chromeArgs = [ - '--headless', - '--disable-gpu', - '--no-sandbox', - '--disable-extensions', - '--disable-dev-shm-usage', - '--remote-debugging-port=0', - ]; - - final String chromePath = findChromeExecutable(platform, fileSystem); - - try { - return await io.Process.start(chromePath, [...chromeArgs, debugUrl]); - } on Exception catch (e) { - throw Exception( - 'Could not launch Chrome at $chromePath: $e. Please ensure Chrome is installed.', - ); - } -} - -/// Cleans up test resources (Chrome process and stdout subscription). -Future _cleanupResources( - io.Process? chromeProcess, - StreamSubscription subscription, -) async { - if (chromeProcess != null) { - try { - chromeProcess.kill(); - await chromeProcess.exitCode; - debugPrint('Chrome process cleaned up'); - } on Exception catch (e) { - debugPrint('Warning: Failed to clean up Chrome process: $e'); - } - } - await subscription.cancel(); -} - -// Helper to run flutter with web-server device using WebSocket connection. -Future runFlutterWithWebServerDevice( - FlutterRunTestDriver flutter, { - bool verbose = false, - bool withDebugger = true, // Enable debugger by default for WebSocket connection - bool startPaused = false, // Don't start paused for this test - List additionalCommandArgs = const [], -}) => flutter.run( - verbose: verbose, - withDebugger: withDebugger, // Enable debugger to establish WebSocket connection - startPaused: startPaused, // Let the app start normally after debugger connects - device: WebServerDevice.kWebServerDeviceId, - additionalCommandArgs: additionalCommandArgs, -); diff --git a/packages/flutter_tools/test/integration.shard/test_driver.dart b/packages/flutter_tools/test/integration.shard/test_driver.dart index a6c5887555d..e173616690b 100644 --- a/packages/flutter_tools/test/integration.shard/test_driver.dart +++ b/packages/flutter_tools/test/integration.shard/test_driver.dart @@ -11,7 +11,7 @@ import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/utils.dart'; import 'package:flutter_tools/src/tester/flutter_tester.dart'; -import 'package:flutter_tools/src/web/web_device.dart' show GoogleChromeDevice, WebServerDevice; +import 'package:flutter_tools/src/web/web_device.dart' show GoogleChromeDevice; import 'package:meta/meta.dart'; import 'package:process/process.dart'; import 'package:vm_service/vm_service.dart'; @@ -568,7 +568,6 @@ final class FlutterRunTestDriver extends FlutterTestDriver { ], withDebugger: withDebugger, startPaused: startPaused, - waitForDebugPort: device != WebServerDevice.kWebServerDeviceId, pauseOnExceptions: pauseOnExceptions, script: script, verbose: verbose, @@ -609,7 +608,6 @@ final class FlutterRunTestDriver extends FlutterTestDriver { bool withDebugger = false, bool startPaused = false, bool pauseOnExceptions = false, - bool waitForDebugPort = false, bool verbose = false, int? attachPort, }) async { @@ -650,11 +648,12 @@ final class FlutterRunTestDriver extends FlutterTestDriver { event: 'app.started', timeout: appStartTimeout, ); - late final Map debugPort; - if (waitForDebugPort || withDebugger) { - debugPort = await _waitFor(event: 'app.debugPort', timeout: appStartTimeout); - } + if (withDebugger) { + final Map debugPort = await _waitFor( + event: 'app.debugPort', + timeout: appStartTimeout, + ); final wsUriString = (debugPort['params']! as Map)['wsUri']! as String; _vmServiceWsUri = Uri.parse(wsUriString); await connectToVmService(pauseOnExceptions: pauseOnExceptions);