[flutter_tools] wire up native-null-assertions for flutter web (#71618)

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 aff6c7c..ee56688d 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/web.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/web.dart
@@ -39,6 +39,9 @@
 /// Whether the dart2js build should output source maps.
 const String kSourceMapsEnabled = 'SourceMaps';
 
+/// Whether the dart2js native null assertions are enabled.
+const String kNativeNullAssertions = 'NativeNullAssertions';
+
 /// The caching strategy for the generated service worker.
 enum ServiceWorkerStrategy {
   /// Download the app shell eagerly and all other assets lazily.
@@ -190,6 +193,7 @@
   Future<void> build(Environment environment) async {
     final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
     final bool sourceMapsEnabled = environment.defines[kSourceMapsEnabled] == 'true';
+    final bool nativeNullAssertions = environment.defines[kNativeNullAssertions] == 'true';
 
     final List<String> sharedCommandOptions = <String>[
       globals.artifacts.getArtifactPath(Artifact.engineDartBinary),
@@ -197,6 +201,8 @@
       globals.artifacts.getArtifactPath(Artifact.dart2jsSnapshot),
       '--libraries-spec=${globals.fs.path.join(globals.artifacts.getArtifactPath(Artifact.flutterWebSdk), 'libraries.json')}',
       ...?decodeDartDefines(environment.defines, kExtraFrontEndOptions),
+      if (nativeNullAssertions)
+        '--native-null-assertions',
       if (buildMode == BuildMode.profile)
         '-Ddart.vm.profile=true'
       else
diff --git a/packages/flutter_tools/lib/src/commands/build_web.dart b/packages/flutter_tools/lib/src/commands/build_web.dart
index 62fad31..05c6ba5 100644
--- a/packages/flutter_tools/lib/src/commands/build_web.dart
+++ b/packages/flutter_tools/lib/src/commands/build_web.dart
@@ -26,6 +26,7 @@
     usesWebRendererOption();
     addEnableExperimentation(hide: !verboseHelp);
     addNullSafetyModeOptions(hide: !verboseHelp);
+    addNativeNullAssertions(hide: false);
     argParser.addFlag('csp',
       defaultsTo: false,
       negatable: false,
@@ -90,6 +91,7 @@
       boolArg('csp'),
       stringArg('pwa-strategy'),
       boolArg('source-maps'),
+      boolArg('native-null-assertions'),
     );
     return FlutterCommandResult.success();
   }
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart
index d18cc18..374a554 100644
--- a/packages/flutter_tools/lib/src/commands/run.dart
+++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -30,6 +30,7 @@
     usesDartDefineOption();
     usesFlavorOption();
     usesWebRendererOption();
+    addNativeNullAssertions(hide: !verboseHelp);
     argParser
       ..addFlag('trace-startup',
         negatable: false,
@@ -205,6 +206,7 @@
           && boolArg('fast-start')
           && !runningWithPrebuiltApplication,
         nullAssertions: boolArg('null-assertions'),
+        nativeNullAssertions: boolArg('native-null-assertions'),
       );
     }
   }
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index 53932d7..019c129 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -863,6 +863,7 @@
     this.vmserviceOutFile,
     this.fastStart = false,
     this.nullAssertions = false,
+    this.nativeNullAssertions = false,
    }) : debuggingEnabled = true;
 
   DebuggingOptions.disabled(this.buildInfo, {
@@ -897,7 +898,8 @@
       vmserviceOutFile = null,
       fastStart = false,
       webEnableExpressionEvaluation = false,
-      nullAssertions = false;
+      nullAssertions = false,
+      nativeNullAssertions = false;
 
   final bool debuggingEnabled;
 
@@ -947,6 +949,12 @@
 
   final bool nullAssertions;
 
+  /// Additional null runtime checks inserted for web applications.
+  ///
+  /// See also:
+  ///   * https://github.com/dart-lang/sdk/blob/master/sdk/lib/html/doc/NATIVE_NULL_ASSERTIONS.md
+  final bool nativeNullAssertions;
+
   bool get hasObservatoryPort => hostVmServicePort != null;
 }
 
diff --git a/packages/flutter_tools/lib/src/isolated/devfs_web.dart b/packages/flutter_tools/lib/src/isolated/devfs_web.dart
index 2c24a79..7ca818b 100644
--- a/packages/flutter_tools/lib/src/isolated/devfs_web.dart
+++ b/packages/flutter_tools/lib/src/isolated/devfs_web.dart
@@ -605,6 +605,7 @@
     @required this.expressionCompiler,
     @required this.chromiumLauncher,
     @required this.nullAssertions,
+    @required this.nativeNullAssertions,
     @required this.nullSafetyMode,
     this.testMode = false,
   }) : _port = port;
