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