Add popup for copying the app ID (#2299)

diff --git a/dwds/debug_extension_mv3/pubspec.yaml b/dwds/debug_extension_mv3/pubspec.yaml
index 2fe28cf..9ff1938 100644
--- a/dwds/debug_extension_mv3/pubspec.yaml
+++ b/dwds/debug_extension_mv3/pubspec.yaml
@@ -1,6 +1,6 @@
 name: mv3_extension
 publish_to: none
-version: 2.0.0
+version: 2.1.0
 homepage: https://github.com/dart-lang/webdev
 description: >-
   A Chrome extension for Dart debugging.
@@ -14,12 +14,14 @@
   js: ^0.6.1+1
 
 dev_dependencies:
+  args: ^2.3.1
   build: ^2.0.0
   build_runner: ^2.4.0
   built_collection: ^5.0.0
   built_value_generator: ^8.3.0
   build_web_compilers: ^4.0.4
   dwds: ^16.0.0
+  path: ^1.8.1
   sse: ^4.1.2
   web_socket_channel: ^2.2.0
 
diff --git a/dwds/debug_extension_mv3/web/background.dart b/dwds/debug_extension_mv3/web/background.dart
index 5b7f659..a96e28f 100644
--- a/dwds/debug_extension_mv3/web/background.dart
+++ b/dwds/debug_extension_mv3/web/background.dart
@@ -55,16 +55,6 @@
 
   chrome.commands.onCommand
       .addListener(allowInterop(_maybeSendCopyAppIdRequest));
-
-  // Detect clicks on the Dart Debug Extension icon.
-  onExtensionIconClicked(
-    allowInterop(
-      (Tab tab) => attachDebugger(
-        tab.id,
-        trigger: Trigger.extensionIcon,
-      ),
-    ),
-  );
 }
 
 Future<void> _handleRuntimeMessages(
@@ -137,6 +127,20 @@
     },
   );
 
+  interceptMessage<DebugStateChange>(
+    message: jsRequest,
+    expectedType: MessageType.debugStateChange,
+    expectedSender: Script.popup,
+    expectedRecipient: Script.background,
+    messageHandler: (DebugStateChange debugStateChange) {
+      final newState = debugStateChange.newState;
+      final tabId = debugStateChange.tabId;
+      if (newState == DebugStateChange.startDebugging) {
+        attachDebugger(tabId, trigger: Trigger.extensionIcon);
+      }
+    },
+  );
+
   interceptMessage<String>(
     message: jsRequest,
     expectedType: MessageType.multipleAppsDetected,
@@ -154,7 +158,7 @@
         value: multipleAppsDetected,
         tabId: dartTab.id,
       );
-      _setWarningIcon();
+      _setWarningIcon(dartTab.id);
     },
   );
 
