| OLD | NEW |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library builtin; | 5 library builtin; |
| 6 // NOTE: Do not import 'dart:io' in builtin. | 6 // NOTE: Do not import 'dart:io' in builtin. |
| 7 import 'dart:isolate'; | 7 import 'dart:isolate'; |
| 8 import 'dart:typed_data'; | 8 import 'dart:typed_data'; |
| 9 | 9 |
| 10 // import 'root_library'; happens here from C Code | |
| 11 | |
| 12 // Build time flag to enable debug logging of loading code. | |
| 13 const _logLoading = false; | |
| 14 | |
| 15 | |
| 16 // The root library (aka the script) is imported into this library. The | 10 // The root library (aka the script) is imported into this library. The |
| 17 // standalone embedder uses this to lookup the main entrypoint in the | 11 // standalone embedder uses this to lookup the main entrypoint in the |
| 18 // root library's namespace. | 12 // root library's namespace. |
| 19 Function _getMainClosure() => main; | 13 Function _getMainClosure() => main; |
| 20 | 14 |
| 21 | 15 |
| 22 // 'print' implementation. | 16 // 'print' implementation. |
| 23 // The standalone embedder registers the closurized _print function with the | 17 // The standalone embedder registers the closurized _print function with the |
| 24 // dart:core library. | 18 // dart:core library. |
| 25 void _printString(String s) native "Builtin_PrintString"; | 19 void _printString(String s) native "Builtin_PrintString"; |
| (...skipping 26 matching lines...) Expand all Loading... |
| 52 | 46 |
| 53 // Asynchronous loading of resources. | 47 // Asynchronous loading of resources. |
| 54 // The embedder forwards most loading requests to this library. | 48 // The embedder forwards most loading requests to this library. |
| 55 | 49 |
| 56 // See Dart_LibraryTag in dart_api.h | 50 // See Dart_LibraryTag in dart_api.h |
| 57 const Dart_kScriptTag = null; | 51 const Dart_kScriptTag = null; |
| 58 const Dart_kImportTag = 0; | 52 const Dart_kImportTag = 0; |
| 59 const Dart_kSourceTag = 1; | 53 const Dart_kSourceTag = 1; |
| 60 const Dart_kCanonicalizeUrl = 2; | 54 const Dart_kCanonicalizeUrl = 2; |
| 61 | 55 |
| 56 // Embedder sets this to true if the --trace-loading flag was passed on the |
| 57 // command line. |
| 58 bool _traceLoading = false; |
| 59 |
| 62 // A port for communicating with the service isolate for I/O. | 60 // A port for communicating with the service isolate for I/O. |
| 63 SendPort _loadPort; | 61 SendPort _loadPort; |
| 64 // Maintain a number of outstanding load requests. Current loading request is | 62 // Maintain a number of outstanding load requests. Current loading request is |
| 65 // finished once there are no outstanding requests. | 63 // finished once there are no outstanding requests. |
| 66 int _numOutstandingLoadRequests = 0; | 64 int _numOutstandingLoadRequests = 0; |
| 67 | 65 |
| 68 // The current working directory when the embedder was launched. | 66 // The current working directory when the embedder was launched. |
| 69 var _workingDirectoryUri; | 67 Uri _workingDirectory; |
| 70 // The URI that the entry point script was loaded from. Remembered so that | 68 // The URI that the root script was loaded from. Remembered so that |
| 71 // package imports can be resolved relative to it. | 69 // package imports can be resolved relative to it. The root script is the basis |
| 72 var _entryPointScript; | 70 // for the root library in the VM. |
| 71 Uri _rootScript; |
| 73 // The directory to look in to resolve "package:" scheme URIs. By detault it is | 72 // The directory to look in to resolve "package:" scheme URIs. By detault it is |
| 74 // the 'packages' directory right next to the script. | 73 // the 'packages' directory right next to the script. |
| 75 var _packageRoot = _entryPointScript.resolve('packages/'); | 74 Uri _packageRoot = _rootScript.resolve('packages/'); |
| 76 | 75 |
| 77 // Special handling for Windows paths so that they are compatible with URI | 76 // Special handling for Windows paths so that they are compatible with URI |
| 78 // handling. | 77 // handling. |
| 79 // Embedder sets whether we are running on Windows. | 78 // Embedder sets this to true if we are running on Windows. |
| 80 var _isWindows; | 79 bool _isWindows = false; |
| 81 | 80 |
| 82 | 81 |
| 83 // A class wrapping the load error message in an Error object. | 82 // A class wrapping the load error message in an Error object. |
| 84 class LoadError extends Error { | 83 class LoadError extends Error { |
| 85 final String message; | 84 final String message; |
| 86 LoadError(this.message); | 85 LoadError(this.message); |
| 87 | 86 |
| 88 String toString() => 'Load Error: $message'; | 87 String toString() => 'Load Error: $message'; |
| 89 } | 88 } |
| 90 | 89 |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 145 if (!uri.endsWith('/')) { | 144 if (!uri.endsWith('/')) { |
| 146 return '$uri/'; | 145 return '$uri/'; |
| 147 } | 146 } |
| 148 return uri; | 147 return uri; |
| 149 } | 148 } |
| 150 | 149 |
| 151 | 150 |
| 152 // Embedder Entrypoint: | 151 // Embedder Entrypoint: |
| 153 // The embedder calls this method with the current working directory. | 152 // The embedder calls this method with the current working directory. |
| 154 void _setWorkingDirectory(cwd) { | 153 void _setWorkingDirectory(cwd) { |
| 155 if (_logLoading) { | 154 if (_traceLoading) { |
| 156 _print('# Setting working directory: $cwd'); | 155 _print('# Setting working directory: $cwd'); |
| 157 } | 156 } |
| 158 _workingDirectoryUri = new Uri.directory(cwd); | 157 _workingDirectory = new Uri.directory(cwd); |
| 159 if (_logLoading) { | 158 if (_traceLoading) { |
| 160 _print('# Working directory URI: $_workingDirectoryUri'); | 159 _print('# Working directory URI: $_workingDirectory'); |
| 161 } | 160 } |
| 162 } | 161 } |
| 163 | 162 |
| 164 | 163 |
| 165 // Embedder Entrypoint: | 164 // Embedder Entrypoint: |
| 166 // The embedder calls this method with a custom package root. | 165 // The embedder calls this method with a custom package root. |
| 167 _setPackageRoot(String packageRoot) { | 166 _setPackageRoot(String packageRoot) { |
| 167 if (_traceLoading) { |
| 168 _print('# Setting package root: $packageRoot'); |
| 169 } |
| 168 packageRoot = _enforceTrailingSlash(packageRoot); | 170 packageRoot = _enforceTrailingSlash(packageRoot); |
| 169 if (packageRoot.startsWith('file:') || | 171 if (packageRoot.startsWith('file:') || |
| 170 packageRoot.startsWith('http:') || | 172 packageRoot.startsWith('http:') || |
| 171 packageRoot.startsWith('https:')) { | 173 packageRoot.startsWith('https:')) { |
| 172 _packageRoot = _workingDirectoryUri.resolve(packageRoot); | 174 _packageRoot = _workingDirectory.resolve(packageRoot); |
| 173 } else { | 175 } else { |
| 174 packageRoot = _sanitizeWindowsPath(packageRoot); | 176 packageRoot = _sanitizeWindowsPath(packageRoot); |
| 175 packageRoot = _trimWindowsPath(packageRoot); | 177 packageRoot = _trimWindowsPath(packageRoot); |
| 176 _packageRoot = _workingDirectoryUri.resolveUri(new Uri.file(packageRoot)); | 178 _packageRoot = _workingDirectory.resolveUri(new Uri.file(packageRoot)); |
| 177 } | 179 } |
| 178 if (_logLoading) { | 180 if (_traceLoading) { |
| 179 _print('# Package root: $packageRoot -> $_packageRoot'); | 181 _print('# Package root URI: $_packageRoot'); |
| 180 } | 182 } |
| 181 } | 183 } |
| 182 | 184 |
| 183 | 185 |
| 184 // Given a uri with a 'package' scheme, return a Uri that is prefixed with | 186 // Given a uri with a 'package' scheme, return a Uri that is prefixed with |
| 185 // the package root. | 187 // the package root. |
| 186 Uri _resolvePackageUri(Uri uri) { | 188 Uri _resolvePackageUri(Uri uri) { |
| 187 if (!uri.host.isEmpty) { | 189 if (!uri.host.isEmpty) { |
| 188 var path = '${uri.host}${uri.path}'; | 190 var path = '${uri.host}${uri.path}'; |
| 189 var right = 'package:$path'; | 191 var right = 'package:$path'; |
| 190 var wrong = 'package://$path'; | 192 var wrong = 'package://$path'; |
| 191 | 193 |
| 192 throw "URIs using the 'package:' scheme should look like " | 194 throw "URIs using the 'package:' scheme should look like " |
| 193 "'$right', not '$wrong'."; | 195 "'$right', not '$wrong'."; |
| 194 } | 196 } |
| 195 | 197 |
| 196 if (_logLoading) { | 198 if (_traceLoading) { |
| 197 _print('# Package root: $_packageRoot'); | 199 _print('# Package root: $_packageRoot'); |
| 198 _print('# uri path: ${uri.path}'); | 200 _print('# uri path: ${uri.path}'); |
| 199 } | 201 } |
| 200 return _packageRoot.resolve(uri.path); | 202 return _packageRoot.resolve(uri.path); |
| 201 } | 203 } |
| 202 | 204 |
| 203 | 205 |
| 204 // Resolves the script uri in the current working directory iff the given uri | 206 // Resolves the script uri in the current working directory iff the given uri |
| 205 // did not specify a scheme (e.g. a path to a script file on the command line). | 207 // did not specify a scheme (e.g. a path to a script file on the command line). |
| 206 Uri _resolveScriptUri(String scriptName) { | 208 Uri _resolveScriptUri(String scriptName) { |
| 207 if (_workingDirectoryUri == null) { | 209 if (_workingDirectory == null) { |
| 208 throw 'No current working directory set.'; | 210 throw 'No current working directory set.'; |
| 209 } | 211 } |
| 210 scriptName = _sanitizeWindowsPath(scriptName); | 212 scriptName = _sanitizeWindowsPath(scriptName); |
| 211 | 213 |
| 212 var scriptUri = Uri.parse(scriptName); | 214 var scriptUri = Uri.parse(scriptName); |
| 213 if (scriptUri.scheme == '') { | 215 if (scriptUri.scheme == '') { |
| 214 // Script does not have a scheme, assume that it is a path, | 216 // Script does not have a scheme, assume that it is a path, |
| 215 // resolve it against the working directory. | 217 // resolve it against the working directory. |
| 216 scriptUri = _workingDirectoryUri.resolveUri(scriptUri); | 218 scriptUri = _workingDirectory.resolveUri(scriptUri); |
| 217 } | 219 } |
| 218 | 220 |
| 219 // Remember the entry point script URI so that we can resolve packages | 221 // Remember the root script URI so that we can resolve packages based on |
| 220 // based on this location. | 222 // this location. |
| 221 _entryPointScript = scriptUri; | 223 _rootScript = scriptUri; |
| 222 | 224 |
| 223 if (_logLoading) { | 225 if (_traceLoading) { |
| 224 _print('# Resolved entry point to: $_entryPointScript'); | 226 _print('# Resolved entry point to: $_rootScript'); |
| 225 } | 227 } |
| 226 return scriptUri; | 228 return scriptUri; |
| 227 } | 229 } |
| 228 | 230 |
| 229 | 231 |
| 230 void _finishLoadRequest(String uri) { | 232 void _finishLoadRequest(String uri) { |
| 231 assert(_numOutstandingLoadRequests > 0); | 233 assert(_numOutstandingLoadRequests > 0); |
| 232 _numOutstandingLoadRequests--; | 234 _numOutstandingLoadRequests--; |
| 233 if (_logLoading) { | 235 if (_traceLoading) { |
| 234 _print("Loading of $uri finished, " | 236 _print("Loading of $uri finished, " |
| 235 "${_numOutstandingLoadRequests} requests remaining"); | 237 "${_numOutstandingLoadRequests} requests remaining"); |
| 236 } | 238 } |
| 237 if (_numOutstandingLoadRequests == 0) { | 239 if (_numOutstandingLoadRequests == 0) { |
| 238 _signalDoneLoading(); | 240 _signalDoneLoading(); |
| 239 } | 241 } |
| 240 } | 242 } |
| 241 | 243 |
| 242 | 244 |
| 243 void _startLoadRequest(String uri, Uri resourceUri) { | 245 void _startLoadRequest(String uri, Uri resourceUri) { |
| 244 assert(_numOutstandingLoadRequests >= 0); | 246 assert(_numOutstandingLoadRequests >= 0); |
| 245 _numOutstandingLoadRequests++; | 247 _numOutstandingLoadRequests++; |
| 246 if (_logLoading) { | 248 if (_traceLoading) { |
| 247 _print("Loading of $resourceUri for $uri started, " | 249 _print("Loading of $resourceUri for $uri started, " |
| 248 "${_numOutstandingLoadRequests} requests outstanding"); | 250 "${_numOutstandingLoadRequests} requests outstanding"); |
| 249 } | 251 } |
| 250 } | 252 } |
| 251 | 253 |
| 252 | 254 |
| 253 void _loadScript(int tag, String uri, String libraryUri, Uint8List data) { | 255 void _loadScript(int tag, String uri, String libraryUri, Uint8List data) { |
| 254 // TODO: Currently a compilation error while loading the script is | 256 // TODO: Currently a compilation error while loading the script is |
| 255 // fatal for the isolate. _loadScriptCallback() does not return and | 257 // fatal for the isolate. _loadScriptCallback() does not return and |
| 256 // the _numOutstandingLoadRequests counter remains out of sync. | 258 // the _numOutstandingLoadRequests counter remains out of sync. |
| 257 _loadScriptCallback(tag, uri, libraryUri, data); | 259 _loadScriptCallback(tag, uri, libraryUri, data); |
| 258 _finishLoadRequest(uri); | 260 _finishLoadRequest(uri); |
| 259 } | 261 } |
| 260 | 262 |
| 261 | 263 |
| 262 void _asyncLoadError(int tag, String uri, String libraryUri, LoadError error) { | 264 void _asyncLoadError(int tag, String uri, String libraryUri, LoadError error) { |
| 263 if (_logLoading) { | 265 if (_traceLoading) { |
| 264 _print("_asyncLoadError($uri), error: $error"); | 266 _print("_asyncLoadError($uri), error: $error"); |
| 265 } | 267 } |
| 266 if (tag == Dart_kImportTag) { | 268 if (tag == Dart_kImportTag) { |
| 267 // When importing a library, the libraryUri is the imported | 269 // When importing a library, the libraryUri is the imported |
| 268 // uri. | 270 // uri. |
| 269 libraryUri = uri; | 271 libraryUri = uri; |
| 270 } | 272 } |
| 271 _asyncLoadErrorCallback(uri, libraryUri, error); | 273 _asyncLoadErrorCallback(uri, libraryUri, error); |
| 272 _finishLoadRequest(uri); | 274 _finishLoadRequest(uri); |
| 273 } | 275 } |
| (...skipping 19 matching lines...) Expand all Loading... |
| 293 // seen LoadError. | 295 // seen LoadError. |
| 294 var error = (e is LoadError) ? e : new LoadError(e.toString); | 296 var error = (e is LoadError) ? e : new LoadError(e.toString); |
| 295 _asyncLoadError(tag, uri, libraryUri, error); | 297 _asyncLoadError(tag, uri, libraryUri, error); |
| 296 }); | 298 }); |
| 297 | 299 |
| 298 try { | 300 try { |
| 299 var msg = [receivePort.sendPort, resourceUri.toString()]; | 301 var msg = [receivePort.sendPort, resourceUri.toString()]; |
| 300 _loadPort.send(msg); | 302 _loadPort.send(msg); |
| 301 _startLoadRequest(uri, resourceUri); | 303 _startLoadRequest(uri, resourceUri); |
| 302 } catch (e) { | 304 } catch (e) { |
| 303 if (_logLoading) { | 305 if (_traceLoading) { |
| 304 _print("Exception when communicating with service isolate: $e"); | 306 _print("Exception when communicating with service isolate: $e"); |
| 305 } | 307 } |
| 306 // Wrap inside a LoadError unless we are already propagating a previously | 308 // Wrap inside a LoadError unless we are already propagating a previously |
| 307 // seen LoadError. | 309 // seen LoadError. |
| 308 var error = (e is LoadError) ? e : new LoadError(e.toString); | 310 var error = (e is LoadError) ? e : new LoadError(e.toString); |
| 309 _asyncLoadError(tag, uri, libraryUri, error); | 311 _asyncLoadError(tag, uri, libraryUri, error); |
| 310 receivePort.close(); | 312 receivePort.close(); |
| 311 } | 313 } |
| 312 } | 314 } |
| 313 | 315 |
| (...skipping 15 matching lines...) Expand all Loading... |
| 329 } | 331 } |
| 330 | 332 |
| 331 _loadDataFromLoadPort(tag, uri, libraryUri, resourceUri); | 333 _loadDataFromLoadPort(tag, uri, libraryUri, resourceUri); |
| 332 } | 334 } |
| 333 | 335 |
| 334 | 336 |
| 335 // Embedder Entrypoint: | 337 // Embedder Entrypoint: |
| 336 // Function called by standalone embedder to resolve uris when the VM requests | 338 // Function called by standalone embedder to resolve uris when the VM requests |
| 337 // Dart_kCanonicalizeUrl from the tag handler. | 339 // Dart_kCanonicalizeUrl from the tag handler. |
| 338 String _resolveUri(String base, String userString) { | 340 String _resolveUri(String base, String userString) { |
| 339 if (_logLoading) { | 341 if (_traceLoading) { |
| 340 _print('# Resolving: $userString from $base'); | 342 _print('# Resolving: $userString from $base'); |
| 341 } | 343 } |
| 342 var baseUri = Uri.parse(base); | 344 var baseUri = Uri.parse(base); |
| 343 var result; | 345 var result; |
| 344 if (userString.startsWith(_DART_EXT)) { | 346 if (userString.startsWith(_DART_EXT)) { |
| 345 var uri = userString.substring(_DART_EXT.length); | 347 var uri = userString.substring(_DART_EXT.length); |
| 346 result = '$_DART_EXT${baseUri.resolve(uri)}'; | 348 result = '$_DART_EXT${baseUri.resolve(uri)}'; |
| 347 } else { | 349 } else { |
| 348 result = baseUri.resolve(userString).toString(); | 350 result = baseUri.resolve(userString).toString(); |
| 349 } | 351 } |
| 350 if (_logLoading) { | 352 if (_traceLoading) { |
| 351 _print('Resolved $userString in $base to $result'); | 353 _print('Resolved $userString in $base to $result'); |
| 352 } | 354 } |
| 353 return result; | 355 return result; |
| 354 } | 356 } |
| 355 | 357 |
| 356 | 358 |
| 357 // Embedder Entrypoint (gen_snapshot): | 359 // Embedder Entrypoint (gen_snapshot): |
| 358 // Resolve relative paths relative to working directory. | 360 // Resolve relative paths relative to working directory. |
| 359 String _resolveInWorkingDirectory(String fileName) { | 361 String _resolveInWorkingDirectory(String fileName) { |
| 360 if (_workingDirectoryUri == null) { | 362 if (_workingDirectory == null) { |
| 361 throw 'No current working directory set.'; | 363 throw 'No current working directory set.'; |
| 362 } | 364 } |
| 363 var name = _sanitizeWindowsPath(fileName); | 365 var name = _sanitizeWindowsPath(fileName); |
| 364 | 366 |
| 365 var uri = Uri.parse(name); | 367 var uri = Uri.parse(name); |
| 366 if (uri.scheme != '') { | 368 if (uri.scheme != '') { |
| 367 throw 'Schemes are not supported when resolving filenames.'; | 369 throw 'Schemes are not supported when resolving filenames.'; |
| 368 } | 370 } |
| 369 uri = _workingDirectoryUri.resolveUri(uri); | 371 uri = _workingDirectory.resolveUri(uri); |
| 370 | 372 |
| 371 if (_logLoading) { | 373 if (_traceLoading) { |
| 372 _print('# Resolved in working directory: $fileName -> $uri'); | 374 _print('# Resolved in working directory: $fileName -> $uri'); |
| 373 } | 375 } |
| 374 return uri.toString(); | 376 return uri.toString(); |
| 375 } | 377 } |
| 376 | 378 |
| 377 | 379 |
| 378 // Handling of dart-ext loading. | 380 // Handling of dart-ext loading. |
| 379 // Dart native extension scheme. | 381 // Dart native extension scheme. |
| 380 const _DART_EXT = 'dart-ext:'; | 382 const _DART_EXT = 'dart-ext:'; |
| 381 | 383 |
| 382 String _nativeLibraryExtension() native "Builtin_NativeLibraryExtension"; | 384 String _nativeLibraryExtension() native "Builtin_NativeLibraryExtension"; |
| 383 | 385 |
| 384 | 386 |
| 385 String _platformExtensionFileName(String name) { | 387 String _platformExtensionFileName(String name) { |
| 386 var extension = _nativeLibraryExtension(); | 388 var extension = _nativeLibraryExtension(); |
| 387 | 389 |
| 388 if (_isWindows) { | 390 if (_isWindows) { |
| 389 return '$name.$extension'; | 391 return '$name.$extension'; |
| 390 } else { | 392 } else { |
| 391 return 'lib$name.$extension'; | 393 return 'lib$name.$extension'; |
| 392 } | 394 } |
| 393 } | 395 } |
| 394 | 396 |
| 395 | 397 |
| 396 // Returns either a file path or a URI starting with http[s]:, as a String. | 398 // Returns either a file path or a URI starting with http[s]:, as a String. |
| 397 String _filePathFromUri(String userUri) { | 399 String _filePathFromUri(String userUri) { |
| 398 var uri = Uri.parse(userUri); | 400 var uri = Uri.parse(userUri); |
| 399 if (_logLoading) { | 401 if (_traceLoading) { |
| 400 _print('# Getting file path from: $uri'); | 402 _print('# Getting file path from: $uri'); |
| 401 } | 403 } |
| 402 | 404 |
| 403 var path; | 405 var path; |
| 404 switch (uri.scheme) { | 406 switch (uri.scheme) { |
| 405 case '': | 407 case '': |
| 406 case 'file': | 408 case 'file': |
| 407 return uri.toFilePath(); | 409 return uri.toFilePath(); |
| 408 case 'package': | 410 case 'package': |
| 409 return _filePathFromUri(_resolvePackageUri(uri).toString()); | 411 return _filePathFromUri(_resolvePackageUri(uri).toString()); |
| 410 case 'data': | 412 case 'data': |
| 411 case 'http': | 413 case 'http': |
| 412 case 'https': | 414 case 'https': |
| 413 return uri.toString(); | 415 return uri.toString(); |
| 414 default: | 416 default: |
| 415 // Only handling file, http, and package URIs | 417 // Only handling file, http, and package URIs |
| 416 // in standalone binary. | 418 // in standalone binary. |
| 417 if (_logLoading) { | 419 if (_traceLoading) { |
| 418 _print('# Unknown scheme (${uri.scheme}) in $uri.'); | 420 _print('# Unknown scheme (${uri.scheme}) in $uri.'); |
| 419 } | 421 } |
| 420 throw 'Not a known scheme: $uri'; | 422 throw 'Not a known scheme: $uri'; |
| 421 } | 423 } |
| 422 } | 424 } |
| 423 | 425 |
| 424 | 426 |
| 425 // Embedder Entrypoint: | 427 // Embedder Entrypoint: |
| 426 // When loading an extension the embedder calls this method to get the | 428 // When loading an extension the embedder calls this method to get the |
| 427 // different components. | 429 // different components. |
| (...skipping 23 matching lines...) Expand all Loading... |
| 451 } else { | 453 } else { |
| 452 name = userUri.substring(index + 1); | 454 name = userUri.substring(index + 1); |
| 453 path = userUri.substring(0, index + 1); | 455 path = userUri.substring(0, index + 1); |
| 454 } | 456 } |
| 455 | 457 |
| 456 path = _filePathFromUri(path); | 458 path = _filePathFromUri(path); |
| 457 var filename = _platformExtensionFileName(name); | 459 var filename = _platformExtensionFileName(name); |
| 458 | 460 |
| 459 return [path, filename, name]; | 461 return [path, filename, name]; |
| 460 } | 462 } |
| OLD | NEW |