Skip to content
Open
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
79 changes: 79 additions & 0 deletions .agents/skills/debugging-devtools-extensions/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
name: debugging-devtools-extensions
description: Guidelines and step-by-step workflow for debugging DevTools extensions locally, including stub mode, fixed-port launching, browser auto-opening, URL query parameters, target app connection, and human-in-the-loop interaction. Use when debugging or testing DevTools extension behavior.
---

# Debugging DevTools Extensions

Follow this workflow to test and debug DevTools extensions locally.

## 1. Local Stub Extensions Mode (No Server Needed)

When running DevTools in standalone web mode (`flutter run -d chrome`), DevTools does not run the `devtools_server` backend by default. To test extensions without a running server backend:

1. Open [`packages/devtools_app/lib/src/shared/development_helpers.dart`](file:///Users/ryjohn/code/github/flutter/devtools/packages/devtools_app/lib/src/shared/development_helpers.dart#L57).
2. Set `const _debugDevToolsExtensions = true;`.

> [!WARNING]
> Never commit `_debugDevToolsExtensions = true;` to git. A repository unit test (`development_helpers_test.dart`) enforces that this flag remains `false`.

Activating stub mode registers the following mock extensions:
- `foo_ext` (`package:foo`)
- `bar_ext` (`package:bar`)
- `provider_ext` (`package:provider`)

## 2. Automated Launch & Browser Navigation

The agent can automate running DevTools AND launching the browser directly to the target URL:

### Step 2a: Launch DevTools on a Fixed Port
In `packages/devtools_app`, launch DevTools specifying a fixed `--web-port`:
```bash
flutter run -d chrome --web-port=52941
```

### Step 2b: Open Browser to Target URL Automatically
Use the system OS open command to launch Chrome/browser directly to the desired test URL:

- **macOS**: `open "http://localhost:52941/foo_ext?embedMode=one"`
- **Linux**: `xdg-open "http://localhost:52941/foo_ext?embedMode=one"`
- **Windows**: `start "http://localhost:52941/foo_ext?embedMode=one"`

## 3. Testing Extension URLs & Embed Modes

Navigating to specific query parameters tests different extension UI states:

- **Single Extension Screen (`embedOne`)**:
`http://localhost:52941/foo_ext?embedMode=one`
*(Renders single extension view; puzzle piece icon IS visible in status bar)*

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
*(Renders single extension view; puzzle piece icon IS visible in status bar)*
*(Renders single extension view like extensions are rendered inside VS Code. In this mode, the extensions settings button, a puzzle piece icon, IS visible in the bottom status bar)*


- **Extensions-Only View**:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
- **Extensions-Only View**:
- **Extensions-Only View (`embedMany`)**:

`http://localhost:52941/?hide=all-except-extensions&embedMode=many`
*(Renders only extension tabs; puzzle piece icon IS visible)*

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
*(Renders only extension tabs; puzzle piece icon IS visible)*
*(Renders only extension tabs like extensions are rendered inside IntelliJ/Android Studio. In this mode, the extensions settings button, a puzzle piece icon, IS visible in the top tab bar)*


- **Standard Core Screen (`embedOne`)**:
`http://localhost:52941/inspector?embedMode=one`
*(Renders standard tool panel; puzzle piece icon IS HIDDEN)*

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
*(Renders standard tool panel; puzzle piece icon IS HIDDEN)*
*(Renders standard tool panel like they are rendered in VS Code. In this mode, the extensions settings button, a puzzle piece icon, IS HIDDEN)*


## 4. Connecting to an End-User Target App

To test against real pub package extensions:

1. Run the sample app in `packages/devtools_extensions/example/app_that_uses_foo`:
```bash
cd packages/devtools_extensions/example/app_that_uses_foo
flutter run -d chrome
```
2. Ask the user to copy/paste the VM Service URI from the terminal output (e.g. `ws://127.0.0.1:8181/xxx=/ws`).
3. Open the browser automatically with the `uri` parameter:
```bash
open "http://localhost:52941/foo_ext?embedMode=one&uri=<VM_SERVICE_URI>"
```

## 5. Human Interaction & User Prompting Steps

When an AI agent is performing this workflow:

- **Obtaining VM Service URI**: When connecting to a target app, ask the user to provide the VM Service URI printed in the target app's console output (using `ask_question` or a direct prompt).
- **Automated Browser Opening**: The agent should launch DevTools and execute `open <url>` to launch the browser automatically.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

step 2B above gives other commands depending on the OS. Should we include those other commands here or link to where this is described above?

- **Manual Visual Verification**: Ask the user to inspect the opened browser window and confirm whether the expected extension UI or status bar button appears.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: this may need to be changed. Not every interaction with debugging extensions is going to be related to the settings button like this particular bug

Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,30 @@ class EmbeddedExtensionControllerImpl extends EmbeddedExtensionController

String get extensionUrl {
if (debugDevToolsExtensions && !isDevToolsServerAvailable) {
return 'https://flutter.dev/';
return 'data:text/html;charset=utf-8,${Uri.encodeComponent('''

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why is this change necessary? Seems like unnecessary work to maintain this custom html rather than just point to something hosted like flutter.dev

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

When I was testing locally, the extension didn't show the flutter.dev homepage because there's an HTTP header that prevents embedding it in DevTools properly (I was getting an error screen)

@parlough parlough Aug 11, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this something we can/should address on the site's firebase config?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Maybe, but I'd prefer to use this because it doesn't depend on the Flutter website having the right headers (We might change how the site is hosted or configured in the future and this would break again)=

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think there's an X-Frame-Options header IIRC.

@johnpryan johnpryan Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

For anyone testing in the future it would be great to show something like this that indicates that the extension is being displayed properly, rather than showing flutter.dev or a broken iframe.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you put this String in a named const in this file

<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
background-color: #202124;
color: #e8eaed;
}
</style>
</head>
<body>
<h3>DevTools Extension Placeholder (${extensionConfig.name})</h3>
<p>Local debugging placeholder view.</p>
</body>
</html>
''')}';
}

final basePath = devtoolsAssetsBasePath(
Expand Down
29 changes: 21 additions & 8 deletions packages/devtools_app/lib/src/framework/scaffold/scaffold.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import '../../app.dart';
import '../../extensions/extension_screen.dart';
import '../../extensions/extension_settings.dart';
import '../../screens/debugger/debugger_screen.dart';
import '../../shared/analytics/prompt.dart';
Expand Down Expand Up @@ -57,14 +58,26 @@ class DevToolsScaffold extends StatefulWidget {
embedMode: embedMode,
);

static List<Widget> defaultActions({Color? color}) => [
OpenSettingsAction(color: color),
if (FeatureFlags.devToolsExtensions.isEnabled &&
!DevToolsQueryParams.load().hideExtensions)
ExtensionSettingsAction(color: color),
ReportFeedbackButton(color: color),
OpenAboutAction(color: color),
];
/// Returns the list of ScaffoldAction widgets.
static List<Widget> defaultActions({Color? color, Screen? currentScreen}) {
final queryParams = DevToolsQueryParams.load();

// If DevTools is running in an IDE (EmbedMode.embedOne), then hide
// [ExtensionSettingsAction], unless this screen is showing an extension.
final showExtensionSettings =
FeatureFlags.devToolsExtensions.isEnabled &&
!queryParams.hideExtensions &&
(ideTheme.embedMode != EmbedMode.embedOne ||
currentScreen is ExtensionScreen ||
queryParams.hideAllExceptExtensions);

return [
OpenSettingsAction(color: color),
if (showExtensionSettings) ExtensionSettingsAction(color: color),
ReportFeedbackButton(color: color),
OpenAboutAction(color: color),
];
}

/// The padding around the content in the DevTools UI.
EdgeInsets get appPadding => EdgeInsets.fromLTRB(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,10 @@ class StatusLine extends StatelessWidget {
BulletSpacer(color: foregroundColor),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: DevToolsScaffold.defaultActions(color: foregroundColor),
children: DevToolsScaffold.defaultActions(
color: foregroundColor,
currentScreen: currentScreen,
),
),
],
];
Expand Down
3 changes: 2 additions & 1 deletion packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ TODO: Remove this section if there are not any updates.

## DevTools extension updates

TODO: Remove this section if there are not any updates.
* Hide the DevTools extensions menu button in single-screen embedded mode (`EmbedMode.embedOne`) on standard screens.
[#8507](https://github.com/flutter/devtools/issues/8507)

## Advanced developer mode updates

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.

import 'package:devtools_app/devtools_app.dart';
import 'package:devtools_app/src/extensions/extension_screen.dart';
import 'package:devtools_app/src/extensions/extension_settings.dart';
import 'package:devtools_app/src/framework/scaffold/scaffold.dart';
import 'package:devtools_app/src/shared/development_helpers.dart';
import 'package:devtools_app/src/shared/framework/framework_controller.dart';
import 'package:devtools_app/src/shared/managers/survey.dart';
import 'package:devtools_app/src/shared/primitives/query_parameters.dart';
Expand Down Expand Up @@ -288,6 +291,64 @@ void main() {
);
expect(scaffold.actions, isEmpty);
});

test(
'defaultActions includes ExtensionSettingsAction based on EmbedMode and screen type',
() {
setGlobal(IdeTheme, IdeTheme());
expect(
DevToolsScaffold.defaultActions().any(
(w) => w is ExtensionSettingsAction,
),
isTrue,
);

setGlobal(IdeTheme, IdeTheme(embedMode: EmbedMode.embedMany));
expect(
DevToolsScaffold.defaultActions().any(
(w) => w is ExtensionSettingsAction,
),
isTrue,
);

setGlobal(IdeTheme, IdeTheme(embedMode: EmbedMode.embedOne));
// Standard screen in embedOne mode hides ExtensionSettingsAction
expect(
DevToolsScaffold.defaultActions(
currentScreen: _screen1,
).any((w) => w is ExtensionSettingsAction),
isFalse,
);

// ExtensionScreen in embedOne mode shows ExtensionSettingsAction
final extensionScreen = ExtensionScreen(
StubDevToolsExtensions.fooExtension,
);
expect(
DevToolsScaffold.defaultActions(
currentScreen: extensionScreen,
).any((w) => w is ExtensionSettingsAction),
isTrue,
);
},
);

testWidgets(
'hides ExtensionSettingsAction in StatusLine for EmbedMode.embedOne',
(WidgetTester tester) async {
setGlobal(IdeTheme, IdeTheme(embedMode: EmbedMode.embedOne));
await tester.pumpWidget(
wrapScaffold(
DevToolsScaffold(
screens: const [_screen1],
page: _screen1.screenId,
embedMode: EmbedMode.embedOne,
),
),
);
expect(find.byType(ExtensionSettingsAction), findsNothing);
},
);
}

class _TestScreen extends Screen {
Expand Down
Loading