From d9cb750cd2dbd058cb152ed16ff92bc66ab7b06d Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Wed, 14 Jan 2026 11:50:38 -0800 Subject: [PATCH 1/3] Fix `?compiler=js` escape hatch for wasm-compiled DevTools (#9615) --- .../lib/src/shared/analytics/constants.dart | 4 ++++ .../src/shared/preferences/preferences.dart | 19 ++++++++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/devtools_app/lib/src/shared/analytics/constants.dart b/packages/devtools_app/lib/src/shared/analytics/constants.dart index 68ca5b59077..45450f842d9 100644 --- a/packages/devtools_app/lib/src/shared/analytics/constants.dart +++ b/packages/devtools_app/lib/src/shared/analytics/constants.dart @@ -57,6 +57,10 @@ const memoryPressureReduce = 'memoryPressureReduce'; /// Wasm. const jsFallback = 'jsFallback'; +/// Event that signals we forced the dart2js compiler via the compiler=js query +/// parameter. +const forceLoadJs = 'forceLoadJs'; + // 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 d1ae1de01de..06ee38c5f8b 100644 --- a/packages/devtools_app/lib/src/shared/preferences/preferences.dart +++ b/packages/devtools_app/lib/src/shared/preferences/preferences.dart @@ -42,7 +42,14 @@ enum _ExperimentPreferences { /// Whether a user has opted out of the dart2wasm experiment. wasmOptOut; - String get storageKey => '$storagePrefix.$name'; + String get storageKey { + if (name == 'wasm') { + _log.warning( + '[deprecated] The "wasm" key is deprecated, use "wasmOptOut" instead.', + ); + } + return '$storagePrefix.$name'; + } static const storagePrefix = 'experiment'; } @@ -180,13 +187,16 @@ class PreferencesController extends DisposableController Future _initWasmEnabled() async { wasmEnabled.value = kIsWasm; + final queryParams = DevToolsQueryParams.load(); // If the user forced the dart2js-compiled DevTools via query parameter, // then set the storage value to match. This will persist across multiple // sessions of DevTools. - if (DevToolsQueryParams.load().useJs) { + final jsEnabledFromQueryParams = queryParams.useJs; + if (jsEnabledFromQueryParams) { safeUnawaited( - storage.setValue(_ExperimentPreferences.wasm.storageKey, 'false'), + storage.setValue(_ExperimentPreferences.wasmOptOut.storageKey, 'true'), ); + ga.impression(gac.devToolsMain, gac.forceLoadJs); } addAutoDisposeListener(wasmEnabled, () async { @@ -218,8 +228,6 @@ class PreferencesController extends DisposableController defaultsTo: false, ); final enabledFromStorage = !optOutFromStorage; - - final queryParams = DevToolsQueryParams.load(); final enabledFromQueryParams = queryParams.useWasm; if (enabledFromQueryParams && !kIsWasm) { @@ -247,6 +255,7 @@ class PreferencesController extends DisposableController final shouldEnableWasm = (enabledFromStorage || enabledFromQueryParams) && + !jsEnabledFromQueryParams && kIsWeb && // Wasm cannot be enabled if DevTools was built using `flutter run`. !usingDebugDevToolsServer; From cec51cbf9f2f47151a3024f3383ff6cfd53af77d Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Thu, 15 Jan 2026 12:42:10 -0800 Subject: [PATCH 2/3] [dart2wasm] Legacy URL format is correctly mapped to new format when `compiler` query parameter is present (#9617) --- .../lib/src/shared/primitives/url_utils.dart | 16 +++++++ .../shared/primitives/url_utils_test.dart | 43 +++++++++++++++++++ .../devtools_app/web/flutter_bootstrap.js | 4 ++ 3 files changed, 63 insertions(+) diff --git a/packages/devtools_app/lib/src/shared/primitives/url_utils.dart b/packages/devtools_app/lib/src/shared/primitives/url_utils.dart index 632f487fb80..933706bb87e 100644 --- a/packages/devtools_app/lib/src/shared/primitives/url_utils.dart +++ b/packages/devtools_app/lib/src/shared/primitives/url_utils.dart @@ -14,6 +14,9 @@ String extractCurrentPageFromUrl(String url) { : uri.path.substring(1); } +const _jsCompilerParam = '?compiler=js'; +const _wasmCompilerParam = '?compiler=wasm'; + /// Maps DevTools URLs in the original fragment format onto the equivalent URLs /// in the new URL format. /// @@ -25,9 +28,16 @@ String? mapLegacyUrl(String url) { // http://localhost:123/#/?page=inspector&uri=ws://... final isRootRequest = uri.path == '/' || uri.path.endsWith('/devtools/'); if (isRootRequest && uri.fragment.isNotEmpty) { + // Note: If there is a ?compiler= query parameter, we remove it from before + // the hash then add it back in as a query parameter at the end. + // See https://github.com/flutter/devtools/issues/9612 for details. + final hasJsParam = url.contains(_jsCompilerParam); + final hasWasmParam = url.contains(_wasmCompilerParam) && !hasJsParam; final basePath = uri.path; // Convert the URL by removing the fragment separator. final newUrl = url + .replaceAll(_jsCompilerParam, '') + .replaceAll(_wasmCompilerParam, '') // Handle localhost:123/#/inspector?uri=xxx .replaceFirst('/#/', '/') // Handle localhost:123/#?page=inspector&uri=xxx @@ -35,6 +45,12 @@ String? mapLegacyUrl(String url) { // Move page names from the querystring into the path. var newUri = Uri.parse(newUrl); + final queryParams = { + ...newUri.queryParameters, + if (hasJsParam) 'compiler': 'js', + if (hasWasmParam) 'compiler': 'wasm', + }; + newUri = newUri.replace(queryParameters: queryParams); final page = newUri.queryParameters['page']; if (newUri.path == basePath && page != null) { final newParams = {...newUri.queryParameters}..remove('page'); diff --git a/packages/devtools_app/test/shared/primitives/url_utils_test.dart b/packages/devtools_app/test/shared/primitives/url_utils_test.dart index ba1ee16ca42..16494d1312e 100644 --- a/packages/devtools_app/test/shared/primitives/url_utils_test.dart +++ b/packages/devtools_app/test/shared/primitives/url_utils_test.dart @@ -65,6 +65,49 @@ void main() { test('maps legacy URIs with no page names', () { expect(mapLegacyUrl('$prefix/#/?foo=bar'), '$prefix/?foo=bar'); }); + + group( + 'with "compiler" query param (https://github.com/flutter/devtools/issues/9612)', + () { + for (final compilerValue in ['js', 'wasm']) { + test( + 'moves ?compiler=$compilerValue from before hash to after path', + () { + final newUrl = '$prefix/inspector?compiler=$compilerValue'; + expect( + mapLegacyUrl( + '$prefix/?compiler=$compilerValue#/inspector', + ), + newUrl, + ); + expect( + mapLegacyUrl( + '$prefix/?compiler=$compilerValue#/?page=inspector', + ), + newUrl, + ); + }, + ); + + test('handles additional query parameters', () { + final newUrl = + '$prefix/inspector?foo=bar&compiler=$compilerValue'; + expect( + mapLegacyUrl( + '$prefix/?compiler=$compilerValue#/inspector?foo=bar', + ), + newUrl, + ); + expect( + mapLegacyUrl( + '$prefix/?compiler=$compilerValue#/?page=inspector&foo=bar', + ), + newUrl, + ); + }); + } + }, + ); }); } }); diff --git a/packages/devtools_app/web/flutter_bootstrap.js b/packages/devtools_app/web/flutter_bootstrap.js index dc563c3b782..ee6a54d68db 100644 --- a/packages/devtools_app/web/flutter_bootstrap.js +++ b/packages/devtools_app/web/flutter_bootstrap.js @@ -84,6 +84,10 @@ async function shouldUseSkwasm() { // Sets or removes the 'wasm' query parameter based on whether DevTools should // be loaded with the skwasm renderer. +// +// Note: In the case of the legacy-formatted URL, this adds the query parameter +// in the wrong place. We fix this in the Dart mapLegacyUrl function. Details: +// https://github.com/flutter/devtools/issues/9612 function updateWasmQueryParameter(useSkwasm) { const url = new URL(window.location.href); if (useSkwasm) { From c13b1e172794a71ffe9436a5cfe0a0a9d7509d78 Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Thu, 15 Jan 2026 14:46:20 -0800 Subject: [PATCH 3/3] Prepare cherry-pick release - DevTools 2.54.1 --- packages/devtools_app/lib/devtools.dart | 2 +- packages/devtools_app/pubspec.yaml | 2 +- pubspec.lock | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/devtools_app/lib/devtools.dart b/packages/devtools_app/lib/devtools.dart index caba9b5b273..3228209950f 100644 --- a/packages/devtools_app/lib/devtools.dart +++ b/packages/devtools_app/lib/devtools.dart @@ -10,4 +10,4 @@ /// Note: a regexp in the `dt update-version' command logic matches the constant /// declaration `const version =`. If you change the declaration you must also /// modify the regex in the `dt update-version' command logic. -const version = '2.54.0'; +const version = '2.54.1'; diff --git a/packages/devtools_app/pubspec.yaml b/packages/devtools_app/pubspec.yaml index e17f6a0db18..0a706bf7e4b 100644 --- a/packages/devtools_app/pubspec.yaml +++ b/packages/devtools_app/pubspec.yaml @@ -7,7 +7,7 @@ publish_to: none # Note: this version should only be updated by running the 'dt update-version' # command that updates the version here and in 'devtools.dart'. -version: 2.54.0 +version: 2.54.1 repository: https://github.com/flutter/devtools/tree/master/packages/devtools_app diff --git a/pubspec.lock b/pubspec.lock index c6ab1cdb244..7127d25f17d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -109,10 +109,10 @@ packages: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" checked_yaml: dependency: transitive description: