Skip to content

Commit 4655961

Browse files
authored
Refactor background.dart in Dart Debug Extension (#1478)
1 parent a0c010d commit 4655961

File tree

1 file changed

+185
-137
lines changed

1 file changed

+185
-137
lines changed

dwds/debug_extension/web/background.dart

Lines changed: 185 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -60,87 +60,53 @@ class DebugSession {
6060
}
6161

6262
void main() {
63-
var startDebugging = allowInterop((_) {
64-
var query = QueryInfo(active: true, currentWindow: true);
65-
Tab currentTab;
66-
67-
// Sends commands to debugger attached to the current tab.
68-
//
69-
// Extracts the extension backend port from the injected JS.
70-
var callback = allowInterop((List<Tab> tabs) async {
71-
currentTab = tabs[0];
72-
if (!_debuggableTabs.contains(currentTab.id)) return;
73-
74-
if (_tabIdToWarning.containsKey(currentTab.id)) {
75-
alert(_tabIdToWarning[currentTab.id]);
76-
return;
77-
}
78-
79-
attach(Debuggee(tabId: currentTab.id), '1.3', allowInterop(() async {
80-
if (lastError != null) {
81-
String alertMessage;
82-
if (lastError.message.contains('Cannot access') ||
83-
lastError.message.contains('Cannot attach')) {
84-
alertMessage = _notADartAppAlert;
85-
} else {
86-
alertMessage = 'DevTools is already opened on a different window.';
87-
}
88-
alert(alertMessage);
89-
return;
90-
}
91-
_tabsToAttach.add(currentTab);
92-
sendCommand(Debuggee(tabId: currentTab.id), 'Runtime.enable',
93-
EmptyParam(), allowInterop((e) {}));
94-
}));
95-
});
63+
// Start debugging when a user clicks the Dart Debug Extension:
64+
browserActionOnClickedAddListener(allowInterop(_startDebugging));
9665

97-
queryTabs(query, allowInterop((List tabs) {
98-
callback(List.from(tabs));
99-
}));
100-
});
101-
browserActionOnClickedAddListener(startDebugging);
66+
// Marks the current tab as debuggable and changes the extension icon to blue
67+
// when it receives a message.
68+
// TODO(elliette): Currently the only messages this ever receives is from
69+
// the context script. Consider making it explicit what messages this is
70+
// listening for.
71+
onMessageAddListener(allowInterop(_maybeMarkTabAsDebuggable));
10272

103-
// For testing only.
104-
onFakeClick = allowInterop(() {
105-
startDebugging(null);
106-
});
73+
// Attaches a debug session to the app when the extension receives a
74+
// Runtime.executionContextCreated event from DWDS:
75+
addDebuggerListener(allowInterop(_maybeAttachDebugSession));
10776

108-
isDartDebugExtension = true;
77+
// When a Dart application tab is closed, detach the corresponding debug
78+
// session:
79+
tabsOnRemovedAddListener(allowInterop(_maybeDetachDebugSessionForTab));
10980

110-
onMessageAddListener(allowInterop(
111-
(Request request, Sender sender, Function sendResponse) async {
112-
// Register any warnings for the tab:
113-
if (request.warning != '') {
114-
_tabIdToWarning[sender.tab.id] = request.warning;
115-
}
116-
_debuggableTabs.add(sender.tab.id);
117-
_updateIcon();
118-
// TODO(grouma) - We can conditionally auto start debugging here.
119-
// For example: startDebugging(null);
120-
sendResponse(true);
81+
// When a debug session is detached, remove the reference to it:
82+
onDetachAddListener(allowInterop((Debuggee source, DetachReason reason) {
83+
_maybeRemoveDebugSessionForTab(source.tabId);
12184
}));
12285

86+
// Save the tab ID for the opened DevTools.
87+
tabsOnCreatedAddListener(allowInterop(_maybeSaveDevToolsTabId));
88+
89+
// Forward debugger events to the backend if applicable.
90+
addDebuggerListener(allowInterop(_filterAndForwardToBackend));
91+
92+
// Maybe update the extension icon when a user clicks the tab:
12393
tabsOnActivatedAddListener(allowInterop((ActiveInfo info) {
12494
_updateIcon();
12595
}));
12696

127-
addDebuggerListener(allowInterop((
128-
Debuggee source,
129-
String method,
130-
Object params,
131-
) async {
132-
if (method == 'Runtime.executionContextCreated') {
133-
var context = json.decode(stringify(params))['context'];
134-
var tab = _tabsToAttach.firstWhere((tab) => tab.id == source.tabId,
135-
orElse: () => null);
136-
if (tab != null) {
137-
if (await _tryAttach(context['id'] as int, tab)) {
138-
_tabsToAttach.remove(tab);
139-
}
140-
}
141-
}
97+
// Message handler enabling communication with external Chrome extensions:
98+
onMessageExternalAddListener(
99+
allowInterop(_handleMessageFromExternalExtensions));
100+
101+
// Message forwarder enabling communication with external Chrome extensions:
102+
addDebuggerListener(allowInterop(_forwardMessageToExternalExtensions));
103+
104+
// Maybe update the extension icon when the window focus changes:
105+
windowOnFocusChangeAddListener(allowInterop((_) {
106+
_updateIcon();
142107
}));
143108

109+
// Maybe update the extension icon during tab navigation:
144110
webNavigationOnCommittedAddListener(
145111
allowInterop((NavigationInfo navigationInfo) {
146112
if (navigationInfo.transitionType != 'auto_subframe' &&
@@ -149,84 +115,166 @@ void main() {
149115
}
150116
}));
151117

152-
windowOnFocusChangeAddListener(allowInterop((_) {
153-
_updateIcon();
118+
/// Everything after this is for testing only.
119+
/// TODO(elliette): Figure out if there is a workaround that would allow us to
120+
/// remove this.
121+
///
122+
/// An automated click on the extension icon is not supported by WebDriver.
123+
/// We initiate a fake click from the `debug_extension_test`
124+
/// after the extension is loaded.
125+
onFakeClick = allowInterop(() {
126+
_startDebugging(null);
127+
});
128+
129+
/// This is how we determine the extension tab to connect to during E2E tests.
130+
isDartDebugExtension = true;
131+
}
132+
133+
// Gets the current tab, then attaches the debugger to it:
134+
void _startDebugging(_) {
135+
final getCurrentTabQuery = QueryInfo(active: true, currentWindow: true);
136+
137+
// Sends commands to debugger attached to the current tab.
138+
// Extracts the extension backend port from the injected JS.
139+
var attachDebuggerToTab = allowInterop(_attachDebuggerToTab);
140+
141+
queryTabs(getCurrentTabQuery, allowInterop((List<Tab> tabs) {
142+
attachDebuggerToTab(tabs[0]);
154143
}));
144+
}
145+
146+
void _attachDebuggerToTab(Tab currentTab) async {
147+
if (!_debuggableTabs.contains(currentTab.id)) return;
148+
149+
if (_tabIdToWarning.containsKey(currentTab.id)) {
150+
alert(_tabIdToWarning[currentTab.id]);
151+
return;
152+
}
155153

156-
tabsOnRemovedAddListener(allowInterop((int tabId, _) {
157-
_debuggableTabs.remove(tabId);
158-
var session = _debugSessions.firstWhere(
159-
(session) =>
160-
session.appTabId == tabId || session.devtoolsTabId == tabId,
161-
orElse: () => null);
162-
if (session != null) {
163-
session.socketClient.close();
164-
_debugSessions.remove(session);
165-
detach(Debuggee(tabId: session.appTabId), allowInterop(() {}));
154+
attach(Debuggee(tabId: currentTab.id), '1.3', allowInterop(() async {
155+
if (lastError != null) {
156+
String alertMessage;
157+
if (lastError.message.contains('Cannot access') ||
158+
lastError.message.contains('Cannot attach')) {
159+
alertMessage = _notADartAppAlert;
160+
} else {
161+
alertMessage = 'DevTools is already opened on a different window.';
162+
}
163+
alert(alertMessage);
164+
return;
166165
}
166+
_tabsToAttach.add(currentTab);
167+
sendCommand(Debuggee(tabId: currentTab.id), 'Runtime.enable', EmptyParam(),
168+
allowInterop((e) {}));
167169
}));
170+
}
168171

169-
onDetachAddListener(allowInterop((Debuggee source, DetachReason reason) {
170-
var session = _debugSessions.firstWhere(
171-
(session) => session.appTabId == source.tabId,
172-
orElse: () => null);
173-
if (session != null) {
174-
session.socketClient.close();
175-
_debugSessions.remove(session);
172+
void _maybeMarkTabAsDebuggable(
173+
Request request, Sender sender, Function sendResponse) async {
174+
// Register any warnings for the tab:
175+
if (request.warning != '') {
176+
_tabIdToWarning[sender.tab.id] = request.warning;
177+
}
178+
_debuggableTabs.add(sender.tab.id);
179+
_updateIcon();
180+
// TODO(grouma) - We can conditionally auto start debugging here.
181+
// For example: _startDebugging(null);
182+
sendResponse(true);
183+
}
184+
185+
void _maybeAttachDebugSession(
186+
Debuggee source,
187+
String method,
188+
Object params,
189+
) async {
190+
// Return early if it's not a Runtime.executionContextCreated event (sent from
191+
// DWDS):
192+
if (method != 'Runtime.executionContextCreated') return;
193+
194+
var context = json.decode(stringify(params))['context'];
195+
var tab = _tabsToAttach.firstWhere((tab) => tab.id == source.tabId,
196+
orElse: () => null);
197+
if (tab != null) {
198+
if (await _tryAttach(context['id'] as int, tab)) {
199+
_tabsToAttach.remove(tab);
176200
}
177-
}));
201+
}
202+
}
178203

179-
tabsOnCreatedAddListener(allowInterop((Tab tab) async {
180-
// Remembers the ID of the DevTools tab.
181-
//
182-
// This assumes that the next launched tab after a session is created is the
183-
// DevTools tab.
184-
if (_debugSessions.isNotEmpty) _debugSessions.last.devtoolsTabId ??= tab.id;
185-
}));
204+
void _maybeDetachDebugSessionForTab(int tabId, _) {
205+
final removedTabId = _maybeRemoveDebugSessionForTab(tabId);
206+
207+
if (removedTabId != -1) {
208+
detach(Debuggee(tabId: removedTabId), allowInterop(() {}));
209+
}
210+
}
186211

187-
addDebuggerListener(allowInterop(_filterAndForward));
188-
189-
onMessageExternalAddListener(allowInterop(
190-
(Request request, Sender sender, Function sendResponse) async {
191-
if (_allowedExtensions.contains(sender.id)) {
192-
if (request.name == 'chrome.debugger.sendCommand') {
193-
try {
194-
var options = request.options as SendCommandOptions;
195-
sendCommand(Debuggee(tabId: request.tabId), options.method,
196-
options.commandParams, allowInterop(([e]) {
197-
// No arguments indicate that an error occurred.
198-
if (e == null) {
199-
sendResponse(ErrorResponse()..error = stringify(lastError));
200-
} else {
201-
sendResponse(e);
202-
}
203-
}));
204-
} catch (e) {
205-
sendResponse(ErrorResponse()..error = '$e');
212+
void _maybeSaveDevToolsTabId(Tab tab) async {
213+
// Remembers the ID of the DevTools tab.
214+
//
215+
// This assumes that the next launched tab after a session is created is the
216+
// DevTools tab.
217+
if (_debugSessions.isNotEmpty) _debugSessions.last.devtoolsTabId ??= tab.id;
218+
}
219+
220+
void _handleMessageFromExternalExtensions(
221+
Request request, Sender sender, Function sendResponse) async {
222+
if (_allowedExtensions.contains(sender.id)) {
223+
if (request.name == 'chrome.debugger.sendCommand') {
224+
try {
225+
var options = request.options as SendCommandOptions;
226+
227+
void sendResponseOrError([e]) {
228+
// No arguments indicate that an error occurred.
229+
if (e == null) {
230+
sendResponse(ErrorResponse()..error = stringify(lastError));
231+
} else {
232+
sendResponse(e);
233+
}
206234
}
207-
} else if (request.name == 'dwds.encodedUri') {
208-
sendResponse(_tabIdToEncodedUri[request.tabId] ?? '');
209-
} else if (request.name == 'dwds.startDebugging') {
210-
startDebugging(null);
211-
// TODO(grouma) - Actually determine if debugging initiated
212-
// successfully.
213-
sendResponse(true);
214-
} else {
215-
sendResponse(
216-
ErrorResponse()..error = 'Unknown request name: ${request.name}');
235+
236+
sendCommand(Debuggee(tabId: request.tabId), options.method,
237+
options.commandParams, allowInterop(sendResponseOrError));
238+
} catch (e) {
239+
sendResponse(ErrorResponse()..error = '$e');
217240
}
241+
} else if (request.name == 'dwds.encodedUri') {
242+
sendResponse(_tabIdToEncodedUri[request.tabId] ?? '');
243+
} else if (request.name == 'dwds.startDebugging') {
244+
_startDebugging(null);
245+
// TODO(grouma) - Actually determine if debugging initiated
246+
// successfully.
247+
sendResponse(true);
248+
} else {
249+
sendResponse(
250+
ErrorResponse()..error = 'Unknown request name: ${request.name}');
218251
}
219-
}));
252+
}
253+
}
220254

221-
addDebuggerListener(
222-
allowInterop((Debuggee source, String method, Object params) async {
223-
if (_allowedEvents.contains(method)) {
224-
sendMessageToExtensions(Request(
225-
name: 'chrome.debugger.event',
226-
tabId: source.tabId,
227-
options: DebugEvent(method: method, params: params)));
228-
}
229-
}));
255+
void _forwardMessageToExternalExtensions(
256+
Debuggee source, String method, Object params) async {
257+
if (_allowedEvents.contains(method)) {
258+
sendMessageToExtensions(Request(
259+
name: 'chrome.debugger.event',
260+
tabId: source.tabId,
261+
options: DebugEvent(method: method, params: params)));
262+
}
263+
}
264+
265+
// Tries to remove the debug session for the specified tab. If no session is
266+
// found, returns -1. Otherwise returns the tab ID.
267+
int _maybeRemoveDebugSessionForTab(int tabId) {
268+
var session = _debugSessions.firstWhere(
269+
(session) => session.appTabId == tabId || session.devtoolsTabId == tabId,
270+
orElse: () => null);
271+
if (session != null) {
272+
session.socketClient.close();
273+
_debugSessions.remove(session);
274+
return session.appTabId;
275+
} else {
276+
return -1;
277+
}
230278
}
231279

232280
void sendMessageToExtensions(Request request) {
@@ -400,8 +448,8 @@ ExtensionEvent _extensionEventFor(String method, Object params) =>
400448
..params = jsonEncode(json.decode(stringify(params)))
401449
..method = jsonEncode(method));
402450

403-
/// Forward the event if applicable.
404-
void _filterAndForward(Debuggee source, String method, Object params) {
451+
/// Forward debugger events to the backend if applicable.
452+
void _filterAndForwardToBackend(Debuggee source, String method, Object params) {
405453
var debugSession = _debugSessions.firstWhere(
406454
(session) => session.appTabId == source.tabId,
407455
orElse: () => null);

0 commit comments

Comments
 (0)