mirror of
https://github.com/flutter/flutter
synced 2024-10-13 11:42:54 +00:00
Enable usage of experimental incremental compiler for web (#43576)
This commit is contained in:
parent
99f2b940f6
commit
e5d6d92498
|
@ -6,5 +6,5 @@ import 'package:flutter_devicelab/framework/framework.dart';
|
|||
import 'package:flutter_devicelab/tasks/web_dev_mode_tests.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
await task(createWebDevModeTest());
|
||||
await task(createWebDevModeTest(WebDevice.webServer, false));
|
||||
}
|
||||
|
|
|
@ -6,5 +6,5 @@ import 'package:flutter_devicelab/framework/framework.dart';
|
|||
import 'package:flutter_devicelab/tasks/web_dev_mode_tests.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
await task(createWebDevModeTest());
|
||||
await task(createWebDevModeTest(WebDevice.webServer, false));
|
||||
}
|
||||
|
|
10
dev/devicelab/bin/tasks/web_incremental_test.dart
Normal file
10
dev/devicelab/bin/tasks/web_incremental_test.dart
Normal file
|
@ -0,0 +1,10 @@
|
|||
// 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 'package:flutter_devicelab/framework/framework.dart';
|
||||
import 'package:flutter_devicelab/tasks/web_dev_mode_tests.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
await task(createWebDevModeTest(WebDevice.chrome, true));
|
||||
}
|
|
@ -6,5 +6,5 @@ import 'package:flutter_devicelab/framework/framework.dart';
|
|||
import 'package:flutter_devicelab/tasks/web_dev_mode_tests.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
await task(createWebDevModeTest());
|
||||
await task(createWebDevModeTest(WebDevice.webServer, false));
|
||||
}
|
||||
|
|
|
@ -20,12 +20,21 @@ const String kFirstRecompileTime = 'FirstRecompileTime';
|
|||
const String kSecondStartupTime = 'SecondStartupTime';
|
||||
const String kSecondRestartTime = 'SecondRestartTime';
|
||||
|
||||
TaskFunction createWebDevModeTest() {
|
||||
|
||||
abstract class WebDevice {
|
||||
static const String chrome = 'chrome';
|
||||
static const String webServer = 'web-server';
|
||||
}
|
||||
|
||||
TaskFunction createWebDevModeTest(String webDevice, bool enableIncrementalCompiler) {
|
||||
return () async {
|
||||
final List<String> options = <String>[
|
||||
'--hot', '-d', 'web-server', '--verbose', '--resident', '--target=lib/main.dart',
|
||||
'--hot', '-d', webDevice, '--verbose', '--resident', '--target=lib/main.dart',
|
||||
];
|
||||
int hotRestartCount = 0;
|
||||
final String expectedMessage = webDevice == WebDevice.webServer
|
||||
? 'Recompile complete'
|
||||
: 'Reloaded application';
|
||||
final Map<String, int> measurements = <String, int>{};
|
||||
await inDirectory<void>(flutterDirectory, () async {
|
||||
rmTree(_editedFlutterGalleryDir);
|
||||
|
@ -38,6 +47,8 @@ TaskFunction createWebDevModeTest() {
|
|||
<String>['packages', 'get'],
|
||||
environment: <String, String>{
|
||||
'FLUTTER_WEB': 'true',
|
||||
if (enableIncrementalCompiler)
|
||||
'WEB_INCREMENTAL_COMPILER': 'true',
|
||||
},
|
||||
);
|
||||
await packagesGet.exitCode;
|
||||
|
@ -46,16 +57,26 @@ TaskFunction createWebDevModeTest() {
|
|||
flutterCommandArgs('run', options),
|
||||
environment: <String, String>{
|
||||
'FLUTTER_WEB': 'true',
|
||||
if (enableIncrementalCompiler)
|
||||
'WEB_INCREMENTAL_COMPILER': 'true',
|
||||
},
|
||||
);
|
||||
|
||||
final Completer<void> stdoutDone = Completer<void>();
|
||||
final Completer<void> stderrDone = Completer<void>();
|
||||
final Stopwatch sw = Stopwatch()..start();
|
||||
bool restarted = false;
|
||||
process.stdout
|
||||
.transform<String>(utf8.decoder)
|
||||
.transform<String>(const LineSplitter())
|
||||
.listen((String line) {
|
||||
// TODO(jonahwilliams): non-dwds builds do not know when the browser is loaded.
|
||||
if (line.contains('Ignoring terminal input')) {
|
||||
Future<void>.delayed(const Duration(seconds: 1)).then((void _) {
|
||||
process.stdin.write(restarted ? 'q' : 'r');
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (line.contains('To hot restart')) {
|
||||
// measure clean start-up time.
|
||||
sw.stop();
|
||||
|
@ -63,9 +84,10 @@ TaskFunction createWebDevModeTest() {
|
|||
sw
|
||||
..reset()
|
||||
..start();
|
||||
process.stdin.write('R');
|
||||
process.stdin.write('r');
|
||||
return;
|
||||
}
|
||||
if (line.contains('Recompile complete')) {
|
||||
if (line.contains(expectedMessage)) {
|
||||
if (hotRestartCount == 0) {
|
||||
measurements[kFirstRestartTime] = sw.elapsedMilliseconds;
|
||||
// Update the file and reload again.
|
||||
|
@ -80,9 +102,10 @@ TaskFunction createWebDevModeTest() {
|
|||
sw
|
||||
..reset()
|
||||
..start();
|
||||
process.stdin.writeln('R');
|
||||
process.stdin.writeln('r');
|
||||
++hotRestartCount;
|
||||
} else {
|
||||
restarted = true;
|
||||
measurements[kFirstRecompileTime] = sw.elapsedMilliseconds;
|
||||
// Quit after second hot restart.
|
||||
process.stdin.writeln('q');
|
||||
|
@ -119,24 +142,35 @@ TaskFunction createWebDevModeTest() {
|
|||
flutterCommandArgs('run', options),
|
||||
environment: <String, String>{
|
||||
'FLUTTER_WEB': 'true',
|
||||
if (enableIncrementalCompiler)
|
||||
'WEB_INCREMENTAL_COMPILER': 'true',
|
||||
},
|
||||
);
|
||||
final Completer<void> stdoutDone = Completer<void>();
|
||||
final Completer<void> stderrDone = Completer<void>();
|
||||
bool restarted = false;
|
||||
process.stdout
|
||||
.transform<String>(utf8.decoder)
|
||||
.transform<String>(const LineSplitter())
|
||||
.listen((String line) {
|
||||
|
||||
// TODO(jonahwilliams): non-dwds builds do not know when the browser is loaded.
|
||||
if (line.contains('Ignoring terminal input')) {
|
||||
Future<void>.delayed(const Duration(seconds: 1)).then((void _) {
|
||||
process.stdin.write(restarted ? 'q' : 'r');
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (line.contains('To hot restart')) {
|
||||
measurements[kSecondStartupTime] = sw.elapsedMilliseconds;
|
||||
sw
|
||||
..reset()
|
||||
..start();
|
||||
process.stdin.write('R');
|
||||
process.stdin.write('r');
|
||||
return;
|
||||
}
|
||||
if (line.contains('Recompile complete')) {
|
||||
measurements[kSecondRestartTime] = sw.elapsedMilliseconds;
|
||||
if (line.contains(expectedMessage)) {
|
||||
restarted = true;
|
||||
measurements[kSecondRestartTime] = sw.elapsedMilliseconds;
|
||||
process.stdin.writeln('q');
|
||||
}
|
||||
print('stdout: $line');
|
||||
|
|
|
@ -112,6 +112,13 @@ tasks:
|
|||
stage: devicelab
|
||||
required_agent_capabilities: ["linux/android"]
|
||||
|
||||
web_incremental_test:
|
||||
description: >
|
||||
Verify that the experimental frontend server support is functional.
|
||||
stage: devicelab
|
||||
required_agent_capabilities: ["linux/android"]
|
||||
flaky: true
|
||||
|
||||
flutter_gallery_ios__compile:
|
||||
description: >
|
||||
Collects various performance metrics of compiling the Flutter
|
||||
|
|
|
@ -28,7 +28,7 @@ enum Artifact {
|
|||
platformLibrariesJson,
|
||||
flutterPatchedSdkPath,
|
||||
frontendServerSnapshotForEngineDartSdk,
|
||||
/// The root directory of the dartk SDK.
|
||||
/// The root directory of the dart SDK.
|
||||
engineDartSdkPath,
|
||||
/// The dart binary used to execute any of the required snapshots.
|
||||
engineDartBinary,
|
||||
|
|
|
@ -8,7 +8,8 @@ import 'package:build_daemon/client.dart';
|
|||
import 'package:dwds/dwds.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:vm_service/vm_service.dart' as vmservice;
|
||||
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart' hide StackTrace;
|
||||
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'
|
||||
hide StackTrace;
|
||||
|
||||
import '../application_package.dart';
|
||||
import '../base/async_guard.dart';
|
||||
|
@ -16,16 +17,21 @@ import '../base/common.dart';
|
|||
import '../base/file_system.dart';
|
||||
import '../base/io.dart';
|
||||
import '../base/logger.dart';
|
||||
import '../base/os.dart';
|
||||
import '../base/terminal.dart';
|
||||
import '../base/utils.dart';
|
||||
import '../build_info.dart';
|
||||
import '../convert.dart';
|
||||
import '../devfs.dart';
|
||||
import '../device.dart';
|
||||
import '../features.dart';
|
||||
import '../globals.dart';
|
||||
import '../project.dart';
|
||||
import '../reporting/reporting.dart';
|
||||
import '../resident_runner.dart';
|
||||
import '../run_hot.dart';
|
||||
import '../web/chrome.dart';
|
||||
import '../web/devfs_web.dart';
|
||||
import '../web/web_device.dart';
|
||||
import '../web/web_runner.dart';
|
||||
import 'web_fs.dart';
|
||||
|
@ -34,14 +40,24 @@ import 'web_fs.dart';
|
|||
class DwdsWebRunnerFactory extends WebRunnerFactory {
|
||||
@override
|
||||
ResidentRunner createWebRunner(
|
||||
Device device, {
|
||||
FlutterDevice device, {
|
||||
String target,
|
||||
@required bool stayResident,
|
||||
@required FlutterProject flutterProject,
|
||||
@required bool ipv6,
|
||||
@required DebuggingOptions debuggingOptions,
|
||||
}) {
|
||||
return ResidentWebRunner(
|
||||
if (featureFlags.isWebIncrementalCompilerEnabled) {
|
||||
return _ExperimentalResidentWebRunner(
|
||||
device,
|
||||
target: target,
|
||||
flutterProject: flutterProject,
|
||||
debuggingOptions: debuggingOptions,
|
||||
ipv6: ipv6,
|
||||
stayResident: stayResident,
|
||||
);
|
||||
}
|
||||
return _DwdsResidentWebRunner(
|
||||
device,
|
||||
target: target,
|
||||
flutterProject: flutterProject,
|
||||
|
@ -53,8 +69,9 @@ class DwdsWebRunnerFactory extends WebRunnerFactory {
|
|||
}
|
||||
|
||||
/// A hot-runner which handles browser specific delegation.
|
||||
class ResidentWebRunner extends ResidentRunner {
|
||||
ResidentWebRunner(this.device, {
|
||||
abstract class ResidentWebRunner extends ResidentRunner {
|
||||
ResidentWebRunner(
|
||||
this.device, {
|
||||
String target,
|
||||
@required this.flutterProject,
|
||||
@required bool ipv6,
|
||||
|
@ -68,22 +85,27 @@ class ResidentWebRunner extends ResidentRunner {
|
|||
stayResident: stayResident,
|
||||
);
|
||||
|
||||
final Device device;
|
||||
final FlutterDevice device;
|
||||
final FlutterProject flutterProject;
|
||||
DateTime firstBuildTime;
|
||||
|
||||
// Only the debug builds of the web support the service protocol.
|
||||
@override
|
||||
bool get supportsServiceProtocol => isRunningDebug && device is! WebServerDevice;
|
||||
bool get supportsServiceProtocol =>
|
||||
isRunningDebug && device.device is! WebServerDevice;
|
||||
|
||||
@override
|
||||
bool get debuggingEnabled => isRunningDebug && device is! WebServerDevice;
|
||||
bool get debuggingEnabled =>
|
||||
isRunningDebug && device.device is! WebServerDevice;
|
||||
|
||||
WebFs _webFs;
|
||||
ConnectionResult _connectionResult;
|
||||
StreamSubscription<vmservice.Event> _stdOutSub;
|
||||
bool _exited = false;
|
||||
WipConnection _wipConnection;
|
||||
|
||||
vmservice.VmService get _vmService => _connectionResult?.debugConnection?.vmService;
|
||||
vmservice.VmService get _vmService =>
|
||||
_connectionResult?.debugConnection?.vmService;
|
||||
|
||||
@override
|
||||
bool get canHotRestart {
|
||||
|
@ -95,7 +117,8 @@ class ResidentWebRunner extends ResidentRunner {
|
|||
String method, {
|
||||
Map<String, dynamic> params,
|
||||
}) async {
|
||||
final vmservice.Response response = await _vmService.callServiceExtension(method, args: params);
|
||||
final vmservice.Response response =
|
||||
await _vmService.callServiceExtension(method, args: params);
|
||||
return response.toJson();
|
||||
}
|
||||
|
||||
|
@ -115,7 +138,7 @@ class ResidentWebRunner extends ResidentRunner {
|
|||
}
|
||||
await _stdOutSub?.cancel();
|
||||
await _webFs?.stop();
|
||||
await device.stopApp(null);
|
||||
await device.device.stopApp(null);
|
||||
if (ChromeLauncher.hasChromeInstance) {
|
||||
final Chrome chrome = await ChromeLauncher.connectedInstance;
|
||||
await chrome.close();
|
||||
|
@ -134,13 +157,15 @@ class ResidentWebRunner extends ResidentRunner {
|
|||
return printHelpDetails();
|
||||
}
|
||||
const String fire = '🔥';
|
||||
const String rawMessage = ' To hot restart changes while running, press "r". '
|
||||
'To hot restart (and refresh the browser), press "R".';
|
||||
const String rawMessage =
|
||||
' To hot restart changes while running, press "r". '
|
||||
'To hot restart (and refresh the browser), press "R".';
|
||||
final String message = terminal.color(
|
||||
fire + terminal.bolden(rawMessage),
|
||||
TerminalColor.red,
|
||||
);
|
||||
printStatus('Warning: Flutter\'s support for web development is not stable yet and hasn\'t');
|
||||
printStatus(
|
||||
'Warning: Flutter\'s support for web development is not stable yet and hasn\'t');
|
||||
printStatus('been thoroughly tested in production environments.');
|
||||
printStatus('For more information see https://flutter.dev/web');
|
||||
printStatus('');
|
||||
|
@ -149,18 +174,202 @@ class ResidentWebRunner extends ResidentRunner {
|
|||
printStatus('For a more detailed help message, press "h". $quitMessage');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> debugDumpApp() async {
|
||||
try {
|
||||
await _vmService?.callServiceExtension(
|
||||
'ext.flutter.debugDumpApp',
|
||||
);
|
||||
} on vmservice.RPCError {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> debugDumpRenderTree() async {
|
||||
try {
|
||||
await _vmService?.callServiceExtension(
|
||||
'ext.flutter.debugDumpRenderTree',
|
||||
);
|
||||
} on vmservice.RPCError {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> debugDumpLayerTree() async {
|
||||
try {
|
||||
await _vmService?.callServiceExtension(
|
||||
'ext.flutter.debugDumpLayerTree',
|
||||
);
|
||||
} on vmservice.RPCError {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> debugDumpSemanticsTreeInTraversalOrder() async {
|
||||
try {
|
||||
await _vmService?.callServiceExtension(
|
||||
'ext.flutter.debugDumpSemanticsTreeInTraversalOrder');
|
||||
} on vmservice.RPCError {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> debugTogglePlatform() async {
|
||||
try {
|
||||
final vmservice.Response response = await _vmService
|
||||
?.callServiceExtension('ext.flutter.platformOverride');
|
||||
final String currentPlatform = response.json['value'];
|
||||
String nextPlatform;
|
||||
switch (currentPlatform) {
|
||||
case 'android':
|
||||
nextPlatform = 'iOS';
|
||||
break;
|
||||
case 'iOS':
|
||||
nextPlatform = 'android';
|
||||
break;
|
||||
}
|
||||
if (nextPlatform == null) {
|
||||
return;
|
||||
}
|
||||
await _vmService?.callServiceExtension('ext.flutter.platformOverride',
|
||||
args: <String, Object>{
|
||||
'value': nextPlatform,
|
||||
});
|
||||
printStatus('Switched operating system to $nextPlatform');
|
||||
} on vmservice.RPCError {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> debugDumpSemanticsTreeInInverseHitTestOrder() async {
|
||||
try {
|
||||
await _vmService?.callServiceExtension(
|
||||
'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder');
|
||||
} on vmservice.RPCError {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> debugToggleDebugPaintSizeEnabled() async {
|
||||
try {
|
||||
final vmservice.Response response =
|
||||
await _vmService?.callServiceExtension(
|
||||
'ext.flutter.debugPaint',
|
||||
);
|
||||
await _vmService?.callServiceExtension(
|
||||
'ext.flutter.debugPaint',
|
||||
args: <dynamic, dynamic>{
|
||||
'enabled': !(response.json['enabled'] == 'true')
|
||||
},
|
||||
);
|
||||
} on vmservice.RPCError {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> debugToggleDebugCheckElevationsEnabled() async {
|
||||
try {
|
||||
final vmservice.Response response =
|
||||
await _vmService?.callServiceExtension(
|
||||
'ext.flutter.debugCheckElevationsEnabled',
|
||||
);
|
||||
await _vmService?.callServiceExtension(
|
||||
'ext.flutter.debugCheckElevationsEnabled',
|
||||
args: <dynamic, dynamic>{
|
||||
'enabled': !(response.json['enabled'] == 'true')
|
||||
},
|
||||
);
|
||||
} on vmservice.RPCError {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> debugTogglePerformanceOverlayOverride() async {
|
||||
try {
|
||||
final vmservice.Response response = await _vmService
|
||||
?.callServiceExtension('ext.flutter.showPerformanceOverlay');
|
||||
await _vmService?.callServiceExtension(
|
||||
'ext.flutter.showPerformanceOverlay',
|
||||
args: <dynamic, dynamic>{
|
||||
'enabled': !(response.json['enabled'] == 'true')
|
||||
},
|
||||
);
|
||||
} on vmservice.RPCError {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> debugToggleWidgetInspector() async {
|
||||
try {
|
||||
final vmservice.Response response = await _vmService
|
||||
?.callServiceExtension('ext.flutter.debugToggleWidgetInspector');
|
||||
await _vmService?.callServiceExtension(
|
||||
'ext.flutter.debugToggleWidgetInspector',
|
||||
args: <dynamic, dynamic>{
|
||||
'enabled': !(response.json['enabled'] == 'true')
|
||||
},
|
||||
);
|
||||
} on vmservice.RPCError {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> debugToggleProfileWidgetBuilds() async {
|
||||
try {
|
||||
final vmservice.Response response = await _vmService
|
||||
?.callServiceExtension('ext.flutter.profileWidgetBuilds');
|
||||
await _vmService?.callServiceExtension(
|
||||
'ext.flutter.profileWidgetBuilds',
|
||||
args: <dynamic, dynamic>{
|
||||
'enabled': !(response.json['enabled'] == 'true')
|
||||
},
|
||||
);
|
||||
} on vmservice.RPCError {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _ExperimentalResidentWebRunner extends ResidentWebRunner {
|
||||
_ExperimentalResidentWebRunner(
|
||||
FlutterDevice device, {
|
||||
String target,
|
||||
@required FlutterProject flutterProject,
|
||||
@required bool ipv6,
|
||||
@required DebuggingOptions debuggingOptions,
|
||||
bool stayResident = true,
|
||||
}) : super(
|
||||
device,
|
||||
flutterProject: flutterProject,
|
||||
target: target ?? fs.path.join('lib', 'main.dart'),
|
||||
debuggingOptions: debuggingOptions,
|
||||
ipv6: ipv6,
|
||||
stayResident: stayResident,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<int> run({
|
||||
Completer<DebugConnectionInfo> connectionInfoCompleter,
|
||||
Completer<void> appStartedCompleter,
|
||||
String route,
|
||||
}) async {
|
||||
firstBuildTime = DateTime.now();
|
||||
final ApplicationPackage package = await ApplicationPackageFactory.instance.getPackageForPlatform(
|
||||
TargetPlatform.web_javascript,
|
||||
applicationBinary: null,
|
||||
);
|
||||
if (package == null) {
|
||||
printError('No application found for TargetPlatform.web_javascript.');
|
||||
printError('This application is not configured to build on the web.');
|
||||
printError('To add web support to a project, run `flutter create .`.');
|
||||
return 1;
|
||||
}
|
||||
|
@ -174,7 +383,200 @@ class ResidentWebRunner extends ResidentRunner {
|
|||
return 1;
|
||||
}
|
||||
final String modeName = debuggingOptions.buildInfo.friendlyModeName;
|
||||
printStatus('Launching ${getDisplayPath(target)} on ${device.name} in $modeName mode...');
|
||||
printStatus('Launching ${getDisplayPath(target)} on ${device.device.name} in $modeName mode...');
|
||||
final String effectiveHostname = debuggingOptions.hostname ?? 'localhost';
|
||||
final int hostPort = debuggingOptions.port == null
|
||||
? await os.findFreePort()
|
||||
: int.tryParse(debuggingOptions.port);
|
||||
device.devFS = WebDevFS(
|
||||
effectiveHostname,
|
||||
hostPort,
|
||||
packagesFilePath,
|
||||
);
|
||||
await device.devFS.create();
|
||||
await _updateDevFS(fullRestart: true);
|
||||
device.generator.accept();
|
||||
await device.device.startApp(
|
||||
package,
|
||||
mainPath: target,
|
||||
debuggingOptions: debuggingOptions,
|
||||
platformArgs: <String, Object>{
|
||||
'uri': 'http://$effectiveHostname:$hostPort',
|
||||
},
|
||||
);
|
||||
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: supportsServiceProtocol
|
||||
? timeoutConfiguration.fastOperation
|
||||
: timeoutConfiguration.slowOperation,
|
||||
progressId: 'hot.restart',
|
||||
);
|
||||
|
||||
final UpdateFSReport report = await _updateDevFS(fullRestart: fullRestart);
|
||||
if (report.success) {
|
||||
device.generator.accept();
|
||||
} else {
|
||||
await device.generator.reject();
|
||||
}
|
||||
final String modules = report.invalidatedModules
|
||||
.map((String module) => '"$module"')
|
||||
.join(',');
|
||||
|
||||
try {
|
||||
if (fullRestart) {
|
||||
await _wipConnection.sendCommand('Page.reload');
|
||||
} else {
|
||||
await _wipConnection.debugger
|
||||
.sendCommand('Runtime.evaluate', params: <String, Object>{
|
||||
'expression': 'window.\$hotReloadHook([$modules])',
|
||||
'awaitPromise': true,
|
||||
'returnByValue': true,
|
||||
});
|
||||
}
|
||||
} on WipError catch (err) {
|
||||
printError(err.toString());
|
||||
return OperationResult(1, err.toString());
|
||||
} finally {
|
||||
status.stop();
|
||||
}
|
||||
final String verb = fullRestart ? 'Restarted' : 'Reloaded';
|
||||
printStatus('$verb application in ${getElapsedAsMilliseconds(timer.elapsed)}.');
|
||||
if (!fullRestart) {
|
||||
flutterUsage.sendTiming('hot', 'web-incremental-restart', timer.elapsed);
|
||||
}
|
||||
HotEvent(
|
||||
'restart',
|
||||
targetPlatform: getNameForTargetPlatform(TargetPlatform.web_javascript),
|
||||
sdkName: await device.device.sdkNameAndVersion,
|
||||
emulator: false,
|
||||
fullRestart: true,
|
||||
reason: reason,
|
||||
).send();
|
||||
return OperationResult.ok;
|
||||
}
|
||||
|
||||
Future<UpdateFSReport> _updateDevFS({bool fullRestart = false}) async {
|
||||
final bool isFirstUpload = !assetBundle.wasBuiltOnce();
|
||||
final bool rebuildBundle = assetBundle.needsBuild();
|
||||
if (rebuildBundle) {
|
||||
printTrace('Updating assets');
|
||||
final int result = await assetBundle.build();
|
||||
if (result != 0) {
|
||||
return UpdateFSReport(success: false);
|
||||
}
|
||||
}
|
||||
final List<Uri> invalidatedFiles =
|
||||
await ProjectFileInvalidator.findInvalidated(
|
||||
lastCompiled: device.devFS.lastCompiled,
|
||||
urisToMonitor: device.devFS.sources,
|
||||
packagesPath: packagesFilePath,
|
||||
);
|
||||
final Status devFSStatus = logger.startProgress(
|
||||
'Syncing files to device ${device.device.name}...',
|
||||
timeout: timeoutConfiguration.fastOperation,
|
||||
);
|
||||
final UpdateFSReport report = await device.devFS.update(
|
||||
mainPath: mainPath,
|
||||
target: target,
|
||||
bundle: assetBundle,
|
||||
firstBuildTime: firstBuildTime,
|
||||
bundleFirstUpload: isFirstUpload,
|
||||
generator: device.generator,
|
||||
fullRestart: fullRestart,
|
||||
dillOutputPath: dillOutputPath,
|
||||
projectRootPath: projectRootPath,
|
||||
pathToReload: getReloadPath(fullRestart: fullRestart),
|
||||
invalidatedFiles: invalidatedFiles,
|
||||
trackWidgetCreation: true,
|
||||
);
|
||||
devFSStatus.stop();
|
||||
printTrace('Synced ${getSizeAsMB(report.syncedBytes)}.');
|
||||
return report;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> attach({
|
||||
Completer<DebugConnectionInfo> connectionInfoCompleter,
|
||||
Completer<void> appStartedCompleter,
|
||||
}) async {
|
||||
final Chrome chrome = await ChromeLauncher.connectedInstance;
|
||||
final ChromeTab chromeTab =
|
||||
await chrome.chromeConnection.getTab((ChromeTab chromeTab) {
|
||||
return chromeTab.url.contains(debuggingOptions.hostname);
|
||||
});
|
||||
_wipConnection = await chromeTab.connect();
|
||||
appStartedCompleter?.complete();
|
||||
connectionInfoCompleter?.complete();
|
||||
if (stayResident) {
|
||||
await waitForAppToFinish();
|
||||
} else {
|
||||
await stopEchoingDeviceLog();
|
||||
await exitApp();
|
||||
}
|
||||
await cleanupAtFinish();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
class _DwdsResidentWebRunner extends ResidentWebRunner {
|
||||
_DwdsResidentWebRunner(
|
||||
FlutterDevice device, {
|
||||
String target,
|
||||
@required FlutterProject flutterProject,
|
||||
@required bool ipv6,
|
||||
@required DebuggingOptions debuggingOptions,
|
||||
bool stayResident = true,
|
||||
}) : super(
|
||||
device,
|
||||
flutterProject: flutterProject,
|
||||
target: target ?? fs.path.join('lib', 'main.dart'),
|
||||
debuggingOptions: debuggingOptions,
|
||||
ipv6: ipv6,
|
||||
stayResident: stayResident,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<int> run({
|
||||
Completer<DebugConnectionInfo> connectionInfoCompleter,
|
||||
Completer<void> appStartedCompleter,
|
||||
String route,
|
||||
}) async {
|
||||
firstBuildTime = DateTime.now();
|
||||
final ApplicationPackage package = await ApplicationPackageFactory.instance.getPackageForPlatform(
|
||||
TargetPlatform.web_javascript,
|
||||
applicationBinary: null,
|
||||
);
|
||||
if (package == null) {
|
||||
printError('This application is not configured to build on the web.');
|
||||
printError('To add web support to a project, run `flutter create .`.');
|
||||
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;
|
||||
}
|
||||
final String modeName = debuggingOptions.buildInfo.friendlyModeName;
|
||||
printStatus(
|
||||
'Launching ${getDisplayPath(target)} on ${device.device.name} in $modeName mode...');
|
||||
Status buildStatus;
|
||||
bool statusActive = false;
|
||||
try {
|
||||
|
@ -204,7 +606,8 @@ class ResidentWebRunner extends ResidentRunner {
|
|||
);
|
||||
statusActive = true;
|
||||
}
|
||||
await device.startApp(package,
|
||||
await device.device.startApp(
|
||||
package,
|
||||
mainPath: target,
|
||||
debuggingOptions: debuggingOptions,
|
||||
platformArgs: <String, Object>{
|
||||
|
@ -229,42 +632,38 @@ class ResidentWebRunner extends ResidentRunner {
|
|||
} on VersionSkew {
|
||||
// Thrown if an older build daemon is already running.
|
||||
throwToolExit(
|
||||
'Another build daemon is already running with an older version.\n'
|
||||
'Try exiting other Flutter processes in this project and try again.'
|
||||
);
|
||||
'Another build daemon is already running with an older version.\n'
|
||||
'Try exiting other Flutter processes in this project and try again.');
|
||||
} on OptionsSkew {
|
||||
// Thrown if a build daemon is already running with different configuration.
|
||||
throwToolExit(
|
||||
'Another build daemon is already running with different configuration.\n'
|
||||
'Try exiting other Flutter processes in this project and try again.'
|
||||
);
|
||||
'Another build daemon is already running with different configuration.\n'
|
||||
'Exit other Flutter processes running in this project and try again.');
|
||||
} on WebSocketException {
|
||||
throwToolExit('Failed to connect to WebSocket.');
|
||||
} on BuildException {
|
||||
throwToolExit('Failed to build application for the Web.');
|
||||
} on ChromeDebugException catch (err, stackTrace) {
|
||||
throwToolExit(
|
||||
'Failed to establish connection with Chrome. Try running the application again.\n'
|
||||
'If this problem persists, please file an issue with the details below:\n$err\n$stackTrace');
|
||||
'Failed to establish connection with Chrome. Try running the application again.\n'
|
||||
'If this problem persists, please file an issue with the details below:\n$err\n$stackTrace');
|
||||
} on AppConnectionException {
|
||||
throwToolExit(
|
||||
'Failed to establish connection with the application instance in Chrome.\n'
|
||||
'This can happen if the websocket connection used by the web tooling is '
|
||||
'unabled to correctly establish a connection, for example due to a firewall.'
|
||||
);
|
||||
} on MissingPortFile {
|
||||
'Failed to establish connection with the application instance in Chrome.\n'
|
||||
'This can happen if the websocket connection used by the web tooling is '
|
||||
'unabled to correctly establish a connection, for example due to a firewall.');
|
||||
} on MissingPortFile {
|
||||
throwToolExit(
|
||||
'Failed to connect to build daemon.\nThe daemon either failed to '
|
||||
'start or was killed by another process.');
|
||||
'Failed to connect to build daemon.\nThe daemon either failed to '
|
||||
'start or was killed by another process.');
|
||||
} on SocketException catch (err) {
|
||||
throwToolExit(err.toString());
|
||||
} on StateError catch (err) {
|
||||
final String message = err.toString();
|
||||
if (message.contains('Unable to start build daemon')) {
|
||||
throwToolExit(
|
||||
'Failed to start build daemon. The process might have '
|
||||
'exited unexpectedly during startup. Try running the application '
|
||||
'again.');
|
||||
throwToolExit('Failed to start build daemon. The process might have '
|
||||
'exited unexpectedly during startup. Try running the application '
|
||||
'again.');
|
||||
}
|
||||
rethrow;
|
||||
} finally {
|
||||
|
@ -275,61 +674,6 @@ class ResidentWebRunner extends ResidentRunner {
|
|||
return 1;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> attach({
|
||||
Completer<DebugConnectionInfo> connectionInfoCompleter,
|
||||
Completer<void> appStartedCompleter,
|
||||
}) async {
|
||||
Uri websocketUri;
|
||||
if (supportsServiceProtocol) {
|
||||
// Cleanup old subscriptions. These will throw if there isn't anything
|
||||
// listening, which is fine because that is what we want to ensure.
|
||||
try {
|
||||
await _vmService.streamCancel('Stdout');
|
||||
} on vmservice.RPCError {
|
||||
// Ignore this specific error.
|
||||
}
|
||||
try {
|
||||
await _vmService.streamListen('Stdout');
|
||||
} on vmservice.RPCError {
|
||||
// Ignore this specific error.
|
||||
}
|
||||
_stdOutSub = _vmService.onStdoutEvent.listen((vmservice.Event log) {
|
||||
final String message = utf8.decode(base64.decode(log.bytes)).trim();
|
||||
printStatus(message);
|
||||
});
|
||||
unawaited(_vmService.registerService('reloadSources', 'FlutterTools'));
|
||||
websocketUri = Uri.parse(_connectionResult.debugConnection.uri);
|
||||
// Always run main after connecting because start paused doesn't work yet.
|
||||
if (!debuggingOptions.startPaused || !supportsServiceProtocol) {
|
||||
_connectionResult.appConnection.runMain();
|
||||
} else {
|
||||
StreamSubscription<void> resumeSub;
|
||||
resumeSub = _connectionResult.debugConnection.vmService.onDebugEvent.listen((vmservice.Event event) {
|
||||
if (event.type == vmservice.EventKind.kResume) {
|
||||
_connectionResult.appConnection.runMain();
|
||||
resumeSub.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (websocketUri != null) {
|
||||
printStatus('Debug service listening on $websocketUri');
|
||||
}
|
||||
connectionInfoCompleter?.complete(
|
||||
DebugConnectionInfo(wsUri: websocketUri)
|
||||
);
|
||||
|
||||
if (stayResident) {
|
||||
await waitForAppToFinish();
|
||||
} else {
|
||||
await stopEchoingDeviceLog();
|
||||
await exitApp();
|
||||
}
|
||||
await cleanupAtFinish();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<OperationResult> restart({
|
||||
bool fullRestart = false,
|
||||
|
@ -356,10 +700,11 @@ class ResidentWebRunner extends ResidentRunner {
|
|||
flutterUsage.sendTiming('hot', 'web-recompile', recompileDuration);
|
||||
try {
|
||||
final vmservice.Response reloadResponse = fullRestart
|
||||
? await _vmService.callServiceExtension('fullReload')
|
||||
: await _vmService.callServiceExtension('hotRestart');
|
||||
? await _vmService.callServiceExtension('fullReload')
|
||||
: await _vmService.callServiceExtension('hotRestart');
|
||||
final String verb = fullRestart ? 'Restarted' : 'Reloaded';
|
||||
printStatus('$verb application in ${getElapsedAsMilliseconds(timer.elapsed)}.');
|
||||
printStatus(
|
||||
'$verb application in ${getElapsedAsMilliseconds(timer.elapsed)}.');
|
||||
|
||||
// Send timing analytics for full restart and for refresh.
|
||||
final bool wasSuccessful = reloadResponse.type == 'Success';
|
||||
|
@ -375,9 +720,10 @@ class ResidentWebRunner extends ResidentRunner {
|
|||
return OperationResult(1, 'Page requires refresh.');
|
||||
} finally {
|
||||
status.stop();
|
||||
HotEvent('restart',
|
||||
HotEvent(
|
||||
'restart',
|
||||
targetPlatform: getNameForTargetPlatform(TargetPlatform.web_javascript),
|
||||
sdkName: await device.sdkNameAndVersion,
|
||||
sdkName: await device.device.sdkNameAndVersion,
|
||||
emulator: false,
|
||||
fullRestart: true,
|
||||
reason: reason,
|
||||
|
@ -405,159 +751,58 @@ class ResidentWebRunner extends ResidentRunner {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<void> debugDumpApp() async {
|
||||
try {
|
||||
await _vmService.callServiceExtension(
|
||||
'ext.flutter.debugDumpApp',
|
||||
);
|
||||
} on vmservice.RPCError {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> debugDumpRenderTree() async {
|
||||
try {
|
||||
await _vmService.callServiceExtension(
|
||||
'ext.flutter.debugDumpRenderTree',
|
||||
);
|
||||
} on vmservice.RPCError {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> debugDumpLayerTree() async {
|
||||
try {
|
||||
await _vmService.callServiceExtension(
|
||||
'ext.flutter.debugDumpLayerTree',
|
||||
);
|
||||
} on vmservice.RPCError {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> debugDumpSemanticsTreeInTraversalOrder() async {
|
||||
try {
|
||||
await _vmService.callServiceExtension(
|
||||
'ext.flutter.debugDumpSemanticsTreeInTraversalOrder');
|
||||
} on vmservice.RPCError {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> debugTogglePlatform() async {
|
||||
try {
|
||||
final vmservice.Response response = await _vmService.callServiceExtension(
|
||||
'ext.flutter.platformOverride');
|
||||
final String currentPlatform = response.json['value'];
|
||||
String nextPlatform;
|
||||
switch (currentPlatform) {
|
||||
case 'android':
|
||||
nextPlatform = 'iOS';
|
||||
break;
|
||||
case 'iOS':
|
||||
nextPlatform = 'android';
|
||||
break;
|
||||
Future<int> attach({
|
||||
Completer<DebugConnectionInfo> connectionInfoCompleter,
|
||||
Completer<void> appStartedCompleter,
|
||||
}) async {
|
||||
Uri websocketUri;
|
||||
if (supportsServiceProtocol) {
|
||||
// Cleanup old subscriptions. These will throw if there isn't anything
|
||||
// listening, which is fine because that is what we want to ensure.
|
||||
try {
|
||||
await _vmService.streamCancel('Stdout');
|
||||
} on vmservice.RPCError {
|
||||
// It is safe to ignore this error because we expect an error to be
|
||||
// thrown if we're not already subscribed.
|
||||
}
|
||||
if (nextPlatform == null) {
|
||||
return;
|
||||
try {
|
||||
await _vmService.streamListen('Stdout');
|
||||
} 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 _vmService.callServiceExtension(
|
||||
'ext.flutter.platformOverride', args: <String, Object>{
|
||||
'value': nextPlatform,
|
||||
_stdOutSub = _vmService.onStdoutEvent.listen((vmservice.Event log) {
|
||||
final String message = utf8.decode(base64.decode(log.bytes)).trim();
|
||||
printStatus(message);
|
||||
});
|
||||
unawaited(_vmService.registerService('reloadSources', 'FlutterTools'));
|
||||
websocketUri = Uri.parse(_connectionResult.debugConnection.uri);
|
||||
// Always run main after connecting because start paused doesn't work yet.
|
||||
if (!debuggingOptions.startPaused || !supportsServiceProtocol) {
|
||||
_connectionResult.appConnection.runMain();
|
||||
} else {
|
||||
StreamSubscription<void> resumeSub;
|
||||
resumeSub = _connectionResult.debugConnection.vmService.onDebugEvent
|
||||
.listen((vmservice.Event event) {
|
||||
if (event.type == vmservice.EventKind.kResume) {
|
||||
_connectionResult.appConnection.runMain();
|
||||
resumeSub.cancel();
|
||||
}
|
||||
});
|
||||
printStatus('Switched operating system to $nextPlatform');
|
||||
} on vmservice.RPCError {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> debugDumpSemanticsTreeInInverseHitTestOrder() async {
|
||||
try {
|
||||
await _vmService.callServiceExtension(
|
||||
'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder');
|
||||
} on vmservice.RPCError {
|
||||
return;
|
||||
if (websocketUri != null) {
|
||||
printStatus('Debug service listening on $websocketUri');
|
||||
}
|
||||
}
|
||||
connectionInfoCompleter?.complete(DebugConnectionInfo(wsUri: websocketUri));
|
||||
|
||||
|
||||
@override
|
||||
Future<void> debugToggleDebugPaintSizeEnabled() async {
|
||||
try {
|
||||
final vmservice.Response response = await _vmService.callServiceExtension(
|
||||
'ext.flutter.debugPaint',
|
||||
);
|
||||
await _vmService.callServiceExtension(
|
||||
'ext.flutter.debugPaint',
|
||||
args: <dynamic, dynamic>{'enabled': !(response.json['enabled'] == 'true')},
|
||||
);
|
||||
} on vmservice.RPCError {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> debugToggleDebugCheckElevationsEnabled() async {
|
||||
try {
|
||||
final vmservice.Response response = await _vmService.callServiceExtension(
|
||||
'ext.flutter.debugCheckElevationsEnabled',
|
||||
);
|
||||
await _vmService.callServiceExtension(
|
||||
'ext.flutter.debugCheckElevationsEnabled',
|
||||
args: <dynamic, dynamic>{'enabled': !(response.json['enabled'] == 'true')},
|
||||
);
|
||||
} on vmservice.RPCError {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> debugTogglePerformanceOverlayOverride() async {
|
||||
try {
|
||||
final vmservice.Response response = await _vmService.callServiceExtension(
|
||||
'ext.flutter.showPerformanceOverlay'
|
||||
);
|
||||
await _vmService.callServiceExtension(
|
||||
'ext.flutter.showPerformanceOverlay',
|
||||
args: <dynamic, dynamic>{'enabled': !(response.json['enabled'] == 'true')},
|
||||
);
|
||||
} on vmservice.RPCError {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> debugToggleWidgetInspector() async {
|
||||
try {
|
||||
final vmservice.Response response = await _vmService.callServiceExtension(
|
||||
'ext.flutter.debugToggleWidgetInspector'
|
||||
);
|
||||
await _vmService.callServiceExtension(
|
||||
'ext.flutter.debugToggleWidgetInspector',
|
||||
args: <dynamic, dynamic>{'enabled': !(response.json['enabled'] == 'true')},
|
||||
);
|
||||
} on vmservice.RPCError {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> debugToggleProfileWidgetBuilds() async {
|
||||
try {
|
||||
final vmservice.Response response = await _vmService.callServiceExtension(
|
||||
'ext.flutter.profileWidgetBuilds'
|
||||
);
|
||||
await _vmService.callServiceExtension(
|
||||
'ext.flutter.profileWidgetBuilds',
|
||||
args: <dynamic, dynamic>{'enabled': !(response.json['enabled'] == 'true')},
|
||||
);
|
||||
} on vmservice.RPCError {
|
||||
return;
|
||||
if (stayResident) {
|
||||
await waitForAppToFinish();
|
||||
} else {
|
||||
await stopEchoingDeviceLog();
|
||||
await exitApp();
|
||||
}
|
||||
await cleanupAtFinish();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -430,7 +430,7 @@ class AppDomain extends Domain {
|
|||
|
||||
if (await device.targetPlatform == TargetPlatform.web_javascript) {
|
||||
runner = webRunnerFactory.createWebRunner(
|
||||
device,
|
||||
flutterDevice,
|
||||
flutterProject: flutterProject,
|
||||
target: target,
|
||||
debuggingOptions: options,
|
||||
|
|
|
@ -453,7 +453,7 @@ class RunCommand extends RunCommandBase {
|
|||
);
|
||||
} else if (webMode) {
|
||||
runner = webRunnerFactory.createWebRunner(
|
||||
devices.single,
|
||||
flutterDevices.single,
|
||||
target: targetFile,
|
||||
flutterProject: flutterProject,
|
||||
ipv6: ipv6,
|
||||
|
|
|
@ -201,7 +201,6 @@ class PackageUriMapper {
|
|||
PackageUriMapper(String scriptPath, String packagesPath, String fileSystemScheme, List<String> fileSystemRoots) {
|
||||
final Map<String, Uri> packageMap = PackageMap(fs.path.absolute(packagesPath)).map;
|
||||
final String scriptUri = Uri.file(scriptPath, windows: platform.isWindows).toString();
|
||||
|
||||
for (String packageName in packageMap.keys) {
|
||||
final String prefix = packageMap[packageName].toString();
|
||||
// Only perform a multi-root mapping if there are multiple roots.
|
||||
|
|
|
@ -349,12 +349,19 @@ class UpdateFSReport {
|
|||
int get invalidatedSourcesCount => _invalidatedSourcesCount;
|
||||
int get syncedBytes => _syncedBytes;
|
||||
|
||||
/// JavaScript modules produced by the incremental compiler in `dartdevc`
|
||||
/// mode.
|
||||
///
|
||||
/// Only used for JavaScript compilation.
|
||||
List<String> invalidatedModules;
|
||||
|
||||
void incorporateResults(UpdateFSReport report) {
|
||||
if (!report._success) {
|
||||
_success = false;
|
||||
}
|
||||
_invalidatedSourcesCount += report._invalidatedSourcesCount;
|
||||
_syncedBytes += report._syncedBytes;
|
||||
invalidatedModules ??= report.invalidatedModules;
|
||||
}
|
||||
|
||||
bool _success;
|
||||
|
|
|
@ -151,10 +151,15 @@ const Feature flutterAndroidEmbeddingV2Feature = Feature(
|
|||
const Feature flutterWebIncrementalCompiler = Feature(
|
||||
name: 'Enable the incremental compiler for web builds',
|
||||
configSetting: 'enable-web-incremental-compiler',
|
||||
environmentOverride: 'WEB_INCREMENTAL_COMPILER',
|
||||
master: FeatureChannelSetting(
|
||||
available: true,
|
||||
enabledByDefault: false,
|
||||
),
|
||||
dev: FeatureChannelSetting(
|
||||
available: true,
|
||||
enabledByDefault: false,
|
||||
),
|
||||
);
|
||||
|
||||
/// A [Feature] is a process for conditionally enabling tool features.
|
||||
|
|
|
@ -75,21 +75,25 @@ define("main_module", ["$entrypoint", "dart_sdk"], function(app, dart_sdk) {
|
|||
dart_sdk.dart.setStartAsyncSynchronously(true);
|
||||
dart_sdk._isolate_helper.startRootIsolate(() => {}, []);
|
||||
dart_sdk._debugger.registerDevtoolsFormatter();
|
||||
dart_sdk.ui.webOnlyInitializePlatform();
|
||||
let voidToNull = () => (voidToNull = dart_sdk.dart.constFn(dart_sdk.dart.fnType(dart_sdk.core.Null, [dart_sdk.dart.void])))();
|
||||
|
||||
// Attach the main entrypoint and hot reload functionality to the window.
|
||||
window.\$mainEntrypoint = app.main.main;
|
||||
if (window.\$hotReload == null) {
|
||||
window.\$hotReload = function(cb) {
|
||||
dart_sdk.developer.invokeExtension("ext.flutter.disassemble", "{}");
|
||||
dart_sdk.dart.hotRestart();
|
||||
window.\$mainEntrypoint();
|
||||
if (cb != null) {
|
||||
cb();
|
||||
}
|
||||
dart_sdk.developer.invokeExtension("ext.flutter.disassemble", "{}").then((_) => {
|
||||
dart_sdk.dart.hotRestart();
|
||||
dart_sdk.ui.webOnlyInitializePlatform().then(dart_sdk.core.Null, dart_sdk.dart.fn(_ => {
|
||||
window.\$mainEntrypoint();
|
||||
window.requestAnimationFrame(cb);
|
||||
}, voidToNull()));
|
||||
});
|
||||
}
|
||||
}
|
||||
app.main.main();
|
||||
|
||||
dart_sdk.ui.webOnlyInitializePlatform().then(dart_sdk.core.Null, dart_sdk.dart.fn(_ => {
|
||||
app.main.main();
|
||||
}, voidToNull()));
|
||||
});
|
||||
|
||||
// Require JS configuration.
|
||||
|
|
|
@ -71,6 +71,11 @@ void resetChromeForTesting() {
|
|||
ChromeLauncher._currentCompleter = Completer<Chrome>();
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
void launchChromeInstance(Chrome chrome) {
|
||||
ChromeLauncher._currentCompleter.complete(chrome);
|
||||
}
|
||||
|
||||
/// Responsible for launching chrome with devtools configured.
|
||||
class ChromeLauncher {
|
||||
const ChromeLauncher();
|
||||
|
|
|
@ -7,12 +7,18 @@ import 'dart:typed_data';
|
|||
import 'package:meta/meta.dart';
|
||||
import 'package:mime/mime.dart' as mime;
|
||||
|
||||
import '../artifacts.dart';
|
||||
import '../asset.dart';
|
||||
import '../base/common.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/io.dart';
|
||||
import '../build_info.dart';
|
||||
import '../bundle.dart';
|
||||
import '../compile.dart';
|
||||
import '../convert.dart';
|
||||
import '../devfs.dart';
|
||||
import '../globals.dart';
|
||||
import 'bootstrap.dart';
|
||||
|
||||
/// A web server which handles serving JavaScript and assets.
|
||||
///
|
||||
|
@ -49,7 +55,10 @@ class WebAssetServer {
|
|||
}
|
||||
|
||||
final HttpServer _httpServer;
|
||||
// If holding these in memory is too much overhead, this can be switched to a
|
||||
// RandomAccessFile and read on demand.
|
||||
final Map<String, Uint8List> _files = <String, Uint8List>{};
|
||||
final Map<String, Uint8List> _sourcemaps = <String, Uint8List>{};
|
||||
|
||||
// handle requests for JavaScript source, dart sources maps, or asset files.
|
||||
Future<void> _handleRequest(HttpRequest request) async {
|
||||
|
@ -71,8 +80,7 @@ class WebAssetServer {
|
|||
}
|
||||
|
||||
// If this is a JavaScript file, it must be in the in-memory cache.
|
||||
// Attempt to look up the file by URI, returning a 404 if it is not
|
||||
// found.
|
||||
// Attempt to look up the file by URI.
|
||||
if (_files.containsKey(request.uri.path)) {
|
||||
final List<int> bytes = _files[request.uri.path];
|
||||
response.headers
|
||||
|
@ -82,6 +90,18 @@ class WebAssetServer {
|
|||
await response.close();
|
||||
return;
|
||||
}
|
||||
// If this is a sourcemap file, then it might be in the in-memory cache.
|
||||
// Attempt to lookup the file by URI.
|
||||
if (_sourcemaps.containsKey(request.uri.path)) {
|
||||
final List<int> bytes = _sourcemaps[request.uri.path];
|
||||
response.headers
|
||||
..add('Content-Length', bytes.length)
|
||||
..add('Content-Type', 'application/json');
|
||||
response.add(bytes);
|
||||
await response.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// If this is a dart file, it must be on the local file system and is
|
||||
// likely coming from a source map request. Attempt to look in the
|
||||
// local filesystem for it, and return a 404 if it is not found. The tool
|
||||
|
@ -95,6 +115,18 @@ class WebAssetServer {
|
|||
file = fs.file(fs.path.join(getAssetBuildDirectory(), fs.path.relative(assetPath)));
|
||||
}
|
||||
|
||||
// If it isn't a project source or an asset, it must be a dart SDK source.
|
||||
// or a flutter web SDK source.
|
||||
if (!file.existsSync()) {
|
||||
final Directory dartSdkParent = fs.directory(artifacts.getArtifactPath(Artifact.engineDartSdkPath)).parent;
|
||||
file = fs.file(fs.path.joinAll(<String>[dartSdkParent.path, ...request.uri.pathSegments]));
|
||||
}
|
||||
|
||||
if (!file.existsSync()) {
|
||||
final String flutterWebSdk = artifacts.getArtifactPath(Artifact.flutterWebSdk);
|
||||
file = fs.file(fs.path.joinAll(<String>[flutterWebSdk, ...request.uri.pathSegments]));
|
||||
}
|
||||
|
||||
if (!file.existsSync()) {
|
||||
response.statusCode = HttpStatus.notFound;
|
||||
await response.close();
|
||||
|
@ -131,30 +163,186 @@ class WebAssetServer {
|
|||
/// Update the in-memory asset server with the provided source and manifest files.
|
||||
///
|
||||
/// Returns a list of updated modules.
|
||||
List<String> write(File sourceFile, File manifestFile) {
|
||||
List<String> write(File codeFile, File manifestFile, File sourcemapFile) {
|
||||
final List<String> modules = <String>[];
|
||||
final Uint8List bytes = sourceFile.readAsBytesSync();
|
||||
final Uint8List codeBytes = codeFile.readAsBytesSync();
|
||||
final Uint8List sourcemapBytes = sourcemapFile.readAsBytesSync();
|
||||
final Map<String, Object> manifest = json.decode(manifestFile.readAsStringSync());
|
||||
for (String filePath in manifest.keys) {
|
||||
if (filePath == null) {
|
||||
printTrace('Invalid manfiest file: $filePath');
|
||||
continue;
|
||||
}
|
||||
final List<Object> offsets = manifest[filePath];
|
||||
if (offsets.length != 2) {
|
||||
final Map<String, Object> offsets = manifest[filePath];
|
||||
final List<Object> codeOffsets = offsets['code'];
|
||||
final List<Object> sourcemapOffsets = offsets['sourcemap'];
|
||||
if (codeOffsets.length != 2 || sourcemapOffsets.length != 2) {
|
||||
printTrace('Invalid manifest byte offsets: $offsets');
|
||||
continue;
|
||||
}
|
||||
final int start = offsets[0];
|
||||
final int end = offsets[1];
|
||||
if (start < 0 || end > bytes.lengthInBytes) {
|
||||
printTrace('Invalid byte index: [$start, $end]');
|
||||
|
||||
final int codeStart = codeOffsets[0];
|
||||
final int codeEnd = codeOffsets[1];
|
||||
if (codeStart < 0 || codeEnd > codeBytes.lengthInBytes) {
|
||||
printTrace('Invalid byte index: [$codeStart, $codeEnd]');
|
||||
continue;
|
||||
}
|
||||
final Uint8List byteView = Uint8List.view(bytes.buffer, start, end - start);
|
||||
final Uint8List byteView = Uint8List.view(
|
||||
codeBytes.buffer,
|
||||
codeStart,
|
||||
codeEnd - codeStart,
|
||||
);
|
||||
_files[filePath] = byteView;
|
||||
|
||||
final int sourcemapStart = sourcemapOffsets[0];
|
||||
final int sourcemapEnd = sourcemapOffsets[1];
|
||||
if (sourcemapStart < 0 || sourcemapEnd > sourcemapBytes.lengthInBytes) {
|
||||
printTrace('Invalid byte index: [$sourcemapStart, $sourcemapEnd]');
|
||||
continue;
|
||||
}
|
||||
final Uint8List sourcemapView = Uint8List.view(
|
||||
sourcemapBytes.buffer,
|
||||
sourcemapStart,
|
||||
sourcemapEnd - sourcemapStart ,
|
||||
);
|
||||
_sourcemaps['$filePath.map'] = sourcemapView;
|
||||
|
||||
modules.add(filePath);
|
||||
}
|
||||
return modules;
|
||||
}
|
||||
}
|
||||
|
||||
class WebDevFS implements DevFS {
|
||||
WebDevFS(this.hostname, this.port, this._packagesFilePath);
|
||||
|
||||
final String hostname;
|
||||
final int port;
|
||||
final String _packagesFilePath;
|
||||
WebAssetServer _webAssetServer;
|
||||
|
||||
@override
|
||||
List<Uri> sources = <Uri>[];
|
||||
|
||||
@override
|
||||
DateTime lastCompiled;
|
||||
|
||||
// We do not evict assets on the web.
|
||||
@override
|
||||
Set<String> get assetPathsToEvict => const <String>{};
|
||||
|
||||
@override
|
||||
Uri get baseUri => null;
|
||||
|
||||
@override
|
||||
Future<Uri> create() async {
|
||||
_webAssetServer = await WebAssetServer.start(hostname, port);
|
||||
return Uri.base;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> destroy() async {
|
||||
await _webAssetServer.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Uri deviceUriToHostUri(Uri deviceUri) {
|
||||
return deviceUri;
|
||||
}
|
||||
|
||||
@override
|
||||
String get fsName => 'web_asset';
|
||||
|
||||
@override
|
||||
Directory get rootDirectory => null;
|
||||
|
||||
@override
|
||||
Future<UpdateFSReport> update({
|
||||
String mainPath,
|
||||
String target,
|
||||
AssetBundle bundle,
|
||||
DateTime firstBuildTime,
|
||||
bool bundleFirstUpload = false,
|
||||
@required ResidentCompiler generator,
|
||||
String dillOutputPath,
|
||||
@required bool trackWidgetCreation,
|
||||
bool fullRestart = false,
|
||||
String projectRootPath,
|
||||
String pathToReload,
|
||||
List<Uri> invalidatedFiles,
|
||||
}) async {
|
||||
assert(trackWidgetCreation != null);
|
||||
assert(generator != null);
|
||||
if (bundleFirstUpload) {
|
||||
final File requireJS = fs.file(fs.path.join(
|
||||
artifacts.getArtifactPath(Artifact.engineDartSdkPath),
|
||||
'lib',
|
||||
'dev_compiler',
|
||||
'kernel',
|
||||
'amd',
|
||||
'require.js',
|
||||
));
|
||||
final File dartSdk = fs.file(fs.path.join(
|
||||
artifacts.getArtifactPath(Artifact.flutterWebSdk),
|
||||
'kernel',
|
||||
'amd',
|
||||
'dart_sdk.js',
|
||||
));
|
||||
final File dartSdkSourcemap = fs.file(fs.path.join(
|
||||
artifacts.getArtifactPath(Artifact.flutterWebSdk),
|
||||
'kernel',
|
||||
'amd',
|
||||
'dart_sdk.js.map',
|
||||
));
|
||||
final File stackTraceMapper = fs.file(fs.path.join(
|
||||
artifacts.getArtifactPath(Artifact.engineDartSdkPath),
|
||||
'lib',
|
||||
'dev_compiler',
|
||||
'web',
|
||||
'dart_stack_trace_mapper.js',
|
||||
));
|
||||
_webAssetServer.writeFile('/main.dart.js', generateBootstrapScript(
|
||||
requireUrl: requireJS.path,
|
||||
mapperUrl: stackTraceMapper.path,
|
||||
entrypoint: '$mainPath.js',
|
||||
));
|
||||
_webAssetServer.writeFile('/main_module.js', generateMainModule(
|
||||
entrypoint: '$mainPath.js',
|
||||
));
|
||||
_webAssetServer.writeFile('/dart_sdk.js', dartSdk.readAsStringSync());
|
||||
_webAssetServer.writeFile('/dart_sdk.js.map', dartSdkSourcemap.readAsStringSync());
|
||||
}
|
||||
final DateTime candidateCompileTime = DateTime.now();
|
||||
if (fullRestart) {
|
||||
generator.reset();
|
||||
}
|
||||
final CompilerOutput compilerOutput = await generator.recompile(
|
||||
mainPath,
|
||||
invalidatedFiles,
|
||||
outputPath: dillOutputPath ?? getDefaultApplicationKernelPath(trackWidgetCreation: trackWidgetCreation),
|
||||
packagesFilePath : _packagesFilePath,
|
||||
);
|
||||
if (compilerOutput == null || compilerOutput.errorCount > 0) {
|
||||
return UpdateFSReport(success: false);
|
||||
}
|
||||
// Only update the last compiled time if we successfully compiled.
|
||||
lastCompiled = candidateCompileTime;
|
||||
// list of sources that needs to be monitored are in [compilerOutput.sources]
|
||||
sources = compilerOutput.sources;
|
||||
File codeFile;
|
||||
File manifestFile;
|
||||
File sourcemapFile;
|
||||
List<String> modules;
|
||||
try {
|
||||
codeFile = fs.file('${compilerOutput.outputFilename}.sources');
|
||||
manifestFile = fs.file('${compilerOutput.outputFilename}.json');
|
||||
sourcemapFile = fs.file('${compilerOutput.outputFilename}.map');
|
||||
modules = _webAssetServer.write(codeFile, manifestFile, sourcemapFile);
|
||||
} on FileSystemException catch (err) {
|
||||
throwToolExit('Failed to load recompiled sources:\n$err');
|
||||
}
|
||||
return UpdateFSReport(success: true, syncedBytes: codeFile.lengthSync(),
|
||||
invalidatedSourcesCount: invalidatedFiles.length)
|
||||
..invalidatedModules = modules;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ abstract class WebRunnerFactory {
|
|||
|
||||
/// Create a [ResidentRunner] for the web.
|
||||
ResidentRunner createWebRunner(
|
||||
Device device, {
|
||||
FlutterDevice device, {
|
||||
String target,
|
||||
@required bool stayResident,
|
||||
@required FlutterProject flutterProject,
|
||||
|
|
|
@ -63,11 +63,12 @@ void main() {
|
|||
test('Refuses to build using runner when missing index.html', () => testbed.run(() async {
|
||||
fs.file(fs.path.join('web', 'index.html')).deleteSync();
|
||||
|
||||
final ResidentWebRunner runner = ResidentWebRunner(
|
||||
final ResidentWebRunner runner = DwdsWebRunnerFactory().createWebRunner(
|
||||
null,
|
||||
flutterProject: FlutterProject.current(),
|
||||
ipv6: false,
|
||||
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
|
||||
stayResident: true,
|
||||
);
|
||||
expect(await runner.run(), 1);
|
||||
}));
|
||||
|
|
|
@ -26,17 +26,21 @@ void main() {
|
|||
Testbed testbed;
|
||||
MockFlutterWebFs mockWebFs;
|
||||
ResidentWebRunner residentWebRunner;
|
||||
MockFlutterDevice mockFlutterDevice;
|
||||
|
||||
setUp(() {
|
||||
mockWebFs = MockFlutterWebFs();
|
||||
final MockWebDevice mockWebDevice = MockWebDevice();
|
||||
mockFlutterDevice = MockFlutterDevice();
|
||||
when(mockFlutterDevice.device).thenReturn(mockWebDevice);
|
||||
testbed = Testbed(
|
||||
setup: () {
|
||||
residentWebRunner = ResidentWebRunner(
|
||||
mockWebDevice,
|
||||
residentWebRunner = residentWebRunner = DwdsWebRunnerFactory().createWebRunner(
|
||||
mockFlutterDevice,
|
||||
flutterProject: FlutterProject.current(),
|
||||
debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
|
||||
ipv6: true,
|
||||
stayResident: true,
|
||||
);
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
|
@ -118,4 +122,4 @@ class MockFlutterWebFs extends Mock implements WebFs {}
|
|||
class MockDebugConnection extends Mock implements DebugConnection {}
|
||||
class MockVmService extends Mock implements VmService {}
|
||||
class MockStatus extends Mock implements Status {}
|
||||
|
||||
class MockFlutterDevice extends Mock implements FlutterDevice {}
|
||||
|
|
|
@ -12,17 +12,22 @@ import 'package:flutter_tools/src/base/file_system.dart';
|
|||
import 'package:flutter_tools/src/base/io.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/build_info.dart';
|
||||
import 'package:flutter_tools/src/compile.dart';
|
||||
import 'package:flutter_tools/src/devfs.dart';
|
||||
import 'package:flutter_tools/src/device.dart';
|
||||
import 'package:flutter_tools/src/features.dart';
|
||||
import 'package:flutter_tools/src/globals.dart';
|
||||
import 'package:flutter_tools/src/project.dart';
|
||||
import 'package:flutter_tools/src/reporting/reporting.dart';
|
||||
import 'package:flutter_tools/src/resident_runner.dart';
|
||||
import 'package:flutter_tools/src/build_runner/resident_web_runner.dart';
|
||||
import 'package:flutter_tools/src/build_runner/web_fs.dart';
|
||||
import 'package:flutter_tools/src/web/chrome.dart';
|
||||
import 'package:flutter_tools/src/web/web_device.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:vm_service/vm_service.dart';
|
||||
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
|
||||
|
||||
import '../src/common.dart';
|
||||
import '../src/testbed.dart';
|
||||
|
@ -33,22 +38,41 @@ void main() {
|
|||
ResidentWebRunner residentWebRunner;
|
||||
MockDebugConnection mockDebugConnection;
|
||||
MockVmService mockVmService;
|
||||
MockWebDevice mockWebDevice;
|
||||
MockChromeDevice mockChromeDevice;
|
||||
MockAppConnection mockAppConnection;
|
||||
MockFlutterDevice mockFlutterDevice;
|
||||
MockWebDevFS mockWebDevFS;
|
||||
MockResidentCompiler mockResidentCompiler;
|
||||
MockChrome mockChrome;
|
||||
MockChromeConnection mockChromeConnection;
|
||||
MockChromeTab mockChromeTab;
|
||||
MockWipConnection mockWipConnection;
|
||||
MockWipDebugger mockWipDebugger;
|
||||
|
||||
setUp(() {
|
||||
resetChromeForTesting();
|
||||
mockWebFs = MockFlutterWebFs();
|
||||
mockDebugConnection = MockDebugConnection();
|
||||
mockVmService = MockVmService();
|
||||
mockWebDevice = MockWebDevice();
|
||||
mockChromeDevice = MockChromeDevice();
|
||||
mockAppConnection = MockAppConnection();
|
||||
mockFlutterDevice = MockFlutterDevice();
|
||||
mockWebDevFS = MockWebDevFS();
|
||||
mockResidentCompiler = MockResidentCompiler();
|
||||
mockChrome = MockChrome();
|
||||
mockChromeConnection = MockChromeConnection();
|
||||
mockChromeTab = MockChromeTab();
|
||||
mockWipConnection = MockWipConnection();
|
||||
mockWipDebugger = MockWipDebugger();
|
||||
when(mockFlutterDevice.device).thenReturn(mockChromeDevice);
|
||||
testbed = Testbed(
|
||||
setup: () {
|
||||
residentWebRunner = ResidentWebRunner(
|
||||
mockWebDevice,
|
||||
residentWebRunner = DwdsWebRunnerFactory().createWebRunner(
|
||||
mockFlutterDevice,
|
||||
flutterProject: FlutterProject.current(),
|
||||
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
|
||||
ipv6: true,
|
||||
stayResident: true,
|
||||
);
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
|
@ -88,26 +112,44 @@ void main() {
|
|||
return const Stream<Event>.empty();
|
||||
});
|
||||
when(mockDebugConnection.uri).thenReturn('ws://127.0.0.1/abcd/');
|
||||
when(mockFlutterDevice.devFS).thenReturn(mockWebDevFS);
|
||||
when(mockWebDevFS.sources).thenReturn(<Uri>[]);
|
||||
when(mockFlutterDevice.generator).thenReturn(mockResidentCompiler);
|
||||
when(mockChrome.chromeConnection).thenReturn(mockChromeConnection);
|
||||
when(mockChromeConnection.getTab(any)).thenAnswer((Invocation invocation) async {
|
||||
return mockChromeTab;
|
||||
});
|
||||
when(mockChromeTab.connect()).thenAnswer((Invocation invocation) async {
|
||||
return mockWipConnection;
|
||||
});
|
||||
when(mockWipConnection.debugger).thenReturn(mockWipDebugger);
|
||||
}
|
||||
|
||||
test('runner with web server device does not support debugging', () => testbed.run(() {
|
||||
final ResidentRunner profileResidentWebRunner = ResidentWebRunner(
|
||||
WebServerDevice(),
|
||||
when(mockFlutterDevice.device).thenReturn(WebServerDevice());
|
||||
final ResidentRunner profileResidentWebRunner = residentWebRunner = DwdsWebRunnerFactory().createWebRunner(
|
||||
mockFlutterDevice,
|
||||
flutterProject: FlutterProject.current(),
|
||||
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
|
||||
ipv6: true,
|
||||
stayResident: true,
|
||||
);
|
||||
|
||||
expect(profileResidentWebRunner.debuggingEnabled, false);
|
||||
|
||||
when(mockFlutterDevice.device).thenReturn(MockChromeDevice());
|
||||
|
||||
expect(residentWebRunner.debuggingEnabled, true);
|
||||
}));
|
||||
|
||||
test('profile does not supportsServiceProtocol', () => testbed.run(() {
|
||||
final ResidentRunner profileResidentWebRunner = ResidentWebRunner(
|
||||
MockWebDevice(),
|
||||
when(mockFlutterDevice.device).thenReturn(mockChromeDevice);
|
||||
final ResidentRunner profileResidentWebRunner = DwdsWebRunnerFactory().createWebRunner(
|
||||
mockFlutterDevice,
|
||||
flutterProject: FlutterProject.current(),
|
||||
debuggingOptions: DebuggingOptions.enabled(BuildInfo.profile),
|
||||
ipv6: true,
|
||||
stayResident: true,
|
||||
);
|
||||
|
||||
expect(profileResidentWebRunner.supportsServiceProtocol, false);
|
||||
|
@ -119,7 +161,7 @@ void main() {
|
|||
final BufferLogger bufferLogger = logger;
|
||||
|
||||
expect(await residentWebRunner.run(), 1);
|
||||
expect(bufferLogger.errorText, contains('No application found for TargetPlatform.web_javascript'));
|
||||
expect(bufferLogger.errorText, contains('This application is not configured to build on the web'));
|
||||
}));
|
||||
|
||||
test('Exits on run if target file does not exist', () => testbed.run(() async {
|
||||
|
@ -156,8 +198,8 @@ void main() {
|
|||
|
||||
test('Can successfully run and disconnect with --no-resident', () => testbed.run(() async {
|
||||
_setupMocks();
|
||||
residentWebRunner = ResidentWebRunner(
|
||||
mockWebDevice,
|
||||
residentWebRunner = DwdsWebRunnerFactory().createWebRunner(
|
||||
mockFlutterDevice,
|
||||
flutterProject: FlutterProject.current(),
|
||||
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
|
||||
ipv6: true,
|
||||
|
@ -192,11 +234,12 @@ void main() {
|
|||
}));
|
||||
|
||||
test('Does not run main with --start-paused', () => testbed.run(() async {
|
||||
residentWebRunner = ResidentWebRunner(
|
||||
mockWebDevice,
|
||||
residentWebRunner = DwdsWebRunnerFactory().createWebRunner(
|
||||
mockFlutterDevice,
|
||||
flutterProject: FlutterProject.current(),
|
||||
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, startPaused: true),
|
||||
ipv6: true,
|
||||
stayResident: true,
|
||||
);
|
||||
_setupMocks();
|
||||
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
|
||||
|
@ -244,6 +287,94 @@ void main() {
|
|||
Usage: () => MockFlutterUsage(),
|
||||
}));
|
||||
|
||||
test('Can hot reload after attaching - experimental', () => testbed.run(() async {
|
||||
_setupMocks();
|
||||
launchChromeInstance(mockChrome);
|
||||
when(mockWebDevFS.update(
|
||||
mainPath: anyNamed('mainPath'),
|
||||
target: anyNamed('target'),
|
||||
bundle: anyNamed('bundle'),
|
||||
firstBuildTime: anyNamed('firstBuildTime'),
|
||||
bundleFirstUpload: anyNamed('bundleFirstUpload'),
|
||||
generator: anyNamed('generator'),
|
||||
fullRestart: anyNamed('fullRestart'),
|
||||
dillOutputPath: anyNamed('dillOutputPath'),
|
||||
trackWidgetCreation: anyNamed('trackWidgetCreation'),
|
||||
projectRootPath: anyNamed('projectRootPath'),
|
||||
pathToReload: anyNamed('pathToReload'),
|
||||
invalidatedFiles: anyNamed('invalidatedFiles'),
|
||||
)).thenAnswer((Invocation invocation) async {
|
||||
return UpdateFSReport(success: true)
|
||||
..invalidatedModules = <String>['example'];
|
||||
});
|
||||
final BufferLogger bufferLogger = logger;
|
||||
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
|
||||
unawaited(residentWebRunner.run(
|
||||
connectionInfoCompleter: connectionInfoCompleter,
|
||||
));
|
||||
await connectionInfoCompleter.future;
|
||||
final OperationResult result = await residentWebRunner.restart(fullRestart: false);
|
||||
|
||||
expect(bufferLogger.statusText, contains('Reloaded application in'));
|
||||
expect(result.code, 0);
|
||||
verify(mockResidentCompiler.accept()).called(2);
|
||||
// ensure that analytics are sent.
|
||||
verify(Usage.instance.sendEvent('hot', 'restart', parameters: <String, String>{
|
||||
'cd27': 'web-javascript',
|
||||
'cd28': null,
|
||||
'cd29': 'false',
|
||||
'cd30': 'true',
|
||||
})).called(1);
|
||||
verify(Usage.instance.sendTiming('hot', 'web-incremental-restart', any)).called(1);
|
||||
}, overrides: <Type, Generator>{
|
||||
Usage: () => MockFlutterUsage(),
|
||||
FeatureFlags: () => TestFeatureFlags(isWebIncrementalCompilerEnabled: true),
|
||||
}));
|
||||
|
||||
test('Can hot restart after attaching - experimental', () => testbed.run(() async {
|
||||
_setupMocks();
|
||||
launchChromeInstance(mockChrome);
|
||||
when(mockWebDevFS.update(
|
||||
mainPath: anyNamed('mainPath'),
|
||||
target: anyNamed('target'),
|
||||
bundle: anyNamed('bundle'),
|
||||
firstBuildTime: anyNamed('firstBuildTime'),
|
||||
bundleFirstUpload: anyNamed('bundleFirstUpload'),
|
||||
generator: anyNamed('generator'),
|
||||
fullRestart: anyNamed('fullRestart'),
|
||||
dillOutputPath: anyNamed('dillOutputPath'),
|
||||
trackWidgetCreation: anyNamed('trackWidgetCreation'),
|
||||
projectRootPath: anyNamed('projectRootPath'),
|
||||
pathToReload: anyNamed('pathToReload'),
|
||||
invalidatedFiles: anyNamed('invalidatedFiles'),
|
||||
)).thenAnswer((Invocation invocation) async {
|
||||
return UpdateFSReport(success: true)
|
||||
..invalidatedModules = <String>['example'];
|
||||
});
|
||||
final BufferLogger bufferLogger = logger;
|
||||
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
|
||||
unawaited(residentWebRunner.run(
|
||||
connectionInfoCompleter: connectionInfoCompleter,
|
||||
));
|
||||
await connectionInfoCompleter.future;
|
||||
final OperationResult result = await residentWebRunner.restart(fullRestart: true);
|
||||
|
||||
expect(bufferLogger.statusText, contains('Restarted application in'));
|
||||
expect(result.code, 0);
|
||||
verify(mockResidentCompiler.accept()).called(2);
|
||||
// ensure that analytics are sent.
|
||||
verify(Usage.instance.sendEvent('hot', 'restart', parameters: <String, String>{
|
||||
'cd27': 'web-javascript',
|
||||
'cd28': null,
|
||||
'cd29': 'false',
|
||||
'cd30': 'true',
|
||||
})).called(1);
|
||||
verifyNever(Usage.instance.sendTiming('hot', 'web-incremental-restart', any));
|
||||
}, overrides: <Type, Generator>{
|
||||
Usage: () => MockFlutterUsage(),
|
||||
FeatureFlags: () => TestFeatureFlags(isWebIncrementalCompilerEnabled: true),
|
||||
}));
|
||||
|
||||
test('Can hot restart after attaching', () => testbed.run(() async {
|
||||
_setupMocks();
|
||||
final BufferLogger bufferLogger = logger;
|
||||
|
@ -526,7 +657,7 @@ void main() {
|
|||
test('cleanup of resources is safe to call multiple times', () => testbed.run(() async {
|
||||
_setupMocks();
|
||||
bool debugClosed = false;
|
||||
when(mockWebDevice.stopApp(any)).thenAnswer((Invocation invocation) async {
|
||||
when(mockChromeDevice.stopApp(any)).thenAnswer((Invocation invocation) async {
|
||||
if (debugClosed) {
|
||||
throw StateError('debug connection closed twice');
|
||||
}
|
||||
|
@ -564,7 +695,7 @@ void main() {
|
|||
|
||||
test('Prints target and device name on run', () => testbed.run(() async {
|
||||
_setupMocks();
|
||||
when(mockWebDevice.name).thenReturn('Chromez');
|
||||
when(mockChromeDevice.name).thenReturn('Chromez');
|
||||
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
|
||||
unawaited(residentWebRunner.run(
|
||||
connectionInfoCompleter: connectionInfoCompleter,
|
||||
|
@ -756,10 +887,18 @@ void main() {
|
|||
}
|
||||
|
||||
class MockFlutterUsage extends Mock implements Usage {}
|
||||
class MockWebDevice extends Mock implements ChromeDevice {}
|
||||
class MockChromeDevice extends Mock implements ChromeDevice {}
|
||||
class MockBuildDaemonCreator extends Mock implements BuildDaemonCreator {}
|
||||
class MockFlutterWebFs extends Mock implements WebFs {}
|
||||
class MockDebugConnection extends Mock implements DebugConnection {}
|
||||
class MockAppConnection extends Mock implements AppConnection {}
|
||||
class MockVmService extends Mock implements VmService {}
|
||||
class MockStatus extends Mock implements Status {}
|
||||
class MockFlutterDevice extends Mock implements FlutterDevice {}
|
||||
class MockWebDevFS extends Mock implements DevFS {}
|
||||
class MockResidentCompiler extends Mock implements ResidentCompiler {}
|
||||
class MockChrome extends Mock implements Chrome {}
|
||||
class MockChromeConnection extends Mock implements ChromeConnection {}
|
||||
class MockChromeTab extends Mock implements ChromeTab {}
|
||||
class MockWipConnection extends Mock implements WipConnection {}
|
||||
class MockWipDebugger extends Mock implements WipDebugger {}
|
||||
|
|
|
@ -89,28 +89,36 @@ void main() {
|
|||
test('Handles against malformed manifest', () => testbed.run(() async {
|
||||
final File source = fs.file('source')
|
||||
..writeAsStringSync('main() {}');
|
||||
final File sourcemap = fs.file('sourcemap')
|
||||
..writeAsStringSync('{}');
|
||||
|
||||
// Missing ending offset.
|
||||
final File manifestMissingOffset = fs.file('manifestA')
|
||||
..writeAsStringSync(json.encode(<String, Object>{'/foo.js': <int>[0]}));
|
||||
// Non-file URI.
|
||||
final File manifestNonFileScheme = fs.file('manifestA')
|
||||
..writeAsStringSync(json.encode(<String, Object>{'/foo.js': <int>[0, 10]}));
|
||||
|
||||
..writeAsStringSync(json.encode(<String, Object>{'/foo.js': <String, Object>{
|
||||
'code': <int>[0],
|
||||
'sourcemap': <int>[0],
|
||||
}}));
|
||||
final File manifestOutOfBounds = fs.file('manifest')
|
||||
..writeAsStringSync(json.encode(<String, Object>{'/foo.js': <int>[0, 100]}));
|
||||
..writeAsStringSync(json.encode(<String, Object>{'/foo.js': <String, Object>{
|
||||
'code': <int>[0, 100],
|
||||
'sourcemap': <int>[0],
|
||||
}}));
|
||||
|
||||
expect(webAssetServer.write(source, manifestMissingOffset), isEmpty);
|
||||
expect(webAssetServer.write(source, manifestNonFileScheme), isEmpty);
|
||||
expect(webAssetServer.write(source, manifestOutOfBounds), isEmpty);
|
||||
expect(webAssetServer.write(source, manifestMissingOffset, sourcemap), isEmpty);
|
||||
expect(webAssetServer.write(source, manifestOutOfBounds, sourcemap), isEmpty);
|
||||
}));
|
||||
|
||||
test('serves JavaScript files from in memory cache', () => testbed.run(() async {
|
||||
final File source = fs.file('source')
|
||||
..writeAsStringSync('main() {}');
|
||||
final File sourcemap = fs.file('sourcemap')
|
||||
..writeAsStringSync('{}');
|
||||
final File manifest = fs.file('manifest')
|
||||
..writeAsStringSync(json.encode(<String, Object>{'/foo.js': <int>[0, source.lengthSync()]}));
|
||||
webAssetServer.write(source, manifest);
|
||||
..writeAsStringSync(json.encode(<String, Object>{'/foo.js': <String, Object>{
|
||||
'code': <int>[0, source.lengthSync()],
|
||||
'sourcemap': <int>[0, 2],
|
||||
}}));
|
||||
webAssetServer.write(source, manifest, sourcemap);
|
||||
|
||||
when(request.uri).thenReturn(Uri.parse('http://foobar/foo.js'));
|
||||
requestController.add(request);
|
||||
|
@ -136,9 +144,14 @@ void main() {
|
|||
test('handles missing JavaScript files from in memory cache', () => testbed.run(() async {
|
||||
final File source = fs.file('source')
|
||||
..writeAsStringSync('main() {}');
|
||||
final File sourcemap = fs.file('sourcemap')
|
||||
..writeAsStringSync('{}');
|
||||
final File manifest = fs.file('manifest')
|
||||
..writeAsStringSync(json.encode(<String, Object>{'/foo.js': <int>[0, source.lengthSync()]}));
|
||||
webAssetServer.write(source, manifest);
|
||||
..writeAsStringSync(json.encode(<String, Object>{'/foo.js': <String, Object>{
|
||||
'code': <int>[0, source.lengthSync()],
|
||||
'sourcemap': <int>[0, 2],
|
||||
}}));
|
||||
webAssetServer.write(source, manifest, sourcemap);
|
||||
|
||||
when(request.uri).thenReturn(Uri.parse('http://foobar/bar.js'));
|
||||
requestController.add(request);
|
||||
|
|
Loading…
Reference in a new issue