Fix incorrect asset path for Dart apps ran with `--observe` (#6993)
diff --git a/packages/devtools_app/lib/src/extensions/embedded/_controller_web.dart b/packages/devtools_app/lib/src/extensions/embedded/_controller_web.dart
index 3e9cf66..bcdc589 100644
--- a/packages/devtools_app/lib/src/extensions/embedded/_controller_web.dart
+++ b/packages/devtools_app/lib/src/extensions/embedded/_controller_web.dart
@@ -43,8 +43,12 @@
       return 'https://flutter.dev/';
     }
 
+    final basePath = devtoolsAssetsBasePath(
+      origin: html.window.location.origin,
+      path: html.window.location.pathname ?? '',
+    );
     final baseUri = path.join(
-      html.window.location.origin,
+      basePath,
       'devtools_extensions',
       extensionConfig.name,
       'index.html',
diff --git a/packages/devtools_app/lib/src/screens/performance/panes/timeline_events/perfetto/_perfetto_controller_web.dart b/packages/devtools_app/lib/src/screens/performance/panes/timeline_events/perfetto/_perfetto_controller_web.dart
index b22d213..466dbcb 100644
--- a/packages/devtools_app/lib/src/screens/performance/panes/timeline_events/perfetto/_perfetto_controller_web.dart
+++ b/packages/devtools_app/lib/src/screens/performance/panes/timeline_events/perfetto/_perfetto_controller_web.dart
@@ -12,7 +12,6 @@
 import '../../../../../shared/globals.dart';
 import '../../../../../shared/primitives/trace_event.dart';
 import '../../../../../shared/primitives/utils.dart';
-import '../../../performance_utils.dart';
 import 'perfetto_controller.dart';
 
 /// Flag to enable embedding an instance of the Perfetto UI running on
@@ -119,9 +118,9 @@
     if (_debugUseLocalPerfetto) {
       return _debugPerfettoUrl;
     }
-    final basePath = assetUrlHelper(
+    final basePath = devtoolsAssetsBasePath(
       origin: html.window.location.origin,
-      path: html.window.location.pathname ?? '',
+      path: html.window.location.pathname  ?? '',
     );
     final indexFilePath = ui_web.assetManager
         .getAssetUrl(devToolsExtensionPoints.perfettoIndexLocation);
diff --git a/packages/devtools_app/lib/src/screens/performance/performance_utils.dart b/packages/devtools_app/lib/src/screens/performance/performance_utils.dart
index 41b0fcf..ff10882 100644
--- a/packages/devtools_app/lib/src/screens/performance/performance_utils.dart
+++ b/packages/devtools_app/lib/src/screens/performance/performance_utils.dart
@@ -94,23 +94,3 @@
       phase == TraceEvent.metadataEventPhase &&
       name == TraceEvent.threadNameEvent;
 }
-
-/// Returns the url (as a string) where the DevTools assets are served.
-///
-/// For Flutter apps and when DevTools is served via the `dart devtools`
-/// command, this url should be equivalent to [html.window.location.origin].
-/// However, when DevTools is served directly from DDS via the --observe flag,
-/// the authentication token and 'devtools/' path part are also required.
-///
-/// Examples:
-/// * 'http://127.0.0.1:61962/mb9Sw4gCYvU=/devtools/performance'
-///     ==> 'http://127.0.0.1:61962/mb9Sw4gCYvU=/devtools'
-/// * 'http://127.0.0.1:61962/performance' ==> 'http://127.0.0.1:61962'
-String assetUrlHelper({required String origin, required String path}) {
-  const separator = '/';
-  final pathParts = path.split(separator);
-  // The last path part is the DevTools page (e.g. 'performance' or 'snapshot'),
-  // which is not part of the hosted asset path.
-  pathParts.removeLast();
-  return '$origin${pathParts.join(separator)}';
-}
diff --git a/packages/devtools_app/lib/src/shared/primitives/utils.dart b/packages/devtools_app/lib/src/shared/primitives/utils.dart
index 1746e62..4cf18ce 100644
--- a/packages/devtools_app/lib/src/shared/primitives/utils.dart
+++ b/packages/devtools_app/lib/src/shared/primitives/utils.dart
@@ -1330,3 +1330,23 @@
   }
   return result;
 }