@@ -621,6 +622,7 @@
   final ExpressionCompiler expressionCompiler;
   final ChromiumLauncher chromiumLauncher;
   final bool nullAssertions;
+  final bool nativeNullAssertions;
   final int _port;
   final NullSafetyMode nullSafetyMode;
 
@@ -779,6 +781,7 @@
         generateMainModule(
           entrypoint: entrypoint,
           nullAssertions: nullAssertions,
+          nativeNullAssertions: nativeNullAssertions,
         ),
       );
       // TODO(jonahwilliams): refactor the asset code in this and the regular devfs to
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 e67be35..f0c98f7 100644
--- a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart
+++ b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart
@@ -501,6 +501,7 @@
           chromiumLauncher: _chromiumLauncher,
           nullAssertions: debuggingOptions.nullAssertions,
           nullSafetyMode: debuggingOptions.buildInfo.nullSafetyMode,
+          nativeNullAssertions: debuggingOptions.nativeNullAssertions,
         );
         final Uri url = await device.devFS.create();
         if (debuggingOptions.buildInfo.isDebug) {
@@ -520,6 +521,7 @@
             false,
             kNoneWorker,
             true,
+            debuggingOptions.nativeNullAssertions,
           );
         }
         await device.device.startApp(
@@ -587,6 +589,7 @@
           false,
           kNoneWorker,
           true,
+          debuggingOptions.nativeNullAssertions,
         );
       } on ToolExit {
         return OperationResult(1, 'Failed to recompile application.');
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index e2d2e35..f0ba9b8 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -674,6 +674,19 @@
     );
   }
 
