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 a simulated DevTools environment for developing extensions #6251

Merged
merged 8 commits into from
Aug 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ abstract class IntegrationTestApp with IOMixin {
Future<void> manuallyStopApp() async {}

Future<void> start() async {
_debugPrint('starting the test app process...');
await startProcess();
assert(
runProcess != null,
Expand All @@ -319,6 +320,7 @@ abstract class IntegrationTestApp with IOMixin {

listenToProcessOutput(runProcess!, printCallback: _debugPrint);

_debugPrint('waiting for app start...');
await waitForAppStart();
}

Expand Down Expand Up @@ -363,9 +365,7 @@ abstract class IntegrationTestApp with IOMixin {
final truncatedMsg =
msg.length > maxLength ? '${msg.substring(0, maxLength)}...' : msg;
_allMessages.add(truncatedMsg);
if (debugTestScript) {
print('_TestApp - $truncatedMsg');
}
debugLog('_TestApp - $truncatedMsg');
return msg;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ class _ExtensionIFrameController extends DisposableController
}

@override
void vmServiceConnectionChanged({String? uri}) {
void vmServiceConnectionChanged({required String? uri}) {
_postMessage(
DevToolsExtensionEvent(
DevToolsExtensionEventType.vmServiceConnection,
Expand Down
31 changes: 0 additions & 31 deletions packages/devtools_app/lib/src/shared/common_widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1021,37 +1021,6 @@ class CenteredCircularProgressIndicator extends StatelessWidget {
}
}

/// An extension on [ScrollController] to facilitate having the scrolling widget
/// auto scroll to the bottom on new content.
extension ScrollControllerAutoScroll on ScrollController {
// TODO(devoncarew): We lose dock-to-bottom when we receive content when we're
// off screen.

/// Return whether the view is currently scrolled to the bottom.
bool get atScrollBottom {
final pos = position;
return pos.pixels == pos.maxScrollExtent;
}

/// Scroll the content to the bottom using the app's default animation
/// duration and curve..
Future<void> autoScrollToBottom() async {
await animateTo(
position.maxScrollExtent,
duration: rapidDuration,
curve: defaultCurve,
);

// Scroll again if we've received new content in the interim.
if (hasClients) {
final pos = position;
if (pos.pixels != pos.maxScrollExtent) {
jumpTo(pos.maxScrollExtent);
}
}
}
}

/// An extension on [LinkedScrollControllerGroup] to facilitate having the
/// scrolling widgets auto scroll to the bottom on new content.
///
Expand Down
33 changes: 32 additions & 1 deletion packages/devtools_app_shared/lib/src/ui/common.dart
Original file line number Diff line number Diff line change
Expand Up @@ -688,7 +688,7 @@ Widget maybeWrapWithTooltip({
}

/// Displays a [json] map as selectable, formatted text.
class FormattedJson extends StatelessWidget {
final class FormattedJson extends StatelessWidget {
const FormattedJson({
super.key,
this.json,
Expand All @@ -713,3 +713,34 @@ class FormattedJson extends StatelessWidget {
);
}
}

/// An extension on [ScrollController] to facilitate having the scrolling widget
/// auto scroll to the bottom on new content.
extension ScrollControllerAutoScroll on ScrollController {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool!

// TODO(devoncarew): We lose dock-to-bottom when we receive content when we're
// off screen.

/// Return whether the view is currently scrolled to the bottom.
bool get atScrollBottom {
final pos = position;
return pos.pixels == pos.maxScrollExtent;
}

/// Scroll the content to the bottom using the app's default animation
/// duration and curve..
Future<void> autoScrollToBottom() async {
await animateTo(
position.maxScrollExtent,
duration: rapidDuration,
curve: defaultCurve,
);

// Scroll again if we've received new content in the interim.
if (hasClients) {
final pos = position;
if (pos.pixels != pos.maxScrollExtent) {
jumpTo(pos.maxScrollExtent);
}
}
}
}
5 changes: 5 additions & 0 deletions packages/devtools_app_shared/lib/src/utils/globals.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,8 @@ final Map<Type, Object> globals = <Type, Object>{};
void setGlobal(Type clazz, Object instance) {
globals[clazz] = instance;
}

void removeGlobal(Type clazz) {
globals.remove(clazz);
assert(globals[clazz] == null);
}
3 changes: 3 additions & 0 deletions packages/devtools_extensions/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 0.0.3-wip
* Add a simulated DevTools environment that for easier development.

## 0.0.2-dev.0

* Add missing dependency on `package:devtools_shared`.
Expand Down
6 changes: 3 additions & 3 deletions packages/devtools_extensions/lib/src/api/api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ enum DevToolsExtensionEventType {
}

/// 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 {
Expand All @@ -44,10 +44,10 @@ abstract interface class DevToolsExtensionHostInterface {
/// 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});
void vmServiceConnectionChanged({required String? uri});

/// Handles events sent by the extension.
///
///
/// If an unknown event is recevied, this handler should call [onUnknownEvent]
/// if non-null.
void onEventReceived(
Expand Down
20 changes: 11 additions & 9 deletions packages/devtools_extensions/lib/src/api/model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,18 @@ import 'api.dart';
/// See [DevToolsExtensionEventType] for different types of events that are
/// supported over this communication channel.
class DevToolsExtensionEvent {
DevToolsExtensionEvent(this.type, {this.data});
DevToolsExtensionEvent(
this.type, {
this.data,
this.source,
});

factory DevToolsExtensionEvent.parse(Map<String, Object?> json) {
final eventType =
DevToolsExtensionEventType.from(json[_typeKey]! as String);
final data = (json[_dataKey] as Map?)?.cast<String, Object?>();
return DevToolsExtensionEvent(eventType, data: data);
final source = json[sourceKey] as String?;
return DevToolsExtensionEvent(eventType, data: data, source: source);
}

static DevToolsExtensionEvent? tryParse(Object data) {
Expand All @@ -30,17 +35,14 @@ class DevToolsExtensionEvent {

static const _typeKey = 'type';
static const _dataKey = 'data';

static DevToolsExtensionEvent ping =
DevToolsExtensionEvent(DevToolsExtensionEventType.ping);

static DevToolsExtensionEvent pong =
DevToolsExtensionEvent(DevToolsExtensionEventType.pong);
static const sourceKey = 'source';

final DevToolsExtensionEventType type;

final Map<String, Object?>? data;

/// Optional field to describe the source that created and sent this event.
final String? source;

Map<String, Object?> toJson() {
return {
_typeKey: type.name,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Copyright 2023 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

part of '_simulated_devtools_environment.dart';

class _VmServiceConnection extends StatelessWidget {
const _VmServiceConnection({
required this.simController,
required this.connected,
});

static const _totalControlsHeight = 45.0;
static const _totalControlsWidth = 415.0;

final _SimulatedDevToolsController simController;
final bool connected;

@override
Widget build(BuildContext context) {
return SizedBox(
height: _totalControlsHeight,
child: connected
? const _ConnectedVmServiceDisplay()
: _DisconnectedVmServiceDisplay(
simController: simController,
),
);
}
}

class _ConnectedVmServiceDisplay extends StatelessWidget {
const _ConnectedVmServiceDisplay();

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Debugging:',
style: theme.regularTextStyle,
),
const Text('<connected uri>'),
// TODO(kenz): uncomment once we can bump to vm_service ^11.10.0
// Text(
// serviceManager.service!.wsUri ?? '--',
// style: theme.boldTextStyle,
// ),
],
),
const Expanded(
child: SizedBox(width: denseSpacing),
),
DevToolsButton(
elevated: true,
label: 'Disconnect',
onPressed: serviceManager.manuallyDisconnect,
),
],
);
}
}

class _DisconnectedVmServiceDisplay extends StatefulWidget {
const _DisconnectedVmServiceDisplay({required this.simController});

final _SimulatedDevToolsController simController;

@override
State<_DisconnectedVmServiceDisplay> createState() =>
_DisconnectedVmServiceDisplayState();
}

class _DisconnectedVmServiceDisplayState
extends State<_DisconnectedVmServiceDisplay> {
static const _connectFieldWidth = 300.0;

late final TextEditingController _connectTextFieldController;

@override
void initState() {
super.initState();
_connectTextFieldController = TextEditingController();
}

@override
void dispose() {
_connectTextFieldController.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: _connectFieldWidth,
child: TextField(
autofocus: true,
style: theme.regularTextStyle,
decoration: InputDecoration(
// contentPadding: const EdgeInsets.all(denseSpacing),
isDense: true,
border: const OutlineInputBorder(),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(width: 0.5, color: theme.focusColor),
),
labelText: 'Dart VM Service URL',
labelStyle: theme.regularTextStyle,
hintText: '(e.g., http://127.0.0.1:60851/fH-kAEXc7MQ=/)',
hintStyle: theme.regularTextStyle,
),
onSubmitted: (value) =>
widget.simController.vmServiceConnectionChanged(uri: value),
controller: _connectTextFieldController,
),
),
const SizedBox(width: denseSpacing),
DevToolsButton(
elevated: true,
label: 'Connect',
onPressed: () => widget.simController.vmServiceConnectionChanged(
uri: _connectTextFieldController.text,
),
),
],
);
}
}
Loading