Skip to content

Commit 705e0ac

Browse files
authored
[MV3 Debug Extension] Show a warning if multiple Dart apps are in a single page (#1976)
1 parent ae7eb80 commit 705e0ac

File tree

10 files changed

+214
-26
lines changed

10 files changed

+214
-26
lines changed

dwds/debug_extension_mv3/web/background.dart

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ void _registerListeners() {
3434
allowInterop(handleMessagesFromAngularDartDevTools),
3535
);
3636
// Update the extension icon on tab navigation:
37-
chrome.tabs.onActivated.addListener(allowInterop((ActiveInfo info) {
38-
_updateIcon(info.tabId);
37+
chrome.tabs.onActivated.addListener(allowInterop((ActiveInfo info) async {
38+
await _updateIcon(info.tabId);
3939
}));
4040
chrome.windows.onFocusChanged.addListener(allowInterop((_) async {
4141
final currentTab = await activeTab;
4242
if (currentTab?.id != null) {
43-
_updateIcon(currentTab!.id);
43+
await _updateIcon(currentTab!.id);
4444
}
4545
}));
4646
chrome.webNavigation.onCommitted
@@ -99,7 +99,7 @@ void _handleRuntimeMessages(
9999
// Update the icon to show that a Dart app has been detected:
100100
final currentTab = await activeTab;
101101
if (currentTab?.id == dartTab.id) {
102-
_setDebuggableIcon();
102+
await _updateIcon(dartTab.id);
103103
}
104104
});
105105

@@ -115,6 +115,26 @@ void _handleRuntimeMessages(
115115
attachDebugger(tabId, trigger: Trigger.extensionPanel);
116116
}
117117
});
118+
119+
interceptMessage<String>(
120+
message: jsRequest,
121+
expectedType: MessageType.multipleAppsDetected,
122+
expectedSender: Script.detector,
123+
expectedRecipient: Script.background,
124+
messageHandler: (String multipleAppsDetected) async {
125+
final dartTab = sender.tab;
126+
if (dartTab == null) {
127+
debugWarn('Received multiple apps detected but tab is missing.');
128+
return;
129+
}
130+
// Save the multiple apps info in storage:
131+
await setStorageObject<String>(
132+
type: StorageObject.multipleAppsDetected,
133+
value: multipleAppsDetected,
134+
tabId: dartTab.id,
135+
);
136+
_setWarningIcon();
137+
});
118138
}
119139

120140
void _detectNavigationAwayFromDartApp(NavigationInfo navigationInfo) async {
@@ -133,19 +153,27 @@ void _detectNavigationAwayFromDartApp(NavigationInfo navigationInfo) async {
133153
}
134154
}
135155

136-
void _updateIcon(int activeTabId) async {
156+
Future<void> _updateIcon(int activeTabId) async {
137157
final debugInfo = await _fetchDebugInfo(activeTabId);
138-
if (debugInfo != null) {
139-
_setDebuggableIcon();
140-
} else {
158+
if (debugInfo == null) {
141159
_setDefaultIcon();
160+
return;
142161
}
162+
final multipleApps = await fetchStorageObject<String>(
163+
type: StorageObject.multipleAppsDetected,
164+
tabId: activeTabId,
165+
);
166+
multipleApps == null ? _setDebuggableIcon() : _setWarningIcon();
143167
}
144168

145169
void _setDebuggableIcon() {
146170
setExtensionIcon(IconInfo(path: 'static_assets/dart.png'));
147171
}
148172

173+
void _setWarningIcon() {
174+
setExtensionIcon(IconInfo(path: 'static_assets/dart_warning.png'));
175+
}
176+
149177
void _setDefaultIcon() {
150178
final iconPath =
151179
isDevMode ? 'static_assets/dart_dev.png' : 'static_assets/dart_grey.png';

dwds/debug_extension_mv3/web/debug_session.dart

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,16 @@ void attachDebugger(int dartAppTabId, {required Trigger trigger}) async {
112112
'Already debugging in ${existingDebuggerLocation.displayName}.',
113113
);
114114
}
115-
115+
// Determine if there are multiple apps in the tab:
116+
final multipleApps = await fetchStorageObject<String>(
117+
type: StorageObject.multipleAppsDetected,
118+
tabId: dartAppTabId,
119+
);
120+
if (multipleApps != null) {
121+
return _showWarningNotification(
122+
'Dart debugging is not supported in a multi-app environment.',
123+
);
124+
}
116125
// Verify that the user is authenticated:
117126
final isAuthenticated = await _authenticateUser(dartAppTabId);
118127
if (!isAuthenticated) return;
@@ -427,9 +436,19 @@ Future<void> _maybeCloseDevTools(int? devToolsTabId) async {
427436
}
428437

429438
Future<void> _removeDebugSessionDataInStorage(int tabId) async {
430-
// Remove the DevTools URI and encoded URI from storage:
431-
await removeStorageObject(type: StorageObject.devToolsUri, tabId: tabId);
432-
await removeStorageObject(type: StorageObject.encodedUri, tabId: tabId);
439+
// Remove the DevTools URI, encoded URI, and multiple apps info from storage:
440+
await removeStorageObject(
441+
type: StorageObject.devToolsUri,
442+
tabId: tabId,
443+
);
444+
await removeStorageObject(
445+
type: StorageObject.encodedUri,
446+
tabId: tabId,
447+
);
448+
await removeStorageObject(
449+
type: StorageObject.multipleAppsDetected,
450+
tabId: tabId,
451+
);
433452
}
434453

435454
void _removeDebugSession(_DebugSession debugSession) {

dwds/debug_extension_mv3/web/detector.dart

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import 'data_serializers.dart';
1717
import 'logger.dart';
1818
import 'messaging.dart';
1919

20+
const _multipleAppsAttribute = 'data-multiple-dart-apps';
21+
2022
void main() {
2123
_registerListeners();
2224
}
@@ -38,6 +40,7 @@ Future<void> _onDartAppReadyEvent(Event event) async {
3840
body: debugInfo,
3941
);
4042
_sendAuthRequest(debugInfo);
43+
_detectMultipleDartApps();
4144
}
4245
}
4346

@@ -50,6 +53,51 @@ Future<void> _onDartAuthEvent(Event event) async {
5053
);
5154
}
5255

