Skip to content

Commit 7c8f57c

Browse files
author
Harry Terkelsen
authored
Report error when instantiating CanvasKit network image (flutter#22159)
1 parent 67d55ed commit 7c8f57c

File tree

3 files changed

+68
-37
lines changed

3 files changed

+68
-37
lines changed

lib/web_ui/lib/src/engine/canvaskit/image.dart

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,33 @@ void skiaInstantiateImageCodec(Uint8List list, Callback<ui.Codec> callback,
1515
callback(codec);
1616
}
1717

18-
/// Instantiates a [ui.Codec] backed by an `SkAnimatedImage` from Skia after requesting from URI.
19-
void skiaInstantiateWebImageCodec(String src, Callback<ui.Codec> callback,
20-
WebOnlyImageCodecChunkCallback? chunkCallback) {
21-
chunkCallback?.call(0, 100);
18+
/// Instantiates a [ui.Codec] backed by an `SkAnimatedImage` from Skia after
19+
/// requesting from URI.
20+
Future<ui.Codec> skiaInstantiateWebImageCodec(
21+
String src, WebOnlyImageCodecChunkCallback? chunkCallback) {
22+
Completer<ui.Codec> completer = Completer<ui.Codec>();
2223
//TODO: Switch to using MakeImageFromCanvasImageSource when animated images are supported.
23-
html.HttpRequest.request(
24-
src,
25-
responseType: "arraybuffer",
26-
).then((html.HttpRequest response) {
27-
chunkCallback?.call(100, 100);
24+
html.HttpRequest.request(src, responseType: "arraybuffer",
25+
onProgress: (html.ProgressEvent event) {
26+
if (event.lengthComputable) {
27+
chunkCallback?.call(event.loaded!, event.total!);
28+
}
29+
}).then((html.HttpRequest response) {
30+
if (response.status != 200) {
31+
completer.completeError(Exception(
32+
'Network image request failed with status: ${response.status}'));
33+
}
2834
final Uint8List list =
2935
new Uint8List.view((response.response as ByteBuffer));
3036
final SkAnimatedImage skAnimatedImage =
3137
canvasKit.MakeAnimatedImageFromEncoded(list);
3238
final CkAnimatedImage animatedImage = CkAnimatedImage(skAnimatedImage);
3339
final CkAnimatedImageCodec codec = CkAnimatedImageCodec(animatedImage);
34-
callback(codec);
40+
completer.complete(codec);
41+
}, onError: (dynamic error) {
42+
completer.completeError(error);
3543
});
44+
return completer.future;
3645
}
3746

3847
/// A wrapper for `SkAnimatedImage`.
@@ -43,7 +52,8 @@ class CkAnimatedImage implements ui.Image {
4352
// being garbage-collected, or by an explicit call to [delete].
4453
late final SkiaObjectBox box;
4554

46-
CkAnimatedImage(SkAnimatedImage skAnimatedImage) : this._(skAnimatedImage, null);
55+
CkAnimatedImage(SkAnimatedImage skAnimatedImage)
56+
: this._(skAnimatedImage, null);
4757

4858
CkAnimatedImage._(this._skAnimatedImage, SkiaObjectBox? boxToClone) {
4959
if (boxToClone != null) {
@@ -66,19 +76,21 @@ class CkAnimatedImage implements ui.Image {
6676
if (assertionsEnabled) {
6777
return _disposed;
6878
}
69-
throw StateError('Image.debugDisposed is only available when asserts are enabled.');
79+
throw StateError(
80+
'Image.debugDisposed is only available when asserts are enabled.');
7081
}
7182

7283
ui.Image clone() => CkAnimatedImage._(_skAnimatedImage, box);
7384

7485
@override
7586
bool isCloneOf(ui.Image other) {
76-
return other is CkAnimatedImage
77-
&& other._skAnimatedImage.isAliasOf(_skAnimatedImage);
87+
return other is CkAnimatedImage &&
88+
other._skAnimatedImage.isAliasOf(_skAnimatedImage);
7889
}
7990

8091
@override
81-
List<StackTrace>? debugGetOpenHandleStackTraces() => box.debugGetStackTraces();
92+
List<StackTrace>? debugGetOpenHandleStackTraces() =>
93+
box.debugGetStackTraces();
8294

8395
int get frameCount => _skAnimatedImage.getFrameCount();
8496

@@ -115,8 +127,9 @@ class CkAnimatedImage implements ui.Image {
115127
);
116128
bytes = _skAnimatedImage.readPixels(imageInfo, 0, 0);
117129
} else {
118-
final SkData skData = _skAnimatedImage.encodeToData(); //defaults to PNG 100%
119-
// make a copy that we can return
130+
// Defaults to PNG 100%.
131+
final SkData skData = _skAnimatedImage.encodeToData();
132+
// Make a copy that we can return.
120133
bytes = Uint8List.fromList(canvasKit.getSkDataBytes(skData));
121134
}
122135

@@ -162,20 +175,21 @@ class CkImage implements ui.Image {
162175
if (assertionsEnabled) {
163176
return _disposed;
164177
}
165-
throw StateError('Image.debugDisposed is only available when asserts are enabled.');
178+
throw StateError(
179+
'Image.debugDisposed is only available when asserts are enabled.');
166180
}
167181

168182
@override
169183
ui.Image clone() => CkImage._(skImage, box);
170184

171185
@override
172186
bool isCloneOf(ui.Image other) {
173-
return other is CkImage
174-
&& other.skImage.isAliasOf(skImage);
187+
return other is CkImage && other.skImage.isAliasOf(skImage);
175188
}
176189

177190
@override
178-
List<StackTrace>? debugGetOpenHandleStackTraces() => box.debugGetStackTraces();
191+
List<StackTrace>? debugGetOpenHandleStackTraces() =>
192+
box.debugGetStackTraces();
179193

180194
@override
181195
int get width => skImage.width();

lib/web_ui/lib/src/ui/painting.dart

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -462,23 +462,23 @@ String? _instantiateImageCodec(Uint8List list, engine.Callback<Codec> callback)
462462
}
463463

464464
Future<Codec> webOnlyInstantiateImageCodecFromUrl(Uri uri,
465-
{engine.WebOnlyImageCodecChunkCallback? chunkCallback}) {
466-
return _futurize<Codec>((engine.Callback<Codec> callback) =>
465+
{engine.WebOnlyImageCodecChunkCallback? chunkCallback}) {
466+
if (engine.useCanvasKit) {
467+
return engine.skiaInstantiateWebImageCodec(
468+
uri.toString(), chunkCallback);
469+
} else {
470+
return _futurize<Codec>((engine.Callback<Codec> callback) =>
467471
_instantiateImageCodecFromUrl(uri, chunkCallback, callback));
472+
}
468473
}
469474

470475
String? _instantiateImageCodecFromUrl(
471476
Uri uri,
472477
engine.WebOnlyImageCodecChunkCallback? chunkCallback,
473478
engine.Callback<Codec> callback,
474479
) {
475-
if (engine.useCanvasKit) {
476-
engine.skiaInstantiateWebImageCodec(uri.toString(), callback, chunkCallback);
477-
return null;
478-
} else {
479-
callback(engine.HtmlCodec(uri.toString(), chunkCallback: chunkCallback));
480-
return null;
481-
}
480+
callback(engine.HtmlCodec(uri.toString(), chunkCallback: chunkCallback));
481+
return null;
482482
}
483483

484484
void decodeImageFromList(Uint8List list, ImageDecoderCallback callback) {

lib/web_ui/test/canvaskit/image_test.dart

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
// found in the LICENSE file.
44

55
// @dart = 2.6
6+
import 'dart:html' show ProgressEvent;
7+
68
import 'package:test/bootstrap/browser.dart';
79
import 'package:test/test.dart';
810
import 'package:ui/src/engine.dart';
@@ -22,14 +24,16 @@ void testMain() {
2224
});
2325

2426
test('CkAnimatedImage toString', () {
25-
final SkAnimatedImage skAnimatedImage = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage);
27+
final SkAnimatedImage skAnimatedImage =
28+
canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage);
2629
final CkAnimatedImage image = CkAnimatedImage(skAnimatedImage);
2730
expect(image.toString(), '[1×1]');
2831
image.dispose();
2932
});
3033

3134
test('CkAnimatedImage can be explicitly disposed of', () {
32-
final SkAnimatedImage skAnimatedImage = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage);
35+
final SkAnimatedImage skAnimatedImage =
36+
canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage);
3337
final CkAnimatedImage image = CkAnimatedImage(skAnimatedImage);
3438
expect(image.box.isDeleted, false);
3539
expect(image.debugDisposed, false);
@@ -42,7 +46,8 @@ void testMain() {
4246
});
4347

4448
test('CkAnimatedImage can be cloned and explicitly disposed of', () async {
45-
final SkAnimatedImage skAnimatedImage = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage);
49+
final SkAnimatedImage skAnimatedImage =
50+
canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage);
4651
final CkAnimatedImage image = CkAnimatedImage(skAnimatedImage);
4752
final CkAnimatedImage imageClone = image.clone();
4853

@@ -63,14 +68,18 @@ void testMain() {
6368
});
6469

6570
test('CkImage toString', () {
66-
final SkImage skImage = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage).getCurrentFrame();
71+
final SkImage skImage =
72+
canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)
73+
.getCurrentFrame();
6774
final CkImage image = CkImage(skImage);
6875
expect(image.toString(), '[1×1]');
6976
image.dispose();
7077
});
7178

