Skip to content

Commit f978b90

Browse files
authored
[MV3] Debug session persists across closing and opening Chrome DevTools (#1894)
1 parent b1b4eff commit f978b90

File tree

4 files changed

+93
-48
lines changed

4 files changed

+93
-48
lines changed

dwds/debug_extension_mv3/web/debug_session.dart

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -294,44 +294,52 @@ void _forwardChromeDebuggerEventToDwds(
294294
}
295295
}
296296

297-
void _openDevTools(String devToolsUrl, {required int dartAppTabId}) async {
298-
if (devToolsUrl.isEmpty) {
299-
debugError('DevTools URL is empty.');
297+
void _openDevTools(String devToolsUri, {required int dartAppTabId}) async {
298+
if (devToolsUri.isEmpty) {
299+
debugError('DevTools URI is empty.');
300300
return;
301301
}
302302
final debugSession = _debugSessionForTab(dartAppTabId, type: TabType.dartApp);
303303
if (debugSession == null) {
304304
debugError('Debug session not found.');
305305
return;
306306
}
307-
// Send the DevTools URL to the extension panels:
308-
_sendDevToolsUrlMessage(devToolsUrl, dartAppTabId: dartAppTabId);
307+
// Save the DevTools URI so that the extension panels have access to it:
308+
await setStorageObject(
309+
type: StorageObject.devToolsUri,
310+
value: devToolsUri,
311+
tabId: dartAppTabId,
312+
);
309313
// Open a separate tab / window if triggered through the extension icon or
310314
// through AngularDart DevTools:
311315
if (debugSession.trigger == Trigger.extensionIcon ||
312316
debugSession.trigger == Trigger.angularDartDevTools) {
313317
final devToolsOpener = await fetchStorageObject<DevToolsOpener>(
314318
type: StorageObject.devToolsOpener);
315319
final devToolsTab = await createTab(
316-
devToolsUrl,
320+
devToolsUri,
317321
inNewWindow: devToolsOpener?.newWindow ?? false,
318322
);
319323
debugSession.devToolsTabId = devToolsTab.id;
320324
}
321325
}
322326

323327
void _handleDebuggerDetach(Debuggee source, DetachReason reason) async {
328+
final tabId = source.tabId;
324329
debugLog(
325330
'Debugger detached due to: $reason',
326331
verbose: true,
327-
prefix: '${source.tabId}',
332+
prefix: '$tabId',
328333
);
329-
final debugSession = _debugSessionForTab(source.tabId, type: TabType.dartApp);
334+
final debugSession = _debugSessionForTab(tabId, type: TabType.dartApp);
330335
if (debugSession == null) return;
331336
debugLog('Removing debug session...');
332337
_removeDebugSession(debugSession);
333338
// Notify the extension panels that the debug session has ended:
334339
_sendStopDebuggingMessage(reason, dartAppTabId: source.tabId);
340+
// Remove the DevTools URI and encoded URI from storage:
341+
await removeStorageObject(type: StorageObject.devToolsUri, tabId: tabId);
342+
await removeStorageObject(type: StorageObject.encodedUri, tabId: tabId);
335343
// Maybe close the associated DevTools tab as well:
336344
final devToolsTabId = debugSession.devToolsTabId;
337345
if (devToolsTabId == null) return;
@@ -370,18 +378,6 @@ void sendConnectFailureMessage(ConnectFailureReason reason,
370378
recipient: Script.debuggerPanel);
371379
}
372380

373-
void _sendDevToolsUrlMessage(String devToolsUrl,
374-
{required int dartAppTabId}) async {
375-
final json = jsonEncode(serializers.serialize(DevToolsUrl((b) => b
376-
..tabId = dartAppTabId
377-
..url = devToolsUrl)));
378-
sendRuntimeMessage(
379-
type: MessageType.devToolsUrl,
380-
body: json,
381-
sender: Script.background,
382-
recipient: Script.debuggerPanel);
383-
}
384-
385381
void _sendStopDebuggingMessage(DetachReason reason,
386382
{required int dartAppTabId}) async {
387383
final json = jsonEncode(serializers.serialize(DebugStateChange((b) => b

dwds/debug_extension_mv3/web/panel.dart

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -39,46 +39,35 @@ const showClass = 'show';
3939
const warningBannerId = 'warningBanner';
4040
const warningMsgId = 'warningMsg';
4141

42+
int get _tabId => chrome.devtools.inspectedWindow.tabId;
43+
4244
void main() {
4345
_registerListeners();
4446
_setColorThemeToMatchChromeDevTools();
4547
_maybeUpdateFileABugLink();
4648
}
4749

4850
void _registerListeners() {
49-
chrome.storage.onChanged.addListener(allowInterop(_handleDebugInfoChanges));
51+
chrome.storage.onChanged.addListener(allowInterop(_handleStorageChanges));
5052
chrome.runtime.onMessage.addListener(allowInterop(_handleRuntimeMessages));
5153
final launchDebugConnectionButton =
5254
document.getElementById(launchDebugConnectionButtonId) as ButtonElement;
5355
launchDebugConnectionButton.addEventListener('click', _launchDebugConnection);
56+
57+
_maybeInjectDevToolsIframe();
5458
}
5559

5660
void _handleRuntimeMessages(
5761
dynamic jsRequest, MessageSender sender, Function sendResponse) async {
5862
if (jsRequest is! String) return;
59-
final tabId = chrome.devtools.inspectedWindow.tabId;
60-
interceptMessage<DevToolsUrl>(
61-
message: jsRequest,
62-
expectedType: MessageType.devToolsUrl,
63-
expectedSender: Script.background,
64-
expectedRecipient: Script.debuggerPanel,
65-
messageHandler: (DevToolsUrl devToolsUrl) async {
66-
if (devToolsUrl.tabId != tabId) {
67-
debugWarn(
68-
'Received DevTools URL, but Dart app tab does not match current tab.');
69-
return;
70-
}
71-
connecting = false;
72-
_injectDevToolsIframe(devToolsUrl.url);
73-
});
7463

7564
interceptMessage<DebugStateChange>(
7665
message: jsRequest,
7766
expectedType: MessageType.debugStateChange,
7867
expectedSender: Script.background,
7968
expectedRecipient: Script.debuggerPanel,
8069
messageHandler: (DebugStateChange debugStateChange) async {
81-
if (debugStateChange.tabId != tabId) {
70+
if (debugStateChange.tabId != _tabId) {
8271
debugWarn(
8372
'Received debug state change request, but Dart app tab does not match current tab.');
8473
return;
@@ -95,8 +84,8 @@ void _handleRuntimeMessages(
9584
expectedRecipient: Script.debuggerPanel,
9685
messageHandler: (ConnectFailure connectFailure) async {
9786
debugLog(
98-
'Received connect failure for ${connectFailure.tabId} vs $tabId');
99-
if (connectFailure.tabId != tabId) {
87+
'Received connect failure for ${connectFailure.tabId} vs $_tabId');
88+
if (connectFailure.tabId != _tabId) {
10089
return;
10190
}
10291
connecting = false;
@@ -106,12 +95,25 @@ void _handleRuntimeMessages(
10695
});
10796
}
10897

109-
void _handleDebugInfoChanges(Object _, String storageArea) async {
98+
void _handleStorageChanges(Object storageObj, String storageArea) {
99+
// We only care about session storage objects:
110100
if (storageArea != 'session') return;
111-
final debugInfo = await fetchStorageObject<DebugInfo>(
112-
type: StorageObject.debugInfo,
113-
tabId: chrome.devtools.inspectedWindow.tabId,
101+
102+
interceptStorageChange<DebugInfo>(
103+
storageObj: storageObj,
104+
expectedType: StorageObject.debugInfo,
105+
tabId: _tabId,
106+
changeHandler: _handleDebugInfoChanges,
107+
);
108+
interceptStorageChange<String>(
109+
storageObj: storageObj,
110+
expectedType: StorageObject.devToolsUri,
111+
tabId: _tabId,
112+
changeHandler: _handleDevToolsUriChanges,
114113
);
114+
}
115+
116+
void _handleDebugInfoChanges(DebugInfo? debugInfo) async {
115117
if (debugInfo == null && isDartApp) {
116118
isDartApp = false;
117119
_showWarningBanner('Dart app is no longer open.');
@@ -122,10 +124,16 @@ void _handleDebugInfoChanges(Object _, String storageArea) async {
122124
}
123125
}
124126

127+
void _handleDevToolsUriChanges(String? devToolsUri) async {
128+
if (devToolsUri != null) {
129+
_injectDevToolsIframe(devToolsUri);
130+
}
131+
}
132+
125133
void _maybeUpdateFileABugLink() async {
126134
final debugInfo = await fetchStorageObject<DebugInfo>(
127135
type: StorageObject.debugInfo,
128-
tabId: chrome.devtools.inspectedWindow.tabId,
136+
tabId: _tabId,
129137
);
130138
final isInternal = debugInfo?.isInternalBuild ?? false;
131139
if (isInternal) {
@@ -204,9 +212,8 @@ void _hideWarningBanner() {
204212
void _launchDebugConnection(Event _) async {
205213
_updateElementVisibility(launchDebugConnectionButtonId, visible: false);
206214
_updateElementVisibility(loadingSpinnerId, visible: true);
207-
final dartAppTabId = chrome.devtools.inspectedWindow.tabId;
208215
final json = jsonEncode(serializers.serialize(DebugStateChange((b) => b
209-
..tabId = dartAppTabId
216+
..tabId = _tabId
210217
..newState = DebugStateChange.startDebugging)));
211218
sendRuntimeMessage(
212219
type: MessageType.debugStateChange,
@@ -224,15 +231,24 @@ void _maybeHandleConnectionTimeout() async {
224231
}
225232
}
226233

227-
void _injectDevToolsIframe(String devToolsUrl) {
234+
void _maybeInjectDevToolsIframe() async {
235+
final devToolsUri = await fetchStorageObject<String>(
236+
type: StorageObject.devToolsUri, tabId: _tabId);
237+
if (devToolsUri != null) {
238+
_injectDevToolsIframe(devToolsUri);
239+
}
240+
}
241+
242+
void _injectDevToolsIframe(String devToolsUri) {
243+
connecting = false;
228244
final iframeContainer = document.getElementById(iframeContainerId);
229245
if (iframeContainer == null) return;
230246
final panelBody = document.getElementById(panelBodyId);
231247
final panelType = panelBody?.getAttribute(panelAttribute) ?? 'debugger';
232248
final iframe = document.createElement('iframe');
233249
iframe.setAttribute(
234250
'src',
235-
'$devToolsUrl&embed=true&page=$panelType&backgroundColor=$devToolsBackgroundColor',
251+
'$devToolsUri&embed=true&page=$panelType&backgroundColor=$devToolsBackgroundColor',
236252
);
237253
_hideWarningBanner();
238254
_updateElementVisibility(landingPageId, visible: false);

dwds/debug_extension_mv3/web/storage.dart

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import 'logger.dart';
1818
enum StorageObject {
1919
debugInfo,
2020
devToolsOpener,
21+
devToolsUri,
2122
encodedUri;
2223

2324
Persistance get persistance {
@@ -26,6 +27,8 @@ enum StorageObject {
2627
return Persistance.sessionOnly;
2728
case StorageObject.devToolsOpener:
2829
return Persistance.acrossSessions;
30+
case StorageObject.devToolsUri:
31+
return Persistance.sessionOnly;
2932
case StorageObject.encodedUri:
3033
return Persistance.sessionOnly;
3134
}
@@ -97,6 +100,33 @@ Future<bool> removeStorageObject<T>({required StorageObject type, int? tabId}) {
97100
return completer.future;
98101
}
99102

103+
void interceptStorageChange<T>({
104+
required Object storageObj,
105+
required StorageObject expectedType,
106+
required void Function(T? storageObj) changeHandler,
107+
int? tabId,
108+
}) {
109+
try {
110+
final expectedStorageKey = _createStorageKey(expectedType, tabId);
111+
final isExpected = hasProperty(storageObj, expectedStorageKey);
112+
if (!isExpected) return;
113+
114+
final objProp = getProperty(storageObj, expectedStorageKey);
115+
final json = getProperty(objProp, 'newValue') as String?;
116+
T? decodedObj;
117+
if (json == null || T == String) {
118+
decodedObj = json as T?;
119+
} else {
120+
decodedObj = serializers.deserialize(jsonDecode(json)) as T?;
121+
}
122+
debugLog('Intercepted $expectedStorageKey change: $json');
123+
return changeHandler(decodedObj);
124+
} catch (error) {
125+
debugError(
126+
'Error intercepting storage object with type $expectedType: $error');
127+
}
128+
}
129+
100130
StorageArea _getStorageArea(Persistance persistance) {
101131
switch (persistance) {
102132
case Persistance.acrossSessions:

dwds/test/puppeteer/extension_test.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,9 @@ void main() async {
311311
serveDevTools: true,
312312
isInternalBuild: true,
313313
isFlutterApp: isFlutterApp,
314+
// TODO(elliette): Figure out if there is a way to close and then
315+
// re-open Chrome DevTools. That way we can test that a debug
316+
// session lasts across Chrome DevTools being opened and closed.
314317
openChromeDevTools: true,
315318
);
316319

0 commit comments

Comments
 (0)