| // Copyright 2019 The Chromium 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 'dart:async'; |
| |
| import 'package:meta/meta.dart'; |
| import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'; |
| |
| import 'application_package.dart'; |
| import 'asset.dart'; |
| import 'base/common.dart'; |
| import 'base/file_system.dart'; |
| import 'base/logger.dart'; |
| import 'base/terminal.dart'; |
| import 'base/utils.dart'; |
| import 'build_info.dart'; |
| import 'bundle.dart'; |
| import 'dart/package_map.dart'; |
| import 'device.dart'; |
| import 'globals.dart'; |
| import 'project.dart'; |
| import 'resident_runner.dart'; |
| import 'run_hot.dart'; |
| import 'web/asset_server.dart'; |
| import 'web/chrome.dart'; |
| import 'web/compile.dart'; |
| |
| /// A hot-runner which handles browser specific delegation. |
| class ResidentWebRunner extends ResidentRunner { |
| ResidentWebRunner( |
| List<FlutterDevice> flutterDevices, { |
| String target, |
| @required this.flutterProject, |
| @required bool ipv6, |
| @required DebuggingOptions debuggingOptions, |
| }) : super( |
| flutterDevices, |
| target: target, |
| usesTerminalUI: true, |
| stayResident: true, |
| debuggingOptions: debuggingOptions, |
| ipv6: ipv6, |
| ); |
| |
| WebAssetServer _server; |
| ProjectFileInvalidator projectFileInvalidator; |
| DateTime _lastCompiled; |
| WipConnection _connection; |
| final FlutterProject flutterProject; |
| |
| @override |
| Future<int> attach( |
| {Completer<DebugConnectionInfo> connectionInfoCompleter, |
| Completer<void> appStartedCompleter}) async { |
| connectionInfoCompleter?.complete(DebugConnectionInfo()); |
| setupTerminal(); |
| final int result = await waitForAppToFinish(); |
| await cleanupAtFinish(); |
| return result; |
| } |
| |
| @override |
| Future<void> cleanupAfterSignal() async { |
| await _connection.sendCommand('Browser.close'); |
| _connection = null; |
| await _server?.dispose(); |
| } |
| |
| @override |
| Future<void> cleanupAtFinish() async { |
| await _connection?.sendCommand('Browser.close'); |
| _connection = null; |
| await _server?.dispose(); |
| } |
| |
| @override |
| Future<void> handleTerminalCommand(String code) async { |
| if (code == 'R') { |
| // If hot restart is not supported for all devices, ignore the command. |
| if (!canHotRestart) { |
| return; |
| } |
| await restart(fullRestart: true); |
| } |
| } |
| |
| @override |
| void printHelp({bool details}) { |
| const String fire = '🔥'; |
| const String rawMessage = |
| ' To hot restart (and rebuild state), press "R".'; |
| final String message = terminal.color( |
| fire + terminal.bolden(rawMessage), |
| TerminalColor.red, |
| ); |
| const String warning = '👻 '; |
| printStatus(warning * 20); |
| printStatus('Warning: Flutter\'s support for building web applications is highly experimental.'); |
| printStatus('For more information see https://github.com/flutter/flutter/issues/34082.'); |
| printStatus(warning * 20); |
| printStatus(''); |
| printStatus(message); |
| const String quitMessage = 'To quit, press "q".'; |
| printStatus('For a more detailed help message, press "h". $quitMessage'); |
| } |
| |
| @override |
| Future<int> run({ |
| Completer<DebugConnectionInfo> connectionInfoCompleter, |
| Completer<void> appStartedCompleter, |
| String route, |
| bool shouldBuild = true, |
| }) async { |
| final ApplicationPackage package = await ApplicationPackageFactory.instance.getPackageForPlatform( |
| TargetPlatform.web_javascript, |
| applicationBinary: null, |
| ); |
| if (package == null) { |
| printError('No application found for TargetPlatform.web_javascript'); |
| return 1; |
| } |
| if (!fs.isFileSync(mainPath)) { |
| String message = 'Tried to run $mainPath, but that file does not exist.'; |
| if (target == null) { |
| message += |
| '\nConsider using the -t option to specify the Dart file to start.'; |
| } |
| printError(message); |
| return 1; |
| } |
| // Start the web compiler and build the assets. |
| await webCompilationProxy.initialize( |
| projectDirectory: flutterProject.directory, |
| ); |
| _lastCompiled = DateTime.now(); |
| final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle(); |
| final int build = await assetBundle.build(); |
| if (build != 0) { |
| throwToolExit('Error: Failed to build asset bundle'); |
| } |
| await writeBundle(fs.directory(getAssetBuildDirectory()), assetBundle.entries); |
| |
| // Step 2: Start an HTTP server |
| _server = WebAssetServer(flutterProject, target, ipv6); |
| await _server.initialize(); |
| |
| // Step 3: Spawn an instance of Chrome and direct it to the created server. |
| final String url = 'http://localhost:${_server.port}'; |
| final Chrome chrome = await chromeLauncher.launch(url); |
| final ChromeTab chromeTab = await chrome.chromeConnection.getTab((ChromeTab chromeTab) { |
| return chromeTab.url.contains(url); // we don't care about trailing slashes or # |
| }); |
| _connection = await chromeTab.connect(); |
| _connection.onClose.listen((WipConnection connection) { |
| exit(); |
| }); |
| |
| // We don't support the debugging proxy yet. |
| appStartedCompleter?.complete(); |
| return attach( |
| connectionInfoCompleter: connectionInfoCompleter, |
| appStartedCompleter: appStartedCompleter, |
| ); |
| } |
| |
| @override |
| Future<OperationResult> restart({ |
| bool fullRestart = false, |
| bool pauseAfterRestart = false, |
| String reason, |
| bool benchmarkMode = false, |
| }) async { |
| final Stopwatch timer = Stopwatch()..start(); |
| final Status status = logger.startProgress( |
| 'Performing hot restart...', |
| timeout: timeoutConfiguration.fastOperation, |
| progressId: 'hot.restart', |
| ); |
| OperationResult result = OperationResult.ok; |
| try { |
| final List<Uri> invalidatedSources = ProjectFileInvalidator.findInvalidated( |
| lastCompiled: _lastCompiled, |
| urisToMonitor: <Uri>[ |
| for (FileSystemEntity entity in flutterProject.directory |
| .childDirectory('lib') |
| .listSync(recursive: true)) |
| if (entity is File && entity.path.endsWith('.dart')) entity.uri |
| ], // Add new class to track this for web. |
| packagesPath: PackageMap.globalPackagesPath, |
| ); |
| await webCompilationProxy.invalidate(inputs: invalidatedSources); |
| await _connection.sendCommand('Page.reload'); |
| await Future<void>.delayed(const Duration(milliseconds: 150)); |
| } catch (err) { |
| result = OperationResult(1, err.toString()); |
| } finally { |
| printStatus('Restarted application in ${getElapsedAsMilliseconds(timer.elapsed)}.'); |
| status.cancel(); |
| } |
| return result; |
| } |
| } |