diff --git a/dwds/CHANGELOG.md b/dwds/CHANGELOG.md index d84f0e4b5..499511c16 100644 --- a/dwds/CHANGELOG.md +++ b/dwds/CHANGELOG.md @@ -1,4 +1,6 @@ -## 19.0.3-wip +## 20.0.0-wip + +- Require clients to specify the `basePath` on `AssetReader`. - [#2160](https://github.com/dart-lang/webdev/pull/2160) ## 19.0.2 diff --git a/dwds/lib/src/handlers/dev_handler.dart b/dwds/lib/src/handlers/dev_handler.dart index d98d73deb..8cc86e3c7 100644 --- a/dwds/lib/src/handlers/dev_handler.dart +++ b/dwds/lib/src/handlers/dev_handler.dart @@ -23,7 +23,6 @@ import 'package:dwds/src/dwds_vm_client.dart'; import 'package:dwds/src/events.dart'; import 'package:dwds/src/handlers/injector.dart'; import 'package:dwds/src/handlers/socket_connections.dart'; -import 'package:dwds/src/loaders/require.dart'; import 'package:dwds/src/readers/asset_reader.dart'; import 'package:dwds/src/servers/devtools.dart'; import 'package:dwds/src/servers/extension_backend.dart'; @@ -196,7 +195,6 @@ class DevHandler { 'localhost', webkitDebugger, executionContext, - basePathForServerUri(appTab.url), _assetReader, appConnection, _urlEncoder, @@ -587,7 +585,6 @@ class DevHandler { _hostname, extensionDebugger, executionContext, - basePathForServerUri(tabUrl), _assetReader, connection, _urlEncoder, diff --git a/dwds/lib/src/loaders/frontend_server_require.dart b/dwds/lib/src/loaders/frontend_server_require.dart index 485528809..8de6262b5 100644 --- a/dwds/lib/src/loaders/frontend_server_require.dart +++ b/dwds/lib/src/loaders/frontend_server_require.dart @@ -36,9 +36,8 @@ class FrontendServerRequireStrategyProvider { this._assetReader, this._packageUriMapper, this._digestsProvider, - this._basePath, this._appEntrypoint, - ); + ) : _basePath = _assetReader.basePath; RequireStrategy get strategy => _requireStrategy; diff --git a/dwds/lib/src/loaders/require.dart b/dwds/lib/src/loaders/require.dart index a8b0f1e68..5d9d8f78b 100644 --- a/dwds/lib/src/loaders/require.dart +++ b/dwds/lib/src/loaders/require.dart @@ -11,17 +11,6 @@ import 'package:dwds/src/services/expression_compiler.dart'; import 'package:path/path.dart' as p; import 'package:shelf/shelf.dart'; -/// Find the path we are serving from the url. -/// -/// Example: -/// https://localhost/base/index.html => base -/// https://localhost/base => base -String basePathForServerUri(String url) { - final uri = Uri.parse(url); - var base = uri.path.endsWith('.html') ? p.dirname(uri.path) : uri.path; - return base = base.startsWith('/') ? base.substring(1) : base; -} - String removeJsExtension(String path) => path.endsWith('.js') ? p.withoutExtension(path) : path; diff --git a/dwds/lib/src/readers/asset_reader.dart b/dwds/lib/src/readers/asset_reader.dart index 98fac9a50..896eb527e 100644 --- a/dwds/lib/src/readers/asset_reader.dart +++ b/dwds/lib/src/readers/asset_reader.dart @@ -11,6 +11,18 @@ typedef UrlEncoder = Future Function(String url); /// A reader for Dart sources and related source maps. abstract class AssetReader { + /// Base path of the application, for example, set up in the index file: + /// + /// ``` + /// + /// + /// + /// + /// + /// + /// ``` + String get basePath; + /// Returns the contents for a source map at the provided server path, or /// null if the resource does not exist. Future sourceMapContents(String serverPath); diff --git a/dwds/lib/src/readers/frontend_server_asset_reader.dart b/dwds/lib/src/readers/frontend_server_asset_reader.dart index d9cb90912..d2f8c32d7 100644 --- a/dwds/lib/src/readers/frontend_server_asset_reader.dart +++ b/dwds/lib/src/readers/frontend_server_asset_reader.dart @@ -20,6 +20,7 @@ class FrontendServerAssetReader implements AssetReader { final File _jsonIncremental; final String _packageRoot; final Future _packageConfig; + final String _basePath; /// Map of Dart module server path to source map contents. final _mapContents = {}; @@ -37,19 +38,25 @@ class FrontendServerAssetReader implements AssetReader { /// /// [_packageRoot] is the path to the directory that contains a /// `.dart_tool/package_config.json` file for the application. - FrontendServerAssetReader( - String outputPath, - this._packageRoot, - ) : _mapOriginal = File('$outputPath.map'), + FrontendServerAssetReader({ + required String outputPath, + required String packageRoot, + String? basePath, + }) : _packageRoot = packageRoot, + _basePath = basePath ?? '', + _mapOriginal = File('$outputPath.map'), _mapIncremental = File('$outputPath.incremental.map'), _jsonOriginal = File('$outputPath.json'), _jsonIncremental = File('$outputPath.incremental.json'), _packageConfig = loadPackageConfig( File( - p.absolute(p.join(_packageRoot, '.dart_tool/package_config.json')), + p.absolute(p.join(packageRoot, '.dart_tool/package_config.json')), ), ); + @override + String get basePath => _basePath; + @override Future dartSourceContents(String serverPath) async { if (serverPath.endsWith('.dart')) { diff --git a/dwds/lib/src/readers/proxy_server_asset_reader.dart b/dwds/lib/src/readers/proxy_server_asset_reader.dart index 48ba343ce..c36c800e3 100644 --- a/dwds/lib/src/readers/proxy_server_asset_reader.dart +++ b/dwds/lib/src/readers/proxy_server_asset_reader.dart @@ -38,6 +38,9 @@ class ProxyServerAssetReader implements AssetReader { _handler = proxyHandler(url, client: _client); } + @override + String get basePath => ''; + @override Future dartSourceContents(String serverPath) => _readResource(serverPath); diff --git a/dwds/lib/src/services/debug_service.dart b/dwds/lib/src/services/debug_service.dart index 96393a967..a0f8fae99 100644 --- a/dwds/lib/src/services/debug_service.dart +++ b/dwds/lib/src/services/debug_service.dart @@ -220,7 +220,6 @@ class DebugService { String hostname, RemoteDebugger remoteDebugger, ExecutionContext executionContext, - String root, AssetReader assetReader, AppConnection appConnection, UrlEncoder? urlEncoder, { @@ -230,6 +229,7 @@ class DebugService { bool useSse = false, ExpressionCompiler? expressionCompiler, }) async { + final root = assetReader.basePath; final chromeProxyService = await ChromeProxyService.create( remoteDebugger, root, diff --git a/dwds/lib/src/version.dart b/dwds/lib/src/version.dart index a4907f485..8892ec587 100644 --- a/dwds/lib/src/version.dart +++ b/dwds/lib/src/version.dart @@ -1,2 +1,2 @@ // Generated code. Do not modify. -const packageVersion = '19.0.3-wip'; +const packageVersion = '20.0.0-wip'; diff --git a/dwds/pubspec.yaml b/dwds/pubspec.yaml index 467f8c497..2f62a60ec 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: 19.0.3-wip +version: 20.0.0-wip description: >- A service that proxies between the Chrome debug protocol and the Dart VM service protocol. diff --git a/dwds/test/evaluate_common.dart b/dwds/test/evaluate_common.dart index f8aa7b728..0354eef59 100644 --- a/dwds/test/evaluate_common.dart +++ b/dwds/test/evaluate_common.dart @@ -155,11 +155,15 @@ void testAll({ scope: scope, ); - getInstanceRef(frame, expr, {scope}) async => await evaluateInFrame( - frame, - expr, - scope: scope, - ) as InstanceRef; + getInstanceRef(frame, expr, {scope}) async { + final result = await evaluateInFrame( + frame, + expr, + scope: scope, + ); + expect(result, isA()); + return result as InstanceRef; + } getInstance(InstanceRef ref) async => await context.service.getObject(isolateId, ref.id!) as Instance; @@ -675,12 +679,15 @@ void testAll({ libraryId, expr, { scope, - }) async => - await evaluate( - libraryId, - expr, - scope: scope, - ) as InstanceRef; + }) async { + final result = await evaluate( + libraryId, + expr, + scope: scope, + ); + expect(result, isA()); + return result as InstanceRef; + } String getRootLibraryId() { expect(isolate.rootLib, isNotNull); diff --git a/dwds/test/fixtures/context.dart b/dwds/test/fixtures/context.dart index fe010249d..c6285bfb6 100644 --- a/dwds/test/fixtures/context.dart +++ b/dwds/test/fixtures/context.dart @@ -211,6 +211,7 @@ class TestContext { Stream buildResults; RequireStrategy requireStrategy; String basePath = ''; + String filePathToServe = project.filePathToServe; _port = await findUnusedPort(); switch (compilationMode) { @@ -279,7 +280,12 @@ class TestContext { break; case CompilationMode.frontendServer: { - _logger.info('Index: ${project.filePathToServe}'); + filePathToServe = webCompatiblePath([ + project.directoryToServe, + project.filePathToServe, + ]); + + _logger.info('Serving: $filePathToServe'); final entry = p.toUri( p.join(project.webAssetsPath, project.dartEntryFileName), @@ -311,7 +317,7 @@ class TestContext { fileSystem, hostname, assetServerPort, - p.join(project.directoryToServe, project.filePathToServe), + filePathToServe, ); if (enableExpressionEvaluation) { @@ -326,7 +332,6 @@ class TestContext { assetReader, packageUriMapper, () async => {}, - basePath, project.dartEntryFilePackageUri, ).strategy; @@ -393,8 +398,8 @@ class TestContext { ); _appUrl = basePath.isEmpty - ? 'http://localhost:$port/${project.filePathToServe}' - : 'http://localhost:$port/$basePath/${project.filePathToServe}'; + ? 'http://localhost:$port/$filePathToServe' + : 'http://localhost:$port/$basePath/$filePathToServe'; if (launchChrome) { await _webDriver?.get(appUrl); diff --git a/dwds/test/fixtures/fakes.dart b/dwds/test/fixtures/fakes.dart index fe1f93330..5274a06af 100644 --- a/dwds/test/fixtures/fakes.dart +++ b/dwds/test/fixtures/fakes.dart @@ -393,6 +393,9 @@ class FakeAssetReader implements AssetReader { _dartSource = dartSource, _sourceMap = sourceMap; + @override + String get basePath => ''; + @override Future dartSourceContents(String serverPath) { return _throwUnimplementedOrReturnContents(_dartSource); diff --git a/dwds/test/instances/instance_test.dart b/dwds/test/instances/instance_test.dart index 7c440529f..dec094009 100644 --- a/dwds/test/instances/instance_test.dart +++ b/dwds/test/instances/instance_test.dart @@ -19,301 +19,337 @@ import '../fixtures/project.dart'; void main() { // Enable verbose logging for debugging. final debug = false; - final provider = TestSdkConfigurationProvider(); + + final provider = TestSdkConfigurationProvider(verbose: debug); tearDownAll(provider.dispose); + for (var compilationMode in [CompilationMode.frontendServer]) { + _runTests( + provider: provider, + compilationMode: compilationMode, + debug: debug, + ); + } +} + +void _runTests({ + required TestSdkConfigurationProvider provider, + required CompilationMode compilationMode, + required bool debug, +}) { final context = TestContext(TestProject.testScopesWithSoundNullSafety, provider); late AppInspector inspector; - setUpAll(() async { - setCurrentLogWriter(debug: debug); - await context.setUp(); - final chromeProxyService = context.service; - inspector = chromeProxyService.inspector; - }); - - tearDownAll(() async { - await context.tearDown(); - }); - - final url = 'org-dartlang-app:///example/scopes/main.dart'; - - String libraryVariableExpression(String variable) => - '${globalLoadStrategy.loadModuleSnippet}("dart_sdk").dart.getModuleLibraries("example/scopes/main")' - '["$url"]["$variable"];'; - - String interceptorsNewExpression(String type) => - "require('dart_sdk')._interceptors.$type['_#new#tearOff']()"; - - /// A reference to the the variable `libraryPublicFinal`, an instance of - /// `MyTestClass`. - Future libraryPublicFinal() => - inspector.jsEvaluate(libraryVariableExpression('libraryPublicFinal')); - - /// A reference to the the variable `libraryPublic`, a List of Strings. - Future libraryPublic() => - inspector.jsEvaluate(libraryVariableExpression('libraryPublic')); - - group('instanceRef', () { - setUp(() => setCurrentLogWriter(debug: debug)); - - test('for a null', () async { - final remoteObject = await libraryPublicFinal(); - final nullVariable = await inspector.loadField(remoteObject, 'notFinal'); - final ref = await inspector.instanceRefFor(nullVariable); - expect(ref!.valueAsString, 'null'); - expect(ref.kind, InstanceKind.kNull); - final classRef = ref.classRef!; - expect(classRef.name, 'Null'); - expect(classRef.id, 'classes|dart:core|Null'); - expect(inspector.isDisplayableObject(ref), isTrue); - }); - - test('for a double', () async { - final remoteObject = await libraryPublicFinal(); - final count = await inspector.loadField(remoteObject, 'count'); - final ref = await inspector.instanceRefFor(count); - expect(ref!.valueAsString, '0'); - expect(ref.kind, InstanceKind.kDouble); - final classRef = ref.classRef!; - expect(classRef.name, 'Double'); - expect(classRef.id, 'classes|dart:core|Double'); - expect(inspector.isDisplayableObject(ref), isTrue); - }); - - test('for a class', () async { - final remoteObject = await libraryPublicFinal(); - final count = await inspector.loadField(remoteObject, 'myselfField'); - final ref = await inspector.instanceRefFor(count); - expect(ref!.kind, InstanceKind.kPlainInstance); - final classRef = ref.classRef!; - expect(classRef.name, 'MyTestClass'); - expect( - classRef.id, - 'classes|org-dartlang-app:///example/scopes/main.dart' - '|MyTestClass'); - expect(inspector.isDisplayableObject(ref), isTrue); - }); - - test('for closure', () async { - final remoteObject = await libraryPublicFinal(); - final properties = await inspector.getProperties(remoteObject.objectId!); - final closure = - properties.firstWhere((property) => property.name == 'closure'); - final ref = await inspector.instanceRefFor(closure.value!); - final functionName = ref!.closureFunction!.name; - // Older SDKs do not contain function names - if (functionName != 'Closure') { - expect(functionName, 'someFunction'); - } - expect(ref.kind, InstanceKind.kClosure); - expect(inspector.isDisplayableObject(ref), isTrue); - }); - - test('for a list', () async { - final remoteObject = await libraryPublic(); - final ref = await inspector.instanceRefFor(remoteObject); - expect(ref!.length, greaterThan(0)); - expect(ref.kind, InstanceKind.kList); - expect(ref.classRef!.name, 'List'); - expect(inspector.isDisplayableObject(ref), isTrue); - }); - - test('for map', () async { - final remoteObject = - await inspector.jsEvaluate(libraryVariableExpression('map')); - final ref = await inspector.instanceRefFor(remoteObject); - expect(ref!.length, 2); - expect(ref.kind, InstanceKind.kMap); - expect(ref.classRef!.name, 'LinkedMap'); - expect(inspector.isDisplayableObject(ref), isTrue); - }); - - test('for an IdentityMap', () async { - final remoteObject = - await inspector.jsEvaluate(libraryVariableExpression('identityMap')); - final ref = await inspector.instanceRefFor(remoteObject); - expect(ref!.length, 2); - expect(ref.kind, InstanceKind.kMap); - expect(ref.classRef!.name, 'IdentityMap'); - expect(inspector.isDisplayableObject(ref), isTrue); - }); - - test('for a native JavaScript error', () async { - final remoteObject = - await inspector.jsEvaluate(interceptorsNewExpression('NativeError')); - final ref = await inspector.instanceRefFor(remoteObject); - expect(ref!.kind, InstanceKind.kPlainInstance); - expect(ref.classRef!.name, 'NativeError'); - expect(inspector.isDisplayableObject(ref), isFalse); - expect(inspector.isNativeJsError(ref), isTrue); - expect(inspector.isNativeJsObject(ref), isFalse); - }); - - test('for a native JavaScript type error', () async { - final remoteObject = await inspector - .jsEvaluate(interceptorsNewExpression('JSNoSuchMethodError')); - final ref = await inspector.instanceRefFor(remoteObject); - expect(ref!.kind, InstanceKind.kPlainInstance); - expect(ref.classRef!.name, 'JSNoSuchMethodError'); - expect(inspector.isDisplayableObject(ref), isFalse); - expect(inspector.isNativeJsError(ref), isTrue); - expect(inspector.isNativeJsObject(ref), isFalse); - }); - - test('for a native JavaScript object', () async { - final remoteObject = await inspector - .jsEvaluate(interceptorsNewExpression('LegacyJavaScriptObject')); - final ref = await inspector.instanceRefFor(remoteObject); - expect(ref!.kind, InstanceKind.kPlainInstance); - expect(ref.classRef!.name, 'LegacyJavaScriptObject'); - expect(inspector.isDisplayableObject(ref), isFalse); - expect(inspector.isNativeJsError(ref), isFalse); - expect(inspector.isNativeJsObject(ref), isTrue); - }); - }); - - group('instance', () { - setUp(() => setCurrentLogWriter(debug: debug)); - test('for class object', () async { - final remoteObject = await libraryPublicFinal(); - final instance = await inspector.instanceFor(remoteObject); - expect(instance!.kind, InstanceKind.kPlainInstance); - final classRef = instance.classRef!; - expect(classRef, isNotNull); - expect(classRef.name, 'MyTestClass'); - final boundFieldNames = - instance.fields!.map((boundField) => boundField.decl!.name).toList(); - expect(boundFieldNames, [ - '_privateField', - 'abstractField', - 'closure', - 'count', - 'message', - 'myselfField', - 'notFinal', - 'tornOff', - ]); - final fieldNames = - instance.fields!.map((boundField) => boundField.name).toList(); - expect(boundFieldNames, fieldNames); - for (var field in instance.fields!) { - expect(field.name, isNotNull); - expect(field.decl!.declaredType, isNotNull); - } - expect(inspector.isDisplayableObject(instance), isTrue); - }); - - test('for closure', () async { - final remoteObject = await libraryPublicFinal(); - final properties = await inspector.getProperties(remoteObject.objectId!); - final closure = - properties.firstWhere((property) => property.name == 'closure'); - final instance = await inspector.instanceFor(closure.value!); - expect(instance!.kind, InstanceKind.kClosure); - expect(instance.classRef!.name, 'Closure'); - expect(inspector.isDisplayableObject(instance), isTrue); - }); - - test('for a nested class', () async { - final libraryRemoteObject = await libraryPublicFinal(); - final fieldRemoteObject = - await inspector.loadField(libraryRemoteObject, 'myselfField'); - final instance = await inspector.instanceFor(fieldRemoteObject); - expect(instance!.kind, InstanceKind.kPlainInstance); - final classRef = instance.classRef!; - expect(classRef, isNotNull); - expect(classRef.name, 'MyTestClass'); - expect(inspector.isDisplayableObject(instance), isTrue); - }); - - test('for a list', () async { - final remote = await libraryPublic(); - final instance = await inspector.instanceFor(remote); - expect(instance!.kind, InstanceKind.kList); - final classRef = instance.classRef!; - expect(classRef, isNotNull); - expect(classRef.name, 'List'); - final first = instance.elements![0]; - expect(first.valueAsString, 'library'); - expect(inspector.isDisplayableObject(instance), isTrue); - }); - - test('for a map', () async { - final remote = - await inspector.jsEvaluate(libraryVariableExpression('map')); - final instance = await inspector.instanceFor(remote); - expect(instance!.kind, InstanceKind.kMap); - final classRef = instance.classRef!; - expect(classRef.name, 'LinkedMap'); - final first = instance.associations![0].value as InstanceRef; - expect(first.kind, InstanceKind.kList); - expect(first.length, 3); - final second = instance.associations![1].value as InstanceRef; - expect(second.kind, InstanceKind.kString); - expect(second.valueAsString, 'something'); - expect(inspector.isDisplayableObject(instance), isTrue); - }); - - test('for an identityMap', () async { - final remote = - await inspector.jsEvaluate(libraryVariableExpression('identityMap')); - final instance = await inspector.instanceFor(remote); - expect(instance!.kind, InstanceKind.kMap); - final classRef = instance.classRef!; - expect(classRef.name, 'IdentityMap'); - final first = instance.associations![0].value; - expect(first.valueAsString, '1'); - expect(inspector.isDisplayableObject(instance), isTrue); - }); - - test('for a class that implements List', () async { - // The VM only uses kind List for SDK lists, and we follow that. - final remote = - await inspector.jsEvaluate(libraryVariableExpression('notAList')); - final instance = await inspector.instanceFor(remote); - expect(instance!.kind, InstanceKind.kPlainInstance); - final classRef = instance.classRef!; - expect(classRef.name, 'NotReallyAList'); - expect(instance.elements, isNull); - final field = instance.fields!.first; - expect(field.decl!.name, '_internal'); - expect(inspector.isDisplayableObject(instance), isTrue); + group('$compilationMode |', () { + setUpAll(() async { + setCurrentLogWriter(debug: debug); + await context.setUp(compilationMode: compilationMode); + final chromeProxyService = context.service; + inspector = chromeProxyService.inspector; }); - test('for a native JavaScript error', () async { - final remoteObject = - await inspector.jsEvaluate(interceptorsNewExpression('NativeError')); - final instance = await inspector.instanceFor(remoteObject); - expect(instance!.kind, InstanceKind.kPlainInstance); - expect(instance.classRef!.name, 'NativeError'); - expect(inspector.isDisplayableObject(instance), isFalse); - expect(inspector.isNativeJsError(instance), isTrue); - expect(inspector.isNativeJsObject(instance), isFalse); + tearDownAll(() async { + await context.tearDown(); }); - test('for a native JavaScript type error', () async { - final remoteObject = await inspector - .jsEvaluate(interceptorsNewExpression('JSNoSuchMethodError')); - final instance = await inspector.instanceFor(remoteObject); - expect(instance!.kind, InstanceKind.kPlainInstance); - expect(instance.classRef!.name, 'JSNoSuchMethodError'); - expect(inspector.isDisplayableObject(instance), isFalse); - expect(inspector.isNativeJsError(instance), isTrue); - expect(inspector.isNativeJsObject(instance), isFalse); + final url = 'org-dartlang-app:///example/scopes/main.dart'; + + String libraryName(CompilationMode compilationMode) => + compilationMode == CompilationMode.frontendServer + ? "example/scopes/main.dart" + : "example/scopes/main"; + + String libraryVariableExpression( + String variable, + CompilationMode compilationMode, + ) => + '${globalLoadStrategy.loadModuleSnippet}("dart_sdk").dart.' + 'getModuleLibraries("${libraryName(compilationMode)}")' + '["$url"]["$variable"];'; + + String interceptorsNewExpression(String type) => + "require('dart_sdk')._interceptors.$type['_#new#tearOff']()"; + + /// A reference to the the variable `libraryPublicFinal`, an instance of + /// `MyTestClass`. + Future libraryPublicFinal(CompilationMode compilationMode) => + inspector.jsEvaluate( + libraryVariableExpression('libraryPublicFinal', compilationMode), + ); + + /// A reference to the the variable `libraryPublic`, a List of Strings. + Future libraryPublic(CompilationMode compilationMode) => + inspector.jsEvaluate( + libraryVariableExpression('libraryPublic', compilationMode), + ); + + group('instanceRef', () { + setUp(() => setCurrentLogWriter(debug: debug)); + + test('for a null', () async { + final remoteObject = await libraryPublicFinal(compilationMode); + final nullVariable = + await inspector.loadField(remoteObject, 'notFinal'); + final ref = await inspector.instanceRefFor(nullVariable); + expect(ref!.valueAsString, 'null'); + expect(ref.kind, InstanceKind.kNull); + final classRef = ref.classRef!; + expect(classRef.name, 'Null'); + expect(classRef.id, 'classes|dart:core|Null'); + expect(inspector.isDisplayableObject(ref), isTrue); + }); + + test('for a double', () async { + final remoteObject = await libraryPublicFinal(compilationMode); + final count = await inspector.loadField(remoteObject, 'count'); + final ref = await inspector.instanceRefFor(count); + expect(ref!.valueAsString, '0'); + expect(ref.kind, InstanceKind.kDouble); + final classRef = ref.classRef!; + expect(classRef.name, 'Double'); + expect(classRef.id, 'classes|dart:core|Double'); + expect(inspector.isDisplayableObject(ref), isTrue); + }); + + test('for a class', () async { + final remoteObject = await libraryPublicFinal(compilationMode); + final count = await inspector.loadField(remoteObject, 'myselfField'); + final ref = await inspector.instanceRefFor(count); + expect(ref!.kind, InstanceKind.kPlainInstance); + final classRef = ref.classRef!; + expect(classRef.name, 'MyTestClass'); + expect( + classRef.id, + 'classes|org-dartlang-app:///example/scopes/main.dart' + '|MyTestClass'); + expect(inspector.isDisplayableObject(ref), isTrue); + }); + + test('for closure', () async { + final remoteObject = await libraryPublicFinal(compilationMode); + final properties = + await inspector.getProperties(remoteObject.objectId!); + final closure = + properties.firstWhere((property) => property.name == 'closure'); + final ref = await inspector.instanceRefFor(closure.value!); + final functionName = ref!.closureFunction!.name; + // Older SDKs do not contain function names + if (functionName != 'Closure') { + expect(functionName, 'someFunction'); + } + expect(ref.kind, InstanceKind.kClosure); + expect(inspector.isDisplayableObject(ref), isTrue); + }); + + test('for a list', () async { + final remoteObject = await libraryPublic(compilationMode); + final ref = await inspector.instanceRefFor(remoteObject); + expect(ref!.length, greaterThan(0)); + expect(ref.kind, InstanceKind.kList); + expect(ref.classRef!.name, 'List'); + expect(inspector.isDisplayableObject(ref), isTrue); + }); + + test('for map', () async { + final remoteObject = await inspector + .jsEvaluate(libraryVariableExpression('map', compilationMode)); + final ref = await inspector.instanceRefFor(remoteObject); + expect(ref!.length, 2); + expect(ref.kind, InstanceKind.kMap); + expect(ref.classRef!.name, 'LinkedMap'); + expect(inspector.isDisplayableObject(ref), isTrue); + }); + + test('for an IdentityMap', () async { + final remoteObject = await inspector.jsEvaluate( + libraryVariableExpression('identityMap', compilationMode), + ); + final ref = await inspector.instanceRefFor(remoteObject); + expect(ref!.length, 2); + expect(ref.kind, InstanceKind.kMap); + expect(ref.classRef!.name, 'IdentityMap'); + expect(inspector.isDisplayableObject(ref), isTrue); + }); + + test('for a native JavaScript error', () async { + final remoteObject = await inspector + .jsEvaluate(interceptorsNewExpression('NativeError')); + final ref = await inspector.instanceRefFor(remoteObject); + expect(ref!.kind, InstanceKind.kPlainInstance); + expect(ref.classRef!.name, 'NativeError'); + expect(inspector.isDisplayableObject(ref), isFalse); + expect(inspector.isNativeJsError(ref), isTrue); + expect(inspector.isNativeJsObject(ref), isFalse); + }); + + test('for a native JavaScript type error', () async { + final remoteObject = await inspector + .jsEvaluate(interceptorsNewExpression('JSNoSuchMethodError')); + final ref = await inspector.instanceRefFor(remoteObject); + expect(ref!.kind, InstanceKind.kPlainInstance); + expect(ref.classRef!.name, 'JSNoSuchMethodError'); + expect(inspector.isDisplayableObject(ref), isFalse); + expect(inspector.isNativeJsError(ref), isTrue); + expect(inspector.isNativeJsObject(ref), isFalse); + }); + + test('for a native JavaScript object', () async { + final remoteObject = await inspector + .jsEvaluate(interceptorsNewExpression('LegacyJavaScriptObject')); + final ref = await inspector.instanceRefFor(remoteObject); + expect(ref!.kind, InstanceKind.kPlainInstance); + expect(ref.classRef!.name, 'LegacyJavaScriptObject'); + expect(inspector.isDisplayableObject(ref), isFalse); + expect(inspector.isNativeJsError(ref), isFalse); + expect(inspector.isNativeJsObject(ref), isTrue); + }); }); - test('for a native JavaScript object', () async { - final remoteObject = await inspector - .jsEvaluate(interceptorsNewExpression('LegacyJavaScriptObject')); - final instance = await inspector.instanceFor(remoteObject); - expect(instance!.kind, InstanceKind.kPlainInstance); - expect(instance.classRef!.name, 'LegacyJavaScriptObject'); - expect(inspector.isDisplayableObject(instance), isFalse); - expect(inspector.isNativeJsError(instance), isFalse); - expect(inspector.isNativeJsObject(instance), isTrue); + group('instance', () { + setUp(() => setCurrentLogWriter(debug: debug)); + test('for class object', () async { + final remoteObject = await libraryPublicFinal(compilationMode); + final instance = await inspector.instanceFor(remoteObject); + expect(instance!.kind, InstanceKind.kPlainInstance); + final classRef = instance.classRef!; + expect(classRef, isNotNull); + expect(classRef.name, 'MyTestClass'); + final boundFieldNames = instance.fields! + .map((boundField) => boundField.decl!.name) + .toList(); + expect(boundFieldNames, [ + '_privateField', + 'abstractField', + 'closure', + 'count', + 'message', + 'myselfField', + 'notFinal', + 'tornOff', + ]); + final fieldNames = + instance.fields!.map((boundField) => boundField.name).toList(); + expect(boundFieldNames, fieldNames); + for (var field in instance.fields!) { + expect(field.name, isNotNull); + expect(field.decl!.declaredType, isNotNull); + } + expect(inspector.isDisplayableObject(instance), isTrue); + }); + + test('for closure', () async { + final remoteObject = await libraryPublicFinal(compilationMode); + final properties = + await inspector.getProperties(remoteObject.objectId!); + final closure = + properties.firstWhere((property) => property.name == 'closure'); + final instance = await inspector.instanceFor(closure.value!); + expect(instance!.kind, InstanceKind.kClosure); + expect(instance.classRef!.name, 'Closure'); + expect(inspector.isDisplayableObject(instance), isTrue); + }); + + test('for a nested class', () async { + final libraryRemoteObject = await libraryPublicFinal(compilationMode); + final fieldRemoteObject = + await inspector.loadField(libraryRemoteObject, 'myselfField'); + final instance = await inspector.instanceFor(fieldRemoteObject); + expect(instance!.kind, InstanceKind.kPlainInstance); + final classRef = instance.classRef!; + expect(classRef, isNotNull); + expect(classRef.name, 'MyTestClass'); + expect(inspector.isDisplayableObject(instance), isTrue); + }); + + test('for a list', () async { + final remote = await libraryPublic(compilationMode); + final instance = await inspector.instanceFor(remote); + expect(instance!.kind, InstanceKind.kList); + final classRef = instance.classRef!; + expect(classRef, isNotNull); + expect(classRef.name, 'List'); + final first = instance.elements![0]; + expect(first.valueAsString, 'library'); + expect(inspector.isDisplayableObject(instance), isTrue); + }); + + test('for a map', () async { + final remote = await inspector + .jsEvaluate(libraryVariableExpression('map', compilationMode)); + final instance = await inspector.instanceFor(remote); + expect(instance!.kind, InstanceKind.kMap); + final classRef = instance.classRef!; + expect(classRef.name, 'LinkedMap'); + final first = instance.associations![0].value as InstanceRef; + expect(first.kind, InstanceKind.kList); + expect(first.length, 3); + final second = instance.associations![1].value as InstanceRef; + expect(second.kind, InstanceKind.kString); + expect(second.valueAsString, 'something'); + expect(inspector.isDisplayableObject(instance), isTrue); + }); + + test('for an identityMap', () async { + final remote = await inspector.jsEvaluate( + libraryVariableExpression('identityMap', compilationMode), + ); + final instance = await inspector.instanceFor(remote); + expect(instance!.kind, InstanceKind.kMap); + final classRef = instance.classRef!; + expect(classRef.name, 'IdentityMap'); + final first = instance.associations![0].value; + expect(first.valueAsString, '1'); + expect(inspector.isDisplayableObject(instance), isTrue); + }); + + test('for a class that implements List', () async { + // The VM only uses kind List for SDK lists, and we follow that. + final remote = await inspector + .jsEvaluate(libraryVariableExpression('notAList', compilationMode)); + final instance = await inspector.instanceFor(remote); + expect(instance!.kind, InstanceKind.kPlainInstance); + final classRef = instance.classRef!; + expect(classRef.name, 'NotReallyAList'); + expect(instance.elements, isNull); + final field = instance.fields!.first; + expect(field.decl!.name, '_internal'); + expect(inspector.isDisplayableObject(instance), isTrue); + }); + + test('for a native JavaScript error', () async { + final remoteObject = await inspector + .jsEvaluate(interceptorsNewExpression('NativeError')); + final instance = await inspector.instanceFor(remoteObject); + expect(instance!.kind, InstanceKind.kPlainInstance); + expect(instance.classRef!.name, 'NativeError'); + expect(inspector.isDisplayableObject(instance), isFalse); + expect(inspector.isNativeJsError(instance), isTrue); + expect(inspector.isNativeJsObject(instance), isFalse); + }); + + test('for a native JavaScript type error', () async { + final remoteObject = await inspector + .jsEvaluate(interceptorsNewExpression('JSNoSuchMethodError')); + final instance = await inspector.instanceFor(remoteObject); + expect(instance!.kind, InstanceKind.kPlainInstance); + expect(instance.classRef!.name, 'JSNoSuchMethodError'); + expect(inspector.isDisplayableObject(instance), isFalse); + expect(inspector.isNativeJsError(instance), isTrue); + expect(inspector.isNativeJsObject(instance), isFalse); + }); + + test('for a native JavaScript object', () async { + final remoteObject = await inspector + .jsEvaluate(interceptorsNewExpression('LegacyJavaScriptObject')); + final instance = await inspector.instanceFor(remoteObject); + expect(instance!.kind, InstanceKind.kPlainInstance); + expect(instance.classRef!.name, 'LegacyJavaScriptObject'); + expect(inspector.isDisplayableObject(instance), isFalse); + expect(inspector.isNativeJsError(instance), isFalse); + expect(inspector.isNativeJsObject(instance), isTrue); + }); }); }); } diff --git a/dwds/test/readers/frontend_server_asset_reader_test.dart b/dwds/test/readers/frontend_server_asset_reader_test.dart index de45b8310..e11c29dcd 100644 --- a/dwds/test/readers/frontend_server_asset_reader_test.dart +++ b/dwds/test/readers/frontend_server_asset_reader_test.dart @@ -45,8 +45,8 @@ void main() { setUp(() async { await createTempFixtures(); assetReader = FrontendServerAssetReader( - p.join(tempFixtures.path, 'main.dart.dill'), - packagesDir, + outputPath: p.join(tempFixtures.path, 'main.dart.dill'), + packageRoot: packagesDir, ); assetReader.updateCaches(); }); diff --git a/frontend_server_common/lib/src/asset_server.dart b/frontend_server_common/lib/src/asset_server.dart index 2731d1d30..784e634f9 100644 --- a/frontend_server_common/lib/src/asset_server.dart +++ b/frontend_server_common/lib/src/asset_server.dart @@ -17,7 +17,7 @@ import 'package:shelf/shelf.dart' as shelf; import 'package:test_common/test_sdk_layout.dart'; class TestAssetServer implements AssetReader { - late final String basePath; + late final String _basePath; final String index; final _logger = Logger('TestAssetServer'); @@ -43,9 +43,12 @@ class TestAssetServer implements AssetReader { this._fileSystem, this._sdkLayout, ) { - basePath = _parseBasePathFromIndexHtml(index); + _basePath = _parseBasePathFromIndexHtml(index); } + @override + String get basePath => _basePath; + bool hasFile(String path) => _files.containsKey(path); Uint8List getFile(String path) => _files[path]!; @@ -82,7 +85,6 @@ class TestAssetServer implements AssetReader { // Assets are served via GET only. return shelf.Response.notFound(''); } - final requestPath = _stripBasePath(request.url.path, basePath); if (requestPath == null) { return shelf.Response.notFound(''); @@ -90,7 +92,7 @@ class TestAssetServer implements AssetReader { var headers = {}; - if (request.url.path.endsWith('index.html')) { + if (request.url.path.endsWith('.html')) { var indexFile = _fileSystem.file(index); if (indexFile.existsSync()) { headers[HttpHeaders.contentTypeHeader] = 'text/html'; diff --git a/frontend_server_common/lib/src/devfs.dart b/frontend_server_common/lib/src/devfs.dart index 1c1c71db5..c0c7f371d 100644 --- a/frontend_server_common/lib/src/devfs.dart +++ b/frontend_server_common/lib/src/devfs.dart @@ -63,13 +63,27 @@ class WebDevFS { final outputDirectoryPath = fileSystem.file(mainPath).parent.path; final entryPoint = mainUri.toString(); + var require = 'require.js'; + var stackMapper = 'stack_trace_mapper.js'; + var main = 'main.dart.js'; + var bootstrap = 'main_module.bootstrap.js'; + + // If base path is not overwritten, use main's subdirectory + // to store all files, so the paths match the requests. + if (assetServer.basePath.isEmpty) { + final directory = p.dirname(entryPoint); + require = '$directory/require.js'; + stackMapper = '$directory/stack_trace_mapper.js'; + main = '$directory/main.dart.js'; + bootstrap = '$directory/main_module.bootstrap.js'; + } + assetServer.writeFile( entryPoint, fileSystem.file(mainPath).readAsStringSync()); - assetServer.writeFile('require.js', requireJS.readAsStringSync()); - assetServer.writeFile( - 'stack_trace_mapper.js', stackTraceMapper.readAsStringSync()); + assetServer.writeFile(require, requireJS.readAsStringSync()); + assetServer.writeFile(stackMapper, stackTraceMapper.readAsStringSync()); assetServer.writeFile( - 'main.dart.js', + main, generateBootstrapScript( requireUrl: 'require.js', mapperUrl: 'stack_trace_mapper.js', @@ -77,7 +91,7 @@ class WebDevFS { ), ); assetServer.writeFile( - 'main_module.bootstrap.js', + bootstrap, generateMainModule( entrypoint: entryPoint, ),