@@ -181,7 +185,7 @@
   final debugInfo = await _fetchDebugInfo(navigationInfo.tabId);
   if (debugInfo == null) return;
   if (debugInfo.tabUrl != navigationInfo.url) {
-    _setDefaultIcon();
+    _setDefaultIcon(navigationInfo.tabId);
     await clearStaleDebugSession(tabId);
     await removeStorageObject(type: StorageObject.debugInfo, tabId: tabId);
     await detachDebugger(
@@ -240,28 +244,38 @@
 Future<void> _updateIcon(int activeTabId) async {
   final debugInfo = await _fetchDebugInfo(activeTabId);
   if (debugInfo == null) {
-    _setDefaultIcon();
+    _setDefaultIcon(activeTabId);
     return;
   }
   final multipleApps = await fetchStorageObject<String>(
     type: StorageObject.multipleAppsDetected,
     tabId: activeTabId,
   );
-  multipleApps == null ? _setDebuggableIcon() : _setWarningIcon();
+  multipleApps == null
+      ? _setDebuggableIcon(activeTabId)
+      : _setWarningIcon(activeTabId);
 }
 
-void _setDebuggableIcon() {
+void _setDebuggableIcon(int tabId) {
   setExtensionIcon(IconInfo(path: 'static_assets/dart.png'));
+  setExtensionPopup(
+    PopupDetails(popup: 'static_assets/popup.html', tabId: tabId),
+  );
 }
 
-void _setWarningIcon() {
-  setExtensionIcon(IconInfo(path: 'static_assets/dart_warning.png'));
+void _setWarningIcon(int tabId) {
+  setExtensionPopup(
+    PopupDetails(popup: 'static_assets/popup.html', tabId: tabId),
+  );
 }
 
-void _setDefaultIcon() {
+void _setDefaultIcon(int tabId) {
   final iconPath =
       isDevMode ? 'static_assets/dart_dev.png' : 'static_assets/dart_grey.png';
   setExtensionIcon(IconInfo(path: iconPath));
+  setExtensionPopup(
+    PopupDetails(popup: '', tabId: tabId),
+  );
 }
 
 Future<DebugInfo?> _fetchDebugInfo(int tabId) {
diff --git a/dwds/debug_extension_mv3/web/manifest_mv2.json b/dwds/debug_extension_mv3/web/manifest_mv2.json
index 84cccef..78ce8d1 100644
--- a/dwds/debug_extension_mv3/web/manifest_mv2.json
+++ b/dwds/debug_extension_mv3/web/manifest_mv2.json
@@ -1,6 +1,6 @@
 {
   "name": "Dart Debug Extension",
-  "version": "2.0",
+  "version": "2.1",
   "manifest_version": 2,
   "devtools_page": "static_assets/devtools.html",
   "browser_action": {
@@ -37,6 +37,5 @@
       "description": "Copy the app ID"
     }
   },
-  "web_accessible_resources": ["debug_info.dart.js"],
-  "options_page": "static_assets/settings.html"
+  "web_accessible_resources": ["debug_info.dart.js"]
 }
diff --git a/dwds/debug_extension_mv3/web/manifest_mv3.json b/dwds/debug_extension_mv3/web/manifest_mv3.json
index 3deac72..de8c490 100644
--- a/dwds/debug_extension_mv3/web/manifest_mv3.json
+++ b/dwds/debug_extension_mv3/web/manifest_mv3.json
@@ -1,6 +1,6 @@
 {
   "name": "Dart Debug Extension",
-  "version": "2.0",
+  "version": "2.1",
   "manifest_version": 3,
   "devtools_page": "static_assets/devtools.html",
   "action": {
@@ -43,6 +43,5 @@
       "matches": ["<all_urls>"],
       "resources": ["debug_info.dart.js"]
     }
-  ],
-  "options_page": "static_assets/settings.html"
+  ]
 }
diff --git a/dwds/debug_extension_mv3/web/messaging.dart b/dwds/debug_extension_mv3/web/messaging.dart
index f299301..9d5fa56 100644
--- a/dwds/debug_extension_mv3/web/messaging.dart
+++ b/dwds/debug_extension_mv3/web/messaging.dart
@@ -25,7 +25,8 @@
   background,
   copier,
   debuggerPanel,
-  detector;
+  detector,
+  popup;
 
   factory Script.fromString(String value) {
     return Script.values.byName(value);
diff --git a/dwds/debug_extension_mv3/web/popup.dart b/dwds/debug_extension_mv3/web/popup.dart
new file mode 100644
index 0000000..ba3ecf3
--- /dev/null
+++ b/dwds/debug_extension_mv3/web/popup.dart
@@ -0,0 +1,170 @@
+// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+@JS()
+library popup;
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:html';
+
+import 'package:dwds/data/debug_info.dart';
+import 'package:js/js.dart';
+
+import 'data_serializers.dart';
+import 'data_types.dart';
+import 'messaging.dart';
+import 'storage.dart';
+import 'utils.dart';
+
+const _appIdContainerId = 'appIdContainer';
+const _appIdDividerId = 'appIdDivider';
+const _appIdSpanId = 'appId';
+const _copyIdButtonId = 'copyIdButton';
+const _copiedSuccessId = 'copiedSuccess';
+const _fileBugButtonId = 'fileBugButton';
+const _hiddenClass = 'hidden';
+const _launchDevToolsButtonId = 'launchDevToolsButton';
+const _loadingSpinnerId = 'loadingSpinner';
+const _windowOption = 'windowOption';
+const _tabOption = 'tabOption';
+
+Future<int?> get _tabId async {
+  final tab = await activeTab;
+  return tab?.id;
+}
+
+String? _appId;
+
+Future<void> main() async {
+  _registerListeners();
+  await _loadUiAndHideSpinner();
+}
+
+void _registerListeners() {
+  final launchDevToolsButton =
+      document.getElementById(_launchDevToolsButtonId) as ButtonElement;
+  launchDevToolsButton.addEventListener('click', _launchDevTools);
+
+  final copyButton = document.getElementById(_copyIdButtonId) as ButtonElement;
+  copyButton.addEventListener('click', _copyAppId);
+
+  final fileABugButton =
+      document.getElementById(_fileBugButtonId) as ButtonElement;
+  fileABugButton.addEventListener('click', _openIssueTracker);
+
+  document.addEventListener('DOMContentLoaded', _updateSettingsFromStorage);
+
+  final windowOption = document.getElementById(_windowOption) as InputElement;
+  windowOption.addEventListener('change', (Event _) {
+    _saveSettingsToStorage(windowOption.value);
+  });
+
+  final tabOption = document.getElementById(_tabOption) as InputElement;
+  tabOption.addEventListener('change', (Event _) {
+    _saveSettingsToStorage(tabOption.value);
+  });
+}
+
+Future<void> _loadUiAndHideSpinner() async {
+  final inserted = await _insertAppId();
+  if (inserted) {
+    _updateElementVisibility(_appIdContainerId, visible: true);
+    _updateElementVisibility(_appIdDividerId, visible: true);
+  }
+  _updateElementVisibility(_loadingSpinnerId, visible: false);
+}
+
+Future<DebugInfo?> _fetchDebugInfo(int? tabId) async {
+  if (tabId == null) return null;
+  final debugInfo = await fetchStorageObject<DebugInfo>(
+    type: StorageObject.debugInfo,
+    tabId: tabId,
+  );
+  return debugInfo;
+}
+
+Future<bool> _insertAppId() async {
+  final tabId = await _tabId;
+  final debugInfo = await _fetchDebugInfo(tabId);
+  if (debugInfo == null) return false;
+  final isInternalBuild = debugInfo.isInternalBuild ?? false;
+  final workspaceName = debugInfo.workspaceName;
+  if (isInternalBuild && workspaceName != null) {
+    _appId = '$workspaceName-$tabId';
+    final appIdSpan = document.getElementById(_appIdSpanId) as SpanElement;
+    appIdSpan.setInnerHtml(_appId);
+    return true;
+  }
+  return false;
+}
+
+Future<void> _openIssueTracker(Event _) async {
+  final debugInfo = await _fetchDebugInfo(await _tabId);
+  final isInternalBuild = debugInfo?.isInternalBuild ?? false;
+  final issueTrackerLink = isInternalBuild
+      ? 'http://b/issues/new?component=775375&template=1791321'
+      : 'https://github.com/dart-lang/webdev/issues/new?labels=dart-debug-extension&projects=&template=dart_debug_extension.md';
+  await createTab(issueTrackerLink);
+}
+
+Future<void> _launchDevTools(Event _) async {
+  final tabId = await _tabId;
+  final json = jsonEncode(
+    serializers.serialize(
+      DebugStateChange(
+        (b) => b
+          ..tabId = tabId
+          ..newState = DebugStateChange.startDebugging,
+      ),
+    ),
+  );
+  await sendRuntimeMessage(
+    type: MessageType.debugStateChange,
+    body: json,
+    sender: Script.popup, // change to popup
+    recipient: Script.background,
+  );
+}
+
+void _copyAppId(Event _) {
+  if (_appId == null) return;
+  final clipboard = window.navigator.clipboard;
+  if (clipboard == null) return;
+  clipboard.writeText(_appId!);
+  _updateElementVisibility(_copiedSuccessId, visible: true);
+}
+
+Future<void> _updateSettingsFromStorage(Event _) async {
+  final devToolsOpener = await fetchStorageObject<DevToolsOpener>(
+    type: StorageObject.devToolsOpener,
+  );
+  final openInNewWindow = devToolsOpener?.newWindow ?? false;
+  _getRadioButton(_windowOption).checked = openInNewWindow;
+  _getRadioButton(_tabOption).checked = !openInNewWindow;
+}
+
+Future<void> _saveSettingsToStorage(String? devToolsOpener) async {
+  if (devToolsOpener == null) return;
+  await setStorageObject<DevToolsOpener>(
+    type: StorageObject.devToolsOpener,
+    value: DevToolsOpener(
+      (b) => b..newWindow = devToolsOpener == 'window',
+    ),
+  );
+}
+
+RadioButtonInputElement _getRadioButton(String id) {
+  return document.getElementById(id) as RadioButtonInputElement;
+}
+
+void _updateElementVisibility(String elementId, {required bool visible}) {
+  final element = document.getElementById(elementId);
+  if (element == null) return;
+  if (visible) {
+    element.classes.remove(_hiddenClass);
+  } else {
+    element.classes.add(_hiddenClass);
+  }
+}
diff --git a/dwds/debug_extension_mv3/web/settings.dart b/dwds/debug_extension_mv3/web/settings.dart
deleted file mode 100644
index 965edee..0000000
--- a/dwds/debug_extension_mv3/web/settings.dart
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-@JS()
-library settings;
-
-import 'dart:async';
-import 'dart:html';
-
-import 'package:js/js.dart';
-
-import 'data_types.dart';
-import 'storage.dart';
-
-void main() {
-  _registerListeners();
-}
-
-void _registerListeners() {
-  document.addEventListener('DOMContentLoaded', _updateSettingsFromStorage);
-  final saveButton = document.getElementById('saveButton') as ButtonElement;
-  saveButton.addEventListener('click', _saveSettingsToStorage);
-}
-
-Future<void> _updateSettingsFromStorage(Event _) async {
-  final devToolsOpener = await fetchStorageObject<DevToolsOpener>(
-    type: StorageObject.devToolsOpener,
-  );
-  final openInNewWindow = devToolsOpener?.newWindow ?? false;
-  _getRadioButton('windowOpt').checked = openInNewWindow;
-  _getRadioButton('tabOpt').checked = !openInNewWindow;
-}
-
-Future<void> _saveSettingsToStorage(Event event) async {
-  event.preventDefault();
-  _maybeHideSavedMsg();
-  final form = document.querySelector("form") as FormElement;
-  final data = FormData(form);
-  final devToolsOpenerValue = data.get('devToolsOpener') as String;
-  await setStorageObject<DevToolsOpener>(
-    type: StorageObject.devToolsOpener,
-    value: DevToolsOpener(
-      (b) => b..newWindow = devToolsOpenerValue == 'window',
-    ),
-  );
-  _showSavedMsg();
-}
-
-void _showSavedMsg() {
-  final snackbar = document.getElementById('savedSnackbar');
-  if (snackbar == null) return;
-  snackbar.classes.add('show');
-  Timer(Duration(seconds: 3), _maybeHideSavedMsg);
-}
-
-void _maybeHideSavedMsg() {
-  final snackbar = document.getElementById('savedSnackbar');
-  if (snackbar == null) return;
-  snackbar.classes.remove('show');
-}
-
-RadioButtonInputElement _getRadioButton(String id) {
-  return document.getElementById(id) as RadioButtonInputElement;
-}
diff --git a/dwds/debug_extension_mv3/web/static_assets/popup.html b/dwds/debug_extension_mv3/web/static_assets/popup.html
new file mode 100644
index 0000000..fbfa510
--- /dev/null
+++ b/dwds/debug_extension_mv3/web/static_assets/popup.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <link
+      rel="stylesheet"
+      href="https://code.getmdl.io/1.3.0/material.indigo-blue.min.css"
+      type="text/css"
+    />
+    <link
+      rel="stylesheet"
+      href="http://fonts.googleapis.com/css?family=Roboto:300,400,500,700"
+      type="text/css"
+    />
+    <link rel="stylesheet" href="styles.css" type="text/css" />
+    <style>
+      html {
+        width: 350px;
+        height: 100px;
+      }
+      hr {
+        margin: 0;
+      }
+    </style>
+  </head>
+
+  <body>
+    <ul class="mdl-list">
+      <li class="mdl-list__item">
+        <button
+          id="launchDevToolsButton"
+          class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--accent"
+        >
+          Open DevTools
+        </button>
+      </li>
+
+      <li class="mdl-list__item">
+        <span class="mdl-list__item-primary-content">
+          Open in a:
+        </span>
+        <span class="mdl-list__item-secondary-action">
+          <form>
+            <fieldset>
+              <label class="mdl-radio mdl-js-radio mdl-js-ripple-effect" for="tabOpt">
+                <input type="radio" id="tabOption" class="mdl-radio__button" name="devToolsOpener" value="tab" checked>
+                <span class="mdl-radio__label">New tab</span>
+              </label>
+              <label class="mdl-radio mdl-js-radio mdl-js-ripple-effect" for="windowOpt">
+                <input type="radio" id="windowOption" class="mdl-radio__button" name="devToolsOpener" value="window">
+                <span class="mdl-radio__label">New window</span>
+              </label>
+            </fieldset>
+          </form>
+        </span>
+      </li>
+
+      <hr id="appIdDivider"></hr>
+
+      <li id="appIdContainer" class="mdl-list__item mdl-list__item--two-line">
+        <span class="mdl-list__item-primary-content">
+          <button
+            id="copyIdButton"
+            class="mdl-button mdl-js-button mdl-button--accent"
+          >
+            Copy ID
+          </button>
+          <span id="copiedSuccess" class="mdl-list__item-sub-title small-padding hidden">Copied!</span>
+        </span>
+        <span class="mdl-list__item-secondary-content">
+          <span id="appId"></span>
+        </span>
+      </li>
+
+      <li class="mdl-list__item mdl-list__item--two-line list-footer">
+        <span class="mdl-list__item-primary-content">
+          <!-- Empty to right-align the "file a bug" button. -->
+        </span>
+        <span class="mdl-list__item-secondary-content">
+          <button
+            id="fileBugButton"
+            class="mdl-button mdl-js-button mdl-button--accent"
+          >
+            File a bug
+          </button>
+        </span>
+      </li>
+
+    </ul>
+    <div id="loadingSpinner" class="spinner-overlay">
+      <div class="spinner"></div>
+    </div>
+    <!-- Load script: -->
+    <script src="../popup.dart.js"></script>
+  </body>
+</html>
diff --git a/dwds/debug_extension_mv3/web/static_assets/settings.html b/dwds/debug_extension_mv3/web/static_assets/settings.html
deleted file mode 100644
index c0fecaa..0000000
--- a/dwds/debug_extension_mv3/web/static_assets/settings.html
+++ /dev/null
@@ -1,61 +0,0 @@
-<!DOCTYPE html>
-<html>
-
-<head>
-  <title>Dart Debug Extension Settings</title>
-  <link rel="icon" href="dart.png">
-  <link rel="stylesheet" href="https://code.getmdl.io/1.3.0/material.indigo-blue.min.css" />
-  <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Roboto:300,400,500,700" type="text/css">
-  <link rel="stylesheet" href="styles.css" type="text/css">
-</head>
-
-<body class="dark-theme">
-
-  <div class="mdl-layout mdl-js-layout">
-
-    <header class="mdl-layout__header">
-      <div class="mdl-layout__header-row">
-        <span class="mdl-layout__title">Dart Debug Extension Settings</span>
-        <div class="mdl-layout-spacer"></div>
-      </div>
-    </header>
-
-    <div class="mdl-grid">
-      <div class="mdl-cell mdl-cell--12-col">
-        
-        <div class="dart-card mdl-card mdl-shadow--2dp">
-          
-          <div class="mdl-card__supporting-text">
-            <h6>Open DevTools in a:</h3>
-              <form>
-                <fieldset>
-                  <label class="mdl-radio mdl-js-radio mdl-js-ripple-effect" for="tabOpt">
-                    <input type="radio" id="tabOpt" class="mdl-radio__button" name="devToolsOpener" value="tab" checked>
-                    <span class="mdl-radio__label">New tab</span>
-                  </label>
-                  <label class="mdl-radio mdl-js-radio mdl-js-ripple-effect" for="windowOpt">
-                    <input type="radio" id="windowOpt" class="mdl-radio__button" name="devToolsOpener" value="window">
-                    <span class="mdl-radio__label">New window</span>
-                  </label>
-                </fieldset>
-              </form>
-          </div>
-
-          <div class="mdl-card__actions mdl-card--border">
-            <button
-                id="saveButton"
-                class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--accent">
-                Save settings
-            </button>
-          </div>
-
-        </div>
-      </div>
-    </div>
-    <div id="savedSnackbar" class="snackbar snackbar--info">Settings saved!</div>
-
-    <!-- Load script: -->
-    <script src="../settings.dart.js"></script>
-</body>
-
-</html>
diff --git a/dwds/debug_extension_mv3/web/static_assets/styles.css b/dwds/debug_extension_mv3/web/static_assets/styles.css
index 07ce1e7..c4a5bf4 100644
--- a/dwds/debug_extension_mv3/web/static_assets/styles.css
+++ b/dwds/debug_extension_mv3/web/static_assets/styles.css
@@ -47,10 +47,18 @@
   margin-top: 15px;
 }
 
+.mdl-list {
+  padding-bottom: 0;
+}
+
 .mdl-button.mdl-js-button.mdl-button--raised.mdl-js-ripple-effect.mdl-button--accent {
   background-color: #0091ea;
 }
 
+.list-footer {
+  background-color: lightgrey;
+}
+
 .dart-card.mdl-card {
   min-height: 150px;
   width: 505px;
@@ -111,6 +119,10 @@
   visibility: none;
 }
 
+.small-padding {
+  margin: 0 16px;
+}
+
 @-webkit-keyframes fadein {
   from {
     bottom: 0;
@@ -132,3 +144,34 @@
     opacity: 1;
   }
 }
+
+.spinner-overlay {
+  background: #ffffff;
+  bottom: 0;
+  left: 0;
+  position: fixed;
+  right: 0;
+  top: 0;
+}
+
+.spinner:before {
+  animation: spinner 0.9s linear infinite;
+  border: 6px solid #ffffff;
+  border-radius: 50%;
+  border-top-color: #4489ff;
+  box-sizing: border-box;
+  content: "";
+  height: 50px;
+  left: 50%;
+  margin-left: -10px;
+  margin-top: -10px;
+  position: absolute;
+  top: 50%;
+  width: 50px;
+}
+
+@keyframes spinner {
+  to {
+    transform: rotate(360deg);
+  }
+}
diff --git a/dwds/debug_extension_mv3/web/utils.dart b/dwds/debug_extension_mv3/web/utils.dart
index 6c9285f..4bcd641 100644
--- a/dwds/debug_extension_mv3/web/utils.dart
+++ b/dwds/debug_extension_mv3/web/utils.dart
@@ -87,14 +87,6 @@
   );
 }
 
-void onExtensionIconClicked(void Function(Tab) callback) {
-  if (isMV3) {
-    _onExtensionIconClickedMV3(callback);
-  } else {
-    _onExtensionIconClickedMV2(callback);
-  }
-}
-
 void setExtensionIcon(IconInfo info) {
   if (isMV3) {
     _setExtensionIconMV3(
@@ -111,6 +103,22 @@
   }
 }
 
+void setExtensionPopup(PopupDetails details) {
+  if (isMV3) {
+    _setExtensionPopupMV3(
+      details,
+      // callback
+      null,
+    );
+  } else {
+    _setExtensionPopupMV2(
+      details,
+      // callback
+      null,
+    );
+  }
+}
+
 bool? _isDevMode;
 
 bool get isDevMode {
@@ -153,21 +161,29 @@
   return newUri.toString();
 }
 
-@JS('chrome.browserAction.onClicked.addListener')
-external void _onExtensionIconClickedMV2(void Function(Tab tab) callback);
-
-@JS('chrome.action.onClicked.addListener')
-external void _onExtensionIconClickedMV3(void Function(Tab tab) callback);
-
 @JS('chrome.browserAction.setIcon')
 external void _setExtensionIconMV2(IconInfo iconInfo, Function? callback);
 
 @JS('chrome.action.setIcon')
 external void _setExtensionIconMV3(IconInfo iconInfo, Function? callback);
 
+@JS('chrome.browserAction.setPopup')
+external void _setExtensionPopupMV2(PopupDetails details, Function? callback);
+
+@JS('chrome.action.setPopup')
+external void _setExtensionPopupMV3(PopupDetails details, Function? callback);
+
 @JS()
 @anonymous
 class IconInfo {
   external String get path;
   external factory IconInfo({required String path});
 }
+
+@JS()
+@anonymous
+class PopupDetails {
+  external int get tabId;
+  external String get popup;
+  external factory PopupDetails({required int tabId, required String popup});
+}
diff --git a/dwds/test/puppeteer/extension_common.dart b/dwds/test/puppeteer/extension_common.dart
index 4b9a82d..6a4fe10 100644
--- a/dwds/test/puppeteer/extension_common.dart
+++ b/dwds/test/puppeteer/extension_common.dart
@@ -20,7 +20,6 @@
 import 'package:test_common/utilities.dart';
 
 import '../../debug_extension_mv3/web/data_serializers.dart';
-import '../../debug_extension_mv3/web/data_types.dart';
 import '../fixtures/context.dart';
 import '../fixtures/project.dart';
 import '../fixtures/utilities.dart';
@@ -139,111 +138,6 @@
           await appTab.close();
         });
 
