diff --git a/dwds/debug_extension_mv3/web/debug_session.dart b/dwds/debug_extension_mv3/web/debug_session.dart index 4ea975ef0..00a514221 100644 --- a/dwds/debug_extension_mv3/web/debug_session.dart +++ b/dwds/debug_extension_mv3/web/debug_session.dart @@ -267,7 +267,8 @@ Future _connectToDwds({ ..instanceId = debugInfo.appInstanceId ..contextId = dartAppContextId ..tabUrl = tabUrl - ..uriOnly = true)); + ..uriOnly = true + ..isMv3Extension = true)); return true; } @@ -358,7 +359,12 @@ void _openDevTools(String devToolsUri, {required int dartAppTabId}) async { final devToolsOpener = await fetchStorageObject( type: StorageObject.devToolsOpener); final devToolsTab = await createTab( - devToolsUri, + addQueryParameters( + devToolsUri, + queryParameters: { + 'ide': 'DebugExtension', + }, + ), inNewWindow: devToolsOpener?.newWindow ?? false, ); debugSession.devToolsTabId = devToolsTab.id; diff --git a/dwds/debug_extension_mv3/web/panel.dart b/dwds/debug_extension_mv3/web/panel.dart index d2ed9a68d..7acc3c820 100644 --- a/dwds/debug_extension_mv3/web/panel.dart +++ b/dwds/debug_extension_mv3/web/panel.dart @@ -18,9 +18,10 @@ import 'debug_session.dart'; import 'logger.dart'; import 'messaging.dart'; import 'storage.dart'; +import 'utils.dart'; bool connecting = false; -String devToolsBackgroundColor = darkColor; +String backgroundColor = darkColor; bool isDartApp = true; const bugLinkId = 'bugLink'; @@ -148,10 +149,10 @@ void _setColorThemeToMatchChromeDevTools() async { final chromeTheme = chrome.devtools.panels.themeName; final panelBody = document.getElementById(panelBodyId); if (chromeTheme == 'dark') { - devToolsBackgroundColor = darkColor; + backgroundColor = darkColor; _updateColorThemeForElement(panelBody, isDarkTheme: true); } else { - devToolsBackgroundColor = lightColor; + backgroundColor = lightColor; _updateColorThemeForElement(panelBody, isDarkTheme: false); } } @@ -246,10 +247,16 @@ void _injectDevToolsIframe(String devToolsUri) { final panelBody = document.getElementById(panelBodyId); final panelType = panelBody?.getAttribute(panelAttribute) ?? 'debugger'; final iframe = document.createElement('iframe'); - iframe.setAttribute( - 'src', - '$devToolsUri&embed=true&page=$panelType&backgroundColor=$devToolsBackgroundColor', + final iframeSrc = addQueryParameters( + devToolsUri, + queryParameters: { + 'ide': 'ChromeDevTools', + 'embed': 'true', + 'page': panelType, + 'backgroundColor': backgroundColor, + }, ); + iframe.setAttribute('src', iframeSrc); _hideWarningBanner(); _updateElementVisibility(landingPageId, visible: false); _updateElementVisibility(loadingSpinnerId, visible: false); diff --git a/dwds/debug_extension_mv3/web/utils.dart b/dwds/debug_extension_mv3/web/utils.dart index c75549e7f..5504e7fc6 100644 --- a/dwds/debug_extension_mv3/web/utils.dart +++ b/dwds/debug_extension_mv3/web/utils.dart @@ -47,3 +47,15 @@ bool isDevMode() { final extensionName = getProperty(extensionManifest, 'name') ?? ''; return extensionName.contains('DEV'); } + +String addQueryParameters( + String uri, { + required Map queryParameters, +}) { + final originalUri = Uri.parse(uri); + final newUri = originalUri.replace(queryParameters: { + ...originalUri.queryParameters, + ...queryParameters, + }); + return newUri.toString(); +} diff --git a/dwds/lib/data/devtools_request.dart b/dwds/lib/data/devtools_request.dart index d4e87b459..de4c65ce3 100644 --- a/dwds/lib/data/devtools_request.dart +++ b/dwds/lib/data/devtools_request.dart @@ -40,11 +40,18 @@ abstract class DevToolsRequest /// correct `dartAppInstanceId` automatically. String? get tabUrl; - /// If this is a uri only request. + /// Designates this as a request to send back the DevTools URI instead of + /// opening DevTools in a new tab or window. /// - /// Only available on requests coming from dart debug extension. - /// If true, DevTools should open in an embedded Chrome DevTools tab. + /// Only available on requests coming from the Dart Debug Extension. Is `null` + /// for local debug service. bool? get uriOnly; + + /// Whether or not the MV3 Dart Debug Extension sent the request. Is `null` + /// for local debug service. + /// + /// Only available on requests coming from the Dart Debug Extension. + bool? get isMv3Extension; } /// A response to a [DevToolsRequest]. diff --git a/dwds/lib/data/devtools_request.g.dart b/dwds/lib/data/devtools_request.g.dart index ba11296ea..f03d737e5 100644 --- a/dwds/lib/data/devtools_request.g.dart +++ b/dwds/lib/data/devtools_request.g.dart @@ -50,6 +50,13 @@ class _$DevToolsRequestSerializer ..add( serializers.serialize(value, specifiedType: const FullType(bool))); } + value = object.isMv3Extension; + if (value != null) { + result + ..add('isMv3Extension') + ..add( + serializers.serialize(value, specifiedType: const FullType(bool))); + } return result; } @@ -85,6 +92,10 @@ class _$DevToolsRequestSerializer result.uriOnly = serializers.deserialize(value, specifiedType: const FullType(bool)) as bool?; break; + case 'isMv3Extension': + result.isMv3Extension = serializers.deserialize(value, + specifiedType: const FullType(bool)) as bool?; + break; } } @@ -163,6 +174,8 @@ class _$DevToolsRequest extends DevToolsRequest { final String? tabUrl; @override final bool? uriOnly; + @override + final bool? isMv3Extension; factory _$DevToolsRequest([void Function(DevToolsRequestBuilder)? updates]) => (new DevToolsRequestBuilder()..update(updates))._build(); @@ -172,7 +185,8 @@ class _$DevToolsRequest extends DevToolsRequest { required this.instanceId, this.contextId, this.tabUrl, - this.uriOnly}) + this.uriOnly, + this.isMv3Extension}) : super._() { BuiltValueNullFieldError.checkNotNull(appId, r'DevToolsRequest', 'appId'); BuiltValueNullFieldError.checkNotNull( @@ -195,7 +209,8 @@ class _$DevToolsRequest extends DevToolsRequest { instanceId == other.instanceId && contextId == other.contextId && tabUrl == other.tabUrl && - uriOnly == other.uriOnly; + uriOnly == other.uriOnly && + isMv3Extension == other.isMv3Extension; } @override @@ -206,6 +221,7 @@ class _$DevToolsRequest extends DevToolsRequest { _$hash = $jc(_$hash, contextId.hashCode); _$hash = $jc(_$hash, tabUrl.hashCode); _$hash = $jc(_$hash, uriOnly.hashCode); + _$hash = $jc(_$hash, isMv3Extension.hashCode); _$hash = $jf(_$hash); return _$hash; } @@ -217,7 +233,8 @@ class _$DevToolsRequest extends DevToolsRequest { ..add('instanceId', instanceId) ..add('contextId', contextId) ..add('tabUrl', tabUrl) - ..add('uriOnly', uriOnly)) + ..add('uriOnly', uriOnly) + ..add('isMv3Extension', isMv3Extension)) .toString(); } } @@ -246,6 +263,11 @@ class DevToolsRequestBuilder bool? get uriOnly => _$this._uriOnly; set uriOnly(bool? uriOnly) => _$this._uriOnly = uriOnly; + bool? _isMv3Extension; + bool? get isMv3Extension => _$this._isMv3Extension; + set isMv3Extension(bool? isMv3Extension) => + _$this._isMv3Extension = isMv3Extension; + DevToolsRequestBuilder(); DevToolsRequestBuilder get _$this { @@ -256,6 +278,7 @@ class DevToolsRequestBuilder _contextId = $v.contextId; _tabUrl = $v.tabUrl; _uriOnly = $v.uriOnly; + _isMv3Extension = $v.isMv3Extension; _$v = null; } return this; @@ -284,7 +307,8 @@ class DevToolsRequestBuilder instanceId, r'DevToolsRequest', 'instanceId'), contextId: contextId, tabUrl: tabUrl, - uriOnly: uriOnly); + uriOnly: uriOnly, + isMv3Extension: isMv3Extension); replace(_$result); return _$result; } diff --git a/dwds/lib/src/handlers/dev_handler.dart b/dwds/lib/src/handlers/dev_handler.dart index 40e045ced..9523c368d 100644 --- a/dwds/lib/src/handlers/dev_handler.dart +++ b/dwds/lib/src/handlers/dev_handler.dart @@ -544,28 +544,40 @@ class DevHandler { extensionDebugConnections.add(DebugConnection(appServices)); _servicesByAppId[appId] = appServices; } + // If we don't have a DevTools instance, then are connecting to an IDE. + // Therefore return early instead of opening DevTools: + if (_devTools == null) return; + final encodedUri = await appServices.debugService.encodedUri; appServices.dwdsStats.updateLoadTime( debuggerStart: debuggerStart, devToolsStart: DateTime.now()); - if (_devTools != null) { - // If we only want the URI, this means we are embedding Dart DevTools in - // Chrome DevTools. Therefore return early. - if (devToolsRequest.uriOnly ?? false) { - final devToolsUri = _constructDevToolsUri( - encodedUri, - ideQueryParam: 'ChromeDevTools', - ); - extensionDebugger.sendEvent('dwds.devtoolsUri', devToolsUri); - return; - } - final devToolsUri = _constructDevToolsUri( + // TODO(elliette): Remove handling requests from the MV2 extension after + // MV3 release. + // If we only want the URI, this means the Dart Debug Extension should + // handle how to open it. Therefore return early before opening a new + // tab or window: + if (devToolsRequest.uriOnly ?? false) { + // The MV3 extension is responsible for adding the IDE query + // parameter to the DevTools URI. + final devToolsUri = (devToolsRequest.isMv3Extension ?? false) + ? _constructDevToolsUri(encodedUri) + : _constructDevToolsUri( + encodedUri, + ideQueryParam: 'ChromeDevTools', + ); + return extensionDebugger.sendEvent('dwds.devtoolsUri', devToolsUri); + } + + // Otherwise, launch DevTools in a new tab / window: + await _launchDevTools( + extensionDebugger, + _constructDevToolsUri( encodedUri, ideQueryParam: 'DebugExtension', - ); - await _launchDevTools(extensionDebugger, devToolsUri); - } + ), + ); }); } diff --git a/dwds/lib/src/injected/client.js b/dwds/lib/src/injected/client.js index 634a6d372..ed121c181 100644 --- a/dwds/lib/src/injected/client.js +++ b/dwds/lib/src/injected/client.js @@ -8309,17 +8309,18 @@ }, _$DevToolsResponseSerializer: function _$DevToolsResponseSerializer() { }, - _$DevToolsRequest: function _$DevToolsRequest(t0, t1, t2, t3, t4) { + _$DevToolsRequest: function _$DevToolsRequest(t0, t1, t2, t3, t4, t5) { var _ = this; _.appId = t0; _.instanceId = t1; _.contextId = t2; _.tabUrl = t3; _.uriOnly = t4; + _.isMv3Extension = t5; }, DevToolsRequestBuilder: function DevToolsRequestBuilder() { var _ = this; - _._uriOnly = _._tabUrl = _._contextId = _._devtools_request$_instanceId = _._devtools_request$_appId = _._devtools_request$_$v = null; + _._isMv3Extension = _._uriOnly = _._tabUrl = _._contextId = _._devtools_request$_instanceId = _._devtools_request$_appId = _._devtools_request$_$v = null; }, _$DevToolsResponse: function _$DevToolsResponse(t0, t1, t2) { this.success = t0; @@ -22935,6 +22936,11 @@ result.push("uriOnly"); result.push(serializers.serialize$2$specifiedType(value, B.FullType_MtR)); } + value = object.isMv3Extension; + if (value != null) { + result.push("isMv3Extension"); + result.push(serializers.serialize$2$specifiedType(value, B.FullType_MtR)); + } return result; }, serialize$2(serializers, object) { @@ -22975,6 +22981,10 @@ t1 = A._asBoolQ(serializers.deserialize$2$specifiedType(value, B.FullType_MtR)); result.get$_devtools_request$_$this()._uriOnly = t1; break; + case "isMv3Extension": + t1 = A._asBoolQ(serializers.deserialize$2$specifiedType(value, B.FullType_MtR)); + result.get$_devtools_request$_$this()._isMv3Extension = t1; + break; } } return result._devtools_request$_build$0(); @@ -23068,11 +23078,11 @@ return false; if (other === _this) return true; - return other instanceof A.DevToolsRequest && _this.appId === other.appId && _this.instanceId === other.instanceId && _this.contextId == other.contextId && _this.tabUrl == other.tabUrl && _this.uriOnly == other.uriOnly; + return other instanceof A.DevToolsRequest && _this.appId === other.appId && _this.instanceId === other.instanceId && _this.contextId == other.contextId && _this.tabUrl == other.tabUrl && _this.uriOnly == other.uriOnly && _this.isMv3Extension == other.isMv3Extension; }, get$hashCode(_) { var _this = this; - return A.$jf(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(0, B.JSString_methods.get$hashCode(_this.appId)), B.JSString_methods.get$hashCode(_this.instanceId)), J.get$hashCode$(_this.contextId)), J.get$hashCode$(_this.tabUrl)), J.get$hashCode$(_this.uriOnly))); + return A.$jf(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(0, B.JSString_methods.get$hashCode(_this.appId)), B.JSString_methods.get$hashCode(_this.instanceId)), J.get$hashCode$(_this.contextId)), J.get$hashCode$(_this.tabUrl)), J.get$hashCode$(_this.uriOnly)), J.get$hashCode$(_this.isMv3Extension))); }, toString$0(_) { var _this = this, @@ -23083,6 +23093,7 @@ t2.add$2(t1, "contextId", _this.contextId); t2.add$2(t1, "tabUrl", _this.tabUrl); t2.add$2(t1, "uriOnly", _this.uriOnly); + t2.add$2(t1, "isMv3Extension", _this.isMv3Extension); return t2.toString$0(t1); } }; @@ -23096,6 +23107,7 @@ _this._contextId = $$v.contextId; _this._tabUrl = $$v.tabUrl; _this._uriOnly = $$v.uriOnly; + _this._isMv3Extension = $$v.isMv3Extension; _this._devtools_request$_$v = null; } return _this; @@ -23109,7 +23121,7 @@ t1 = type$.String; t2 = A.BuiltValueNullFieldError_checkNotNull(_this.get$_devtools_request$_$this()._devtools_request$_appId, _s15_, "appId", t1); t3 = A.BuiltValueNullFieldError_checkNotNull(_this.get$_devtools_request$_$this()._devtools_request$_instanceId, _s15_, _s10_, t1); - _$result = new A._$DevToolsRequest(t2, t3, _this.get$_devtools_request$_$this()._contextId, _this.get$_devtools_request$_$this()._tabUrl, _this.get$_devtools_request$_$this()._uriOnly); + _$result = new A._$DevToolsRequest(t2, t3, _this.get$_devtools_request$_$this()._contextId, _this.get$_devtools_request$_$this()._tabUrl, _this.get$_devtools_request$_$this()._uriOnly, _this.get$_devtools_request$_$this()._isMv3Extension); A.BuiltValueNullFieldError_checkNotNull(t2, _s15_, "appId", t1); A.BuiltValueNullFieldError_checkNotNull(t3, _s15_, _s10_, t1); } diff --git a/dwds/test/puppeteer/extension_test.dart b/dwds/test/puppeteer/extension_test.dart index 088de5b66..f1cc393f7 100644 --- a/dwds/test/puppeteer/extension_test.dart +++ b/dwds/test/puppeteer/extension_test.dart @@ -148,7 +148,7 @@ void main() async { // Click on the Dart Debug Extension icon: await workerEvalDelay(); await clickOnExtensionIcon(worker); - // Verify the extension opened the Dart docs in the same window: + // Verify the extension opened DevTools in the same window: var devToolsTabTarget = await browser.waitForTarget( (target) => target.url.contains(devToolsUrlFragment)); var devToolsTab = await devToolsTabTarget.page; @@ -195,6 +195,33 @@ void main() async { await appTab.close(); }); + test('DevTools is opened with the correct query parameters', () async { + final appUrl = context.appUrl; + final devToolsUrlFragment = + useSse ? 'debugger?uri=sse' : 'debugger?uri=ws'; + // Navigate to the Dart app: + final appTab = + await navigateToPage(browser, url: appUrl, isNew: true); + // Click on the Dart Debug Extension icon: + await workerEvalDelay(); + await clickOnExtensionIcon(worker); + print('clicked, waiting for devtools'); + // Wait for DevTools to open: + final devToolsTabTarget = await browser.waitForTarget( + (target) => target.url.contains(devToolsUrlFragment)); + final devToolsUrl = devToolsTabTarget.url; + // Expect the correct query parameters to be on the DevTools url: + final uri = Uri.parse(devToolsUrl); + final queryParameters = uri.queryParameters; + expect(queryParameters.keys, unorderedMatches(['uri', 'ide'])); + expect(queryParameters, containsPair('ide', 'DebugExtension')); + expect(queryParameters, containsPair('uri', isNotEmpty)); + // Close the DevTools tab: + final devToolsTab = await devToolsTabTarget.page; + await devToolsTab.close(); + await appTab.close(); + }); + test( 'navigating away from the Dart app while debugging closes DevTools', () async { @@ -511,6 +538,47 @@ void main() async { 'debuggerPanelDisconnected_${isFlutterApp ? 'flutterApp' : 'dartApp'}', ); }); + + test('The Dart DevTools IFRAME has the correct query parameters', + () async { + final chromeDevToolsPage = await _getChromeDevToolsPage(browser); + // There are no hooks for when a panel is added to Chrome DevTools, + // therefore we rely on a slight delay: + await Future.delayed(Duration(seconds: 1)); + // Navigate to the Dart Debugger panel: + _tabLeft(chromeDevToolsPage); + if (isFlutterApp) { + _tabLeft(chromeDevToolsPage); + } + await _clickLaunchButton( + browser, + panel: Panel.debugger, + ); + // Expect the Dart DevTools IFRAME to be added: + final devToolsUrlFragment = + 'ide=ChromeDevTools&embed=true&page=debugger'; + final iframeTarget = await browser.waitForTarget( + (target) => target.url.contains(devToolsUrlFragment), + ); + final iframeUrl = iframeTarget.url; + // Expect the correct query parameters to be on the IFRAME url: + final uri = Uri.parse(iframeUrl); + final queryParameters = uri.queryParameters; + expect( + queryParameters.keys, + unorderedMatches([ + 'uri', + 'ide', + 'embed', + 'page', + 'backgroundColor', + ])); + expect(queryParameters, containsPair('ide', 'ChromeDevTools')); + expect(queryParameters, containsPair('uri', isNotEmpty)); + expect(queryParameters, containsPair('page', isNotEmpty)); + expect( + queryParameters, containsPair('backgroundColor', isNotEmpty)); + }); }); } });