@@ -60,87 +60,53 @@ class DebugSession {
6060}
6161
6262void 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
232280void 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