Skip to content

[MV3 Debug Extension] User can reload app and continue to debug #1968

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Feb 17, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion dwds/debug_extension_mv3/web/background.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ void _handleRuntimeMessages(
debugWarn('Received debug info but tab is missing.');
return;
}
// If this is a new Dart app, we need to clear old debug session data:
if (!await _matchesAppInStorage(debugInfo.appId, tabId: dartTab.id)) {
await clearStaleDebugSession(dartTab.id);
}
// Save the debug info for the Dart app in storage:
await setStorageObject<DebugInfo>(
type: StorageObject.debugInfo, value: debugInfo, tabId: dartTab.id);
Expand Down Expand Up @@ -119,8 +123,9 @@ void _detectNavigationAwayFromDartApp(NavigationInfo navigationInfo) async {
if (debugInfo == null) return;
if (debugInfo.appUrl != navigationInfo.url) {
_setDefaultIcon();
await clearStaleDebugSession(tabId);
await removeStorageObject(type: StorageObject.debugInfo, tabId: tabId);
detachDebugger(
await detachDebugger(
tabId,
type: TabType.dartApp,
reason: DetachReason.navigatedAwayFromApp,
Expand Down Expand Up @@ -153,3 +158,8 @@ Future<DebugInfo?> _fetchDebugInfo(int tabId) {
tabId: tabId,
);
}

Future<bool> _matchesAppInStorage(String? appId, {required int tabId}) async {
final debugInfo = await _fetchDebugInfo(tabId);
return appId != null && appId == debugInfo?.appId;
}
109 changes: 75 additions & 34 deletions dwds/debug_extension_mv3/web/debug_session.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ enum DetachReason {
connectionDoneEvent,
devToolsTabClosed,
navigatedAwayFromApp,
staleDebugSession,
unknown;

factory DetachReason.fromString(String value) {
Expand Down Expand Up @@ -127,40 +128,60 @@ void attachDebugger(int dartAppTabId, {required Trigger trigger}) async {
);
}

void detachDebugger(
Future<bool> detachDebugger(
int tabId, {
required TabType type,
required DetachReason reason,
}) async {
final debugSession = _debugSessionForTab(tabId, type: type);
if (debugSession == null) return;
if (debugSession == null) return false;
final debuggee = Debuggee(tabId: debugSession.appTabId);
final completer = Completer<bool>();
chrome.debugger.detach(debuggee, allowInterop(() {
final error = chrome.runtime.lastError;
if (error != null) {
debugWarn(
'Error detaching tab for reason: $reason. Error: ${error.message}');
completer.complete(false);
} else {
_handleDebuggerDetach(debuggee, reason);
completer.complete(true);
}
}));
return completer.future;
}

bool isActiveDebugSession(int tabId) =>
_debugSessionForTab(tabId, type: TabType.dartApp) != null;

Future<void> clearStaleDebugSession(int tabId) async {
final debugSession = _debugSessionForTab(tabId, type: TabType.dartApp);
if (debugSession != null) {
await detachDebugger(
tabId,
type: TabType.dartApp,
reason: DetachReason.staleDebugSession,
);
} else {
await _removeDebugSessionDataInStorage(tabId);
}
}

void _registerDebugEventListeners() {
chrome.debugger.onEvent.addListener(allowInterop(_onDebuggerEvent));
chrome.debugger.onDetach.addListener(allowInterop(
(source, _) => _handleDebuggerDetach(
chrome.debugger.onDetach.addListener(allowInterop((source, _) async {
await _handleDebuggerDetach(
source,
DetachReason.canceledByUser,
),
));
chrome.tabs.onRemoved.addListener(allowInterop(
(tabId, _) => detachDebugger(
);
}));
chrome.tabs.onRemoved.addListener(allowInterop((tabId, _) async {
await detachDebugger(
tabId,
type: TabType.devTools,
reason: DetachReason.devToolsTabClosed,
),
));
);
}));
}

_enableExecutionContextReporting(int tabId) {
Expand Down Expand Up @@ -222,7 +243,7 @@ Future<void> _maybeConnectToDwds(int tabId, Object? params) async {
);
if (!connected) {
debugWarn('Failed to connect to DWDS for $contextOrigin.');
_sendConnectFailureMessage(ConnectFailureReason.unknown,
await _sendConnectFailureMessage(ConnectFailureReason.unknown,
dartAppTabId: tabId);
}
}
Expand All @@ -247,16 +268,16 @@ Future<bool> _connectToDwds({
appTabId: dartAppTabId,
trigger: trigger,
onIncoming: (data) => _routeDwdsEvent(data, client, dartAppTabId),
onDone: () {
detachDebugger(
onDone: () async {
await detachDebugger(
dartAppTabId,
type: TabType.dartApp,
reason: DetachReason.connectionDoneEvent,
);
},
onError: (err) {
onError: (err) async {
debugWarn('Connection error: $err', verbose: true);
detachDebugger(
await detachDebugger(
dartAppTabId,
type: TabType.dartApp,
reason: DetachReason.connectionErrorEvent,
Expand Down Expand Up @@ -377,24 +398,26 @@ void _openDevTools(String devToolsUri, {required int dartAppTabId}) async {
}
}

void _handleDebuggerDetach(Debuggee source, DetachReason reason) async {
Future<void> _handleDebuggerDetach(Debuggee source, DetachReason reason) async {
final tabId = source.tabId;
debugLog(
'Debugger detached due to: $reason',
verbose: true,
prefix: '$tabId',
);
final debugSession = _debugSessionForTab(tabId, type: TabType.dartApp);
if (debugSession == null) return;
debugLog('Removing debug session...');
_removeDebugSession(debugSession);
// Notify the extension panels that the debug session has ended:
_sendStopDebuggingMessage(reason, dartAppTabId: source.tabId);
// Remove the DevTools URI and encoded URI from storage:
await removeStorageObject(type: StorageObject.devToolsUri, tabId: tabId);
await removeStorageObject(type: StorageObject.encodedUri, tabId: tabId);
// Maybe close the associated DevTools tab as well:
final devToolsTabId = debugSession.devToolsTabId;
if (debugSession != null) {
debugLog('Removing debug session...');
_removeDebugSession(debugSession);
// Notify the extension panels that the debug session has ended:
await _sendStopDebuggingMessage(reason, dartAppTabId: tabId);
// Maybe close the associated DevTools tab as well:
await _maybeCloseDevTools(debugSession.devToolsTabId);
}
await _removeDebugSessionDataInStorage(tabId);
}

Future<void> _maybeCloseDevTools(int? devToolsTabId) async {
if (devToolsTabId == null) return;
final devToolsTab = await getTab(devToolsTabId);
if (devToolsTab != null) {
Expand All @@ -403,6 +426,12 @@ void _handleDebuggerDetach(Debuggee source, DetachReason reason) async {
}
}

Future<void> _removeDebugSessionDataInStorage(int tabId) async {
// Remove the DevTools URI and encoded URI from storage:
await removeStorageObject(type: StorageObject.devToolsUri, tabId: tabId);
await removeStorageObject(type: StorageObject.encodedUri, tabId: tabId);
}

void _removeDebugSession(_DebugSession debugSession) {
// Note: package:sse will try to keep the connection alive, even after the
// client has been closed. Therefore the extension sends an event to notify
Expand All @@ -422,25 +451,25 @@ void _removeDebugSession(_DebugSession debugSession) {
}
}

void _sendConnectFailureMessage(ConnectFailureReason reason,
Future<bool> _sendConnectFailureMessage(ConnectFailureReason reason,
{required int dartAppTabId}) async {
final json = jsonEncode(serializers.serialize(ConnectFailure((b) => b
..tabId = dartAppTabId
..reason = reason.name)));
sendRuntimeMessage(
return await sendRuntimeMessage(
type: MessageType.connectFailure,
body: json,
sender: Script.background,
recipient: Script.debuggerPanel);
}

void _sendStopDebuggingMessage(DetachReason reason,
Future<bool> _sendStopDebuggingMessage(DetachReason reason,
{required int dartAppTabId}) async {
final json = jsonEncode(serializers.serialize(DebugStateChange((b) => b
..tabId = dartAppTabId
..reason = reason.name
..newState = DebugStateChange.stopDebugging)));
sendRuntimeMessage(
return await sendRuntimeMessage(
type: MessageType.debugStateChange,
body: json,
sender: Script.background,
Expand Down Expand Up @@ -478,7 +507,7 @@ Future<bool> _authenticateUser(int tabId) async {
tabId: tabId,
);
} else {
_sendConnectFailureMessage(
await _sendConnectFailureMessage(
ConnectFailureReason.authentication,
dartAppTabId: tabId,
);
Expand Down Expand Up @@ -622,8 +651,20 @@ class _DebugSession {
}

void close() {
_socketClient.close();
_batchSubscription.cancel();
_batchController.close();
try {
_socketClient.close();
} catch (error) {
debugError('Error closing socket client: $error');
}
try {
_batchSubscription.cancel();
} catch (error) {
debugError('Error canceling batch subscription: $error');
}
try {
_batchController.close();
} catch (error) {
debugError('Error closing batch controller: $error');
}
}
}
14 changes: 7 additions & 7 deletions dwds/debug_extension_mv3/web/detector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,25 @@ void _registerListeners() {
document.addEventListener('dart-auth-response', _onDartAuthEvent);
}

void _onDartAppReadyEvent(Event event) {
Future<void> _onDartAppReadyEvent(Event event) async {
final debugInfo = getProperty(event, 'detail') as String?;
if (debugInfo == null) {
debugWarn(
'No debug info sent with ready event, instead reading from Window.');
_injectDebugInfoScript();
} else {
_sendMessageToBackgroundScript(
await _sendMessageToBackgroundScript(
type: MessageType.debugInfo,
body: debugInfo,
);
_sendAuthRequest(debugInfo);
}
}

void _onDartAuthEvent(Event event) {
Future<void> _onDartAuthEvent(Event event) async {
final isAuthenticated = getProperty(event, 'detail') as String?;
if (isAuthenticated == null) return;
_sendMessageToBackgroundScript(
await _sendMessageToBackgroundScript(
type: MessageType.isAuthenticated,
body: isAuthenticated,
);
Expand All @@ -61,11 +61,11 @@ void _injectDebugInfoScript() {
document.head?.append(script);
}

void _sendMessageToBackgroundScript({
Future<void> _sendMessageToBackgroundScript({
required MessageType type,
required String body,
}) {
sendRuntimeMessage(
}) async {
await sendRuntimeMessage(
type: type,
body: body,
sender: Script.detector,
Expand Down
14 changes: 12 additions & 2 deletions dwds/debug_extension_mv3/web/messaging.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
@JS()
library messaging;

import 'dart:async';
import 'dart:convert';

import 'package:js/js.dart';
Expand Down Expand Up @@ -100,7 +101,7 @@ void interceptMessage<T>({
}
}

void sendRuntimeMessage(
Future<bool> sendRuntimeMessage(
{required MessageType type,
required String body,
required Script sender,
Expand All @@ -111,10 +112,19 @@ void sendRuntimeMessage(
type: type,
body: body,
);
final completer = Completer<bool>();
chrome.runtime.sendMessage(
/*id*/ null,
message.toJSON(),
/*options*/ null,
/*callback*/ null,
allowInterop(() {
final error = chrome.runtime.lastError;
if (error != null) {
debugError(
'Error sending $type to $recipient from $sender: ${error.message}');
}
completer.complete(error != null);
}),
);
return completer.future;
}
Loading