From 1fad8acb102e3dd012d5d50762de4565530a767e Mon Sep 17 00:00:00 2001 From: Dwayne Slater Date: Fri, 30 Oct 2020 15:33:03 -0700 Subject: [PATCH] Fix crash when a MultiFrameImageStreamCompleter is disposed during frame decoding (#69219) --- .../lib/src/painting/image_stream.dart | 6 +++++ .../test/painting/image_stream_test.dart | 25 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/packages/flutter/lib/src/painting/image_stream.dart b/packages/flutter/lib/src/painting/image_stream.dart index abffeee7c8..1b6a8a5e37 100644 --- a/packages/flutter/lib/src/painting/image_stream.dart +++ b/packages/flutter/lib/src/painting/image_stream.dart @@ -920,6 +920,12 @@ class MultiFrameImageStreamCompleter extends ImageStreamCompleter { return; } if (_codec!.frameCount == 1) { + // ImageStreamCompleter listeners removed while waiting for next frame to + // be decoded. + // There's no reason to emit the frame without active listeners. + if (!hasListeners) { + return; + } // This is not an animated image, just return it and don't schedule more // frames. _emitFrame(ImageInfo( diff --git a/packages/flutter/test/painting/image_stream_test.dart b/packages/flutter/test/painting/image_stream_test.dart index 491b115cf8..6b984622ca 100644 --- a/packages/flutter/test/painting/image_stream_test.dart +++ b/packages/flutter/test/painting/image_stream_test.dart @@ -130,6 +130,31 @@ void main() { expect(mockCodec.numFramesAsked, 1); }); + testWidgets('Decoding does not crash when disposed', (WidgetTester tester) async { + final Completer completer = Completer(); + final MockCodec mockCodec = MockCodec(); + mockCodec.frameCount = 1; + final ImageStreamCompleter imageStream = MultiFrameImageStreamCompleter( + codec: completer.future, + scale: 1.0, + ); + + completer.complete(mockCodec); + await tester.idle(); + expect(mockCodec.numFramesAsked, 0); + + final ImageListener listener = (ImageInfo image, bool synchronousCall) { }; + final ImageStreamListener streamListener = ImageStreamListener(listener); + imageStream.addListener(streamListener); + await tester.idle(); + expect(mockCodec.numFramesAsked, 1); + + final FrameInfo frame = FakeFrameInfo(const Duration(milliseconds: 200), image20x10); + mockCodec.completeNextFrame(frame); + imageStream.removeListener(streamListener); + await tester.idle(); + }); + testWidgets('Chunk events of base ImageStreamCompleter are delivered', (WidgetTester tester) async { final List chunkEvents = []; final StreamController streamController = StreamController();