diff --git a/dwds/debug_extension_mv3/web/background.dart b/dwds/debug_extension_mv3/web/background.dart index 1ccec7d2e..b24d44629 100644 --- a/dwds/debug_extension_mv3/web/background.dart +++ b/dwds/debug_extension_mv3/web/background.dart @@ -9,6 +9,7 @@ import 'package:dwds/data/debug_info.dart'; import 'package:js/js.dart'; import 'chrome_api.dart'; +import 'cider_connection.dart'; import 'cross_extension_communication.dart'; import 'data_types.dart'; import 'debug_session.dart'; @@ -31,6 +32,10 @@ void _registerListeners() { chrome.runtime.onMessageExternal.addListener( allowInterop(handleMessagesFromAngularDartDevTools), ); + // The only external service that sends messages to the Dart Debug Extension + // is Cider. + chrome.runtime.onConnectExternal + .addListener(allowInterop(handleCiderConnectRequest)); // Update the extension icon on tab navigation: chrome.tabs.onActivated.addListener( allowInterop((ActiveInfo info) async { @@ -105,7 +110,7 @@ Future _handleRuntimeMessages( // Save the debug info for the Dart app in storage: await setStorageObject( type: StorageObject.debugInfo, - value: _addTabUrl(debugInfo, tabUrl: dartTab.url), + value: _addTabInfo(debugInfo, tab: dartTab), tabId: dartTab.id, ); // Update the icon to show that a Dart app has been detected: @@ -183,7 +188,7 @@ bool _isInternalNavigation(NavigationInfo navigationInfo) { ].contains(navigationInfo.transitionType); } -DebugInfo _addTabUrl(DebugInfo debugInfo, {required String tabUrl}) { +DebugInfo _addTabInfo(DebugInfo debugInfo, {required Tab tab}) { return DebugInfo( (b) => b ..appEntrypointPath = debugInfo.appEntrypointPath @@ -195,7 +200,9 @@ DebugInfo _addTabUrl(DebugInfo debugInfo, {required String tabUrl}) { ..extensionUrl = debugInfo.extensionUrl ..isInternalBuild = debugInfo.isInternalBuild ..isFlutterApp = debugInfo.isFlutterApp - ..tabUrl = tabUrl, + ..workspaceName = debugInfo.workspaceName + ..tabUrl = tab.url + ..tabId = tab.id, ); } diff --git a/dwds/debug_extension_mv3/web/chrome_api.dart b/dwds/debug_extension_mv3/web/chrome_api.dart index 20bb95df6..937d1d9f7 100644 --- a/dwds/debug_extension_mv3/web/chrome_api.dart +++ b/dwds/debug_extension_mv3/web/chrome_api.dart @@ -181,6 +181,8 @@ class Runtime { external ConnectionHandler get onConnect; + external ConnectionHandler get onConnectExternal; + external OnMessageHandler get onMessage; external OnMessageHandler get onMessageExternal; @@ -203,9 +205,19 @@ class ConnectInfo { class Port { external String? get name; external void disconnect(); + external void postMessage(Object message); + external OnPortMessageHandler get onMessage; external ConnectionHandler get onDisconnect; } +@JS() +@anonymous +class OnPortMessageHandler { + external void addListener( + void Function(dynamic, Port) callback, + ); +} + @JS() @anonymous class ConnectionHandler { @@ -252,7 +264,10 @@ class Storage { @JS() @anonymous class StorageArea { - external Object get(List keys, void Function(Object result) callback); + external Object get( + List? keys, + void Function(Object result) callback, + ); external Object set(Object items, void Function()? callback); diff --git a/dwds/debug_extension_mv3/web/cider_connection.dart b/dwds/debug_extension_mv3/web/cider_connection.dart new file mode 100644 index 000000000..71b4b30be --- /dev/null +++ b/dwds/debug_extension_mv3/web/cider_connection.dart @@ -0,0 +1,190 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +@JS() +library cider_connection; + +import 'dart:convert'; + +import 'package:dwds/data/debug_info.dart'; +import 'package:js/js.dart'; + +import 'chrome_api.dart'; +import 'debug_session.dart'; +import 'logger.dart'; +import 'storage.dart'; + +/// Defines the message types that can be passed to/from Cider. +/// +/// The types must match those defined by ChromeExtensionMessageType in the +/// Cider extension. +enum CiderMessageType { + error, + startDebugResponse, + startDebugRequest, + stopDebugResponse, + stopDebugRequest, +} + +/// Defines the error types that can be sent to Cider. +/// +/// The types must match those defined by ChromeExtensionErrorType in the +/// Cider extension. +enum CiderErrorType { + chromeError, + internalError, + invalidRequest, + multipleDartTabs, + noDartTab, + noWorkspace, +} + +const _ciderPortName = 'cider'; +Port? _ciderPort; + +/// Handles a connect request from Cider. +/// +/// The only site allowed to connect with this extension is Cider. The allowed +/// URIs for Cider are set in the externally_connectable field in the manifest. +void handleCiderConnectRequest(Port port) { + if (port.name == _ciderPortName) { + _ciderPort = port; + + port.onMessage.addListener( + allowInterop(_handleMessageFromCider), + ); + } +} + +/// Sends a message to the Cider-connected port. +void sendMessageToCider({ + required CiderMessageType messageType, + String? messageBody, +}) { + if (_ciderPort == null) return; + final message = jsonEncode({ + 'messageType': messageType.name, + 'messageBody': messageBody, + }); + _ciderPort!.postMessage(message); +} + +/// Sends an error message to the Cider-connected port. +void sendErrorMessageToCider({ + required CiderErrorType errorType, + String? errorDetails, +}) { + debugWarn('CiderError.${errorType.name} $errorDetails'); + if (_ciderPort == null) return; + final message = jsonEncode({ + 'messageType': CiderMessageType.error.name, + 'errorType': errorType.name, + 'messageBody': errorDetails, + }); + _ciderPort!.postMessage(message); +} + +Future _handleMessageFromCider(dynamic message, Port _) async { + if (message is! String) { + sendErrorMessageToCider( + errorType: CiderErrorType.invalidRequest, + errorDetails: 'Expected request to be a string: $message', + ); + return; + } + + final decoded = jsonDecode(message) as Map; + final messageType = decoded['messageType'] as String?; + final messageBody = decoded['messageBody'] as String?; + + if (messageType == CiderMessageType.startDebugRequest.name) { + await _startDebugging(workspaceName: messageBody); + } else if (messageType == CiderMessageType.stopDebugRequest.name) { + await _stopDebugging(workspaceName: messageBody); + } +} + +Future _startDebugging({String? workspaceName}) async { + if (workspaceName == null) { + _sendNoWorkspaceError(); + return; + } + + final dartTab = await _findDartTabIdForWorkspace(workspaceName); + if (dartTab != null) { + // TODO(https://github.com/dart-lang/webdev/issues/2198): When debugging + // with Cider, disable debugging with DevTools. + await attachDebugger(dartTab, trigger: Trigger.cider); + } +} + +Future _stopDebugging({String? workspaceName}) async { + if (workspaceName == null) { + _sendNoWorkspaceError(); + return; + } + + final dartTab = await _findDartTabIdForWorkspace(workspaceName); + if (dartTab == null) return; + final successfullyDetached = await detachDebugger( + dartTab, + type: TabType.dartApp, + reason: DetachReason.canceledByUser, + ); + + if (successfullyDetached) { + sendMessageToCider(messageType: CiderMessageType.stopDebugResponse); + } else { + sendErrorMessageToCider( + errorType: CiderErrorType.internalError, + errorDetails: 'Unable to detach debugger.', + ); + } +} + +void _sendNoWorkspaceError() { + sendErrorMessageToCider( + errorType: CiderErrorType.noWorkspace, + errorDetails: 'Cannot find a debuggable Dart tab without a workspace', + ); +} + +Future _findDartTabIdForWorkspace(String workspaceName) async { + final allTabsInfo = await fetchAllStorageObjectsOfType( + type: StorageObject.debugInfo, + ); + final dartTabIds = allTabsInfo + .where( + (debugInfo) => debugInfo.workspaceName == workspaceName, + ) + .map( + (info) => info.tabId, + ) + .toList(); + + if (dartTabIds.isEmpty) { + sendErrorMessageToCider( + errorType: CiderErrorType.noDartTab, + errorDetails: 'No debuggable Dart tabs found.', + ); + return null; + } + if (dartTabIds.length > 1) { + sendErrorMessageToCider( + errorType: CiderErrorType.noDartTab, + errorDetails: 'Too many debuggable Dart tabs found.', + ); + return null; + } + final tabId = dartTabIds.first; + if (tabId == null) { + sendErrorMessageToCider( + errorType: CiderErrorType.chromeError, + errorDetails: 'Debuggable Dart tab is null.', + ); + return null; + } + + return tabId; +} diff --git a/dwds/debug_extension_mv3/web/debug_session.dart b/dwds/debug_extension_mv3/web/debug_session.dart index 310f5b684..2008408d2 100644 --- a/dwds/debug_extension_mv3/web/debug_session.dart +++ b/dwds/debug_extension_mv3/web/debug_session.dart @@ -22,6 +22,7 @@ import 'package:sse/client/sse_client.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; import 'chrome_api.dart'; +import 'cider_connection.dart'; import 'cross_extension_communication.dart'; import 'data_serializers.dart'; import 'data_types.dart'; @@ -79,6 +80,7 @@ enum TabType { enum Trigger { angularDartDevTools, + cider, extensionPanel, extensionIcon, } @@ -408,7 +410,15 @@ void _routeDwdsEvent(String eventData, SocketClient client, int tabId) { tabId: tabId, ); if (message.method == 'dwds.devtoolsUri') { - _openDevTools(message.params, dartAppTabId: tabId); + if (_tabIdToTrigger[tabId] != Trigger.cider) { + _openDevTools(message.params, dartAppTabId: tabId); + } + } + if (message.method == 'dwds.plainUri') { + sendMessageToCider( + messageType: CiderMessageType.startDebugResponse, + messageBody: message.params, + ); } if (message.method == 'dwds.encodedUri') { setStorageObject( @@ -774,6 +784,8 @@ DebuggerLocation? _debuggerLocation(int dartAppTabId) { return DebuggerLocation.angularDartDevTools; case Trigger.extensionPanel: return DebuggerLocation.chromeDevTools; + case Trigger.cider: + return DebuggerLocation.ide; } } diff --git a/dwds/debug_extension_mv3/web/manifest_mv2.json b/dwds/debug_extension_mv3/web/manifest_mv2.json index 49f78ad4f..1c700f1ea 100644 --- a/dwds/debug_extension_mv3/web/manifest_mv2.json +++ b/dwds/debug_extension_mv3/web/manifest_mv2.json @@ -7,7 +7,15 @@ "default_icon": "static_assets/dart_grey.png" }, "externally_connectable": { - "ids": ["nbkbficgbembimioedhceniahniffgpl"] + "ids": ["nbkbficgbembimioedhceniahniffgpl"], + "matches": [ + "https://cider.corp.google.com/*", + "https://cider-staging.corp.google.com/*", + "https://cider-test.corp.google.com/*", + "https://cider-v.corp.google.com/*", + "https://cider-v-staging.corp.google.com/*", + "https://cider-v-test.corp.google.com/*" + ] }, "permissions": ["debugger", "notifications", "storage", "webNavigation"], "background": { diff --git a/dwds/debug_extension_mv3/web/manifest_mv3.json b/dwds/debug_extension_mv3/web/manifest_mv3.json index f73e0e29a..1b0475353 100644 --- a/dwds/debug_extension_mv3/web/manifest_mv3.json +++ b/dwds/debug_extension_mv3/web/manifest_mv3.json @@ -7,7 +7,15 @@ "default_icon": "static_assets/dart_grey.png" }, "externally_connectable": { - "ids": ["nbkbficgbembimioedhceniahniffgpl"] + "ids": ["nbkbficgbembimioedhceniahniffgpl"], + "matches": [ + "https://cider.corp.google.com/*", + "https://cider-staging.corp.google.com/*", + "https://cider-test.corp.google.com/*", + "https://cider-v.corp.google.com/*", + "https://cider-v-staging.corp.google.com/*", + "https://cider-v-test.corp.google.com/*" + ] }, "permissions": [ "debugger", diff --git a/dwds/debug_extension_mv3/web/storage.dart b/dwds/debug_extension_mv3/web/storage.dart index 3281783cc..0b46cd4a9 100644 --- a/dwds/debug_extension_mv3/web/storage.dart +++ b/dwds/debug_extension_mv3/web/storage.dart @@ -102,6 +102,36 @@ Future fetchStorageObject({required StorageObject type, int? tabId}) { return completer.future; } +Future> fetchAllStorageObjectsOfType({required StorageObject type}) { + final completer = Completer>(); + final storageArea = _getStorageArea(type.persistence); + storageArea.get( + null, + allowInterop((Object? storageContents) { + if (storageContents == null) { + debugWarn('No storage objects of type exist.', prefix: type.name); + completer.complete([]); + return; + } + final allKeys = List.from(objectKeys(storageContents)); + final storageKeys = allKeys.where((key) => key.contains(type.name)); + final result = []; + for (final key in storageKeys) { + final json = getProperty(storageContents, key) as String?; + if (json != null) { + if (T == String) { + result.add(json as T); + } else { + result.add(serializers.deserialize(jsonDecode(json)) as T); + } + } + } + completer.complete(result); + }), + ); + return completer.future; +} + Future removeStorageObject({required StorageObject type, int? tabId}) { final storageKey = _createStorageKey(type, tabId); final completer = Completer(); diff --git a/dwds/lib/data/debug_info.dart b/dwds/lib/data/debug_info.dart index d1f9eb5e4..fa0132bff 100644 --- a/dwds/lib/data/debug_info.dart +++ b/dwds/lib/data/debug_info.dart @@ -24,5 +24,7 @@ abstract class DebugInfo implements Built { String? get extensionUrl; bool? get isInternalBuild; bool? get isFlutterApp; + String? get workspaceName; String? get tabUrl; + int? get tabId; } diff --git a/dwds/lib/data/debug_info.g.dart b/dwds/lib/data/debug_info.g.dart index 85a444ff6..e4ccd3ee5 100644 --- a/dwds/lib/data/debug_info.g.dart +++ b/dwds/lib/data/debug_info.g.dart @@ -89,6 +89,13 @@ class _$DebugInfoSerializer implements StructuredSerializer { ..add( serializers.serialize(value, specifiedType: const FullType(bool))); } + value = object.workspaceName; + if (value != null) { + result + ..add('workspaceName') + ..add(serializers.serialize(value, + specifiedType: const FullType(String))); + } value = object.tabUrl; if (value != null) { result @@ -96,6 +103,12 @@ class _$DebugInfoSerializer implements StructuredSerializer { ..add(serializers.serialize(value, specifiedType: const FullType(String))); } + value = object.tabId; + if (value != null) { + result + ..add('tabId') + ..add(serializers.serialize(value, specifiedType: const FullType(int))); + } return result; } @@ -150,10 +163,18 @@ class _$DebugInfoSerializer implements StructuredSerializer { result.isFlutterApp = serializers.deserialize(value, specifiedType: const FullType(bool)) as bool?; break; + case 'workspaceName': + result.workspaceName = serializers.deserialize(value, + specifiedType: const FullType(String)) as String?; + break; case 'tabUrl': result.tabUrl = serializers.deserialize(value, specifiedType: const FullType(String)) as String?; break; + case 'tabId': + result.tabId = serializers.deserialize(value, + specifiedType: const FullType(int)) as int?; + break; } } @@ -183,7 +204,11 @@ class _$DebugInfo extends DebugInfo { @override final bool? isFlutterApp; @override + final String? workspaceName; + @override final String? tabUrl; + @override + final int? tabId; factory _$DebugInfo([void Function(DebugInfoBuilder)? updates]) => (new DebugInfoBuilder()..update(updates))._build(); @@ -199,7 +224,9 @@ class _$DebugInfo extends DebugInfo { this.extensionUrl, this.isInternalBuild, this.isFlutterApp, - this.tabUrl}) + this.workspaceName, + this.tabUrl, + this.tabId}) : super._(); @override @@ -223,7 +250,9 @@ class _$DebugInfo extends DebugInfo { extensionUrl == other.extensionUrl && isInternalBuild == other.isInternalBuild && isFlutterApp == other.isFlutterApp && - tabUrl == other.tabUrl; + workspaceName == other.workspaceName && + tabUrl == other.tabUrl && + tabId == other.tabId; } @override @@ -239,7 +268,9 @@ class _$DebugInfo extends DebugInfo { _$hash = $jc(_$hash, extensionUrl.hashCode); _$hash = $jc(_$hash, isInternalBuild.hashCode); _$hash = $jc(_$hash, isFlutterApp.hashCode); + _$hash = $jc(_$hash, workspaceName.hashCode); _$hash = $jc(_$hash, tabUrl.hashCode); + _$hash = $jc(_$hash, tabId.hashCode); _$hash = $jf(_$hash); return _$hash; } @@ -257,7 +288,9 @@ class _$DebugInfo extends DebugInfo { ..add('extensionUrl', extensionUrl) ..add('isInternalBuild', isInternalBuild) ..add('isFlutterApp', isFlutterApp) - ..add('tabUrl', tabUrl)) + ..add('workspaceName', workspaceName) + ..add('tabUrl', tabUrl) + ..add('tabId', tabId)) .toString(); } } @@ -308,10 +341,19 @@ class DebugInfoBuilder implements Builder { bool? get isFlutterApp => _$this._isFlutterApp; set isFlutterApp(bool? isFlutterApp) => _$this._isFlutterApp = isFlutterApp; + String? _workspaceName; + String? get workspaceName => _$this._workspaceName; + set workspaceName(String? workspaceName) => + _$this._workspaceName = workspaceName; + String? _tabUrl; String? get tabUrl => _$this._tabUrl; set tabUrl(String? tabUrl) => _$this._tabUrl = tabUrl; + int? _tabId; + int? get tabId => _$this._tabId; + set tabId(int? tabId) => _$this._tabId = tabId; + DebugInfoBuilder(); DebugInfoBuilder get _$this { @@ -327,7 +369,9 @@ class DebugInfoBuilder implements Builder { _extensionUrl = $v.extensionUrl; _isInternalBuild = $v.isInternalBuild; _isFlutterApp = $v.isFlutterApp; + _workspaceName = $v.workspaceName; _tabUrl = $v.tabUrl; + _tabId = $v.tabId; _$v = null; } return this; @@ -360,7 +404,9 @@ class DebugInfoBuilder implements Builder { extensionUrl: extensionUrl, isInternalBuild: isInternalBuild, isFlutterApp: isFlutterApp, - tabUrl: tabUrl); + workspaceName: workspaceName, + tabUrl: tabUrl, + tabId: tabId); replace(_$result); return _$result; } diff --git a/dwds/lib/src/injected/client.js b/dwds/lib/src/injected/client.js index eaf5751cf..cf995b3be 100644 --- a/dwds/lib/src/injected/client.js +++ b/dwds/lib/src/injected/client.js @@ -1,4 +1,4 @@ -// Generated by dart2js (NullSafetyMode.sound, csp, intern-composite-values), the Dart to JavaScript compiler version: 3.2.0-edge.01653dc009582892c9ad334502b0c9f262272544. +// Generated by dart2js (NullSafetyMode.sound, csp, intern-composite-values), the Dart to JavaScript compiler version: 3.2.0-edge.a8a9cc0337efe1c4855901f78adc877cb28dd1c7. // The code supports the following hooks: // dartPrint(message): // if this function is defined it is called instead of the Dart [print] @@ -9031,7 +9031,7 @@ }, _$DebugInfoSerializer: function _$DebugInfoSerializer() { }, - _$DebugInfo: function _$DebugInfo(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10) { + _$DebugInfo: function _$DebugInfo(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12) { var _ = this; _.appEntrypointPath = t0; _.appId = t1; @@ -9043,11 +9043,13 @@ _.extensionUrl = t7; _.isInternalBuild = t8; _.isFlutterApp = t9; - _.tabUrl = t10; + _.workspaceName = t10; + _.tabUrl = t11; + _.tabId = t12; }, DebugInfoBuilder: function DebugInfoBuilder() { var _ = this; - _._tabUrl = _._isFlutterApp = _._isInternalBuild = _._extensionUrl = _._dwdsVersion = _._authUrl = _._appUrl = _._appOrigin = _._appInstanceId = _._debug_info$_appId = _._appEntrypointPath = _._debug_info$_$v = null; + _._tabId = _._tabUrl = _._workspaceName = _._isFlutterApp = _._isInternalBuild = _._extensionUrl = _._dwdsVersion = _._authUrl = _._appUrl = _._appOrigin = _._appInstanceId = _._debug_info$_appId = _._appEntrypointPath = _._debug_info$_$v = null; }, DevToolsRequest: function DevToolsRequest() { }, @@ -23117,11 +23119,21 @@ result.push("isFlutterApp"); result.push(serializers.serialize$2$specifiedType(value, B.FullType_MtR)); } + value = object.workspaceName; + if (value != null) { + result.push("workspaceName"); + result.push(serializers.serialize$2$specifiedType(value, B.FullType_h8g)); + } value = object.tabUrl; if (value != null) { result.push("tabUrl"); result.push(serializers.serialize$2$specifiedType(value, B.FullType_h8g)); } + value = object.tabId; + if (value != null) { + result.push("tabId"); + result.push(serializers.serialize$2$specifiedType(value, B.FullType_kjq)); + } return result; }, serialize$2(serializers, object) { @@ -23178,10 +23190,18 @@ t1 = A._asBoolQ(serializers.deserialize$2$specifiedType(value, B.FullType_MtR)); result.get$_$this()._isFlutterApp = t1; break; + case "workspaceName": + t1 = A._asStringQ(serializers.deserialize$2$specifiedType(value, B.FullType_h8g)); + result.get$_$this()._workspaceName = t1; + break; case "tabUrl": t1 = A._asStringQ(serializers.deserialize$2$specifiedType(value, B.FullType_h8g)); result.get$_$this()._tabUrl = t1; break; + case "tabId": + t1 = A._asIntQ(serializers.deserialize$2$specifiedType(value, B.FullType_kjq)); + result.get$_$this()._tabId = t1; + break; } } return result._debug_info$_build$0(); @@ -23205,11 +23225,11 @@ return false; if (other === _this) return true; - return other instanceof A._$DebugInfo && _this.appEntrypointPath == other.appEntrypointPath && _this.appId == other.appId && _this.appInstanceId == other.appInstanceId && _this.appOrigin == other.appOrigin && _this.appUrl == other.appUrl && _this.authUrl == other.authUrl && _this.dwdsVersion == other.dwdsVersion && _this.extensionUrl == other.extensionUrl && _this.isInternalBuild == other.isInternalBuild && _this.isFlutterApp == other.isFlutterApp && _this.tabUrl == other.tabUrl; + return other instanceof A._$DebugInfo && _this.appEntrypointPath == other.appEntrypointPath && _this.appId == other.appId && _this.appInstanceId == other.appInstanceId && _this.appOrigin == other.appOrigin && _this.appUrl == other.appUrl && _this.authUrl == other.authUrl && _this.dwdsVersion == other.dwdsVersion && _this.extensionUrl == other.extensionUrl && _this.isInternalBuild == other.isInternalBuild && _this.isFlutterApp == other.isFlutterApp && _this.workspaceName == other.workspaceName && _this.tabUrl == other.tabUrl && _this.tabId == other.tabId; }, get$hashCode(_) { var _this = this; - return A.$jf(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(0, J.get$hashCode$(_this.appEntrypointPath)), J.get$hashCode$(_this.appId)), J.get$hashCode$(_this.appInstanceId)), J.get$hashCode$(_this.appOrigin)), J.get$hashCode$(_this.appUrl)), J.get$hashCode$(_this.authUrl)), J.get$hashCode$(_this.dwdsVersion)), J.get$hashCode$(_this.extensionUrl)), J.get$hashCode$(_this.isInternalBuild)), J.get$hashCode$(_this.isFlutterApp)), J.get$hashCode$(_this.tabUrl))); + return A.$jf(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(A.$jc(0, J.get$hashCode$(_this.appEntrypointPath)), J.get$hashCode$(_this.appId)), J.get$hashCode$(_this.appInstanceId)), J.get$hashCode$(_this.appOrigin)), J.get$hashCode$(_this.appUrl)), J.get$hashCode$(_this.authUrl)), J.get$hashCode$(_this.dwdsVersion)), J.get$hashCode$(_this.extensionUrl)), J.get$hashCode$(_this.isInternalBuild)), J.get$hashCode$(_this.isFlutterApp)), J.get$hashCode$(_this.workspaceName)), J.get$hashCode$(_this.tabUrl)), J.get$hashCode$(_this.tabId))); }, toString$0(_) { var _this = this, @@ -23225,7 +23245,9 @@ t2.add$2(t1, "extensionUrl", _this.extensionUrl); t2.add$2(t1, "isInternalBuild", _this.isInternalBuild); t2.add$2(t1, "isFlutterApp", _this.isFlutterApp); + t2.add$2(t1, "workspaceName", _this.workspaceName); t2.add$2(t1, "tabUrl", _this.tabUrl); + t2.add$2(t1, "tabId", _this.tabId); return t2.toString$0(t1); } }; @@ -23244,7 +23266,9 @@ _this._extensionUrl = $$v.extensionUrl; _this._isInternalBuild = $$v.isInternalBuild; _this._isFlutterApp = $$v.isFlutterApp; + _this._workspaceName = $$v.workspaceName; _this._tabUrl = $$v.tabUrl; + _this._tabId = $$v.tabId; _this._debug_info$_$v = null; } return _this; @@ -23253,7 +23277,7 @@ var _this = this, _$result = _this._debug_info$_$v; if (_$result == null) - _$result = new A._$DebugInfo(_this.get$_$this()._appEntrypointPath, _this.get$_$this()._debug_info$_appId, _this.get$_$this()._appInstanceId, _this.get$_$this()._appOrigin, _this.get$_$this()._appUrl, _this.get$_$this()._authUrl, _this.get$_$this()._dwdsVersion, _this.get$_$this()._extensionUrl, _this.get$_$this()._isInternalBuild, _this.get$_$this()._isFlutterApp, _this.get$_$this()._tabUrl); + _$result = new A._$DebugInfo(_this.get$_$this()._appEntrypointPath, _this.get$_$this()._debug_info$_appId, _this.get$_$this()._appInstanceId, _this.get$_$this()._appOrigin, _this.get$_$this()._appUrl, _this.get$_$this()._authUrl, _this.get$_$this()._dwdsVersion, _this.get$_$this()._extensionUrl, _this.get$_$this()._isInternalBuild, _this.get$_$this()._isFlutterApp, _this.get$_$this()._workspaceName, _this.get$_$this()._tabUrl, _this.get$_$this()._tabId); A.ArgumentError_checkNotNull(_$result, "other", type$.DebugInfo); return _this._debug_info$_$v = _$result; }