-        test('whether to open in a new tab or window is saved in local storage',
-            () async {
-          // Navigate to the extension settings page:
-          final extensionOrigin = getExtensionOrigin(browser);
-          final settingsTab = await navigateToPage(
-            browser,
-            url: '$extensionOrigin/static_assets/settings.html',
-            isNew: true,
-          );
-          // Set the settings to open DevTools in a new window:
-          await settingsTab.tap('#windowOpt');
-          await settingsTab.tap('#saveButton');
-          // Wait for the saved message to verify settings have been saved:
-          await settingsTab.waitForSelector('.show');
-          // Close the settings tab:
-          await settingsTab.close();
-          // Check that is has been saved in local storage:
-          final devToolsOpener = await _fetchStorageObj<DevToolsOpener>(
-            'devToolsOpener',
-            storageArea: 'local',
-            worker: worker,
-            backgroundPage: backgroundPage,
-          );
-          expect(devToolsOpener.newWindow, isTrue);
-        });
-
-        test(
-            'can configure opening DevTools in a tab/window with extension settings',
-            () async {
-          final appUrl = context.appUrl;
-          final devToolsUrlFragment =
-              useSse ? 'debugger?uri=sse' : 'debugger?uri=ws';
-          // Navigate to the Dart app:
-          final appTab =
-              await navigateToPage(browser, url: appUrl, isNew: true);
-          final appWindowId = await _getCurrentWindowId(
-            worker: worker,
-            backgroundPage: backgroundPage,
-          );
-          // Click on the Dart Debug Extension icon:
-          await workerEvalDelay();
-          await clickOnExtensionIcon(
-            worker: worker,
-            backgroundPage: backgroundPage,
-          );
-          // Verify the extension opened DevTools in the same window:
-          var devToolsTabTarget = await browser.waitForTarget(
-            (target) => target.url.contains(devToolsUrlFragment),
-          );
-          var devToolsTab = await devToolsTabTarget.page;
-          // Navigate to the newly opened DevTools tab:
-          await navigateToPage(
-            browser,
-            url: devToolsTabTarget.url,
-          );
-          var devToolsWindowId = await _getCurrentWindowId(
-            worker: worker,
-            backgroundPage: backgroundPage,
-          );
-          expect(devToolsWindowId == appWindowId, isTrue);
-          // Close the DevTools tab:
-          devToolsTab = await devToolsTabTarget.page;
-          await devToolsTab.close();
-          // Navigate to the extension settings page:
-          final extensionOrigin = getExtensionOrigin(browser);
-          final settingsTab = await navigateToPage(
-            browser,
-            url: '$extensionOrigin/static_assets/settings.html',
-            isNew: true,
-          );
-          // Set the settings to open DevTools in a new window:
-          await settingsTab.tap('#windowOpt');
-          await settingsTab.tap('#saveButton');
-          // Wait for the saved message to verify settings have been saved:
-          await settingsTab.waitForSelector('.show');
-          // Close the settings tab:
-          await settingsTab.close();
-          // Navigate to the Dart app:
-          await navigateToPage(browser, url: appUrl);
-          // Click on the Dart Debug Extension icon:
-          await clickOnExtensionIcon(
-            worker: worker,
-            backgroundPage: backgroundPage,
-          );
-          // Verify the extension opened DevTools in a different window:
-          devToolsTabTarget = await browser.waitForTarget(
-            (target) => target.url.contains(devToolsUrlFragment),
-          );
-          devToolsTab = await devToolsTabTarget.page;
-          // Navigate to the newly opened DevTools tab:
-          await navigateToPage(
-            browser,
-            url: devToolsTabTarget.url,
-          );
-          devToolsWindowId = await _getCurrentWindowId(
-            worker: worker,
-            backgroundPage: backgroundPage,
-          );
-          expect(devToolsWindowId == appWindowId, isFalse);
-          // Close the DevTools tab:
-          devToolsTab = await devToolsTabTarget.page;
-          await devToolsTab.close();
-          await appTab.close();
-        });
-
         test('DevTools is opened with the correct query parameters', () async {
           final appUrl = context.appUrl;
           final devToolsUrlFragment =
@@ -254,6 +148,7 @@
           // Click on the Dart Debug Extension icon:
           await workerEvalDelay();
           await clickOnExtensionIcon(
+            browser: browser,
             worker: worker,
             backgroundPage: backgroundPage,
           );
@@ -286,6 +181,7 @@
           // Click on the Dart Debug Extension icon:
           await workerEvalDelay();
           await clickOnExtensionIcon(
+            browser: browser,
             worker: worker,
             backgroundPage: backgroundPage,
           );
@@ -315,6 +211,7 @@
           // Click on the Dart Debug Extension icon:
           await workerEvalDelay();
           await clickOnExtensionIcon(
+            browser: browser,
             worker: worker,
             backgroundPage: backgroundPage,
           );
@@ -339,6 +236,7 @@
           // Click on the Dart Debug Extension icon:
           await workerEvalDelay();
           await clickOnExtensionIcon(
+            browser: browser,
             worker: worker,
             backgroundPage: backgroundPage,
           );
@@ -358,6 +256,7 @@
           // Click on the Dart Debug Extension icon again:
           await workerEvalDelay();
           await clickOnExtensionIcon(
+            browser: browser,
             worker: worker,
             backgroundPage: backgroundPage,
           );
@@ -385,6 +284,7 @@
           // Click on the Dart Debug Extension icon:
           await workerEvalDelay();
           await clickOnExtensionIcon(
+            browser: browser,
             worker: worker,
             backgroundPage: backgroundPage,
           );
@@ -411,6 +311,7 @@
           // Click on the Dart Debug Extension icon:
           await workerEvalDelay();
           await clickOnExtensionIcon(
+            browser: browser,
             worker: worker,
             backgroundPage: backgroundPage,
           );
@@ -915,6 +816,7 @@
 
         // Click on the extension icon:
         await clickOnExtensionIcon(
+          browser: browser,
           worker: worker,
           backgroundPage: backgroundPage,
         );
@@ -1013,17 +915,6 @@
   )) as int;
 }
 