7279
test('CkImage can be explicitly disposed of', () {
73-
final SkImage skImage = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage).getCurrentFrame();
80+
final SkImage skImage =
81+
canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)
82+
.getCurrentFrame();
7483
final CkImage image = CkImage(skImage);
7584
expect(image.debugDisposed, false);
7685
expect(image.box.isDeleted, false);
@@ -83,7 +92,9 @@ void testMain() {
8392
});
8493

8594
test('CkImage can be explicitly disposed of when cloned', () async {
86-
final SkImage skImage = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage).getCurrentFrame();
95+
final SkImage skImage =
96+
canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)
97+
.getCurrentFrame();
8798
final CkImage image = CkImage(skImage);
8899
final CkImage imageClone = image.clone();
89100

@@ -102,6 +113,12 @@ void testMain() {
102113
await Future<void>.delayed(Duration.zero);
103114
expect(skImage.isDeleted(), true);
104115
});
105-
// TODO: https://github.com/flutter/flutter/issues/60040
116+
117+
test('skiaInstantiateWebImageCodec throws exception if given invalid URL',
118+
() async {
119+
expect(skiaInstantiateWebImageCodec('invalid-url', null),
120+
throwsA(isA<ProgressEvent>()));
121+
});
122+
// TODO: https://github.com/flutter/flutter/issues/60040
106123
}, skip: isIosSafari);
107124
}

0 commit comments

Comments
 (0)