diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 1ba835c509a0b4c983eed357a9f7c383f2260a47..a3d7309a88733fc23fcb1bca7375a07f41cfe18b 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 192e301c1bdcdcf16971c72f68d8af5f05cb3001..6acdcd4176e186ad95621c6712c5f34a845b3057 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 3b434685f2bf649cb4c5e0df3ad475019752428c..db1fa461852ea69038866f36f4c68d51db2ab53a 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 0809c3f1bc4adcb02af978c71aa4fce231bf577b..98e0be968341d86f03fcb38b1fc4baa40d9447f4 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 08be25e0937aa1885ac8f18d6587498819cd953f..8451ddccb9651fec8230b8ed3c4de3145702ec0f 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 e8fbcbb05b404184f771e759a933a31b9ecb73c6..331462571a5738250139dfc9d4e8d8d452779370 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 e7397545124d9005990b90a2c3c47d0093b18808..0c14ac765eaa44aee1340025b3bf4bdda3206cdc 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 9e2dcb365c45a204a6eac861ce4e1ad3482db794..cd4f5268d53b46c0b1c158191fbe4a63595fa80a 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 {