From d197947953ad7726cadd33711b76149d04168a4d Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Sat, 28 Jun 2025 11:11:39 -0700 Subject: [PATCH 01/13] Invalidate deleted modules and libraries and process reloaded modules and libraries in provider --- .../debugging/metadata/module_metadata.dart | 2 - dwds/lib/src/debugging/metadata/provider.dart | 150 +++++++----- dwds/lib/src/injected/client.js | 231 ++++++++++++++---- dwds/lib/src/loaders/ddc_library_bundle.dart | 6 - dwds/lib/src/loaders/strategy.dart | 10 +- .../src/services/chrome_proxy_service.dart | 14 +- dwds/test/fixtures/context.dart | 2 +- dwds/test/metadata_test.dart | 83 +++---- .../ddc_library_bundle_restarter.dart | 19 +- dwds/web/reloader/ddc_restarter.dart | 2 +- dwds/web/reloader/manager.dart | 9 +- dwds/web/reloader/require_restarter.dart | 2 +- dwds/web/reloader/restarter.dart | 9 +- frontend_server_common/lib/src/devfs.dart | 5 +- 14 files changed, 359 insertions(+), 185 deletions(-) diff --git a/dwds/lib/src/debugging/metadata/module_metadata.dart b/dwds/lib/src/debugging/metadata/module_metadata.dart index 02e1dec61..d66641af0 100644 --- a/dwds/lib/src/debugging/metadata/module_metadata.dart +++ b/dwds/lib/src/debugging/metadata/module_metadata.dart @@ -103,8 +103,6 @@ class ModuleMetadata { /// /// Used as a name of the js module created by the compiler and /// as key to store and load modules in the debugger and the browser - // TODO(srujzs): Remove once https://github.com/dart-lang/sdk/issues/59618 is - // resolved. final String name; /// Name of the function enclosing the module diff --git a/dwds/lib/src/debugging/metadata/provider.dart b/dwds/lib/src/debugging/metadata/provider.dart index 1c1b9e0ee..0125112e4 100644 --- a/dwds/lib/src/debugging/metadata/provider.dart +++ b/dwds/lib/src/debugging/metadata/provider.dart @@ -15,15 +15,14 @@ class MetadataProvider { final AssetReader _assetReader; final _logger = Logger('MetadataProvider'); final String entrypoint; - final List _libraries = []; + final Set _libraries = {}; final Map _scriptToModule = {}; final Map _moduleToSourceMap = {}; final Map _modulePathToModule = {}; final Map _moduleToModulePath = {}; + final Map> _moduleToLibraries = {}; final Map> _scripts = {}; final _metadataMemoizer = AsyncMemoizer(); - // Whether to use the `name` provided in the module metadata. - final bool _useModuleName; /// Implicitly imported libraries in any DDC component. /// @@ -66,11 +65,7 @@ class MetadataProvider { 'dart:ui', ]; - MetadataProvider( - this.entrypoint, - this._assetReader, { - required bool useModuleName, - }) : _useModuleName = useModuleName; + MetadataProvider(this.entrypoint, this._assetReader); /// A sound null safety mode for the whole app. /// @@ -81,17 +76,17 @@ class MetadataProvider { return true; } - /// A list of all libraries in the Dart application. + /// A set of all libraries in the Dart application. /// /// Example: /// - /// [ + /// { /// dart:web_gl, /// dart:math, /// org-dartlang-app:///web/main.dart - /// ] + /// } /// - Future> get libraries async { + Future> get libraries async { await _initialize(); return _libraries; } @@ -118,9 +113,6 @@ class MetadataProvider { /// org-dartlang-app:///web/main.dart : /// web/main /// } - /// - /// If [_useModuleName] is false, the values will be the module paths instead - /// of the name except for 'dart_sdk'. Future> get scriptToModule async { await _initialize(); return _scriptToModule; @@ -134,9 +126,6 @@ class MetadataProvider { /// org-dartlang-app:///web/main.dart : /// web/main.ddc.js.map /// } - /// - /// If [_useModuleName] is false, the keys will be the module paths instead of - /// the name. Future> get moduleToSourceMap async { await _initialize(); return _moduleToSourceMap; @@ -150,9 +139,6 @@ class MetadataProvider { /// web/main.ddc.js : /// web/main /// } - /// - /// If [_useModuleName] is false, the values will be the module paths instead - /// of the name, making this an identity map. Future> get modulePathToModule async { await _initialize(); return _modulePathToModule; @@ -166,9 +152,6 @@ class MetadataProvider { /// web/main /// web/main.ddc.js : /// } - /// - /// If [_useModuleName] is false, the keys will be the module paths instead of - /// the name, making this an identity map. Future> get moduleToModulePath async { await _initialize(); return _moduleToModulePath; @@ -182,67 +165,108 @@ class MetadataProvider { /// web/main, /// web/foo/bar /// ] - /// - /// If [_useModuleName] is false, this will be the set of module paths - /// instead. Future> get modules async { await _initialize(); return _moduleToModulePath.keys.toList(); } - Future _initialize() async { - await _metadataMemoizer.runOnce(() async { - // The merged metadata resides next to the entrypoint. - // Assume that .bootstrap.js has .ddc_merged_metadata - if (entrypoint.endsWith('.bootstrap.js')) { - _logger.info('Loading debug metadata...'); - final serverPath = entrypoint.replaceAll( - '.bootstrap.js', - '.ddc_merged_metadata', - ); - final merged = await _assetReader.metadataContents(serverPath); - if (merged != null) { - _addSdkMetadata(); - for (final contents in merged.split('\n')) { - try { - if (contents.isEmpty || - contents.startsWith('// intentionally empty:')) { - continue; - } - final moduleJson = json.decode(contents); - final metadata = ModuleMetadata.fromJson( - moduleJson as Map, - ); + Future?> _processMetadata(bool hotReload) async { + final modules = hotReload ? {} : null; + // The merged metadata resides next to the entrypoint. + // Assume that .bootstrap.js has .ddc_merged_metadata + if (entrypoint.endsWith('.bootstrap.js')) { + _logger.info('Loading debug metadata...'); + final serverPath = entrypoint.replaceAll( + '.bootstrap.js', + '.ddc_merged_metadata', + ); + final merged = await _assetReader.metadataContents(serverPath); + if (merged != null) { + // We can't hot reload the SDK yet, so no need to invalidate this data. + if (!hotReload) _addSdkMetadata(); + for (final contents in merged.split('\n')) { + try { + if (contents.isEmpty || + contents.startsWith('// intentionally empty:')) { + continue; + } + final moduleJson = json.decode(contents); + final metadata = ModuleMetadata.fromJson( + moduleJson as Map, + ); + final moduleName = metadata.name; + if (hotReload) { + modules?[moduleName] = metadata; + } else { _addMetadata(metadata); - final moduleName = - _useModuleName ? metadata.name : metadata.moduleUri; - _logger.fine('Loaded debug metadata for module: $moduleName'); - } catch (e) { - _logger.warning('Failed to read metadata: $e'); - rethrow; } + _logger.fine('Loaded debug metadata for module: $moduleName'); + } catch (e) { + _logger.warning('Failed to read metadata: $e'); + rethrow; } } } - }); + } + return modules; + } + + Future _initialize() async { + await _metadataMemoizer.runOnce(() => _processMetadata(false)); + } + + Future reinitializeAfterReload(Set reloadedModules) async { + final modules = (await _processMetadata(true))!; + final invalidatedLibraries = {}; + void invalidateLibrary(String libraryImportUri) { + invalidatedLibraries.add(libraryImportUri); + _libraries.remove(libraryImportUri); + _scriptToModule.remove(libraryImportUri); + _scripts[libraryImportUri]?.forEach(_scriptToModule.remove); + _scripts.remove(libraryImportUri); + } + + final deletedModules = {}; + final invalidatedModules = {}; + for (final module in _moduleToLibraries.keys) { + final deletedModule = !modules.containsKey(module); + final invalidatedModule = reloadedModules.contains(module); + assert(!(deletedModule && invalidatedModule)); + // If the module was either deleted or reloaded, invalidate all previous + // information both about the module and its libraries. + if (deletedModule || invalidatedModule) { + _modulePathToModule.remove(module); + _moduleToLibraries[module]?.forEach(invalidateLibrary); + _moduleToModulePath.remove(module); + _moduleToSourceMap.remove(module); + } + if (deletedModule) deletedModules.add(module); + } + for (final module in reloadedModules) { + _addMetadata(modules[module]!); + } + // The libraries that were removed from the program or those that we + // invalidated but were never added again. + final deletedLibraries = invalidatedLibraries.where( + (library) => !_libraries.contains(library), + ); } void _addMetadata(ModuleMetadata metadata) { final modulePath = stripLeadingSlashes(metadata.moduleUri); final sourceMapPath = stripLeadingSlashes(metadata.sourceMapUri); - // DDC library bundle module format does not provide names for library - // bundles, and therefore we use the URI instead to represent a library - // bundle. - final moduleName = _useModuleName ? metadata.name : modulePath; + final moduleName = metadata.name; _moduleToSourceMap[moduleName] = sourceMapPath; _modulePathToModule[modulePath] = moduleName; _moduleToModulePath[moduleName] = modulePath; + final moduleLibraries = {}; for (final library in metadata.libraries.values) { if (library.importUri.startsWith('file:/')) { throw AbsoluteImportUriException(library.importUri); } + moduleLibraries.add(library.importUri); _libraries.add(library.importUri); _scripts[library.importUri] = []; @@ -254,6 +278,7 @@ class MetadataProvider { _scriptToModule[partPath] = moduleName; } } + _moduleToLibraries[moduleName] = moduleLibraries; } void _addSdkMetadata() { @@ -264,7 +289,8 @@ class MetadataProvider { _scripts[lib] = []; // TODO(srujzs): It feels weird that we add this mapping to only this map // and not any of the other module maps. We should maybe handle this - // differently. + // differently. This will become relevant if we ever support hot reload + // for the Dart SDK. _scriptToModule[lib] = moduleName; } } diff --git a/dwds/lib/src/injected/client.js b/dwds/lib/src/injected/client.js index 488511c5c..e85ffd662 100644 --- a/dwds/lib/src/injected/client.js +++ b/dwds/lib/src/injected/client.js @@ -21651,6 +21651,7 @@ }, deserialize$3$specifiedType(serializers, serialized, specifiedType) { var t1, t2, value, $$v, _$result, + _s11_ = "BuildResult", result = new A.BuildResultBuilder(), iterator = J.get$iterator$ax(type$.Iterable_nullable_Object._as(serialized)); for (t1 = type$.BuildStatus; iterator.moveNext$0();) { @@ -21674,7 +21675,13 @@ } } _$result = result._build_result$_$v; - return result._build_result$_$v = _$result == null ? new A._$BuildResult(A.BuiltValueNullFieldError_checkNotNull(result.get$_build_result$_$this()._status, "BuildResult", "status", t1)) : _$result; + if (_$result == null) { + t2 = A.BuiltValueNullFieldError_checkNotNull(result.get$_build_result$_$this()._status, _s11_, "status", t1); + _$result = new A._$BuildResult(t2); + A.BuiltValueNullFieldError_checkNotNull(t2, _s11_, "status", t1); + } + A.ArgumentError_checkNotNull(_$result, "other", type$.BuildResult); + return result._build_result$_$v = _$result; }, deserialize$2(serializers, serialized) { return this.deserialize$3$specifiedType(serializers, serialized, B.FullType_null_List_empty_false); @@ -21805,13 +21812,22 @@ return _this; }, _connect_request$_build$0() { - var t1, _this = this, + var t1, t2, t3, t4, _this = this, _s14_ = "ConnectRequest", + _s10_ = "instanceId", + _s14_0 = "entrypointPath", _$result = _this._connect_request$_$v; if (_$result == null) { t1 = type$.String; - _$result = new A._$ConnectRequest(A.BuiltValueNullFieldError_checkNotNull(_this.get$_connect_request$_$this()._connect_request$_appId, _s14_, "appId", t1), A.BuiltValueNullFieldError_checkNotNull(_this.get$_connect_request$_$this()._instanceId, _s14_, "instanceId", t1), A.BuiltValueNullFieldError_checkNotNull(_this.get$_connect_request$_$this()._entrypointPath, _s14_, "entrypointPath", t1)); - } + t2 = A.BuiltValueNullFieldError_checkNotNull(_this.get$_connect_request$_$this()._connect_request$_appId, _s14_, "appId", t1); + t3 = A.BuiltValueNullFieldError_checkNotNull(_this.get$_connect_request$_$this()._instanceId, _s14_, _s10_, t1); + t4 = A.BuiltValueNullFieldError_checkNotNull(_this.get$_connect_request$_$this()._entrypointPath, _s14_, _s14_0, t1); + _$result = new A._$ConnectRequest(t2, t3, t4); + A.BuiltValueNullFieldError_checkNotNull(t2, _s14_, "appId", t1); + A.BuiltValueNullFieldError_checkNotNull(t3, _s14_, _s10_, t1); + A.BuiltValueNullFieldError_checkNotNull(t4, _s14_, _s14_0, t1); + } + A.ArgumentError_checkNotNull(_$result, "other", type$.ConnectRequest); return _this._connect_request$_$v = _$result; } }; @@ -21976,13 +21992,23 @@ return _this; }, _debug_event$_build$0() { - var t1, _this = this, + var t1, t2, t3, t4, t5, _this = this, _s10_ = "DebugEvent", + _s9_ = "eventData", + _s9_0 = "timestamp", _$result = _this._debug_event$_$v; if (_$result == null) { t1 = type$.String; - _$result = new A._$DebugEvent(A.BuiltValueNullFieldError_checkNotNull(_this.get$_debug_event$_$this()._debug_event$_kind, _s10_, "kind", t1), A.BuiltValueNullFieldError_checkNotNull(_this.get$_debug_event$_$this()._eventData, _s10_, "eventData", t1), A.BuiltValueNullFieldError_checkNotNull(_this.get$_debug_event$_$this()._timestamp, _s10_, "timestamp", type$.int)); - } + t2 = A.BuiltValueNullFieldError_checkNotNull(_this.get$_debug_event$_$this()._debug_event$_kind, _s10_, "kind", t1); + t3 = A.BuiltValueNullFieldError_checkNotNull(_this.get$_debug_event$_$this()._eventData, _s10_, _s9_, t1); + t4 = type$.int; + t5 = A.BuiltValueNullFieldError_checkNotNull(_this.get$_debug_event$_$this()._timestamp, _s10_, _s9_0, t4); + _$result = new A._$DebugEvent(t2, t3, t5); + A.BuiltValueNullFieldError_checkNotNull(t2, _s10_, "kind", t1); + A.BuiltValueNullFieldError_checkNotNull(t3, _s10_, _s9_, t1); + A.BuiltValueNullFieldError_checkNotNull(t5, _s10_, _s9_0, t4); + } + A.ArgumentError_checkNotNull(_$result, "other", type$.DebugEvent); return _this._debug_event$_$v = _$result; } }; @@ -22027,10 +22053,17 @@ return _this; }, _debug_event$_build$0() { - var _$failedField, e, _$result0, exception, t1, _this = this, _$result = null; + var _$failedField, e, _$result0, t1, exception, t2, _this = this, + _s18_ = "BatchedDebugEvents", + _$result = null; try { _$result0 = _this._debug_event$_$v; - _$result = _$result0 == null ? new A._$BatchedDebugEvents(_this.get$events().build$0()) : _$result0; + if (_$result0 == null) { + t1 = _this.get$events().build$0(); + _$result0 = new A._$BatchedDebugEvents(t1); + A.BuiltValueNullFieldError_checkNotNull(t1, _s18_, "events", type$.BuiltList_DebugEvent); + } + _$result = _$result0; } catch (exception) { _$failedField = A._Cell$named("_$failedField"); try { @@ -22038,12 +22071,15 @@ _this.get$events().build$0(); } catch (exception) { e = A.unwrapException(exception); - t1 = A.BuiltValueNestedFieldError$("BatchedDebugEvents", _$failedField.readLocal$0(), J.toString$0$(e)); + t1 = A.BuiltValueNestedFieldError$(_s18_, _$failedField.readLocal$0(), J.toString$0$(e)); throw A.wrapException(t1); } throw exception; } - _this._debug_event$_$v = type$.BatchedDebugEvents._as(_$result); + t1 = type$.BatchedDebugEvents; + t2 = t1._as(_$result); + A.ArgumentError_checkNotNull(t2, "other", t1); + _this._debug_event$_$v = t2; return _$result; }, set$_events(_events) { @@ -22263,7 +22299,10 @@ _build$0() { var _this = this, _$result = _this._$v; - return _this._$v = _$result == null ? new A._$DebugInfo(_this.get$_$this()._appEntrypointPath, _this.get$_$this()._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) : _$result; + if (_$result == null) + _$result = new A._$DebugInfo(_this.get$_$this()._appEntrypointPath, _this.get$_$this()._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._$v = _$result; } }; A.DevToolsRequest.prototype = {}; @@ -22369,7 +22408,8 @@ return this.serialize$3$specifiedType(serializers, object, B.FullType_null_List_empty_false); }, deserialize$3$specifiedType(serializers, serialized, specifiedType) { - var t1, value, _$result, + var t1, value, _$result, t2, t3, + _s15_ = "promptExtension", _s16_ = "DevToolsResponse", result = new A.DevToolsResponseBuilder(), iterator = J.get$iterator$ax(type$.Iterable_nullable_Object._as(serialized)); @@ -22401,8 +22441,13 @@ _$result = result._devtools_request$_$v; if (_$result == null) { t1 = type$.bool; - _$result = new A._$DevToolsResponse(A.BuiltValueNullFieldError_checkNotNull(result.get$_devtools_request$_$this()._success, _s16_, "success", t1), A.BuiltValueNullFieldError_checkNotNull(result.get$_devtools_request$_$this()._promptExtension, _s16_, "promptExtension", t1), result.get$_devtools_request$_$this()._error); + t2 = A.BuiltValueNullFieldError_checkNotNull(result.get$_devtools_request$_$this()._success, _s16_, "success", t1); + t3 = A.BuiltValueNullFieldError_checkNotNull(result.get$_devtools_request$_$this()._promptExtension, _s16_, _s15_, t1); + _$result = new A._$DevToolsResponse(t2, t3, result.get$_devtools_request$_$this()._error); + A.BuiltValueNullFieldError_checkNotNull(t2, _s16_, "success", t1); + A.BuiltValueNullFieldError_checkNotNull(t3, _s16_, _s15_, t1); } + A.ArgumentError_checkNotNull(_$result, "other", type$.DevToolsResponse); return result._devtools_request$_$v = _$result; }, deserialize$2(serializers, serialized) { @@ -22459,13 +22504,19 @@ return _this; }, _devtools_request$_build$0() { - var t1, _this = this, + var t1, t2, t3, _this = this, _s15_ = "DevToolsRequest", + _s10_ = "instanceId", _$result = _this._devtools_request$_$v; if (_$result == null) { t1 = type$.String; - _$result = new A._$DevToolsRequest(A.BuiltValueNullFieldError_checkNotNull(_this.get$_devtools_request$_$this()._devtools_request$_appId, _s15_, "appId", t1), A.BuiltValueNullFieldError_checkNotNull(_this.get$_devtools_request$_$this()._devtools_request$_instanceId, _s15_, "instanceId", t1), _this.get$_devtools_request$_$this()._contextId, _this.get$_devtools_request$_$this()._devtools_request$_tabUrl, _this.get$_devtools_request$_$this()._uriOnly, _this.get$_devtools_request$_$this()._devtools_request$_client); + 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()._devtools_request$_tabUrl, _this.get$_devtools_request$_$this()._uriOnly, _this.get$_devtools_request$_$this()._devtools_request$_client); + A.BuiltValueNullFieldError_checkNotNull(t2, _s15_, "appId", t1); + A.BuiltValueNullFieldError_checkNotNull(t3, _s15_, _s10_, t1); } + A.ArgumentError_checkNotNull(_$result, "other", type$.DevToolsRequest); return _this._devtools_request$_$v = _$result; } }; @@ -22516,7 +22567,8 @@ return this.serialize$3$specifiedType(serializers, object, B.FullType_null_List_empty_false); }, deserialize$3$specifiedType(serializers, serialized, specifiedType) { - var t1, value, $$v, _$result, + var t1, value, $$v, _$result, t2, t3, + _s10_ = "stackTrace", _s13_ = "ErrorResponse", result = new A.ErrorResponseBuilder(), iterator = J.get$iterator$ax(type$.Iterable_nullable_Object._as(serialized)); @@ -22556,8 +22608,13 @@ _$result = result._error_response$_$v; if (_$result == null) { t1 = type$.String; - _$result = new A._$ErrorResponse(A.BuiltValueNullFieldError_checkNotNull(result.get$_error_response$_$this()._error_response$_error, _s13_, "error", t1), A.BuiltValueNullFieldError_checkNotNull(result.get$_error_response$_$this()._error_response$_stackTrace, _s13_, "stackTrace", t1)); + t2 = A.BuiltValueNullFieldError_checkNotNull(result.get$_error_response$_$this()._error_response$_error, _s13_, "error", t1); + t3 = A.BuiltValueNullFieldError_checkNotNull(result.get$_error_response$_$this()._error_response$_stackTrace, _s13_, _s10_, t1); + _$result = new A._$ErrorResponse(t2, t3); + A.BuiltValueNullFieldError_checkNotNull(t2, _s13_, "error", t1); + A.BuiltValueNullFieldError_checkNotNull(t3, _s13_, _s10_, t1); } + A.ArgumentError_checkNotNull(_$result, "other", type$.ErrorResponse); return result._error_response$_$v = _$result; }, deserialize$2(serializers, serialized) { @@ -22623,7 +22680,7 @@ return this.serialize$3$specifiedType(serializers, object, B.FullType_null_List_empty_false); }, deserialize$3$specifiedType(serializers, serialized, specifiedType) { - var t1, value, _$result, + var t1, value, _$result, t2, t3, t4, _s16_ = "ExtensionRequest", result = new A.ExtensionRequestBuilder(), iterator = J.get$iterator$ax(type$.Iterable_nullable_Object._as(serialized)); @@ -22653,7 +22710,17 @@ } } _$result = result._extension_request$_$v; - return result._extension_request$_$v = _$result == null ? new A._$ExtensionRequest(A.BuiltValueNullFieldError_checkNotNull(result.get$_extension_request$_$this()._id, _s16_, "id", type$.int), A.BuiltValueNullFieldError_checkNotNull(result.get$_extension_request$_$this()._command, _s16_, "command", type$.String), result.get$_extension_request$_$this()._commandParams) : _$result; + if (_$result == null) { + t1 = type$.int; + t2 = A.BuiltValueNullFieldError_checkNotNull(result.get$_extension_request$_$this()._id, _s16_, "id", t1); + t3 = type$.String; + t4 = A.BuiltValueNullFieldError_checkNotNull(result.get$_extension_request$_$this()._command, _s16_, "command", t3); + _$result = new A._$ExtensionRequest(t2, t4, result.get$_extension_request$_$this()._commandParams); + A.BuiltValueNullFieldError_checkNotNull(t2, _s16_, "id", t1); + A.BuiltValueNullFieldError_checkNotNull(t4, _s16_, "command", t3); + } + A.ArgumentError_checkNotNull(_$result, "other", type$.ExtensionRequest); + return result._extension_request$_$v = _$result; }, deserialize$2(serializers, serialized) { return this.deserialize$3$specifiedType(serializers, serialized, B.FullType_null_List_empty_false); @@ -22683,7 +22750,7 @@ return this.serialize$3$specifiedType(serializers, object, B.FullType_null_List_empty_false); }, deserialize$3$specifiedType(serializers, serialized, specifiedType) { - var t1, value, _$result, + var t1, value, _$result, t2, t3, t4, t5, t6, _s17_ = "ExtensionResponse", result = new A.ExtensionResponseBuilder(), iterator = J.get$iterator$ax(type$.Iterable_nullable_Object._as(serialized)); @@ -22719,7 +22786,20 @@ } } _$result = result._extension_request$_$v; - return result._extension_request$_$v = _$result == null ? new A._$ExtensionResponse(A.BuiltValueNullFieldError_checkNotNull(result.get$_extension_request$_$this()._id, _s17_, "id", type$.int), A.BuiltValueNullFieldError_checkNotNull(result.get$_extension_request$_$this()._extension_request$_success, _s17_, "success", type$.bool), A.BuiltValueNullFieldError_checkNotNull(result.get$_extension_request$_$this()._extension_request$_result, _s17_, "result", type$.String), result.get$_extension_request$_$this()._extension_request$_error) : _$result; + if (_$result == null) { + t1 = type$.int; + t2 = A.BuiltValueNullFieldError_checkNotNull(result.get$_extension_request$_$this()._id, _s17_, "id", t1); + t3 = type$.bool; + t4 = A.BuiltValueNullFieldError_checkNotNull(result.get$_extension_request$_$this()._extension_request$_success, _s17_, "success", t3); + t5 = type$.String; + t6 = A.BuiltValueNullFieldError_checkNotNull(result.get$_extension_request$_$this()._extension_request$_result, _s17_, "result", t5); + _$result = new A._$ExtensionResponse(t2, t4, t6, result.get$_extension_request$_$this()._extension_request$_error); + A.BuiltValueNullFieldError_checkNotNull(t2, _s17_, "id", t1); + A.BuiltValueNullFieldError_checkNotNull(t4, _s17_, "success", t3); + A.BuiltValueNullFieldError_checkNotNull(t6, _s17_, "result", t5); + } + A.ArgumentError_checkNotNull(_$result, "other", type$.ExtensionResponse); + return result._extension_request$_$v = _$result; }, deserialize$2(serializers, serialized) { return this.deserialize$3$specifiedType(serializers, serialized, B.FullType_null_List_empty_false); @@ -22742,7 +22822,7 @@ return this.serialize$3$specifiedType(serializers, object, B.FullType_null_List_empty_false); }, deserialize$3$specifiedType(serializers, serialized, specifiedType) { - var t1, value, $$v, _$result, + var t1, value, $$v, _$result, t2, t3, _s14_ = "ExtensionEvent", result = new A.ExtensionEventBuilder(), iterator = J.get$iterator$ax(type$.Iterable_nullable_Object._as(serialized)); @@ -22782,8 +22862,13 @@ _$result = result._extension_request$_$v; if (_$result == null) { t1 = type$.String; - _$result = new A._$ExtensionEvent(A.BuiltValueNullFieldError_checkNotNull(result.get$_extension_request$_$this()._params, _s14_, "params", t1), A.BuiltValueNullFieldError_checkNotNull(result.get$_extension_request$_$this()._extension_request$_method, _s14_, "method", t1)); + t2 = A.BuiltValueNullFieldError_checkNotNull(result.get$_extension_request$_$this()._params, _s14_, "params", t1); + t3 = A.BuiltValueNullFieldError_checkNotNull(result.get$_extension_request$_$this()._extension_request$_method, _s14_, "method", t1); + _$result = new A._$ExtensionEvent(t2, t3); + A.BuiltValueNullFieldError_checkNotNull(t2, _s14_, "params", t1); + A.BuiltValueNullFieldError_checkNotNull(t3, _s14_, "method", t1); } + A.ArgumentError_checkNotNull(_$result, "other", type$.ExtensionEvent); return result._extension_request$_$v = _$result; }, deserialize$2(serializers, serialized) { @@ -23019,10 +23104,17 @@ return t1; }, _extension_request$_build$0() { - var _$failedField, e, _$result0, exception, t1, _this = this, _$result = null; + var _$failedField, e, _$result0, t1, exception, t2, _this = this, + _s13_ = "BatchedEvents", + _$result = null; try { _$result0 = _this._extension_request$_$v; - _$result = _$result0 == null ? new A._$BatchedEvents(_this.get$events().build$0()) : _$result0; + if (_$result0 == null) { + t1 = _this.get$events().build$0(); + _$result0 = new A._$BatchedEvents(t1); + A.BuiltValueNullFieldError_checkNotNull(t1, _s13_, "events", type$.BuiltList_ExtensionEvent); + } + _$result = _$result0; } catch (exception) { _$failedField = A._Cell$named("_$failedField"); try { @@ -23030,12 +23122,15 @@ _this.get$events().build$0(); } catch (exception) { e = A.unwrapException(exception); - t1 = A.BuiltValueNestedFieldError$("BatchedEvents", _$failedField.readLocal$0(), J.toString$0$(e)); + t1 = A.BuiltValueNestedFieldError$(_s13_, _$failedField.readLocal$0(), J.toString$0$(e)); throw A.wrapException(t1); } throw exception; } - _this._extension_request$_$v = type$.BatchedEvents._as(_$result); + t1 = type$.BatchedEvents; + t2 = t1._as(_$result); + A.ArgumentError_checkNotNull(t2, "other", t1); + _this._extension_request$_$v = t2; return _$result; }, set$_extension_request$_events(_events) { @@ -23051,7 +23146,8 @@ return this.serialize$3$specifiedType(serializers, object, B.FullType_null_List_empty_false); }, deserialize$3$specifiedType(serializers, serialized, specifiedType) { - var t1, value, $$v, _$result, + var t1, value, $$v, _$result, t2, + _s16_ = "HotReloadRequest", result = new A.HotReloadRequestBuilder(), iterator = J.get$iterator$ax(type$.Iterable_nullable_Object._as(serialized)); for (; iterator.moveNext$0();) { @@ -23075,7 +23171,14 @@ } } _$result = result._hot_reload_request$_$v; - return result._hot_reload_request$_$v = _$result == null ? new A._$HotReloadRequest(A.BuiltValueNullFieldError_checkNotNull(result.get$_hot_reload_request$_$this()._hot_reload_request$_id, "HotReloadRequest", "id", type$.String)) : _$result; + if (_$result == null) { + t1 = type$.String; + t2 = A.BuiltValueNullFieldError_checkNotNull(result.get$_hot_reload_request$_$this()._hot_reload_request$_id, _s16_, "id", t1); + _$result = new A._$HotReloadRequest(t2); + A.BuiltValueNullFieldError_checkNotNull(t2, _s16_, "id", t1); + } + A.ArgumentError_checkNotNull(_$result, "other", type$.HotReloadRequest); + return result._hot_reload_request$_$v = _$result; }, deserialize$2(serializers, serialized) { return this.deserialize$3$specifiedType(serializers, serialized, B.FullType_null_List_empty_false); @@ -23223,10 +23326,20 @@ return _this; }, _hot_reload_response$_build$0() { - var _this = this, + var t1, t2, t3, t4, _this = this, _s17_ = "HotReloadResponse", _$result = _this._hot_reload_response$_$v; - return _this._hot_reload_response$_$v = _$result == null ? new A._$HotReloadResponse(A.BuiltValueNullFieldError_checkNotNull(_this.get$_hot_reload_response$_$this()._hot_reload_response$_id, _s17_, "id", type$.String), A.BuiltValueNullFieldError_checkNotNull(_this.get$_hot_reload_response$_$this()._hot_reload_response$_success, _s17_, "success", type$.bool), _this.get$_hot_reload_response$_$this()._errorMessage) : _$result; + if (_$result == null) { + t1 = type$.String; + t2 = A.BuiltValueNullFieldError_checkNotNull(_this.get$_hot_reload_response$_$this()._hot_reload_response$_id, _s17_, "id", t1); + t3 = type$.bool; + t4 = A.BuiltValueNullFieldError_checkNotNull(_this.get$_hot_reload_response$_$this()._hot_reload_response$_success, _s17_, "success", t3); + _$result = new A._$HotReloadResponse(t2, t4, _this.get$_hot_reload_response$_$this()._errorMessage); + A.BuiltValueNullFieldError_checkNotNull(t2, _s17_, "id", t1); + A.BuiltValueNullFieldError_checkNotNull(t4, _s17_, "success", t3); + } + A.ArgumentError_checkNotNull(_$result, "other", type$.HotReloadResponse); + return _this._hot_reload_response$_$v = _$result; } }; A.IsolateExit.prototype = {}; @@ -23297,7 +23410,10 @@ A.IsolateExitBuilder.prototype = { _isolate_events$_build$0() { var _$result = this._isolate_events$_$v; - return this._isolate_events$_$v = _$result == null ? new A._$IsolateExit() : _$result; + if (_$result == null) + _$result = new A._$IsolateExit(); + A.ArgumentError_checkNotNull(_$result, "other", type$.IsolateExit); + return this._isolate_events$_$v = _$result; } }; A._$IsolateStart.prototype = { @@ -23318,7 +23434,10 @@ A.IsolateStartBuilder.prototype = { _isolate_events$_build$0() { var _$result = this._isolate_events$_$v; - return this._isolate_events$_$v = _$result == null ? new A._$IsolateStart() : _$result; + if (_$result == null) + _$result = new A._$IsolateStart(); + A.ArgumentError_checkNotNull(_$result, "other", type$.IsolateStart); + return this._isolate_events$_$v = _$result; } }; A.RegisterEvent.prototype = {}; @@ -23412,10 +23531,22 @@ return _this; }, _register_event$_build$0() { - var _this = this, + var t1, t2, t3, t4, _this = this, _s13_ = "RegisterEvent", + _s9_ = "eventData", + _s9_0 = "timestamp", _$result = _this._register_event$_$v; - return _this._register_event$_$v = _$result == null ? new A._$RegisterEvent(A.BuiltValueNullFieldError_checkNotNull(_this.get$_register_event$_$this()._register_event$_eventData, _s13_, "eventData", type$.String), A.BuiltValueNullFieldError_checkNotNull(_this.get$_register_event$_$this()._register_event$_timestamp, _s13_, "timestamp", type$.int)) : _$result; + if (_$result == null) { + t1 = type$.String; + t2 = A.BuiltValueNullFieldError_checkNotNull(_this.get$_register_event$_$this()._register_event$_eventData, _s13_, _s9_, t1); + t3 = type$.int; + t4 = A.BuiltValueNullFieldError_checkNotNull(_this.get$_register_event$_$this()._register_event$_timestamp, _s13_, _s9_0, t3); + _$result = new A._$RegisterEvent(t2, t4); + A.BuiltValueNullFieldError_checkNotNull(t2, _s13_, _s9_, t1); + A.BuiltValueNullFieldError_checkNotNull(t4, _s13_, _s9_0, t3); + } + A.ArgumentError_checkNotNull(_$result, "other", type$.RegisterEvent); + return _this._register_event$_$v = _$result; } }; A.RunRequest.prototype = {}; @@ -23428,8 +23559,11 @@ return this.serialize$3$specifiedType(serializers, object, B.FullType_null_List_empty_false); }, deserialize$3$specifiedType(serializers, serialized, specifiedType) { + var _$result; type$.Iterable_nullable_Object._as(serialized); - return new A._$RunRequest(); + _$result = new A._$RunRequest(); + A.ArgumentError_checkNotNull(_$result, "other", type$.RunRequest); + return _$result; }, deserialize$2(serializers, serialized) { return this.deserialize$3$specifiedType(serializers, serialized, B.FullType_null_List_empty_false); @@ -26745,7 +26879,7 @@ }; A.main__closure.prototype = { call$0() { - return A.FutureOfJSAnyToJSPromise_get_toJS(this.manager._restarter.hotReloadStart$1(A.hotReloadSourcesPath()), type$.JSArray_nullable_Object); + return A.FutureOfJSAnyToJSPromise_get_toJS(this.manager._restarter.hotReloadStart$1(A.hotReloadSourcesPath()), type$.JSObject); }, $signature: 10 }; @@ -27162,8 +27296,8 @@ }, hotReloadStart$1(hotReloadSourcesPath) { var $async$goto = 0, - $async$completer = A._makeAsyncAwaitCompleter(type$.JSArray_nullable_Object), - $async$returnValue, $async$self = this, t4, srcLibraries, filesToLoad, librariesToReload, t5, t6, srcLibraryCast, libraries, t7, t1, t2, t3, xhr, $async$temp1, $async$temp2, $async$temp3; + $async$completer = A._makeAsyncAwaitCompleter(type$.JSObject), + $async$returnValue, $async$self = this, t4, srcModuleLibraries, filesToLoad, _this, librariesToReload, t5, t6, srcModuleLibraryCast, module, libraries, t7, t1, t2, t3, xhr, $async$temp1, $async$temp2, $async$temp3; var $async$hotReloadStart$1 = A._wrapJsFunctionForAsync(function($async$errorCode, $async$result) { if ($async$errorCode === 1) return A._asyncRethrow($async$result, $async$completer); @@ -27187,14 +27321,17 @@ return A._asyncAwait(t1, $async$hotReloadStart$1); case 3: // returning from await. - srcLibraries = $async$temp1.cast$1$0$ax($async$temp2._as($async$temp3.decode$1($async$result)), type$.Map_dynamic_dynamic); + srcModuleLibraries = $async$temp1.cast$1$0$ax($async$temp2._as($async$temp3.decode$1($async$result)), type$.Map_dynamic_dynamic); t1 = type$.JSArray_nullable_Object; filesToLoad = t1._as(new t2.Array()); + _this = {}; librariesToReload = t1._as(new t2.Array()); - for (t1 = srcLibraries.get$iterator(srcLibraries), t5 = type$.String, t6 = type$.Object; t1.moveNext$0();) { - srcLibraryCast = t1.get$current().cast$2$0(0, t5, t6); - filesToLoad.push(A._asString(srcLibraryCast.$index(0, "src"))); - libraries = J.cast$1$0$ax(t4._as(srcLibraryCast.$index(0, "libraries")), t5); + for (t1 = srcModuleLibraries.get$iterator(srcModuleLibraries), t5 = type$.String, t6 = type$.Object; t1.moveNext$0();) { + srcModuleLibraryCast = t1.get$current().cast$2$0(0, t5, t6); + filesToLoad.push(A._asString(srcModuleLibraryCast.$index(0, "src"))); + module = A._asString(srcModuleLibraryCast.$index(0, "module")); + libraries = J.cast$1$0$ax(t4._as(srcModuleLibraryCast.$index(0, "libraries")), t5); + _this[module] = A.jsify(libraries); for (t7 = libraries.get$iterator(libraries); t7.moveNext$0();) librariesToReload.push(t7.get$current()); } @@ -27203,7 +27340,7 @@ return A._asyncAwait(A.promiseToFuture(t3._as(t3._as(t2.dartDevEmbedder).hotReload(filesToLoad, librariesToReload)), type$.nullable_Object), $async$hotReloadStart$1); case 4: // returning from await. - $async$returnValue = librariesToReload; + $async$returnValue = _this; // goto return $async$goto = 1; break; @@ -28101,6 +28238,8 @@ BuildResult: findType("BuildResult"), BuildStatus: findType("BuildStatus"), BuiltListMultimap_dynamic_dynamic: findType("BuiltListMultimap<@,@>"), + BuiltList_DebugEvent: findType("BuiltList"), + BuiltList_ExtensionEvent: findType("BuiltList"), BuiltList_dynamic: findType("BuiltList<@>"), BuiltList_nullable_Object: findType("BuiltList"), BuiltMap_dynamic_dynamic: findType("BuiltMap<@,@>"), diff --git a/dwds/lib/src/loaders/ddc_library_bundle.dart b/dwds/lib/src/loaders/ddc_library_bundle.dart index 220b73828..3fb592dde 100644 --- a/dwds/lib/src/loaders/ddc_library_bundle.dart +++ b/dwds/lib/src/loaders/ddc_library_bundle.dart @@ -223,10 +223,4 @@ window.\$dartLoader.loader.nextAttempt(); @override String? g3RelativePath(String absolutePath) => _g3RelativePath(absolutePath); - - @override - MetadataProvider createProvider(String entrypoint, AssetReader reader) => - // DDC library bundle format does not provide module names in the module - // metadata. - MetadataProvider(entrypoint, reader, useModuleName: false); } diff --git a/dwds/lib/src/loaders/strategy.dart b/dwds/lib/src/loaders/strategy.dart index f224f8e2d..c66fd3097 100644 --- a/dwds/lib/src/loaders/strategy.dart +++ b/dwds/lib/src/loaders/strategy.dart @@ -171,7 +171,7 @@ abstract class LoadStrategy { /// Creates and returns a [MetadataProvider] with the given [entrypoint] and /// [reader]. MetadataProvider createProvider(String entrypoint, AssetReader reader) => - MetadataProvider(entrypoint, reader, useModuleName: true); + MetadataProvider(entrypoint, reader); /// Initializes a [MetadataProvider] for the application located at the /// provided [entrypoint]. @@ -182,6 +182,14 @@ abstract class LoadStrategy { // this method: return Future.value(); } + + Future reinitializeEntrypointAfterReload( + String entrypoint, + Set modules, + ) { + final provider = _providers[entrypoint]!; + return provider.reinitializeAfterReload(modules); + } } enum ReloadConfiguration { none, hotReload, hotRestart, liveReload } diff --git a/dwds/lib/src/services/chrome_proxy_service.dart b/dwds/lib/src/services/chrome_proxy_service.dart index 836a1de21..6be61af11 100644 --- a/dwds/lib/src/services/chrome_proxy_service.dart +++ b/dwds/lib/src/services/chrome_proxy_service.dart @@ -241,9 +241,13 @@ class ChromeProxyService implements VmServiceInterface { // separation between "existing" information and "new" information, making // this difficult. // https://github.com/dart-lang/webdev/issues/2628 - Future _reinitializeForHotReload() async { + Future _reinitializeForHotReload( + Map reloadedModules, + ) async { final entrypoint = inspector.appConnection.request.entrypointPath; - await globalToolConfiguration.loadStrategy.trackEntrypoint(entrypoint); + final modules = reloadedModules.keys.toSet(); + await globalToolConfiguration.loadStrategy + .reinitializeEntrypointAfterReload(entrypoint, modules); _initializeEntrypoint(entrypoint); await inspector.initialize(); } @@ -1206,11 +1210,13 @@ class ChromeProxyService implements VmServiceInterface { // Initiate a hot reload. _logger.info('Issuing \$dartHotReloadStartDwds request'); - await inspector.jsEvaluate( + final reloadedModulesMap = await inspector.jsEvaluate( '\$dartHotReloadStartDwds();', awaitPromise: true, returnByValue: true, ); + final reloadedModules = + (reloadedModulesMap.value as Map).cast(); if (!pauseIsolatesOnStart) { // Finish hot reload immediately. @@ -1245,7 +1251,7 @@ class ChromeProxyService implements VmServiceInterface { await pause(isolateId); await pausedEvent; - await _reinitializeForHotReload(); + await _reinitializeForHotReload(reloadedModules); // This lets the client know that we're ready for breakpoint management // and a resume. diff --git a/dwds/test/fixtures/context.dart b/dwds/test/fixtures/context.dart index a6a1ab373..f372658f6 100644 --- a/dwds/test/fixtures/context.dart +++ b/dwds/test/fixtures/context.dart @@ -412,7 +412,7 @@ class TestContext { 'remote-debugging-port=$debugPort', if (enableDebugExtension) '--load-extension=debug_extension/prod_build', - if (headless) '--headless', + // if (headless) '--headless', ], }, }); diff --git a/dwds/test/metadata_test.dart b/dwds/test/metadata_test.dart index 54019467a..375c39d67 100644 --- a/dwds/test/metadata_test.dart +++ b/dwds/test/metadata_test.dart @@ -36,61 +36,50 @@ void main() { ); setGlobalsForTesting(toolConfiguration: toolConfiguration); test('can parse metadata with empty sources', () async { - for (final useModuleName in [true, false]) { - final provider = MetadataProvider( - 'foo.bootstrap.js', - FakeAssetReader(metadata: _emptySourceMetadata), - useModuleName: useModuleName, - ); - expect( - await provider.libraries, - contains('org-dartlang-app:///web/main.dart'), - ); - } + final provider = MetadataProvider( + 'foo.bootstrap.js', + FakeAssetReader(metadata: _emptySourceMetadata), + ); + expect( + await provider.libraries, + contains('org-dartlang-app:///web/main.dart'), + ); }); test('throws on metadata with absolute import uris', () async { - for (final useModuleName in [true, false]) { - final provider = MetadataProvider( - 'foo.bootstrap.js', - FakeAssetReader(metadata: _fileUriMetadata), - useModuleName: useModuleName, - ); - await expectLater( - provider.libraries, - throwsA(const TypeMatcher()), - ); - } + final provider = MetadataProvider( + 'foo.bootstrap.js', + FakeAssetReader(metadata: _fileUriMetadata), + ); + await expectLater( + provider.libraries, + throwsA(const TypeMatcher()), + ); }); test( 'module name exists if useModuleName and otherwise use module uri', () async { - for (final useModuleName in [true, false]) { - final provider = MetadataProvider( - 'foo.bootstrap.js', - FakeAssetReader(metadata: _emptySourceMetadata), - useModuleName: useModuleName, - ); - final modulePath = 'foo/web/main.ddc.js'; - final moduleName = 'web/main'; - final module = useModuleName ? moduleName : modulePath; - expect( - await provider.scriptToModule, - predicate>( - (scriptToModule) => - !scriptToModule.values.any( - (value) => value == (useModuleName ? modulePath : moduleName), - ), - ), - ); - expect(await provider.moduleToSourceMap, { - module: 'foo/web/main.ddc.js.map', - }); - expect(await provider.modulePathToModule, {modulePath: module}); - expect(await provider.moduleToModulePath, {module: modulePath}); - expect(await provider.modules, {module}); - } + final provider = MetadataProvider( + 'foo.bootstrap.js', + FakeAssetReader(metadata: _emptySourceMetadata), + ); + final modulePath = 'foo/web/main.ddc.js'; + final moduleName = 'web/main'; + final module = moduleName; + expect( + await provider.scriptToModule, + predicate>( + (scriptToModule) => + !scriptToModule.values.any((value) => value == modulePath), + ), + ); + expect(await provider.moduleToSourceMap, { + module: 'foo/web/main.ddc.js.map', + }); + expect(await provider.modulePathToModule, {modulePath: module}); + expect(await provider.moduleToModulePath, {module: modulePath}); + expect(await provider.modules, {module}); }, ); diff --git a/dwds/web/reloader/ddc_library_bundle_restarter.dart b/dwds/web/reloader/ddc_library_bundle_restarter.dart index b9bffc843..6ce3faea2 100644 --- a/dwds/web/reloader/ddc_library_bundle_restarter.dart +++ b/dwds/web/reloader/ddc_library_bundle_restarter.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; import 'package:dwds/src/utilities/shared.dart'; @@ -85,7 +86,7 @@ class DdcLibraryBundleRestarter implements Restarter { } @override - Future> hotReloadStart(String hotReloadSourcesPath) async { + Future hotReloadStart(String hotReloadSourcesPath) async { final completer = Completer(); final xhr = _XMLHttpRequest(); xhr.withCredentials = true; @@ -101,13 +102,17 @@ class DdcLibraryBundleRestarter implements Restarter { xhr.send(); final responseText = await completer.future; - final srcLibraries = (json.decode(responseText) as List).cast(); + final srcModuleLibraries = (json.decode(responseText) as List).cast(); final filesToLoad = JSArray(); + final moduleMap = JSObject(); final librariesToReload = JSArray(); - for (final srcLibrary in srcLibraries) { - final srcLibraryCast = srcLibrary.cast(); - filesToLoad.push((srcLibraryCast['src'] as String).toJS); - final libraries = (srcLibraryCast['libraries'] as List).cast(); + for (final srcModuleLibrary in srcModuleLibraries) { + final srcModuleLibraryCast = srcModuleLibrary.cast(); + filesToLoad.push((srcModuleLibraryCast['src'] as String).toJS); + final module = srcModuleLibraryCast['module'] as String; + final libraries = + (srcModuleLibraryCast['libraries'] as List).cast(); + moduleMap[module] = libraries.jsify(); for (final library in libraries) { librariesToReload.push(library.toJS); } @@ -117,7 +122,7 @@ class DdcLibraryBundleRestarter implements Restarter { _capturedHotReloadEndCallback = hotReloadEndCallback; }.toJS; await _dartDevEmbedder.hotReload(filesToLoad, librariesToReload).toDart; - return librariesToReload; + return moduleMap; } @override diff --git a/dwds/web/reloader/ddc_restarter.dart b/dwds/web/reloader/ddc_restarter.dart index 095f21ef1..30399cfcb 100644 --- a/dwds/web/reloader/ddc_restarter.dart +++ b/dwds/web/reloader/ddc_restarter.dart @@ -47,7 +47,7 @@ class DdcRestarter implements Restarter { ); @override - Future> hotReloadStart(String hotReloadSourcesPath) => + Future hotReloadStart(String hotReloadSourcesPath) => throw UnimplementedError( 'Hot reload is not supported for the DDC module format.', ); diff --git a/dwds/web/reloader/manager.dart b/dwds/web/reloader/manager.dart index 22c4b0fb4..dc6c5a0b4 100644 --- a/dwds/web/reloader/manager.dart +++ b/dwds/web/reloader/manager.dart @@ -43,13 +43,15 @@ class ReloadingManager { } /// Computes the sources and libraries to reload, loads them into the page, - /// and returns the list of libraries using [hotReloadSourcesPath] as the path - /// to a JSONified list of maps which follows the following format: + /// and returns a map of module names to libraries using + /// [hotReloadSourcesPath] as the path to a JSONified list of maps which + /// follows the following format: /// /// ```json /// [ /// { /// "src": "", + /// "module": "", /// "libraries": ["", ""], /// }, /// ] @@ -57,9 +59,10 @@ class ReloadingManager { /// /// `src`: A string that corresponds to the file path containing a DDC library /// bundle. + /// `module`: The name of the library bundle in `src`. /// `libraries`: An array of strings containing the libraries that were /// compiled in `src`. - Future> hotReloadStart(String hotReloadSourcesPath) => + Future hotReloadStart(String hotReloadSourcesPath) => _restarter.hotReloadStart(hotReloadSourcesPath); /// Does a hard reload of the application. diff --git a/dwds/web/reloader/require_restarter.dart b/dwds/web/reloader/require_restarter.dart index 0e650629a..01115fffb 100644 --- a/dwds/web/reloader/require_restarter.dart +++ b/dwds/web/reloader/require_restarter.dart @@ -168,7 +168,7 @@ class RequireRestarter implements Restarter { ); @override - Future> hotReloadStart(String hotReloadSourcesPath) => + Future hotReloadStart(String hotReloadSourcesPath) => throw UnimplementedError( 'Hot reload is not supported for the AMD module format.', ); diff --git a/dwds/web/reloader/restarter.dart b/dwds/web/reloader/restarter.dart index e3ca0f70f..31cb09484 100644 --- a/dwds/web/reloader/restarter.dart +++ b/dwds/web/reloader/restarter.dart @@ -14,13 +14,15 @@ abstract class Restarter { Future hotReloadEnd(); /// Computes the sources and libraries to reload, loads them into the page, - /// and returns the list of libraries using [hotReloadSourcesPath] as the path - /// to a JSONified list of maps which follows the following format: + /// and returns a map of module names to libraries using + /// [hotReloadSourcesPath] as the path to a JSONified list of maps which + /// follows the following format: /// /// ```json /// [ /// { /// "src": "", + /// "module": "", /// "libraries": ["", ""], /// }, /// ] @@ -28,7 +30,8 @@ abstract class Restarter { /// /// `src`: A string that corresponds to the file path containing a DDC library /// bundle. + /// `module`: The name of the library bundle in `src`. /// `libraries`: An array of strings containing the libraries that were /// compiled in `src`. - Future> hotReloadStart(String hotReloadSourcesPath); + Future hotReloadStart(String hotReloadSourcesPath); } diff --git a/frontend_server_common/lib/src/devfs.dart b/frontend_server_common/lib/src/devfs.dart index 02a24839e..ace83aa7c 100644 --- a/frontend_server_common/lib/src/devfs.dart +++ b/frontend_server_common/lib/src/devfs.dart @@ -253,10 +253,11 @@ class WebDevFS { static const String reloadScriptsFileName = 'reload_scripts.json'; /// Given a list of [modules] that need to be reloaded, writes a file that - /// contains a list of objects each with two fields: + /// contains a list of objects each with three fields: /// /// `src`: A string that corresponds to the file path containing a DDC library /// bundle. + /// `module`: The name of the library bundle in `src`. /// `libraries`: An array of strings containing the libraries that were /// compiled in `src`. /// @@ -265,6 +266,7 @@ class WebDevFS { /// [ /// { /// "src": "", + /// "module": "", /// "libraries": ["", ""], /// }, /// ] @@ -286,6 +288,7 @@ class WebDevFS { final libraries = metadata.libraries.keys.toList(); moduleToLibrary.add({ 'src': _findModuleToLoad(module, entrypointDirectory), + 'module': metadata.name, 'libraries': libraries }); } From 79a66608919fa1e64b8e42a7fa7a0f62ae002ba5 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Sat, 28 Jun 2025 11:13:12 -0700 Subject: [PATCH 02/13] Resolve analysis errors --- dwds/lib/src/debugging/metadata/provider.dart | 8 ++++---- dwds/test/fixtures/context.dart | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dwds/lib/src/debugging/metadata/provider.dart b/dwds/lib/src/debugging/metadata/provider.dart index 0125112e4..832a9e099 100644 --- a/dwds/lib/src/debugging/metadata/provider.dart +++ b/dwds/lib/src/debugging/metadata/provider.dart @@ -227,7 +227,7 @@ class MetadataProvider { } final deletedModules = {}; - final invalidatedModules = {}; + // final invalidatedModules = {}; for (final module in _moduleToLibraries.keys) { final deletedModule = !modules.containsKey(module); final invalidatedModule = reloadedModules.contains(module); @@ -247,9 +247,9 @@ class MetadataProvider { } // The libraries that were removed from the program or those that we // invalidated but were never added again. - final deletedLibraries = invalidatedLibraries.where( - (library) => !_libraries.contains(library), - ); + // final deletedLibraries = invalidatedLibraries.where( + // (library) => !_libraries.contains(library), + // ); } void _addMetadata(ModuleMetadata metadata) { diff --git a/dwds/test/fixtures/context.dart b/dwds/test/fixtures/context.dart index f372658f6..a6a1ab373 100644 --- a/dwds/test/fixtures/context.dart +++ b/dwds/test/fixtures/context.dart @@ -412,7 +412,7 @@ class TestContext { 'remote-debugging-port=$debugPort', if (enableDebugExtension) '--load-extension=debug_extension/prod_build', - // if (headless) '--headless', + if (headless) '--headless', ], }, }); From a15ad8190cca3584dbc2ec1751584bb203fc9999 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Sat, 28 Jun 2025 17:15:13 -0700 Subject: [PATCH 03/13] Optimize skip lists and locations --- dwds/lib/src/debugging/debugger.dart | 1 + dwds/lib/src/debugging/location.dart | 34 ++++++++++++--- dwds/lib/src/debugging/metadata/provider.dart | 43 ++++++++++++++++--- dwds/lib/src/debugging/modules.dart | 28 ++++++++++-- dwds/lib/src/debugging/skip_list.dart | 40 ++++++++++++++++- dwds/lib/src/loaders/strategy.dart | 6 +-- .../src/services/chrome_proxy_service.dart | 19 ++++---- dwds/test/debugger_test.dart | 4 +- dwds/test/expression_evaluator_test.dart | 4 +- dwds/test/fixtures/context.dart | 2 +- dwds/test/fixtures/fakes.dart | 10 ++++- dwds/test/location_test.dart | 4 +- dwds/test/skip_list_test.dart | 12 +++--- 13 files changed, 162 insertions(+), 45 deletions(-) diff --git a/dwds/lib/src/debugging/debugger.dart b/dwds/lib/src/debugging/debugger.dart index 2a638dae7..c96c2bdbe 100644 --- a/dwds/lib/src/debugging/debugger.dart +++ b/dwds/lib/src/debugging/debugger.dart @@ -594,6 +594,7 @@ class Debugger extends Domain { params: { 'skipList': _skipLists.compute( scriptId, + url, await _locations.locationsForUrl(url), ), }, diff --git a/dwds/lib/src/debugging/location.dart b/dwds/lib/src/debugging/location.dart index f4640fe2a..aba95159a 100644 --- a/dwds/lib/src/debugging/location.dart +++ b/dwds/lib/src/debugging/location.dart @@ -4,6 +4,7 @@ import 'package:async/async.dart'; import 'package:dwds/src/config/tool_configuration.dart'; +import 'package:dwds/src/debugging/metadata/provider.dart'; import 'package:dwds/src/debugging/modules.dart'; import 'package:dwds/src/readers/asset_reader.dart'; import 'package:dwds/src/utilities/dart_uri.dart'; @@ -151,12 +152,33 @@ class Locations { Modules get modules => _modules; - void initialize(String entrypoint) { - _sourceToTokenPosTable.clear(); - _sourceToLocation.clear(); - _locationMemoizer.clear(); - _moduleToLocations.clear(); - _entrypoint = entrypoint; + Future initialize( + String entrypoint, [ + InvalidatedModuleReport? invalidatedModuleReport, + ]) async { + // If we know that only certain modules are deleted or added, we can only + // invalidate those. + if (invalidatedModuleReport != null) { + for (final module in invalidatedModuleReport.deletedModules.union( + invalidatedModuleReport.reloadedModules, + )) { + _locationMemoizer.remove(module); + _moduleToLocations.remove(module); + final sources = await _modules.sourcesForModule(module); + if (sources != null) { + for (final serverPath in sources) { + _sourceToTokenPosTable.remove(serverPath); + _sourceToLocation.remove(serverPath); + } + } + } + } else { + _sourceToTokenPosTable.clear(); + _locationMemoizer.clear(); + _sourceToLocation.clear(); + _moduleToLocations.clear(); + _entrypoint = entrypoint; + } } /// Returns all [Location] data for a provided Dart source. diff --git a/dwds/lib/src/debugging/metadata/provider.dart b/dwds/lib/src/debugging/metadata/provider.dart index 832a9e099..8754cbff4 100644 --- a/dwds/lib/src/debugging/metadata/provider.dart +++ b/dwds/lib/src/debugging/metadata/provider.dart @@ -215,7 +215,9 @@ class MetadataProvider { await _metadataMemoizer.runOnce(() => _processMetadata(false)); } - Future reinitializeAfterReload(Set reloadedModules) async { + Future reinitializeAfterReload( + Map reloadedModulesToLibraries, + ) async { final modules = (await _processMetadata(true))!; final invalidatedLibraries = {}; void invalidateLibrary(String libraryImportUri) { @@ -227,10 +229,9 @@ class MetadataProvider { } final deletedModules = {}; - // final invalidatedModules = {}; for (final module in _moduleToLibraries.keys) { final deletedModule = !modules.containsKey(module); - final invalidatedModule = reloadedModules.contains(module); + final invalidatedModule = reloadedModulesToLibraries.containsKey(module); assert(!(deletedModule && invalidatedModule)); // If the module was either deleted or reloaded, invalidate all previous // information both about the module and its libraries. @@ -242,14 +243,27 @@ class MetadataProvider { } if (deletedModule) deletedModules.add(module); } - for (final module in reloadedModules) { + final reloadedModules = {}; + final reloadedLibraries = {}; + for (final module in reloadedModulesToLibraries.keys) { + reloadedModules.add(module); + reloadedLibraries.addAll( + reloadedModulesToLibraries[module]!.cast(), + ); _addMetadata(modules[module]!); } // The libraries that were removed from the program or those that we // invalidated but were never added again. - // final deletedLibraries = invalidatedLibraries.where( - // (library) => !_libraries.contains(library), - // ); + final deletedLibraries = + invalidatedLibraries + .where((library) => !_libraries.contains(library)) + .toSet(); + return InvalidatedModuleReport( + deletedModules: deletedModules, + deletedLibraries: deletedLibraries, + reloadedModules: reloadedModules, + reloadedLibraries: reloadedLibraries, + ); } void _addMetadata(ModuleMetadata metadata) { @@ -303,3 +317,18 @@ class AbsoluteImportUriException implements Exception { @override String toString() => "AbsoluteImportUriError: '$importUri'"; } + +class InvalidatedModuleReport { + final Set deletedModules; + final Set deletedLibraries; + // The union of invalidated and new modules. + final Set reloadedModules; + // The union of invalidated and new libraries. + final Set reloadedLibraries; + InvalidatedModuleReport({ + required this.deletedModules, + required this.deletedLibraries, + required this.reloadedModules, + required this.reloadedLibraries, + }); +} diff --git a/dwds/lib/src/debugging/modules.dart b/dwds/lib/src/debugging/modules.dart index ea4dd1117..fc4b5518b 100644 --- a/dwds/lib/src/debugging/modules.dart +++ b/dwds/lib/src/debugging/modules.dart @@ -5,6 +5,7 @@ import 'package:async/async.dart'; import 'package:dwds/src/config/tool_configuration.dart'; import 'package:dwds/src/debugging/debugger.dart'; +import 'package:dwds/src/debugging/metadata/provider.dart'; import 'package:dwds/src/utilities/dart_uri.dart'; import 'package:logging/logging.dart'; @@ -16,6 +17,9 @@ class Modules { // The Dart server path to containing module. final _sourceToModule = {}; + // Module to Dart server paths. + final _moduleToSources = >{}; + // The Dart server path to library import uri final _sourceToLibrary = {}; var _moduleMemoizer = AsyncMemoizer(); @@ -31,9 +35,13 @@ class Modules { /// Intended to be called multiple times throughout the development workflow, /// e.g. after a hot-reload. void initialize(String entrypoint) { - // We only clear the source to module mapping as script IDs may persist - // across hot reloads. + // TODO(srujzs): Can we do better and only invalidate the sources/modules + // that were deleted/reloaded? This would require removing the + // deleted/reloaded libraries/sources/modules from the following maps and + // then only processing that set in `_initializeMapping`. It's doable, but + // these calculations are also not that expensive. _sourceToModule.clear(); + _moduleToSources.clear(); _sourceToLibrary.clear(); _libraryToModule.clear(); _moduleMemoizer = AsyncMemoizer(); @@ -46,6 +54,12 @@ class Modules { return _sourceToModule[serverPath]; } + /// Returns the Dart server paths for the provided module. + Future?> sourcesForModule(String module) async { + await _moduleMemoizer.runOnce(_initializeMapping); + return _moduleToSources[module]; + } + /// Returns the containing library importUri for the provided Dart server path. Future libraryForSource(String serverPath) async { await _moduleMemoizer.runOnce(_initializeMapping); @@ -69,11 +83,15 @@ class Modules { ) async { final serverPath = await globalToolConfiguration.loadStrategy .serverPathForModule(entrypoint, module); + // TODO(srujzs): We should wait until all scripts are parsed before + // accessing. return chromePathToRuntimeScriptId[serverPath]; } - /// Initializes [_sourceToModule] and [_sourceToLibrary]. - Future _initializeMapping() async { + /// Initializes [_sourceToModule], [_moduleToSources], and [_sourceToLibrary]. + Future _initializeMapping([ + InvalidatedModuleReport? invalidatedModuleReport, + ]) async { final provider = globalToolConfiguration.loadStrategy.metadataProviderFor( _entrypoint, ); @@ -92,6 +110,7 @@ class Modules { final module = scriptToModule[library]!; _sourceToModule[libraryServerPath] = module; + _moduleToSources.putIfAbsent(module, () => {}).add(libraryServerPath); _sourceToLibrary[libraryServerPath] = Uri.parse(library); _libraryToModule[library] = module; @@ -102,6 +121,7 @@ class Modules { : DartUri(script, _root).serverPath; _sourceToModule[scriptServerPath] = module; + _moduleToSources[module]!.add(scriptServerPath); _sourceToLibrary[scriptServerPath] = Uri.parse(library); } } else { diff --git a/dwds/lib/src/debugging/skip_list.dart b/dwds/lib/src/debugging/skip_list.dart index d2b0c817d..a60d0951f 100644 --- a/dwds/lib/src/debugging/skip_list.dart +++ b/dwds/lib/src/debugging/skip_list.dart @@ -2,15 +2,46 @@ // 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. +import 'package:dwds/src/config/tool_configuration.dart'; import 'package:dwds/src/debugging/location.dart'; +import 'package:dwds/src/debugging/metadata/provider.dart'; +import 'package:dwds/src/utilities/dart_uri.dart'; const maxValue = 2147483647; class SkipLists { // Map of script ID to scriptList. final _idToList = >>{}; + // Map of url to script ID. + final _urlToId = {}; + final String _root; - void initialize() => _idToList.clear(); + SkipLists(this._root); + + Future initialize([ + String? entrypoint, + InvalidatedModuleReport? invalidatedModuleReport, + ]) async { + if (invalidatedModuleReport != null) { + assert(entrypoint != null); + final invalidatedModules = invalidatedModuleReport.deletedModules.union( + invalidatedModuleReport.reloadedModules, + ); + for (final url in _urlToId.keys) { + if (url.isEmpty) continue; + + final dartUri = DartUri(url, _root); + final serverPath = dartUri.serverPath; + final module = await globalToolConfiguration.loadStrategy + .moduleForServerPath(entrypoint!, serverPath); + if (invalidatedModules.contains(module)) { + _idToList.remove(_urlToId[url]!); + } + } + } else { + _idToList.clear(); + } + } /// Returns a skipList as defined by the Chrome DevTools Protocol. /// @@ -18,7 +49,12 @@ class SkipLists { /// https://chromedevtools.github.io/devtools-protocol/tot/Debugger/#method-stepInto /// /// Can return a cached value. - List> compute(String scriptId, Set locations) { + List> compute( + String scriptId, + String url, + Set locations, + ) { + _urlToId[url] = scriptId; if (_idToList.containsKey(scriptId)) return _idToList[scriptId]!; final sortedLocations = diff --git a/dwds/lib/src/loaders/strategy.dart b/dwds/lib/src/loaders/strategy.dart index c66fd3097..c75c81635 100644 --- a/dwds/lib/src/loaders/strategy.dart +++ b/dwds/lib/src/loaders/strategy.dart @@ -183,12 +183,12 @@ abstract class LoadStrategy { return Future.value(); } - Future reinitializeEntrypointAfterReload( + Future reinitializeEntrypointAfterReload( String entrypoint, - Set modules, + Map reloadedModulesToLibraries, ) { final provider = _providers[entrypoint]!; - return provider.reinitializeAfterReload(modules); + return provider.reinitializeAfterReload(reloadedModulesToLibraries); } } diff --git a/dwds/lib/src/services/chrome_proxy_service.dart b/dwds/lib/src/services/chrome_proxy_service.dart index 6be61af11..6ed105635 100644 --- a/dwds/lib/src/services/chrome_proxy_service.dart +++ b/dwds/lib/src/services/chrome_proxy_service.dart @@ -17,6 +17,7 @@ import 'package:dwds/src/debugging/execution_context.dart'; import 'package:dwds/src/debugging/inspector.dart'; import 'package:dwds/src/debugging/instance.dart'; import 'package:dwds/src/debugging/location.dart'; +import 'package:dwds/src/debugging/metadata/provider.dart'; import 'package:dwds/src/debugging/modules.dart'; import 'package:dwds/src/debugging/remote_debugger.dart'; import 'package:dwds/src/debugging/skip_list.dart'; @@ -196,7 +197,7 @@ class ChromeProxyService implements VmServiceInterface { final modules = Modules(root); final locations = Locations(assetReader, modules, root); - final skipLists = SkipLists(); + final skipLists = SkipLists(root); final service = ChromeProxyService._( vm, root, @@ -245,18 +246,20 @@ class ChromeProxyService implements VmServiceInterface { Map reloadedModules, ) async { final entrypoint = inspector.appConnection.request.entrypointPath; - final modules = reloadedModules.keys.toSet(); - await globalToolConfiguration.loadStrategy - .reinitializeEntrypointAfterReload(entrypoint, modules); - _initializeEntrypoint(entrypoint); + final invalidatedModuleReport = await globalToolConfiguration.loadStrategy + .reinitializeEntrypointAfterReload(entrypoint, reloadedModules); + await _initializeEntrypoint(entrypoint, invalidatedModuleReport); await inspector.initialize(); } /// Initializes metadata in [Locations], [Modules], and [ExpressionCompiler]. - void _initializeEntrypoint(String entrypoint) { - _locations.initialize(entrypoint); + Future _initializeEntrypoint( + String entrypoint, [ + InvalidatedModuleReport? invalidatedModuleReport, + ]) async { _modules.initialize(entrypoint); - _skipLists.initialize(); + await _locations.initialize(entrypoint, invalidatedModuleReport); + await _skipLists.initialize(entrypoint, invalidatedModuleReport); // We do not need to wait for compiler dependencies to be updated as the // [ExpressionEvaluator] is robust to evaluation requests during updates. safeUnawaited(_updateCompilerDependencies(entrypoint)); diff --git a/dwds/test/debugger_test.dart b/dwds/test/debugger_test.dart index bfe6cc15b..ec6fd766e 100644 --- a/dwds/test/debugger_test.dart +++ b/dwds/test/debugger_test.dart @@ -88,8 +88,8 @@ void main() async { FakeModules(), root, ); - locations.initialize('fake_entrypoint'); - skipLists = SkipLists(); + await locations.initialize('fake_entrypoint'); + skipLists = SkipLists(root); debugger = await Debugger.create( webkitDebugger, (_, __) {}, diff --git a/dwds/test/expression_evaluator_test.dart b/dwds/test/expression_evaluator_test.dart index a9a147224..7f32c276c 100644 --- a/dwds/test/expression_evaluator_test.dart +++ b/dwds/test/expression_evaluator_test.dart @@ -54,9 +54,9 @@ void main() async { final root = 'fakeRoot'; final entry = 'fake_entrypoint'; final locations = Locations(assetReader, modules, root); - locations.initialize(entry); + await locations.initialize(entry); - final skipLists = SkipLists(); + final skipLists = SkipLists(root); final debugger = await Debugger.create( webkitDebugger, (_, e) => debugEventController.sink.add(e), diff --git a/dwds/test/fixtures/context.dart b/dwds/test/fixtures/context.dart index a6a1ab373..f372658f6 100644 --- a/dwds/test/fixtures/context.dart +++ b/dwds/test/fixtures/context.dart @@ -412,7 +412,7 @@ class TestContext { 'remote-debugging-port=$debugPort', if (enableDebugExtension) '--load-extension=debug_extension/prod_build', - if (headless) '--headless', + // if (headless) '--headless', ], }, }); diff --git a/dwds/test/fixtures/fakes.dart b/dwds/test/fixtures/fakes.dart index 114c3e2a7..ab961e0ea 100644 --- a/dwds/test/fixtures/fakes.dart +++ b/dwds/test/fixtures/fakes.dart @@ -157,7 +157,10 @@ class FakeModules implements Modules { _path = path; @override - void initialize(String entrypoint) {} + void initialize( + String entrypoint, [ + InvalidatedModuleReport? invalidatedModuleReport, + ]) {} @override Future libraryForSource(String serverPath) async => Uri(path: _library); @@ -166,7 +169,10 @@ class FakeModules implements Modules { Future moduleForSource(String serverPath) async => _module; @override - Future> modules() async => {_module: _path}; + Future> sourcesForModule(String module) async => {_path}; + + @override + Future> modules() async => {_path: _module}; @override Future moduleForLibrary(String libraryUri) async => _module; diff --git a/dwds/test/location_test.dart b/dwds/test/location_test.dart index ba5e38a4b..e3a914320 100644 --- a/dwds/test/location_test.dart +++ b/dwds/test/location_test.dart @@ -20,7 +20,7 @@ final sourceMapContents = 'AAGV,IAFI,kCAAqC,QAAC;AACX,MAA/B,WAAM,AAAwB,0BAAP,QAAF,AAAE,KAAK,GAAP;' ';EAEzB","file":"main.ddc.js"}'; -void main() { +void main() async { const lines = 100; const lineLength = 150; final assetReader = FakeAssetReader(sourceMap: sourceMapContents); @@ -32,7 +32,7 @@ void main() { final modules = FakeModules(module: _module); final locations = Locations(assetReader, modules, ''); - locations.initialize('fake_entrypoint'); + await locations.initialize('fake_entrypoint'); group('JS locations |', () { const fakeRuntimeScriptId = '12'; diff --git a/dwds/test/skip_list_test.dart b/dwds/test/skip_list_test.dart index d1c626529..0707fdc0b 100644 --- a/dwds/test/skip_list_test.dart +++ b/dwds/test/skip_list_test.dart @@ -20,11 +20,11 @@ void main() { const fakeRuntimeScriptId = '12'; group('SkipLists', () { setUp(() { - skipLists = SkipLists(); + skipLists = SkipLists(''); }); test('do not include known ranges', () { - final skipList = skipLists.compute('123', { + final skipList = skipLists.compute('123', '456', { Location.from( 'foo', TargetLineEntry(1, []), @@ -47,7 +47,7 @@ void main() { }); test('do not include start of the file', () { - final skipList = skipLists.compute('123', { + final skipList = skipLists.compute('123', '456', { Location.from( 'foo', TargetLineEntry(0, []), @@ -69,7 +69,7 @@ void main() { }); test('does not depend on order of locations', () { - final skipList = skipLists.compute('123', { + final skipList = skipLists.compute('123', '456', { Location.from( 'foo', TargetLineEntry(10, []), @@ -92,14 +92,14 @@ void main() { test('contains the provided id', () { final id = '123'; - final skipList = skipLists.compute(id, {}); + final skipList = skipLists.compute(id, '456', {}); for (final range in skipList) { expect(range['scriptId'], id); } }); test('ignores the whole file if provided no locations', () { - final skipList = skipLists.compute('123', {}); + final skipList = skipLists.compute('123', '456', {}); expect(skipList.length, 1); _validateRange(skipList.first, 0, 0, maxValue, maxValue); }); From c09a43237e55a69693b437a5c6164c86e4440dfc Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Mon, 30 Jun 2025 09:16:42 -0700 Subject: [PATCH 04/13] More optimizations --- dwds/lib/src/debugging/inspector.dart | 67 +++++++++++++--- dwds/lib/src/debugging/libraries.dart | 27 +++++-- dwds/lib/src/debugging/modules.dart | 79 ++++++++++++++----- .../src/services/chrome_proxy_service.dart | 9 ++- dwds/test/fixtures/fakes.dart | 8 +- 5 files changed, 146 insertions(+), 44 deletions(-) diff --git a/dwds/lib/src/debugging/inspector.dart b/dwds/lib/src/debugging/inspector.dart index 1e4932e38..53808d092 100644 --- a/dwds/lib/src/debugging/inspector.dart +++ b/dwds/lib/src/debugging/inspector.dart @@ -13,6 +13,7 @@ import 'package:dwds/src/debugging/execution_context.dart'; import 'package:dwds/src/debugging/instance.dart'; import 'package:dwds/src/debugging/libraries.dart'; import 'package:dwds/src/debugging/location.dart'; +import 'package:dwds/src/debugging/metadata/provider.dart'; import 'package:dwds/src/debugging/remote_debugger.dart'; import 'package:dwds/src/loaders/ddc_library_bundle.dart'; import 'package:dwds/src/readers/asset_reader.dart'; @@ -34,7 +35,9 @@ import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'; class AppInspector implements AppInspectorInterface { var _scriptCacheMemoizer = AsyncMemoizer>(); - Future> get scriptRefs => _populateScriptCaches(); + Future> getScriptRefs([ + InvalidatedModuleReport? invalidatedModuleReport, + ]) => _populateScriptCaches(invalidatedModuleReport); final _logger = Logger('AppInspector'); @@ -103,24 +106,35 @@ class AppInspector implements AppInspectorInterface { /// Reset all caches and recompute any mappings. /// - /// Should be called across hot reloads. - Future initialize() async { + /// Should be called across hot reloads with a valid [invalidatedModuleReport]. + Future initialize([ + InvalidatedModuleReport? invalidatedModuleReport, + ]) async { _scriptCacheMemoizer = AsyncMemoizer>(); - _scriptRefsById.clear(); - _serverPathToScriptRef.clear(); - _scriptIdToLibraryId.clear(); - _libraryIdToScriptRefs.clear(); - _libraryHelper = LibraryHelper(this); + // TODO(srujzs): We can invalidate these in a smarter way instead of + // reinitializing when doing a hot reload, but these helpers recompute info + // on demand and therefore are not in the critical path. _classHelper = ClassHelper(this); _instanceHelper = InstanceHelper(this); + if (invalidatedModuleReport != null) { + // Invalidate `_libraryHelper` as we use it populate any script caches. + _libraryHelper.invalidate(invalidatedModuleReport); + } else { + _libraryHelper = LibraryHelper(this); + _scriptRefsById.clear(); + _serverPathToScriptRef.clear(); + _scriptIdToLibraryId.clear(); + _libraryIdToScriptRefs.clear(); + } + final libraries = await _libraryHelper.libraryRefs; isolate.rootLib = await _libraryHelper.rootLib; isolate.libraries?.clear(); isolate.libraries?.addAll(libraries); - final scripts = await scriptRefs; + final scripts = await getScriptRefs(invalidatedModuleReport); await DartUri.initialize(); DartUri.recordAbsoluteUris(libraries.map((lib) => lib.uri).nonNulls); @@ -583,7 +597,7 @@ class AppInspector implements AppInspectorInterface { /// All the scripts in the isolate. @override Future getScripts() async { - return ScriptList(scripts: await scriptRefs); + return ScriptList(scripts: await getScriptRefs()); } /// Calls the Chrome Runtime.getProperties API for the object with [objectId]. @@ -714,19 +728,50 @@ class AppInspector implements AppInspectorInterface { /// /// This will get repopulated on restarts and reloads. /// + /// If [invalidatedModuleReport] is provided, only invalidates and + /// recalculates caches for the invalidated libraries. + /// /// Returns the list of scripts refs cached. - Future> _populateScriptCaches() { + Future> _populateScriptCaches([ + InvalidatedModuleReport? invalidatedModuleReport, + ]) { return _scriptCacheMemoizer.runOnce(() async { final scripts = await globalToolConfiguration.loadStrategy .metadataProviderFor(appConnection.request.entrypointPath) .scripts; + final invalidatedLibraries = invalidatedModuleReport?.deletedLibraries + .union(invalidatedModuleReport.reloadedLibraries); + if (invalidatedLibraries != null) { + for (final libraryUri in invalidatedLibraries) { + final libraryRef = await _libraryHelper.libraryRefFor(libraryUri); + final libraryId = libraryRef?.id; + if (libraryId == null) continue; + final scriptRefs = _libraryIdToScriptRefs.remove(libraryId); + if (scriptRefs == null) continue; + for (final scriptRef in scriptRefs) { + final scriptId = scriptRef.id; + final scriptUri = scriptRef.uri; + if (scriptId != null && scriptUri != null) { + _scriptRefsById.remove(scriptId); + _scriptIdToLibraryId.remove(scriptId); + _serverPathToScriptRef.remove( + DartUri(scriptUri, _root).serverPath, + ); + } + } + } + } // For all the non-dart: libraries, find their parts and create scriptRefs // for them. final userLibraries = _userLibraryUris( isolate.libraries ?? [], ); for (final uri in userLibraries) { + if (invalidatedLibraries != null && + !invalidatedLibraries.contains(uri)) { + continue; + } final parts = scripts[uri]; final scriptRefs = [ ScriptRef(uri: uri, id: createId()), diff --git a/dwds/lib/src/debugging/libraries.dart b/dwds/lib/src/debugging/libraries.dart index e97234bed..8fabe4b48 100644 --- a/dwds/lib/src/debugging/libraries.dart +++ b/dwds/lib/src/debugging/libraries.dart @@ -5,6 +5,7 @@ import 'package:collection/collection.dart'; import 'package:dwds/src/config/tool_configuration.dart'; import 'package:dwds/src/debugging/metadata/class.dart'; +import 'package:dwds/src/debugging/metadata/provider.dart'; import 'package:dwds/src/services/chrome_debug_exception.dart'; import 'package:dwds/src/utilities/domain.dart'; import 'package:logging/logging.dart'; @@ -51,9 +52,27 @@ class LibraryHelper extends Domain { return _rootLib!; } + void invalidate(InvalidatedModuleReport invalidatedModuleReport) { + final invalidatedLibraries = invalidatedModuleReport.deletedLibraries.union( + invalidatedModuleReport.reloadedLibraries, + ); + for (final library in invalidatedLibraries) { + // These will later be initialized by `libraryFor` if needed. + _librariesById.remove(library); + _libraryRefsById.remove(library); + } + for (final library in invalidatedModuleReport.reloadedLibraries) { + _libraryRefsById[library] = _createLibraryRef(library); + } + } + + LibraryRef _createLibraryRef(String library) => + LibraryRef(id: library, name: library, uri: library); + /// Returns all libraryRefs in the app. /// - /// Note this can return a cached result. + /// Note this can return a cached result that can be selectively reinitialized + /// using [invalidate]. Future> get libraryRefs async { if (_libraryRefsById.isNotEmpty) return _libraryRefsById.values.toList(); final libraries = @@ -61,11 +80,7 @@ class LibraryHelper extends Domain { .metadataProviderFor(inspector.appConnection.request.entrypointPath) .libraries; for (final library in libraries) { - _libraryRefsById[library] = LibraryRef( - id: library, - name: library, - uri: library, - ); + _libraryRefsById[library] = _createLibraryRef(library); } return _libraryRefsById.values.toList(); } diff --git a/dwds/lib/src/debugging/modules.dart b/dwds/lib/src/debugging/modules.dart index fc4b5518b..a5549eadc 100644 --- a/dwds/lib/src/debugging/modules.dart +++ b/dwds/lib/src/debugging/modules.dart @@ -30,22 +30,25 @@ class Modules { Modules(this._root); - /// Initializes the mapping from source to module. + /// Initializes mappings after invalidating modified libraries/modules. /// /// Intended to be called multiple times throughout the development workflow, /// e.g. after a hot-reload. - void initialize(String entrypoint) { - // TODO(srujzs): Can we do better and only invalidate the sources/modules - // that were deleted/reloaded? This would require removing the - // deleted/reloaded libraries/sources/modules from the following maps and - // then only processing that set in `_initializeMapping`. It's doable, but - // these calculations are also not that expensive. - _sourceToModule.clear(); - _moduleToSources.clear(); - _sourceToLibrary.clear(); - _libraryToModule.clear(); - _moduleMemoizer = AsyncMemoizer(); - _entrypoint = entrypoint; + Future initialize( + String entrypoint, [ + InvalidatedModuleReport? invalidatedModuleReport, + ]) async { + if (invalidatedModuleReport != null) { + assert(_entrypoint == entrypoint); + await _initializeMapping(invalidatedModuleReport); + } else { + _sourceToModule.clear(); + _moduleToSources.clear(); + _sourceToLibrary.clear(); + _libraryToModule.clear(); + _moduleMemoizer = AsyncMemoizer(); + _entrypoint = entrypoint; + } } /// Returns the containing module for the provided Dart server path. @@ -88,7 +91,37 @@ class Modules { return chromePathToRuntimeScriptId[serverPath]; } + String _getLibraryServerPath(String library) => + library.startsWith('dart:') + ? library + : DartUri(library, _root).serverPath; + + Set _invalidateLibraries( + InvalidatedModuleReport invalidatedModuleReport, + ) { + Set invalidatedLibraries; + invalidatedLibraries = invalidatedModuleReport.deletedLibraries.union( + invalidatedModuleReport.reloadedLibraries, + ); + final invalidatedModules = invalidatedModuleReport.deletedModules.union( + invalidatedModuleReport.reloadedModules, + ); + for (final library in invalidatedLibraries) { + final libraryServerPath = _getLibraryServerPath(library); + _sourceToLibrary.remove(libraryServerPath); + _sourceToModule.remove(libraryServerPath); + _libraryToModule.remove(library); + } + for (final module in invalidatedModules) { + _moduleToSources.remove(module); + } + return invalidatedLibraries; + } + /// Initializes [_sourceToModule], [_moduleToSources], and [_sourceToLibrary]. + /// + /// If [invalidatedModuleReport] is not null, only updates the maps for the + /// invalidated libraries in the report. Future _initializeMapping([ InvalidatedModuleReport? invalidatedModuleReport, ]) async { @@ -99,12 +132,19 @@ class Modules { final libraryToScripts = await provider.scripts; final scriptToModule = await provider.scriptToModule; + final invalidatedLibraries = + invalidatedModuleReport != null + ? _invalidateLibraries(invalidatedModuleReport) + : null; + for (final library in libraryToScripts.keys) { + if (invalidatedLibraries != null) { + // Note that every module will have at least one library associated with + // it, so it's okay to only process the invalidated libraries. + if (!invalidatedLibraries.contains(library)) continue; + } final scripts = libraryToScripts[library]!; - final libraryServerPath = - library.startsWith('dart:') - ? library - : DartUri(library, _root).serverPath; + final libraryServerPath = _getLibraryServerPath(library); if (scriptToModule.containsKey(library)) { final module = scriptToModule[library]!; @@ -115,10 +155,7 @@ class Modules { _libraryToModule[library] = module; for (final script in scripts) { - final scriptServerPath = - script.startsWith('dart:') - ? script - : DartUri(script, _root).serverPath; + final scriptServerPath = _getLibraryServerPath(script); _sourceToModule[scriptServerPath] = module; _moduleToSources[module]!.add(scriptServerPath); diff --git a/dwds/lib/src/services/chrome_proxy_service.dart b/dwds/lib/src/services/chrome_proxy_service.dart index 6ed105635..a67d0d826 100644 --- a/dwds/lib/src/services/chrome_proxy_service.dart +++ b/dwds/lib/src/services/chrome_proxy_service.dart @@ -249,15 +249,18 @@ class ChromeProxyService implements VmServiceInterface { final invalidatedModuleReport = await globalToolConfiguration.loadStrategy .reinitializeEntrypointAfterReload(entrypoint, reloadedModules); await _initializeEntrypoint(entrypoint, invalidatedModuleReport); - await inspector.initialize(); + await inspector.initialize(invalidatedModuleReport); } /// Initializes metadata in [Locations], [Modules], and [ExpressionCompiler]. + /// + /// If [invalidatedModuleReport] is not null, only removes and reinitializes + /// invalidated metadata. Future _initializeEntrypoint( String entrypoint, [ InvalidatedModuleReport? invalidatedModuleReport, ]) async { - _modules.initialize(entrypoint); + await _modules.initialize(entrypoint, invalidatedModuleReport); await _locations.initialize(entrypoint, invalidatedModuleReport); await _skipLists.initialize(entrypoint, invalidatedModuleReport); // We do not need to wait for compiler dependencies to be updated as the @@ -353,7 +356,7 @@ class ChromeProxyService implements VmServiceInterface { if (!newConnection) { await globalToolConfiguration.loadStrategy.trackEntrypoint(entrypoint); } - _initializeEntrypoint(entrypoint); + await _initializeEntrypoint(entrypoint); debugger.notifyPausedAtStart(); _inspector = await AppInspector.create( diff --git a/dwds/test/fixtures/fakes.dart b/dwds/test/fixtures/fakes.dart index ab961e0ea..a4cbc70d2 100644 --- a/dwds/test/fixtures/fakes.dart +++ b/dwds/test/fixtures/fakes.dart @@ -68,7 +68,9 @@ class FakeInspector implements AppInspector { } @override - Future initialize() async => {}; + Future initialize([ + InvalidatedModuleReport? invalidatedModuleReport, + ]) async => {}; @override Future instanceRefFor(Object value) async => @@ -157,10 +159,10 @@ class FakeModules implements Modules { _path = path; @override - void initialize( + Future initialize( String entrypoint, [ InvalidatedModuleReport? invalidatedModuleReport, - ]) {} + ]) async {} @override Future libraryForSource(String serverPath) async => Uri(path: _library); From 143dc7c9f8008128d63752473b8d14630ad26650 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Mon, 30 Jun 2025 16:20:49 -0700 Subject: [PATCH 05/13] Add metadata test --- dwds/lib/src/debugging/inspector.dart | 17 +- dwds/lib/src/debugging/metadata/provider.dart | 28 +++- dwds/test/fixtures/fakes.dart | 9 +- dwds/test/fixtures/utilities.dart | 4 +- dwds/test/metadata_test.dart | 149 ++++++++++++++++++ 5 files changed, 190 insertions(+), 17 deletions(-) diff --git a/dwds/lib/src/debugging/inspector.dart b/dwds/lib/src/debugging/inspector.dart index 53808d092..848bc6ca6 100644 --- a/dwds/lib/src/debugging/inspector.dart +++ b/dwds/lib/src/debugging/inspector.dart @@ -114,7 +114,7 @@ class AppInspector implements AppInspectorInterface { // TODO(srujzs): We can invalidate these in a smarter way instead of // reinitializing when doing a hot reload, but these helpers recompute info - // on demand and therefore are not in the critical path. + // on demand later and therefore are not in the critical path. _classHelper = ClassHelper(this); _instanceHelper = InstanceHelper(this); @@ -729,7 +729,7 @@ class AppInspector implements AppInspectorInterface { /// This will get repopulated on restarts and reloads. /// /// If [invalidatedModuleReport] is provided, only invalidates and - /// recalculates caches for the invalidated libraries. + /// recalculates caches for the invalidated/new libraries. /// /// Returns the list of scripts refs cached. Future> _populateScriptCaches([ @@ -740,10 +740,13 @@ class AppInspector implements AppInspectorInterface { await globalToolConfiguration.loadStrategy .metadataProviderFor(appConnection.request.entrypointPath) .scripts; - final invalidatedLibraries = invalidatedModuleReport?.deletedLibraries + final invalidatedAndNewLibraries = invalidatedModuleReport + ?.deletedLibraries .union(invalidatedModuleReport.reloadedLibraries); - if (invalidatedLibraries != null) { - for (final libraryUri in invalidatedLibraries) { + if (invalidatedAndNewLibraries != null) { + // Invalidate any script caches that were computed for the now invalid + // libraries. They will get repopulated later. + for (final libraryUri in invalidatedAndNewLibraries) { final libraryRef = await _libraryHelper.libraryRefFor(libraryUri); final libraryId = libraryRef?.id; if (libraryId == null) continue; @@ -768,8 +771,8 @@ class AppInspector implements AppInspectorInterface { isolate.libraries ?? [], ); for (final uri in userLibraries) { - if (invalidatedLibraries != null && - !invalidatedLibraries.contains(uri)) { + if (invalidatedAndNewLibraries != null && + !invalidatedAndNewLibraries.contains(uri)) { continue; } final parts = scripts[uri]; diff --git a/dwds/lib/src/debugging/metadata/provider.dart b/dwds/lib/src/debugging/metadata/provider.dart index 8754cbff4..12b65f5c1 100644 --- a/dwds/lib/src/debugging/metadata/provider.dart +++ b/dwds/lib/src/debugging/metadata/provider.dart @@ -170,6 +170,12 @@ class MetadataProvider { return _moduleToModulePath.keys.toList(); } + /// Compute metadata information after reading the metadata contents. + /// + /// If [hotReload] is true, skips adding the SDK metadata and caches the + /// computed [ModuleMetadata], returning the resulting cache. + /// + /// Otherwise, adds all metadata and returns null. Future?> _processMetadata(bool hotReload) async { final modules = hotReload ? {} : null; // The merged metadata resides next to the entrypoint. @@ -211,10 +217,17 @@ class MetadataProvider { return modules; } + /// Process all metadata and compute caches once. Future _initialize() async { await _metadataMemoizer.runOnce(() => _processMetadata(false)); } + /// Given a map of hot reloaded modules mapped to their respective libraries, + /// determines deleted and invalidated libraries and modules, invalidates them + /// in any caches, and recomputes the necessary information. + /// + /// Returns an [InvalidatedModuleReport] that can be used to invalidate other + /// caches after a hot reload. Future reinitializeAfterReload( Map reloadedModulesToLibraries, ) async { @@ -318,12 +331,23 @@ class AbsoluteImportUriException implements Exception { String toString() => "AbsoluteImportUriError: '$importUri'"; } +/// Computed after a hot reload using +/// [MetadataProvider.reinitializeAfterReload], represents the modules and +/// libraries in the program that were deleted, reloaded, and therefore, +/// invalidated. +/// +/// Used to recompute caches throughout DWDS. class InvalidatedModuleReport { + /// Module names that are no longer in the program. final Set deletedModules; + + /// Library uris that are no longer in the program. final Set deletedLibraries; - // The union of invalidated and new modules. + + /// Module names that were loaded during the hot reload. final Set reloadedModules; - // The union of invalidated and new libraries. + + /// Library uris that were loaded during the hot reload. final Set reloadedLibraries; InvalidatedModuleReport({ required this.deletedModules, diff --git a/dwds/test/fixtures/fakes.dart b/dwds/test/fixtures/fakes.dart index a4cbc70d2..e8460d571 100644 --- a/dwds/test/fixtures/fakes.dart +++ b/dwds/test/fixtures/fakes.dart @@ -414,12 +414,11 @@ class FakeStrategy extends LoadStrategy { } class FakeAssetReader implements AssetReader { - final String? _metadata; + String? metadata; final String? _dartSource; final String? _sourceMap; - const FakeAssetReader({metadata, dartSource, sourceMap}) - : _metadata = metadata, - _dartSource = dartSource, + FakeAssetReader({this.metadata, dartSource, sourceMap}) + : _dartSource = dartSource, _sourceMap = sourceMap; @override @@ -432,7 +431,7 @@ class FakeAssetReader implements AssetReader { @override Future metadataContents(String serverPath) { - return _throwUnimplementedOrReturnContents(_metadata); + return _throwUnimplementedOrReturnContents(metadata); } @override diff --git a/dwds/test/fixtures/utilities.dart b/dwds/test/fixtures/utilities.dart index 1fec81e30..6d779c1bc 100644 --- a/dwds/test/fixtures/utilities.dart +++ b/dwds/test/fixtures/utilities.dart @@ -196,9 +196,7 @@ class TestToolConfiguration extends ToolConfiguration { TestDebugSettings super.debugSettings = const TestDebugSettings.noDevTools(), TestBuildSettings buildSettings = const TestBuildSettings.dart(), - }) : super( - loadStrategy: TestStrategy(const FakeAssetReader(), buildSettings), - ); + }) : super(loadStrategy: TestStrategy(FakeAssetReader(), buildSettings)); TestToolConfiguration.withLoadStrategy({ TestAppMetadata super.appMetadata = const TestAppMetadata.externalApp(), diff --git a/dwds/test/metadata_test.dart b/dwds/test/metadata_test.dart index 375c39d67..7652701ec 100644 --- a/dwds/test/metadata_test.dart +++ b/dwds/test/metadata_test.dart @@ -5,6 +5,8 @@ @Timeout(Duration(minutes: 2)) library; +import 'dart:convert'; + import 'package:dwds/src/debugging/metadata/module_metadata.dart'; import 'package:dwds/src/debugging/metadata/provider.dart'; import 'package:test/test.dart'; @@ -115,4 +117,151 @@ void main() { expect(parts[0], 'org-dartlang-app:///web/main.dart'); } }); + + String createMetadataContents( + Map> moduleToLibraries, + Map> libraryToParts, + ) { + final contents = StringBuffer(''); + for (final module in moduleToLibraries.keys) { + final moduleMetadata = ModuleMetadata( + module, + 'load__web__$module', + 'foo/web/$module.ddc.js.map', + 'foo/web/$module.ddc.js', + ); + for (final library in moduleToLibraries[module]!) { + moduleMetadata.addLibrary( + LibraryMetadata(library, library, libraryToParts[library] ?? []), + ); + } + contents.writeln(json.encode(moduleMetadata.toJson())); + } + contents.write('// intentionally empty: ...'); + return contents.toString(); + } + + Future verifyExpectations( + MetadataProvider provider, + Map> moduleToLibraries, + Map> libraryToParts, + ) async { + final scriptToModule = await provider.scriptToModule; + final expectedScriptToModule = {}; + for (final module in moduleToLibraries.keys) { + final libraries = moduleToLibraries[module]!; + for (final library in libraries) { + expectedScriptToModule[library] = module; + final parts = libraryToParts[library]; + if (parts != null) { + for (final part in parts) { + expectedScriptToModule[part] = module; + } + } + } + } + for (final entry in expectedScriptToModule.entries) { + expect(scriptToModule[entry.key], entry.value); + } + + final moduleToSourceMap = await provider.moduleToSourceMap; + final expectedModuleToSourceMap = moduleToLibraries.keys.fold( + {}, + (map, module) { + map[module] = 'foo/web/$module.ddc.js.map'; + return map; + }, + ); + for (final entry in expectedModuleToSourceMap.entries) { + expect(moduleToSourceMap[entry.key], entry.value); + } + + final modulePathToModule = await provider.modulePathToModule; + final expectedModulePathToModule = moduleToLibraries.keys.fold( + {}, + (map, module) { + map['foo/web/$module.ddc.js'] = module; + return map; + }, + ); + for (final entry in expectedModulePathToModule.entries) { + expect(modulePathToModule[entry.key], entry.value); + } + + expect(await provider.modules, containsAll(moduleToLibraries.keys)); + } + + test('reinitialize produces correct report', () async { + final moduleToLibraries = >{ + 'm1': [ + 'org-dartlang-app:///web/l1.dart', + 'org-dartlang-app:///web/l2.dart', + ], + 'm2': [ + 'org-dartlang-app:///web/l3.dart', + 'org-dartlang-app:///web/l4.dart', + ], + 'm3': [ + 'org-dartlang-app:///web/l5.dart', + 'org-dartlang-app:///web/l6.dart', + ], + }; + final libraryToParts = >{ + 'l1': ['org-dartlang-app:///web/l1_p1.dart'], + }; + final assetReader = FakeAssetReader( + metadata: createMetadataContents(moduleToLibraries, libraryToParts), + ); + final provider = MetadataProvider('foo.bootstrap.js', assetReader); + await verifyExpectations(provider, moduleToLibraries, libraryToParts); + + final newModuleToLibraries = >{ + 'm1': [ + 'org-dartlang-app:///web/l1.dart', + 'org-dartlang-app:///web/l2.dart', + ], + 'm3': ['org-dartlang-app:///web/l3.dart'], + 'm4': [ + 'org-dartlang-app:///web/l4.dart', + 'org-dartlang-app:///web/l7.dart', + ], + }; + final newLibraryToParts = >{ + 'l1': ['org-dartlang-app:///web/l1_p1.dart'], + 'l7': ['org-dartlang-app:///web/l7_p1.dart'], + }; + final reloadedModulesToLibraries = >{ + 'm3': ['org-dartlang-app:///web/l3.dart'], + 'm4': [ + 'org-dartlang-app:///web/l4.dart', + 'org-dartlang-app:///web/l7.dart', + ], + }; + assetReader.metadata = createMetadataContents( + newModuleToLibraries, + newLibraryToParts, + ); + final invalidatedModuleReport = await provider.reinitializeAfterReload( + reloadedModulesToLibraries, + ); + expect(invalidatedModuleReport.deletedModules, ['m2']); + expect(invalidatedModuleReport.deletedLibraries, [ + 'org-dartlang-app:///web/l5.dart', + 'org-dartlang-app:///web/l6.dart', + ]); + expect( + invalidatedModuleReport.reloadedModules, + reloadedModulesToLibraries.keys, + ); + expect( + invalidatedModuleReport.reloadedLibraries, + containsAll( + reloadedModulesToLibraries.values.fold>([], (value, l) { + value.addAll(l); + return l; + }), + ), + ); + await verifyExpectations(provider, newModuleToLibraries, newLibraryToParts); + }); } From 3c305d1d3a347e3095c5fa0095e2d24857a4e102 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Mon, 30 Jun 2025 16:22:08 -0700 Subject: [PATCH 06/13] Add back headless --- dwds/test/fixtures/context.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dwds/test/fixtures/context.dart b/dwds/test/fixtures/context.dart index f372658f6..a6a1ab373 100644 --- a/dwds/test/fixtures/context.dart +++ b/dwds/test/fixtures/context.dart @@ -412,7 +412,7 @@ class TestContext { 'remote-debugging-port=$debugPort', if (enableDebugExtension) '--load-extension=debug_extension/prod_build', - // if (headless) '--headless', + if (headless) '--headless', ], }, }); From 58af580260afb01b884b785b55795edd212d8fc9 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Mon, 30 Jun 2025 19:55:13 -0700 Subject: [PATCH 07/13] InvalidatedModuleReport -> ModifiedModuleReport --- dwds/lib/src/debugging/inspector.dart | 33 +++++------ dwds/lib/src/debugging/libraries.dart | 9 +-- dwds/lib/src/debugging/location.dart | 8 +-- .../debugging/metadata/module_metadata.dart | 2 + dwds/lib/src/debugging/metadata/provider.dart | 23 +++++--- dwds/lib/src/debugging/modules.dart | 57 +++++++------------ dwds/lib/src/debugging/skip_list.dart | 9 +-- dwds/lib/src/loaders/strategy.dart | 2 +- .../src/services/chrome_proxy_service.dart | 27 ++++----- dwds/test/fixtures/fakes.dart | 7 +-- dwds/test/metadata_test.dart | 48 +++++++++------- 11 files changed, 105 insertions(+), 120 deletions(-) diff --git a/dwds/lib/src/debugging/inspector.dart b/dwds/lib/src/debugging/inspector.dart index 848bc6ca6..e9ba024a1 100644 --- a/dwds/lib/src/debugging/inspector.dart +++ b/dwds/lib/src/debugging/inspector.dart @@ -36,8 +36,8 @@ class AppInspector implements AppInspectorInterface { var _scriptCacheMemoizer = AsyncMemoizer>(); Future> getScriptRefs([ - InvalidatedModuleReport? invalidatedModuleReport, - ]) => _populateScriptCaches(invalidatedModuleReport); + ModifiedModuleReport? modifiedModuleReport, + ]) => _populateScriptCaches(modifiedModuleReport); final _logger = Logger('AppInspector'); @@ -106,10 +106,8 @@ class AppInspector implements AppInspectorInterface { /// Reset all caches and recompute any mappings. /// - /// Should be called across hot reloads with a valid [invalidatedModuleReport]. - Future initialize([ - InvalidatedModuleReport? invalidatedModuleReport, - ]) async { + /// Should be called across hot reloads with a valid [ModifiedModuleReport]. + Future initialize([ModifiedModuleReport? modifiedModuleReport]) async { _scriptCacheMemoizer = AsyncMemoizer>(); // TODO(srujzs): We can invalidate these in a smarter way instead of @@ -118,9 +116,9 @@ class AppInspector implements AppInspectorInterface { _classHelper = ClassHelper(this); _instanceHelper = InstanceHelper(this); - if (invalidatedModuleReport != null) { + if (modifiedModuleReport != null) { // Invalidate `_libraryHelper` as we use it populate any script caches. - _libraryHelper.invalidate(invalidatedModuleReport); + _libraryHelper.invalidate(modifiedModuleReport); } else { _libraryHelper = LibraryHelper(this); _scriptRefsById.clear(); @@ -134,7 +132,7 @@ class AppInspector implements AppInspectorInterface { isolate.libraries?.clear(); isolate.libraries?.addAll(libraries); - final scripts = await getScriptRefs(invalidatedModuleReport); + final scripts = await getScriptRefs(modifiedModuleReport); await DartUri.initialize(); DartUri.recordAbsoluteUris(libraries.map((lib) => lib.uri).nonNulls); @@ -728,25 +726,22 @@ class AppInspector implements AppInspectorInterface { /// /// This will get repopulated on restarts and reloads. /// - /// If [invalidatedModuleReport] is provided, only invalidates and - /// recalculates caches for the invalidated/new libraries. + /// If [modifiedModuleReport] is provided, only invalidates and + /// recalculates caches for the modified libraries. /// /// Returns the list of scripts refs cached. Future> _populateScriptCaches([ - InvalidatedModuleReport? invalidatedModuleReport, + ModifiedModuleReport? modifiedModuleReport, ]) { return _scriptCacheMemoizer.runOnce(() async { final scripts = await globalToolConfiguration.loadStrategy .metadataProviderFor(appConnection.request.entrypointPath) .scripts; - final invalidatedAndNewLibraries = invalidatedModuleReport - ?.deletedLibraries - .union(invalidatedModuleReport.reloadedLibraries); - if (invalidatedAndNewLibraries != null) { + if (modifiedModuleReport != null) { // Invalidate any script caches that were computed for the now invalid // libraries. They will get repopulated later. - for (final libraryUri in invalidatedAndNewLibraries) { + for (final libraryUri in modifiedModuleReport.modifiedLibraries) { final libraryRef = await _libraryHelper.libraryRefFor(libraryUri); final libraryId = libraryRef?.id; if (libraryId == null) continue; @@ -771,8 +766,8 @@ class AppInspector implements AppInspectorInterface { isolate.libraries ?? [], ); for (final uri in userLibraries) { - if (invalidatedAndNewLibraries != null && - !invalidatedAndNewLibraries.contains(uri)) { + if (modifiedModuleReport != null && + !modifiedModuleReport.modifiedLibraries.contains(uri)) { continue; } final parts = scripts[uri]; diff --git a/dwds/lib/src/debugging/libraries.dart b/dwds/lib/src/debugging/libraries.dart index 8fabe4b48..97b803a61 100644 --- a/dwds/lib/src/debugging/libraries.dart +++ b/dwds/lib/src/debugging/libraries.dart @@ -52,16 +52,13 @@ class LibraryHelper extends Domain { return _rootLib!; } - void invalidate(InvalidatedModuleReport invalidatedModuleReport) { - final invalidatedLibraries = invalidatedModuleReport.deletedLibraries.union( - invalidatedModuleReport.reloadedLibraries, - ); - for (final library in invalidatedLibraries) { + void invalidate(ModifiedModuleReport modifiedModuleReport) { + for (final library in modifiedModuleReport.modifiedLibraries) { // These will later be initialized by `libraryFor` if needed. _librariesById.remove(library); _libraryRefsById.remove(library); } - for (final library in invalidatedModuleReport.reloadedLibraries) { + for (final library in modifiedModuleReport.reloadedLibraries) { _libraryRefsById[library] = _createLibraryRef(library); } } diff --git a/dwds/lib/src/debugging/location.dart b/dwds/lib/src/debugging/location.dart index aba95159a..79d4204f5 100644 --- a/dwds/lib/src/debugging/location.dart +++ b/dwds/lib/src/debugging/location.dart @@ -154,14 +154,12 @@ class Locations { Future initialize( String entrypoint, [ - InvalidatedModuleReport? invalidatedModuleReport, + ModifiedModuleReport? modifiedModuleReport, ]) async { // If we know that only certain modules are deleted or added, we can only // invalidate those. - if (invalidatedModuleReport != null) { - for (final module in invalidatedModuleReport.deletedModules.union( - invalidatedModuleReport.reloadedModules, - )) { + if (modifiedModuleReport != null) { + for (final module in modifiedModuleReport.modifiedModules) { _locationMemoizer.remove(module); _moduleToLocations.remove(module); final sources = await _modules.sourcesForModule(module); diff --git a/dwds/lib/src/debugging/metadata/module_metadata.dart b/dwds/lib/src/debugging/metadata/module_metadata.dart index d66641af0..02e1dec61 100644 --- a/dwds/lib/src/debugging/metadata/module_metadata.dart +++ b/dwds/lib/src/debugging/metadata/module_metadata.dart @@ -103,6 +103,8 @@ class ModuleMetadata { /// /// Used as a name of the js module created by the compiler and /// as key to store and load modules in the debugger and the browser + // TODO(srujzs): Remove once https://github.com/dart-lang/sdk/issues/59618 is + // resolved. final String name; /// Name of the function enclosing the module diff --git a/dwds/lib/src/debugging/metadata/provider.dart b/dwds/lib/src/debugging/metadata/provider.dart index 12b65f5c1..0b6422ce5 100644 --- a/dwds/lib/src/debugging/metadata/provider.dart +++ b/dwds/lib/src/debugging/metadata/provider.dart @@ -202,7 +202,7 @@ class MetadataProvider { ); final moduleName = metadata.name; if (hotReload) { - modules?[moduleName] = metadata; + modules![moduleName] = metadata; } else { _addMetadata(metadata); } @@ -226,9 +226,9 @@ class MetadataProvider { /// determines deleted and invalidated libraries and modules, invalidates them /// in any caches, and recomputes the necessary information. /// - /// Returns an [InvalidatedModuleReport] that can be used to invalidate other + /// Returns an [ModifiedModuleReport] that can be used to invalidate other /// caches after a hot reload. - Future reinitializeAfterReload( + Future reinitializeAfterReload( Map reloadedModulesToLibraries, ) async { final modules = (await _processMetadata(true))!; @@ -271,7 +271,7 @@ class MetadataProvider { invalidatedLibraries .where((library) => !_libraries.contains(library)) .toSet(); - return InvalidatedModuleReport( + return ModifiedModuleReport( deletedModules: deletedModules, deletedLibraries: deletedLibraries, reloadedModules: reloadedModules, @@ -334,10 +334,10 @@ class AbsoluteImportUriException implements Exception { /// Computed after a hot reload using /// [MetadataProvider.reinitializeAfterReload], represents the modules and /// libraries in the program that were deleted, reloaded, and therefore, -/// invalidated. +/// modified. /// /// Used to recompute caches throughout DWDS. -class InvalidatedModuleReport { +class ModifiedModuleReport { /// Module names that are no longer in the program. final Set deletedModules; @@ -349,10 +349,17 @@ class InvalidatedModuleReport { /// Library uris that were loaded during the hot reload. final Set reloadedLibraries; - InvalidatedModuleReport({ + + /// Module names that were either removed or modified. + final Set modifiedModules; + + /// Library uris that were either removed or modified. + final Set modifiedLibraries; + ModifiedModuleReport({ required this.deletedModules, required this.deletedLibraries, required this.reloadedModules, required this.reloadedLibraries, - }); + }) : modifiedModules = deletedModules.union(reloadedModules), + modifiedLibraries = deletedLibraries.union(reloadedLibraries); } diff --git a/dwds/lib/src/debugging/modules.dart b/dwds/lib/src/debugging/modules.dart index a5549eadc..8115ac4bf 100644 --- a/dwds/lib/src/debugging/modules.dart +++ b/dwds/lib/src/debugging/modules.dart @@ -34,13 +34,25 @@ class Modules { /// /// Intended to be called multiple times throughout the development workflow, /// e.g. after a hot-reload. + /// + /// If [modifiedModuleReport] is not null, removes and recalculates caches for + /// any modified modules and libraries. Future initialize( String entrypoint, [ - InvalidatedModuleReport? invalidatedModuleReport, + ModifiedModuleReport? modifiedModuleReport, ]) async { - if (invalidatedModuleReport != null) { + if (modifiedModuleReport != null) { assert(_entrypoint == entrypoint); - await _initializeMapping(invalidatedModuleReport); + for (final library in modifiedModuleReport.modifiedLibraries) { + final libraryServerPath = _getLibraryServerPath(library); + _sourceToLibrary.remove(libraryServerPath); + _sourceToModule.remove(libraryServerPath); + _libraryToModule.remove(library); + } + for (final module in modifiedModuleReport.modifiedModules) { + _moduleToSources.remove(module); + } + await _initializeMapping(modifiedModuleReport); } else { _sourceToModule.clear(); _moduleToSources.clear(); @@ -96,34 +108,12 @@ class Modules { ? library : DartUri(library, _root).serverPath; - Set _invalidateLibraries( - InvalidatedModuleReport invalidatedModuleReport, - ) { - Set invalidatedLibraries; - invalidatedLibraries = invalidatedModuleReport.deletedLibraries.union( - invalidatedModuleReport.reloadedLibraries, - ); - final invalidatedModules = invalidatedModuleReport.deletedModules.union( - invalidatedModuleReport.reloadedModules, - ); - for (final library in invalidatedLibraries) { - final libraryServerPath = _getLibraryServerPath(library); - _sourceToLibrary.remove(libraryServerPath); - _sourceToModule.remove(libraryServerPath); - _libraryToModule.remove(library); - } - for (final module in invalidatedModules) { - _moduleToSources.remove(module); - } - return invalidatedLibraries; - } - /// Initializes [_sourceToModule], [_moduleToSources], and [_sourceToLibrary]. /// - /// If [invalidatedModuleReport] is not null, only updates the maps for the - /// invalidated libraries in the report. + /// If [modifiedModuleReport] is not null, only updates the maps for the + /// modified libraries in the report. Future _initializeMapping([ - InvalidatedModuleReport? invalidatedModuleReport, + ModifiedModuleReport? modifiedModuleReport, ]) async { final provider = globalToolConfiguration.loadStrategy.metadataProviderFor( _entrypoint, @@ -132,16 +122,11 @@ class Modules { final libraryToScripts = await provider.scripts; final scriptToModule = await provider.scriptToModule; - final invalidatedLibraries = - invalidatedModuleReport != null - ? _invalidateLibraries(invalidatedModuleReport) - : null; - for (final library in libraryToScripts.keys) { - if (invalidatedLibraries != null) { + if (modifiedModuleReport != null) { // Note that every module will have at least one library associated with - // it, so it's okay to only process the invalidated libraries. - if (!invalidatedLibraries.contains(library)) continue; + // it, so it's okay to only process the modified libraries. + if (!modifiedModuleReport.modifiedLibraries.contains(library)) continue; } final scripts = libraryToScripts[library]!; final libraryServerPath = _getLibraryServerPath(library); diff --git a/dwds/lib/src/debugging/skip_list.dart b/dwds/lib/src/debugging/skip_list.dart index a60d0951f..ccdcb0f12 100644 --- a/dwds/lib/src/debugging/skip_list.dart +++ b/dwds/lib/src/debugging/skip_list.dart @@ -20,13 +20,10 @@ class SkipLists { Future initialize([ String? entrypoint, - InvalidatedModuleReport? invalidatedModuleReport, + ModifiedModuleReport? modifiedModuleReport, ]) async { - if (invalidatedModuleReport != null) { + if (modifiedModuleReport != null) { assert(entrypoint != null); - final invalidatedModules = invalidatedModuleReport.deletedModules.union( - invalidatedModuleReport.reloadedModules, - ); for (final url in _urlToId.keys) { if (url.isEmpty) continue; @@ -34,7 +31,7 @@ class SkipLists { final serverPath = dartUri.serverPath; final module = await globalToolConfiguration.loadStrategy .moduleForServerPath(entrypoint!, serverPath); - if (invalidatedModules.contains(module)) { + if (modifiedModuleReport.modifiedModules.contains(module)) { _idToList.remove(_urlToId[url]!); } } diff --git a/dwds/lib/src/loaders/strategy.dart b/dwds/lib/src/loaders/strategy.dart index c75c81635..1fca85746 100644 --- a/dwds/lib/src/loaders/strategy.dart +++ b/dwds/lib/src/loaders/strategy.dart @@ -183,7 +183,7 @@ abstract class LoadStrategy { return Future.value(); } - Future reinitializeEntrypointAfterReload( + Future reinitializeEntrypointAfterReload( String entrypoint, Map reloadedModulesToLibraries, ) { diff --git a/dwds/lib/src/services/chrome_proxy_service.dart b/dwds/lib/src/services/chrome_proxy_service.dart index a67d0d826..9763931ff 100644 --- a/dwds/lib/src/services/chrome_proxy_service.dart +++ b/dwds/lib/src/services/chrome_proxy_service.dart @@ -235,34 +235,29 @@ class ChromeProxyService implements VmServiceInterface { } /// Reinitializes any caches so that they can be recomputed across hot reload. - // TODO(srujzs): We can maybe do better here than reinitializing all the data. - // Specifically, we can invalidate certain parts as we know what libraries - // will be stale, and therefore recompute information only for those libraries - // and possibly libraries that depend on them. Currently, there's no good - // separation between "existing" information and "new" information, making - // this difficult. - // https://github.com/dart-lang/webdev/issues/2628 + /// + /// We use the [ModifiedModuleReport] to more efficiently invalidate caches. Future _reinitializeForHotReload( Map reloadedModules, ) async { final entrypoint = inspector.appConnection.request.entrypointPath; - final invalidatedModuleReport = await globalToolConfiguration.loadStrategy + final modifiedModuleReport = await globalToolConfiguration.loadStrategy .reinitializeEntrypointAfterReload(entrypoint, reloadedModules); - await _initializeEntrypoint(entrypoint, invalidatedModuleReport); - await inspector.initialize(invalidatedModuleReport); + await _initializeEntrypoint(entrypoint, modifiedModuleReport); + await inspector.initialize(modifiedModuleReport); } /// Initializes metadata in [Locations], [Modules], and [ExpressionCompiler]. /// - /// If [invalidatedModuleReport] is not null, only removes and reinitializes - /// invalidated metadata. + /// If [modifiedModuleReport] is not null, only removes and reinitializes + /// modified metadata. Future _initializeEntrypoint( String entrypoint, [ - InvalidatedModuleReport? invalidatedModuleReport, + ModifiedModuleReport? modifiedModuleReport, ]) async { - await _modules.initialize(entrypoint, invalidatedModuleReport); - await _locations.initialize(entrypoint, invalidatedModuleReport); - await _skipLists.initialize(entrypoint, invalidatedModuleReport); + await _modules.initialize(entrypoint, modifiedModuleReport); + await _locations.initialize(entrypoint, modifiedModuleReport); + await _skipLists.initialize(entrypoint, modifiedModuleReport); // We do not need to wait for compiler dependencies to be updated as the // [ExpressionEvaluator] is robust to evaluation requests during updates. safeUnawaited(_updateCompilerDependencies(entrypoint)); diff --git a/dwds/test/fixtures/fakes.dart b/dwds/test/fixtures/fakes.dart index e8460d571..38e6a4e64 100644 --- a/dwds/test/fixtures/fakes.dart +++ b/dwds/test/fixtures/fakes.dart @@ -68,9 +68,8 @@ class FakeInspector implements AppInspector { } @override - Future initialize([ - InvalidatedModuleReport? invalidatedModuleReport, - ]) async => {}; + Future initialize([ModifiedModuleReport? modifiedModuleReport]) async => + {}; @override Future instanceRefFor(Object value) async => @@ -161,7 +160,7 @@ class FakeModules implements Modules { @override Future initialize( String entrypoint, [ - InvalidatedModuleReport? invalidatedModuleReport, + ModifiedModuleReport? modifiedModuleReport, ]) async {} @override diff --git a/dwds/test/metadata_test.dart b/dwds/test/metadata_test.dart index 7652701ec..f5ed38c8b 100644 --- a/dwds/test/metadata_test.dart +++ b/dwds/test/metadata_test.dart @@ -141,7 +141,7 @@ void main() { return contents.toString(); } - Future verifyExpectations( + Future validateProvider( MetadataProvider provider, Map> moduleToLibraries, Map> libraryToParts, @@ -191,7 +191,7 @@ void main() { expect(await provider.modules, containsAll(moduleToLibraries.keys)); } - test('reinitialize produces correct report', () async { + test('reinitialize produces correct ModifiedModuleReport', () async { final moduleToLibraries = >{ 'm1': [ 'org-dartlang-app:///web/l1.dart', @@ -213,7 +213,7 @@ void main() { metadata: createMetadataContents(moduleToLibraries, libraryToParts), ); final provider = MetadataProvider('foo.bootstrap.js', assetReader); - await verifyExpectations(provider, moduleToLibraries, libraryToParts); + await validateProvider(provider, moduleToLibraries, libraryToParts); final newModuleToLibraries = >{ 'm1': [ @@ -241,27 +241,37 @@ void main() { newModuleToLibraries, newLibraryToParts, ); - final invalidatedModuleReport = await provider.reinitializeAfterReload( + final modifiedModuleReport = await provider.reinitializeAfterReload( reloadedModulesToLibraries, ); - expect(invalidatedModuleReport.deletedModules, ['m2']); - expect(invalidatedModuleReport.deletedLibraries, [ - 'org-dartlang-app:///web/l5.dart', - 'org-dartlang-app:///web/l6.dart', - ]); + expect(modifiedModuleReport.deletedModules, ['m2']); expect( - invalidatedModuleReport.reloadedModules, - reloadedModulesToLibraries.keys, + modifiedModuleReport.deletedLibraries, + unorderedEquals([ + 'org-dartlang-app:///web/l5.dart', + 'org-dartlang-app:///web/l6.dart', + ]), + ); + expect(modifiedModuleReport.reloadedModules, ['m3', 'm4']); + expect( + modifiedModuleReport.reloadedLibraries, + unorderedEquals([ + 'org-dartlang-app:///web/l3.dart', + 'org-dartlang-app:///web/l4.dart', + 'org-dartlang-app:///web/l7.dart', + ]), ); + expect(modifiedModuleReport.modifiedModules, ['m2', 'm3', 'm4']); expect( - invalidatedModuleReport.reloadedLibraries, - containsAll( - reloadedModulesToLibraries.values.fold>([], (value, l) { - value.addAll(l); - return l; - }), - ), + modifiedModuleReport.modifiedLibraries, + unorderedEquals([ + 'org-dartlang-app:///web/l3.dart', + 'org-dartlang-app:///web/l4.dart', + 'org-dartlang-app:///web/l5.dart', + 'org-dartlang-app:///web/l6.dart', + 'org-dartlang-app:///web/l7.dart', + ]), ); - await verifyExpectations(provider, newModuleToLibraries, newLibraryToParts); + await validateProvider(provider, newModuleToLibraries, newLibraryToParts); }); } From 470429d3a76530212d43cbdca797c1cbae852136 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Mon, 30 Jun 2025 20:34:00 -0700 Subject: [PATCH 08/13] Fix up --- dwds/CHANGELOG.md | 1 + dwds/lib/src/debugging/inspector.dart | 2 +- dwds/lib/src/debugging/libraries.dart | 2 ++ dwds/lib/src/debugging/metadata/provider.dart | 18 +++++++++--------- dwds/lib/src/debugging/modules.dart | 3 ++- dwds/lib/src/debugging/skip_list.dart | 9 +++++---- dwds/lib/src/loaders/strategy.dart | 4 ++-- .../lib/src/services/chrome_proxy_service.dart | 10 +++++----- dwds/test/metadata_test.dart | 10 ++++++---- 9 files changed, 33 insertions(+), 26 deletions(-) diff --git a/dwds/CHANGELOG.md b/dwds/CHANGELOG.md index bdc733b8e..1ddf798c4 100644 --- a/dwds/CHANGELOG.md +++ b/dwds/CHANGELOG.md @@ -1,6 +1,7 @@ ## 24.4.0-wip - Added support for breakpoint registering on a hot reload with the DDC library bundle format using PausePostRequests. +- `FrontendServerDdcLibraryBundleStrategy.hotReloadSourceUri` is now expected to also provide the reloaded modules. ## 24.3.11 diff --git a/dwds/lib/src/debugging/inspector.dart b/dwds/lib/src/debugging/inspector.dart index e9ba024a1..52c642e0c 100644 --- a/dwds/lib/src/debugging/inspector.dart +++ b/dwds/lib/src/debugging/inspector.dart @@ -740,7 +740,7 @@ class AppInspector implements AppInspectorInterface { .scripts; if (modifiedModuleReport != null) { // Invalidate any script caches that were computed for the now invalid - // libraries. They will get repopulated later. + // libraries. They will get repopulated below. for (final libraryUri in modifiedModuleReport.modifiedLibraries) { final libraryRef = await _libraryHelper.libraryRefFor(libraryUri); final libraryId = libraryRef?.id; diff --git a/dwds/lib/src/debugging/libraries.dart b/dwds/lib/src/debugging/libraries.dart index 97b803a61..1535f39ca 100644 --- a/dwds/lib/src/debugging/libraries.dart +++ b/dwds/lib/src/debugging/libraries.dart @@ -52,6 +52,8 @@ class LibraryHelper extends Domain { return _rootLib!; } + /// Removes any modified libraries from the cache and either eagerly or lazily + /// computes values for the reloaded libraries in the [modifiedModuleReport]. void invalidate(ModifiedModuleReport modifiedModuleReport) { for (final library in modifiedModuleReport.modifiedLibraries) { // These will later be initialized by `libraryFor` if needed. diff --git a/dwds/lib/src/debugging/metadata/provider.dart b/dwds/lib/src/debugging/metadata/provider.dart index 0b6422ce5..060060424 100644 --- a/dwds/lib/src/debugging/metadata/provider.dart +++ b/dwds/lib/src/debugging/metadata/provider.dart @@ -76,19 +76,19 @@ class MetadataProvider { return true; } - /// A set of all libraries in the Dart application. + /// A list of all libraries in the Dart application. /// /// Example: /// - /// { + /// [ /// dart:web_gl, /// dart:math, /// org-dartlang-app:///web/main.dart - /// } + /// ] /// - Future> get libraries async { + Future> get libraries async { await _initialize(); - return _libraries; + return _libraries.toList(); } /// A map of library uri to dart scripts. @@ -226,9 +226,9 @@ class MetadataProvider { /// determines deleted and invalidated libraries and modules, invalidates them /// in any caches, and recomputes the necessary information. /// - /// Returns an [ModifiedModuleReport] that can be used to invalidate other + /// Returns a [ModifiedModuleReport] that can be used to invalidate other /// caches after a hot reload. - Future reinitializeAfterReload( + Future reinitializeAfterHotReload( Map reloadedModulesToLibraries, ) async { final modules = (await _processMetadata(true))!; @@ -265,7 +265,7 @@ class MetadataProvider { ); _addMetadata(modules[module]!); } - // The libraries that were removed from the program or those that we + // The libraries that are removed from the program are those that we // invalidated but were never added again. final deletedLibraries = invalidatedLibraries @@ -332,7 +332,7 @@ class AbsoluteImportUriException implements Exception { } /// Computed after a hot reload using -/// [MetadataProvider.reinitializeAfterReload], represents the modules and +/// [MetadataProvider.reinitializeAfterHotReload], represents the modules and /// libraries in the program that were deleted, reloaded, and therefore, /// modified. /// diff --git a/dwds/lib/src/debugging/modules.dart b/dwds/lib/src/debugging/modules.dart index 8115ac4bf..1b5ed5bb0 100644 --- a/dwds/lib/src/debugging/modules.dart +++ b/dwds/lib/src/debugging/modules.dart @@ -99,7 +99,8 @@ class Modules { final serverPath = await globalToolConfiguration.loadStrategy .serverPathForModule(entrypoint, module); // TODO(srujzs): We should wait until all scripts are parsed before - // accessing. + // accessing after a hot reload. See + // https://github.com/dart-lang/webdev/issues/2640. return chromePathToRuntimeScriptId[serverPath]; } diff --git a/dwds/lib/src/debugging/skip_list.dart b/dwds/lib/src/debugging/skip_list.dart index ccdcb0f12..fec2d73cc 100644 --- a/dwds/lib/src/debugging/skip_list.dart +++ b/dwds/lib/src/debugging/skip_list.dart @@ -25,14 +25,13 @@ class SkipLists { if (modifiedModuleReport != null) { assert(entrypoint != null); for (final url in _urlToId.keys) { - if (url.isEmpty) continue; - final dartUri = DartUri(url, _root); final serverPath = dartUri.serverPath; final module = await globalToolConfiguration.loadStrategy .moduleForServerPath(entrypoint!, serverPath); if (modifiedModuleReport.modifiedModules.contains(module)) { _idToList.remove(_urlToId[url]!); + _urlToId.remove(url); } } } else { @@ -51,7 +50,6 @@ class SkipLists { String url, Set locations, ) { - _urlToId[url] = scriptId; if (_idToList.containsKey(scriptId)) return _idToList[scriptId]!; final sortedLocations = @@ -82,7 +80,10 @@ class SkipLists { } ranges.add(_rangeFor(scriptId, startLine, startColumn, maxValue, maxValue)); - _idToList[scriptId] = ranges; + if (url.isNotEmpty) { + _idToList[scriptId] = ranges; + _urlToId[url] = scriptId; + } return ranges; } diff --git a/dwds/lib/src/loaders/strategy.dart b/dwds/lib/src/loaders/strategy.dart index 1fca85746..e38cea083 100644 --- a/dwds/lib/src/loaders/strategy.dart +++ b/dwds/lib/src/loaders/strategy.dart @@ -183,12 +183,12 @@ abstract class LoadStrategy { return Future.value(); } - Future reinitializeEntrypointAfterReload( + Future reinitializeProviderAfterHotReload( String entrypoint, Map reloadedModulesToLibraries, ) { final provider = _providers[entrypoint]!; - return provider.reinitializeAfterReload(reloadedModulesToLibraries); + return provider.reinitializeAfterHotReload(reloadedModulesToLibraries); } } diff --git a/dwds/lib/src/services/chrome_proxy_service.dart b/dwds/lib/src/services/chrome_proxy_service.dart index 9763931ff..1c283c0bf 100644 --- a/dwds/lib/src/services/chrome_proxy_service.dart +++ b/dwds/lib/src/services/chrome_proxy_service.dart @@ -242,7 +242,7 @@ class ChromeProxyService implements VmServiceInterface { ) async { final entrypoint = inspector.appConnection.request.entrypointPath; final modifiedModuleReport = await globalToolConfiguration.loadStrategy - .reinitializeEntrypointAfterReload(entrypoint, reloadedModules); + .reinitializeProviderAfterHotReload(entrypoint, reloadedModules); await _initializeEntrypoint(entrypoint, modifiedModuleReport); await inspector.initialize(modifiedModuleReport); } @@ -1211,13 +1211,13 @@ class ChromeProxyService implements VmServiceInterface { // Initiate a hot reload. _logger.info('Issuing \$dartHotReloadStartDwds request'); - final reloadedModulesMap = await inspector.jsEvaluate( + final remoteObject = await inspector.jsEvaluate( '\$dartHotReloadStartDwds();', awaitPromise: true, returnByValue: true, ); - final reloadedModules = - (reloadedModulesMap.value as Map).cast(); + final reloadedModulesToLibraries = + (remoteObject.value as Map).cast(); if (!pauseIsolatesOnStart) { // Finish hot reload immediately. @@ -1252,7 +1252,7 @@ class ChromeProxyService implements VmServiceInterface { await pause(isolateId); await pausedEvent; - await _reinitializeForHotReload(reloadedModules); + await _reinitializeForHotReload(reloadedModulesToLibraries); // This lets the client know that we're ready for breakpoint management // and a resume. diff --git a/dwds/test/metadata_test.dart b/dwds/test/metadata_test.dart index f5ed38c8b..91477f888 100644 --- a/dwds/test/metadata_test.dart +++ b/dwds/test/metadata_test.dart @@ -207,7 +207,8 @@ void main() { ], }; final libraryToParts = >{ - 'l1': ['org-dartlang-app:///web/l1_p1.dart'], + 'org-dartlang-app:///web/l1.dart': ['org-dartlang-app:///web/l1_p1.dart'], + 'org-dartlang-app:///web/l3.dart': ['org-dartlang-app:///web/l3_p1.dart'], }; final assetReader = FakeAssetReader( metadata: createMetadataContents(moduleToLibraries, libraryToParts), @@ -227,8 +228,9 @@ void main() { ], }; final newLibraryToParts = >{ - 'l1': ['org-dartlang-app:///web/l1_p1.dart'], - 'l7': ['org-dartlang-app:///web/l7_p1.dart'], + 'org-dartlang-app:///web/l2.dart': ['org-dartlang-app:///web/l1_p1.dart'], + 'org-dartlang-app:///web/l3.dart': ['org-dartlang-app:///web/l3_p2.dart'], + 'org-dartlang-app:///web/l7.dart': ['org-dartlang-app:///web/l7_p1.dart'], }; final reloadedModulesToLibraries = >{ 'm3': ['org-dartlang-app:///web/l3.dart'], @@ -241,7 +243,7 @@ void main() { newModuleToLibraries, newLibraryToParts, ); - final modifiedModuleReport = await provider.reinitializeAfterReload( + final modifiedModuleReport = await provider.reinitializeAfterHotReload( reloadedModulesToLibraries, ); expect(modifiedModuleReport.deletedModules, ['m2']); From 062fbe415bcd8a384c93a51cba93f5c8bf4b8900 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Mon, 30 Jun 2025 20:43:11 -0700 Subject: [PATCH 09/13] Reorder for consistency --- dwds/lib/src/debugging/location.dart | 4 ++-- dwds/lib/src/debugging/modules.dart | 6 +++--- dwds/lib/src/debugging/skip_list.dart | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/dwds/lib/src/debugging/location.dart b/dwds/lib/src/debugging/location.dart index 79d4204f5..d525a004b 100644 --- a/dwds/lib/src/debugging/location.dart +++ b/dwds/lib/src/debugging/location.dart @@ -171,10 +171,10 @@ class Locations { } } } else { - _sourceToTokenPosTable.clear(); _locationMemoizer.clear(); - _sourceToLocation.clear(); _moduleToLocations.clear(); + _sourceToTokenPosTable.clear(); + _sourceToLocation.clear(); _entrypoint = entrypoint; } } diff --git a/dwds/lib/src/debugging/modules.dart b/dwds/lib/src/debugging/modules.dart index 1b5ed5bb0..5024b6821 100644 --- a/dwds/lib/src/debugging/modules.dart +++ b/dwds/lib/src/debugging/modules.dart @@ -54,12 +54,12 @@ class Modules { } await _initializeMapping(modifiedModuleReport); } else { - _sourceToModule.clear(); - _moduleToSources.clear(); + _entrypoint = entrypoint; _sourceToLibrary.clear(); + _sourceToModule.clear(); _libraryToModule.clear(); + _moduleToSources.clear(); _moduleMemoizer = AsyncMemoizer(); - _entrypoint = entrypoint; } } diff --git a/dwds/lib/src/debugging/skip_list.dart b/dwds/lib/src/debugging/skip_list.dart index fec2d73cc..eb061c835 100644 --- a/dwds/lib/src/debugging/skip_list.dart +++ b/dwds/lib/src/debugging/skip_list.dart @@ -36,6 +36,7 @@ class SkipLists { } } else { _idToList.clear(); + _urlToId.clear(); } } From 812d07e863d3acc5151bffe5149d0389a4a0daa4 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Tue, 1 Jul 2025 08:34:39 -0700 Subject: [PATCH 10/13] Add intiializer to LibraryHelper --- dwds/lib/src/debugging/inspector.dart | 4 +-- dwds/lib/src/debugging/libraries.dart | 42 +++++++++++++++++---------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/dwds/lib/src/debugging/inspector.dart b/dwds/lib/src/debugging/inspector.dart index 52c642e0c..1d4bcae7d 100644 --- a/dwds/lib/src/debugging/inspector.dart +++ b/dwds/lib/src/debugging/inspector.dart @@ -118,9 +118,9 @@ class AppInspector implements AppInspectorInterface { if (modifiedModuleReport != null) { // Invalidate `_libraryHelper` as we use it populate any script caches. - _libraryHelper.invalidate(modifiedModuleReport); + _libraryHelper.initialize(modifiedModuleReport); } else { - _libraryHelper = LibraryHelper(this); + _libraryHelper = LibraryHelper(this)..initialize(); _scriptRefsById.clear(); _serverPathToScriptRef.clear(); _scriptIdToLibraryId.clear(); diff --git a/dwds/lib/src/debugging/libraries.dart b/dwds/lib/src/debugging/libraries.dart index 1535f39ca..786725b09 100644 --- a/dwds/lib/src/debugging/libraries.dart +++ b/dwds/lib/src/debugging/libraries.dart @@ -17,10 +17,10 @@ class LibraryHelper extends Domain { final Logger _logger = Logger('LibraryHelper'); /// Map of library ID to [Library]. - final _librariesById = {}; + late final Map _librariesById; /// Map of libraryRef ID to [LibraryRef]. - final _libraryRefsById = {}; + late final Map _libraryRefsById; LibraryRef? _rootLib; @@ -28,6 +28,29 @@ class LibraryHelper extends Domain { inspector = appInspector; } + /// Initialize any caches. + /// + /// If [modifiedModuleReport] is not null, invalidates only modified libraries + /// from the cache and recomputes values for any eager caches. + void initialize([ModifiedModuleReport? modifiedModuleReport]) { + _rootLib = null; + if (modifiedModuleReport != null) { + for (final library in modifiedModuleReport.modifiedLibraries) { + // These will later be initialized by `libraryFor` if needed. + _librariesById.remove(library); + _libraryRefsById.remove(library); + } + for (final library in modifiedModuleReport.reloadedLibraries) { + // These need to be recomputed here as `libraryRefs` only checks if this + // map is empty before returning. + _libraryRefsById[library] = _createLibraryRef(library); + } + } else { + _librariesById = {}; + _libraryRefsById = {}; + } + } + Future get rootLib async { if (_rootLib != null) return _rootLib!; final libraries = await libraryRefs; @@ -52,26 +75,13 @@ class LibraryHelper extends Domain { return _rootLib!; } - /// Removes any modified libraries from the cache and either eagerly or lazily - /// computes values for the reloaded libraries in the [modifiedModuleReport]. - void invalidate(ModifiedModuleReport modifiedModuleReport) { - for (final library in modifiedModuleReport.modifiedLibraries) { - // These will later be initialized by `libraryFor` if needed. - _librariesById.remove(library); - _libraryRefsById.remove(library); - } - for (final library in modifiedModuleReport.reloadedLibraries) { - _libraryRefsById[library] = _createLibraryRef(library); - } - } - LibraryRef _createLibraryRef(String library) => LibraryRef(id: library, name: library, uri: library); /// Returns all libraryRefs in the app. /// /// Note this can return a cached result that can be selectively reinitialized - /// using [invalidate]. + /// using [initialize]. Future> get libraryRefs async { if (_libraryRefsById.isNotEmpty) return _libraryRefsById.values.toList(); final libraries = From 5edc9c59955f4dd576d62654e97647950fa7b1a6 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Thu, 3 Jul 2025 10:10:07 -0700 Subject: [PATCH 11/13] Address review comments --- dwds/lib/src/debugging/inspector.dart | 16 +++++----- dwds/lib/src/debugging/libraries.dart | 12 ++++---- dwds/lib/src/debugging/location.dart | 20 ++++++++----- dwds/lib/src/debugging/metadata/provider.dart | 30 ++++++++----------- dwds/lib/src/debugging/modules.dart | 18 +++++------ dwds/lib/src/debugging/skip_list.dart | 19 +++++++----- .../src/services/chrome_proxy_service.dart | 26 +++++++++++----- dwds/test/fixtures/fakes.dart | 6 ++-- 8 files changed, 81 insertions(+), 66 deletions(-) diff --git a/dwds/lib/src/debugging/inspector.dart b/dwds/lib/src/debugging/inspector.dart index 1d4bcae7d..b2fd3902e 100644 --- a/dwds/lib/src/debugging/inspector.dart +++ b/dwds/lib/src/debugging/inspector.dart @@ -35,9 +35,9 @@ import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'; class AppInspector implements AppInspectorInterface { var _scriptCacheMemoizer = AsyncMemoizer>(); - Future> getScriptRefs([ + Future> getScriptRefs({ ModifiedModuleReport? modifiedModuleReport, - ]) => _populateScriptCaches(modifiedModuleReport); + }) => _populateScriptCaches(modifiedModuleReport: modifiedModuleReport); final _logger = Logger('AppInspector'); @@ -107,7 +107,7 @@ class AppInspector implements AppInspectorInterface { /// Reset all caches and recompute any mappings. /// /// Should be called across hot reloads with a valid [ModifiedModuleReport]. - Future initialize([ModifiedModuleReport? modifiedModuleReport]) async { + Future initialize({ModifiedModuleReport? modifiedModuleReport}) async { _scriptCacheMemoizer = AsyncMemoizer>(); // TODO(srujzs): We can invalidate these in a smarter way instead of @@ -118,7 +118,7 @@ class AppInspector implements AppInspectorInterface { if (modifiedModuleReport != null) { // Invalidate `_libraryHelper` as we use it populate any script caches. - _libraryHelper.initialize(modifiedModuleReport); + _libraryHelper.initialize(modifiedModuleReport: modifiedModuleReport); } else { _libraryHelper = LibraryHelper(this)..initialize(); _scriptRefsById.clear(); @@ -132,7 +132,9 @@ class AppInspector implements AppInspectorInterface { isolate.libraries?.clear(); isolate.libraries?.addAll(libraries); - final scripts = await getScriptRefs(modifiedModuleReport); + final scripts = await getScriptRefs( + modifiedModuleReport: modifiedModuleReport, + ); await DartUri.initialize(); DartUri.recordAbsoluteUris(libraries.map((lib) => lib.uri).nonNulls); @@ -730,9 +732,9 @@ class AppInspector implements AppInspectorInterface { /// recalculates caches for the modified libraries. /// /// Returns the list of scripts refs cached. - Future> _populateScriptCaches([ + Future> _populateScriptCaches({ ModifiedModuleReport? modifiedModuleReport, - ]) { + }) { return _scriptCacheMemoizer.runOnce(() async { final scripts = await globalToolConfiguration.loadStrategy diff --git a/dwds/lib/src/debugging/libraries.dart b/dwds/lib/src/debugging/libraries.dart index 786725b09..150280b77 100644 --- a/dwds/lib/src/debugging/libraries.dart +++ b/dwds/lib/src/debugging/libraries.dart @@ -17,10 +17,10 @@ class LibraryHelper extends Domain { final Logger _logger = Logger('LibraryHelper'); /// Map of library ID to [Library]. - late final Map _librariesById; + final Map _librariesById = {}; /// Map of libraryRef ID to [LibraryRef]. - late final Map _libraryRefsById; + final Map _libraryRefsById = {}; LibraryRef? _rootLib; @@ -32,7 +32,7 @@ class LibraryHelper extends Domain { /// /// If [modifiedModuleReport] is not null, invalidates only modified libraries /// from the cache and recomputes values for any eager caches. - void initialize([ModifiedModuleReport? modifiedModuleReport]) { + void initialize({ModifiedModuleReport? modifiedModuleReport}) { _rootLib = null; if (modifiedModuleReport != null) { for (final library in modifiedModuleReport.modifiedLibraries) { @@ -45,10 +45,10 @@ class LibraryHelper extends Domain { // map is empty before returning. _libraryRefsById[library] = _createLibraryRef(library); } - } else { - _librariesById = {}; - _libraryRefsById = {}; + return; } + _librariesById.clear(); + _libraryRefsById.clear(); } Future get rootLib async { diff --git a/dwds/lib/src/debugging/location.dart b/dwds/lib/src/debugging/location.dart index d525a004b..d983f280f 100644 --- a/dwds/lib/src/debugging/location.dart +++ b/dwds/lib/src/debugging/location.dart @@ -152,10 +152,14 @@ class Locations { Modules get modules => _modules; + /// Initialize any caches. + /// + /// If [modifiedModuleReport] is not null, only invalidates the caches for the + /// modified modules instead. Future initialize( - String entrypoint, [ + String entrypoint, { ModifiedModuleReport? modifiedModuleReport, - ]) async { + }) async { // If we know that only certain modules are deleted or added, we can only // invalidate those. if (modifiedModuleReport != null) { @@ -170,13 +174,13 @@ class Locations { } } } - } else { - _locationMemoizer.clear(); - _moduleToLocations.clear(); - _sourceToTokenPosTable.clear(); - _sourceToLocation.clear(); - _entrypoint = entrypoint; + return; } + _locationMemoizer.clear(); + _moduleToLocations.clear(); + _sourceToTokenPosTable.clear(); + _sourceToLocation.clear(); + _entrypoint = entrypoint; } /// Returns all [Location] data for a provided Dart source. diff --git a/dwds/lib/src/debugging/metadata/provider.dart b/dwds/lib/src/debugging/metadata/provider.dart index 060060424..aee5ae032 100644 --- a/dwds/lib/src/debugging/metadata/provider.dart +++ b/dwds/lib/src/debugging/metadata/provider.dart @@ -170,14 +170,10 @@ class MetadataProvider { return _moduleToModulePath.keys.toList(); } - /// Compute metadata information after reading the metadata contents. - /// - /// If [hotReload] is true, skips adding the SDK metadata and caches the - /// computed [ModuleMetadata], returning the resulting cache. - /// - /// Otherwise, adds all metadata and returns null. - Future?> _processMetadata(bool hotReload) async { - final modules = hotReload ? {} : null; + /// Compute metadata information after reading the metadata contents and + /// return a map from module names to their [ModuleMetadata]. + Future> _processMetadata() async { + final modules = {}; // The merged metadata resides next to the entrypoint. // Assume that .bootstrap.js has .ddc_merged_metadata if (entrypoint.endsWith('.bootstrap.js')) { @@ -188,8 +184,6 @@ class MetadataProvider { ); final merged = await _assetReader.metadataContents(serverPath); if (merged != null) { - // We can't hot reload the SDK yet, so no need to invalidate this data. - if (!hotReload) _addSdkMetadata(); for (final contents in merged.split('\n')) { try { if (contents.isEmpty || @@ -201,11 +195,7 @@ class MetadataProvider { moduleJson as Map, ); final moduleName = metadata.name; - if (hotReload) { - modules![moduleName] = metadata; - } else { - _addMetadata(metadata); - } + modules[moduleName] = metadata; _logger.fine('Loaded debug metadata for module: $moduleName'); } catch (e) { _logger.warning('Failed to read metadata: $e'); @@ -217,9 +207,13 @@ class MetadataProvider { return modules; } - /// Process all metadata and compute caches once. + /// Process all metadata, including SDK metadata, and compute caches once. Future _initialize() async { - await _metadataMemoizer.runOnce(() => _processMetadata(false)); + await _metadataMemoizer.runOnce(() async { + final metadata = await _processMetadata(); + _addSdkMetadata(); + metadata.values.forEach(_addMetadata); + }); } /// Given a map of hot reloaded modules mapped to their respective libraries, @@ -231,7 +225,7 @@ class MetadataProvider { Future reinitializeAfterHotReload( Map reloadedModulesToLibraries, ) async { - final modules = (await _processMetadata(true))!; + final modules = await _processMetadata(); final invalidatedLibraries = {}; void invalidateLibrary(String libraryImportUri) { invalidatedLibraries.add(libraryImportUri); diff --git a/dwds/lib/src/debugging/modules.dart b/dwds/lib/src/debugging/modules.dart index 5024b6821..716a76a61 100644 --- a/dwds/lib/src/debugging/modules.dart +++ b/dwds/lib/src/debugging/modules.dart @@ -38,9 +38,9 @@ class Modules { /// If [modifiedModuleReport] is not null, removes and recalculates caches for /// any modified modules and libraries. Future initialize( - String entrypoint, [ + String entrypoint, { ModifiedModuleReport? modifiedModuleReport, - ]) async { + }) async { if (modifiedModuleReport != null) { assert(_entrypoint == entrypoint); for (final library in modifiedModuleReport.modifiedLibraries) { @@ -53,14 +53,14 @@ class Modules { _moduleToSources.remove(module); } await _initializeMapping(modifiedModuleReport); - } else { - _entrypoint = entrypoint; - _sourceToLibrary.clear(); - _sourceToModule.clear(); - _libraryToModule.clear(); - _moduleToSources.clear(); - _moduleMemoizer = AsyncMemoizer(); + return; } + _entrypoint = entrypoint; + _sourceToLibrary.clear(); + _sourceToModule.clear(); + _libraryToModule.clear(); + _moduleToSources.clear(); + _moduleMemoizer = AsyncMemoizer(); } /// Returns the containing module for the provided Dart server path. diff --git a/dwds/lib/src/debugging/skip_list.dart b/dwds/lib/src/debugging/skip_list.dart index eb061c835..7b5f94291 100644 --- a/dwds/lib/src/debugging/skip_list.dart +++ b/dwds/lib/src/debugging/skip_list.dart @@ -18,26 +18,29 @@ class SkipLists { SkipLists(this._root); - Future initialize([ - String? entrypoint, + /// Initialize any caches. + /// + /// If [modifiedModuleReport] is not null, only invalidates the caches for the + /// modified modules instead. + Future initialize( + String entrypoint, { ModifiedModuleReport? modifiedModuleReport, - ]) async { + }) async { if (modifiedModuleReport != null) { - assert(entrypoint != null); for (final url in _urlToId.keys) { final dartUri = DartUri(url, _root); final serverPath = dartUri.serverPath; final module = await globalToolConfiguration.loadStrategy - .moduleForServerPath(entrypoint!, serverPath); + .moduleForServerPath(entrypoint, serverPath); if (modifiedModuleReport.modifiedModules.contains(module)) { _idToList.remove(_urlToId[url]!); _urlToId.remove(url); } } - } else { - _idToList.clear(); - _urlToId.clear(); + return; } + _idToList.clear(); + _urlToId.clear(); } /// Returns a skipList as defined by the Chrome DevTools Protocol. diff --git a/dwds/lib/src/services/chrome_proxy_service.dart b/dwds/lib/src/services/chrome_proxy_service.dart index 1c283c0bf..c98bbc8ab 100644 --- a/dwds/lib/src/services/chrome_proxy_service.dart +++ b/dwds/lib/src/services/chrome_proxy_service.dart @@ -243,8 +243,11 @@ class ChromeProxyService implements VmServiceInterface { final entrypoint = inspector.appConnection.request.entrypointPath; final modifiedModuleReport = await globalToolConfiguration.loadStrategy .reinitializeProviderAfterHotReload(entrypoint, reloadedModules); - await _initializeEntrypoint(entrypoint, modifiedModuleReport); - await inspector.initialize(modifiedModuleReport); + await _initializeEntrypoint( + entrypoint, + modifiedModuleReport: modifiedModuleReport, + ); + await inspector.initialize(modifiedModuleReport: modifiedModuleReport); } /// Initializes metadata in [Locations], [Modules], and [ExpressionCompiler]. @@ -252,12 +255,21 @@ class ChromeProxyService implements VmServiceInterface { /// If [modifiedModuleReport] is not null, only removes and reinitializes /// modified metadata. Future _initializeEntrypoint( - String entrypoint, [ + String entrypoint, { ModifiedModuleReport? modifiedModuleReport, - ]) async { - await _modules.initialize(entrypoint, modifiedModuleReport); - await _locations.initialize(entrypoint, modifiedModuleReport); - await _skipLists.initialize(entrypoint, modifiedModuleReport); + }) async { + await _modules.initialize( + entrypoint, + modifiedModuleReport: modifiedModuleReport, + ); + await _locations.initialize( + entrypoint, + modifiedModuleReport: modifiedModuleReport, + ); + await _skipLists.initialize( + entrypoint, + modifiedModuleReport: modifiedModuleReport, + ); // We do not need to wait for compiler dependencies to be updated as the // [ExpressionEvaluator] is robust to evaluation requests during updates. safeUnawaited(_updateCompilerDependencies(entrypoint)); diff --git a/dwds/test/fixtures/fakes.dart b/dwds/test/fixtures/fakes.dart index 38e6a4e64..ce1adf000 100644 --- a/dwds/test/fixtures/fakes.dart +++ b/dwds/test/fixtures/fakes.dart @@ -68,7 +68,7 @@ class FakeInspector implements AppInspector { } @override - Future initialize([ModifiedModuleReport? modifiedModuleReport]) async => + Future initialize({ModifiedModuleReport? modifiedModuleReport}) async => {}; @override @@ -159,9 +159,9 @@ class FakeModules implements Modules { @override Future initialize( - String entrypoint, [ + String entrypoint, { ModifiedModuleReport? modifiedModuleReport, - ]) async {} + }) async {} @override Future libraryForSource(String serverPath) async => Uri(path: _library); From 85e3c35dfb958c6dc9ea78a9f117a33747983aaa Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Thu, 3 Jul 2025 10:15:34 -0700 Subject: [PATCH 12/13] Publish 24.4.0 --- dwds/CHANGELOG.md | 2 +- dwds/lib/src/version.dart | 2 +- dwds/pubspec.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dwds/CHANGELOG.md b/dwds/CHANGELOG.md index 1ddf798c4..17f414c11 100644 --- a/dwds/CHANGELOG.md +++ b/dwds/CHANGELOG.md @@ -1,4 +1,4 @@ -## 24.4.0-wip +## 24.4.0 - Added support for breakpoint registering on a hot reload with the DDC library bundle format using PausePostRequests. - `FrontendServerDdcLibraryBundleStrategy.hotReloadSourceUri` is now expected to also provide the reloaded modules. diff --git a/dwds/lib/src/version.dart b/dwds/lib/src/version.dart index 588ff7d61..2786a7b0a 100644 --- a/dwds/lib/src/version.dart +++ b/dwds/lib/src/version.dart @@ -1,2 +1,2 @@ // Generated code. Do not modify. -const packageVersion = '24.4.0-wip'; +const packageVersion = '24.4.0'; diff --git a/dwds/pubspec.yaml b/dwds/pubspec.yaml index 7c0b832fc..f139974a6 100644 --- a/dwds/pubspec.yaml +++ b/dwds/pubspec.yaml @@ -1,6 +1,6 @@ name: dwds # Every time this changes you need to run `dart run build_runner build`. -version: 24.4.0-wip +version: 24.4.0 description: >- A service that proxies between the Chrome debug protocol and the Dart VM From beea7dfaef5343af44093e337161e04c512729f1 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Fri, 4 Jul 2025 11:19:10 -0700 Subject: [PATCH 13/13] More review comments --- dwds/lib/src/debugging/inspector.dart | 4 +- dwds/lib/src/debugging/libraries.dart | 4 +- dwds/lib/src/debugging/metadata/provider.dart | 4 +- dwds/lib/src/debugging/modules.dart | 4 +- dwds/test/fixtures/fakes.dart | 3 +- dwds/test/metadata_test.dart | 61 +++++++++---------- 6 files changed, 38 insertions(+), 42 deletions(-) diff --git a/dwds/lib/src/debugging/inspector.dart b/dwds/lib/src/debugging/inspector.dart index b2fd3902e..25cf1b61a 100644 --- a/dwds/lib/src/debugging/inspector.dart +++ b/dwds/lib/src/debugging/inspector.dart @@ -746,6 +746,7 @@ class AppInspector implements AppInspectorInterface { for (final libraryUri in modifiedModuleReport.modifiedLibraries) { final libraryRef = await _libraryHelper.libraryRefFor(libraryUri); final libraryId = libraryRef?.id; + // If this was not a pre-existing library, nothing to invalidate. if (libraryId == null) continue; final scriptRefs = _libraryIdToScriptRefs.remove(libraryId); if (scriptRefs == null) continue; @@ -768,8 +769,7 @@ class AppInspector implements AppInspectorInterface { isolate.libraries ?? [], ); for (final uri in userLibraries) { - if (modifiedModuleReport != null && - !modifiedModuleReport.modifiedLibraries.contains(uri)) { + if (modifiedModuleReport?.modifiedLibraries.contains(uri) == false) { continue; } final parts = scripts[uri]; diff --git a/dwds/lib/src/debugging/libraries.dart b/dwds/lib/src/debugging/libraries.dart index 150280b77..1dc07a4c8 100644 --- a/dwds/lib/src/debugging/libraries.dart +++ b/dwds/lib/src/debugging/libraries.dart @@ -17,10 +17,10 @@ class LibraryHelper extends Domain { final Logger _logger = Logger('LibraryHelper'); /// Map of library ID to [Library]. - final Map _librariesById = {}; + final _librariesById = {}; /// Map of libraryRef ID to [LibraryRef]. - final Map _libraryRefsById = {}; + final _libraryRefsById = {}; LibraryRef? _rootLib; diff --git a/dwds/lib/src/debugging/metadata/provider.dart b/dwds/lib/src/debugging/metadata/provider.dart index aee5ae032..86fb5e730 100644 --- a/dwds/lib/src/debugging/metadata/provider.dart +++ b/dwds/lib/src/debugging/metadata/provider.dart @@ -344,10 +344,10 @@ class ModifiedModuleReport { /// Library uris that were loaded during the hot reload. final Set reloadedLibraries; - /// Module names that were either removed or modified. + /// Module names that were either removed or modified, including additions. final Set modifiedModules; - /// Library uris that were either removed or modified. + /// Library uris that were either removed or modified, including additions. final Set modifiedLibraries; ModifiedModuleReport({ required this.deletedModules, diff --git a/dwds/lib/src/debugging/modules.dart b/dwds/lib/src/debugging/modules.dart index 716a76a61..15f10b06a 100644 --- a/dwds/lib/src/debugging/modules.dart +++ b/dwds/lib/src/debugging/modules.dart @@ -124,10 +124,10 @@ class Modules { final scriptToModule = await provider.scriptToModule; for (final library in libraryToScripts.keys) { - if (modifiedModuleReport != null) { + if (modifiedModuleReport?.modifiedLibraries.contains(library) == false) { // Note that every module will have at least one library associated with // it, so it's okay to only process the modified libraries. - if (!modifiedModuleReport.modifiedLibraries.contains(library)) continue; + continue; } final scripts = libraryToScripts[library]!; final libraryServerPath = _getLibraryServerPath(library); diff --git a/dwds/test/fixtures/fakes.dart b/dwds/test/fixtures/fakes.dart index ce1adf000..2b021101d 100644 --- a/dwds/test/fixtures/fakes.dart +++ b/dwds/test/fixtures/fakes.dart @@ -68,8 +68,7 @@ class FakeInspector implements AppInspector { } @override - Future initialize({ModifiedModuleReport? modifiedModuleReport}) async => - {}; + Future initialize({ModifiedModuleReport? modifiedModuleReport}) async {} @override Future instanceRefFor(Object value) async => diff --git a/dwds/test/metadata_test.dart b/dwds/test/metadata_test.dart index 91477f888..50d12c8e1 100644 --- a/dwds/test/metadata_test.dart +++ b/dwds/test/metadata_test.dart @@ -122,15 +122,16 @@ void main() { Map> moduleToLibraries, Map> libraryToParts, ) { - final contents = StringBuffer(''); - for (final module in moduleToLibraries.keys) { + final contents = StringBuffer(); + for (final MapEntry(key: module, value: libraries) + in moduleToLibraries.entries) { final moduleMetadata = ModuleMetadata( module, 'load__web__$module', 'foo/web/$module.ddc.js.map', 'foo/web/$module.ddc.js', ); - for (final library in moduleToLibraries[module]!) { + for (final library in libraries) { moduleMetadata.addLibrary( LibraryMetadata(library, library, libraryToParts[library] ?? []), ); @@ -146,10 +147,12 @@ void main() { Map> moduleToLibraries, Map> libraryToParts, ) async { - final scriptToModule = await provider.scriptToModule; final expectedScriptToModule = {}; - for (final module in moduleToLibraries.keys) { - final libraries = moduleToLibraries[module]!; + final expectedModuleToSourceMap = {}; + final expectedModulePathToModule = {}; + final expectedModules = {}; + for (final MapEntry(key: module, value: libraries) + in moduleToLibraries.entries) { for (final library in libraries) { expectedScriptToModule[library] = module; final parts = libraryToParts[library]; @@ -159,40 +162,34 @@ void main() { } } } + expectedModuleToSourceMap[module] = 'foo/web/$module.ddc.js.map'; + expectedModulePathToModule['foo/web/$module.ddc.js'] = module; + expectedModules.add(module); } - for (final entry in expectedScriptToModule.entries) { - expect(scriptToModule[entry.key], entry.value); + + final scriptToModule = await provider.scriptToModule; + for (final MapEntry(key: script, value: module) + in expectedScriptToModule.entries) { + expect(scriptToModule[script], module); } final moduleToSourceMap = await provider.moduleToSourceMap; - final expectedModuleToSourceMap = moduleToLibraries.keys.fold( - {}, - (map, module) { - map[module] = 'foo/web/$module.ddc.js.map'; - return map; - }, - ); - for (final entry in expectedModuleToSourceMap.entries) { - expect(moduleToSourceMap[entry.key], entry.value); + for (final MapEntry(key: module, value: sourceMap) + in expectedModuleToSourceMap.entries) { + expect(moduleToSourceMap[module], sourceMap); } final modulePathToModule = await provider.modulePathToModule; - final expectedModulePathToModule = moduleToLibraries.keys.fold( - {}, - (map, module) { - map['foo/web/$module.ddc.js'] = module; - return map; - }, - ); - for (final entry in expectedModulePathToModule.entries) { - expect(modulePathToModule[entry.key], entry.value); + for (final MapEntry(key: modulePath, value: module) + in expectedModulePathToModule.entries) { + expect(modulePathToModule[modulePath], module); } - expect(await provider.modules, containsAll(moduleToLibraries.keys)); + expect(await provider.modules, containsAll(expectedModules)); } test('reinitialize produces correct ModifiedModuleReport', () async { - final moduleToLibraries = >{ + const moduleToLibraries = >{ 'm1': [ 'org-dartlang-app:///web/l1.dart', 'org-dartlang-app:///web/l2.dart', @@ -206,7 +203,7 @@ void main() { 'org-dartlang-app:///web/l6.dart', ], }; - final libraryToParts = >{ + const libraryToParts = >{ 'org-dartlang-app:///web/l1.dart': ['org-dartlang-app:///web/l1_p1.dart'], 'org-dartlang-app:///web/l3.dart': ['org-dartlang-app:///web/l3_p1.dart'], }; @@ -216,7 +213,7 @@ void main() { final provider = MetadataProvider('foo.bootstrap.js', assetReader); await validateProvider(provider, moduleToLibraries, libraryToParts); - final newModuleToLibraries = >{ + const newModuleToLibraries = >{ 'm1': [ 'org-dartlang-app:///web/l1.dart', 'org-dartlang-app:///web/l2.dart', @@ -227,12 +224,12 @@ void main() { 'org-dartlang-app:///web/l7.dart', ], }; - final newLibraryToParts = >{ + const newLibraryToParts = >{ 'org-dartlang-app:///web/l2.dart': ['org-dartlang-app:///web/l1_p1.dart'], 'org-dartlang-app:///web/l3.dart': ['org-dartlang-app:///web/l3_p2.dart'], 'org-dartlang-app:///web/l7.dart': ['org-dartlang-app:///web/l7_p1.dart'], }; - final reloadedModulesToLibraries = >{ + const reloadedModulesToLibraries = >{ 'm3': ['org-dartlang-app:///web/l3.dart'], 'm4': [ 'org-dartlang-app:///web/l4.dart',