Unverified Commit e666ea8d authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] cleanups to web runner functionality (#61178)

Skip unnecessary parsing of chrome URI. Ensure stack traces are initialized in web server. Disclaimer on web server that it does not support debugging and remove help message. Fix generated entrypoint to check for main(List<String> args)

- Fixes #59643
- Fixes #55084
- Fixes #60417
parent a1097ea5
...@@ -189,8 +189,10 @@ abstract class ResidentWebRunner extends ResidentRunner { ...@@ -189,8 +189,10 @@ abstract class ResidentWebRunner extends ResidentRunner {
globals.printStatus(''); globals.printStatus('');
globals.printStatus(message); globals.printStatus(message);
const String quitMessage = 'To quit, press "q".'; const String quitMessage = 'To quit, press "q".';
if (device.device is! WebServerDevice) {
globals.printStatus('For a more detailed help message, press "h". $quitMessage'); globals.printStatus('For a more detailed help message, press "h". $quitMessage');
} }
}
@override @override
Future<List<FlutterView>> listFlutterViews() async { Future<List<FlutterView>> listFlutterViews() async {
...@@ -636,6 +638,7 @@ class _ResidentWebRunner extends ResidentWebRunner { ...@@ -636,6 +638,7 @@ class _ResidentWebRunner extends ResidentWebRunner {
'// Flutter web bootstrap script for $importedEntrypoint.', '// Flutter web bootstrap script for $importedEntrypoint.',
'', '',
"import 'dart:ui' as ui;", "import 'dart:ui' as ui;",
"import 'dart:async';",
'', '',
"import '$importedEntrypoint' as entrypoint;", "import '$importedEntrypoint' as entrypoint;",
if (hasWebPlugins) if (hasWebPlugins)
...@@ -643,11 +646,16 @@ class _ResidentWebRunner extends ResidentWebRunner { ...@@ -643,11 +646,16 @@ class _ResidentWebRunner extends ResidentWebRunner {
if (hasWebPlugins) if (hasWebPlugins)
"import '$generatedImport';", "import '$generatedImport';",
'', '',
'typedef _UnaryFunction = dynamic Function(List<String> args);',
'typedef _NullaryFunction = dynamic Function();',
'Future<void> main() async {', 'Future<void> main() async {',
if (hasWebPlugins) if (hasWebPlugins)
' registerPlugins(webPluginRegistry);', ' registerPlugins(webPluginRegistry);',
' await ui.webOnlyInitializePlatform();', ' await ui.webOnlyInitializePlatform();',
' entrypoint.main();', ' if (entrypoint.main is _UnaryFunction) {',
' return (entrypoint.main as _UnaryFunction)(<String>[]);',
' }',
' return (entrypoint.main as _NullaryFunction)();',
'}', '}',
'', '',
].join('\n'); ].join('\n');
......
...@@ -88,10 +88,10 @@ class _DefaultDoctorValidatorsProvider implements DoctorValidatorsProvider { ...@@ -88,10 +88,10 @@ class _DefaultDoctorValidatorsProvider implements DoctorValidatorsProvider {
chromiumLauncher: ChromiumLauncher( chromiumLauncher: ChromiumLauncher(
browserFinder: findChromeExecutable, browserFinder: findChromeExecutable,
fileSystem: globals.fs, fileSystem: globals.fs,
logger: globals.logger,
operatingSystemUtils: globals.os, operatingSystemUtils: globals.os,
platform: globals.platform, platform: globals.platform,
processManager: globals.processManager, processManager: globals.processManager,
logger: globals.logger,
), ),
platform: globals.platform, platform: globals.platform,
), ),
......
...@@ -628,9 +628,9 @@ class BrowserManager { ...@@ -628,9 +628,9 @@ class BrowserManager {
browserFinder: findChromeExecutable, browserFinder: findChromeExecutable,
fileSystem: globals.fs, fileSystem: globals.fs,
operatingSystemUtils: globals.os, operatingSystemUtils: globals.os,
logger: globals.logger,
platform: globals.platform, platform: globals.platform,
processManager: globals.processManager, processManager: globals.processManager,
logger: globals.logger,
); );
final Chromium chrome = final Chromium chrome =
await chromiumLauncher.launch(url.toString(), headless: headless); await chromiumLauncher.launch(url.toString(), headless: headless);
...@@ -668,7 +668,7 @@ class BrowserManager { ...@@ -668,7 +668,7 @@ class BrowserManager {
/// Loads [_BrowserEnvironment]. /// Loads [_BrowserEnvironment].
Future<_BrowserEnvironment> _loadBrowserEnvironment() async { Future<_BrowserEnvironment> _loadBrowserEnvironment() async {
return _BrowserEnvironment( return _BrowserEnvironment(
this, null, _browser.remoteDebuggerUri, _onRestartController.stream); this, null, _browser.chromeConnection.url, _onRestartController.stream);
} }
/// Tells the browser to load a test suite from the URL [url]. /// Tells the browser to load a test suite from the URL [url].
......
...@@ -66,6 +66,7 @@ define("main_module.bootstrap", ["$entrypoint", "dart_sdk"], function(app, dart_ ...@@ -66,6 +66,7 @@ define("main_module.bootstrap", ["$entrypoint", "dart_sdk"], function(app, dart_
window.\$dartLoader.rootDirectories = []; window.\$dartLoader.rootDirectories = [];
if (window.\$requireLoader) { if (window.\$requireLoader) {
window.\$requireLoader.getModuleLibraries = dart_sdk.dart.getModuleLibraries; window.\$requireLoader.getModuleLibraries = dart_sdk.dart.getModuleLibraries;
}
if (window.\$dartStackTraceUtility && !window.\$dartStackTraceUtility.ready) { if (window.\$dartStackTraceUtility && !window.\$dartStackTraceUtility.ready) {
window.\$dartStackTraceUtility.ready = true; window.\$dartStackTraceUtility.ready = true;
let dart = dart_sdk.dart; let dart = dart_sdk.dart;
...@@ -79,7 +80,6 @@ define("main_module.bootstrap", ["$entrypoint", "dart_sdk"], function(app, dart_ ...@@ -79,7 +80,6 @@ define("main_module.bootstrap", ["$entrypoint", "dart_sdk"], function(app, dart_
return dart.getSourceMap(url); return dart.getSourceMap(url);
}); });
} }
}
}); });
'''; ''';
} }
...@@ -15,7 +15,6 @@ import '../base/logger.dart'; ...@@ -15,7 +15,6 @@ import '../base/logger.dart';
import '../base/os.dart'; import '../base/os.dart';
import '../base/platform.dart'; import '../base/platform.dart';
import '../convert.dart'; import '../convert.dart';
import '../globals.dart' as globals;
/// An environment variable used to override the location of Google Chrome. /// An environment variable used to override the location of Google Chrome.
const String kChromeEnvironment = 'CHROME_EXECUTABLE'; const String kChromeEnvironment = 'CHROME_EXECUTABLE';
...@@ -105,14 +104,14 @@ class ChromiumLauncher { ...@@ -105,14 +104,14 @@ class ChromiumLauncher {
@required Platform platform, @required Platform platform,
@required ProcessManager processManager, @required ProcessManager processManager,
@required OperatingSystemUtils operatingSystemUtils, @required OperatingSystemUtils operatingSystemUtils,
@required Logger logger,
@required BrowserFinder browserFinder, @required BrowserFinder browserFinder,
@required Logger logger,
}) : _fileSystem = fileSystem, }) : _fileSystem = fileSystem,
_platform = platform, _platform = platform,
_processManager = processManager, _processManager = processManager,
_operatingSystemUtils = operatingSystemUtils, _operatingSystemUtils = operatingSystemUtils,
_logger = logger,
_browserFinder = browserFinder, _browserFinder = browserFinder,
_logger = logger,
_fileSystemUtils = FileSystemUtils( _fileSystemUtils = FileSystemUtils(
fileSystem: fileSystem, fileSystem: fileSystem,
platform: platform, platform: platform,
...@@ -122,9 +121,9 @@ class ChromiumLauncher { ...@@ -122,9 +121,9 @@ class ChromiumLauncher {
final Platform _platform; final Platform _platform;
final ProcessManager _processManager; final ProcessManager _processManager;
final OperatingSystemUtils _operatingSystemUtils; final OperatingSystemUtils _operatingSystemUtils;
Logger _logger;
final BrowserFinder _browserFinder; final BrowserFinder _browserFinder;
final FileSystemUtils _fileSystemUtils; final FileSystemUtils _fileSystemUtils;
final Logger _logger;
bool get hasChromeInstance => _currentCompleter.isCompleted; bool get hasChromeInstance => _currentCompleter.isCompleted;
...@@ -163,7 +162,6 @@ class ChromiumLauncher { ...@@ -163,7 +162,6 @@ class ChromiumLauncher {
bool skipCheck = false, bool skipCheck = false,
Directory cacheDir, Directory cacheDir,
}) async { }) async {
_logger ??= globals.logger;
if (_currentCompleter.isCompleted) { if (_currentCompleter.isCompleted) {
throwToolExit('Only one instance of chrome can be started.'); throwToolExit('Only one instance of chrome can be started.');
} }
...@@ -207,13 +205,6 @@ class ChromiumLauncher { ...@@ -207,13 +205,6 @@ class ChromiumLauncher {
final Process process = await _processManager.start(args); final Process process = await _processManager.start(args);
// When the process exits, copy the user settings back to the provided data-dir.
if (cacheDir != null) {
unawaited(process.exitCode.whenComplete(() {
_cacheUserSessionInformation(userDataDir, cacheDir);
}));
}
process.stdout process.stdout
.transform(utf8.decoder) .transform(utf8.decoder)
.transform(const LineSplitter()) .transform(const LineSplitter())
...@@ -221,7 +212,8 @@ class ChromiumLauncher { ...@@ -221,7 +212,8 @@ class ChromiumLauncher {
_logger.printTrace('[CHROME]: $line'); _logger.printTrace('[CHROME]: $line');
}); });
// Wait until the DevTools are listening before trying to connect. // Wait until the DevTools are listening before trying to connect. This is
// only required for flutter_test --platform=chrome and not flutter run.
await process.stderr await process.stderr
.transform(utf8.decoder) .transform(utf8.decoder)
.transform(const LineSplitter()) .transform(const LineSplitter())
...@@ -232,13 +224,18 @@ class ChromiumLauncher { ...@@ -232,13 +224,18 @@ class ChromiumLauncher {
.firstWhere((String line) => line.startsWith('DevTools listening'), orElse: () { .firstWhere((String line) => line.startsWith('DevTools listening'), orElse: () {
return 'Failed to spawn stderr'; return 'Failed to spawn stderr';
}); });
final Uri remoteDebuggerUri = await _getRemoteDebuggerUrl(Uri.parse('http://localhost:$port'));
// When the process exits, copy the user settings back to the provided data-dir.
if (cacheDir != null) {
unawaited(process.exitCode.whenComplete(() {
_cacheUserSessionInformation(userDataDir, cacheDir);
}));
}
return _connect(Chromium._( return _connect(Chromium._(
port, port,
ChromeConnection('localhost', port), ChromeConnection('localhost', port),
url: url, url: url,
process: process, process: process,
remoteDebuggerUri: remoteDebuggerUri,
chromiumLauncher: this, chromiumLauncher: this,
), skipCheck); ), skipCheck);
} }
...@@ -311,28 +308,6 @@ class ChromiumLauncher { ...@@ -311,28 +308,6 @@ class ChromiumLauncher {
} }
Future<Chromium> get connectedInstance => _currentCompleter.future; Future<Chromium> get connectedInstance => _currentCompleter.future;
/// Returns the full URL of the Chrome remote debugger for the main page.
///
/// This takes the [base] remote debugger URL (which points to a browser-wide
/// page) and uses its JSON API to find the resolved URL for debugging the host
/// page.
Future<Uri> _getRemoteDebuggerUrl(Uri base) async {
try {
final HttpClient client = HttpClient();
final HttpClientRequest request = await client.getUrl(base.resolve('/json/list'));
final HttpClientResponse response = await request.close();
final List<dynamic> jsonObject = await json.fuse(utf8).decoder.bind(response).single as List<dynamic>;
if (jsonObject == null || jsonObject.isEmpty) {
return base;
}
return base.resolve(jsonObject.first['devtoolsFrontendUrl'] as String);
} on Exception {
// If we fail to talk to the remote debugger protocol, give up and return
// the raw URL rather than crashing.
return base;
}
}
} }
/// A class for managing an instance of a Chromium browser. /// A class for managing an instance of a Chromium browser.
...@@ -342,7 +317,6 @@ class Chromium { ...@@ -342,7 +317,6 @@ class Chromium {
this.chromeConnection, { this.chromeConnection, {
this.url, this.url,
Process process, Process process,
this.remoteDebuggerUri,
@required ChromiumLauncher chromiumLauncher, @required ChromiumLauncher chromiumLauncher,
}) : _process = process, }) : _process = process,
_chromiumLauncher = chromiumLauncher; _chromiumLauncher = chromiumLauncher;
...@@ -351,7 +325,6 @@ class Chromium { ...@@ -351,7 +325,6 @@ class Chromium {
final int debugPort; final int debugPort;
final Process _process; final Process _process;
final ChromeConnection chromeConnection; final ChromeConnection chromeConnection;
final Uri remoteDebuggerUri;
final ChromiumLauncher _chromiumLauncher; final ChromiumLauncher _chromiumLauncher;
Future<int> get onExit => _process.exitCode; Future<int> get onExit => _process.exitCode;
......
...@@ -305,10 +305,10 @@ class WebDevices extends PollingDeviceDiscovery { ...@@ -305,10 +305,10 @@ class WebDevices extends PollingDeviceDiscovery {
chromiumLauncher: ChromiumLauncher( chromiumLauncher: ChromiumLauncher(
browserFinder: findChromeExecutable, browserFinder: findChromeExecutable,
fileSystem: fileSystem, fileSystem: fileSystem,
logger: logger,
platform: platform, platform: platform,
processManager: processManager, processManager: processManager,
operatingSystemUtils: operatingSystemUtils, operatingSystemUtils: operatingSystemUtils,
logger: logger,
), ),
); );
if (platform.isWindows) { if (platform.isWindows) {
...@@ -316,10 +316,10 @@ class WebDevices extends PollingDeviceDiscovery { ...@@ -316,10 +316,10 @@ class WebDevices extends PollingDeviceDiscovery {
chromiumLauncher: ChromiumLauncher( chromiumLauncher: ChromiumLauncher(
browserFinder: findEdgeExecutable, browserFinder: findEdgeExecutable,
fileSystem: fileSystem, fileSystem: fileSystem,
logger: logger,
platform: platform, platform: platform,
processManager: processManager, processManager: processManager,
operatingSystemUtils: operatingSystemUtils, operatingSystemUtils: operatingSystemUtils,
logger: logger,
), ),
processManager: processManager, processManager: processManager,
logger: logger, logger: logger,
...@@ -450,6 +450,10 @@ class WebServerDevice extends Device { ...@@ -450,6 +450,10 @@ class WebServerDevice extends Device {
} else { } else {
_logger.printStatus('$mainPath is being served at $url', emphasis: true); _logger.printStatus('$mainPath is being served at $url', emphasis: true);
} }
_logger.printStatus(
'The web-server device does not support debugging. Consider using '
'the Chrome or Edge devices for an improved development workflow.'
);
_logger.sendEvent('app.webLaunchUrl', <String, dynamic>{'url': url, 'launched': false}); _logger.sendEvent('app.webLaunchUrl', <String, dynamic>{'url': url, 'launched': false});
return LaunchResult.succeeded(observatoryUri: url != null ? Uri.parse(url): null); return LaunchResult.succeeded(observatoryUri: url != null ? Uri.parse(url): null);
} }
......
...@@ -35,10 +35,8 @@ void main() { ...@@ -35,10 +35,8 @@ void main() {
Platform platform; Platform platform;
FakeProcessManager processManager; FakeProcessManager processManager;
OperatingSystemUtils operatingSystemUtils; OperatingSystemUtils operatingSystemUtils;
Logger logger;
setUp(() { setUp(() {
logger = BufferLogger.test();
operatingSystemUtils = MockOperatingSystemUtils(); operatingSystemUtils = MockOperatingSystemUtils();
when(operatingSystemUtils.findFreePort()) when(operatingSystemUtils.findFreePort())
.thenAnswer((Invocation invocation) async { .thenAnswer((Invocation invocation) async {
...@@ -54,8 +52,8 @@ void main() { ...@@ -54,8 +52,8 @@ void main() {
platform: platform, platform: platform,
processManager: processManager, processManager: processManager,
operatingSystemUtils: operatingSystemUtils, operatingSystemUtils: operatingSystemUtils,
logger: logger,
browserFinder: findChromeExecutable, browserFinder: findChromeExecutable,
logger: BufferLogger.test(),
); );
}); });
...@@ -162,7 +160,7 @@ void main() { ...@@ -162,7 +160,7 @@ void main() {
.childFile('preferences'); .childFile('preferences');
preferencesFile preferencesFile
..createSync(recursive: true) ..createSync(recursive: true)
..writeAsStringSync('example'); ..writeAsStringSync('"exit_type":"Crashed"');
final Directory localStorageContentsDirectory = dataDir final Directory localStorageContentsDirectory = dataDir
.childDirectory('Default') .childDirectory('Default')
...@@ -186,18 +184,8 @@ void main() { ...@@ -186,18 +184,8 @@ void main() {
cacheDir: dataDir, cacheDir: dataDir,
); );
// validate preferences
final File tempFile = fileSystem
.directory('.tmp_rand0/flutter_tools_chrome_device.rand0')
.childDirectory('Default')
.childFile('preferences');
expect(tempFile.existsSync(), true);
expect(tempFile.readAsStringSync(), 'example');
// write crash to file:
tempFile.writeAsStringSync('"exit_type":"Crashed"');
exitCompleter.complete(); exitCompleter.complete();
await Future<void>.delayed(const Duration(microseconds: 1));
// writes non-crash back to dart_tool // writes non-crash back to dart_tool
expect(preferencesFile.readAsStringSync(), '"exit_type":"Normal"'); expect(preferencesFile.readAsStringSync(), '"exit_type":"Normal"');
......
...@@ -34,8 +34,8 @@ void main() { ...@@ -34,8 +34,8 @@ void main() {
platform: platform, platform: platform,
processManager: processManager, processManager: processManager,
operatingSystemUtils: null, operatingSystemUtils: null,
logger: BufferLogger.test(),
browserFinder: findChromeExecutable, browserFinder: findChromeExecutable,
logger: BufferLogger.test(),
); );
webValidator = webValidator = ChromeValidator( webValidator = webValidator = ChromeValidator(
platform: platform, platform: platform,
......
...@@ -52,3 +52,46 @@ class BasicProject extends Project { ...@@ -52,3 +52,46 @@ class BasicProject extends Project {
Uri get topLevelFunctionBreakpointUri => mainDart; Uri get topLevelFunctionBreakpointUri => mainDart;
int get topLevelFunctionBreakpointLine => lineContaining(main, '// TOP LEVEL BREAKPOINT'); int get topLevelFunctionBreakpointLine => lineContaining(main, '// TOP LEVEL BREAKPOINT');
} }
class BasicProjectWithUnaryMain extends Project {
@override
final String pubspec = '''
name: test
environment:
sdk: ">=2.0.0-dev.68.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
''';
@override
final String main = r'''
import 'dart:async';
import 'package:flutter/material.dart';
Future<void> main(List<String> args) async {
while (true) {
runApp(new MyApp());
await Future.delayed(const Duration(milliseconds: 50));
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
topLevelFunction();
return new MaterialApp( // BUILD BREAKPOINT
title: 'Flutter Demo',
home: new Container(),
);
}
}
topLevelFunction() {
print("topLevelFunction"); // TOP LEVEL BREAKPOINT
}
''';
}
// 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.
import 'package:file/file.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import '../src/common.dart';
import 'test_data/basic_project.dart';
import 'test_driver.dart';
import 'test_utils.dart';
void main() {
Directory tempDir;
final BasicProjectWithUnaryMain project = BasicProjectWithUnaryMain();
FlutterRunTestDriver flutter;
setUp(() async {
tempDir = createResolvedTempDirectorySync('run_test.');
await project.setUpIn(tempDir);
flutter = FlutterRunTestDriver(tempDir);
});
tearDown(() async {
await flutter.stop();
tryToDelete(tempDir);
});
test('flutter run works on web devices with a unary main function', () async {
await flutter.run(chrome: true);
}, skip: 'Web CI skipped');
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment