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`
diff --git a/dev/bots/suite_runners/run_web_long_running_tests.dart b/dev/bots/suite_runners/run_web_long_running_tests.dart
index 4ce1f02..5ae5d0c 100644
--- a/dev/bots/suite_runners/run_web_long_running_tests.dart
+++ b/dev/bots/suite_runners/run_web_long_running_tests.dart
@@ -32,6 +32,7 @@
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 @@
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 @@
driver: path.join('test_driver', 'extended_integration_test.dart'),
buildMode: buildMode,
renderer: 'canvaskit',
+ wasm: false,
expectWriteResponseFile: true,
expectResponseFileContent: '''
{
@@ -97,6 +110,7 @@
() => _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 @@
target: 'test_driver/smoke_web_engine.dart',
buildMode: 'profile',
renderer: 'auto',
+ wasm: false,
),
() => _runGalleryE2eWebTest('debug'),
() => _runGalleryE2eWebTest('debug', canvasKit: true),
@@ -192,12 +207,14 @@
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 @@
required String buildMode,
required String renderer,
required String testAppDirectory,
+ required bool wasm,
String? driver,
bool expectFailure = false,
bool silenceBrowserOutput = false,
@@ -235,6 +253,7 @@
'web-server',
'--$buildMode',
'--web-renderer=$renderer',
+ if (wasm) '--wasm',
],
expectNonZeroExit: expectFailure,
workingDirectory: testAppDirectory,
diff --git a/dev/integration_tests/non_nullable/web/flutter_bootstrap.js b/dev/integration_tests/non_nullable/web/flutter_bootstrap.js
new file mode 100644
index 0000000..be78d93
--- /dev/null
+++ b/dev/integration_tests/non_nullable/web/flutter_bootstrap.js
@@ -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/",
+ },
+});
diff --git a/dev/integration_tests/non_nullable/web/index.html b/dev/integration_tests/non_nullable/web/index.html
index aae9e6e..dd2f083 100644
--- a/dev/integration_tests/non_nullable/web/index.html
+++ b/dev/integration_tests/non_nullable/web/index.html
@@ -22,16 +22,6 @@
<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>
diff --git a/dev/integration_tests/web_compile_tests/web/flutter_bootstrap.js b/dev/integration_tests/web_compile_tests/web/flutter_bootstrap.js
new file mode 100644
index 0000000..be78d93
--- /dev/null
+++ b/dev/integration_tests/web_compile_tests/web/flutter_bootstrap.js
@@ -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/",
+ },
+});
diff --git a/dev/integration_tests/web_compile_tests/web/index.html b/dev/integration_tests/web_compile_tests/web/index.html
index a856086..313adac 100644
--- a/dev/integration_tests/web_compile_tests/web/index.html
+++ b/dev/integration_tests/web_compile_tests/web/index.html
@@ -7,6 +7,6 @@
<title>Hello, World</title>
</head>
<body>
- <script src="main.dart.js"></script>
+ <script src="flutter_bootstrap.js" async></script>
</body>
</html>
diff --git a/dev/integration_tests/web_e2e_tests/test_driver/capabilities_integration_canvaskit.dart b/dev/integration_tests/web_e2e_tests/test_driver/capabilities_integration_canvaskit.dart
index 2dbbfb5..46d8a8a 100644
--- a/dev/integration_tests/web_e2e_tests/test_driver/capabilities_integration_canvaskit.dart
+++ b/dev/integration_tests/web_e2e_tests/test_driver/capabilities_integration_canvaskit.dart
@@ -12,5 +12,7 @@
testWidgets('isCanvasKit returns true in CanvasKit mode', (WidgetTester tester) async {
await tester.pumpAndSettle();
expect(isCanvasKit, true);
+ expect(isSkwasm, false);
+ expect(isSkiaWeb, true);
});
}
diff --git a/dev/integration_tests/web_e2e_tests/test_driver/capabilities_integration_html.dart b/dev/integration_tests/web_e2e_tests/test_driver/capabilities_integration_html.dart
index 1f0a11e..d0830e1 100644
--- a/dev/integration_tests/web_e2e_tests/test_driver/capabilities_integration_html.dart
+++ b/dev/integration_tests/web_e2e_tests/test_driver/capabilities_integration_html.dart
@@ -8,8 +8,10 @@
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);
});
}
diff --git a/dev/integration_tests/web_e2e_tests/test_driver/capabilities_integration_skwasm.dart b/dev/integration_tests/web_e2e_tests/test_driver/capabilities_integration_skwasm.dart
new file mode 100644
index 0000000..c16afb2
--- /dev/null
+++ b/dev/integration_tests/web_e2e_tests/test_driver/capabilities_integration_skwasm.dart
@@ -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);
+ });
+}
diff --git a/dev/integration_tests/web_e2e_tests/test_driver/capabilities_integration_skwasm_test.dart b/dev/integration_tests/web_e2e_tests/test_driver/capabilities_integration_skwasm_test.dart
new file mode 100644
index 0000000..b2d2a17
--- /dev/null
+++ b/dev/integration_tests/web_e2e_tests/test_driver/capabilities_integration_skwasm_test.dart
@@ -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();
diff --git a/examples/hello_world/web/flutter_bootstrap.js b/examples/hello_world/web/flutter_bootstrap.js
new file mode 100644
index 0000000..be78d93
--- /dev/null
+++ b/examples/hello_world/web/flutter_bootstrap.js
@@ -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/",
+ },
+});
diff --git a/examples/hello_world/web/index.html b/examples/hello_world/web/index.html
index a856086..313adac 100644
--- a/examples/hello_world/web/index.html
+++ b/examples/hello_world/web/index.html
@@ -7,6 +7,6 @@
<title>Hello, World</title>
</head>
<body>
- <script src="main.dart.js"></script>
+ <script src="flutter_bootstrap.js" async></script>
</body>
</html>
diff --git a/packages/flutter_tools/lib/src/commands/build_web.dart b/packages/flutter_tools/lib/src/commands/build_web.dart
index db67a41..7244c45 100644
--- a/packages/flutter_tools/lib/src/commands/build_web.dart
+++ b/packages/flutter_tools/lib/src/commands/build_web.dart
@@ -64,7 +64,7 @@
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'],
);
//
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart
index 8890928..548b9fc 100644
--- a/packages/flutter_tools/lib/src/commands/run.dart
+++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -30,6 +30,7 @@
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 @@
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 @@
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 @@
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 @@
webBrowserFlags: webBrowserFlags,
webHeaders: webHeaders,
webRenderer: webRenderer,
+ webUseWasm: useWasm,
enableImpeller: enableImpeller,
enableVulkanValidation: enableVulkanValidation,
uninstallFirst: uninstallFirst,
@@ -314,6 +324,7 @@
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 @@
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);
diff --git a/packages/flutter_tools/lib/src/commands/test.dart b/packages/flutter_tools/lib/src/commands/test.dart
index abaa42f..b3cf671 100644
--- a/packages/flutter_tools/lib/src/commands/test.dart
+++ b/packages/flutter_tools/lib/src/commands/test.dart
@@ -330,6 +330,11 @@
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 @@
);
}
- 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 @@
enableImpeller: ImpellerStatus.fromBool(argResults!['enable-impeller'] as bool?),
debugLogsDirectoryPath: debugLogsDirectoryPath,
webRenderer: webRenderer,
+ webUseWasm: useWasm,
);
String? testAssetDirectory;
@@ -511,6 +513,10 @@
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 @@
testAssetDirectory: testAssetDirectory,
flutterProject: flutterProject,
web: isWeb,
- useWasm: useWasm,
randomSeed: stringArg('test-randomize-ordering-seed'),
reporter: stringArg('reporter'),
fileReporter: stringArg('file-reporter'),
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index 96006cd..6afabbd 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -994,6 +994,7 @@
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 @@
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 @@
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 @@
/// 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 @@
'webLaunchUrl': webLaunchUrl,
'webHeaders': webHeaders,
'webRenderer': webRenderer.name,
+ 'webUseWasm': webUseWasm,
'vmserviceOutFile': vmserviceOutFile,
'fastStart': fastStart,
'nullAssertions': nullAssertions,
@@ -1356,6 +1363,7 @@
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,
diff --git a/packages/flutter_tools/lib/src/drive/web_driver_service.dart b/packages/flutter_tools/lib/src/drive/web_driver_service.dart
index fa10cfb..fd1e99c 100644
--- a/packages/flutter_tools/lib/src/drive/web_driver_service.dart
+++ b/packages/flutter_tools/lib/src/drive/web_driver_service.dart
@@ -79,6 +79,7 @@
port: debuggingOptions.port,
hostname: debuggingOptions.hostname,
webRenderer: debuggingOptions.webRenderer,
+ webUseWasm: debuggingOptions.webUseWasm
)
: DebuggingOptions.enabled(
buildInfo,
@@ -86,6 +87,7 @@
hostname: debuggingOptions.hostname,
disablePortPublication: debuggingOptions.disablePortPublication,
webRenderer: debuggingOptions.webRenderer,
+ webUseWasm: debuggingOptions.webUseWasm,
),
stayResident: true,
flutterProject: FlutterProject.current(),
diff --git a/packages/flutter_tools/lib/src/isolated/devfs_web.dart b/packages/flutter_tools/lib/src/isolated/devfs_web.dart
index a4a2c9f..656c5f1 100644
--- a/packages/flutter_tools/lib/src/isolated/devfs_web.dart
+++ b/packages/flutter_tools/lib/src/isolated/devfs_web.dart
@@ -180,6 +180,7 @@
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 @@
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 @@
flutterRoot: Cache.flutterRoot,
webBuildDirectory: getWebBuildDirectory(),
basePath: server.basePath,
+ needsCoopCoep: webRenderer == WebRendererMode.skwasm,
);
runZonedGuarded(() {
shelf.serveRequests(httpServer!, releaseAssetServer.handle);
@@ -737,6 +739,7 @@
required this.nullSafetyMode,
required this.ddcModuleSystem,
required this.webRenderer,
+ required this.isWasm,
required this.rootDirectory,
this.testMode = false,
}) : _port = port;
@@ -763,6 +766,7 @@
final String? tlsCertPath;
final String? tlsCertKeyPath;
final WebRendererMode webRenderer;
+ final bool isWasm;
late WebAssetServer webAssetServer;
@@ -863,6 +867,7 @@
extraHeaders,
nullSafetyMode,
webRenderer: webRenderer,
+ isWasm: isWasm,
testMode: testMode,
ddcModuleSystem: ddcModuleSystem,
);
@@ -1108,11 +1113,13 @@
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 @@
final FileSystem _fileSystem;
final FileSystemUtils _fileSystemUtils;
final Platform _platform;
+ final bool _needsCoopCoep;
/// The base path to serve from.
///
@@ -1174,14 +1182,23 @@
'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',
+ },
+ });
}
}
diff --git a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart
index 6228520..a0de64f 100644
--- a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart
+++ b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart
@@ -134,9 +134,9 @@
// 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 @@
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 @@
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 @@
}
}
+ 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 @@
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 @@
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.');
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart
index 87a2e51..addb06a 100644
--- a/packages/flutter_tools/lib/src/resident_runner.dart
+++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -1181,10 +1181,10 @@
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;
diff --git a/packages/flutter_tools/lib/src/test/runner.dart b/packages/flutter_tools/lib/src/test/runner.dart
index 9b85adb..bfa5c1d 100644
--- a/packages/flutter_tools/lib/src/test/runner.dart
+++ b/packages/flutter_tools/lib/src/test/runner.dart
@@ -52,7 +52,6 @@
String? icudtlPath,
Directory? coverageDirectory,
bool web = false,
- bool useWasm = false,
String? randomSeed,
String? reporter,
String? fileReporter,
@@ -118,7 +117,6 @@
String? icudtlPath,
Directory? coverageDirectory,
bool web = false,
- bool useWasm = false,
String? randomSeed,
String? reporter,
String? fileReporter,
@@ -188,7 +186,7 @@
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 @@
),
testTimeRecorder: testTimeRecorder,
webRenderer: debuggingOptions.webRenderer,
- useWasm: useWasm,
+ useWasm: debuggingOptions.webUseWasm,
);
},
);
diff --git a/packages/flutter_tools/lib/src/web/compile.dart b/packages/flutter_tools/lib/src/web/compile.dart
index 22302d0..66a99ad 100644
--- a/packages/flutter_tools/lib/src/web/compile.dart
+++ b/packages/flutter_tools/lib/src/web/compile.dart
@@ -184,6 +184,17 @@
/// 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, '-');
diff --git a/packages/flutter_tools/lib/src/web/compiler_config.dart b/packages/flutter_tools/lib/src/web/compiler_config.dart
index 79206ad..352baa5 100644
--- a/packages/flutter_tools/lib/src/web/compiler_config.dart
+++ b/packages/flutter_tools/lib/src/web/compiler_config.dart
@@ -22,7 +22,7 @@
/// 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)
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart
index d62cafd..4d74c30 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart
@@ -29,6 +29,7 @@
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 @@
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 @@
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(
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart
index fa27e70..7ddef36 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart
@@ -1348,6 +1348,24 @@
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(),
+ });
});
}
diff --git a/packages/flutter_tools/test/general.shard/web/devfs_web_ddc_modules_test.dart b/packages/flutter_tools/test/general.shard/web/devfs_web_ddc_modules_test.dart
index bcb9e24..2b18e13 100644
--- a/packages/flutter_tools/test/general.shard/web/devfs_web_ddc_modules_test.dart
+++ b/packages/flutter_tools/test/general.shard/web/devfs_web_ddc_modules_test.dart
@@ -137,6 +137,7 @@
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 @@
nullSafetyMode: NullSafetyMode.unsound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.html,
+ isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.ddcModuleLoaderJS.createSync(recursive: true);
@@ -1063,6 +1065,7 @@
nullSafetyMode: NullSafetyMode.sound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.html,
+ isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.ddcModuleLoaderJS.createSync(recursive: true);
@@ -1199,6 +1202,7 @@
nullSafetyMode: NullSafetyMode.sound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit,
+ isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.ddcModuleLoaderJS.createSync(recursive: true);
@@ -1272,6 +1276,7 @@
nullSafetyMode: NullSafetyMode.sound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit,
+ isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.ddcModuleLoaderJS.createSync(recursive: true);
@@ -1321,6 +1326,7 @@
nullSafetyMode: NullSafetyMode.sound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit,
+ isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.ddcModuleLoaderJS.createSync(recursive: true);
@@ -1372,6 +1378,7 @@
nullSafetyMode: NullSafetyMode.sound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.auto,
+ isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.ddcModuleLoaderJS.createSync(recursive: true);
@@ -1425,6 +1432,7 @@
nullSafetyMode: NullSafetyMode.unsound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit,
+ isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.ddcModuleLoaderJS.createSync(recursive: true);
@@ -1463,6 +1471,7 @@
const <String, String>{},
NullSafetyMode.unsound,
webRenderer: WebRendererMode.canvaskit,
+ isWasm: false,
testMode: true);
expect(webAssetServer.defaultResponseHeaders['x-frame-options'], null);
@@ -1496,6 +1505,7 @@
},
NullSafetyMode.unsound,
webRenderer: WebRendererMode.canvaskit,
+ isWasm: false,
testMode: true);
expect(webAssetServer.defaultResponseHeaders[extraHeaderKey],
@@ -1596,6 +1606,7 @@
nullSafetyMode: NullSafetyMode.unsound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit,
+ isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.ddcModuleLoaderJS.createSync(recursive: true);
diff --git a/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart b/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart
index 7802b1d..c4074f5 100644
--- a/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart
+++ b/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart
@@ -76,6 +76,7 @@
flutterRoot: null,
platform: FakePlatform(),
webBuildDirectory: null,
+ needsCoopCoep: false,
);
}, overrides: <Type, Generator>{
Logger: () => logger,
@@ -715,6 +716,7 @@
nullSafetyMode: NullSafetyMode.unsound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.html,
+ isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.requireJS.createSync(recursive: true);
@@ -827,6 +829,7 @@
nullSafetyMode: NullSafetyMode.sound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.html,
+ isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.requireJS.createSync(recursive: true);
@@ -945,6 +948,7 @@
nullSafetyMode: NullSafetyMode.sound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit,
+ isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.requireJS.createSync(recursive: true);
@@ -1009,6 +1013,7 @@
nullSafetyMode: NullSafetyMode.sound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit,
+ isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.requireJS.createSync(recursive: true);
@@ -1057,6 +1062,7 @@
nullSafetyMode: NullSafetyMode.sound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit,
+ isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.requireJS.createSync(recursive: true);
@@ -1106,6 +1112,7 @@
nullSafetyMode: NullSafetyMode.sound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.auto,
+ isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.requireJS.createSync(recursive: true);
@@ -1156,6 +1163,7 @@
nullSafetyMode: NullSafetyMode.unsound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit,
+ isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.requireJS.createSync(recursive: true);
@@ -1194,6 +1202,7 @@
const <String, String>{},
NullSafetyMode.unsound,
webRenderer: WebRendererMode.canvaskit,
+ isWasm: false,
testMode: true
);
@@ -1228,6 +1237,7 @@
},
NullSafetyMode.unsound,
webRenderer: WebRendererMode.canvaskit,
+ isWasm: false,
testMode: true
);
@@ -1310,6 +1320,7 @@
nullSafetyMode: NullSafetyMode.unsound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit,
+ isWasm: false,
rootDirectory: globals.fs.currentDirectory,
);
webDevFS.requireJS.createSync(recursive: true);
diff --git a/packages/flutter_tools/test/general.shard/web/web_asset_server_test.dart b/packages/flutter_tools/test/general.shard/web/web_asset_server_test.dart
index 858ed11..d0b8196 100644
--- a/packages/flutter_tools/test/general.shard/web/web_asset_server_test.dart
+++ b/packages/flutter_tools/test/general.shard/web/web_asset_server_test.dart
@@ -49,6 +49,7 @@
platform: platform,
flutterRoot: '/flutter',
webBuildDirectory: 'build/web',
+ needsCoopCoep: false,
);
fileSystem.file('build/web/assets/foo.png')
..createSync(recursive: true)
@@ -68,6 +69,7 @@
platform: platform,
flutterRoot: '/flutter',
webBuildDirectory: 'build/web',
+ needsCoopCoep: false,
);
fileSystem.file('build/web/assets/foo.js')
..createSync(recursive: true)
@@ -87,6 +89,7 @@
platform: platform,
flutterRoot: '/flutter',
webBuildDirectory: 'build/web',
+ needsCoopCoep: false,
);
fileSystem.file('build/web/assets/foo.html')
..createSync(recursive: true)
@@ -106,6 +109,7 @@
platform: platform,
flutterRoot: '/flutter',
webBuildDirectory: 'build/web',
+ needsCoopCoep: false,
);
fileSystem.file('flutter/bar.dart')
..createSync(recursive: true)
@@ -122,6 +126,7 @@
platform: platform,
flutterRoot: '/flutter',
webBuildDirectory: 'build/web',
+ needsCoopCoep: false,
);
fileSystem.file('bar.dart')
..createSync(recursive: true)
@@ -131,4 +136,44 @@
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);
+ });
}
diff --git a/packages/integration_test/example/web/flutter_bootstrap.js b/packages/integration_test/example/web/flutter_bootstrap.js
new file mode 100644
index 0000000..be78d93
--- /dev/null
+++ b/packages/integration_test/example/web/flutter_bootstrap.js
@@ -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/",
+ },
+});
diff --git a/packages/integration_test/example/web/index.html b/packages/integration_test/example/web/index.html
index fe77e24..c02e33b 100644
--- a/packages/integration_test/example/web/index.html
+++ b/packages/integration_test/example/web/index.html
@@ -21,16 +21,6 @@
<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>