From b9520248c4b7c735f627770b578c836cf1479a62 Mon Sep 17 00:00:00 2001 From: Dan Field Date: Fri, 23 Oct 2020 10:27:02 -0700 Subject: [PATCH] Add debugDisposed to Image (#21547) --- lib/ui/painting.dart | 13 +++++++++++ .../lib/src/engine/canvaskit/image.dart | 22 +++++++++++++++++++ .../lib/src/engine/html_image_codec.dart | 13 +++++++++++ lib/web_ui/lib/src/ui/painting.dart | 1 + lib/web_ui/test/canvaskit/image_test.dart | 6 +++++ .../engine/image/html_image_codec_test.dart | 8 +++++++ .../engine/recording_canvas_golden_test.dart | 3 +++ testing/dart/image_dispose_test.dart | 19 ++++++++++++++++ 8 files changed, 85 insertions(+) diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 1ba835c50..a3d7309a8 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -1632,6 +1632,19 @@ class Image { } } + /// Whether this reference to the underlying image is [dispose]d. + /// + /// This only returns a valid value if asserts are enabled, and must not be + /// used otherwise. + bool get debugDisposed { + bool? disposed; + assert(() { + disposed = _disposed; + return true; + }()); + return disposed ?? (throw StateError('Image.debugDisposed is only available when asserts are enabled.')); + } + /// Converts the [Image] object into a byte array. /// /// The [format] argument specifies the format in which the bytes will be diff --git a/lib/web_ui/lib/src/engine/canvaskit/image.dart b/lib/web_ui/lib/src/engine/canvaskit/image.dart index 192e301c1..6acdcd417 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/image.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/image.dart @@ -54,12 +54,21 @@ class CkAnimatedImage implements ui.Image { } } + bool _disposed = false; @override void dispose() { box.delete(); + _disposed = true; } @override + bool get debugDisposed { + if (assertionsEnabled) { + return _disposed; + } + throw StateError('Image.debugDisposed is only available when asserts are enabled.'); + } + ui.Image clone() => CkAnimatedImage._(_skAnimatedImage, box); @override @@ -138,9 +147,22 @@ class CkImage implements ui.Image { } } + bool _disposed = false; @override void dispose() { box.delete(); + assert(() { + _disposed = true; + return true; + }()); + } + + @override + bool get debugDisposed { + if (assertionsEnabled) { + return _disposed; + } + throw StateError('Image.debugDisposed is only available when asserts are enabled.'); } @override diff --git a/lib/web_ui/lib/src/engine/html_image_codec.dart b/lib/web_ui/lib/src/engine/html_image_codec.dart index 3b434685f..db1fa4618 100644 --- a/lib/web_ui/lib/src/engine/html_image_codec.dart +++ b/lib/web_ui/lib/src/engine/html_image_codec.dart @@ -116,12 +116,25 @@ class HtmlImage implements ui.Image { bool _requiresClone = false; HtmlImage(this.imgElement, this.width, this.height); + bool _disposed = false; @override void dispose() { // Do nothing. The codec that owns this image should take care of // releasing the object url. + if (assertionsEnabled) { + _disposed = true; + } + } + + @override + bool get debugDisposed { + if (assertionsEnabled) { + return _disposed; + } + return throw StateError('Image.debugDisposed is only available when asserts are enabled.'); } + @override ui.Image clone() => this; diff --git a/lib/web_ui/lib/src/ui/painting.dart b/lib/web_ui/lib/src/ui/painting.dart index 0809c3f1b..98e0be968 100644 --- a/lib/web_ui/lib/src/ui/painting.dart +++ b/lib/web_ui/lib/src/ui/painting.dart @@ -329,6 +329,7 @@ abstract class Image { int get height; Future toByteData({ImageByteFormat format = ImageByteFormat.rawRgba}); void dispose(); + bool get debugDisposed; Image clone() => this; diff --git a/lib/web_ui/test/canvaskit/image_test.dart b/lib/web_ui/test/canvaskit/image_test.dart index 08be25e09..8451ddccb 100644 --- a/lib/web_ui/test/canvaskit/image_test.dart +++ b/lib/web_ui/test/canvaskit/image_test.dart @@ -32,10 +32,13 @@ void testMain() { final SkAnimatedImage skAnimatedImage = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage); final CkAnimatedImage image = CkAnimatedImage(skAnimatedImage); expect(image.box.isDeleted, false); + expect(image.debugDisposed, false); image.dispose(); expect(image.box.isDeleted, true); + expect(image.debugDisposed, true); image.dispose(); expect(image.box.isDeleted, true); + expect(image.debugDisposed, true); }); test('CkAnimatedImage can be cloned and explicitly disposed of', () async { @@ -69,10 +72,13 @@ void testMain() { test('CkImage can be explicitly disposed of', () { final SkImage skImage = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage).getCurrentFrame(); final CkImage image = CkImage(skImage); + expect(image.debugDisposed, false); expect(image.box.isDeleted, false); image.dispose(); + expect(image.debugDisposed, true); expect(image.box.isDeleted, true); image.dispose(); + expect(image.debugDisposed, true); expect(image.box.isDeleted, true); }); diff --git a/lib/web_ui/test/engine/image/html_image_codec_test.dart b/lib/web_ui/test/engine/image/html_image_codec_test.dart index e8fbcbb05..331462571 100644 --- a/lib/web_ui/test/engine/image/html_image_codec_test.dart +++ b/lib/web_ui/test/engine/image/html_image_codec_test.dart @@ -63,6 +63,14 @@ void testMain() async { expect(frameInfo.image.width, 100); expect(frameInfo.image.toString(), '[100×100]'); }); + test('dispose image image', () async { + final HtmlCodec codec = HtmlCodec('sample_image1.png'); + final ui.FrameInfo frameInfo = await codec.getNextFrame(); + expect(frameInfo.image, isNotNull); + expect(frameInfo.image.debugDisposed, false); + frameInfo.image.dispose(); + expect(frameInfo.image.debugDisposed, true); + }); test('provides image loading progress', () async { StringBuffer buffer = new StringBuffer(); final HtmlCodec codec = HtmlCodec('sample_image1.png', diff --git a/lib/web_ui/test/golden_tests/engine/recording_canvas_golden_test.dart b/lib/web_ui/test/golden_tests/engine/recording_canvas_golden_test.dart index e73975451..0c14ac765 100644 --- a/lib/web_ui/test/golden_tests/engine/recording_canvas_golden_test.dart +++ b/lib/web_ui/test/golden_tests/engine/recording_canvas_golden_test.dart @@ -723,6 +723,9 @@ class TestImage implements Image { @override void dispose() {} + @override + bool get debugDisposed => false; + @override Image clone() => this; diff --git a/testing/dart/image_dispose_test.dart b/testing/dart/image_dispose_test.dart index 9e2dcb365..cd4f5268d 100644 --- a/testing/dart/image_dispose_test.dart +++ b/testing/dart/image_dispose_test.dart @@ -126,6 +126,25 @@ void main() { expect(frame2.image.clone()..dispose(), isNotNull); frame2.image.dispose(); }); + + test('debugDisposed works', () async { + final Uint8List bytes = await readFile('2x2.png'); + final Codec codec = await instantiateImageCodec(bytes); + final FrameInfo frame = await codec.getNextFrame(); + + if (assertsEnabled) { + expect(frame.image.debugDisposed, false); + } else { + expect(() => frame.image.debugDisposed, throwsStateError); + } + + frame.image.dispose(); + if (assertsEnabled) { + expect(frame.image.debugDisposed, true); + } else { + expect(() => frame.image.debugDisposed, throwsStateError); + } + }); } Future readFile(String fileName) async { -- GitLab