diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/assets.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/assets.dart index 86a4dc4346..101aa9775a 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/assets.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/assets.dart @@ -48,14 +48,34 @@ class AssetManager { return Uri.encodeFull((_baseUrl ?? '') + '$assetsDir/$asset'); } + /// Returns true if buffer contains html document. + static bool _responseIsHtmlPage(ByteData data) { + const String htmlDocTypeResponse = ''; + final int testLength = htmlDocTypeResponse.length; + if (data.lengthInBytes < testLength) { + return false; + } + for (int i = 0; i < testLength; i++) { + if (data.getInt8(i) != htmlDocTypeResponse.codeUnitAt(i)) + return false; + } + return true; + } + Future load(String asset) async { final String url = getAssetUrl(asset); try { final html.HttpRequest request = await html.HttpRequest.request(url, responseType: 'arraybuffer'); - + // Development server will return index.html for invalid urls. + // The check below makes sure when it is returned for non html assets + // we report an error instead of silent failure. final ByteBuffer response = request.response; - return response.asByteData(); + final ByteData data = response.asByteData(); + if (!url.endsWith('html') && _responseIsHtmlPage(data)) { + throw AssetManagerException(url, 404); + } + return data; } on html.ProgressEvent catch (e) { final html.EventTarget? target = e.target; if (target is html.HttpRequest) { diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/html_image_codec.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/html_image_codec.dart index 8dfe8fb75f..1fa41d3621 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/html_image_codec.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/html_image_codec.dart @@ -86,6 +86,7 @@ class HtmlCodec implements ui.Codec { loadSubscription?.cancel(); errorSubscription.cancel(); completer.completeError(event); + throw ArgumentError('Unable to load image asset: $src'); }); loadSubscription = imgElement.onLoad.listen((html.Event event) { if (chunkCallback != null) { diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart index 7adfbfbb57..cb81d7dab6 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -314,6 +314,20 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { }; } + void _reportAssetLoadError(String url, + ui.PlatformMessageResponseCallback? callback, String error) { + const MethodCodec codec = JSONMethodCodec(); + final String message = 'Error while trying to load an asset $url'; + if (!assertionsEnabled) { + /// For web/release mode log the load failure on console. + printWarning(message); + } + _replyToPlatformMessage( + callback, codec.encodeErrorEnvelope(code: 'errorCode', + message: message, + details: error)); + } + void _sendPlatformMessage( String name, ByteData? data, @@ -335,7 +349,6 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { } switch (name) { - /// This should be in sync with shell/common/shell.cc case 'flutter/skia': const MethodCodec codec = JSONMethodCodec(); @@ -363,12 +376,15 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { case 'flutter/assets': final String url = utf8.decode(data!.buffer.asUint8List()); - ui.webOnlyAssetManager.load(url).then((ByteData assetData) { - _replyToPlatformMessage(callback, assetData); - }, onError: (dynamic error) { - printWarning('Error while trying to load an asset: $error'); - _replyToPlatformMessage(callback, null); - }); + ui.webOnlyAssetManager.load(url) + .then((ByteData assetData) { + _replyToPlatformMessage(callback, assetData); + }, onError: (dynamic error) { + _reportAssetLoadError(url, callback, error); + } + ).catchError((dynamic e) { + _reportAssetLoadError(url, callback, e); + }); return; case 'flutter/platform':