Support flutter run --wasm and flutter drive --wasm. (#146231)

This adds support for adding the `--wasm` flag to `flutter run` and `flutter drive`
* Emits errors if you attempt to use the skwasm renderer without the `--wasm` flag
* Emits errors if you try to use `--wasm` when not using a web device
* Uses the skwasm renderer by default if you pass `--wasm` and no `--web-renderer`
This commit is contained in:
Jackson Gardner 2024-04-12 12:27:26 -07:00 committed by GitHub
parent e2c812155c
commit 9973673752
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 337 additions and 64 deletions

View file

@ -32,6 +32,7 @@ Future<void> webLongRunningTestsRunner(String flutterRoot) async {
target: path.join('test_driver', 'failure.dart'),
buildMode: buildMode,
renderer: 'canvaskit',
wasm: false,
// This test intentionally fails and prints stack traces in the browser
// logs. To avoid confusion, silence browser output.
silenceBrowserOutput: true,
@ -42,6 +43,17 @@ Future<void> webLongRunningTestsRunner(String flutterRoot) async {
driver: path.join('test_driver', 'integration_test.dart'),
buildMode: buildMode,
renderer: 'canvaskit',
wasm: false,
expectWriteResponseFile: true,
expectResponseFileContent: 'null',
),
() => _runFlutterDriverWebTest(
testAppDirectory: path.join('packages', 'integration_test', 'example'),
target: path.join('integration_test', 'example_test.dart'),
driver: path.join('test_driver', 'integration_test.dart'),
buildMode: buildMode,
renderer: 'skwasm',
wasm: true,
expectWriteResponseFile: true,
expectResponseFileContent: 'null',
),
@ -51,6 +63,7 @@ Future<void> webLongRunningTestsRunner(String flutterRoot) async {
driver: path.join('test_driver', 'extended_integration_test.dart'),
buildMode: buildMode,
renderer: 'canvaskit',
wasm: false,
expectWriteResponseFile: true,
expectResponseFileContent: '''
{
@ -97,6 +110,7 @@ Future<void> webLongRunningTestsRunner(String flutterRoot) async {
() => _runWebE2eTest('capabilities_integration_canvaskit', buildMode: 'debug', renderer: 'auto'),
() => _runWebE2eTest('capabilities_integration_canvaskit', buildMode: 'profile', renderer: 'canvaskit'),
() => _runWebE2eTest('capabilities_integration_html', buildMode: 'release', renderer: 'html'),
() => _runWebE2eTest('capabilities_integration_skwasm', buildMode: 'release', renderer: 'skwasm', wasm: true),
// This test doesn't do anything interesting w.r.t. rendering, so we don't run the full build mode x renderer matrix.
// CacheWidth and CacheHeight are only currently supported in CanvasKit mode, so we don't run the test in HTML mode.
@ -110,6 +124,7 @@ Future<void> webLongRunningTestsRunner(String flutterRoot) async {
target: 'test_driver/smoke_web_engine.dart',
buildMode: 'profile',
renderer: 'auto',
wasm: false,
),
() => _runGalleryE2eWebTest('debug'),
() => _runGalleryE2eWebTest('debug', canvasKit: true),
@ -192,12 +207,14 @@ Future<void> _runWebE2eTest(
String name, {
required String buildMode,
required String renderer,
bool wasm = false,
}) async {
await _runFlutterDriverWebTest(
target: path.join('test_driver', '$name.dart'),
buildMode: buildMode,
renderer: renderer,
testAppDirectory: path.join(flutterRoot, 'dev', 'integration_tests', 'web_e2e_tests'),
wasm: wasm,
);
}
@ -206,6 +223,7 @@ Future<void> _runFlutterDriverWebTest({
required String buildMode,
required String renderer,
required String testAppDirectory,
required bool wasm,
String? driver,
bool expectFailure = false,
bool silenceBrowserOutput = false,
@ -235,6 +253,7 @@ Future<void> _runFlutterDriverWebTest({
'web-server',
'--$buildMode',
'--web-renderer=$renderer',
if (wasm) '--wasm',
],
expectNonZeroExit: expectFailure,
workingDirectory: testAppDirectory,

View file

@ -0,0 +1,12 @@
// 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.
{{flutter_js}}
{{flutter_build_config}}
_flutter.loader.load({
config: {
// Use the local CanvasKit bundle instead of the CDN to reduce test flakiness.
canvasKitBaseUrl: "/canvaskit/",
},
});

View file

@ -22,16 +22,6 @@ found in the LICENSE file. -->
<link rel="manifest" href="manifest.json">
</head>
<body>
<!-- This script installs service_worker.js to provide PWA functionality to
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('flutter_service_worker.js');
});
}
</script>
<script src="main.dart.js" type="application/javascript"></script>
<script src="flutter_bootstrap.js" async></script>
</body>
</html>

View file

@ -0,0 +1,12 @@
// 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.
{{flutter_js}}
{{flutter_build_config}}
_flutter.loader.load({
config: {
// Use the local CanvasKit bundle instead of the CDN to reduce test flakiness.
canvasKitBaseUrl: "/canvaskit/",
},
});

View file

@ -7,6 +7,6 @@ found in the LICENSE file. -->
<title>Hello, World</title>
</head>
<body>
<script src="main.dart.js"></script>
<script src="flutter_bootstrap.js" async></script>
</body>
</html>

View file

@ -12,5 +12,7 @@ void main() {
testWidgets('isCanvasKit returns true in CanvasKit mode', (WidgetTester tester) async {
await tester.pumpAndSettle();
expect(isCanvasKit, true);
expect(isSkwasm, false);
expect(isSkiaWeb, true);
});
}

View file

@ -8,8 +8,10 @@ import 'package:integration_test/integration_test.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('isCanvasKit returns false in HTML mode', (WidgetTester tester) async {
testWidgets('capabilities are set properly in HTML mode', (WidgetTester tester) async {
await tester.pumpAndSettle();
expect(isCanvasKit, false);
expect(isSkwasm, false);
expect(isSkiaWeb, false);
});
}

View file

@ -0,0 +1,18 @@
// 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:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('capabilities are set properly in Skwasm mode', (WidgetTester tester) async {
await tester.pumpAndSettle();
expect(isCanvasKit, false);
expect(isSkwasm, true);
expect(isSkiaWeb, true);
});
}

View file

@ -0,0 +1,7 @@
// 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:integration_test/integration_test_driver.dart' as test;
Future<void> main() async => test.integrationDriver();

View file

@ -0,0 +1,12 @@
// 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.
{{flutter_js}}
{{flutter_build_config}}
_flutter.loader.load({
config: {
// Use the local CanvasKit bundle instead of the CDN to reduce test flakiness.
canvasKitBaseUrl: "/canvaskit/",
},
});

View file

@ -7,6 +7,6 @@ found in the LICENSE file. -->
<title>Hello, World</title>
</head>
<body>
<script src="main.dart.js"></script>
<script src="flutter_bootstrap.js" async></script>
</body>
</html>

View file

@ -64,7 +64,7 @@ class BuildWebCommand extends BuildSubCommand {
help:
'Sets the optimization level used for Dart compilation to JavaScript/Wasm.',
defaultsTo: '${WebCompilerConfig.kDefaultOptimizationLevel}',
allowed: const <String>['1', '2', '3', '4'],
allowed: const <String>['0', '1', '2', '3', '4'],
);
//

View file

@ -30,6 +30,7 @@ import '../runner/flutter_command_runner.dart';
import '../tracing.dart';
import '../vmservice.dart';
import '../web/compile.dart';
import '../web/web_constants.dart';
import '../web/web_runner.dart';
import 'daemon.dart';
@ -179,7 +180,12 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
hide: !verboseHelp,
help: 'Uninstall previous versions of the app on the device '
'before reinstalling. Currently only supported on iOS.',
);
)
..addFlag(
FlutterOptions.kWebWasmFlag,
help: 'Compile to WebAssembly rather than JavaScript.\n$kWasmMoreInfo',
negatable: false,
);
usesWebOptions(verboseHelp: verboseHelp);
usesTargetOption();
usesPortOptions(verboseHelp: verboseHelp);
@ -227,6 +233,13 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
String? get traceAllowlist => stringArg('trace-allowlist');
bool get useWasm => boolArg(FlutterOptions.kWebWasmFlag);
WebRendererMode get webRenderer => WebRendererMode.fromCliOption(
stringArg(FlutterOptions.kWebRendererFlag),
useWasm: useWasm
);
/// Create a debugging options instance for the current `run` or `drive` invocation.
@visibleForTesting
@protected
@ -242,10 +255,6 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
final Map<String, String> webHeaders = featureFlags.isWebEnabled
? extractWebHeaders()
: const <String, String>{};
final String? webRendererString = stringArg('web-renderer');
final WebRendererMode webRenderer = (webRendererString != null)
? WebRendererMode.values.byName(webRendererString)
: WebRendererMode.auto;
if (buildInfo.mode.isRelease) {
return DebuggingOptions.disabled(
@ -264,6 +273,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
webBrowserFlags: webBrowserFlags,
webHeaders: webHeaders,
webRenderer: webRenderer,
webUseWasm: useWasm,
enableImpeller: enableImpeller,
enableVulkanValidation: enableVulkanValidation,
uninstallFirst: uninstallFirst,
@ -314,6 +324,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
webLaunchUrl: featureFlags.isWebEnabled ? stringArg('web-launch-url') : null,
webHeaders: webHeaders,
webRenderer: webRenderer,
webUseWasm: useWasm,
vmserviceOutFile: stringArg('vmservice-out-file'),
fastStart: argParser.options.containsKey('fast-start')
&& boolArg('fast-start')
@ -630,12 +641,21 @@ class RunCommand extends RunCommandBase {
if (devices!.any((Device device) => device is AndroidDevice)) {
_deviceDeprecationBehavior = DeprecationBehavior.exit;
}
// Only support "web mode" with a single web device due to resident runner
// refactoring required otherwise.
webMode = featureFlags.isWebEnabled &&
devices!.length == 1 &&
await devices!.single.targetPlatform == TargetPlatform.web_javascript;
if (useWasm && !webMode) {
throwToolExit('--wasm is only supported on the web platform');
}
if (webRenderer == WebRendererMode.skwasm && !useWasm) {
throwToolExit('Skwasm renderer requires --wasm');
}
final String? flavor = stringArg('flavor');
final bool flavorsSupportedOnEveryDevice = devices!
.every((Device device) => device.supportsFlavors);

View file

@ -330,6 +330,11 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
return super.verifyThenRunCommand(commandPath);
}
WebRendererMode get webRenderer => WebRendererMode.fromCliOption(
stringArg(FlutterOptions.kWebRendererFlag),
useWasm: useWasm
);
@override
Future<FlutterCommandResult> runCommand() async {
if (!globals.fs.isFileSync('pubspec.yaml')) {
@ -388,10 +393,6 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
);
}
final String? webRendererString = stringArg('web-renderer');
final WebRendererMode webRenderer = (webRendererString != null)
? WebRendererMode.values.byName(webRendererString)
: WebRendererMode.auto;
final DebuggingOptions debuggingOptions = DebuggingOptions.enabled(
buildInfo,
startPaused: startPaused,
@ -405,6 +406,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
enableImpeller: ImpellerStatus.fromBool(argResults!['enable-impeller'] as bool?),
debugLogsDirectoryPath: debugLogsDirectoryPath,
webRenderer: webRenderer,
webUseWasm: useWasm,
);
String? testAssetDirectory;
@ -511,6 +513,10 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
throwToolExit('--wasm is only supported on the web platform');
}
if (webRenderer == WebRendererMode.skwasm && !useWasm) {
throwToolExit('Skwasm renderer requires --wasm');
}
Device? integrationTestDevice;
if (_isIntegrationTest) {
integrationTestDevice = await findTargetDevice();
@ -589,7 +595,6 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
testAssetDirectory: testAssetDirectory,
flutterProject: flutterProject,
web: isWeb,
useWasm: useWasm,
randomSeed: stringArg('test-randomize-ordering-seed'),
reporter: stringArg('reporter'),
fileReporter: stringArg('file-reporter'),

View file

@ -994,6 +994,7 @@ class DebuggingOptions {
this.webHeaders = const <String, String>{},
this.webLaunchUrl,
this.webRenderer = WebRendererMode.auto,
this.webUseWasm = false,
this.vmserviceOutFile,
this.fastStart = false,
this.nullAssertions = false,
@ -1024,6 +1025,7 @@ class DebuggingOptions {
this.webLaunchUrl,
this.webHeaders = const <String, String>{},
this.webRenderer = WebRendererMode.auto,
this.webUseWasm = false,
this.cacheSkSL = false,
this.traceAllowlist,
this.enableImpeller = ImpellerStatus.platformDefault,
@ -1104,6 +1106,7 @@ class DebuggingOptions {
required this.webHeaders,
required this.webLaunchUrl,
required this.webRenderer,
required this.webUseWasm,
required this.vmserviceOutFile,
required this.fastStart,
required this.nullAssertions,
@ -1191,6 +1194,9 @@ class DebuggingOptions {
/// Which web renderer to use for the debugging session
final WebRendererMode webRenderer;
/// Whether to compile to webassembly
final bool webUseWasm;
/// A file where the VM Service URL should be written after the application is started.
final String? vmserviceOutFile;
final bool fastStart;
@ -1300,6 +1306,7 @@ class DebuggingOptions {
'webLaunchUrl': webLaunchUrl,
'webHeaders': webHeaders,
'webRenderer': webRenderer.name,
'webUseWasm': webUseWasm,
'vmserviceOutFile': vmserviceOutFile,
'fastStart': fastStart,
'nullAssertions': nullAssertions,
@ -1356,6 +1363,7 @@ class DebuggingOptions {
webHeaders: (json['webHeaders']! as Map<dynamic, dynamic>).cast<String, String>(),
webLaunchUrl: json['webLaunchUrl'] as String?,
webRenderer: WebRendererMode.values.byName(json['webRenderer']! as String),
webUseWasm: json['webUseWasm']! as bool,
vmserviceOutFile: json['vmserviceOutFile'] as String?,
fastStart: json['fastStart']! as bool,
nullAssertions: json['nullAssertions']! as bool,

View file

@ -79,6 +79,7 @@ class WebDriverService extends DriverService {
port: debuggingOptions.port,
hostname: debuggingOptions.hostname,
webRenderer: debuggingOptions.webRenderer,
webUseWasm: debuggingOptions.webUseWasm
)
: DebuggingOptions.enabled(
buildInfo,
@ -86,6 +87,7 @@ class WebDriverService extends DriverService {
hostname: debuggingOptions.hostname,
disablePortPublication: debuggingOptions.disablePortPublication,
webRenderer: debuggingOptions.webRenderer,
webUseWasm: debuggingOptions.webUseWasm,
),
stayResident: true,
flutterProject: FlutterProject.current(),

View file

@ -180,6 +180,7 @@ class WebAssetServer implements AssetReader {
Map<String, String> extraHeaders,
NullSafetyMode nullSafetyMode, {
required WebRendererMode webRenderer,
required bool isWasm,
bool testMode = false,
DwdsLauncher dwdsLauncher = Dwds.start,
// TODO(markzipan): Make sure this default value aligns with that in the debugger options.
@ -237,8 +238,8 @@ class WebAssetServer implements AssetReader {
return server;
}
// In release builds deploy a simpler proxy server.
if (buildInfo.mode != BuildMode.debug) {
// In release builds (or wasm builds) deploy a simpler proxy server.
if (buildInfo.mode != BuildMode.debug || isWasm) {
final ReleaseAssetServer releaseAssetServer = ReleaseAssetServer(
entrypoint,
fileSystem: globals.fs,
@ -246,6 +247,7 @@ class WebAssetServer implements AssetReader {
flutterRoot: Cache.flutterRoot,
webBuildDirectory: getWebBuildDirectory(),
basePath: server.basePath,
needsCoopCoep: webRenderer == WebRendererMode.skwasm,
);
runZonedGuarded(() {
shelf.serveRequests(httpServer!, releaseAssetServer.handle);
@ -737,6 +739,7 @@ class WebDevFS implements DevFS {
required this.nullSafetyMode,
required this.ddcModuleSystem,
required this.webRenderer,
required this.isWasm,
required this.rootDirectory,
this.testMode = false,
}) : _port = port;
@ -763,6 +766,7 @@ class WebDevFS implements DevFS {
final String? tlsCertPath;
final String? tlsCertKeyPath;
final WebRendererMode webRenderer;
final bool isWasm;
late WebAssetServer webAssetServer;
@ -863,6 +867,7 @@ class WebDevFS implements DevFS {
extraHeaders,
nullSafetyMode,
webRenderer: webRenderer,
isWasm: isWasm,
testMode: testMode,
ddcModuleSystem: ddcModuleSystem,
);
@ -1108,11 +1113,13 @@ class ReleaseAssetServer {
required String? webBuildDirectory,
required String? flutterRoot,
required Platform platform,
required bool needsCoopCoep,
this.basePath = '',
}) : _fileSystem = fileSystem,
_platform = platform,
_flutterRoot = flutterRoot,
_webBuildDirectory = webBuildDirectory,
_needsCoopCoep = needsCoopCoep,
_fileSystemUtils =
FileSystemUtils(fileSystem: fileSystem, platform: platform);
@ -1122,6 +1129,7 @@ class ReleaseAssetServer {
final FileSystem _fileSystem;
final FileSystemUtils _fileSystemUtils;
final Platform _platform;
final bool _needsCoopCoep;
/// The base path to serve from.
///
@ -1174,14 +1182,23 @@ class ReleaseAssetServer {
'application/octet-stream';
return shelf.Response.ok(bytes, headers: <String, String>{
'Content-Type': mimeType,
if (_needsCoopCoep && file.basename == 'index.html') ...<String, String>{
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp',
}
});
}
final File file = _fileSystem
.file(_fileSystem.path.join(_webBuildDirectory!, 'index.html'));
return shelf.Response.ok(file.readAsBytesSync(), headers: <String, String>{
'Content-Type': 'text/html',
});
return shelf.Response.ok(file.readAsBytesSync(),
headers: <String, String>{
'Content-Type': 'text/html',
if (_needsCoopCoep) ...<String, String>{
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp',
},
});
}
}

View file

@ -134,9 +134,9 @@ class ResidentWebRunner extends ResidentRunner {
// and platform initialization.
Directory? _generatedEntrypointDirectory;
// Only the debug builds of the web support the service protocol.
// Only non-wasm debug builds of the web support the service protocol.
@override
bool get supportsServiceProtocol => isRunningDebug && deviceIsDebuggable;
bool get supportsServiceProtocol => !debuggingOptions.webUseWasm && isRunningDebug && deviceIsDebuggable;
@override
bool get debuggingEnabled => isRunningDebug && deviceIsDebuggable;
@ -311,13 +311,14 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
nativeNullAssertions: debuggingOptions.nativeNullAssertions,
ddcModuleSystem: debuggingOptions.buildInfo.ddcModuleFormat == DdcModuleFormat.ddc,
webRenderer: debuggingOptions.webRenderer,
isWasm: debuggingOptions.webUseWasm,
rootDirectory: fileSystem.directory(projectRootPath),
);
Uri url = await device!.devFS!.create();
if (debuggingOptions.tlsCertKeyPath != null && debuggingOptions.tlsCertPath != null) {
url = url.replace(scheme: 'https');
}
if (debuggingOptions.buildInfo.isDebug) {
if (debuggingOptions.buildInfo.isDebug && !debuggingOptions.webUseWasm) {
await runSourceGenerators();
final UpdateFSReport report = await _updateDevFS(fullRestart: true);
if (!report.success) {
@ -342,12 +343,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
target,
debuggingOptions.buildInfo,
ServiceWorkerStrategy.none,
compilerConfigs: <WebCompilerConfig>[
JsCompilerConfig.run(
nativeNullAssertions: debuggingOptions.nativeNullAssertions,
renderer: debuggingOptions.webRenderer,
)
]
compilerConfigs: <WebCompilerConfig>[_compilerConfig],
);
}
await device!.device!.startApp(
@ -386,6 +382,17 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
}
}
WebCompilerConfig get _compilerConfig => (debuggingOptions.webUseWasm)
? WasmCompilerConfig(
optimizationLevel: 0,
stripWasm: false,
renderer: debuggingOptions.webRenderer
)
: JsCompilerConfig.run(
nativeNullAssertions: debuggingOptions.nativeNullAssertions,
renderer: debuggingOptions.webRenderer,
);
@override
Future<OperationResult> restart({
bool fullRestart = false,
@ -399,7 +406,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
progressId: 'hot.restart',
);
if (debuggingOptions.buildInfo.isDebug) {
if (debuggingOptions.buildInfo.isDebug && !debuggingOptions.webUseWasm) {
await runSourceGenerators();
// Full restart is always false for web, since the extra recompile is wasteful.
final UpdateFSReport report = await _updateDevFS();
@ -426,12 +433,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
target,
debuggingOptions.buildInfo,
ServiceWorkerStrategy.none,
compilerConfigs: <WebCompilerConfig>[
JsCompilerConfig.run(
nativeNullAssertions: debuggingOptions.nativeNullAssertions,
renderer: debuggingOptions.webRenderer,
)
],
compilerConfigs: <WebCompilerConfig>[_compilerConfig],
);
} on ToolExit {
return OperationResult(1, 'Failed to recompile application.');

View file

@ -1181,10 +1181,10 @@ abstract class ResidentRunner extends ResidentHandlers {
bool get debuggingEnabled => debuggingOptions.debuggingEnabled;
@override
bool get isRunningDebug => debuggingOptions.buildInfo.isDebug;
bool get isRunningDebug => !debuggingOptions.webUseWasm && debuggingOptions.buildInfo.isDebug;
@override
bool get isRunningProfile => debuggingOptions.buildInfo.isProfile;
bool get isRunningProfile => !debuggingOptions.webUseWasm && debuggingOptions.buildInfo.isProfile;
@override
bool get isRunningRelease => debuggingOptions.buildInfo.isRelease;

View file

@ -52,7 +52,6 @@ abstract class FlutterTestRunner {
String? icudtlPath,
Directory? coverageDirectory,
bool web = false,
bool useWasm = false,
String? randomSeed,
String? reporter,
String? fileReporter,
@ -118,7 +117,6 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner {
String? icudtlPath,
Directory? coverageDirectory,
bool web = false,
bool useWasm = false,
String? randomSeed,
String? reporter,
String? fileReporter,
@ -188,7 +186,7 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner {
testFiles: testFiles.map((Uri uri) => uri.toFilePath()).toList(),
buildInfo: debuggingOptions.buildInfo,
webRenderer: debuggingOptions.webRenderer,
useWasm: useWasm,
useWasm: debuggingOptions.webUseWasm,
);
testArgs
..add('--platform=chrome')
@ -221,7 +219,7 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner {
),
testTimeRecorder: testTimeRecorder,
webRenderer: debuggingOptions.webRenderer,
useWasm: useWasm,
useWasm: debuggingOptions.webUseWasm,
);
},
);

View file

@ -184,6 +184,17 @@ enum WebRendererMode implements CliEnum {
/// Always use skwasm.
skwasm;
factory WebRendererMode.fromCliOption(String? webRendererString, {required bool useWasm}) {
final WebRendererMode mode = webRendererString != null
? WebRendererMode.values.byName(webRendererString)
: WebRendererMode.auto;
if (mode == WebRendererMode.auto && useWasm) {
// Wasm defaults to skwasm
return WebRendererMode.skwasm;
}
return mode;
}
@override
String get cliName => snakeCase(name, '-');

View file

@ -22,7 +22,7 @@ sealed class WebCompilerConfig {
/// The compiler optimization level.
///
/// Valid values are O1 (lowest, profile default) to O4 (highest, release default).
/// Valid values are O0 (lowest, debug default) to O4 (highest, release default).
final int optimizationLevel;
/// Returns which target this compiler outputs (js or wasm)

View file

@ -29,6 +29,7 @@ import 'package:flutter_tools/src/resident_runner.dart';
import 'package:flutter_tools/src/run_hot.dart';
import 'package:flutter_tools/src/runner/flutter_command.dart';
import 'package:flutter_tools/src/vmservice.dart';
import 'package:flutter_tools/src/web/compile.dart';
import 'package:test/fake.dart';
import 'package:unified_analytics/unified_analytics.dart' as analytics;
import 'package:vm_service/vm_service.dart';
@ -986,6 +987,36 @@ void main() {
DeviceManager: () => testDeviceManager,
});
testUsingContext('throws a ToolExit when using --wasm on a non-web platform', () async {
final RunCommand command = RunCommand();
await expectLater(
() => createTestCommandRunner(command).run(<String>[
'run',
'--no-pub',
'--wasm',
]), throwsToolExit(message: '--wasm is only supported on the web platform'));
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
Logger: () => logger,
DeviceManager: () => testDeviceManager,
});
testUsingContext('throws a ToolExit when using the skwasm renderer without --wasm', () async {
final RunCommand command = RunCommand();
await expectLater(
() => createTestCommandRunner(command).run(<String>[
'run',
'--no-pub',
'--web-renderer=skwasm',
]), throwsToolExit(message: 'Skwasm renderer requires --wasm'));
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
Logger: () => logger,
DeviceManager: () => testDeviceManager,
});
testUsingContext('accepts headers with commas in them', () async {
final RunCommand command = RunCommand();
await expectLater(
@ -1170,6 +1201,24 @@ void main() {
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('wasm mode selects skwasm renderer by default', () async {
final RunCommand command = RunCommand();
await expectLater(() => createTestCommandRunner(command).run(<String>[
'run',
'-d chrome',
'--wasm',
]), throwsToolExit());
final DebuggingOptions options = await command.createDebuggingOptions(false);
expect(options.webUseWasm, true);
expect(options.webRenderer, WebRendererMode.skwasm);
}, overrides: <Type, Generator>{
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('fails when "--web-launch-url" is not supported', () async {
final RunCommand command = RunCommand();
await expectLater(

View file

@ -1348,6 +1348,24 @@ dev_dependencies:
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('Web renderer defaults to Skwasm when using wasm', () async {
final FakeFlutterTestRunner testRunner = FakeFlutterTestRunner(0);
final TestCommand testCommand = TestCommand(testRunner: testRunner);
final CommandRunner<void> commandRunner = createTestCommandRunner(testCommand);
await commandRunner.run(const <String>[
'test',
'--no-pub',
'--platform=chrome',
'--wasm',
]);
expect(testRunner.lastDebuggingOptionsValue.webRenderer, WebRendererMode.skwasm);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
});
}

View file

@ -137,6 +137,7 @@ void main() {
flutterRoot: null, // ignore: avoid_redundant_argument_values
platform: FakePlatform(),
webBuildDirectory: null, // ignore: avoid_redundant_argument_values
needsCoopCoep: false,
);
},
overrides: <Type, Generator>{
@ -927,6 +928,7 @@ void main() {
nullSafetyMode: NullSafetyMode.unsound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.html,
isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.ddcModuleLoaderJS.createSync(recursive: true);
@ -1063,6 +1065,7 @@ void main() {
nullSafetyMode: NullSafetyMode.sound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.html,
isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.ddcModuleLoaderJS.createSync(recursive: true);
@ -1199,6 +1202,7 @@ void main() {
nullSafetyMode: NullSafetyMode.sound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit,
isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.ddcModuleLoaderJS.createSync(recursive: true);
@ -1272,6 +1276,7 @@ void main() {
nullSafetyMode: NullSafetyMode.sound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit,
isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.ddcModuleLoaderJS.createSync(recursive: true);
@ -1321,6 +1326,7 @@ void main() {
nullSafetyMode: NullSafetyMode.sound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit,
isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.ddcModuleLoaderJS.createSync(recursive: true);
@ -1372,6 +1378,7 @@ void main() {
nullSafetyMode: NullSafetyMode.sound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.auto,
isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.ddcModuleLoaderJS.createSync(recursive: true);
@ -1425,6 +1432,7 @@ void main() {
nullSafetyMode: NullSafetyMode.unsound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit,
isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.ddcModuleLoaderJS.createSync(recursive: true);
@ -1463,6 +1471,7 @@ void main() {
const <String, String>{},
NullSafetyMode.unsound,
webRenderer: WebRendererMode.canvaskit,
isWasm: false,
testMode: true);
expect(webAssetServer.defaultResponseHeaders['x-frame-options'], null);
@ -1496,6 +1505,7 @@ void main() {
},
NullSafetyMode.unsound,
webRenderer: WebRendererMode.canvaskit,
isWasm: false,
testMode: true);
expect(webAssetServer.defaultResponseHeaders[extraHeaderKey],
@ -1596,6 +1606,7 @@ void main() {
nullSafetyMode: NullSafetyMode.unsound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit,
isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.ddcModuleLoaderJS.createSync(recursive: true);

View file

@ -76,6 +76,7 @@ void main() {
flutterRoot: null,
platform: FakePlatform(),
webBuildDirectory: null,
needsCoopCoep: false,
);
}, overrides: <Type, Generator>{
Logger: () => logger,
@ -715,6 +716,7 @@ void main() {
nullSafetyMode: NullSafetyMode.unsound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.html,
isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.requireJS.createSync(recursive: true);
@ -827,6 +829,7 @@ void main() {
nullSafetyMode: NullSafetyMode.sound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.html,
isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.requireJS.createSync(recursive: true);
@ -945,6 +948,7 @@ void main() {
nullSafetyMode: NullSafetyMode.sound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit,
isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.requireJS.createSync(recursive: true);
@ -1009,6 +1013,7 @@ void main() {
nullSafetyMode: NullSafetyMode.sound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit,
isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.requireJS.createSync(recursive: true);
@ -1057,6 +1062,7 @@ void main() {
nullSafetyMode: NullSafetyMode.sound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit,
isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.requireJS.createSync(recursive: true);
@ -1106,6 +1112,7 @@ void main() {
nullSafetyMode: NullSafetyMode.sound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.auto,
isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.requireJS.createSync(recursive: true);
@ -1156,6 +1163,7 @@ void main() {
nullSafetyMode: NullSafetyMode.unsound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit,
isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.requireJS.createSync(recursive: true);
@ -1194,6 +1202,7 @@ void main() {
const <String, String>{},
NullSafetyMode.unsound,
webRenderer: WebRendererMode.canvaskit,
isWasm: false,
testMode: true
);
@ -1228,6 +1237,7 @@ void main() {
},
NullSafetyMode.unsound,
webRenderer: WebRendererMode.canvaskit,
isWasm: false,
testMode: true
);
@ -1310,6 +1320,7 @@ void main() {
nullSafetyMode: NullSafetyMode.unsound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit,
isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.requireJS.createSync(recursive: true);

View file

@ -49,6 +49,7 @@ void main() {
platform: platform,
flutterRoot: '/flutter',
webBuildDirectory: 'build/web',
needsCoopCoep: false,
);
fileSystem.file('build/web/assets/foo.png')
..createSync(recursive: true)
@ -68,6 +69,7 @@ void main() {
platform: platform,
flutterRoot: '/flutter',
webBuildDirectory: 'build/web',
needsCoopCoep: false,
);
fileSystem.file('build/web/assets/foo.js')
..createSync(recursive: true)
@ -87,6 +89,7 @@ void main() {
platform: platform,
flutterRoot: '/flutter',
webBuildDirectory: 'build/web',
needsCoopCoep: false,
);
fileSystem.file('build/web/assets/foo.html')
..createSync(recursive: true)
@ -106,6 +109,7 @@ void main() {
platform: platform,
flutterRoot: '/flutter',
webBuildDirectory: 'build/web',
needsCoopCoep: false,
);
fileSystem.file('flutter/bar.dart')
..createSync(recursive: true)
@ -122,6 +126,7 @@ void main() {
platform: platform,
flutterRoot: '/flutter',
webBuildDirectory: 'build/web',
needsCoopCoep: false,
);
fileSystem.file('bar.dart')
..createSync(recursive: true)
@ -131,4 +136,44 @@ void main() {
expect(response.statusCode, HttpStatus.ok);
});
testWithoutContext('release asset server serves html content with COOP/COEP headers when specified', () async {
final ReleaseAssetServer assetServer = ReleaseAssetServer(Uri.base,
fileSystem: fileSystem,
platform: platform,
flutterRoot: '/flutter',
webBuildDirectory: 'build/web',
needsCoopCoep: true,
);
fileSystem.file('build/web/index.html')
..createSync(recursive: true)
..writeAsStringSync('<html></html>');
final Response response = await assetServer
.handle(Request('GET', Uri.parse('http://localhost:8080/index.html')));
expect(response.statusCode, HttpStatus.ok);
final Map<String, String> headers = response.headers;
expect(headers['Cross-Origin-Opener-Policy'], 'same-origin');
expect(headers['Cross-Origin-Embedder-Policy'], 'require-corp');
});
testWithoutContext('release asset server serves html content without COOP/COEP headers when specified', () async {
final ReleaseAssetServer assetServer = ReleaseAssetServer(Uri.base,
fileSystem: fileSystem,
platform: platform,
flutterRoot: '/flutter',
webBuildDirectory: 'build/web',
needsCoopCoep: false,
);
fileSystem.file('build/web/index.html')
..createSync(recursive: true)
..writeAsStringSync('<html></html>');
final Response response = await assetServer
.handle(Request('GET', Uri.parse('http://localhost:8080/index.html')));
expect(response.statusCode, HttpStatus.ok);
final Map<String, String> headers = response.headers;
expect(headers.containsKey('Cross-Origin-Opener-Policy'), false);
expect(headers.containsKey('Cross-Origin-Embedder-Policy'), false);
});
}

View file

@ -0,0 +1,12 @@
// 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.
{{flutter_js}}
{{flutter_build_config}}
_flutter.loader.load({
config: {
// Use the local CanvasKit bundle instead of the CDN to reduce test flakiness.
canvasKitBaseUrl: "/canvaskit/",
},
});

View file

@ -21,16 +21,6 @@ found in the LICENSE file. -->
<link rel="manifest" href="/manifest.json">
</head>
<body>
<!-- This script installs service_worker.js to provide PWA functionality to
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('/flutter_service_worker.js');
});
}
</script>
<script src="main.dart.js" type="application/javascript"></script>
<script src="flutter_bootstrap.js" async></script>
</body>
</html>