@@ -60,87 +60,53 @@ class DebugSession {
60
60
}
61
61
62
62
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));
96
65
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));
102
72
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));
107
76
108
- isDartDebugExtension = true ;
77
+ // When a Dart application tab is closed, detach the corresponding debug
78
+ // session:
79
+ tabsOnRemovedAddListener (allowInterop (_maybeDetachDebugSessionForTab));
109
80
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);
121
84
}));
122
85
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:
123
93
tabsOnActivatedAddListener (allowInterop ((ActiveInfo info) {
124
94
_updateIcon ();
125
95
}));
126
96
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 ();
142
107
}));
143
108
109
+ // Maybe update the extension icon during tab navigation:
144
110
webNavigationOnCommittedAddListener (
145
111
allowInterop ((NavigationInfo navigationInfo) {
146
112
if (navigationInfo.transitionType != 'auto_subframe' &&
@@ -149,84 +115,166 @@ void main() {
149
115
}
150
116
}));
151
117
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 ]);
154
143
}));
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
+ }
155
153
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 ;
166
165
}
166
+ _tabsToAttach.add (currentTab);
167
+ sendCommand (Debuggee (tabId: currentTab.id), 'Runtime.enable' , EmptyParam (),
168
+ allowInterop ((e) {}));
167
169
}));
170
+ }
168
171
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);
176
200
}
177
- }));
201
+ }
202
+ }
178
203
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
+ }
186
211
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
+ }
206
234
}
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 ' );
217
240
}
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 }' );
218
251
}
219
- }));
252
+ }
253
+ }
220
254
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
+ }
230
278
}
231
279
232
280
void sendMessageToExtensions (Request request) {
@@ -400,8 +448,8 @@ ExtensionEvent _extensionEventFor(String method, Object params) =>
400
448
..params = jsonEncode (json.decode (stringify (params)))
401
449
..method = jsonEncode (method));
402
450
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) {
405
453
var debugSession = _debugSessions.firstWhere (
406
454
(session) => session.appTabId == source.tabId,
407
455
orElse: () => null );
0 commit comments