blob: bfabfe23e6de0ce69ded4664b6f4f556bae7b8d9 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.6
part of engine;
/// This class downloads assets over the network.
///
/// The assets are resolved relative to [assetsDir] inside the directory
/// containing the currently executing JS script.
class AssetManager {
static const String _defaultAssetsDir = 'assets';
/// The directory containing the assets.
final String assetsDir;
const AssetManager({this.assetsDir = _defaultAssetsDir});
String get _baseUrl {
return html.window.document
.querySelectorAll('meta')
.whereType<html.MetaElement>()
.firstWhere((dynamic e) => e.name == 'assetBase', orElse: () => null)
?.content;
}
/// Returns the URL to load the asset from, given the asset key.
///
/// We URL-encode the asset URL in order to correctly issue the right
/// HTTP request to the server.
///
/// For example, if you have an asset in the file "assets/hello world.png",
/// two things will happen. When the app is built, the asset will be copied
/// to an asset directory with the file name URL-encoded. So our asset will
/// be copied to something like "assets/hello%20world.png". To account for
/// the assets being copied over with a URL-encoded name, the Flutter
/// framework URL-encodes the asset key so when it sends a request to the
/// engine to load "assets/hello world.png", it actually sends a request to
/// load "assets/hello%20world.png". However, on the web, if we try to load
/// "assets/hello%20world.png", the request will be URL-decoded, we will
/// request "assets/hello world.png", and the request will 404. Therefore, we
/// must URL-encode the asset key *again* so when it is decoded, it is
/// requesting the once-URL-encoded asset key.
String getAssetUrl(String asset) {
if (Uri.parse(asset).hasScheme) {
return Uri.encodeFull(asset);
}
return Uri.encodeFull((_baseUrl ?? '') + '$assetsDir/$asset');
}
Future<ByteData> load(String asset) async {
final String url = getAssetUrl(asset);
try {
final html.HttpRequest request =
await html.HttpRequest.request(url, responseType: 'arraybuffer');
final ByteBuffer response = request.response;
return response.asByteData();
} on html.ProgressEvent catch (e) {
final html.EventTarget target = e.target;
if (target is html.HttpRequest) {
if (target.status == 404 && asset == 'AssetManifest.json') {
html.window.console
.warn('Asset manifest does not exist at `$url` – ignoring.');
return Uint8List.fromList(utf8.encode('{}')).buffer.asByteData();
}
throw AssetManagerException(url, target.status);
}
rethrow;
}
}
}
class AssetManagerException implements Exception {
final String url;
final int httpStatus;
AssetManagerException(this.url, this.httpStatus);
@override
String toString() => 'Failed to load asset at "$url" ($httpStatus)';
}
/// An asset manager that gives fake empty responses for assets.
class WebOnlyMockAssetManager implements AssetManager {
String defaultAssetsDir = '';
String defaultAssetManifest = '{}';
String defaultFontManifest = '[]';
@override
String get assetsDir => defaultAssetsDir;
@override
String get _baseUrl => '';
@override
String getAssetUrl(String asset) => '$asset';
@override
Future<ByteData> load(String asset) {
if (asset == getAssetUrl('AssetManifest.json')) {
return Future<ByteData>.value(
_toByteData(utf8.encode(defaultAssetManifest)));
}
if (asset == getAssetUrl('FontManifest.json')) {
return Future<ByteData>.value(
_toByteData(utf8.encode(defaultFontManifest)));
}
throw AssetManagerException(asset, 404);
}
ByteData _toByteData(List<int> bytes) {
final ByteData byteData = ByteData(bytes.length);
for (int i = 0; i < bytes.length; i++) {
byteData.setUint8(i, bytes[i]);
}
return byteData;
}
}