@@ -18,9 +18,9 @@ import 'image_stream.dart';
1818/// used for testing purposes.
1919typedef HttpRequestFactory = web.XMLHttpRequest Function ();
2020
21- /// The type for an overridable factory function for creating <img> elements,
22- /// used for testing purposes.
23- typedef ImgElementFactory = web.HTMLImageElement Function ();
21+ /// The type for an overridable factory function for creating HTML elements to
22+ /// display images, used for testing purposes.
23+ typedef HtmlElementFactory = web.HTMLImageElement Function ();
2424
2525// Method signature for _loadAsync decode callbacks.
2626typedef _SimpleDecoderCallback = Future <ui.Codec > Function (ui.ImmutableBuffer buffer);
@@ -40,17 +40,17 @@ void debugRestoreHttpRequestFactory() {
4040 httpRequestFactory = _httpClient;
4141}
4242
43- /// The default <img> element factory.
43+ /// The default HTML element factory.
4444 web.HTMLImageElement _imgElementFactory () {
4545 return web.document.createElement ('img' ) as web.HTMLImageElement ;
4646}
4747
48- /// The factory function that creates <img> elements, can be overridden for
48+ /// The factory function that creates HTML elements, can be overridden for
4949/// tests.
5050@visibleForTesting
51- ImgElementFactory imgElementFactory = _imgElementFactory;
51+ HtmlElementFactory imgElementFactory = _imgElementFactory;
5252
53- /// Restores the default <img> element factory.
53+ /// Restores the default HTML element factory.
5454@visibleForTesting
5555void debugRestoreImgElementFactory () {
5656 imgElementFactory = _imgElementFactory;
@@ -63,7 +63,12 @@ void debugRestoreImgElementFactory() {
6363class NetworkImage extends image_provider.ImageProvider <image_provider.NetworkImage >
6464 implements image_provider.NetworkImage {
6565 /// Creates an object that fetches the image at the given URL.
66- const NetworkImage (this .url, {this .scale = 1.0 , this .headers});
66+ const NetworkImage (
67+ this .url, {
68+ this .scale = 1.0 ,
69+ this .headers,
70+ this .webHtmlElementStrategy = image_provider.WebHtmlElementStrategy .never,
71+ });
6772
6873 @override
6974 final String url;
@@ -74,6 +79,9 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm
7479 @override
7580 final Map <String , String >? headers;
7681
82+ @override
83+ final image_provider.WebHtmlElementStrategy webHtmlElementStrategy;
84+
7785 @override
7886 Future <NetworkImage > obtainKey (image_provider.ImageConfiguration configuration) {
7987 return SynchronousFuture <NetworkImage >(this );
@@ -136,19 +144,7 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm
136144 ) async {
137145 assert (key == this );
138146
139- final Uri resolved = Uri .base .resolve (key.url);
140-
141- final bool containsNetworkImageHeaders = key.headers? .isNotEmpty ?? false ;
142-
143- // We use a different method when headers are set because the
144- // `ui_web.createImageCodecFromUrl` method is not capable of handling headers.
145- if (containsNetworkImageHeaders) {
146- // It is not possible to load an <img> element and pass the headers with
147- // the request to fetch the image. Since the user has provided headers,
148- // this function should assume the headers are required to resolve to
149- // the correct resource and should not attempt to load the image in an
150- // <img> tag without the headers.
151-
147+ Future <ImageStreamCompleter > loadViaDecode () async {
152148 // Resolve the Codec before passing it to
153149 // [MultiFrameImageStreamCompleter] so any errors aren't reported
154150 // twice (once from the MultiFrameImageStreamCompleter and again
@@ -161,34 +157,38 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm
161157 debugLabel: key.url,
162158 informationCollector: _imageStreamInformationCollector (key),
163159 );
164- } else if (isSkiaWeb) {
165- try {
166- // Resolve the Codec before passing it to
167- // [MultiFrameImageStreamCompleter] so any errors aren't reported
168- // twice (once from the MultiFrameImageStreamCompleter and again
169- // from the wrapping [ForwardingImageStreamCompleter]).
170- final ui.Codec codec = await _fetchImageBytes (decode);
171- return MultiFrameImageStreamCompleter (
172- chunkEvents: chunkEvents.stream,
173- codec: Future <ui.Codec >.value (codec),
174- scale: key.scale,
175- debugLabel: key.url,
176- informationCollector: _imageStreamInformationCollector (key),
177- );
178- } catch (e) {
179- // If we failed to fetch the bytes, try to load the image in an <img>
180- // element instead.
181- final web.HTMLImageElement imageElement = imgElementFactory ();
182- imageElement.src = key.url;
183- // Decode the <img> element before creating the ImageStreamCompleter
184- // to avoid double reporting the error.
185- await imageElement.decode ().toDart;
186- return OneFrameImageStreamCompleter (
187- Future <ImageInfo >.value (WebImageInfo (imageElement, debugLabel: key.url)),
188- informationCollector: _imageStreamInformationCollector (key),
189- )..debugLabel = key.url;
190- }
191- } else {
160+ }
161+
162+ Future <ImageStreamCompleter > loadViaImgElement () async {
163+ // If we failed to fetch the bytes, try to load the image in an <img>
164+ // element instead.
165+ final web.HTMLImageElement imageElement = imgElementFactory ();
166+ imageElement.src = key.url;
167+ // Decode the <img> element before creating the ImageStreamCompleter
168+ // to avoid double reporting the error.
169+ await imageElement.decode ().toDart;
170+ return OneFrameImageStreamCompleter (
171+ Future <ImageInfo >.value (WebImageInfo (imageElement, debugLabel: key.url)),
172+ informationCollector: _imageStreamInformationCollector (key),
173+ )..debugLabel = key.url;
174+ }
175+
176+ final bool containsNetworkImageHeaders = key.headers? .isNotEmpty ?? false ;
177+ // When headers are set, the image can only be loaded by decoding.
178+ //
179+ // For the HTML renderer, `ui_web.createImageCodecFromUrl` method is not
180+ // capable of handling headers.
181+ //
182+ // For CanvasKit and Skwasm, it is not possible to load an <img> element and
183+ // pass the headers with the request to fetch the image. Since the user has
184+ // provided headers, this function should assume the headers are required to
185+ // resolve to the correct resource and should not attempt to load the image
186+ // in an <img> tag without the headers.
187+ if (containsNetworkImageHeaders) {
188+ return loadViaDecode ();
189+ }
190+
191+ if (! isSkiaWeb) {
192192 // This branch is only hit by the HTML renderer, which is deprecated. The
193193 // HTML renderer supports loading images with CORS restrictions, so we
194194 // don't need to catch errors and try loading the image in an <img> tag
@@ -198,6 +198,7 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm
198198 // [MultiFrameImageStreamCompleter] so any errors aren't reported
199199 // twice (once from the MultiFrameImageStreamCompleter) and again
200200 // from the wrapping [ForwardingImageStreamCompleter].
201+ final Uri resolved = Uri .base .resolve (key.url);
201202 final ui.Codec codec = await ui_web.createImageCodecFromUrl (
202203 resolved,
203204 chunkCallback: (int bytes, int total) {
@@ -212,6 +213,21 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm
212213 informationCollector: _imageStreamInformationCollector (key),
213214 );
214215 }
216+
217+ switch (webHtmlElementStrategy) {
218+ case image_provider.WebHtmlElementStrategy .never:
219+ return loadViaDecode ();
220+ case image_provider.WebHtmlElementStrategy .prefer:
221+ return loadViaImgElement ();
222+ case image_provider.WebHtmlElementStrategy .fallback:
223+ try {
224+ // Await here so that errors occurred during the asynchronous process
225+ // of `loadViaDecode` are caught and triggers `loadViaImgElement`.
226+ return await loadViaDecode ();
227+ } catch (e) {
228+ return loadViaImgElement ();
229+ }
230+ }
215231 }
216232
217233 Future <ui.Codec > _fetchImageBytes (_SimpleDecoderCallback decode) async {
0 commit comments