Skip to content

Commit cd66172

Browse files
authored
[MV3 Debug Extension] User can reload app and continue to debug (#1968)
1 parent 8b7f9d3 commit cd66172

File tree

8 files changed

+171
-61
lines changed

8 files changed

+171
-61
lines changed

dwds/debug_extension_mv3/web/background.dart

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ void _handleRuntimeMessages(
8989
debugWarn('Received debug info but tab is missing.');
9090
return;
9191
}
92+
// If this is a new Dart app, we need to clear old debug session data:
93+
if (!await _matchesAppInStorage(debugInfo.appId, tabId: dartTab.id)) {
94+
await clearStaleDebugSession(dartTab.id);
95+
}
9296
// Save the debug info for the Dart app in storage:
9397
await setStorageObject<DebugInfo>(
9498
type: StorageObject.debugInfo, value: debugInfo, tabId: dartTab.id);
@@ -119,8 +123,9 @@ void _detectNavigationAwayFromDartApp(NavigationInfo navigationInfo) async {
119123
if (debugInfo == null) return;
120124
if (debugInfo.appUrl != navigationInfo.url) {
121125
_setDefaultIcon();
126+
await clearStaleDebugSession(tabId);
122127
await removeStorageObject(type: StorageObject.debugInfo, tabId: tabId);
123-
detachDebugger(
128+
await detachDebugger(
124129
tabId,
125130
type: TabType.dartApp,
126131
reason: DetachReason.navigatedAwayFromApp,
@@ -153,3 +158,8 @@ Future<DebugInfo?> _fetchDebugInfo(int tabId) {
153158
tabId: tabId,
154159
);
155160
}
161+
162+
Future<bool> _matchesAppInStorage(String? appId, {required int tabId}) async {
163+
final debugInfo = await _fetchDebugInfo(tabId);
164+
return appId != null && appId == debugInfo?.appId;
165+
}

dwds/debug_extension_mv3/web/debug_session.dart

Lines changed: 75 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ enum DetachReason {
4949
connectionDoneEvent,
5050
devToolsTabClosed,
5151
navigatedAwayFromApp,
52+
staleDebugSession,
5253
unknown;
5354

5455
factory DetachReason.fromString(String value) {
@@ -127,40 +128,60 @@ void attachDebugger(int dartAppTabId, {required Trigger trigger}) async {
127128
);
128129
}
129130

130-
void detachDebugger(
131+
Future<bool> detachDebugger(
131132
int tabId, {
132133
required TabType type,
133134
required DetachReason reason,
134135
}) async {
135136
final debugSession = _debugSessionForTab(tabId, type: type);
136-
if (debugSession == null) return;
137+
if (debugSession == null) return false;
137138
final debuggee = Debuggee(tabId: debugSession.appTabId);
139+
final completer = Completer<bool>();
138140
chrome.debugger.detach(debuggee, allowInterop(() {
139141
final error = chrome.runtime.lastError;
140142
if (error != null) {
141143
debugWarn(
142144
'Error detaching tab for reason: $reason. Error: ${error.message}');
145+
completer.complete(false);
143146
} else {
144147
_handleDebuggerDetach(debuggee, reason);
148+
completer.complete(true);
145149
}
146150
}));
151+
return completer.future;
152+
}
153+
154+
bool isActiveDebugSession(int tabId) =>
155+
_debugSessionForTab(tabId, type: TabType.dartApp) != null;
156+
157+
Future<void> clearStaleDebugSession(int tabId) async {
158+
final debugSession = _debugSessionForTab(tabId, type: TabType.dartApp);
159+
if (debugSession != null) {
160+
await detachDebugger(
161+
tabId,
162+
type: TabType.dartApp,
163+
reason: DetachReason.staleDebugSession,
164+
);
165+
} else {
166+
await _removeDebugSessionDataInStorage(tabId);
167+
}
147168
}
148169

149170
void _registerDebugEventListeners() {
150171
chrome.debugger.onEvent.addListener(allowInterop(_onDebuggerEvent));
151-
chrome.debugger.onDetach.addListener(allowInterop(
152-
(source, _) => _handleDebuggerDetach(
172+
chrome.debugger.onDetach.addListener(allowInterop((source, _) async {
173+
await _handleDebuggerDetach(
153174
source,
154175
DetachReason.canceledByUser,
155-
),
156-
));
157-
chrome.tabs.onRemoved.addListener(allowInterop(
158-
(tabId, _) => detachDebugger(
176+
);
177+
}));
178+
chrome.tabs.onRemoved.addListener(allowInterop((tabId, _) async {
179+
await detachDebugger(
159180
tabId,
160181
type: TabType.devTools,
161182
reason: DetachReason.devToolsTabClosed,
162-
),
163-
));
183+
);
184+
}));
164185
}
165186

166187
_enableExecutionContextReporting(int tabId) {
@@ -222,7 +243,7 @@ Future<void> _maybeConnectToDwds(int tabId, Object? params) async {
222243
);
223244
if (!connected) {
224245
debugWarn('Failed to connect to DWDS for $contextOrigin.');
225-
_sendConnectFailureMessage(ConnectFailureReason.unknown,
246+
await _sendConnectFailureMessage(ConnectFailureReason.unknown,
226247
dartAppTabId: tabId);
227248
}
228249
}
@@ -247,16 +268,16 @@ Future<bool> _connectToDwds({
247268
appTabId: dartAppTabId,
248269
trigger: trigger,
249270
onIncoming: (data) => _routeDwdsEvent(data, client, dartAppTabId),
250-
onDone: () {
251-
detachDebugger(
271+
onDone: () async {
272+
await detachDebugger(
252273
dartAppTabId,
253274
type: TabType.dartApp,
254275
reason: DetachReason.connectionDoneEvent,
255276
);
256277
},
257-
onError: (err) {
278+
onError: (err) async {
258279
debugWarn('Connection error: $err', verbose: true);
259-
detachDebugger(
280+
await detachDebugger(
260281
dartAppTabId,
261282
type: TabType.dartApp,
262283
reason: DetachReason.connectionErrorEvent,
@@ -377,24 +398,26 @@ void _openDevTools(String devToolsUri, {required int dartAppTabId}) async {
377398
}
378399
}
379400

380-
void _handleDebuggerDetach(Debuggee source, DetachReason reason) async {
401+
Future<void> _handleDebuggerDetach(Debuggee source, DetachReason reason) async {
381402
final tabId = source.tabId;
382403
debugLog(
383404
'Debugger detached due to: $reason',
384405
verbose: true,
385406
prefix: '$tabId',
386407
);
387408
final debugSession = _debugSessionForTab(tabId, type: TabType.dartApp);
388-
if (debugSession == null) return;
389-
debugLog('Removing debug session...');
390-
_removeDebugSession(debugSession);
391-
// Notify the extension panels that the debug session has ended:
392-
_sendStopDebuggingMessage(reason, dartAppTabId: source.tabId);
393-
// Remove the DevTools URI and encoded URI from storage:
394-
await removeStorageObject(type: StorageObject.devToolsUri, tabId: tabId);
395-
await removeStorageObject(type: StorageObject.encodedUri, tabId: tabId);
396-
// Maybe close the associated DevTools tab as well:
397-
final devToolsTabId = debugSession.devToolsTabId;
409+
if (debugSession != null) {
410+
debugLog('Removing debug session...');
411+
_removeDebugSession(debugSession);
412+
// Notify the extension panels that the debug session has ended:
413+
await _sendStopDebuggingMessage(reason, dartAppTabId: tabId);
414+
// Maybe close the associated DevTools tab as well:
415+
await _maybeCloseDevTools(debugSession.devToolsTabId);
416+
}
417+
await _removeDebugSessionDataInStorage(tabId);
418+
}
419+
420+
Future<void> _maybeCloseDevTools(int? devToolsTabId) async {
398421
if (devToolsTabId == null) return;
399422
final devToolsTab = await getTab(devToolsTabId);
400423
if (devToolsTab != null) {
@@ -403,6 +426,12 @@ void _handleDebuggerDetach(Debuggee source, DetachReason reason) async {
403426
}
404427
}
405428

429+
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);
433+
}
434+
406435
void _removeDebugSession(_DebugSession debugSession) {
407436
// Note: package:sse will try to keep the connection alive, even after the
408437
// client has been closed. Therefore the extension sends an event to notify
@@ -422,25 +451,25 @@ void _removeDebugSession(_DebugSession debugSession) {
422451
}
423452
}
424453

425-
void _sendConnectFailureMessage(ConnectFailureReason reason,
454+
Future<bool> _sendConnectFailureMessage(ConnectFailureReason reason,
426455
{required int dartAppTabId}) async {
427456
final json = jsonEncode(serializers.serialize(ConnectFailure((b) => b
428457
..tabId = dartAppTabId
429458
..reason = reason.name)));
430-
sendRuntimeMessage(
459+
return await sendRuntimeMessage(
431460
type: MessageType.connectFailure,
432461
body: json,
433462
sender: Script.background,
434463
recipient: Script.debuggerPanel);
435464
}
436465

437-
void _sendStopDebuggingMessage(DetachReason reason,
466+
Future<bool> _sendStopDebuggingMessage(DetachReason reason,
438467
{required int dartAppTabId}) async {
439468
final json = jsonEncode(serializers.serialize(DebugStateChange((b) => b
440469
..tabId = dartAppTabId
441470
..reason = reason.name
442471
..newState = DebugStateChange.stopDebugging)));
443-
sendRuntimeMessage(
472+
return await sendRuntimeMessage(
444473
type: MessageType.debugStateChange,
445474
body: json,
446475
sender: Script.background,
@@ -478,7 +507,7 @@ Future<bool> _authenticateUser(int tabId) async {
478507
tabId: tabId,
479508
);
480509
} else {
481-
_sendConnectFailureMessage(
510+
await _sendConnectFailureMessage(
482511
ConnectFailureReason.authentication,
483512
dartAppTabId: tabId,
484513
);
@@ -622,8 +651,20 @@ class _DebugSession {
622651
}
623652

624653
void close() {
625-
_socketClient.close();
626-
_batchSubscription.cancel();
627-
_batchController.close();
654+
try {
655+
_socketClient.close();
656+
} catch (error) {
657+
debugError('Error closing socket client: $error');
658+
}
659+
try {
660+
_batchSubscription.cancel();
661+
} catch (error) {
662+
debugError('Error canceling batch subscription: $error');
663+
}
664+
try {
665+
_batchController.close();
666+
} catch (error) {
667+
debugError('Error closing batch controller: $error');
668+
}
628669
}
629670
}

dwds/debug_extension_mv3/web/detector.dart

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,25 +26,25 @@ void _registerListeners() {
2626
document.addEventListener('dart-auth-response', _onDartAuthEvent);
2727
}
2828

29-
void _onDartAppReadyEvent(Event event) {
29+
Future<void> _onDartAppReadyEvent(Event event) async {
3030
final debugInfo = getProperty(event, 'detail') as String?;
3131
if (debugInfo == null) {
3232
debugWarn(
3333
'No debug info sent with ready event, instead reading from Window.');
3434
_injectDebugInfoScript();
3535
} else {
36-
_sendMessageToBackgroundScript(
36+
await _sendMessageToBackgroundScript(
3737
type: MessageType.debugInfo,
3838
body: debugInfo,
3939
);
4040
_sendAuthRequest(debugInfo);
4141
}
4242
}
4343

44-
void _onDartAuthEvent(Event event) {
44+
Future<void> _onDartAuthEvent(Event event) async {
4545
final isAuthenticated = getProperty(event, 'detail') as String?;
4646
if (isAuthenticated == null) return;
47-
_sendMessageToBackgroundScript(
47+
await _sendMessageToBackgroundScript(
4848
type: MessageType.isAuthenticated,
4949
body: isAuthenticated,
5050
);
@@ -61,11 +61,11 @@ void _injectDebugInfoScript() {
6161
document.head?.append(script);
6262
}
6363

64-
void _sendMessageToBackgroundScript({
64+
Future<void> _sendMessageToBackgroundScript({
6565
required MessageType type,
6666
required String body,
67-
}) {
68-
sendRuntimeMessage(
67+
}) async {
68+
await sendRuntimeMessage(
6969
type: type,
7070
body: body,
7171
sender: Script.detector,

dwds/debug_extension_mv3/web/messaging.dart

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
@JS()
66
library messaging;
77

8+
import 'dart:async';
89
import 'dart:convert';
910

1011
import 'package:js/js.dart';
@@ -100,7 +101,7 @@ void interceptMessage<T>({
100101
}
101102
}
102103

103-
void sendRuntimeMessage(
104+
Future<bool> sendRuntimeMessage(
104105
{required MessageType type,
105106
required String body,
106107
required Script sender,
@@ -111,10 +112,19 @@ void sendRuntimeMessage(
111112
type: type,
112113
body: body,
113114
);
115+
final completer = Completer<bool>();
114116
chrome.runtime.sendMessage(
115117
/*id*/ null,
116118
message.toJSON(),
117119
/*options*/ null,
118-
/*callback*/ null,
120+
allowInterop(() {
121+
final error = chrome.runtime.lastError;
122+
if (error != null) {
123+
debugError(
124+
'Error sending $type to $recipient from $sender: ${error.message}');
125+
}
126+
completer.complete(error != null);
127+
}),
119128
);
129+
return completer.future;
120130
}

0 commit comments

Comments
 (0)