Skip to content

Commit 6316b48

Browse files
Check for a null codec in MultiFrameImageStreamCompleter after calling _emitFrame (#165009)
_emitFrame invokes image listeners. A listener callback could remove all of the MultiFrameImageStreamCompleter's listeners, resulting in a _maybeDispose call that destroys the codec. Fixes flutter/flutter#164944 Fixes flutter/flutter#164537
1 parent 212f66f commit 6316b48

File tree

2 files changed

+38
-1
lines changed

2 files changed

+38
-1
lines changed

packages/flutter/lib/src/painting/image_stream.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1047,6 +1047,10 @@ class MultiFrameImageStreamCompleter extends ImageStreamCompleter {
10471047
_frameDuration = _nextFrame!.duration;
10481048
_nextFrame!.image.dispose();
10491049
_nextFrame = null;
1050+
if (_codec == null) {
1051+
// codec was disposed during _emitFrame
1052+
return;
1053+
}
10501054
final int completedCycles = _framesEmitted ~/ _codec!.frameCount;
10511055
if (_codec!.repetitionCount == -1 || completedCycles <= _codec!.repetitionCount) {
10521056
_decodeNextFrameAndSchedule();
@@ -1108,7 +1112,7 @@ class MultiFrameImageStreamCompleter extends ImageStreamCompleter {
11081112
_nextFrame!.image.dispose();
11091113
_nextFrame = null;
11101114

1111-
_codec!.dispose();
1115+
_codec?.dispose();
11121116
_codec = null;
11131117
return;
11141118
}

packages/flutter/test/painting/image_stream_test.dart

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,4 +1076,37 @@ void main() {
10761076
areCreateAndDispose,
10771077
);
10781078
});
1079+
1080+
testWidgets('MultiFrameImageStreamCompleter image callback can remove listener', (
1081+
WidgetTester tester,
1082+
) async {
1083+
final Completer<Codec> completer = Completer<Codec>();
1084+
final MockCodec mockCodec = MockCodec();
1085+
mockCodec.frameCount = 1;
1086+
final ImageStreamCompleter imageStream = MultiFrameImageStreamCompleter(
1087+
codec: completer.future,
1088+
scale: 1.0,
1089+
);
1090+
1091+
completer.complete(mockCodec);
1092+
await tester.idle();
1093+
expect(mockCodec.numFramesAsked, 0);
1094+
1095+
late ImageStreamListener streamListener;
1096+
1097+
void listener(ImageInfo image, bool synchronousCall) {
1098+
addTearDown(image.dispose);
1099+
imageStream.removeListener(streamListener);
1100+
}
1101+
1102+
streamListener = ImageStreamListener(listener);
1103+
imageStream.addListener(streamListener);
1104+
await tester.idle();
1105+
expect(mockCodec.numFramesAsked, 1);
1106+
1107+
final FrameInfo frame = FakeFrameInfo(const Duration(milliseconds: 200), image20x10);
1108+
mockCodec.completeNextFrame(frame);
1109+
await tester.idle();
1110+
expect(mockCodec.disposed, true);
1111+
});
10791112
}

0 commit comments

Comments
 (0)