From 81c45cf2d1d140e4f92c4e7981274ab9f39253fd Mon Sep 17 00:00:00 2001 From: Kenzie Schmoll Date: Fri, 25 Aug 2023 06:28:59 -0700 Subject: [PATCH 1/3] Add `DevToolsExtensionHostInterface` and clean up some service management in extensions --- .../src/extensions/embedded/_view_web.dart | 72 ++++++++++++------- .../lib/src/service/connected_app.dart | 2 + .../devtools_extensions/lib/src/api/api.dart | 26 +++++++ .../lib/src/template/extension_manager.dart | 53 +++++++------- .../lib/src/service/service.dart | 4 +- 5 files changed, 105 insertions(+), 52 deletions(-) diff --git a/packages/devtools_app/lib/src/extensions/embedded/_view_web.dart b/packages/devtools_app/lib/src/extensions/embedded/_view_web.dart index eca5a8cc718..ea6e4f10d2c 100644 --- a/packages/devtools_app/lib/src/extensions/embedded/_view_web.dart +++ b/packages/devtools_app/lib/src/extensions/embedded/_view_web.dart @@ -48,7 +48,8 @@ class _EmbeddedExtensionState extends State { } class _ExtensionIFrameController extends DisposableController - with AutoDisposeControllerMixin { + with AutoDisposeControllerMixin + implements DevToolsExtensionHostInterface { _ExtensionIFrameController(this.embeddedExtensionController); final EmbeddedExtensionControllerImpl embeddedExtensionController; @@ -120,29 +121,12 @@ class _ExtensionIFrameController extends DisposableController if (e is html.MessageEvent) { final extensionEvent = DevToolsExtensionEvent.tryParse(e.data); if (extensionEvent != null) { - switch (extensionEvent.type) { - case DevToolsExtensionEventType.ping: - // Ignore. DevTools should not receive/handle ping events. - case DevToolsExtensionEventType.pong: - if (!_extensionHandlerReady.isCompleted) { - _extensionHandlerReady.complete(); - } - break; - case DevToolsExtensionEventType.vmServiceConnection: - final service = serviceConnection.serviceManager.service; - if (service == null) break; - _postMessage( - DevToolsExtensionEvent( - DevToolsExtensionEventType.vmServiceConnection, - data: {'uri': service.connectedUri.toString()}, - ), - ); - break; - default: - notificationService.push( - 'Unknown event received from extension: ${e.data}', - ); - } + onEventReceived( + extensionEvent, + onUnknownEvent: () => notificationService.push( + 'Unknown event received from extension: ${e.data}', + ), + ); } } } @@ -160,7 +144,7 @@ class _ExtensionIFrameController extends DisposableController // Once the extension UI is ready, the extension will receive this // [DevToolsExtensionEventType.ping] message and return a // [DevToolsExtensionEventType.pong] message, handled in [_handleMessage]. - _postMessage(DevToolsExtensionEvent.ping); + ping(); }); await _extensionHandlerReady.future.timeout( @@ -181,4 +165,42 @@ class _ExtensionIFrameController extends DisposableController _pollForExtensionHandlerReady?.cancel(); super.dispose(); } + + @override + void ping() { + _postMessage(DevToolsExtensionEvent(DevToolsExtensionEventType.ping)); + } + + @override + void vmServiceConnectionChanged({String? uri}) { + _postMessage( + DevToolsExtensionEvent( + DevToolsExtensionEventType.vmServiceConnection, + data: {'uri': uri!}, + ), + ); + } + + @override + void onEventReceived( + DevToolsExtensionEvent event, { + void Function()? onUnknownEvent, + }) { + switch (event.type) { + case DevToolsExtensionEventType.ping: + // Ignore. DevTools should not receive/handle ping events. + case DevToolsExtensionEventType.pong: + if (!_extensionHandlerReady.isCompleted) { + _extensionHandlerReady.complete(); + } + break; + case DevToolsExtensionEventType.vmServiceConnection: + final service = serviceConnection.serviceManager.service; + if (service == null) break; + vmServiceConnectionChanged(uri: service.connectedUri.toString()); + break; + default: + onUnknownEvent?.call(); + } + } } diff --git a/packages/devtools_app_shared/lib/src/service/connected_app.dart b/packages/devtools_app_shared/lib/src/service/connected_app.dart index 57d286ea711..81505961b8c 100644 --- a/packages/devtools_app_shared/lib/src/service/connected_app.dart +++ b/packages/devtools_app_shared/lib/src/service/connected_app.dart @@ -147,6 +147,8 @@ class ConnectedApp { // Return early if already initialized. if (initialized.isCompleted) return; + assert(serviceManager!.isServiceAvailable); + await Future.wait([isFlutterApp, isProfileBuild, isDartWebApp]); _operatingSystem = serviceManager!.vm!.operatingSystem ?? unknownOS; diff --git a/packages/devtools_extensions/lib/src/api/api.dart b/packages/devtools_extensions/lib/src/api/api.dart index b4eab77b9e6..d0cec7c8d02 100644 --- a/packages/devtools_extensions/lib/src/api/api.dart +++ b/packages/devtools_extensions/lib/src/api/api.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'model.dart'; + /// Supported events that can be sent and received over 'postMessage' between /// DevTools and a DevTools extension running in an embedded iFrame. enum DevToolsExtensionEventType { @@ -29,3 +31,27 @@ enum DevToolsExtensionEventType { return unknown; } } + +/// Interface that a DevTools extension host should implement. +/// +/// This interface is implemented by DevTools itself as well as by a simulated +/// DevTools environment for simplifying extension development. +abstract interface class DevToolsExtensionHostInterface { + /// This method should send a [DevToolsExtensionEventType.ping] event to the + /// DevTools extension to check that it is ready. + void ping(); + + /// This method should send a [DevToolsExtensionEventType.vmServiceConnection] + /// event to the extension to notify it of the vm service uri it should + /// establish a connection to. + void vmServiceConnectionChanged({String? uri}); + + /// Handles events sent by the extension. + /// + /// If an unknown event is recevied, this handler should call [onUnknownEvent] + /// if non-null. + void onEventReceived( + DevToolsExtensionEvent event, { + void Function()? onUnknownEvent, + }); +} diff --git a/packages/devtools_extensions/lib/src/template/extension_manager.dart b/packages/devtools_extensions/lib/src/template/extension_manager.dart index dee1c4ad5cf..da5b4e61eaa 100644 --- a/packages/devtools_extensions/lib/src/template/extension_manager.dart +++ b/packages/devtools_extensions/lib/src/template/extension_manager.dart @@ -56,13 +56,7 @@ class ExtensionManager { break; case DevToolsExtensionEventType.vmServiceConnection: final vmServiceUri = extensionEvent.data?['uri'] as String?; - unawaited( - connectToVmService(vmServiceUri).catchError((e) { - // TODO(kenz): post a notification to DevTools for errors - // or create an error panel for the extensions screens. - print('Error connecting to VM service: $e'); - }), - ); + unawaited(_connectToVmService(vmServiceUri)); break; case DevToolsExtensionEventType.unknown: default: @@ -80,25 +74,34 @@ class ExtensionManager { html.window.parent?.postMessage(event.toJson(), html.window.origin!); } - Future connectToVmService(String? vmServiceUri) async { + Future _connectToVmService(String? vmServiceUri) async { if (vmServiceUri == null) return; - final finishedCompleter = Completer(); - final vmService = await connect( - uri: Uri.parse(vmServiceUri), - finishedCompleter: finishedCompleter, - createService: ({ - // ignore: avoid-dynamic, code needs to match API from VmService. - required Stream /*String|List*/ inStream, - required void Function(String message) writeMessage, - required Uri connectedUri, - }) { - return VmService(inStream, writeMessage); - }, - ); - await serviceManager.vmServiceOpened( - vmService, - onClosed: finishedCompleter.future, - ); + try { + final finishedCompleter = Completer(); + final vmService = await connect( + uri: Uri.parse(vmServiceUri), + finishedCompleter: finishedCompleter, + createService: ({ + // ignore: avoid-dynamic, code needs to match API from VmService. + required Stream /*String|List*/ inStream, + required void Function(String message) writeMessage, + required Uri connectedUri, + }) { + return VmService( + inStream, + writeMessage, + ); + }, + ); + await serviceManager.vmServiceOpened( + vmService, + onClosed: finishedCompleter.future, + ); + } catch (e) { + // TODO(kenz): post a notification to DevTools for errors + // or create an error panel for the extensions screens. + print('Unable to connect to VM service at $vmServiceUri: $e'); + } } } diff --git a/packages/devtools_shared/lib/src/service/service.dart b/packages/devtools_shared/lib/src/service/service.dart index 794794b3589..6915eeddf7a 100644 --- a/packages/devtools_shared/lib/src/service/service.dart +++ b/packages/devtools_shared/lib/src/service/service.dart @@ -102,8 +102,8 @@ Future connect({ // Connects to a VM Service but does not verify the connection was fully // successful. Future connectHelper() async { - T service; - service = uri.scheme == 'sse' || uri.scheme == 'sses' + final useSse = uri.scheme == 'sse' || uri.scheme == 'sses'; + final T service = useSse ? await _connectWithSse( uri: uri, onError: onError, From ef7a93b0fb445736a18909c5962ac2ef3f40a179 Mon Sep 17 00:00:00 2001 From: Kenzie Schmoll Date: Fri, 25 Aug 2023 09:38:49 -0700 Subject: [PATCH 2/3] remove null assert --- .../devtools_app/lib/src/extensions/embedded/_view_web.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/devtools_app/lib/src/extensions/embedded/_view_web.dart b/packages/devtools_app/lib/src/extensions/embedded/_view_web.dart index ea6e4f10d2c..a7549648e19 100644 --- a/packages/devtools_app/lib/src/extensions/embedded/_view_web.dart +++ b/packages/devtools_app/lib/src/extensions/embedded/_view_web.dart @@ -176,7 +176,7 @@ class _ExtensionIFrameController extends DisposableController _postMessage( DevToolsExtensionEvent( DevToolsExtensionEventType.vmServiceConnection, - data: {'uri': uri!}, + data: {'uri': uri}, ), ); } From 78872f22506bd8db18a633e3842c161874ce6f22 Mon Sep 17 00:00:00 2001 From: Kenzie Schmoll Date: Fri, 25 Aug 2023 09:39:56 -0700 Subject: [PATCH 3/3] add null check --- .../devtools_app/lib/src/extensions/embedded/_view_web.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/devtools_app/lib/src/extensions/embedded/_view_web.dart b/packages/devtools_app/lib/src/extensions/embedded/_view_web.dart index a7549648e19..ac006b44410 100644 --- a/packages/devtools_app/lib/src/extensions/embedded/_view_web.dart +++ b/packages/devtools_app/lib/src/extensions/embedded/_view_web.dart @@ -196,8 +196,7 @@ class _ExtensionIFrameController extends DisposableController break; case DevToolsExtensionEventType.vmServiceConnection: final service = serviceConnection.serviceManager.service; - if (service == null) break; - vmServiceConnectionChanged(uri: service.connectedUri.toString()); + vmServiceConnectionChanged(uri: service?.connectedUri.toString()); break; default: onUnknownEvent?.call();