Skip to content

Commit 1f8b3f5

Browse files
authored
Disable screens not compatible with DWDS websocket mode (#9481)
The DWDS websocket mode used by the Flutter `web-server` device provides a limited subset of the service protocol that doesn't include debugging capabilities, expression evaluation support, or object inspection. This change adds detection for applications running in this limited mode and hides screens that rely on unsupported functionality (e.g., debugger).
1 parent 6b9ba48 commit 1f8b3f5

File tree

4 files changed

+117
-51
lines changed

4 files changed

+117
-51
lines changed

packages/devtools_app/lib/src/shared/framework/screen.dart

Lines changed: 51 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ enum ScreenMetaData {
3232
'home',
3333
iconAsset: 'icons/app_bar/devtools.png',
3434
requiresConnection: false,
35+
supportsWebServerDevice: true,
3536
tutorialVideoTimestamp: '?t=0',
3637
),
3738
inspector(
@@ -40,6 +41,7 @@ enum ScreenMetaData {
4041
iconAsset: 'icons/app_bar/inspector.png',
4142
requiresFlutter: true,
4243
requiresDebugBuild: true,
44+
supportsWebServerDevice: true,
4345
tutorialVideoTimestamp: '?t=172',
4446
),
4547
performance(
@@ -89,6 +91,7 @@ enum ScreenMetaData {
8991
'logging',
9092
title: 'Logging',
9193
iconAsset: 'icons/app_bar/logging.png',
94+
supportsWebServerDevice: true,
9295
tutorialVideoTimestamp: '?t=558',
9396
),
9497
provider(
@@ -138,6 +141,7 @@ enum ScreenMetaData {
138141
this.requiresFlutter = false,
139142
this.requiresDebugBuild = false,
140143
this.requiresAdvancedDeveloperMode = false,
144+
this.supportsWebServerDevice = false,
141145
this.worksWithOfflineData = false,
142146
this.requiresLibrary,
143147
this.tutorialVideoTimestamp,
@@ -155,6 +159,7 @@ enum ScreenMetaData {
155159
final bool requiresFlutter;
156160
final bool requiresDebugBuild;
157161
final bool requiresAdvancedDeveloperMode;
162+
final bool supportsWebServerDevice;
158163
final bool worksWithOfflineData;
159164
final String? requiresLibrary;
160165

@@ -204,6 +209,7 @@ abstract class Screen {
204209
this.requiresFlutter = false,
205210
this.requiresDebugBuild = false,
206211
this.requiresAdvancedDeveloperMode = false,
212+
this.supportsWebServerDevice = false,
207213
this.worksWithOfflineData = false,
208214
this.showFloatingDebuggerControls = true,
209215
}) : assert(
@@ -223,6 +229,7 @@ abstract class Screen {
223229
bool requiresFlutter = false,
224230
bool requiresDebugBuild = false,
225231
bool requiresAdvancedDeveloperMode = false,
232+
bool supportsWebServerDevice = false,
226233
bool worksWithOfflineData = false,
227234
bool Function(FlutterVersion? currentVersion)? shouldShowForFlutterVersion,
228235
bool showFloatingDebuggerControls = true,
@@ -239,6 +246,7 @@ abstract class Screen {
239246
requiresFlutter: requiresFlutter,
240247
requiresDebugBuild: requiresDebugBuild,
241248
requiresAdvancedDeveloperMode: requiresAdvancedDeveloperMode,
249+
supportsWebServerDevice: supportsWebServerDevice,
242250
worksWithOfflineData: worksWithOfflineData,
243251
showFloatingDebuggerControls: showFloatingDebuggerControls,
244252
title: title,
@@ -262,6 +270,7 @@ abstract class Screen {
262270
requiresFlutter: metadata.requiresFlutter,
263271
requiresDebugBuild: metadata.requiresDebugBuild,
264272
requiresAdvancedDeveloperMode: metadata.requiresAdvancedDeveloperMode,
273+
supportsWebServerDevice: metadata.supportsWebServerDevice,
265274
worksWithOfflineData: metadata.worksWithOfflineData,
266275
shouldShowForFlutterVersion: shouldShowForFlutterVersion,
267276
showFloatingDebuggerControls: showFloatingDebuggerControls,
@@ -339,6 +348,10 @@ abstract class Screen {
339348
/// is enabled.
340349
final bool requiresAdvancedDeveloperMode;
341350

351+
/// Whether this screen should be included when the app is a web app without full debugging
352+
/// support.
353+
final bool supportsWebServerDevice;
354+
342355
/// Whether this screen works offline and should show in offline mode even if conditions are not met.
343356
final bool worksWithOfflineData;
344357

@@ -490,9 +503,11 @@ abstract class Screen {
490503
}
491504
}
492505

506+
final serviceManager = serviceConnection.serviceManager;
507+
final connectedApp = serviceManager.connectedApp;
493508
final serviceReady =
494-
serviceConnection.serviceManager.isServiceAvailable &&
495-
serviceConnection.serviceManager.connectedApp!.connectedAppInitialized;
509+
serviceManager.isServiceAvailable &&
510+
connectedApp!.connectedAppInitialized;
496511
if (!serviceReady) {
497512
if (!screen.requiresConnection) {
498513
_log.finest('screen does not require connection: returning true');
@@ -509,42 +524,42 @@ abstract class Screen {
509524
}
510525
}
511526

512-
if (screen.requiresLibrary != null) {
513-
if (serviceConnection.serviceManager.isolateManager.mainIsolate.value ==
514-
null ||
515-
!serviceConnection.serviceManager.libraryUriAvailableNow(
516-
screen.requiresLibrary,
517-
)) {
518-
_log.finest(
519-
'screen requires library ${screen.requiresLibrary}: returning false',
520-
);
521-
return (
522-
show: false,
523-
disabledReason: ScreenDisabledReason.requiresDartLibrary,
524-
);
525-
}
527+
if (screen.requiresLibrary != null &&
528+
(serviceManager.isolateManager.mainIsolate.value == null ||
529+
!serviceManager.libraryUriAvailableNow(screen.requiresLibrary))) {
530+
_log.finest(
531+
'screen requires library ${screen.requiresLibrary}: returning false',
532+
);
533+
return (
534+
show: false,
535+
disabledReason: ScreenDisabledReason.requiresDartLibrary,
536+
);
526537
}
527-
if (screen.requiresDartVm) {
528-
if (serviceConnection.serviceManager.connectedApp!.isRunningOnDartVM !=
529-
true) {
530-
_log.finest('screen requires Dart VM: returning false');
531-
return (show: false, disabledReason: ScreenDisabledReason.requiresDartVm);
532-
}
538+
if (screen.requiresDartVm && connectedApp.isRunningOnDartVM != true) {
539+
_log.finest('screen requires Dart VM: returning false');
540+
return (show: false, disabledReason: ScreenDisabledReason.requiresDartVm);
533541
}
534-
if (screen.requiresFlutter &&
535-
serviceConnection.serviceManager.connectedApp!.isFlutterAppNow == false) {
542+
if (screen.requiresFlutter && connectedApp.isFlutterAppNow == false) {
536543
_log.finest('screen requires Flutter: returning false');
537544
return (show: false, disabledReason: ScreenDisabledReason.requiresFlutter);
538545
}
539-
if (screen.requiresDebugBuild) {
540-
if (serviceConnection.serviceManager.connectedApp!.isProfileBuildNow ==
541-
true) {
542-
_log.finest('screen requires debug build: returning false');
543-
return (
544-
show: false,
545-
disabledReason: ScreenDisabledReason.requiresDebugBuild,
546-
);
547-
}
546+
if (screen.requiresDebugBuild && connectedApp.isProfileBuildNow == true) {
547+
_log.finest('screen requires debug build: returning false');
548+
return (
549+
show: false,
550+
disabledReason: ScreenDisabledReason.requiresDebugBuild,
551+
);
552+
}
553+
if (!screen.supportsWebServerDevice &&
554+
connectedApp.isDartWebAppNow == true &&
555+
!connectedApp.isDebuggableWebApp) {
556+
_log.finest(
557+
'screen requires a debuggable web application: returning false',
558+
);
559+
return (
560+
show: false,
561+
disabledReason: ScreenDisabledReason.requiresDebuggableWebApp,
562+
);
548563
}
549564
_log.finest('${screen.screenId} screen supported: returning true');
550565
return (show: true, disabledReason: null);
@@ -573,6 +588,9 @@ enum ScreenDisabledReason {
573588
requiresAdvancedDeveloperMode(
574589
'only works when Advanced Developer Mode is enabled',
575590
),
591+
requiresDebuggableWebApp(
592+
'only works with web applications with full debugging support.',
593+
),
576594
serviceNotReady(
577595
'requires a connected application, but there is no connection available.',
578596
);

packages/devtools_app/test/shared/framework/visible_screens_test.dart

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ void main() {
4141

4242
void setupMockConnectedApp({
4343
bool web = false,
44+
bool debuggableWeb = true,
4445
bool flutter = false,
4546
bool debugMode = true,
4647
SemanticVersion? flutterVersion,
@@ -55,6 +56,7 @@ void main() {
5556
isFlutterApp: flutter,
5657
isProfileBuild: !debugMode,
5758
isWebApp: web,
59+
isDebuggableWebApp: debuggableWeb,
5860
);
5961
if (flutter) {
6062
fakeServiceConnection.serviceManager.availableLibraries.add(
@@ -114,6 +116,31 @@ void main() {
114116
);
115117
});
116118

119+
testWidgets('are correct for Dart Web app (DWDS websocket mode)', (
120+
WidgetTester tester,
121+
) async {
122+
setupMockConnectedApp(web: true, debuggableWeb: false);
123+
124+
expect(
125+
visibleScreenTypes,
126+
equals([
127+
HomeScreen,
128+
// InspectorScreen,
129+
// LegacyPerformanceScreen,
130+
// PerformanceScreen,
131+
// ProfilerScreen,
132+
// MemoryScreen,
133+
// DebuggerScreen,
134+
// NetworkScreen,
135+
LoggingScreen,
136+
// AppSizeScreen,
137+
// DeepLinksScreen,
138+
// VMDeveloperToolsScreen,
139+
// DTDToolsScreen,
140+
]),
141+
);
142+
});
143+
117144
testWidgets('are correct for Flutter (non-web) debug app', (
118145
WidgetTester tester,
119146
) async {
@@ -189,6 +216,31 @@ void main() {
189216
);
190217
});
191218

219+
testWidgets('are correct for Flutter web debug app (DWDS websocket mode)', (
220+
WidgetTester tester,
221+
) async {
222+
setupMockConnectedApp(flutter: true, web: true, debuggableWeb: false);
223+
224+
expect(
225+
visibleScreenTypes,
226+
equals([
227+
HomeScreen,
228+
InspectorScreen,
229+
// LegacyPerformanceScreen,
230+
// PerformanceScreen,
231+
// ProfilerScreen,
232+
// MemoryScreen,
233+
// DebuggerScreen,
234+
// NetworkScreen,
235+
LoggingScreen,
236+
// AppSizeScreen,
237+
// DeepLinksScreen,
238+
// VMDeveloperToolsScreen,
239+
// DTDToolsScreen,
240+
]),
241+
);
242+
});
243+
192244
testWidgets('are correct for Flutter app on old Flutter version', (
193245
WidgetTester tester,
194246
) async {

packages/devtools_app_shared/lib/src/service/connected_app.dart

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,16 @@ class ConnectedApp {
101101

102102
bool get isDebugFlutterAppNow => isFlutterAppNow! && !isProfileBuildNow!;
103103

104+
/// Returns true is the connected application's VM has the name
105+
/// [dwdsChromeDebugProxyDeviceName], indicating that DWDS has an active
106+
/// Chrome debugger connection with support for expression evaluation,
107+
/// object inspection, and setting breakpoints.
108+
///
109+
/// If false, the connected application supports a reduced subset of the VM
110+
/// service protocol.
111+
bool get isDebuggableWebApp =>
112+
serviceManager!.vm!.name == dwdsChromeDebugProxyDeviceName;
113+
104114
bool? get isRunningOnDartVM {
105115
final name = serviceManager!.vm!.name;
106116
// These are the two possible VM names returned by DWDS.
@@ -133,24 +143,6 @@ class ConnectedApp {
133143
shouldLogError: false,
134144
);
135145
return !(value?.kind == 'Bool');
136-
137-
// TODO(terry): Disabled below code, it will hang if flutter run --start-paused
138-
// see issue https://github.com/flutter/devtools/issues/2082.
139-
// Currently, if eval (see above) doesn't work then we're
140-
// running in Profile mode.
141-
/*
142-
assert(serviceConnectionManager.isServiceAvailable);
143-
// Only flutter apps have profile and non-profile builds. If this changes in
144-
// the future (flutter web), we can modify this check.
145-
if (!isRunningOnDartVM || !await isFlutterApp) return false;
146-
147-
await serviceConnectionManager.manager.serviceExtensionManager.extensionStatesUpdated.future;
148-
149-
// The debugAllowBanner extension is only available in debug builds
150-
final hasDebugExtension = serviceConnectionManager.manager.serviceExtensionManager
151-
.isServiceExtensionAvailable(extensions.debugAllowBanner.extension);
152-
return !hasDebugExtension;
153-
*/
154146
}
155147

156148
Future<void> initializeValues({void Function()? onComplete}) async {

packages/devtools_test/lib/src/mocks/mocks.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ void mockConnectedApp(
146146
bool isFlutterApp = true,
147147
bool isProfileBuild = false,
148148
bool isWebApp = false,
149+
bool isDebuggableWebApp = true,
149150
String os = 'ios',
150151
String flutterVersion = '2.10.0',
151152
}) {
@@ -160,6 +161,9 @@ void mockConnectedApp(
160161
when(
161162
connectedApp.isFlutterNativeAppNow,
162163
).thenReturn(isFlutterApp && !isWebApp);
164+
when(
165+
connectedApp.isDebuggableWebApp,
166+
).thenReturn(isWebApp && isDebuggableWebApp);
163167
if (isFlutterApp) {
164168
when(connectedApp.flutterVersionNow).thenReturn(
165169
FlutterVersion.parse({

0 commit comments

Comments
 (0)