Revert "Use FlutterFeatures to configure web and desktop devices (#36465)" (#36654)

This reverts commit bd52a78c7162e26721a383b76bac53744e5656a7.
diff --git a/packages/flutter_tools/lib/src/commands/assemble.dart b/packages/flutter_tools/lib/src/commands/assemble.dart
index 9919e65..e5a6659 100644
--- a/packages/flutter_tools/lib/src/commands/assemble.dart
+++ b/packages/flutter_tools/lib/src/commands/assemble.dart
@@ -30,6 +30,8 @@
   @override
   String get name => 'assemble';
 
+  @override
+  bool get isExperimental => true;
 
   @override
   Future<FlutterCommandResult> runCommand() {
@@ -125,6 +127,9 @@
   String get name => 'run';
 
   @override
+  bool get isExperimental => true;
+
+  @override
   Future<FlutterCommandResult> runCommand() async {
     final BuildResult result = await buildSystem.build(targetName, environment, BuildSystemConfig(
       resourcePoolSize: argResults['resource-pool-size'],
@@ -151,6 +156,9 @@
   String get name => 'describe';
 
   @override
+  bool get isExperimental => true;
+
+  @override
   Future<FlutterCommandResult> runCommand() {
     try {
       printStatus(
@@ -173,6 +181,9 @@
   String get name => 'inputs';
 
   @override
+  bool get isExperimental => true;
+
+  @override
   Future<FlutterCommandResult> runCommand() {
     try {
       final List<Map<String, Object>> results = buildSystem.describe(targetName, environment);
@@ -199,6 +210,9 @@
   String get name => 'build-dir';
 
   @override
+  bool get isExperimental => true;
+
+  @override
   Future<FlutterCommandResult> runCommand() {
     printStatus(environment.buildDir.path);
     return null;
diff --git a/packages/flutter_tools/lib/src/commands/build_bundle.dart b/packages/flutter_tools/lib/src/commands/build_bundle.dart
index d15cfa4..ef2235f 100644
--- a/packages/flutter_tools/lib/src/commands/build_bundle.dart
+++ b/packages/flutter_tools/lib/src/commands/build_bundle.dart
@@ -8,10 +8,10 @@
 import '../base/file_system.dart';
 import '../build_info.dart';
 import '../bundle.dart';
-import '../features.dart';
 import '../project.dart';
 import '../reporting/usage.dart';
 import '../runner/flutter_command.dart' show FlutterOptions, FlutterCommandResult;
+import '../version.dart';
 import 'build.dart';
 
 class BuildBundleCommand extends BuildSubCommand {
@@ -98,21 +98,13 @@
     if (platform == null) {
       throwToolExit('Unknown platform: $targetPlatform');
     }
-    // Check for target platforms that are only allowed via feature flags.
+    // Check for target platforms that are only allowed on unstable Flutter.
     switch (platform) {
       case TargetPlatform.darwin_x64:
-        if (!featureFlags.isMacOSEnabled) {
-          throwToolExit('macOS is not a supported target platform.');
-        }
-        break;
       case TargetPlatform.windows_x64:
-        if (!featureFlags.isWindowsEnabled) {
-          throwToolExit('Windows is not a supported target platform.');
-        }
-        break;
       case TargetPlatform.linux_x64:
-        if (!featureFlags.isLinuxEnabled) {
-          throwToolExit('Linux is not a supported target platform.');
+        if (!FlutterVersion.instance.isMaster) {
+          throwToolExit('$targetPlatform is not supported on stable Flutter.');
         }
         break;
       default:
diff --git a/packages/flutter_tools/lib/src/commands/build_fuchsia.dart b/packages/flutter_tools/lib/src/commands/build_fuchsia.dart
index 39e0a84..473f943 100644
--- a/packages/flutter_tools/lib/src/commands/build_fuchsia.dart
+++ b/packages/flutter_tools/lib/src/commands/build_fuchsia.dart
@@ -25,6 +25,9 @@
   final String name = 'fuchsia';
 
   @override
+  bool isExperimental = true;
+
+  @override
   bool hidden = true;
 
   @override
diff --git a/packages/flutter_tools/lib/src/commands/build_linux.dart b/packages/flutter_tools/lib/src/commands/build_linux.dart
index e75e411..4175b6d 100644
--- a/packages/flutter_tools/lib/src/commands/build_linux.dart
+++ b/packages/flutter_tools/lib/src/commands/build_linux.dart
@@ -8,7 +8,6 @@
 import '../base/platform.dart';
 import '../build_info.dart';
 import '../cache.dart';
-import '../features.dart';
 import '../linux/build_linux.dart';
 import '../project.dart';
 import '../runner/flutter_command.dart' show FlutterCommandResult;
@@ -36,6 +35,9 @@
   final String name = 'linux';
 
   @override
+  bool isExperimental = true;
+
+  @override
   bool hidden = true;
 
   @override
@@ -52,9 +54,6 @@
     Cache.releaseLockEarly();
     final BuildInfo buildInfo = getBuildInfo();
     final FlutterProject flutterProject = FlutterProject.current();
-    if (!featureFlags.isLinuxEnabled) {
-      throwToolExit('"build linux" is not currently supported.');
-    }
     if (!platform.isLinux) {
       throwToolExit('"build linux" only supported on Linux hosts.');
     }
diff --git a/packages/flutter_tools/lib/src/commands/build_macos.dart b/packages/flutter_tools/lib/src/commands/build_macos.dart
index f871186..e773f42 100644
--- a/packages/flutter_tools/lib/src/commands/build_macos.dart
+++ b/packages/flutter_tools/lib/src/commands/build_macos.dart
@@ -8,7 +8,6 @@
 import '../base/platform.dart';
 import '../build_info.dart';
 import '../cache.dart';
-import '../features.dart';
 import '../macos/build_macos.dart';
 import '../project.dart';
 import '../runner/flutter_command.dart' show FlutterCommandResult;
@@ -36,6 +35,9 @@
   final String name = 'macos';
 
   @override
+  bool isExperimental = true;
+
+  @override
   bool hidden = true;
 
   @override
@@ -45,16 +47,13 @@
   };
 
   @override
-  String get description => 'build the macOS desktop target.';
+  String get description => 'build the macOS desktop target (Experimental).';
 
   @override
   Future<FlutterCommandResult> runCommand() async {
     Cache.releaseLockEarly();
     final BuildInfo buildInfo = getBuildInfo();
     final FlutterProject flutterProject = FlutterProject.current();
-    if (!featureFlags.isMacOSEnabled) {
-      throwToolExit('"build macos" is not currently supported.');
-    }
     if (!platform.isMacOS) {
       throwToolExit('"build macos" only supported on macOS hosts.');
     }
diff --git a/packages/flutter_tools/lib/src/commands/build_web.dart b/packages/flutter_tools/lib/src/commands/build_web.dart
index 575d1b0..32cea06 100644
--- a/packages/flutter_tools/lib/src/commands/build_web.dart
+++ b/packages/flutter_tools/lib/src/commands/build_web.dart
@@ -4,9 +4,7 @@
 
 import 'dart:async';
 
-import '../base/common.dart';
 import '../build_info.dart';
-import '../features.dart';
 import '../project.dart';
 import '../runner/flutter_command.dart'
     show DevelopmentArtifact, FlutterCommandResult;
@@ -34,13 +32,13 @@
   bool get hidden => true;
 
   @override
-  final String description = 'build a web application bundle.';
+  bool get isExperimental => true;
+
+  @override
+  final String description = '(EXPERIMENTAL) build a web application bundle.';
 
   @override
   Future<FlutterCommandResult> runCommand() async {
-    if (!featureFlags.isWebEnabled) {
-      throwToolExit('"build web" is not currently supported.');
-    }
     final FlutterProject flutterProject = FlutterProject.current();
     final String target = argResults['target'];
     final BuildInfo buildInfo = getBuildInfo();
diff --git a/packages/flutter_tools/lib/src/commands/build_windows.dart b/packages/flutter_tools/lib/src/commands/build_windows.dart
index e41de03..884d26f 100644
--- a/packages/flutter_tools/lib/src/commands/build_windows.dart
+++ b/packages/flutter_tools/lib/src/commands/build_windows.dart
@@ -8,7 +8,6 @@
 import '../base/platform.dart';
 import '../build_info.dart';
 import '../cache.dart';
-import '../features.dart';
 import '../project.dart';
 import '../runner/flutter_command.dart' show FlutterCommandResult;
 import '../windows/build_windows.dart';
@@ -36,6 +35,9 @@
   final String name = 'windows';
 
   @override
+  bool isExperimental = true;
+
+  @override
   bool hidden = true;
 
   @override
@@ -45,16 +47,13 @@
   };
 
   @override
-  String get description => 'build the desktop Windows target.';
+  String get description => 'build the desktop Windows target (Experimental).';
 
   @override
   Future<FlutterCommandResult> runCommand() async {
     Cache.releaseLockEarly();
     final FlutterProject flutterProject = FlutterProject.current();
     final BuildInfo buildInfo = getBuildInfo();
-    if (!featureFlags.isWindowsEnabled) {
-      throwToolExit('"build windows" is not currently supported.');
-    }
     if (!platform.isWindows) {
       throwToolExit('"build windows" only supported on Windows hosts.');
     }
diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart
index 875beae..17458ee 100644
--- a/packages/flutter_tools/lib/src/commands/create.dart
+++ b/packages/flutter_tools/lib/src/commands/create.dart
@@ -20,7 +20,6 @@
 import '../convert.dart';
 import '../dart/pub.dart';
 import '../doctor.dart';
-import '../features.dart';
 import '../globals.dart';
 import '../project.dart';
 import '../reporting/usage.dart';
@@ -614,7 +613,7 @@
       'iosLanguage': iosLanguage,
       'flutterRevision': FlutterVersion.instance.frameworkRevision,
       'flutterChannel': FlutterVersion.instance.channel,
-      'web': web && featureFlags.isWebEnabled,
+      'web': web && FlutterVersion.instance.isMaster
     };
   }
 
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart
index 0254e3e..91de004 100644
--- a/packages/flutter_tools/lib/src/commands/run.dart
+++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -13,7 +13,6 @@
 import '../build_info.dart';
 import '../cache.dart';
 import '../device.dart';
-import '../features.dart';
 import '../globals.dart';
 import '../macos/xcode.dart';
 import '../project.dart';
@@ -411,11 +410,11 @@
       );
       flutterDevices.add(flutterDevice);
     }
-    // Only support "web mode" with a single web device due to resident runner
-    // refactoring required otherwise.
-    final bool webMode = featureFlags.isWebEnabled &&
-                         devices.length == 1  &&
-                         await devices.single.targetPlatform == TargetPlatform.web_javascript;
+    // Only support "web mode" on non-stable branches with a single web device
+    // in a "hot mode".
+    final bool webMode = FlutterVersion.instance.isMaster
+      && devices.length == 1
+      && await devices.single.targetPlatform == TargetPlatform.web_javascript;
 
     ResidentRunner runner;
     final String applicationBinaryPath = argResults['use-application-binary'];
diff --git a/packages/flutter_tools/lib/src/commands/unpack.dart b/packages/flutter_tools/lib/src/commands/unpack.dart
index 481c326..799815e 100644
--- a/packages/flutter_tools/lib/src/commands/unpack.dart
+++ b/packages/flutter_tools/lib/src/commands/unpack.dart
@@ -70,6 +70,9 @@
   bool get hidden => true;
 
   @override
+  bool get isExperimental => true;
+
+  @override
   Future<Set<DevelopmentArtifact>> get requiredArtifacts async {
     final Set<DevelopmentArtifact> result = <DevelopmentArtifact>{
       DevelopmentArtifact.universal,
diff --git a/packages/flutter_tools/lib/src/desktop.dart b/packages/flutter_tools/lib/src/desktop.dart
index e62c4e7..501e241 100644
--- a/packages/flutter_tools/lib/src/desktop.dart
+++ b/packages/flutter_tools/lib/src/desktop.dart
@@ -4,10 +4,32 @@
 
 import 'dart:async';
 
+import 'package:meta/meta.dart';
+
+import 'base/common.dart';
 import 'base/io.dart';
+import 'base/platform.dart';
 import 'base/process_manager.dart';
 import 'convert.dart';
 import 'device.dart';
+import 'version.dart';
+
+@visibleForTesting
+bool debugDisableDesktop = false;
+
+/// Only launch or display desktop embedding devices from the command line
+/// or if `ENABLE_FLUTTER_DESKTOP` environment variable is set to true.
+bool get flutterDesktopEnabled {
+  if (debugDisableDesktop) {
+    return false;
+  }
+  if (isRunningFromDaemon) {
+    final bool platformEnabled = platform
+        .environment['ENABLE_FLUTTER_DESKTOP']?.toLowerCase() == 'true';
+    return platformEnabled && FlutterVersion.instance.isMaster;
+  }
+  return FlutterVersion.instance.isMaster;
+}
 
 /// Kills a process on linux or macOS.
 Future<bool> killProcess(String executable) async {
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index 734172f..936bf15 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -14,7 +14,9 @@
 import 'base/file_system.dart';
 import 'base/utils.dart';
 import 'build_info.dart';
+import 'desktop.dart';
 import 'fuchsia/fuchsia_device.dart';
+
 import 'globals.dart';
 import 'ios/devices.dart';
 import 'ios/simulators.dart';
@@ -23,6 +25,7 @@
 import 'project.dart';
 import 'tester/flutter_tester.dart';
 import 'web/web_device.dart';
+import 'web/workflow.dart';
 import 'windows/windows_device.dart';
 
 DeviceManager get deviceManager => context.get<DeviceManager>();
@@ -71,11 +74,23 @@
     IOSSimulators(),
     FuchsiaDevices(),
     FlutterTesterDevices(),
-    MacOSDevices(),
-    LinuxDevices(),
-    WindowsDevices(),
-    WebDevices(),
-  ]);
+  ] + _conditionalDesktopDevices + _conditionalWebDevices);
+
+  /// Only add desktop devices if the flag is enabled.
+  static List<DeviceDiscovery> get _conditionalDesktopDevices {
+    return flutterDesktopEnabled ? <DeviceDiscovery>[
+      MacOSDevices(),
+      LinuxDevices(),
+      WindowsDevices(),
+    ] : <DeviceDiscovery>[];
+  }
+
+  /// Only add web devices if the flag is enabled.
+  static List<DeviceDiscovery> get _conditionalWebDevices {
+    return flutterWebEnabled ? <DeviceDiscovery>[
+      WebDevices(),
+    ] : <DeviceDiscovery>[];
+  }
 
   String _specifiedDeviceId;
 
diff --git a/packages/flutter_tools/lib/src/doctor.dart b/packages/flutter_tools/lib/src/doctor.dart
index cf697c7..fa4081d 100644
--- a/packages/flutter_tools/lib/src/doctor.dart
+++ b/packages/flutter_tools/lib/src/doctor.dart
@@ -19,6 +19,7 @@
 import 'base/utils.dart';
 import 'base/version.dart';
 import 'cache.dart';
+import 'desktop.dart';
 import 'device.dart';
 import 'fuchsia/fuchsia_workflow.dart';
 import 'globals.dart';
@@ -73,10 +74,12 @@
           GroupedValidator(<DoctorValidator>[xcodeValidator, cocoapodsValidator]),
         if (webWorkflow.appliesToHostPlatform)
           const WebValidator(),
-        if (linuxWorkflow.appliesToHostPlatform)
-          LinuxDoctorValidator(),
-        if (windowsWorkflow.appliesToHostPlatform)
-          visualStudioValidator,
+        // Add desktop doctors to workflow if the flag is enabled.
+        if (flutterDesktopEnabled)
+          ...<DoctorValidator>[
+            if (linuxWorkflow.appliesToHostPlatform) LinuxDoctorValidator(),
+            if (windowsWorkflow.appliesToHostPlatform) visualStudioValidator,
+          ],
         if (ideValidators.isNotEmpty)
           ...ideValidators
         else
diff --git a/packages/flutter_tools/lib/src/linux/linux_workflow.dart b/packages/flutter_tools/lib/src/linux/linux_workflow.dart
index e51c26b..4959f72 100644
--- a/packages/flutter_tools/lib/src/linux/linux_workflow.dart
+++ b/packages/flutter_tools/lib/src/linux/linux_workflow.dart
@@ -4,8 +4,8 @@
 
 import '../base/context.dart';
 import '../base/platform.dart';
+import '../desktop.dart';
 import '../doctor.dart';
-import '../features.dart';
 
 /// The [WindowsWorkflow] instance.
 LinuxWorkflow get linuxWorkflow => context.get<LinuxWorkflow>();
@@ -18,13 +18,13 @@
   const LinuxWorkflow();
 
   @override
-  bool get appliesToHostPlatform => platform.isLinux && featureFlags.isLinuxEnabled;
+  bool get appliesToHostPlatform => platform.isLinux;
 
   @override
-  bool get canLaunchDevices => platform.isLinux && featureFlags.isLinuxEnabled;
+  bool get canLaunchDevices => platform.isLinux && flutterDesktopEnabled;
 
   @override
-  bool get canListDevices => platform.isLinux && featureFlags.isLinuxEnabled;
+  bool get canListDevices => platform.isLinux && flutterDesktopEnabled;
 
   @override
   bool get canListEmulators => false;
diff --git a/packages/flutter_tools/lib/src/macos/macos_workflow.dart b/packages/flutter_tools/lib/src/macos/macos_workflow.dart
index 1c0391f..e85e3d4 100644
--- a/packages/flutter_tools/lib/src/macos/macos_workflow.dart
+++ b/packages/flutter_tools/lib/src/macos/macos_workflow.dart
@@ -4,8 +4,8 @@
 
 import '../base/context.dart';
 import '../base/platform.dart';
+import '../desktop.dart';
 import '../doctor.dart';
-import '../features.dart';
 
 /// The [MacOSWorkflow] instance.
 MacOSWorkflow get macOSWorkflow => context.get<MacOSWorkflow>();
@@ -18,13 +18,13 @@
   const MacOSWorkflow();
 
   @override
-  bool get appliesToHostPlatform => platform.isMacOS && featureFlags.isMacOSEnabled;
+  bool get appliesToHostPlatform => platform.isMacOS;
 
   @override
-  bool get canLaunchDevices => platform.isMacOS && featureFlags.isMacOSEnabled;
+  bool get canLaunchDevices => platform.isMacOS && flutterDesktopEnabled;
 
   @override
-  bool get canListDevices => platform.isMacOS && featureFlags.isMacOSEnabled;
+  bool get canListDevices => platform.isMacOS && flutterDesktopEnabled;
 
   @override
   bool get canListEmulators => false;
diff --git a/packages/flutter_tools/lib/src/plugins.dart b/packages/flutter_tools/lib/src/plugins.dart
index 021ec84..0242a62 100644
--- a/packages/flutter_tools/lib/src/plugins.dart
+++ b/packages/flutter_tools/lib/src/plugins.dart
@@ -9,7 +9,7 @@
 
 import 'base/file_system.dart';
 import 'dart/package_map.dart';
-import 'features.dart';
+import 'desktop.dart';
 import 'globals.dart';
 import 'macos/cocoapods.dart';
 import 'project.dart';
@@ -364,7 +364,7 @@
   // TODO(stuartmorgan): Revisit the condition here once the plans for handling
   // desktop in existing projects are in place. For now, ignore checkProjects
   // on desktop and always treat it as true.
-  if (featureFlags.isMacOSEnabled && project.macos.existsSync()) {
+  if (flutterDesktopEnabled && project.macos.existsSync()) {
     await _writeMacOSPluginRegistrant(project, plugins);
   }
   for (final XcodeBasedProject subproject in <XcodeBasedProject>[project.ios, project.macos]) {
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart
index 95b526e..0a1b5b8 100644
--- a/packages/flutter_tools/lib/src/project.dart
+++ b/packages/flutter_tools/lib/src/project.dart
@@ -14,7 +14,7 @@
 import 'build_info.dart';
 import 'bundle.dart' as bundle;
 import 'cache.dart';
-import 'features.dart';
+import 'desktop.dart';
 import 'flutter_manifest.dart';
 import 'globals.dart';
 import 'ios/ios_workflow.dart';
@@ -22,6 +22,7 @@
 import 'ios/xcodeproj.dart' as xcode;
 import 'plugins.dart';
 import 'template.dart';
+import 'web/workflow.dart';
 
 FlutterProjectFactory get projectFactory => context.get<FlutterProjectFactory>() ?? const FlutterProjectFactory();
 
@@ -205,10 +206,10 @@
     }
     // TODO(stuartmorgan): Add checkProjects logic once a create workflow exists
     // for macOS. For now, always treat checkProjects as true for macOS.
-    if (featureFlags.isMacOSEnabled && macos.existsSync()) {
+    if (flutterDesktopEnabled && macos.existsSync()) {
       await macos.ensureReadyForPlatformSpecificTooling();
     }
-    if (featureFlags.isWebEnabled && web.existsSync()) {
+    if (flutterWebEnabled && web.existsSync()) {
       await web.ensureReadyForPlatformSpecificTooling();
     }
     await injectPlugins(this, checkProjects: checkProjects);
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index 2f56364..e9d9211 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -25,10 +25,10 @@
 import '../dart/pub.dart';
 import '../device.dart';
 import '../doctor.dart';
-import '../features.dart';
 import '../globals.dart';
 import '../project.dart';
 import '../reporting/usage.dart';
+import '../version.dart';
 import 'flutter_command_runner.dart';
 
 export '../cache.dart' show DevelopmentArtifact;
@@ -355,6 +355,11 @@
     }
   }
 
+  /// Whether this feature should not be usable on stable branches.
+  ///
+  /// Defaults to false, meaning it is usable.
+  bool get isExperimental => false;
+
   /// Additional usage values to be sent with the usage ping.
   Future<Map<String, String>> get usageValues async => const <String, String>{};
 
@@ -550,6 +555,12 @@
   @protected
   @mustCallSuper
   Future<void> validateCommand() async {
+    // If we're on a stable branch, then don't allow the usage of
+    // "experimental" features.
+    if (isExperimental && !FlutterVersion.instance.isMaster) {
+      throwToolExit('Experimental feature $name is not supported on stable branches');
+    }
+
     if (_requiresPubspecYaml && !PackageMap.isUsingCustomPackagesPath) {
       // Don't expect a pubspec.yaml file if the user passed in an explicit .packages file path.
       if (!fs.isFileSync('pubspec.yaml')) {
@@ -642,17 +653,17 @@
     case TargetPlatform.ios:
       return DevelopmentArtifact.iOS;
     case TargetPlatform.darwin_x64:
-      if (featureFlags.isMacOSEnabled) {
+      if (FlutterVersion.instance.isMaster) {
         return DevelopmentArtifact.macOS;
       }
       return null;
     case TargetPlatform.windows_x64:
-      if (featureFlags.isWindowsEnabled) {
+      if (!FlutterVersion.instance.isMaster) {
         return DevelopmentArtifact.windows;
       }
       return null;
     case TargetPlatform.linux_x64:
-      if (featureFlags.isLinuxEnabled) {
+      if (!FlutterVersion.instance.isMaster) {
         return DevelopmentArtifact.linux;
       }
       return null;
diff --git a/packages/flutter_tools/lib/src/web/web_device.dart b/packages/flutter_tools/lib/src/web/web_device.dart
index 689eb72..ecd8f41 100644
--- a/packages/flutter_tools/lib/src/web/web_device.dart
+++ b/packages/flutter_tools/lib/src/web/web_device.dart
@@ -11,7 +11,6 @@
 import '../base/process_manager.dart';
 import '../build_info.dart';
 import '../device.dart';
-import '../features.dart';
 import '../project.dart';
 import 'chrome.dart';
 import 'workflow.dart';
@@ -75,7 +74,7 @@
   Future<String> get emulatorId async => null;
 
   @override
-  bool isSupported() =>  featureFlags.isWebEnabled && canFindChrome();
+  bool isSupported() => flutterWebEnabled && canFindChrome();
 
   @override
   String get name => 'Chrome';
@@ -152,7 +151,7 @@
   final ChromeDevice _webDevice = ChromeDevice();
 
   @override
-  bool get canListAnything => featureFlags.isWebEnabled;
+  bool get canListAnything => flutterWebEnabled;
 
   @override
   Future<List<Device>> pollingGetDevices() async {
@@ -162,7 +161,7 @@
   }
 
   @override
-  bool get supportsPlatform =>  featureFlags.isWebEnabled;
+  bool get supportsPlatform => flutterWebEnabled;
 }
 
 @visibleForTesting
diff --git a/packages/flutter_tools/lib/src/web/workflow.dart b/packages/flutter_tools/lib/src/web/workflow.dart
index 1f388ca..925ec3d 100644
--- a/packages/flutter_tools/lib/src/web/workflow.dart
+++ b/packages/flutter_tools/lib/src/web/workflow.dart
@@ -2,13 +2,33 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'package:meta/meta.dart';
+
+import '../base/common.dart';
 import '../base/context.dart';
 import '../base/platform.dart';
 import '../base/process_manager.dart';
 import '../doctor.dart';
-import '../features.dart';
+import '../version.dart';
 import 'chrome.dart';
 
+@visibleForTesting
+bool debugDisableWeb = false;
+
+/// Only launch or display web devices if `FLUTTER_WEB`
+/// environment variable is set to true from the daemon.
+bool get flutterWebEnabled {
+  if (debugDisableWeb) {
+    return false;
+  }
+  if (isRunningFromDaemon) {
+    final bool platformEnabled = platform
+        .environment['FLUTTER_WEB']?.toLowerCase() == 'true';
+    return platformEnabled && FlutterVersion.instance.isMaster;
+  }
+  return FlutterVersion.instance.isMaster;
+}
+
 /// The  web workflow instance.
 WebWorkflow get webWorkflow => context.get<WebWorkflow>();
 
@@ -16,13 +36,13 @@
   const WebWorkflow();
 
   @override
-  bool get appliesToHostPlatform => featureFlags.isWebEnabled && (platform.isWindows || platform.isMacOS || platform.isLinux);
+  bool get appliesToHostPlatform => flutterWebEnabled && (platform.isWindows || platform.isMacOS || platform.isLinux);
 
   @override
-  bool get canLaunchDevices => featureFlags.isWebEnabled && canFindChrome();
+  bool get canLaunchDevices => flutterWebEnabled && canFindChrome();
 
   @override
-  bool get canListDevices => featureFlags.isWebEnabled && canFindChrome();
+  bool get canListDevices => flutterWebEnabled && canFindChrome();
 
   @override
   bool get canListEmulators => false;
diff --git a/packages/flutter_tools/lib/src/windows/windows_workflow.dart b/packages/flutter_tools/lib/src/windows/windows_workflow.dart
index 2a423bb..8d8e34e 100644
--- a/packages/flutter_tools/lib/src/windows/windows_workflow.dart
+++ b/packages/flutter_tools/lib/src/windows/windows_workflow.dart
@@ -4,8 +4,8 @@
 
 import '../base/context.dart';
 import '../base/platform.dart';
+import '../desktop.dart';
 import '../doctor.dart';
-import '../features.dart';
 
 /// The [WindowsWorkflow] instance.
 WindowsWorkflow get windowsWorkflow => context.get<WindowsWorkflow>();
@@ -18,13 +18,13 @@
   const WindowsWorkflow();
 
   @override
-  bool get appliesToHostPlatform => platform.isWindows && featureFlags.isWindowsEnabled;
+  bool get appliesToHostPlatform => platform.isWindows;
 
   @override
-  bool get canLaunchDevices => platform.isWindows && featureFlags.isWindowsEnabled;
+  bool get canLaunchDevices => platform.isWindows && flutterDesktopEnabled;
 
   @override
-  bool get canListDevices => platform.isWindows && featureFlags.isWindowsEnabled;
+  bool get canListDevices => platform.isWindows && flutterDesktopEnabled;
 
   @override
   bool get canListEmulators => false;