diff --git a/packages/flutter_tools/lib/src/build_runner/web_fs.dart b/packages/flutter_tools/lib/src/build_runner/web_fs.dart index f4c97b6895..243b1d88d3 100644 --- a/packages/flutter_tools/lib/src/build_runner/web_fs.dart +++ b/packages/flutter_tools/lib/src/build_runner/web_fs.dart @@ -150,7 +150,14 @@ class WebFs { /// Recompile the web application and return whether this was successful. Future recompile() async { if (!_useBuildRunner) { - await buildWeb(_flutterProject, _target, _buildInfo, _initializePlatform, _dartDefines); + await buildWeb( + _flutterProject, + _target, + _buildInfo, + _initializePlatform, + _dartDefines, + false, + ); return true; } _client.startBuild(); @@ -309,7 +316,14 @@ class WebFs { handler = pipeline.addHandler(proxyHandler('http://localhost:$daemonAssetPort/web/')); } } else { - await buildWeb(flutterProject, target, buildInfo, initializePlatform, dartDefines); + await buildWeb( + flutterProject, + target, + buildInfo, + initializePlatform, + dartDefines, + false, + ); firstBuildCompleter.complete(true); } diff --git a/packages/flutter_tools/lib/src/build_system/targets/web.dart b/packages/flutter_tools/lib/src/build_system/targets/web.dart index 00d9f63493..2237175dd8 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/web.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/web.dart @@ -26,6 +26,9 @@ const String kHasWebPlugins = 'HasWebPlugins'; /// Valid values are O1 (lowest, profile default) to O4 (highest, release default). const String kDart2jsOptimization = 'Dart2jsOptimization'; +/// Whether to disable dynamic generation code to satisfy csp policies. +const String kCspMode = 'cspMode'; + /// Generates an entry point for a web target. class WebEntrypointTarget extends Target { const WebEntrypointTarget(); @@ -146,6 +149,7 @@ class Dart2JSTarget extends Target { @override Future build(Environment environment) async { final String dart2jsOptimization = environment.defines[kDart2jsOptimization]; + final bool csp = environment.defines[kCspMode] == 'true'; final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]); final String specPath = globals.fs.path.join(globals.artifacts.getArtifactPath(Artifact.flutterWebSdk), 'libraries.json'); final String packageFile = FlutterProject.fromDirectory(environment.projectDir).hasBuilders @@ -170,6 +174,8 @@ class Dart2JSTarget extends Target { '-Ddart.vm.profile=true' else '-Ddart.vm.product=true', + if (csp) + '--csp', for (final String dartDefine in parseDartDefines(environment)) '-D$dartDefine', environment.buildDir.childFile('main.dart').path, diff --git a/packages/flutter_tools/lib/src/commands/build_web.dart b/packages/flutter_tools/lib/src/commands/build_web.dart index 7e68e099ff..4c2419cefa 100644 --- a/packages/flutter_tools/lib/src/commands/build_web.dart +++ b/packages/flutter_tools/lib/src/commands/build_web.dart @@ -25,6 +25,12 @@ class BuildWebCommand extends BuildSubCommand { hide: true, help: 'Whether to automatically invoke webOnlyInitializePlatform.', ); + argParser.addFlag('csp', + defaultsTo: false, + negatable: false, + help: 'Disable dynamic generation of code in the generated output.' + 'This is necessary to satisfy CSP restrictions (see http://www.w3.org/TR/CSP/).' + ); } @override @@ -59,6 +65,7 @@ class BuildWebCommand extends BuildSubCommand { buildInfo, boolArg('web-initialize-platform'), dartDefines, + boolArg('csp') ); return null; } diff --git a/packages/flutter_tools/lib/src/web/compile.dart b/packages/flutter_tools/lib/src/web/compile.dart index c0620314ee..266ecc483d 100644 --- a/packages/flutter_tools/lib/src/web/compile.dart +++ b/packages/flutter_tools/lib/src/web/compile.dart @@ -28,6 +28,7 @@ Future buildWeb( BuildInfo buildInfo, bool initializePlatform, List dartDefines, + bool csp, ) async { if (!flutterProject.web.existsSync()) { throwToolExit('Missing index.html.'); @@ -50,6 +51,7 @@ Future buildWeb( kInitializePlatform: initializePlatform.toString(), kHasWebPlugins: hasWebPlugins.toString(), kDartDefines: jsonEncode(dartDefines), + kCspMode: csp.toString(), }, )); if (!result.success) { diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_web_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_web_test.dart index 7565fa3a4d..96c790d329 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_web_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_web_test.dart @@ -59,6 +59,7 @@ void main() { BuildInfo.debug, false, const [], + false, ), throwsA(isInstanceOf())); })); diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart index 4c3b261647..b5c3b38aed 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart @@ -165,6 +165,33 @@ void main() { expect(generated, contains('entrypoint.main();')); })); + test('Dart2JSTarget calls dart2js with expected args with csp', () => testbed.run(() async { + environment.defines[kBuildMode] = 'profile'; + environment.defines[kCspMode] = 'true'; + when(globals.processManager.run(any)).thenAnswer((Invocation invocation) async { + return FakeProcessResult(exitCode: 0); + }); + await const Dart2JSTarget().build(environment); + + final List expected = [ + globals.fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'dart'), + globals.fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'snapshots', 'dart2js.dart.snapshot'), + '--libraries-spec=' + globals.fs.path.join('bin', 'cache', 'flutter_web_sdk', 'libraries.json'), + '-O4', // highest optimizations + '--no-minify', // but uses unminified names for debugging + '-o', + environment.buildDir.childFile('main.dart.js').absolute.path, + '--packages=${globals.fs.path.join('foo', '.packages')}', + '-Ddart.vm.profile=true', + '--csp', + environment.buildDir.childFile('main.dart').absolute.path, + ]; + verify(globals.processManager.run(expected)).called(1); + }, overrides: { + ProcessManager: () => MockProcessManager(), + })); + + test('Dart2JSTarget calls dart2js with expected args in profile mode', () => testbed.run(() async { environment.defines[kBuildMode] = 'profile'; when(globals.processManager.run(any)).thenAnswer((Invocation invocation) async {