blob: 396409e757ff5b1e790f46edc2eccdd78a8bcedf [file] [log] [blame]
// 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;
}
}