56+
void _detectMultipleDartApps() {
57+
final documentElement = document.documentElement;
58+
if (documentElement == null) return;
59+
60+
if (documentElement.hasAttribute(_multipleAppsAttribute)) {
61+
_sendMessageToBackgroundScript(
62+
type: MessageType.multipleAppsDetected,
63+
body: 'true',
64+
);
65+
return;
66+
}
67+
68+
final multipleAppsObserver =
69+
MutationObserver(_detectMultipleDartAppsCallback);
70+
multipleAppsObserver.observe(
71+
documentElement,
72+
attributeFilter: [_multipleAppsAttribute],
73+
);
74+
}
75+
76+
void _detectMultipleDartAppsCallback(
77+
List<dynamic> mutations,
78+
MutationObserver observer,
79+
) {
80+
for (var mutation in mutations) {
81+
if (_isMultipleAppsMutation(mutation)) {
82+
_sendMessageToBackgroundScript(
83+
type: MessageType.multipleAppsDetected,
84+
body: 'true',
85+
);
86+
observer.disconnect();
87+
}
88+
}
89+
}
90+
91+
bool _isMultipleAppsMutation(dynamic mutation) {
92+
final isAttributeMutation = hasProperty(mutation, 'type') &&
93+
getProperty(mutation, 'type') == 'attributes';
94+
if (isAttributeMutation) {
95+
return hasProperty(mutation, 'attributeName') &&
96+
getProperty(mutation, 'attributeName') == _multipleAppsAttribute;
97+
}
98+
return false;
99+
}
100+
53101
// TODO(elliette): Remove once DWDS 17.0.0 is in Flutter stable. If we are on an
54102
// older version of DWDS, then the debug info is not sent along with the ready
55103
// event. Therefore we must read it from the Window object, which is slower.

