Propose fallback version when Flutter is crashing a lot.
diff --git a/packages/devtools_app/lib/main.dart b/packages/devtools_app/lib/main.dart
index f72fdc1..98d97e5 100644
--- a/packages/devtools_app/lib/main.dart
+++ b/packages/devtools_app/lib/main.dart
@@ -6,9 +6,49 @@
 
 import 'src/app.dart';
 import 'src/config_specific/framework_initialize/framework_initialize.dart';
+import 'src/config_specific/load_fallback_app/load_fallback_app.dart';
 import 'src/preferences.dart';
 
 void main() async {
+  final defaultOnError = FlutterError.onError;
+  var numErrors = 0;
+  var fallbackVersionDialogShown = false;
+
+  // The SKIA/CanvasKit version of Flutter Web is newer than other platforms
+  // and depends on WebGL so we need to detect whether it is behaving badly
+  // to give users the option to reopen DevTools using a fallback slower version
+  // that may be more stable.
+  // TODO(jacobr): remove this error listening code once
+  // https://github.com/flutter/devtools/issues/2125 is fixed.
+  if (const bool.fromEnvironment('FLUTTER_WEB_USE_SKIA')) {
+    FlutterError.onError = (FlutterErrorDetails details) {
+      if (defaultOnError != null) {
+        defaultOnError(details);
+      }
+      final bool overflowError = details.toString().contains('overflowed');
+
+      if (overflowError) {
+        // Overflow errors do not indicate a CanvasKit specific problem and
+        // occur frequently in debug builds of apps so we filter them out to
+        // avoid distraction. We could alternately suppress this code completely
+        // in debug builds but it is risky to add code that runs in release
+        // builds but not debug builds.
+        return;
+      }
+      // TODO(jacobr): detect SKIA specific errors and warn on the first SKIA
+      // specific error rather than waiting for multiple errors.
+      numErrors++;
+      if (numErrors >= 20 && !fallbackVersionDialogShown) {
+        fallbackVersionDialogShown = true;
+        promptToLoadFallbackApp(
+          "The Flutter web backend ('CanvasKit') has encountered multiple errors.\n"
+          'Would you like to open a fallback version of DevTools that is '
+          'slower but does not depend on WebGL?',
+        );
+      }
+    };
+  }
+
   final preferences = PreferencesController();
   // Wait for preferences to load before rendering the app to avoid a flash of
   // content with the incorrect theme.
diff --git a/packages/devtools_app/lib/src/config_specific/load_fallback_app/_load_fallback_app_stub.dart b/packages/devtools_app/lib/src/config_specific/load_fallback_app/_load_fallback_app_stub.dart
new file mode 100644
index 0000000..84113d3
--- /dev/null
+++ b/packages/devtools_app/lib/src/config_specific/load_fallback_app/_load_fallback_app_stub.dart
@@ -0,0 +1,11 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+bool promptToLoadFallbackApp(String message) {
+  print(
+    'Loading a fallback version of DevTools is not supported on this platform.\n'
+    '$message',
+  );
+  return false;
+}
diff --git a/packages/devtools_app/lib/src/config_specific/load_fallback_app/_load_fallback_app_web.dart b/packages/devtools_app/lib/src/config_specific/load_fallback_app/_load_fallback_app_web.dart
new file mode 100644
index 0000000..8874de1
--- /dev/null
+++ b/packages/devtools_app/lib/src/config_specific/load_fallback_app/_load_fallback_app_web.dart
@@ -0,0 +1,15 @@
+// Copyright 2020 The Chromium 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 'dart:html' as html;
+
+bool promptToLoadFallbackApp(String message) {
+  final loadFallback = html.window.confirm(message);
+  if (loadFallback) {
+    var uri = Uri.parse(html.window.location.toString());
+    uri = uri.replace(path: 'index_fallback.html');
+    html.window.location.replace(uri.toString());
+  }
+  return loadFallback;
+}
diff --git a/packages/devtools_app/lib/src/config_specific/load_fallback_app/load_fallback_app.dart b/packages/devtools_app/lib/src/config_specific/load_fallback_app/load_fallback_app.dart
new file mode 100644
index 0000000..fc75edf
--- /dev/null
+++ b/packages/devtools_app/lib/src/config_specific/load_fallback_app/load_fallback_app.dart
@@ -0,0 +1,15 @@
+// Copyright 2020 The Chromium 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 '_load_fallback_app_stub.dart'
+    if (dart.library.html) '_load_fallback_app_web.dart' as implementation;
+
+/// Load a fallback version of the app if the user agrees.
+///
+/// The dialog to load a fallback version of the app must use platform specific
+/// features as the regular app is likely broken enough that it cannot render
+/// a dialog.
+bool promptToLoadFallbackApp(String message) {
+  return implementation.promptToLoadFallbackApp(message);
+}
diff --git a/packages/devtools_app/web/index_fallback.html b/packages/devtools_app/web/index_fallback.html
new file mode 100644
index 0000000..e8f26a3
--- /dev/null
+++ b/packages/devtools_app/web/index_fallback.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+
+<!--
+  Copyright 2018 The Chromium Authors. All rights reserved.
+  Use of this source code is governed by a BSD-style license that can be
+  found in the LICENSE file.
+-->
+
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+    <title>Dart DevTools</title>
+    <link href="favicon.png" rel="icon" sizes="64x64">
+
+    <script type="text/javascript">
+        function supportsES6Classes() {
+            "use strict";
+            try { eval("class Foo {}"); }
+            catch (e) { return false; }
+            return true;
+        }
+
+        if (!supportsES6Classes()) {
+            window.location.href = '/unsupported-browser.html';
+        }
+
+        // Handle URLs that pass all variables directly on a querystring without
+        // the fragment (for ex. VS Code while it has some encoding bugs preventing
+        // building the correct URLs using fragments
+        // https://github.com/microsoft/vscode/issues/85930).
+        if (window.location.search && window.location.search.length > 1) {
+          // Ensure each component is encoded, because if the URI contains / slashes
+          // Flutter will split on them and try to push multiple routes.
+          const params = new URLSearchParams(unescape(window.location.search));
+          params.forEach(function(v, k) { params.set(k, encodeURIComponent(v)) });
+          window.location.replace(window.location.origin + '/#/?' + params.toString());
+        }
+    </script>
+</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_fallback.dart.js" type="application/javascript"></script>
+</body>
+</html>
diff --git a/tool/build_release.sh b/tool/build_release.sh
index 880e8db..99e3236 100755
--- a/tool/build_release.sh
+++ b/tool/build_release.sh
@@ -15,6 +15,9 @@
 # Build a profile build rather than a release build to avoid minification
 # as code size doesn't matter very much for us as minification makes some
 # crashes harder to debug. For example, https://github.com/flutter/devtools/issues/2125
+
+flutter build web --profile --dart-define=FLUTTER_WEB_USE_EXPERIMENTAL_CANVAS_TEXT=true --no-tree-shake-icons
+cp build/web/main.dart.js build/web/main_fallback.dart.js
 flutter build web --profile --dart-define=FLUTTER_WEB_USE_SKIA=true --no-tree-shake-icons
 mv build/web ../devtools/build