+
+/// Returns the url (as a string) where the DevTools assets are served.
+///
+/// For Flutter apps and when DevTools is served via the `dart devtools`
+/// command, this url should be equivalent to [html.window.location.origin].
+/// However, when DevTools is served directly from DDS via the --observe flag,
+/// the authentication token and 'devtools/' path part are also required.
+///
+/// Examples:
+/// * 'http://127.0.0.1:61962/mb9Sw4gCYvU=/devtools/performance'
+///     ==> 'http://127.0.0.1:61962/mb9Sw4gCYvU=/devtools'
+/// * 'http://127.0.0.1:61962/performance' ==> 'http://127.0.0.1:61962'
+String devtoolsAssetsBasePath({required String origin, required String path}) {
+  const separator = '/';
+  final pathParts = path.split(separator);
+  // The last path part is the DevTools page (e.g. 'performance' or 'snapshot'),
+  // which is not part of the hosted asset path.
+  pathParts.removeLast();
+  return '$origin${pathParts.join(separator)}';
+}
diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
index ec8f8ad..eb4a204 100644
--- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
+++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
@@ -47,3 +47,12 @@
 ## VS Code Sidebar updates
 
 TODO: Remove this section if there are not any general updates.
+
+## DevTools Extension updates
+
+* Fixed a couple bugs preventing Dart server apps from connecting to DevTools extensions. - [#6982](https://github.com/flutter/devtools/pull/6982), [#6993](https://github.com/flutter/devtools/pull/6993)
+
+## Full commit history
+
+To find a complete list of changes in this release, check out the
+[DevTools git log](https://github.com/flutter/devtools/tree/v2.28.5).
diff --git a/packages/devtools_app/test/performance/performance_utils_test.dart b/packages/devtools_app/test/performance/performance_utils_test.dart
index 3e68b46..79dc184 100644
--- a/packages/devtools_app/test/performance/performance_utils_test.dart
+++ b/packages/devtools_app/test/performance/performance_utils_test.dart
@@ -108,23 +108,5 @@
         equals(-1),
       );
     });
-
-    test('assetUrlHelper', () {
-      // This is how a DevTools url will be structured when DevTools is served
-      // directly from DDS using the `--observe` flag.
-      expect(
-        assetUrlHelper(
-          origin: 'http://127.0.0.1:61962',
-          path: '/mb9Sw4gCYvU=/devtools/performance',
-        ),
-        equals('http://127.0.0.1:61962/mb9Sw4gCYvU=/devtools'),
-      );
-      // This is how a DevTools url will be structured when served from DevTools
-      // server (e.g. from Flutter tools and from the `dart devtools` command).
-      expect(
-        assetUrlHelper(origin: 'http://127.0.0.1:61962', path: '/performance'),
-        equals('http://127.0.0.1:61962'),
-      );
-    });
   });
 }
diff --git a/packages/devtools_app/test/primitives/utils_test.dart b/packages/devtools_app/test/primitives/utils_test.dart
index b8b320f..9f65648 100644
--- a/packages/devtools_app/test/primitives/utils_test.dart
+++ b/packages/devtools_app/test/primitives/utils_test.dart
@@ -1447,6 +1447,27 @@
       expect(['A', 'B', 'C'].joinWithTrailing(':'), equals('A:B:C:'));
     });
   });
+
+  test('devtoolsAssetsBasePath', () {
+    // This is how a DevTools url will be structured when DevTools is served
+    // directly from DDS using the `--observe` flag.
+    expect(
+      devtoolsAssetsBasePath(
+        origin: 'http://127.0.0.1:61962',
+        path: '/mb9Sw4gCYvU=/devtools/performance',
+      ),
+      equals('http://127.0.0.1:61962/mb9Sw4gCYvU=/devtools'),
+    );
+    // This is how a DevTools url will be structured when served from DevTools
+    // server (e.g. from Flutter tools and from the `dart devtools` command).
+    expect(
+      devtoolsAssetsBasePath(
+        origin: 'http://127.0.0.1:61962',
+        path: '/performance',
+      ),
+      equals('http://127.0.0.1:61962'),
+    );
+  });
 }
 
 class _SubtractionResult {