From 3a65990222d9121c122e0322cd3e72ef000ba5d2 Mon Sep 17 00:00:00 2001 From: Kenzie Schmoll Date: Wed, 28 Aug 2024 15:34:32 -0700 Subject: [PATCH 01/24] Add wasm opt-in setting --- .../lib/src/framework/settings_dialog.dart | 22 +++- .../enhance_tracing/enhance_tracing.dart | 2 +- .../service/service_extension_widgets.dart | 2 +- .../lib/src/shared/analytics/constants.dart | 1 + .../lib/src/shared/common_widgets.dart | 10 +- .../src/shared/preferences/preferences.dart | 120 +++++++++++++++--- .../lib/src/shared/query_parameters.dart | 7 + .../devtools_app/web/flutter_bootstrap.js | 3 + packages/devtools_app_shared/CHANGELOG.md | 3 + .../lib/src/utils/url/_url_stub.dart | 6 + .../lib/src/utils/url/_url_web.dart | 20 +++ packages/devtools_app_shared/pubspec.yaml | 2 +- packages/devtools_extensions/CHANGELOG.md | 3 + .../lib/src/template/extension_manager.dart | 26 +--- packages/devtools_extensions/pubspec.yaml | 4 +- 15 files changed, 183 insertions(+), 48 deletions(-) diff --git a/packages/devtools_app/lib/src/framework/settings_dialog.dart b/packages/devtools_app/lib/src/framework/settings_dialog.dart index f0c874b9808..817ed9d9bf9 100644 --- a/packages/devtools_app/lib/src/framework/settings_dialog.dart +++ b/packages/devtools_app/lib/src/framework/settings_dialog.dart @@ -37,6 +37,7 @@ class SettingsDialog extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); final analyticsController = Provider.of(context); return DevToolsDialog( title: const DialogTitleText('Settings'), @@ -50,6 +51,7 @@ class SettingsDialog extends StatelessWidget { title: 'Use a dark theme', notifier: preferences.darkModeEnabled, onChanged: preferences.toggleDarkModeTheme, + gaScreen: gac.settingsDialog, gaItem: gac.darkTheme, ), ), @@ -61,6 +63,7 @@ class SettingsDialog extends StatelessWidget { onChanged: (enable) => unawaited( analyticsController.toggleAnalyticsEnabled(enable), ), + gaScreen: gac.settingsDialog, gaItem: gac.analytics, ), ), @@ -69,10 +72,26 @@ class SettingsDialog extends StatelessWidget { title: 'Enable VM developer mode', notifier: preferences.vmDeveloperModeEnabled, onChanged: preferences.toggleVmDeveloperMode, + gaScreen: gac.settingsDialog, gaItem: gac.vmDeveloperMode, ), ), - const PaddedDivider(), + const SizedBox(height: largeSpacing), + ...dialogSubHeader(theme, 'Experimental Features'), + Flexible( + child: CheckboxSetting( + title: 'Enable WASM build', + description: + 'This will trigger a reload of the page to load DevTools ' + 'with the skwasm renderer.', + notifier: preferences.wasmMode, + onChanged: preferences.toggleWasmMode, + gaScreen: gac.settingsDialog, + gaItem: gac.wasmMode, + ), + ), + const SizedBox(height: largeSpacing), + ...dialogSubHeader(theme, 'Troubleshooting'), const _VerboseLoggingSetting(), ], ), @@ -99,6 +118,7 @@ class _VerboseLoggingSetting extends StatelessWidget { title: 'Enable verbose logging', notifier: preferences.verboseLoggingEnabled, onChanged: (enable) => preferences.toggleVerboseLogging(enable), + gaScreen: gac.settingsDialog, gaItem: gac.verboseLogging, ), ), diff --git a/packages/devtools_app/lib/src/screens/performance/panes/controls/enhance_tracing/enhance_tracing.dart b/packages/devtools_app/lib/src/screens/performance/panes/controls/enhance_tracing/enhance_tracing.dart index cf2b9927ce8..83478a55e1b 100644 --- a/packages/devtools_app/lib/src/screens/performance/panes/controls/enhance_tracing/enhance_tracing.dart +++ b/packages/devtools_app/lib/src/screens/performance/panes/controls/enhance_tracing/enhance_tracing.dart @@ -280,7 +280,7 @@ class TraceWidgetBuildsCheckbox extends StatelessWidget { tooltip: extension.tooltip, onChanged: _checkboxChanged, enabled: enabled, - gaScreenName: extension.gaScreenName, + gaScreen: extension.gaScreenName, gaItem: extension.gaItem, ), ), diff --git a/packages/devtools_app/lib/src/service/service_extension_widgets.dart b/packages/devtools_app/lib/src/service/service_extension_widgets.dart index 0c8419a127d..8de126c55ad 100644 --- a/packages/devtools_app/lib/src/service/service_extension_widgets.dart +++ b/packages/devtools_app/lib/src/service/service_extension_widgets.dart @@ -588,7 +588,7 @@ class _ServiceExtensionCheckboxState extends State tooltip: widget.serviceExtension.tooltip, onChanged: _onChanged, enabled: available, - gaScreenName: widget.serviceExtension.gaScreenName, + gaScreen: widget.serviceExtension.gaScreenName, gaItem: widget.serviceExtension.gaItem, ), ), diff --git a/packages/devtools_app/lib/src/shared/analytics/constants.dart b/packages/devtools_app/lib/src/shared/analytics/constants.dart index b33dc8fcdd5..6c293359781 100644 --- a/packages/devtools_app/lib/src/shared/analytics/constants.dart +++ b/packages/devtools_app/lib/src/shared/analytics/constants.dart @@ -114,6 +114,7 @@ const settingsDialog = 'settings'; const darkTheme = 'darkTheme'; const analytics = 'analytics'; const vmDeveloperMode = 'vmDeveloperMode'; +const wasmMode = 'wasmMode'; const verboseLogging = 'verboseLogging'; const inspectorHoverEvalMode = 'inspectorHoverEvalMode'; const clearLogs = 'clearLogs'; diff --git a/packages/devtools_app/lib/src/shared/common_widgets.dart b/packages/devtools_app/lib/src/shared/common_widgets.dart index 87da35e202d..a24d6f2e49b 100644 --- a/packages/devtools_app/lib/src/shared/common_widgets.dart +++ b/packages/devtools_app/lib/src/shared/common_widgets.dart @@ -1521,7 +1521,7 @@ class CheckboxSetting extends StatelessWidget { this.tooltip, this.onChanged, this.enabled = true, - this.gaScreenName, + this.gaScreen, this.gaItem, this.checkboxKey, }); @@ -1539,7 +1539,7 @@ class CheckboxSetting extends StatelessWidget { /// Whether this checkbox setting should be enabled for interaction. final bool enabled; - final String? gaScreenName; + final String? gaScreen; final String? gaItem; @@ -1554,10 +1554,10 @@ class CheckboxSetting extends StatelessWidget { NotifierCheckbox( notifier: notifier, onChanged: (bool? value) { - final gaScreenName = this.gaScreenName; + final gaScreen = this.gaScreen; final gaItem = this.gaItem; - if (gaScreenName != null && gaItem != null) { - ga.select(gaScreenName, gaItem); + if (gaScreen != null && gaItem != null) { + ga.select(gaScreen, '$gaItem-$value'); } final onChanged = this.onChanged; if (onChanged != null) { diff --git a/packages/devtools_app/lib/src/shared/preferences/preferences.dart b/packages/devtools_app/lib/src/shared/preferences/preferences.dart index 4e91a17f0e3..36128ed23d2 100644 --- a/packages/devtools_app/lib/src/shared/preferences/preferences.dart +++ b/packages/devtools_app/lib/src/shared/preferences/preferences.dart @@ -17,6 +17,7 @@ import '../config_specific/logger/logger_helpers.dart'; import '../constants.dart'; import '../diagnostics/inspector_service.dart'; import '../globals.dart'; +import '../query_parameters.dart'; import '../utils.dart'; part '_extension_preferences.dart'; @@ -28,6 +29,32 @@ part '_performance_preferences.dart'; const _thirdPartyPathSegment = 'third_party'; +/// DevTools preferences for experimental features. +enum _ExperimentPreferences { + wasm; + + String get storageKey => '$storagePrefix.$name'; + + static const storagePrefix = 'experiment'; +} + +/// DevTools preferences for UI-related settings. +enum _UiPreferences { + darkMode, + vmDeveloperMode; + + String get storageKey => '$storagePrefix.$name'; + + static const storagePrefix = 'ui'; +} + +/// DevTools preferences for general settings. +/// +/// These values are not stored in the DevTools storage file with a prefix. +enum _GeneralPreferences { + verboseLogging, +} + /// A controller for global application preferences. class PreferencesController extends DisposableController with AutoDisposeControllerMixin { @@ -43,9 +70,12 @@ class PreferencesController extends DisposableController final vmDeveloperModeEnabled = ValueNotifier(false); + /// Whether DevTools should be rendered with the skwasm renderer instead of + /// canvaskit. + final wasmMode = ValueNotifier(false); + final verboseLoggingEnabled = ValueNotifier(Logger.root.level == verboseLoggingLevel); - static const _verboseLoggingStorageId = 'verboseLogging'; // TODO(https://github.com/flutter/devtools/issues/7860): Clean-up after // Inspector V2 has been released. @@ -69,44 +99,95 @@ class PreferencesController extends DisposableController Future init() async { // Get the current values and listen for and write back changes. - final darkModeValue = await storage.getValue('ui.darkMode'); + await _initDarkMode(); + await _initVmDeveloperMode(); + await _initWasmEnabled(); + await _initVerboseLogging(); + + await inspector.init(); + await memory.init(); + await logging.init(); + await performance.init(); + await devToolsExtensions.init(); + + setGlobal(PreferencesController, this); + } + + Future _initDarkMode() async { + final darkModeValue = + await storage.getValue(_UiPreferences.darkMode.storageKey); final useDarkMode = (darkModeValue == null && useDarkThemeAsDefault) || darkModeValue == 'true'; ga.impression(gac.devToolsMain, gac.startingTheme(darkMode: useDarkMode)); toggleDarkModeTheme(useDarkMode); addAutoDisposeListener(darkModeEnabled, () { - storage.setValue('ui.darkMode', '${darkModeEnabled.value}'); + storage.setValue( + _UiPreferences.darkMode.storageKey, + '${darkModeEnabled.value}', + ); }); + } + Future _initVmDeveloperMode() async { final vmDeveloperModeValue = await boolValueFromStorage( - 'ui.vmDeveloperMode', + _UiPreferences.vmDeveloperMode.storageKey, defaultsTo: false, ); toggleVmDeveloperMode(vmDeveloperModeValue); addAutoDisposeListener(vmDeveloperModeEnabled, () { - storage.setValue('ui.vmDeveloperMode', '${vmDeveloperModeEnabled.value}'); + storage.setValue( + _UiPreferences.vmDeveloperMode.storageKey, + '${vmDeveloperModeEnabled.value}', + ); }); + } - await _initVerboseLogging(); + Future _initWasmEnabled() async { + // TODO(https://github.com/flutter/devtools/issues/7856): set the current + // value based on whether DevTools is actually loaded with wasm. + wasmMode.value = false; + + // It is important that this listener is added before we set the initial + // state of the wasm mode setting below. This is because the query parameter + // for wasm may need to be updated based on the value of the preference in + // the storage file, which we take into account when we call + // [toggleWasmMode] at the end of this method. + addAutoDisposeListener(wasmMode, () async { + final enabled = wasmMode.value; + await storage.setValue( + _ExperimentPreferences.wasm.storageKey, + '$enabled', + ); - await inspector.init(); - await memory.init(); - await logging.init(); - await performance.init(); - await devToolsExtensions.init(); + // Update the wasm mode query parameter if it does not match the value of + // the setting. + final wasmEnabledFromQueryParams = DevToolsQueryParams.load().useWasm; + if (wasmEnabledFromQueryParams != enabled) { + updateQueryParameter( + DevToolsQueryParams.useWasmKey, + enabled ? 'skwasm' : null, + reload: true, + ); + } + }); - setGlobal(PreferencesController, this); + final wasmEnabledFromStorage = await boolValueFromStorage( + _ExperimentPreferences.wasm.storageKey, + defaultsTo: false, + ); + final wasmEnabledFromQueryParams = DevToolsQueryParams.load().useWasm; + toggleWasmMode(wasmEnabledFromStorage || wasmEnabledFromQueryParams); } Future _initVerboseLogging() async { final verboseLoggingEnabledValue = await boolValueFromStorage( - _verboseLoggingStorageId, + _GeneralPreferences.verboseLogging.name, defaultsTo: false, ); toggleVerboseLogging(verboseLoggingEnabledValue); addAutoDisposeListener(verboseLoggingEnabled, () { storage.setValue( - 'verboseLogging', + _GeneralPreferences.verboseLogging.name, verboseLoggingEnabled.value.toString(), ); }); @@ -122,14 +203,14 @@ class PreferencesController extends DisposableController super.dispose(); } - /// Change the value for the dark mode setting. + /// Change the value of the dark mode setting. void toggleDarkModeTheme(bool? useDarkMode) { if (useDarkMode != null) { darkModeEnabled.value = useDarkMode; } } - /// Change the value for the VM developer mode setting. + /// Change the value of the VM developer mode setting. void toggleVmDeveloperMode(bool? enableVmDeveloperMode) { if (enableVmDeveloperMode != null) { vmDeveloperModeEnabled.value = enableVmDeveloperMode; @@ -137,6 +218,13 @@ class PreferencesController extends DisposableController } } + /// Change the value of the wasm mode setting. + void toggleWasmMode(bool? enable) { + if (enable != null) { + wasmMode.value = enable; + } + } + void toggleVerboseLogging(bool? enableVerboseLogging) { if (enableVerboseLogging != null) { verboseLoggingEnabled.value = enableVerboseLogging; diff --git a/packages/devtools_app/lib/src/shared/query_parameters.dart b/packages/devtools_app/lib/src/shared/query_parameters.dart index 67d2975362e..7c50b8bf2c0 100644 --- a/packages/devtools_app/lib/src/shared/query_parameters.dart +++ b/packages/devtools_app/lib/src/shared/query_parameters.dart @@ -39,6 +39,10 @@ extension type DevToolsQueryParams(Map params) { // Keys for theming values that an IDE may pass in the embedded DevTools URI. IdeThemeQueryParams get ideThemeParams => IdeThemeQueryParams(params); + /// Whether DevTools should be loaded using the skwasm renderer instead of + /// canvaskit. + bool get useWasm => params[useWasmKey] == 'skwasm'; + static const vmServiceUriKey = 'uri'; static const hideScreensKey = 'hide'; static const hideExtensionsValue = 'extensions'; @@ -46,6 +50,9 @@ extension type DevToolsQueryParams(Map params) { static const offlineScreenIdKey = 'screen'; static const inspectorRefKey = 'inspectorRef'; + // TODO(kenz): consider using `?wasm=true` instead of `?renderer=skwasm`. + static const useWasmKey = 'renderer'; + // TODO(kenz): remove legacy value in May of 2025 when all IDEs are not using // these and 12 months have passed to allow users ample upgrade time. String? get legacyPage => params[legacyPageKey]; diff --git a/packages/devtools_app/web/flutter_bootstrap.js b/packages/devtools_app/web/flutter_bootstrap.js index bcb75f5cb1a..607db456299 100644 --- a/packages/devtools_app/web/flutter_bootstrap.js +++ b/packages/devtools_app/web/flutter_bootstrap.js @@ -23,6 +23,9 @@ function unregisterDevToolsServiceWorker() { // Bootstrap app for 3P environments: function bootstrapAppFor3P() { + const searchParams = new URLSearchParams(window.location.search); + const renderer = searchParams.get('renderer'); + const userConfig = renderer ? {'renderer': renderer} : {}; _flutter.loader.load({ serviceWorkerSettings: { serviceWorkerVersion: {{flutter_service_worker_version}}, diff --git a/packages/devtools_app_shared/CHANGELOG.md b/packages/devtools_app_shared/CHANGELOG.md index eb966dd80b0..15a26f97d27 100644 --- a/packages/devtools_app_shared/CHANGELOG.md +++ b/packages/devtools_app_shared/CHANGELOG.md @@ -1,3 +1,6 @@ +## 0.2.4 +* Add `updateQueryParameter` utility method. + ## 0.2.3 * Bump `web` dependency to `^1.0.0` * Bump `pointer_interceptor` dependency to `^0.10.1+1` diff --git a/packages/devtools_app_shared/lib/src/utils/url/_url_stub.dart b/packages/devtools_app_shared/lib/src/utils/url/_url_stub.dart index 9103800db01..2589b428a62 100644 --- a/packages/devtools_app_shared/lib/src/utils/url/_url_stub.dart +++ b/packages/devtools_app_shared/lib/src/utils/url/_url_stub.dart @@ -15,3 +15,9 @@ String? getWebUrl() => null; // Unused parameter lint doesn't make sense for stub files. // ignore: avoid-unused-parameters void webRedirect(String url) {} + +/// Updates the query parameter with [key] to the new [value], and optionally +/// reloads the page when [reload] is true. +/// +/// No-op for non-web platforms. +void updateQueryParameter(String key, String? value, {bool reload = false}) {} diff --git a/packages/devtools_app_shared/lib/src/utils/url/_url_web.dart b/packages/devtools_app_shared/lib/src/utils/url/_url_web.dart index def617b3d59..ab1f6acb375 100644 --- a/packages/devtools_app_shared/lib/src/utils/url/_url_web.dart +++ b/packages/devtools_app_shared/lib/src/utils/url/_url_web.dart @@ -15,3 +15,23 @@ String? getWebUrl() => window.location.toString(); void webRedirect(String url) { window.location.replace(url); } + +void updateQueryParameter(String key, String? value, {bool reload = false}) { + final newQueryParams = Map.of(loadQueryParams()); + if (value == null) { + newQueryParams.remove(key); + } else { + newQueryParams[key] = value; + } + final newUri = Uri.parse(window.location.toString()) + .replace(queryParameters: newQueryParams); + window.history.replaceState( + window.history.state, + '', + newUri.toString(), + ); + + if (reload) { + window.location.reload(); + } +} diff --git a/packages/devtools_app_shared/pubspec.yaml b/packages/devtools_app_shared/pubspec.yaml index f1a8bd26421..a3c31dafd43 100644 --- a/packages/devtools_app_shared/pubspec.yaml +++ b/packages/devtools_app_shared/pubspec.yaml @@ -1,6 +1,6 @@ name: devtools_app_shared description: Package of Dart & Flutter structures shared between devtools_app and devtools extensions. -version: 0.2.3 +version: 0.2.4 repository: https://github.com/flutter/devtools/tree/master/packages/devtools_app_shared environment: diff --git a/packages/devtools_extensions/CHANGELOG.md b/packages/devtools_extensions/CHANGELOG.md index 2bbd77ac3f0..e55e64e3cb4 100644 --- a/packages/devtools_extensions/CHANGELOG.md +++ b/packages/devtools_extensions/CHANGELOG.md @@ -1,3 +1,6 @@ +## 0.3.0-dev.1 +* Bump `devtools_app_shared` dependency to `0.2.4`. + ## 0.3.0-dev.0 * Add `ExtensionManager.copyToClipboard` method. * Add `DevToolsExtensionEventType.copyToClipboard` enum type. diff --git a/packages/devtools_extensions/lib/src/template/extension_manager.dart b/packages/devtools_extensions/lib/src/template/extension_manager.dart index 401a84d8edb..230b896639e 100644 --- a/packages/devtools_extensions/lib/src/template/extension_manager.dart +++ b/packages/devtools_extensions/lib/src/template/extension_manager.dart @@ -161,7 +161,7 @@ class ExtensionManager { await serviceManager.manuallyDisconnect(); } if (loadQueryParams().containsKey(_vmServiceQueryParameter)) { - _updateQueryParameter(_vmServiceQueryParameter, null); + updateQueryParameter(_vmServiceQueryParameter, null); } return; } @@ -182,7 +182,7 @@ class ExtensionManager { vmService, onClosed: finishedCompleter.future, ); - _updateQueryParameter( + updateQueryParameter( _vmServiceQueryParameter, serviceManager.serviceUri!, ); @@ -202,14 +202,14 @@ class ExtensionManager { await dtdManager.disconnect(); } if (loadQueryParams().containsKey(_dtdQueryParameter)) { - _updateQueryParameter(_dtdQueryParameter, null); + updateQueryParameter(_dtdQueryParameter, null); } return; } try { await dtdManager.connect(Uri.parse(dtdUri)); - _updateQueryParameter( + updateQueryParameter( _dtdQueryParameter, dtdManager.uri.toString(), ); @@ -228,7 +228,7 @@ class ExtensionManager { // Use a post frame callback so that we do not try to update this while a // build is in progress. WidgetsBinding.instance.addPostFrameCallback((_) { - _updateQueryParameter( + updateQueryParameter( 'theme', useDarkTheme ? ExtensionEventParameters.themeValueDark @@ -316,20 +316,4 @@ class ExtensionManager { ), ); } - - void _updateQueryParameter(String key, String? value) { - final newQueryParams = Map.of(loadQueryParams()); - if (value == null) { - newQueryParams.remove(key); - } else { - newQueryParams[key] = value; - } - final newUri = Uri.parse(window.location.toString()) - .replace(queryParameters: newQueryParams); - window.history.replaceState( - window.history.state, - '', - newUri.toString(), - ); - } } diff --git a/packages/devtools_extensions/pubspec.yaml b/packages/devtools_extensions/pubspec.yaml index acba38656c0..85a8bb14db4 100644 --- a/packages/devtools_extensions/pubspec.yaml +++ b/packages/devtools_extensions/pubspec.yaml @@ -1,6 +1,6 @@ name: devtools_extensions description: A package for building and supporting extensions for Dart DevTools. -version: 0.3.0-dev.0 +version: 0.3.0-dev.1 repository: https://github.com/flutter/devtools/tree/master/packages/devtools_extensions @@ -14,7 +14,7 @@ executables: dependencies: args: ^2.4.2 devtools_shared: ^10.0.2 - devtools_app_shared: ^0.2.3 + devtools_app_shared: ^0.2.4 flutter: sdk: flutter io: ^1.0.4 From 407eea6a37cfa100a77ca0d94b2f90615572fc75 Mon Sep 17 00:00:00 2001 From: Kenzie Schmoll Date: Wed, 28 Aug 2024 16:51:48 -0700 Subject: [PATCH 02/24] fixes --- .../lib/src/framework/settings_dialog.dart | 8 ++-- .../lib/src/shared/analytics/constants.dart | 2 +- .../src/shared/preferences/preferences.dart | 42 +++++++++---------- .../lib/src/shared/query_parameters.dart | 6 +-- .../devtools_app/web/flutter_bootstrap.js | 6 +-- 5 files changed, 31 insertions(+), 33 deletions(-) diff --git a/packages/devtools_app/lib/src/framework/settings_dialog.dart b/packages/devtools_app/lib/src/framework/settings_dialog.dart index 817ed9d9bf9..1e59140e4a3 100644 --- a/packages/devtools_app/lib/src/framework/settings_dialog.dart +++ b/packages/devtools_app/lib/src/framework/settings_dialog.dart @@ -80,14 +80,14 @@ class SettingsDialog extends StatelessWidget { ...dialogSubHeader(theme, 'Experimental Features'), Flexible( child: CheckboxSetting( - title: 'Enable WASM build', + title: 'Enable Skwasm renderer', description: 'This will trigger a reload of the page to load DevTools ' 'with the skwasm renderer.', - notifier: preferences.wasmMode, - onChanged: preferences.toggleWasmMode, + notifier: preferences.skwasmEnabled, + onChanged: preferences.toggleSkasmEnabled, gaScreen: gac.settingsDialog, - gaItem: gac.wasmMode, + gaItem: gac.skwasm, ), ), const SizedBox(height: largeSpacing), diff --git a/packages/devtools_app/lib/src/shared/analytics/constants.dart b/packages/devtools_app/lib/src/shared/analytics/constants.dart index 6c293359781..92a221028fa 100644 --- a/packages/devtools_app/lib/src/shared/analytics/constants.dart +++ b/packages/devtools_app/lib/src/shared/analytics/constants.dart @@ -114,7 +114,7 @@ const settingsDialog = 'settings'; const darkTheme = 'darkTheme'; const analytics = 'analytics'; const vmDeveloperMode = 'vmDeveloperMode'; -const wasmMode = 'wasmMode'; +const skwasm = 'skwasm'; const verboseLogging = 'verboseLogging'; const inspectorHoverEvalMode = 'inspectorHoverEvalMode'; const clearLogs = 'clearLogs'; diff --git a/packages/devtools_app/lib/src/shared/preferences/preferences.dart b/packages/devtools_app/lib/src/shared/preferences/preferences.dart index 36128ed23d2..4aca41a3ff4 100644 --- a/packages/devtools_app/lib/src/shared/preferences/preferences.dart +++ b/packages/devtools_app/lib/src/shared/preferences/preferences.dart @@ -31,7 +31,7 @@ const _thirdPartyPathSegment = 'third_party'; /// DevTools preferences for experimental features. enum _ExperimentPreferences { - wasm; + skwasm; String get storageKey => '$storagePrefix.$name'; @@ -72,7 +72,7 @@ class PreferencesController extends DisposableController /// Whether DevTools should be rendered with the skwasm renderer instead of /// canvaskit. - final wasmMode = ValueNotifier(false); + final skwasmEnabled = ValueNotifier(false); final verboseLoggingEnabled = ValueNotifier(Logger.root.level == verboseLoggingLevel); @@ -144,39 +144,39 @@ class PreferencesController extends DisposableController Future _initWasmEnabled() async { // TODO(https://github.com/flutter/devtools/issues/7856): set the current - // value based on whether DevTools is actually loaded with wasm. - wasmMode.value = false; + // value based on whether DevTools is actually loaded with skwasm. + skwasmEnabled.value = false; // It is important that this listener is added before we set the initial - // state of the wasm mode setting below. This is because the query parameter - // for wasm may need to be updated based on the value of the preference in - // the storage file, which we take into account when we call - // [toggleWasmMode] at the end of this method. - addAutoDisposeListener(wasmMode, () async { - final enabled = wasmMode.value; + // state of the skwasm mode setting below. This is because the query + // parameter for skwasm may need to be updated based on the value of the + // preference in the storage file, which we take into account when we call + // [toggleSkasmEnabled] at the end of this method. + addAutoDisposeListener(skwasmEnabled, () async { + final enabled = skwasmEnabled.value; await storage.setValue( - _ExperimentPreferences.wasm.storageKey, + _ExperimentPreferences.skwasm.storageKey, '$enabled', ); // Update the wasm mode query parameter if it does not match the value of // the setting. - final wasmEnabledFromQueryParams = DevToolsQueryParams.load().useWasm; - if (wasmEnabledFromQueryParams != enabled) { + final skwasmEnabledFromQueryParams = DevToolsQueryParams.load().useSkwasm; + if (skwasmEnabledFromQueryParams != enabled) { updateQueryParameter( - DevToolsQueryParams.useWasmKey, - enabled ? 'skwasm' : null, + DevToolsQueryParams.skwasmKey, + '$enabled', reload: true, ); } }); - final wasmEnabledFromStorage = await boolValueFromStorage( - _ExperimentPreferences.wasm.storageKey, + final enabledFromStorage = await boolValueFromStorage( + _ExperimentPreferences.skwasm.storageKey, defaultsTo: false, ); - final wasmEnabledFromQueryParams = DevToolsQueryParams.load().useWasm; - toggleWasmMode(wasmEnabledFromStorage || wasmEnabledFromQueryParams); + final enabledFromQueryParams = DevToolsQueryParams.load().useSkwasm; + toggleSkasmEnabled(enabledFromStorage || enabledFromQueryParams); } Future _initVerboseLogging() async { @@ -219,9 +219,9 @@ class PreferencesController extends DisposableController } /// Change the value of the wasm mode setting. - void toggleWasmMode(bool? enable) { + void toggleSkasmEnabled(bool? enable) { if (enable != null) { - wasmMode.value = enable; + skwasmEnabled.value = enable; } } diff --git a/packages/devtools_app/lib/src/shared/query_parameters.dart b/packages/devtools_app/lib/src/shared/query_parameters.dart index 7c50b8bf2c0..d3305b4fd31 100644 --- a/packages/devtools_app/lib/src/shared/query_parameters.dart +++ b/packages/devtools_app/lib/src/shared/query_parameters.dart @@ -41,7 +41,7 @@ extension type DevToolsQueryParams(Map params) { /// Whether DevTools should be loaded using the skwasm renderer instead of /// canvaskit. - bool get useWasm => params[useWasmKey] == 'skwasm'; + bool get useSkwasm => params[skwasmKey] == 'true'; static const vmServiceUriKey = 'uri'; static const hideScreensKey = 'hide'; @@ -49,9 +49,7 @@ extension type DevToolsQueryParams(Map params) { static const hideAllExceptExtensionsValue = 'all-except-extensions'; static const offlineScreenIdKey = 'screen'; static const inspectorRefKey = 'inspectorRef'; - - // TODO(kenz): consider using `?wasm=true` instead of `?renderer=skwasm`. - static const useWasmKey = 'renderer'; + static const skwasmKey = 'skwasm'; // TODO(kenz): remove legacy value in May of 2025 when all IDEs are not using // these and 12 months have passed to allow users ample upgrade time. diff --git a/packages/devtools_app/web/flutter_bootstrap.js b/packages/devtools_app/web/flutter_bootstrap.js index 607db456299..6007ef1dd7c 100644 --- a/packages/devtools_app/web/flutter_bootstrap.js +++ b/packages/devtools_app/web/flutter_bootstrap.js @@ -24,14 +24,14 @@ function unregisterDevToolsServiceWorker() { // Bootstrap app for 3P environments: function bootstrapAppFor3P() { const searchParams = new URLSearchParams(window.location.search); - const renderer = searchParams.get('renderer'); - const userConfig = renderer ? {'renderer': renderer} : {}; + const useSkwasm = searchParams.get('skwasm'); _flutter.loader.load({ serviceWorkerSettings: { serviceWorkerVersion: {{flutter_service_worker_version}}, }, config: { - canvasKitBaseUrl: 'canvaskit/' + canvasKitBaseUrl: 'canvaskit/', + renderer: useSkwasm ? 'skwasm' : 'canvaskit' } }); } From 9dba64920d72ae6909d27265ed6e53d1910e184d Mon Sep 17 00:00:00 2001 From: Kenzie Schmoll Date: Wed, 28 Aug 2024 17:49:46 -0700 Subject: [PATCH 03/24] clean up --- .../lib/src/framework/settings_dialog.dart | 13 +++--- .../lib/src/shared/analytics/constants.dart | 2 +- .../src/shared/preferences/preferences.dart | 44 +++++++++---------- .../lib/src/shared/query_parameters.dart | 12 +++-- .../devtools_app/web/flutter_bootstrap.js | 7 ++- 5 files changed, 42 insertions(+), 36 deletions(-) diff --git a/packages/devtools_app/lib/src/framework/settings_dialog.dart b/packages/devtools_app/lib/src/framework/settings_dialog.dart index 1e59140e4a3..df45296998b 100644 --- a/packages/devtools_app/lib/src/framework/settings_dialog.dart +++ b/packages/devtools_app/lib/src/framework/settings_dialog.dart @@ -80,14 +80,15 @@ class SettingsDialog extends StatelessWidget { ...dialogSubHeader(theme, 'Experimental Features'), Flexible( child: CheckboxSetting( - title: 'Enable Skwasm renderer', + title: 'Enable the WebAssembly build', description: 'This will trigger a reload of the page to load DevTools ' - 'with the skwasm renderer.', - notifier: preferences.skwasmEnabled, - onChanged: preferences.toggleSkasmEnabled, + 'compiled with WebAssembly. This may yield better ' + 'performance.', + notifier: preferences.wasmEnabled, + onChanged: preferences.toggleWasmEnabled, gaScreen: gac.settingsDialog, - gaItem: gac.skwasm, + gaItem: gac.wasm, ), ), const SizedBox(height: largeSpacing), @@ -118,7 +119,7 @@ class _VerboseLoggingSetting extends StatelessWidget { title: 'Enable verbose logging', notifier: preferences.verboseLoggingEnabled, onChanged: (enable) => preferences.toggleVerboseLogging(enable), - gaScreen: gac.settingsDialog, + gaScreen: gac.settingsDialog, gaItem: gac.verboseLogging, ), ), diff --git a/packages/devtools_app/lib/src/shared/analytics/constants.dart b/packages/devtools_app/lib/src/shared/analytics/constants.dart index 92a221028fa..5e52a08a7ac 100644 --- a/packages/devtools_app/lib/src/shared/analytics/constants.dart +++ b/packages/devtools_app/lib/src/shared/analytics/constants.dart @@ -114,7 +114,7 @@ const settingsDialog = 'settings'; const darkTheme = 'darkTheme'; const analytics = 'analytics'; const vmDeveloperMode = 'vmDeveloperMode'; -const skwasm = 'skwasm'; +const wasm = 'wasm'; const verboseLogging = 'verboseLogging'; const inspectorHoverEvalMode = 'inspectorHoverEvalMode'; const clearLogs = 'clearLogs'; diff --git a/packages/devtools_app/lib/src/shared/preferences/preferences.dart b/packages/devtools_app/lib/src/shared/preferences/preferences.dart index 4aca41a3ff4..c4963b1460d 100644 --- a/packages/devtools_app/lib/src/shared/preferences/preferences.dart +++ b/packages/devtools_app/lib/src/shared/preferences/preferences.dart @@ -31,7 +31,7 @@ const _thirdPartyPathSegment = 'third_party'; /// DevTools preferences for experimental features. enum _ExperimentPreferences { - skwasm; + wasm; String get storageKey => '$storagePrefix.$name'; @@ -70,9 +70,9 @@ class PreferencesController extends DisposableController final vmDeveloperModeEnabled = ValueNotifier(false); - /// Whether DevTools should be rendered with the skwasm renderer instead of - /// canvaskit. - final skwasmEnabled = ValueNotifier(false); + /// Whether DevTools should loaded with the dart2wasm + skwasm instead of + /// dart2js + canvaskit + final wasmEnabled = ValueNotifier(false); final verboseLoggingEnabled = ValueNotifier(Logger.root.level == verboseLoggingLevel); @@ -143,40 +143,38 @@ class PreferencesController extends DisposableController } Future _initWasmEnabled() async { - // TODO(https://github.com/flutter/devtools/issues/7856): set the current - // value based on whether DevTools is actually loaded with skwasm. - skwasmEnabled.value = false; + wasmEnabled.value = kIsWasm; // It is important that this listener is added before we set the initial - // state of the skwasm mode setting below. This is because the query - // parameter for skwasm may need to be updated based on the value of the - // preference in the storage file, which we take into account when we call - // [toggleSkasmEnabled] at the end of this method. - addAutoDisposeListener(skwasmEnabled, () async { - final enabled = skwasmEnabled.value; + // state of the wasm mode setting below. This is because the query parameter + // for wasm may need to be updated based on the value of the preference in + // the storage file, which we take into account when we call + // [toggleWasmEnabled] at the end of this method. + addAutoDisposeListener(wasmEnabled, () async { + final enabled = wasmEnabled.value; await storage.setValue( - _ExperimentPreferences.skwasm.storageKey, + _ExperimentPreferences.wasm.storageKey, '$enabled', ); // Update the wasm mode query parameter if it does not match the value of // the setting. - final skwasmEnabledFromQueryParams = DevToolsQueryParams.load().useSkwasm; - if (skwasmEnabledFromQueryParams != enabled) { + final wasmEnabledFromQueryParams = DevToolsQueryParams.load().useWasm; + if (wasmEnabledFromQueryParams != enabled) { updateQueryParameter( - DevToolsQueryParams.skwasmKey, - '$enabled', + DevToolsQueryParams.wasmKey, + enabled ? 'true' : null, reload: true, ); } }); final enabledFromStorage = await boolValueFromStorage( - _ExperimentPreferences.skwasm.storageKey, + _ExperimentPreferences.wasm.storageKey, defaultsTo: false, ); - final enabledFromQueryParams = DevToolsQueryParams.load().useSkwasm; - toggleSkasmEnabled(enabledFromStorage || enabledFromQueryParams); + final enabledFromQueryParams = DevToolsQueryParams.load().useWasm; + toggleWasmEnabled(enabledFromStorage || enabledFromQueryParams); } Future _initVerboseLogging() async { @@ -219,9 +217,9 @@ class PreferencesController extends DisposableController } /// Change the value of the wasm mode setting. - void toggleSkasmEnabled(bool? enable) { + void toggleWasmEnabled(bool? enable) { if (enable != null) { - skwasmEnabled.value = enable; + wasmEnabled.value = enable; } } diff --git a/packages/devtools_app/lib/src/shared/query_parameters.dart b/packages/devtools_app/lib/src/shared/query_parameters.dart index d3305b4fd31..66341feb177 100644 --- a/packages/devtools_app/lib/src/shared/query_parameters.dart +++ b/packages/devtools_app/lib/src/shared/query_parameters.dart @@ -39,9 +39,9 @@ extension type DevToolsQueryParams(Map params) { // Keys for theming values that an IDE may pass in the embedded DevTools URI. IdeThemeQueryParams get ideThemeParams => IdeThemeQueryParams(params); - /// Whether DevTools should be loaded using the skwasm renderer instead of - /// canvaskit. - bool get useSkwasm => params[skwasmKey] == 'true'; + /// Whether DevTools should be loaded using dart2wasm + skwasm instead of + /// dart2js + canvaskit. + bool get useWasm => params[wasmKey] == 'true'; static const vmServiceUriKey = 'uri'; static const hideScreensKey = 'hide'; @@ -49,7 +49,11 @@ extension type DevToolsQueryParams(Map params) { static const hideAllExceptExtensionsValue = 'all-except-extensions'; static const offlineScreenIdKey = 'screen'; static const inspectorRefKey = 'inspectorRef'; - static const skwasmKey = 'skwasm'; + + // This query parameter must match the String value in the Flutter bootstrap + // logic that is used to select a web renderer. See + // devtools/packages/devtools_app/web/flutter_bootstrap.js. + static const wasmKey = 'wasm'; // TODO(kenz): remove legacy value in May of 2025 when all IDEs are not using // these and 12 months have passed to allow users ample upgrade time. diff --git a/packages/devtools_app/web/flutter_bootstrap.js b/packages/devtools_app/web/flutter_bootstrap.js index 6007ef1dd7c..b01b74bd209 100644 --- a/packages/devtools_app/web/flutter_bootstrap.js +++ b/packages/devtools_app/web/flutter_bootstrap.js @@ -24,14 +24,17 @@ function unregisterDevToolsServiceWorker() { // Bootstrap app for 3P environments: function bootstrapAppFor3P() { const searchParams = new URLSearchParams(window.location.search); - const useSkwasm = searchParams.get('skwasm'); + // This query parameter must match the String value specified by + // `DevToolsQueryParameters.wasmKey`. See + // devtools/packages/devtools_app/lib/src/shared/query_parameters.dart + const useWasm = searchParams.get('wasm'); _flutter.loader.load({ serviceWorkerSettings: { serviceWorkerVersion: {{flutter_service_worker_version}}, }, config: { canvasKitBaseUrl: 'canvaskit/', - renderer: useSkwasm ? 'skwasm' : 'canvaskit' + renderer: useWasm ? 'skwasm' : 'canvaskit' } }); } From be4406652039513b2071e569ac4cef7a722d8d7c Mon Sep 17 00:00:00 2001 From: Kenzie Schmoll Date: Thu, 29 Aug 2024 09:36:19 -0700 Subject: [PATCH 04/24] Add feature flag --- .../lib/src/framework/settings_dialog.dart | 31 ++++++++++--------- .../lib/src/shared/feature_flags.dart | 5 +++ .../src/shared/preferences/preferences.dart | 5 ++- .../devtools_app/web/flutter_bootstrap.js | 4 +++ 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/packages/devtools_app/lib/src/framework/settings_dialog.dart b/packages/devtools_app/lib/src/framework/settings_dialog.dart index df45296998b..06564a378a0 100644 --- a/packages/devtools_app/lib/src/framework/settings_dialog.dart +++ b/packages/devtools_app/lib/src/framework/settings_dialog.dart @@ -12,6 +12,7 @@ import '../shared/analytics/analytics_controller.dart'; import '../shared/analytics/constants.dart' as gac; import '../shared/common_widgets.dart'; import '../shared/config_specific/copy_to_clipboard/copy_to_clipboard.dart'; +import '../shared/feature_flags.dart'; import '../shared/globals.dart'; import '../shared/log_storage.dart'; import '../shared/server/server.dart'; @@ -76,21 +77,23 @@ class SettingsDialog extends StatelessWidget { gaItem: gac.vmDeveloperMode, ), ), - const SizedBox(height: largeSpacing), - ...dialogSubHeader(theme, 'Experimental Features'), - Flexible( - child: CheckboxSetting( - title: 'Enable the WebAssembly build', - description: - 'This will trigger a reload of the page to load DevTools ' - 'compiled with WebAssembly. This may yield better ' - 'performance.', - notifier: preferences.wasmEnabled, - onChanged: preferences.toggleWasmEnabled, - gaScreen: gac.settingsDialog, - gaItem: gac.wasm, + if (FeatureFlags.wasmOptInSetting) ...[ + const SizedBox(height: largeSpacing), + ...dialogSubHeader(theme, 'Experimental Features'), + Flexible( + child: CheckboxSetting( + title: 'Enable WebAssembly', + description: + 'This will trigger a reload of the page to load DevTools ' + 'compiled with WebAssembly. This may yield better ' + 'performance.', + notifier: preferences.wasmEnabled, + onChanged: preferences.toggleWasmEnabled, + gaScreen: gac.settingsDialog, + gaItem: gac.wasm, + ), ), - ), + ], const SizedBox(height: largeSpacing), ...dialogSubHeader(theme, 'Troubleshooting'), const _VerboseLoggingSetting(), diff --git a/packages/devtools_app/lib/src/shared/feature_flags.dart b/packages/devtools_app/lib/src/shared/feature_flags.dart index 1f2785e5908..8be7caef1ea 100644 --- a/packages/devtools_app/lib/src/shared/feature_flags.dart +++ b/packages/devtools_app/lib/src/shared/feature_flags.dart @@ -99,6 +99,11 @@ abstract class FeatureFlags { /// https://github.com/flutter/devtools/issues/7854 static bool inspectorV2 = enableExperiments; + /// Flag to enable the DevTools setting to opt-in to WASM. + /// + /// https://github.com/flutter/devtools/issues/7856 + static bool wasmOptInSetting = enableExperiments; + /// Stores a map of all the feature flags for debugging purposes. /// /// When adding a new flag, you are responsible for adding it to this map as diff --git a/packages/devtools_app/lib/src/shared/preferences/preferences.dart b/packages/devtools_app/lib/src/shared/preferences/preferences.dart index c4963b1460d..007e106b90b 100644 --- a/packages/devtools_app/lib/src/shared/preferences/preferences.dart +++ b/packages/devtools_app/lib/src/shared/preferences/preferences.dart @@ -16,6 +16,7 @@ import '../analytics/constants.dart' as gac; import '../config_specific/logger/logger_helpers.dart'; import '../constants.dart'; import '../diagnostics/inspector_service.dart'; +import '../feature_flags.dart'; import '../globals.dart'; import '../query_parameters.dart'; import '../utils.dart'; @@ -101,7 +102,9 @@ class PreferencesController extends DisposableController // Get the current values and listen for and write back changes. await _initDarkMode(); await _initVmDeveloperMode(); - await _initWasmEnabled(); + if (FeatureFlags.wasmOptInSetting) { + await _initWasmEnabled(); + } await _initVerboseLogging(); await inspector.init(); diff --git a/packages/devtools_app/web/flutter_bootstrap.js b/packages/devtools_app/web/flutter_bootstrap.js index b01b74bd209..b8314a5169f 100644 --- a/packages/devtools_app/web/flutter_bootstrap.js +++ b/packages/devtools_app/web/flutter_bootstrap.js @@ -28,6 +28,10 @@ function bootstrapAppFor3P() { // `DevToolsQueryParameters.wasmKey`. See // devtools/packages/devtools_app/lib/src/shared/query_parameters.dart const useWasm = searchParams.get('wasm'); + + // TODO(https://github.com/flutter/devtools/issues/7856): can we also + // look up the wasm preference from the DevTools preferences file? Can + // we make a direct call to the DevTools server from here? _flutter.loader.load({ serviceWorkerSettings: { serviceWorkerVersion: {{flutter_service_worker_version}}, From d78412845e869e07d387be95c7f2e73508cc0adf Mon Sep 17 00:00:00 2001 From: Kenzie Schmoll Date: Thu, 29 Aug 2024 13:00:45 -0700 Subject: [PATCH 05/24] checkpoint --- .../lib/src/shared/feature_flags.dart | 2 +- .../src/shared/preferences/preferences.dart | 37 ++++++++++++++-- .../release_notes/NEXT_RELEASE_NOTES.md | 5 +++ .../release_notes/images/wasm_setting.png | Bin 0 -> 46170 bytes .../devtools_app/web/flutter_bootstrap.js | 41 +++++++++++++++--- 5 files changed, 75 insertions(+), 10 deletions(-) create mode 100644 packages/devtools_app/release_notes/images/wasm_setting.png diff --git a/packages/devtools_app/lib/src/shared/feature_flags.dart b/packages/devtools_app/lib/src/shared/feature_flags.dart index 8be7caef1ea..e5d076fa608 100644 --- a/packages/devtools_app/lib/src/shared/feature_flags.dart +++ b/packages/devtools_app/lib/src/shared/feature_flags.dart @@ -102,7 +102,7 @@ abstract class FeatureFlags { /// Flag to enable the DevTools setting to opt-in to WASM. /// /// https://github.com/flutter/devtools/issues/7856 - static bool wasmOptInSetting = enableExperiments; + static bool wasmOptInSetting = true; /// Stores a map of all the feature flags for debugging purposes. /// diff --git a/packages/devtools_app/lib/src/shared/preferences/preferences.dart b/packages/devtools_app/lib/src/shared/preferences/preferences.dart index 007e106b90b..6663c42c094 100644 --- a/packages/devtools_app/lib/src/shared/preferences/preferences.dart +++ b/packages/devtools_app/lib/src/shared/preferences/preferences.dart @@ -146,7 +146,24 @@ class PreferencesController extends DisposableController } Future _initWasmEnabled() async { + print('_initWasmEnabled - start'); wasmEnabled.value = kIsWasm; + print('kIsWasm: $kIsWasm'); + + final enabledFromStorage = await boolValueFromStorage( + _ExperimentPreferences.wasm.storageKey, + defaultsTo: false, + ); + final enabledFromQueryParams = DevToolsQueryParams.load().useWasm; + print('enabledFromQueryParams: $enabledFromQueryParams'); + print('enabledFromStorage: $enabledFromStorage'); + + if (kIsWasm != enabledFromQueryParams) { + print('kIsWasm != enabledFromQueryParams'); + // If we hit this case, we tried to reload DevTools with the wasm query + // parameter set to true, but DevTools did not load with wasm. This means + // that something went wrong and that we fellback to JS. + } // It is important that this listener is added before we set the initial // state of the wasm mode setting below. This is because the query parameter @@ -155,6 +172,7 @@ class PreferencesController extends DisposableController // [toggleWasmEnabled] at the end of this method. addAutoDisposeListener(wasmEnabled, () async { final enabled = wasmEnabled.value; + print('listener: setting storage value'); await storage.setValue( _ExperimentPreferences.wasm.storageKey, '$enabled', @@ -163,7 +181,12 @@ class PreferencesController extends DisposableController // Update the wasm mode query parameter if it does not match the value of // the setting. final wasmEnabledFromQueryParams = DevToolsQueryParams.load().useWasm; + print('listener: enabled: $enabled'); + print( + 'listener: wasmEnabledFromQueryParams: $wasmEnabledFromQueryParams'); if (wasmEnabledFromQueryParams != enabled) { + print('updating query param and reloading the page'); + await Future.delayed(const Duration(seconds: 7)); updateQueryParameter( DevToolsQueryParams.wasmKey, enabled ? 'true' : null, @@ -172,12 +195,18 @@ class PreferencesController extends DisposableController } }); - final enabledFromStorage = await boolValueFromStorage( - _ExperimentPreferences.wasm.storageKey, - defaultsTo: false, + // TODO(kenz): this may cause an infinite loop of reloading the page if + // the setting from storage or the query parameter indicate we should be + // loading with WASM, but each time we reload the page, something goes wrong + // and we fall back to JS. + print( + 'calling toggleWasmEnabled ' + '${enabledFromStorage || enabledFromQueryParams}, ' + '(enabledFromStorage: $enabledFromStorage, ' + 'enabledFromQueryParams: $enabledFromQueryParams)', ); - final enabledFromQueryParams = DevToolsQueryParams.load().useWasm; toggleWasmEnabled(enabledFromStorage || enabledFromQueryParams); + print('_initWasmEnabled - end'); } Future _initVerboseLogging() async { diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md index bd15f2f4176..a45b24822ef 100644 --- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md +++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md @@ -13,6 +13,11 @@ To learn more about DevTools, check out the * Fixed a bug that was causing the DevTools release notes to always show. - [#8277](https://github.com/flutter/devtools/pull/8277) +* Add a setting that allows users to opt-in to loading DevTools +with WebAssembly. - [#8270](https://github.com/flutter/devtools/pull/8270) + +![Wasm opt-in setting](images/wasm_setting.png "DevTools setting to opt into wasm.") + ## Inspector updates TODO: Remove this section if there are not any general updates. diff --git a/packages/devtools_app/release_notes/images/wasm_setting.png b/packages/devtools_app/release_notes/images/wasm_setting.png new file mode 100644 index 0000000000000000000000000000000000000000..e3aabc32e93b1d94d14082b12334cc28a2804f67 GIT binary patch literal 46170 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 literal 0 HcmV?d00001 diff --git a/packages/devtools_app/web/flutter_bootstrap.js b/packages/devtools_app/web/flutter_bootstrap.js index b8314a5169f..7e4a9d44181 100644 --- a/packages/devtools_app/web/flutter_bootstrap.js +++ b/packages/devtools_app/web/flutter_bootstrap.js @@ -21,24 +21,55 @@ function unregisterDevToolsServiceWorker() { } } +function getDevToolsWasmPreference() { + // TODO(https://github.com/flutter/devtools/issues/7856): can we also + // look up the wasm preference from the DevTools preferences file? Can + // we make a direct call to the DevTools server from here? + fetch('api/sse/') + const eventSource = new EventSource('/api/sse'); + eventSource.onopen = () => { + console.log('SSE connection opened.'); + }; + + eventSource.onmessage = (event) => { + const data = JSON.parse(event.data); + console.log('Received data:', data); + }; + + eventSource.onerror = (error) => { + console.error('SSE error:', error); + eventSource.close(); + }; + + console.log('eventSource.url: ' + eventSource.url); + + return true; +} + // Bootstrap app for 3P environments: function bootstrapAppFor3P() { const searchParams = new URLSearchParams(window.location.search); // This query parameter must match the String value specified by // `DevToolsQueryParameters.wasmKey`. See // devtools/packages/devtools_app/lib/src/shared/query_parameters.dart - const useWasm = searchParams.get('wasm'); + const wasmEnabledFromQueryParameter = searchParams.get('wasm'); + console.log('wasmEnabledFromQueryParameter: ' + wasmEnabledFromQueryParameter); + console.log('wasmEnabledFromQueryParameter === \'true\' : ' + wasmEnabledFromQueryParameter === 'true'); + + const wasmEnabledFromDevToolsPreference = getDevToolsWasmPreference(); + console.log('wasmEnabledFromDevToolsPreference: ' + wasmEnabledFromDevToolsPreference); + + console.log('boolean value: ' + wasmEnabledFromQueryParameter || wasmEnabledFromDevToolsPreference); - // TODO(https://github.com/flutter/devtools/issues/7856): can we also - // look up the wasm preference from the DevTools preferences file? Can - // we make a direct call to the DevTools server from here? _flutter.loader.load({ serviceWorkerSettings: { serviceWorkerVersion: {{flutter_service_worker_version}}, }, config: { canvasKitBaseUrl: 'canvaskit/', - renderer: useWasm ? 'skwasm' : 'canvaskit' + renderer: wasmEnabledFromQueryParameter === 'true' || wasmEnabledFromDevToolsPreference + ? 'skwasm' + : 'canvaskit' } }); } From f98f77161420000fecc05c7a992cc3fd46383255 Mon Sep 17 00:00:00 2001 From: Kenzie Schmoll Date: Fri, 6 Sep 2024 14:12:52 -0700 Subject: [PATCH 06/24] Update flutter_bootstrap.js --- .../devtools_app/web/flutter_bootstrap.js | 66 +++++++++---------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/packages/devtools_app/web/flutter_bootstrap.js b/packages/devtools_app/web/flutter_bootstrap.js index 7e4a9d44181..7a5275f3db9 100644 --- a/packages/devtools_app/web/flutter_bootstrap.js +++ b/packages/devtools_app/web/flutter_bootstrap.js @@ -21,55 +21,53 @@ function unregisterDevToolsServiceWorker() { } } -function getDevToolsWasmPreference() { - // TODO(https://github.com/flutter/devtools/issues/7856): can we also - // look up the wasm preference from the DevTools preferences file? Can - // we make a direct call to the DevTools server from here? - fetch('api/sse/') - const eventSource = new EventSource('/api/sse'); - eventSource.onopen = () => { - console.log('SSE connection opened.'); - }; - - eventSource.onmessage = (event) => { - const data = JSON.parse(event.data); - console.log('Received data:', data); - }; - - eventSource.onerror = (error) => { - console.error('SSE error:', error); - eventSource.close(); - }; - - console.log('eventSource.url: ' + eventSource.url); +// Calls the DevTools server API to read the user's wasm preference. +async function getDevToolsWasmPreference() { + const request = 'api/getPreferenceValue?key=experiment.wasm'; + try { + const response = await fetch(request); + if (!response.ok) { + console.warn(`[${response.status} response] ${request}`); + return false; + } - return true; + // The response text should be an encoded boolean value ("true" or "false"). + return JSON.parse(await response.text()); + } catch (error) { + console.error('Error fetching experiment.wasm preference value:', error); + return false; + } } // Bootstrap app for 3P environments: -function bootstrapAppFor3P() { - const searchParams = new URLSearchParams(window.location.search); +async function bootstrapAppFor3P() { // This query parameter must match the String value specified by // `DevToolsQueryParameters.wasmKey`. See // devtools/packages/devtools_app/lib/src/shared/query_parameters.dart - const wasmEnabledFromQueryParameter = searchParams.get('wasm'); - console.log('wasmEnabledFromQueryParameter: ' + wasmEnabledFromQueryParameter); - console.log('wasmEnabledFromQueryParameter === \'true\' : ' + wasmEnabledFromQueryParameter === 'true'); - - const wasmEnabledFromDevToolsPreference = getDevToolsWasmPreference(); - console.log('wasmEnabledFromDevToolsPreference: ' + wasmEnabledFromDevToolsPreference); + const wasmQueryParameterKey = 'wasm'; + + const searchParams = new URLSearchParams(window.location.search); + const wasmEnabledFromQueryParameter = searchParams.get(wasmQueryParameterKey) === 'true'; + const wasmEnabledFromDevToolsPreference = await getDevToolsWasmPreference(); - console.log('boolean value: ' + wasmEnabledFromQueryParameter || wasmEnabledFromDevToolsPreference); + // Add the 'wasm=true' query parameter if WASM should be enabled based on + // the DevTools preferences, but the query parameter is not set to 'true'. + if (wasmEnabledFromDevToolsPreference === true && wasmEnabledFromQueryParameter === false) { + const url = new URL(window.location.href); // Get the current URL object + url.searchParams.set(wasmQueryParameterKey, 'true'); + // Update the browser's history without reloading + window.history.pushState({}, '', url); + } + const shouldUseSkwasm = wasmEnabledFromQueryParameter === true || wasmEnabledFromDevToolsPreference === true; + const renderer = shouldUseSkwasm ? 'skwasm' : 'canvaskit'; _flutter.loader.load({ serviceWorkerSettings: { serviceWorkerVersion: {{flutter_service_worker_version}}, }, config: { canvasKitBaseUrl: 'canvaskit/', - renderer: wasmEnabledFromQueryParameter === 'true' || wasmEnabledFromDevToolsPreference - ? 'skwasm' - : 'canvaskit' + renderer: renderer, } }); } From 7eaa6d46d66c5c0841d3713118ac0cc4963c1046 Mon Sep 17 00:00:00 2001 From: Kenzie Schmoll Date: Fri, 6 Sep 2024 15:23:17 -0700 Subject: [PATCH 07/24] bootstrap --- packages/devtools_app/web/flutter_bootstrap.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/devtools_app/web/flutter_bootstrap.js b/packages/devtools_app/web/flutter_bootstrap.js index 7a5275f3db9..43decafffac 100644 --- a/packages/devtools_app/web/flutter_bootstrap.js +++ b/packages/devtools_app/web/flutter_bootstrap.js @@ -32,7 +32,8 @@ async function getDevToolsWasmPreference() { } // The response text should be an encoded boolean value ("true" or "false"). - return JSON.parse(await response.text()); + const wasmEnabled = JSON.parse(await response.text()); + return wasmEnabled === true || wasmEnabled === 'true'; } catch (error) { console.error('Error fetching experiment.wasm preference value:', error); return false; @@ -45,7 +46,7 @@ async function bootstrapAppFor3P() { // `DevToolsQueryParameters.wasmKey`. See // devtools/packages/devtools_app/lib/src/shared/query_parameters.dart const wasmQueryParameterKey = 'wasm'; - + const searchParams = new URLSearchParams(window.location.search); const wasmEnabledFromQueryParameter = searchParams.get(wasmQueryParameterKey) === 'true'; const wasmEnabledFromDevToolsPreference = await getDevToolsWasmPreference(); @@ -56,11 +57,12 @@ async function bootstrapAppFor3P() { const url = new URL(window.location.href); // Get the current URL object url.searchParams.set(wasmQueryParameterKey, 'true'); // Update the browser's history without reloading - window.history.pushState({}, '', url); + window.history.pushState({}, '', url); } const shouldUseSkwasm = wasmEnabledFromQueryParameter === true || wasmEnabledFromDevToolsPreference === true; - const renderer = shouldUseSkwasm ? 'skwasm' : 'canvaskit'; + const renderer = shouldUseSkwasm ? 'skwasm' : 'canvaskit'; + console.log('Loading DevTools with ' + renderer + ' renderer.'); _flutter.loader.load({ serviceWorkerSettings: { serviceWorkerVersion: {{flutter_service_worker_version}}, From 0f1fe6265a5c7514fd2e0882c7d055f1c1a8462e Mon Sep 17 00:00:00 2001 From: Kenzie Schmoll Date: Mon, 9 Sep 2024 09:18:17 -0700 Subject: [PATCH 08/24] bootstrap cleanup --- .../devtools_app/web/flutter_bootstrap.js | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/packages/devtools_app/web/flutter_bootstrap.js b/packages/devtools_app/web/flutter_bootstrap.js index 43decafffac..16af599717b 100644 --- a/packages/devtools_app/web/flutter_bootstrap.js +++ b/packages/devtools_app/web/flutter_bootstrap.js @@ -40,8 +40,11 @@ async function getDevToolsWasmPreference() { } } -// Bootstrap app for 3P environments: -async function bootstrapAppFor3P() { +// Gets the renderer ('canvaskit' or 'skwasm') for the Flutter bootstrap config +// based on the value of the 'wasm' query parameter or the wasm setting from the +// DevTools preference file. This method also updates the 'wasm' query parameter +// to reflect the renderer that will be used. +async function getRenderer() { // This query parameter must match the String value specified by // `DevToolsQueryParameters.wasmKey`. See // devtools/packages/devtools_app/lib/src/shared/query_parameters.dart @@ -50,18 +53,22 @@ async function bootstrapAppFor3P() { const searchParams = new URLSearchParams(window.location.search); const wasmEnabledFromQueryParameter = searchParams.get(wasmQueryParameterKey) === 'true'; const wasmEnabledFromDevToolsPreference = await getDevToolsWasmPreference(); + const shouldUseSkwasm = wasmEnabledFromQueryParameter === true || wasmEnabledFromDevToolsPreference === true; + + const url = new URL(window.location.href); + // Ensure the 'wasm' query parameter in the URL is accurate for the renderer + // DevTools will be loaded with. + url.searchParams.set(wasmQueryParameterKey, shouldUseSkwasm ? 'true' : null); + // Update the browser's history without reloading. This is a no-op if the wasm + // query parameter does not actually need to be updated. + window.history.pushState({}, '', url); - // Add the 'wasm=true' query parameter if WASM should be enabled based on - // the DevTools preferences, but the query parameter is not set to 'true'. - if (wasmEnabledFromDevToolsPreference === true && wasmEnabledFromQueryParameter === false) { - const url = new URL(window.location.href); // Get the current URL object - url.searchParams.set(wasmQueryParameterKey, 'true'); - // Update the browser's history without reloading - window.history.pushState({}, '', url); - } + return shouldUseSkwasm ? 'skwasm' : 'canvaskit'; +} - const shouldUseSkwasm = wasmEnabledFromQueryParameter === true || wasmEnabledFromDevToolsPreference === true; - const renderer = shouldUseSkwasm ? 'skwasm' : 'canvaskit'; +// Bootstrap app for 3P environments: +async function bootstrapAppFor3P() { + const renderer = await getRenderer(); console.log('Loading DevTools with ' + renderer + ' renderer.'); _flutter.loader.load({ serviceWorkerSettings: { From 99800ebdfb08eb49b070bcd6eaee1f551c6b71e0 Mon Sep 17 00:00:00 2001 From: Kenzie Schmoll Date: Mon, 9 Sep 2024 09:19:25 -0700 Subject: [PATCH 09/24] fix dialog header --- packages/devtools_app/lib/src/framework/settings_dialog.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/devtools_app/lib/src/framework/settings_dialog.dart b/packages/devtools_app/lib/src/framework/settings_dialog.dart index 06564a378a0..fed19127e03 100644 --- a/packages/devtools_app/lib/src/framework/settings_dialog.dart +++ b/packages/devtools_app/lib/src/framework/settings_dialog.dart @@ -79,7 +79,7 @@ class SettingsDialog extends StatelessWidget { ), if (FeatureFlags.wasmOptInSetting) ...[ const SizedBox(height: largeSpacing), - ...dialogSubHeader(theme, 'Experimental Features'), + ...dialogSubHeader(theme, 'Experimental features'), Flexible( child: CheckboxSetting( title: 'Enable WebAssembly', From c30a1dfd8410e81fc2f78aadfeee39d723780f12 Mon Sep 17 00:00:00 2001 From: Kenzie Schmoll Date: Mon, 9 Sep 2024 10:24:49 -0700 Subject: [PATCH 10/24] lints --- .../devtools_app/lib/src/shared/preferences/preferences.dart | 3 ++- packages/devtools_shared/test/helpers/helpers.dart | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/devtools_app/lib/src/shared/preferences/preferences.dart b/packages/devtools_app/lib/src/shared/preferences/preferences.dart index cc4de6e6875..29f6fa54ea4 100644 --- a/packages/devtools_app/lib/src/shared/preferences/preferences.dart +++ b/packages/devtools_app/lib/src/shared/preferences/preferences.dart @@ -179,7 +179,8 @@ class PreferencesController extends DisposableController final wasmEnabledFromQueryParams = DevToolsQueryParams.load().useWasm; print('listener: enabled: $enabled'); print( - 'listener: wasmEnabledFromQueryParams: $wasmEnabledFromQueryParams'); + 'listener: wasmEnabledFromQueryParams: $wasmEnabledFromQueryParams', + ); if (wasmEnabledFromQueryParams != enabled) { print('updating query param and reloading the page'); await Future.delayed(const Duration(seconds: 7)); diff --git a/packages/devtools_shared/test/helpers/helpers.dart b/packages/devtools_shared/test/helpers/helpers.dart index d5fbb435779..64ae19f0f18 100644 --- a/packages/devtools_shared/test/helpers/helpers.dart +++ b/packages/devtools_shared/test/helpers/helpers.dart @@ -44,7 +44,7 @@ Future startDtd() async { completer.complete( ( info: DtdInfo(Uri.parse(uri), secret: secret), - process: dtdProcess + process: dtdProcess, ), ); } else { From 91564ba132f25c56ecafb0f6668d7bd1aecc3e16 Mon Sep 17 00:00:00 2001 From: Kenzie Schmoll Date: Mon, 9 Sep 2024 12:33:07 -0700 Subject: [PATCH 11/24] checkpoint --- .../lib/src/shared/analytics/constants.dart | 4 ++ .../src/shared/preferences/preferences.dart | 56 +++++++++++-------- 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/packages/devtools_app/lib/src/shared/analytics/constants.dart b/packages/devtools_app/lib/src/shared/analytics/constants.dart index 5e52a08a7ac..3b8e8bcc07b 100644 --- a/packages/devtools_app/lib/src/shared/analytics/constants.dart +++ b/packages/devtools_app/lib/src/shared/analytics/constants.dart @@ -47,6 +47,10 @@ const devToolsMain = 'main'; const appDisconnected = 'appDisconnected'; const init = 'init'; +/// Event that signals we fell back to JS when trying to load DevTools with +/// Wasm. +const jsFallback = 'jsFallback'; + // DevTools UI action selected (clicked). // Main bar UX actions: diff --git a/packages/devtools_app/lib/src/shared/preferences/preferences.dart b/packages/devtools_app/lib/src/shared/preferences/preferences.dart index 29f6fa54ea4..ee23d539b3e 100644 --- a/packages/devtools_app/lib/src/shared/preferences/preferences.dart +++ b/packages/devtools_app/lib/src/shared/preferences/preferences.dart @@ -27,6 +27,8 @@ part '_memory_preferences.dart'; part '_logging_preferences.dart'; part '_performance_preferences.dart'; +final _log = Logger('PreferencesController'); + const _thirdPartyPathSegment = 'third_party'; /// DevTools preferences for experimental features. @@ -142,32 +144,22 @@ class PreferencesController extends DisposableController } Future _initWasmEnabled() async { - print('_initWasmEnabled - start'); - wasmEnabled.value = kIsWasm; - print('kIsWasm: $kIsWasm'); - final enabledFromStorage = await boolValueFromStorage( _ExperimentPreferences.wasm.storageKey, defaultsTo: false, ); final enabledFromQueryParams = DevToolsQueryParams.load().useWasm; + + print('kIsWasm: $kIsWasm'); print('enabledFromQueryParams: $enabledFromQueryParams'); print('enabledFromStorage: $enabledFromStorage'); - if (kIsWasm != enabledFromQueryParams) { - print('kIsWasm != enabledFromQueryParams'); - // If we hit this case, we tried to reload DevTools with the wasm query - // parameter set to true, but DevTools did not load with wasm. This means - // that something went wrong and that we fellback to JS. - } - - // It is important that this listener is added before we set the initial - // state of the wasm mode setting below. This is because the query parameter - // for wasm may need to be updated based on the value of the preference in - // the storage file, which we take into account when we call - // [toggleWasmEnabled] at the end of this method. + wasmEnabled.value = kIsWasm; addAutoDisposeListener(wasmEnabled, () async { final enabled = wasmEnabled.value; + _log.fine('preference update (wasmEnabled = $enabled)'); + + print('listener: enabled: $enabled'); print('listener: setting storage value'); await storage.setValue( _ExperimentPreferences.wasm.storageKey, @@ -177,11 +169,13 @@ class PreferencesController extends DisposableController // Update the wasm mode query parameter if it does not match the value of // the setting. final wasmEnabledFromQueryParams = DevToolsQueryParams.load().useWasm; - print('listener: enabled: $enabled'); print( 'listener: wasmEnabledFromQueryParams: $wasmEnabledFromQueryParams', ); if (wasmEnabledFromQueryParams != enabled) { + _log.fine( + 'Reloading DevTools for Wasm preference update (enabled = $enabled)', + ); print('updating query param and reloading the page'); await Future.delayed(const Duration(seconds: 7)); updateQueryParameter( @@ -192,18 +186,34 @@ class PreferencesController extends DisposableController } }); - // TODO(kenz): this may cause an infinite loop of reloading the page if - // the setting from storage or the query parameter indicate we should be - // loading with WASM, but each time we reload the page, something goes wrong - // and we fall back to JS. + if (enabledFromQueryParams && !kIsWasm) { + // If we hit this case, we tried to load DevTools with WASM but we fell + // back to JS. We know this because the flutter_bootstrap.js logic always + // sets the 'wasm' query parameter to 'true' when attempting to load + // DevTools with wasm. Remove the wasm query parameter and return early. + updateQueryParameter(DevToolsQueryParams.wasmKey, null); + ga.impression(gac.devToolsMain, gac.jsFallback); + // TODO(kenz): supress for VS Code + notificationService.push( + 'Something went wrong when trying to load DevTools with WebAssembly. ' + 'Falling back to Javascript.', + ); + return; + } + print( 'calling toggleWasmEnabled ' '${enabledFromStorage || enabledFromQueryParams}, ' '(enabledFromStorage: $enabledFromStorage, ' 'enabledFromQueryParams: $enabledFromQueryParams)', ); - toggleWasmEnabled(enabledFromStorage || enabledFromQueryParams); - print('_initWasmEnabled - end'); + + final shouldEnableWasm = enabledFromStorage || enabledFromQueryParams; + assert(kIsWasm == shouldEnableWasm); + // This should be a no-op if the flutter_bootstrap.js logic set the + // renderer propertly, but we call this to be safe in case something went + // wrong. + toggleWasmEnabled(shouldEnableWasm); } Future _initVerboseLogging() async { From fe436fbf02bfb3b903bb83a0d0024674eeb41014 Mon Sep 17 00:00:00 2001 From: Kenzie Schmoll Date: Mon, 9 Sep 2024 13:54:41 -0700 Subject: [PATCH 12/24] todo --- .../devtools_app/lib/src/framework/app_error_handling.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/devtools_app/lib/src/framework/app_error_handling.dart b/packages/devtools_app/lib/src/framework/app_error_handling.dart index 5cff96016b8..86292c53cf8 100644 --- a/packages/devtools_app/lib/src/framework/app_error_handling.dart +++ b/packages/devtools_app/lib/src/framework/app_error_handling.dart @@ -53,6 +53,10 @@ void setupErrorHandling(Future Function() appStartCallback) { return appStartCallback(); }, (Object error, StackTrace stack) { + // TODO(https://github.com/flutter/devtools/issues/7856): can we detect + // severe errors here that are related to dart2wasm? Otherwise we may + // crash DevTools for the user without any way for them to force reload + // with JS. reportError(error, stack: stack, errorType: 'zoneGuarded'); throw error; }, From d77e8cab7f1888065adf56e60848613a4fb9fbc8 Mon Sep 17 00:00:00 2001 From: Kenzie Schmoll Date: Mon, 9 Sep 2024 13:58:53 -0700 Subject: [PATCH 13/24] flags --- packages/devtools_app/lib/src/shared/feature_flags.dart | 9 +++++---- .../test/shared/primitives/feature_flags_test.dart | 6 ++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/devtools_app/lib/src/shared/feature_flags.dart b/packages/devtools_app/lib/src/shared/feature_flags.dart index e5d076fa608..455858c31ef 100644 --- a/packages/devtools_app/lib/src/shared/feature_flags.dart +++ b/packages/devtools_app/lib/src/shared/feature_flags.dart @@ -102,7 +102,7 @@ abstract class FeatureFlags { /// Flag to enable the DevTools setting to opt-in to WASM. /// /// https://github.com/flutter/devtools/issues/7856 - static bool wasmOptInSetting = true; + static bool wasmOptInSetting = enableExperiments; /// Stores a map of all the feature flags for debugging purposes. /// @@ -110,11 +110,12 @@ abstract class FeatureFlags { /// well. static final _allFlags = { 'widgetRebuildStats': widgetRebuildStats, - 'memoryOffline': memoryDisconnectExperience, - 'dapDebugging': dapDebugging, - 'loggingV2': loggingV2, + 'memorySaveLoad': memorySaveLoad, 'deepLinkIosCheck': deepLinkIosCheck, + 'loggingV2': loggingV2, + 'dapDebugging': dapDebugging, 'inspectorV2': inspectorV2, + 'wasmOptInSetting': wasmOptInSetting, }; /// A helper to print the status of all the feature flags. diff --git a/packages/devtools_app/test/shared/primitives/feature_flags_test.dart b/packages/devtools_app/test/shared/primitives/feature_flags_test.dart index cf6f0bb7824..5dd5268bef2 100644 --- a/packages/devtools_app/test/shared/primitives/feature_flags_test.dart +++ b/packages/devtools_app/test/shared/primitives/feature_flags_test.dart @@ -11,5 +11,11 @@ void main() { expect(enableExperiments, false); expect(enableBeta, false); expect(isExternalBuild, true); + expect(FeatureFlags.memorySaveLoad, false); + expect(FeatureFlags.deepLinkIosCheck, false); + expect(FeatureFlags.loggingV2, false); + expect(FeatureFlags.dapDebugging, false); + expect(FeatureFlags.inspectorV2, false); + expect(FeatureFlags.wasmOptInSetting, false); }); } From 1318b8ee2b30eb9d5cb09c195942697c29dcf056 Mon Sep 17 00:00:00 2001 From: Kenzie Schmoll Date: Mon, 9 Sep 2024 15:44:49 -0700 Subject: [PATCH 14/24] remove prints --- .../src/shared/preferences/preferences.dart | 32 ++++--------------- packages/devtools_app/pubspec.yaml | 2 +- .../devtools_app/web/flutter_bootstrap.js | 2 +- 3 files changed, 9 insertions(+), 27 deletions(-) diff --git a/packages/devtools_app/lib/src/shared/preferences/preferences.dart b/packages/devtools_app/lib/src/shared/preferences/preferences.dart index ee23d539b3e..337bf7371ab 100644 --- a/packages/devtools_app/lib/src/shared/preferences/preferences.dart +++ b/packages/devtools_app/lib/src/shared/preferences/preferences.dart @@ -144,23 +144,11 @@ class PreferencesController extends DisposableController } Future _initWasmEnabled() async { - final enabledFromStorage = await boolValueFromStorage( - _ExperimentPreferences.wasm.storageKey, - defaultsTo: false, - ); - final enabledFromQueryParams = DevToolsQueryParams.load().useWasm; - - print('kIsWasm: $kIsWasm'); - print('enabledFromQueryParams: $enabledFromQueryParams'); - print('enabledFromStorage: $enabledFromStorage'); - wasmEnabled.value = kIsWasm; addAutoDisposeListener(wasmEnabled, () async { final enabled = wasmEnabled.value; _log.fine('preference update (wasmEnabled = $enabled)'); - print('listener: enabled: $enabled'); - print('listener: setting storage value'); await storage.setValue( _ExperimentPreferences.wasm.storageKey, '$enabled', @@ -169,15 +157,10 @@ class PreferencesController extends DisposableController // Update the wasm mode query parameter if it does not match the value of // the setting. final wasmEnabledFromQueryParams = DevToolsQueryParams.load().useWasm; - print( - 'listener: wasmEnabledFromQueryParams: $wasmEnabledFromQueryParams', - ); if (wasmEnabledFromQueryParams != enabled) { _log.fine( 'Reloading DevTools for Wasm preference update (enabled = $enabled)', ); - print('updating query param and reloading the page'); - await Future.delayed(const Duration(seconds: 7)); updateQueryParameter( DevToolsQueryParams.wasmKey, enabled ? 'true' : null, @@ -186,6 +169,12 @@ class PreferencesController extends DisposableController } }); + final enabledFromStorage = await boolValueFromStorage( + _ExperimentPreferences.wasm.storageKey, + defaultsTo: false, + ); + final enabledFromQueryParams = DevToolsQueryParams.load().useWasm; + if (enabledFromQueryParams && !kIsWasm) { // If we hit this case, we tried to load DevTools with WASM but we fell // back to JS. We know this because the flutter_bootstrap.js logic always @@ -201,17 +190,10 @@ class PreferencesController extends DisposableController return; } - print( - 'calling toggleWasmEnabled ' - '${enabledFromStorage || enabledFromQueryParams}, ' - '(enabledFromStorage: $enabledFromStorage, ' - 'enabledFromQueryParams: $enabledFromQueryParams)', - ); - final shouldEnableWasm = enabledFromStorage || enabledFromQueryParams; assert(kIsWasm == shouldEnableWasm); // This should be a no-op if the flutter_bootstrap.js logic set the - // renderer propertly, but we call this to be safe in case something went + // renderer properly, but we call this to be safe in case something went // wrong. toggleWasmEnabled(shouldEnableWasm); } diff --git a/packages/devtools_app/pubspec.yaml b/packages/devtools_app/pubspec.yaml index abeca29fdf3..55328e7dd66 100644 --- a/packages/devtools_app/pubspec.yaml +++ b/packages/devtools_app/pubspec.yaml @@ -53,7 +53,7 @@ dependencies: stack_trace: ^1.10.0 stream_channel: ^2.1.1 string_scanner: ^1.1.0 - unified_analytics: ^6.1.0 + unified_analytics: ^6.1.3 vm_service: ^14.2.5 vm_service_protos: ^1.0.0 vm_snapshot_analysis: ^0.7.6 diff --git a/packages/devtools_app/web/flutter_bootstrap.js b/packages/devtools_app/web/flutter_bootstrap.js index 16af599717b..244ce5ca36b 100644 --- a/packages/devtools_app/web/flutter_bootstrap.js +++ b/packages/devtools_app/web/flutter_bootstrap.js @@ -58,7 +58,7 @@ async function getRenderer() { const url = new URL(window.location.href); // Ensure the 'wasm' query parameter in the URL is accurate for the renderer // DevTools will be loaded with. - url.searchParams.set(wasmQueryParameterKey, shouldUseSkwasm ? 'true' : null); + url.searchParams.set(wasmQueryParameterKey, shouldUseSkwasm ? 'true' : ''); // Update the browser's history without reloading. This is a no-op if the wasm // query parameter does not actually need to be updated. window.history.pushState({}, '', url); From ed1f77de9be27ec89ec955d07400ee88cef1c9b8 Mon Sep 17 00:00:00 2001 From: Kenzie Davisson Date: Tue, 10 Sep 2024 09:01:12 -0700 Subject: [PATCH 15/24] analytics --- .../src/shared/analytics/_analytics_web.dart | 54 ++++++++++++++++--- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/packages/devtools_app/lib/src/shared/analytics/_analytics_web.dart b/packages/devtools_app/lib/src/shared/analytics/_analytics_web.dart index 4dba24e3488..a12a076177e 100644 --- a/packages/devtools_app/lib/src/shared/analytics/_analytics_web.dart +++ b/packages/devtools_app/lib/src/shared/analytics/_analytics_web.dart @@ -89,6 +89,7 @@ extension type GtagEventDevTools._(JSObject _) implements GtagEvent { // "onDebugPrompt" - user responded to prompt when running a debug session // "languageStatus" - launched from the language status popout String? ide_launched_feature, + String? is_wasm, // dimension13 whether DevTools is running with WASM. // Performance screen metrics. See [PerformanceScreenMetrics]. int? ui_duration_micros, // metric1 @@ -122,6 +123,7 @@ extension type GtagEventDevTools._(JSObject _) implements GtagEvent { external String? get is_embedded; external String? get g3_username; external String? get ide_launched_feature; + external String? get is_wasm; // Custom metrics: external int? get ui_duration_micros; @@ -169,6 +171,7 @@ extension type GtagExceptionDevTools._(JSObject _) implements GtagException { // "onDebugPrompt" - user responded to prompt when running a debug session // "languageStatus" - launched from the language status popout String? ide_launched_feature, + String? is_wasm, // dimension13 whether DevTools is running with WASM. // Performance screen metrics. See [PerformanceScreenMetrics]. int? ui_duration_micros, // metric1 @@ -202,6 +205,7 @@ extension type GtagExceptionDevTools._(JSObject _) implements GtagException { external String? get is_embedded; external String? get g3_username; external String? get ide_launched_feature; + external String? get is_wasm; // Custom metrics: external int? get ui_duration_micros; @@ -246,6 +250,7 @@ GtagEventDevTools _gtagEvent({ is_embedded: isEmbedded().toString(), g3_username: devToolsEnvironmentParameters.username(), ide_launched_feature: ideLaunchedFeature, + is_wasm: kIsWasm.toString(), // [PerformanceScreenMetrics] ui_duration_micros: screenMetrics is PerformanceScreenMetrics ? screenMetrics.uiDuration?.inMicroseconds @@ -311,6 +316,7 @@ GtagExceptionDevTools _gtagException( is_embedded: isEmbedded().toString(), g3_username: devToolsEnvironmentParameters.username(), ide_launched_feature: ideLaunchedFeature, + is_wasm: kIsWasm.toString(), // [PerformanceScreenMetrics] ui_duration_micros: screenMetrics is PerformanceScreenMetrics ? screenMetrics.uiDuration?.inMicroseconds @@ -612,16 +618,13 @@ void reportError( if (_lastGaError == errorMessage) return; _lastGaError = errorMessage; - GTag.exception( - gaExceptionProvider: () => _gtagException( - errorMessage, - fatal: fatal, - ), + final gTagException = _gtagException( + errorMessage, + fatal: fatal, ); + GTag.exception(gaExceptionProvider: () => gTagException); - // TODO(kenz): we may want to create a new event `devtoolsException` if we - // need all of our custom dimensions logged with exceptions. - final uaEvent = ua.Event.exception(exception: errorMessage); + final uaEvent = uaEventFromGtagException(gTagException); unawaited(dtdManager.sendAnalyticsEvent(uaEvent)); } @@ -867,6 +870,7 @@ ua.Event uaEventFromGtagEvent(GtagEventDevTools gtagEvent) { isExternalBuild: gtagEvent.is_external_build, isEmbedded: gtagEvent.is_embedded, ideLaunchedFeature: gtagEvent.ide_launched_feature, + isWasm: gtagEvent.is_wasm, g3Username: gtagEvent.g3_username, uiDurationMicros: gtagEvent.ui_duration_micros, rasterDurationMicros: gtagEvent.raster_duration_micros, @@ -883,3 +887,37 @@ ua.Event uaEventFromGtagEvent(GtagEventDevTools gtagEvent) { inspectorTreeControllerId: gtagEvent.inspector_tree_controller_id, ); } + +ua.Event uaEventFromGtagException(GtagExceptionDevTools gtagException) { + return ua.Event.exception( + exception: gtagException.description ?? 'unknown exception', + data: { + 'fatal': gtagException.fatal, + 'userApp': gtagException.user_app, + 'userBuild': gtagException.user_build, + 'userPlatform': gtagException.user_platform, + 'devtoolsPlatform': gtagException.devtools_platform, + 'devtoolsChrome': gtagException.devtools_chrome, + 'devtoolsVersion': gtagException.devtools_version, + 'ideLaunched': gtagException.ide_launched, + 'isExternalBuild': gtagException.is_external_build, + 'isEmbedded': gtagException.is_embedded, + 'ideLaunchedFeature': gtagException.ide_launched_feature, + 'isWasm': gtagException.is_wasm, + 'g3Username': gtagException.g3_username, + 'uiDurationMicros': gtagException.ui_duration_micros, + 'rasterDurationMicros': gtagException.raster_duration_micros, + 'shaderCompilationDurationMicros': + gtagException.shader_compilation_duration_micros, + 'traceEventCount': gtagException.trace_event_count, + 'cpuSampleCount': gtagException.cpu_sample_count, + 'cpuStackDepth': gtagException.cpu_stack_depth, + 'heapDiffObjectsBefore': gtagException.heap_diff_objects_before, + 'heapDiffObjectsAfter': gtagException.heap_diff_objects_after, + 'heapObjectsTotal': gtagException.heap_objects_total, + 'rootSetCount': gtagException.root_set_count, + 'rowCount': gtagException.row_count, + 'inspectorTreeControllerId': gtagException.inspector_tree_controller_id, + }, + ); +} From 5857f27bbfed2f13ebca7407cd62c6084c6547ff Mon Sep 17 00:00:00 2001 From: Kenzie Davisson Date: Tue, 10 Sep 2024 09:15:13 -0700 Subject: [PATCH 16/24] revert this --- flutter-candidate.txt | 2 +- .../lib/src/shared/common_widgets.dart | 20 +++++++++---------- .../lib/src/shared/feature_flags.dart | 2 +- packages/devtools_app/pubspec.yaml | 2 ++ 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/flutter-candidate.txt b/flutter-candidate.txt index 4eec4d6c3ae..9a66865d80c 100644 --- a/flutter-candidate.txt +++ b/flutter-candidate.txt @@ -1 +1 @@ -aea84342eb861832bbf8578de432c4da5a074136 +3e4f1cdf494e09304587f648456f2560d3628fe9 diff --git a/packages/devtools_app/lib/src/shared/common_widgets.dart b/packages/devtools_app/lib/src/shared/common_widgets.dart index ac33add94e3..a24d6f2e49b 100644 --- a/packages/devtools_app/lib/src/shared/common_widgets.dart +++ b/packages/devtools_app/lib/src/shared/common_widgets.dart @@ -893,11 +893,11 @@ extension ColorExtension on Color { percent = 1.0 - percent; final c = this; - return Color.from( - alpha: c.a, - red: c.r * percent, - green: c.g * percent, - blue: c.b * percent, + return Color.fromARGB( + c.alpha, + (c.red * percent).round(), + (c.green * percent).round(), + (c.blue * percent).round(), ); } @@ -906,11 +906,11 @@ extension ColorExtension on Color { assert(0.0 <= percent && percent <= 1.0); final c = this; - return Color.from( - alpha: c.a, - red: c.r + ((1.0 - c.r) * percent), - green: c.g + ((1.0 - c.g) * percent), - blue: c.b + ((1.0 - c.b) * percent), + return Color.fromARGB( + c.alpha, + c.red + ((255 - c.red) * percent).round(), + c.green + ((255 - c.green) * percent).round(), + c.blue + ((255 - c.blue) * percent).round(), ); } } diff --git a/packages/devtools_app/lib/src/shared/feature_flags.dart b/packages/devtools_app/lib/src/shared/feature_flags.dart index 455858c31ef..319d641c01e 100644 --- a/packages/devtools_app/lib/src/shared/feature_flags.dart +++ b/packages/devtools_app/lib/src/shared/feature_flags.dart @@ -102,7 +102,7 @@ abstract class FeatureFlags { /// Flag to enable the DevTools setting to opt-in to WASM. /// /// https://github.com/flutter/devtools/issues/7856 - static bool wasmOptInSetting = enableExperiments; + static bool wasmOptInSetting = true; /// Stores a map of all the feature flags for debugging purposes. /// diff --git a/packages/devtools_app/pubspec.yaml b/packages/devtools_app/pubspec.yaml index 55328e7dd66..d31a14a76a2 100644 --- a/packages/devtools_app/pubspec.yaml +++ b/packages/devtools_app/pubspec.yaml @@ -174,3 +174,5 @@ dependency_overrides: path: ../devtools_test devtools_extensions: path: ../devtools_extensions + unified_analytics: + path: ../../../dart-sdk/tools/pkgs/unified_analytics From e4f217e3e67796651bce341a6479f57b304ee2d0 Mon Sep 17 00:00:00 2001 From: Kenzie Davisson Date: Tue, 10 Sep 2024 10:32:23 -0700 Subject: [PATCH 17/24] Suppress notification in VS Code --- .../src/shared/preferences/preferences.dart | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/devtools_app/lib/src/shared/preferences/preferences.dart b/packages/devtools_app/lib/src/shared/preferences/preferences.dart index 337bf7371ab..165e4e03e87 100644 --- a/packages/devtools_app/lib/src/shared/preferences/preferences.dart +++ b/packages/devtools_app/lib/src/shared/preferences/preferences.dart @@ -173,7 +173,8 @@ class PreferencesController extends DisposableController _ExperimentPreferences.wasm.storageKey, defaultsTo: false, ); - final enabledFromQueryParams = DevToolsQueryParams.load().useWasm; + final queryParams = DevToolsQueryParams.load(); + final enabledFromQueryParams = queryParams.useWasm; if (enabledFromQueryParams && !kIsWasm) { // If we hit this case, we tried to load DevTools with WASM but we fell @@ -182,11 +183,19 @@ class PreferencesController extends DisposableController // DevTools with wasm. Remove the wasm query parameter and return early. updateQueryParameter(DevToolsQueryParams.wasmKey, null); ga.impression(gac.devToolsMain, gac.jsFallback); - // TODO(kenz): supress for VS Code - notificationService.push( - 'Something went wrong when trying to load DevTools with WebAssembly. ' - 'Falling back to Javascript.', - ); + + // Do not show the JS fallback notification when embedded in VS Code + // because we do not expect the WASM build to load successfully by + // default. This is because cross-origin-isolation is disabled by VS + // Code. See https://github.com/microsoft/vscode/issues/186614. + final embeddedInVsCode = + queryParams.embedMode.embedded && queryParams.ide == 'VSCode'; + if (!embeddedInVsCode) { + notificationService.push( + 'Something went wrong when trying to load DevTools with WebAssembly. ' + 'Falling back to Javascript.', + ); + } return; } From 991218adeafb757d0d9bb8e0df233ecdf83af123 Mon Sep 17 00:00:00 2001 From: Kenzie Davisson Date: Tue, 10 Sep 2024 10:36:35 -0700 Subject: [PATCH 18/24] revert stuff --- flutter-candidate.txt | 2 +- .../lib/src/shared/common_widgets.dart | 20 +++++++++---------- .../lib/src/shared/feature_flags.dart | 2 +- packages/devtools_app/pubspec.yaml | 2 -- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/flutter-candidate.txt b/flutter-candidate.txt index 9a66865d80c..4eec4d6c3ae 100644 --- a/flutter-candidate.txt +++ b/flutter-candidate.txt @@ -1 +1 @@ -3e4f1cdf494e09304587f648456f2560d3628fe9 +aea84342eb861832bbf8578de432c4da5a074136 diff --git a/packages/devtools_app/lib/src/shared/common_widgets.dart b/packages/devtools_app/lib/src/shared/common_widgets.dart index a24d6f2e49b..ac33add94e3 100644 --- a/packages/devtools_app/lib/src/shared/common_widgets.dart +++ b/packages/devtools_app/lib/src/shared/common_widgets.dart @@ -893,11 +893,11 @@ extension ColorExtension on Color { percent = 1.0 - percent; final c = this; - return Color.fromARGB( - c.alpha, - (c.red * percent).round(), - (c.green * percent).round(), - (c.blue * percent).round(), + return Color.from( + alpha: c.a, + red: c.r * percent, + green: c.g * percent, + blue: c.b * percent, ); } @@ -906,11 +906,11 @@ extension ColorExtension on Color { assert(0.0 <= percent && percent <= 1.0); final c = this; - return Color.fromARGB( - c.alpha, - c.red + ((255 - c.red) * percent).round(), - c.green + ((255 - c.green) * percent).round(), - c.blue + ((255 - c.blue) * percent).round(), + return Color.from( + alpha: c.a, + red: c.r + ((1.0 - c.r) * percent), + green: c.g + ((1.0 - c.g) * percent), + blue: c.b + ((1.0 - c.b) * percent), ); } } diff --git a/packages/devtools_app/lib/src/shared/feature_flags.dart b/packages/devtools_app/lib/src/shared/feature_flags.dart index 319d641c01e..455858c31ef 100644 --- a/packages/devtools_app/lib/src/shared/feature_flags.dart +++ b/packages/devtools_app/lib/src/shared/feature_flags.dart @@ -102,7 +102,7 @@ abstract class FeatureFlags { /// Flag to enable the DevTools setting to opt-in to WASM. /// /// https://github.com/flutter/devtools/issues/7856 - static bool wasmOptInSetting = true; + static bool wasmOptInSetting = enableExperiments; /// Stores a map of all the feature flags for debugging purposes. /// diff --git a/packages/devtools_app/pubspec.yaml b/packages/devtools_app/pubspec.yaml index d31a14a76a2..55328e7dd66 100644 --- a/packages/devtools_app/pubspec.yaml +++ b/packages/devtools_app/pubspec.yaml @@ -174,5 +174,3 @@ dependency_overrides: path: ../devtools_test devtools_extensions: path: ../devtools_extensions - unified_analytics: - path: ../../../dart-sdk/tools/pkgs/unified_analytics From e738552afd73798d5746663260b0bcc0659516d4 Mon Sep 17 00:00:00 2001 From: Kenzie Davisson Date: Tue, 10 Sep 2024 11:21:36 -0700 Subject: [PATCH 19/24] query param fix --- .../devtools_app/lib/src/framework/framework_core.dart | 7 +++++-- packages/devtools_app/web/flutter_bootstrap.js | 10 +++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/devtools_app/lib/src/framework/framework_core.dart b/packages/devtools_app/lib/src/framework/framework_core.dart index 1d7a4208463..788745c6a85 100644 --- a/packages/devtools_app/lib/src/framework/framework_core.dart +++ b/packages/devtools_app/lib/src/framework/framework_core.dart @@ -9,6 +9,7 @@ import 'package:devtools_app_shared/ui.dart'; import 'package:devtools_app_shared/utils.dart'; import 'package:devtools_shared/devtools_shared.dart'; import 'package:devtools_shared/service.dart'; +import 'package:flutter/foundation.dart'; import 'package:logging/logging.dart'; import 'package:vm_service/vm_service.dart'; @@ -46,8 +47,10 @@ abstract class FrameworkCore { await initializePlatform(); - // Print the version number at startup. - _log.info('DevTools version $devToolsVersion.'); + // Print DevTools info at startup. + _log.info( + 'Version: $devToolsVersion, Renderer: ${kIsWasm ? 'skwasm' : 'canvaskit'}', + ); await _initDTDConnection(); diff --git a/packages/devtools_app/web/flutter_bootstrap.js b/packages/devtools_app/web/flutter_bootstrap.js index 244ce5ca36b..73b004f6aee 100644 --- a/packages/devtools_app/web/flutter_bootstrap.js +++ b/packages/devtools_app/web/flutter_bootstrap.js @@ -44,7 +44,7 @@ async function getDevToolsWasmPreference() { // based on the value of the 'wasm' query parameter or the wasm setting from the // DevTools preference file. This method also updates the 'wasm' query parameter // to reflect the renderer that will be used. -async function getRenderer() { +async function getRendererAndUpdateQueryParameter() { // This query parameter must match the String value specified by // `DevToolsQueryParameters.wasmKey`. See // devtools/packages/devtools_app/lib/src/shared/query_parameters.dart @@ -58,7 +58,11 @@ async function getRenderer() { const url = new URL(window.location.href); // Ensure the 'wasm' query parameter in the URL is accurate for the renderer // DevTools will be loaded with. - url.searchParams.set(wasmQueryParameterKey, shouldUseSkwasm ? 'true' : ''); + if (shouldUseSkwasm) { + url.searchParams.set(wasmQueryParameterKey, 'true'); + } else { + url.searchParams.delete(wasmQueryParameterKey); + } // Update the browser's history without reloading. This is a no-op if the wasm // query parameter does not actually need to be updated. window.history.pushState({}, '', url); @@ -68,7 +72,7 @@ async function getRenderer() { // Bootstrap app for 3P environments: async function bootstrapAppFor3P() { - const renderer = await getRenderer(); + const renderer = await getRendererAndUpdateQueryParameter(); console.log('Loading DevTools with ' + renderer + ' renderer.'); _flutter.loader.load({ serviceWorkerSettings: { From 16398ded51104393fde42a24d6573ac236b73baf Mon Sep 17 00:00:00 2001 From: Kenzie Davisson Date: Tue, 10 Sep 2024 11:27:25 -0700 Subject: [PATCH 20/24] fix rnotes --- .../release_notes/NEXT_RELEASE_NOTES.md | 2 +- .../release_notes/images/wasm_setting.png | Bin 46170 -> 43352 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md index a45b24822ef..377dc1bb466 100644 --- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md +++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md @@ -16,7 +16,7 @@ show. - [#8277](https://github.com/flutter/devtools/pull/8277) * Add a setting that allows users to opt-in to loading DevTools with WebAssembly. - [#8270](https://github.com/flutter/devtools/pull/8270) -![Wasm opt-in setting](images/wasm_setting.png "DevTools setting to opt into wasm.") + ![Wasm opt-in setting](images/wasm_setting.png "DevTools setting to opt into wasm.") ## Inspector updates diff --git a/packages/devtools_app/release_notes/images/wasm_setting.png b/packages/devtools_app/release_notes/images/wasm_setting.png index e3aabc32e93b1d94d14082b12334cc28a2804f67..73bbc659781efe5c90de99a96c73dae88199d363 100644 GIT binary patch literal 43352 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 From 985a1d96f5f99ba75cb85e95709a2103186613a0 Mon Sep 17 00:00:00 2001 From: Kenzie Davisson Date: Tue, 10 Sep 2024 11:49:34 -0700 Subject: [PATCH 21/24] enable feature flag --- packages/devtools_app/lib/src/shared/feature_flags.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/devtools_app/lib/src/shared/feature_flags.dart b/packages/devtools_app/lib/src/shared/feature_flags.dart index 455858c31ef..319d641c01e 100644 --- a/packages/devtools_app/lib/src/shared/feature_flags.dart +++ b/packages/devtools_app/lib/src/shared/feature_flags.dart @@ -102,7 +102,7 @@ abstract class FeatureFlags { /// Flag to enable the DevTools setting to opt-in to WASM. /// /// https://github.com/flutter/devtools/issues/7856 - static bool wasmOptInSetting = enableExperiments; + static bool wasmOptInSetting = true; /// Stores a map of all the feature flags for debugging purposes. /// From ca2b7afadd12622b91d9b483dc229028d4897751 Mon Sep 17 00:00:00 2001 From: Kenzie Davisson Date: Tue, 10 Sep 2024 12:10:30 -0700 Subject: [PATCH 22/24] fix test --- .../devtools_app/test/shared/primitives/feature_flags_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/devtools_app/test/shared/primitives/feature_flags_test.dart b/packages/devtools_app/test/shared/primitives/feature_flags_test.dart index 5dd5268bef2..caceaab59e1 100644 --- a/packages/devtools_app/test/shared/primitives/feature_flags_test.dart +++ b/packages/devtools_app/test/shared/primitives/feature_flags_test.dart @@ -16,6 +16,6 @@ void main() { expect(FeatureFlags.loggingV2, false); expect(FeatureFlags.dapDebugging, false); expect(FeatureFlags.inspectorV2, false); - expect(FeatureFlags.wasmOptInSetting, false); + expect(FeatureFlags.wasmOptInSetting, true); }); } From b247b5eba01b7c2bd6406d81e107d727d478ee57 Mon Sep 17 00:00:00 2001 From: Kenzie Davisson Date: Wed, 11 Sep 2024 11:00:01 -0700 Subject: [PATCH 23/24] bootstrap changes --- .../devtools_app/web/flutter_bootstrap.js | 49 +++++++++++-------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/packages/devtools_app/web/flutter_bootstrap.js b/packages/devtools_app/web/flutter_bootstrap.js index 73b004f6aee..218b66d8996 100644 --- a/packages/devtools_app/web/flutter_bootstrap.js +++ b/packages/devtools_app/web/flutter_bootstrap.js @@ -21,6 +21,11 @@ function unregisterDevToolsServiceWorker() { } } +// This query parameter must match the String value specified by +// `DevToolsQueryParameters.wasmKey`. See +// devtools/packages/devtools_app/lib/src/shared/query_parameters.dart +const wasmQueryParameterKey = 'wasm'; + // Calls the DevTools server API to read the user's wasm preference. async function getDevToolsWasmPreference() { const request = 'api/getPreferenceValue?key=experiment.wasm'; @@ -40,25 +45,21 @@ async function getDevToolsWasmPreference() { } } -// Gets the renderer ('canvaskit' or 'skwasm') for the Flutter bootstrap config -// based on the value of the 'wasm' query parameter or the wasm setting from the -// DevTools preference file. This method also updates the 'wasm' query parameter -// to reflect the renderer that will be used. -async function getRendererAndUpdateQueryParameter() { - // This query parameter must match the String value specified by - // `DevToolsQueryParameters.wasmKey`. See - // devtools/packages/devtools_app/lib/src/shared/query_parameters.dart - const wasmQueryParameterKey = 'wasm'; - +// Returns whether DevTools should be loaded with the skwasm renderer based on the +// value of the 'wasm' query parameter or the wasm setting from the DevTools +// preference file. +async function shouldUseSkwasm() { const searchParams = new URLSearchParams(window.location.search); const wasmEnabledFromQueryParameter = searchParams.get(wasmQueryParameterKey) === 'true'; const wasmEnabledFromDevToolsPreference = await getDevToolsWasmPreference(); - const shouldUseSkwasm = wasmEnabledFromQueryParameter === true || wasmEnabledFromDevToolsPreference === true; - + return wasmEnabledFromQueryParameter === true || wasmEnabledFromDevToolsPreference === true; +} + +// Sets or removes the 'wasm' query parameter based on whether DevTools should +// be loaded with the skwasm renderer. +function updateWasmQueryParameter(useSkwasm) { const url = new URL(window.location.href); - // Ensure the 'wasm' query parameter in the URL is accurate for the renderer - // DevTools will be loaded with. - if (shouldUseSkwasm) { + if (useSkwasm) { url.searchParams.set(wasmQueryParameterKey, 'true'); } else { url.searchParams.delete(wasmQueryParameterKey); @@ -66,21 +67,29 @@ async function getRendererAndUpdateQueryParameter() { // Update the browser's history without reloading. This is a no-op if the wasm // query parameter does not actually need to be updated. window.history.pushState({}, '', url); - - return shouldUseSkwasm ? 'skwasm' : 'canvaskit'; } // Bootstrap app for 3P environments: async function bootstrapAppFor3P() { - const renderer = await getRendererAndUpdateQueryParameter(); - console.log('Loading DevTools with ' + renderer + ' renderer.'); + const useSkwasm = await shouldUseSkwasm(); + + // Ensure the 'wasm' query parameter in the URL is accurate for the renderer + // DevTools will be loaded with. + updateWasmQueryParameter(useSkwasm); + + const rendererForLog = useSkwasm ? 'skwasm' : 'canvaskit'; + console.log('Attempting to load DevTools with ' + rendererForLog + ' renderer.'); + + const rendererConfig = useSkwasm ? {} : { renderer: 'canvaskit' }; + console.log('using renderer config: '); + console.log(rendererConfig); _flutter.loader.load({ serviceWorkerSettings: { serviceWorkerVersion: {{flutter_service_worker_version}}, }, config: { canvasKitBaseUrl: 'canvaskit/', - renderer: renderer, + ...rendererConfig, } }); } From 8c3c7983cff70fde7bf098cb5a4956d8090ae82d Mon Sep 17 00:00:00 2001 From: Kenzie Davisson Date: Wed, 11 Sep 2024 11:45:53 -0700 Subject: [PATCH 24/24] remove print --- packages/devtools_app/web/flutter_bootstrap.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/devtools_app/web/flutter_bootstrap.js b/packages/devtools_app/web/flutter_bootstrap.js index 218b66d8996..e63cd54c178 100644 --- a/packages/devtools_app/web/flutter_bootstrap.js +++ b/packages/devtools_app/web/flutter_bootstrap.js @@ -81,7 +81,6 @@ async function bootstrapAppFor3P() { console.log('Attempting to load DevTools with ' + rendererForLog + ' renderer.'); const rendererConfig = useSkwasm ? {} : { renderer: 'canvaskit' }; - console.log('using renderer config: '); console.log(rendererConfig); _flutter.loader.load({ serviceWorkerSettings: {