Skip to content

Commit

Permalink
Add wasm opt-in setting (flutter#8270)
Browse files Browse the repository at this point in the history
  • Loading branch information
kenzieschmoll authored Sep 11, 2024
1 parent fd2988f commit 7efeecf
Show file tree
Hide file tree
Showing 12 changed files with 222 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
},
Expand Down
7 changes: 5 additions & 2 deletions packages/devtools_app/lib/src/framework/framework_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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();

Expand Down
26 changes: 25 additions & 1 deletion packages/devtools_app/lib/src/framework/settings_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -37,6 +38,7 @@ class SettingsDialog extends StatelessWidget {

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final analyticsController = Provider.of<AnalyticsController>(context);
return DevToolsDialog(
title: const DialogTitleText('Settings'),
Expand All @@ -50,6 +52,7 @@ class SettingsDialog extends StatelessWidget {
title: 'Use a dark theme',
notifier: preferences.darkModeEnabled,
onChanged: preferences.toggleDarkModeTheme,
gaScreen: gac.settingsDialog,
gaItem: gac.darkTheme,
),
),
Expand All @@ -61,6 +64,7 @@ class SettingsDialog extends StatelessWidget {
onChanged: (enable) => unawaited(
analyticsController.toggleAnalyticsEnabled(enable),
),
gaScreen: gac.settingsDialog,
gaItem: gac.analytics,
),
),
Expand All @@ -69,10 +73,29 @@ class SettingsDialog extends StatelessWidget {
title: 'Enable VM developer mode',
notifier: preferences.vmDeveloperModeEnabled,
onChanged: preferences.toggleVmDeveloperMode,
gaScreen: gac.settingsDialog,
gaItem: gac.vmDeveloperMode,
),
),
const PaddedDivider(),
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(),
],
),
Expand All @@ -99,6 +122,7 @@ class _VerboseLoggingSetting extends StatelessWidget {
title: 'Enable verbose logging',
notifier: preferences.verboseLoggingEnabled,
onChanged: (enable) => preferences.toggleVerboseLogging(enable),
gaScreen: gac.settingsDialog,
gaItem: gac.verboseLogging,
),
),
Expand Down
5 changes: 5 additions & 0 deletions packages/devtools_app/lib/src/shared/analytics/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -114,6 +118,7 @@ const settingsDialog = 'settings';
const darkTheme = 'darkTheme';
const analytics = 'analytics';
const vmDeveloperMode = 'vmDeveloperMode';
const wasm = 'wasm';
const verboseLogging = 'verboseLogging';
const inspectorHoverEvalMode = 'inspectorHoverEvalMode';
const clearLogs = 'clearLogs';
Expand Down
2 changes: 1 addition & 1 deletion packages/devtools_app/lib/src/shared/common_widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1557,7 +1557,7 @@ class CheckboxSetting extends StatelessWidget {
final gaScreen = this.gaScreen;
final gaItem = this.gaItem;
if (gaScreen != null && gaItem != null) {
ga.select(gaScreen, gaItem);
ga.select(gaScreen, '$gaItem-$value');
}
final onChanged = this.onChanged;
if (onChanged != null) {
Expand Down
12 changes: 9 additions & 3 deletions packages/devtools_app/lib/src/shared/feature_flags.dart
Original file line number Diff line number Diff line change
Expand Up @@ -99,17 +99,23 @@ 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 = true;

/// 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
/// well.
static final _allFlags = <String, bool>{
'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.
Expand Down
91 changes: 91 additions & 0 deletions packages/devtools_app/lib/src/shared/preferences/preferences.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ 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';

part '_extension_preferences.dart';
Expand All @@ -25,8 +27,19 @@ 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.
enum _ExperimentPreferences {
wasm;

String get storageKey => '$storagePrefix.$name';

static const storagePrefix = 'experiment';
}

/// DevTools preferences for UI-related settings.
enum _UiPreferences {
darkMode,
Expand Down Expand Up @@ -59,6 +72,10 @@ class PreferencesController extends DisposableController

final vmDeveloperModeEnabled = ValueNotifier<bool>(false);

/// Whether DevTools should loaded with the dart2wasm + skwasm instead of
/// dart2js + canvaskit
final wasmEnabled = ValueNotifier<bool>(false);

final verboseLoggingEnabled =
ValueNotifier<bool>(Logger.root.level == verboseLoggingLevel);

Expand All @@ -83,6 +100,9 @@ class PreferencesController extends DisposableController
// Get the current values and listen for and write back changes.
await _initDarkMode();
await _initVmDeveloperMode();
if (FeatureFlags.wasmOptInSetting) {
await _initWasmEnabled();
}
await _initVerboseLogging();

await inspector.init();
Expand Down Expand Up @@ -123,6 +143,70 @@ class PreferencesController extends DisposableController
});
}

Future<void> _initWasmEnabled() async {
wasmEnabled.value = kIsWasm;
addAutoDisposeListener(wasmEnabled, () async {
final enabled = wasmEnabled.value;
_log.fine('preference update (wasmEnabled = $enabled)');

await storage.setValue(
_ExperimentPreferences.wasm.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) {
_log.fine(
'Reloading DevTools for Wasm preference update (enabled = $enabled)',
);
updateQueryParameter(
DevToolsQueryParams.wasmKey,
enabled ? 'true' : null,
reload: true,
);
}
});

final enabledFromStorage = await boolValueFromStorage(
_ExperimentPreferences.wasm.storageKey,
defaultsTo: false,
);
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
// 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);

// 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;
}

final shouldEnableWasm = enabledFromStorage || enabledFromQueryParams;
assert(kIsWasm == shouldEnableWasm);
// This should be a no-op if the flutter_bootstrap.js logic set the
// renderer properly, but we call this to be safe in case something went
// wrong.
toggleWasmEnabled(shouldEnableWasm);
}

Future<void> _initVerboseLogging() async {
final verboseLoggingEnabledValue = await boolValueFromStorage(
_GeneralPreferences.verboseLogging.name,
Expand Down Expand Up @@ -162,6 +246,13 @@ class PreferencesController extends DisposableController
}
}

/// Change the value of the wasm mode setting.
void toggleWasmEnabled(bool? enable) {
if (enable != null) {
wasmEnabled.value = enable;
}
}

void toggleVerboseLogging(bool? enableVerboseLogging) {
if (enableVerboseLogging != null) {
verboseLoggingEnabled.value = enableVerboseLogging;
Expand Down
9 changes: 9 additions & 0 deletions packages/devtools_app/lib/src/shared/query_parameters.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ extension type DevToolsQueryParams(Map<String, String?> params) {
/// The current [EmbedMode] of DevTools based on the query parameters.
EmbedMode get embedMode => ideThemeParams.embedMode;

/// 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';
static const hideExtensionsValue = 'extensions';
Expand All @@ -71,6 +75,11 @@ extension type DevToolsQueryParams(Map<String, String?> params) {
static const ideKey = 'ide';
static const ideFeatureKey = 'ideFeature';

// 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.
String? get legacyPage => params[legacyPageKey];
Expand Down
5 changes: 5 additions & 0 deletions packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -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, true);
});
}
Loading

0 comments on commit 7efeecf

Please sign in to comment.