Support running DWDS without a Chrome Debug Port (web-socket-based)#2639
Support running DWDS without a Chrome Debug Port (web-socket-based)#2639jyameo merged 37 commits intodart-lang:mainfrom
Conversation
There was a problem hiding this comment.
As a follow-up (in a different change) should we update the ChromeProxyService to also use this Built object for service extensions?
There was a problem hiding this comment.
The ChromeProxyService leverages the inspector to evaluate JavaScript expressions via invokeExtensionJsExpression (as seen in the _callServiceExtension method). This pattern is consistently used throughout ChromeProxyService for all interactions with the dartDevEmbedder. It evaluates JavaScript expressions directly in the browser context rather than sending structured messages. Since ChromeProxyService operates by executing JavaScript in the browser, using a Built object here wouldn't align with ChromeProxyService's JavaScript evaluation approach. The current pattern maintains consistency with how ChromeProxyService handles all other browser interactions.
| remoteDebugger.onClose.first.then((_) => close()); | ||
| } | ||
| } catch (_) { | ||
| // Chrome proxy service not available in WebSocket-only mode - ignore |
There was a problem hiding this comment.
What exceptions are we now catching and ignoring here?
If we're in "websocket-only mode" (does this mean -d web-server?) then I would expect the debugger to never even be connected. But if we're not in websocket-only mode (-d chrome?) then it seems like it should at least be a warning if we get an exception here.
Can you add more context on what this is doing?
There was a problem hiding this comment.
This was because we were throwing an UnsupportedError in the web_socket_app_debug_services when accessing chromeProxyService. I have changed the logic to return null instead, so we can now simply do: _appDebugServices.chromeProxyService?.remoteDebugger.onClose.first.then((_) { close(); });
There was a problem hiding this comment.
I believe there could be a debugger connected when in "web-server" mode after the application starts if the user manually connects it using the Dart DevTools chrome extension.
Actually now I think I agree, what errors are we swallowing here?
There was a problem hiding this comment.
Initially, the UnsupportedError was being thrown when accessing chromeProxyService from web_socket_app_debug_services, which isn't supposed to happen since that service doesn’t support Chrome debugging. I’ve since refactored the code so both app_debug_services and web_socket_app_debug_services implement a common interface. As part of that, chromeProxyService has been removed in favour of a unified proxyService interface, which can be type-checked as needed.
| Future<void>? _closed; | ||
|
|
||
| DebugConnection(this._appDebugServices) { | ||
| _appDebugServices.chromeProxyService.remoteDebugger.onClose.first.then((_) { |
There was a problem hiding this comment.
Can this all just be:
_appDebugServices.chromeProxyService?.remoteDebugger.onClose.first.then((_) { close(); });
All the new code here looks to be doing all the same checks as close below.
There was a problem hiding this comment.
Done! I've simplified the constructor now
| // The sink has already closed (app is disconnected), or another StateError occurred. | ||
| _logger.warning( | ||
| 'Failed to send request to client, connection likely closed. Error: $e', | ||
| 'Failed to send request to client ${injectedConnection.hashCode}, ' |
There was a problem hiding this comment.
Does the hashCode actually add any information to these messages? I'm assuming the injected clients don't override hashCode so it'll just be a random number every time.
There was a problem hiding this comment.
Ah yes, you are right! I was using the hashCode as a simple way to differentiate between multiple connections in log messages, but it's not very useful since there is no persistence and it's hard to track.
| Future<Map<String, dynamic>?> handleServiceExtension( | ||
| String method, | ||
| Map<String, dynamic> args, | ||
| ) async { |
There was a problem hiding this comment.
Is there a missing await somewhere in this method?
| await webSocketProxyService.isInitialized; | ||
| _logger.fine('WebSocket proxy service initialized successfully'); | ||
| } else { | ||
| _logger.warning('WebSocket proxy service is null'); |
There was a problem hiding this comment.
Is there ever an instance where this is okay? Should we throw/assert instead? Similar question below.
There was a problem hiding this comment.
good catch! I have updated the code to use try/catch/throw instead!
| appConnection.runMain(); | ||
| _mainHasStarted = true; | ||
| } catch (e) { | ||
| if (e.toString().contains('Main has already started')) { |
There was a problem hiding this comment.
Do we need to catch this? I would assume the check on line 900 prevents this from occurring.
There was a problem hiding this comment.
Yes, the try/catch block is needed here to handle race conditions where multiple concurrent resume() calls can pass the if (!_mainHasStarted) check before any of them complete and set _mainHasStarted = true
…default createIsolate and destroyIsolate in ProxyService
| /// | ||
| /// All subsequent calls to [close] will return this future. | ||
| /// Chrome-based debug services container. | ||
| class AppDebugServices implements IAppDebugServices { |
There was a problem hiding this comment.
Would it make sense to name this ChromeAppDebugServices and the interface AppDebugServices?
There was a problem hiding this comment.
That's a good idea. I have updated the name for the classes and interface to be more clear.
| port: port, | ||
| path: authToken, | ||
| ), | ||
| serviceUri: Uri(scheme: 'http', host: hostname, port: ddsPort ?? 0), |
There was a problem hiding this comment.
Is 0 a valid default port realistically? Does it make sense to require a ddsPort so we don't need to try and pick a default here?
There was a problem hiding this comment.
this is what we have been doing for chrome previous to my changes so I kept the existing behavior
| webSocketProxyService, | ||
| ); | ||
|
|
||
| final server = await startHttpServer(hostname, port: 0); |
There was a problem hiding this comment.
Similar question here. Why are we using port: 0?
There was a problem hiding this comment.
Ah! Good catch here. I updated this to be port: 44456 which is what we currently have for chrome
| class ChromeDebugService implements DebugService { | ||
| static String? _ddsUri; | ||
|
|
||
| final VmServiceInterface chromeProxyService; |
There was a problem hiding this comment.
It seems like this should be typed as ChromeProxyService chromeProxyService
|
|
||
| @override | ||
| ProxyService get proxyService => | ||
| debugService.chromeProxyService as ChromeProxyService; |
There was a problem hiding this comment.
See below, think we can get rid of this cast.
|
|
WebSocketProxyServiceclass that implements VM service protocol over WebSockets.WebSocketProxyService,WebSocketDebugService,WebSocketAppDebugServices, andWebSocketDwdsVmClientto support socket-based DWDS functionality.DevHandlerwithuseWebSocketConnectionflag to toggle between Chrome-based and WebSocket-based communication protocols.Required Flutter Tools changes: flutter/flutter#171648