dwds/debug_extension_mv3/web/messaging.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ enum MessageType {
2929
connectFailure,
3030
debugInfo,
3131
debugStateChange,
32-
devToolsUrl;
32+
devToolsUrl,
33+
multipleAppsDetected;
3334

3435
factory MessageType.fromString(String value) {
3536
return MessageType.values.byName(value);

dwds/debug_extension_mv3/web/panel.dart

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,23 @@ const _lostConnectionMsg = 'Lost connection.';
4646
const _connectionTimeoutMsg = 'Connection timed out.';
4747
const _failedToConnectMsg = 'Failed to connect, please try again.';
4848
const _pleaseAuthenticateMsg = 'Please re-authenticate and try again.';
49+
const _multipleAppsMsg = 'Cannot debug multiple apps in a page.';
4950

5051
int get _tabId => chrome.devtools.inspectedWindow.tabId;
5152

52-
void main() {
53+
Future<void> main() async {
5354
unawaited(
5455
_registerListeners().catchError((error) {
5556
debugWarn('Error registering listeners in panel: $error');
5657
}),
5758
);
5859
_setColorThemeToMatchChromeDevTools();
59-
_maybeUpdateFileABugLink();
60+
await _maybeUpdateFileABugLink();
61+
final multipleApps = await fetchStorageObject<String>(
62+
type: StorageObject.multipleAppsDetected,
63+
tabId: _tabId,
64+
);
65+
_maybeShowMultipleAppsWarning(multipleApps);
6066
}
6167

6268
Future<void> _registerListeners() async {
@@ -70,7 +76,7 @@ Future<void> _registerListeners() async {
7076
}
7177

7278
void _handleRuntimeMessages(
73-
dynamic jsRequest, MessageSender sender, Function sendResponse) async {
79+
dynamic jsRequest, MessageSender sender, Function sendResponse) {
7480
if (jsRequest is! String) return;
7581

7682
interceptMessage<DebugStateChange>(
@@ -120,9 +126,15 @@ void _handleStorageChanges(Object storageObj, String storageArea) {
120126
tabId: _tabId,
121127
changeHandler: _handleDevToolsUriChanges,
122128
);
129+
interceptStorageChange<String>(
130+
storageObj: storageObj,
131+
expectedType: StorageObject.multipleAppsDetected,
132+
tabId: _tabId,
133+
changeHandler: _maybeShowMultipleAppsWarning,
134+
);
123135
}
124136

125-
void _handleDebugInfoChanges(DebugInfo? debugInfo) async {
137+
void _handleDebugInfoChanges(DebugInfo? debugInfo) {
126138
if (debugInfo == null && _isDartApp) {
127139
_isDartApp = false;
128140
if (!_warningBannerIsVisible()) {
@@ -137,13 +149,23 @@ void _handleDebugInfoChanges(DebugInfo? debugInfo) async {
137149
}
138150
}
139151

140-
void _handleDevToolsUriChanges(String? devToolsUri) async {
152+
void _handleDevToolsUriChanges(String? devToolsUri) {
141153
if (devToolsUri != null) {
142154
_injectDevToolsIframe(devToolsUri);
143155
}
144156
}
145157

146-
void _maybeUpdateFileABugLink() async {
158+
void _maybeShowMultipleAppsWarning(String? multipleApps) {
159+
if (multipleApps != null) {
160+
_showWarningBanner(_multipleAppsMsg);
161+
} else {
162+
if (_warningBannerIsVisible(message: _multipleAppsMsg)) {
163+
_hideWarningBanner();
164+
}
165+
}
166+
}
167+
168+
Future<void> _maybeUpdateFileABugLink() async {
147169
final debugInfo = await fetchStorageObject<DebugInfo>(
148170
type: StorageObject.debugInfo,
149171
tabId: _tabId,
@@ -157,7 +179,7 @@ void _maybeUpdateFileABugLink() async {
157179
}
158180
}
159181

160-
void _setColorThemeToMatchChromeDevTools() async {
182+
void _setColorThemeToMatchChromeDevTools() {
161183
final chromeTheme = chrome.devtools.panels.themeName;
162184
final panelBody = document.getElementById(_panelBodyId);
163185
if (chromeTheme == 'dark') {
@@ -217,9 +239,13 @@ void _handleConnectFailure(ConnectFailureReason reason) {
217239
_updateElementVisibility(_loadingSpinnerId, visible: false);
218240
}
219241

220-
bool _warningBannerIsVisible() {
242+
bool _warningBannerIsVisible({String? message}) {
221243
final warningBanner = document.getElementById(_warningBannerId);
222-
return warningBanner != null && warningBanner.classes.contains(_showClass);
244+
final isVisible =
245+
warningBanner != null && warningBanner.classes.contains(_showClass);
246+
if (message == null || isVisible == false) return isVisible;
247+
final warningMsg = document.getElementById(_warningMsgId);
248+
return warningMsg?.innerHtml == message;
223249
}
224250

225251
void _showWarningBanner(String message) {
@@ -234,7 +260,7 @@ void _hideWarningBanner() {
234260
warningBanner?.classes.remove(_showClass);
235261
}
236262

237-
void _launchDebugConnection(Event _) async {
263+
Future<void> _launchDebugConnection(Event _) async {
238264
_updateElementVisibility(_launchDebugConnectionButtonId, visible: false);
239265
_updateElementVisibility(_loadingSpinnerId, visible: true);
240266
final json = jsonEncode(serializers.serialize(DebugStateChange((b) => b
@@ -245,10 +271,10 @@ void _launchDebugConnection(Event _) async {
245271
body: json,
246272
sender: Script.debuggerPanel,
247273
recipient: Script.background);
248-
_maybeHandleConnectionTimeout();
274+
unawaited(_maybeHandleConnectionTimeout().catchError((_) {}));
249275
}
250276

251-
void _maybeHandleConnectionTimeout() async {
277+
Future<void> _maybeHandleConnectionTimeout() async {
252278
_connecting = true;
253279
await Future.delayed(Duration(seconds: 10));
254280
if (_connecting == true) {
Loading

dwds/debug_extension_mv3/web/storage.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ enum StorageObject {
2121
devToolsOpener,
2222
devToolsUri,
2323
encodedUri,
24-
isAuthenticated;
24+
isAuthenticated,
25+
multipleAppsDetected;
2526

2627
Persistance get persistance {
2728
switch (this) {
@@ -35,6 +36,8 @@ enum StorageObject {
3536
return Persistance.sessionOnly;
3637
case StorageObject.isAuthenticated:
3738
return Persistance.sessionOnly;
39+
case StorageObject.multipleAppsDetected:
40+
return Persistance.sessionOnly;
3841
}
3942
}
4043
}

0 commit comments

Comments
 (0)