+  void addNativeNullAssertions({ bool hide = false }) {
+    argParser.addFlag('native-null-assertions',
+      defaultsTo: true,
+      hide: hide,
+      help: 'Enables additional runtime null checks in web applications to ensure '
+        'the correct nullability of native (such as in dart:html) and external '
+        '(such as with JS interop) types. This is enabled by default but only takes '
+        'effect in sound mode. To report an issue with a null assertion failure in '
+        'dart:html or the other dart web libraries, please file a bug at '
+        'https://github.com/dart-lang/sdk/issues/labels/web-libraries .'
+    );
+  }
+
   /// Adds build options common to all of the desktop build commands.
   void addCommonDesktopBuildOptions({ bool verboseHelp = false }) {
     addBuildModeFlags(verboseHelp: verboseHelp);
diff --git a/packages/flutter_tools/lib/src/test/flutter_web_platform.dart b/packages/flutter_tools/lib/src/test/flutter_web_platform.dart
index 4eb4aa0..2b230f1 100644
--- a/packages/flutter_tools/lib/src/test/flutter_web_platform.dart
+++ b/packages/flutter_tools/lib/src/test/flutter_web_platform.dart
@@ -224,6 +224,7 @@
       final String generatedFile = _fileSystem.path.split(leadingPath).join('_') + '.dart.test.dart.js';
       return shelf.Response.ok(generateMainModule(
         nullAssertions: nullAssertions,
+        nativeNullAssertions: true,
         bootstrapModule: _fileSystem.path.basename(leadingPath) + '.dart.bootstrap',
         entrypoint: '/' + generatedFile
        ), headers: <String, String>{
diff --git a/packages/flutter_tools/lib/src/web/bootstrap.dart b/packages/flutter_tools/lib/src/web/bootstrap.dart
index 18c85a3..6071a27 100644
--- a/packages/flutter_tools/lib/src/web/bootstrap.dart
+++ b/packages/flutter_tools/lib/src/web/bootstrap.dart
@@ -54,6 +54,7 @@
 String generateMainModule({
   @required String entrypoint,
   @required bool nullAssertions,
+  @required bool nativeNullAssertions,
   String bootstrapModule = 'main_module.bootstrap',
 }) {
   // TODO(jonahwilliams): fix typo in dwds and update.
@@ -63,9 +64,8 @@
 define("$bootstrapModule", ["$entrypoint", "dart_sdk"], function(app, dart_sdk) {
   dart_sdk.dart.setStartAsyncSynchronously(true);
   dart_sdk._debugger.registerDevtoolsFormatter();
-  if ($nullAssertions) {
-    dart_sdk.dart.nonNullAsserts(true);
-  }
+  dart_sdk.dart.nonNullAsserts($nullAssertions);
+  dart_sdk.dart.nativeNonNullAsserts($nativeNullAssertions);
 
   // See the generateMainModule doc comment.
   var child = {};
diff --git a/packages/flutter_tools/lib/src/web/compile.dart b/packages/flutter_tools/lib/src/web/compile.dart
index 7e08363..160f41d 100644
--- a/packages/flutter_tools/lib/src/web/compile.dart
+++ b/packages/flutter_tools/lib/src/web/compile.dart
@@ -24,6 +24,7 @@
   bool csp,
   String serviceWorkerStrategy,
   bool sourceMaps,
+  bool nativeNullAssertions,
 ) async {
   if (!flutterProject.web.existsSync()) {
     throwToolExit('Missing index.html.');
@@ -51,6 +52,7 @@
         kCspMode: csp.toString(),
         kIconTreeShakerFlag: buildInfo.treeShakeIcons.toString(),
         kSourceMapsEnabled: sourceMaps.toString(),
+        kNativeNullAssertions: nativeNullAssertions.toString(),
         if (serviceWorkerStrategy != null)
          kServiceWorkerStrategy: serviceWorkerStrategy,
         if (buildInfo.extraFrontEndOptions?.isNotEmpty ?? false)
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 4a76cf5..ec9a0bf 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
@@ -56,6 +56,7 @@
       false,
       null,
       true,
+      true,
     ), throwsToolExit());
   }, overrides: <Type, Generator>{
     Platform: () => fakePlatform,
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 e5202ba..dac00f9 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
@@ -392,6 +392,40 @@
     ProcessManager: () => processManager,
   }));
 
+  test('Dart2JSTarget calls dart2js with expected args in release mode with native null assertions', () => testbed.run(() async {
+    environment.defines[kBuildMode] = 'release';
+    environment.defines[kNativeNullAssertions] = 'true';
+    processManager.addCommand(FakeCommand(
+      command: <String>[
+        ...kDart2jsLinuxArgs,
+        '--native-null-assertions',
+        '-Ddart.vm.product=true',
+        '--no-source-maps',
+        '-o',
+        environment.buildDir.childFile('app.dill').absolute.path,
+        '--packages=.packages',
+        '--cfe-only',
+        environment.buildDir.childFile('main.dart').absolute.path,
+      ]
+    ));
+    processManager.addCommand(FakeCommand(
+      command: <String>[
+        ...kDart2jsLinuxArgs,
+        '--native-null-assertions',
+        '-Ddart.vm.product=true',
+        '--no-source-maps',
+        '-O4',
+        '-o',
+        environment.buildDir.childFile('main.dart.js').absolute.path,
+        environment.buildDir.childFile('app.dill').absolute.path,
+      ]
+    ));
+
+    await const Dart2JSTarget().build(environment);
+  }, overrides: <Type, Generator>{
+    ProcessManager: () => processManager,
+  }));
+
   test('Dart2JSTarget calls dart2js with expected args in release with dart2js optimization override', () => testbed.run(() async {
     environment.defines[kBuildMode] = 'release';
     environment.defines[kDart2jsOptimization] = 'O3';
diff --git a/packages/flutter_tools/test/general.shard/web/bootstrap_test.dart b/packages/flutter_tools/test/general.shard/web/bootstrap_test.dart
index 5bb0bd6..0cb5888 100644
--- a/packages/flutter_tools/test/general.shard/web/bootstrap_test.dart
+++ b/packages/flutter_tools/test/general.shard/web/bootstrap_test.dart
@@ -24,6 +24,7 @@
     final String result = generateMainModule(
       entrypoint: 'foo/bar/main.js',
       nullAssertions: false,
+      nativeNullAssertions: false,
     );
     // bootstrap main module has correct defined module.
     expect(result, contains('define("main_module.bootstrap", ["foo/bar/main.js", "dart_sdk"], '
@@ -34,6 +35,7 @@
     final String result = generateMainModule(
       entrypoint: 'foo/bar/main.js',
       nullAssertions: false,
+      nativeNullAssertions: false,
       bootstrapModule: 'foo_module.bootstrap',
     );
     // bootstrap main module has correct defined module.
@@ -45,11 +47,22 @@
     final String result = generateMainModule(
       entrypoint: 'foo/bar/main.js',
       nullAssertions: true,
+      nativeNullAssertions: true,
     );
 
-    expect(result, contains('''
-  if (true) {
-    dart_sdk.dart.nonNullAsserts(true);'''));
+    expect(result, contains('''dart_sdk.dart.nonNullAsserts(true);'''));
+    expect(result, contains('''dart_sdk.dart.nativeNonNullAsserts(true);'''));
+  });
+
+  test('generateMainModule can disable null safety switches', () {
+    final String result = generateMainModule(
+      entrypoint: 'foo/bar/main.js',
+      nullAssertions: false,
+      nativeNullAssertions: false,
+    );
+
+    expect(result, contains('''dart_sdk.dart.nonNullAsserts(false);'''));
+    expect(result, contains('''dart_sdk.dart.nativeNonNullAsserts(false);'''));
   });
 
   test('generateTestBootstrapFileContents embeds urls correctly', () {
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 77228e4..2d580b2 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
@@ -617,6 +617,7 @@
       useSseForDebugProxy: true,
       useSseForDebugBackend: true,
       nullAssertions: true,
+      nativeNullAssertions: true,
       buildInfo: const BuildInfo(
         BuildMode.debug,
         '',
@@ -732,6 +733,7 @@
       useSseForDebugProxy: true,
       useSseForDebugBackend: true,
       nullAssertions: true,
+      nativeNullAssertions: true,
       buildInfo: const BuildInfo(
         BuildMode.debug,
         '',
@@ -850,6 +852,7 @@
       expressionCompiler: null,
       chromiumLauncher: null,
       nullAssertions: true,
+      nativeNullAssertions: true,
       nullSafetyMode: NullSafetyMode.sound,
     );
     webDevFS.requireJS.createSync(recursive: true);
@@ -886,6 +889,7 @@
       useSseForDebugProxy: true,
       useSseForDebugBackend: true,
       nullAssertions: true,
+      nativeNullAssertions: true,
       buildInfo: const BuildInfo(
         BuildMode.debug,
         '',
@@ -936,6 +940,7 @@
       useSseForDebugProxy: true,
       useSseForDebugBackend: true,
       nullAssertions: true,
+      nativeNullAssertions: true,
       buildInfo: const BuildInfo(
         BuildMode.debug,
         '',