-Future<int?> _getCurrentWindowId({
-  Worker? worker,
-  Page? backgroundPage,
-}) async {
-  return (await evaluate(
-    _currentWindowIdJs,
-    worker: worker,
-    backgroundPage: backgroundPage,
-  )) as int?;
-}
-
 Future<T> _fetchStorageObj<T>(
   String storageKey, {
   required String storageArea,
@@ -1057,17 +948,6 @@
     }
 ''';
 
-String _currentWindowIdJs = '''
-    async () => {
-      return new Promise((resolve, reject) => {
-        chrome.tabs.query({active: true, currentWindow: true}, (tabs) => {
-          const tab = tabs[0];
-          resolve(tab.windowId);
-        });
-      });
-    }
-''';
-
 String _fetchStorageObjJs(
   String storageKey, {
   required String storageArea,
diff --git a/dwds/test/puppeteer/test_utils.dart b/dwds/test/puppeteer/test_utils.dart
index d757a1f..5ef5747 100644
--- a/dwds/test/puppeteer/test_utils.dart
+++ b/dwds/test/puppeteer/test_utils.dart
@@ -155,14 +155,21 @@
 }
 
 Future<void> clickOnExtensionIcon({
+  required Browser browser,
   Worker? worker,
   Page? backgroundPage,
 }) async {
-  return evaluate(
+  await evaluate(
     _clickIconJs(isMV3: worker != null),
     worker: worker,
     backgroundPage: backgroundPage,
   );
+  final popupTarget = await browser.waitForTarget(
+    (target) => target.url.contains('popup'),
+  );
+  final popupPage = await popupTarget.page;
+  final launchDevToolsButton = await popupPage.$OrNull('#launchDevToolsButton');
+  await launchDevToolsButton?.click();
 }
 
 // Note: The following delay is required to reduce flakiness. It makes