Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DevToolsExtensionHostInterface and clean up some service management for extensions #6250

Merged
merged 3 commits into from
Aug 25, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 47 additions & 25 deletions packages/devtools_app/lib/src/extensions/embedded/_view_web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ class _EmbeddedExtensionState extends State<EmbeddedExtension> {
}

class _ExtensionIFrameController extends DisposableController
with AutoDisposeControllerMixin {
with AutoDisposeControllerMixin
implements DevToolsExtensionHostInterface {
_ExtensionIFrameController(this.embeddedExtensionController);

final EmbeddedExtensionControllerImpl embeddedExtensionController;
Expand Down Expand Up @@ -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}',
),
);
}
}
}
Expand All @@ -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(
Expand All @@ -181,4 +165,42 @@ class _ExtensionIFrameController extends DisposableController
_pollForExtensionHandlerReady?.cancel();
super.dispose();
}

@override
void ping() {
_postMessage(DevToolsExtensionEvent(DevToolsExtensionEventType.ping));
}

@override
void vmServiceConnectionChanged({String? uri}) {
kenzieschmoll marked this conversation as resolved.
Show resolved Hide resolved
_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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
26 changes: 26 additions & 0 deletions packages/devtools_extensions/lib/src/api/api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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();
bkonyi marked this conversation as resolved.
Show resolved Hide resolved

/// 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,
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -80,25 +74,34 @@ class ExtensionManager {
html.window.parent?.postMessage(event.toJson(), html.window.origin!);
}

Future<void> connectToVmService(String? vmServiceUri) async {
Future<void> _connectToVmService(String? vmServiceUri) async {
if (vmServiceUri == null) return;

final finishedCompleter = Completer<void>();
final vmService = await connect<VmService>(
uri: Uri.parse(vmServiceUri),
finishedCompleter: finishedCompleter,
createService: ({
// ignore: avoid-dynamic, code needs to match API from VmService.
required Stream<dynamic> /*String|List<int>*/ 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<void>();
final vmService = await connect<VmService>(
uri: Uri.parse(vmServiceUri),
finishedCompleter: finishedCompleter,
createService: ({
// ignore: avoid-dynamic, code needs to match API from VmService.
required Stream<dynamic> /*String|List<int>*/ 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');
}
}
}
4 changes: 2 additions & 2 deletions packages/devtools_shared/lib/src/service/service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ Future<T> connect<T extends VmService>({
// Connects to a VM Service but does not verify the connection was fully
// successful.
Future<T> 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<T>(
uri: uri,
onError: onError,
Expand Down
Loading