diff --git a/lib/ui/compositing.dart b/lib/ui/compositing.dart index 7550a3ac19f1a..8fdec02dc90e4 100644 --- a/lib/ui/compositing.dart +++ b/lib/ui/compositing.dart @@ -80,6 +80,9 @@ base class _NativeScene extends NativeFieldWrapperClass1 implements Scene { @override @Native)>(symbol: 'Scene::dispose') external void dispose(); + + @override + String toString() => 'Scene'; } // Lightweight wrapper of a native layer object. @@ -951,4 +954,7 @@ base class _NativeSceneBuilder extends NativeFieldWrapperClass1 implements Scene @Native, Handle)>(symbol: 'SceneBuilder::build') external void _build(Scene outScene); + + @override + String toString() => 'SceneBuilder'; } diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 043e5c807b63d..0ae3597dddcfc 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -2131,6 +2131,9 @@ base class _NativeCodec extends NativeFieldWrapperClass1 implements Codec { @override @Native)>(symbol: 'Codec::dispose') external void dispose(); + + @override + String toString() => 'Codec(${_cachedFrameCount == null ? "" : "$_cachedFrameCount frames"})'; } /// Instantiates an image [Codec]. @@ -3110,6 +3113,9 @@ base class _NativePath extends NativeFieldWrapperClass1 implements Path { PathMetrics computeMetrics({bool forceClosed = false}) { return PathMetrics._(this, forceClosed); } + + @override + String toString() => 'Path'; } /// The geometric description of a tangent: the angle at a point. @@ -6238,6 +6244,9 @@ base class _NativeCanvas extends NativeFieldWrapperClass1 implements Canvas { @Native, Pointer, Uint32, Double, Bool)>(symbol: 'Canvas::drawShadow') external void _drawShadow(_NativePath path, int color, double elevation, bool transparentOccluder); + + @override + String toString() => 'Canvas(recording: ${_recorder != null})'; } /// Signature for [Picture] lifecycle events. @@ -6381,6 +6390,9 @@ base class _NativePicture extends NativeFieldWrapperClass1 implements Picture { @override @Native)>(symbol: 'Picture::GetAllocationSize', isLeaf: true) external int get approximateBytesUsed; + + @override + String toString() => 'Picture'; } /// Records a [Picture] containing a sequence of graphical operations. @@ -6438,6 +6450,9 @@ base class _NativePictureRecorder extends NativeFieldWrapperClass1 implements Pi external void _endRecording(_NativePicture outPicture); _NativeCanvas? _canvas; + + @override + String toString() => 'PictureRecorder(recording: $isRecording)'; } /// A single shadow. @@ -6895,6 +6910,9 @@ base class _NativeImageDescriptor extends NativeFieldWrapperClass1 implements Im @Native, Handle, Int32, Int32)>(symbol: 'ImageDescriptor::instantiateCodec') external void _instantiateCodec(Codec outCodec, int targetWidth, int targetHeight); + + @override + String toString() => 'ImageDescriptor(width: ${_width ?? '?'}, height: ${_height ?? '?'}, bytes per pixel: ${_bytesPerPixel ?? '?'})'; } /// Generic callback signature, used by [_futurize]. diff --git a/lib/ui/semantics.dart b/lib/ui/semantics.dart index de8a6956ce44b..3a99e8322a817 100644 --- a/lib/ui/semantics.dart +++ b/lib/ui/semantics.dart @@ -1027,6 +1027,9 @@ base class _NativeSemanticsUpdateBuilder extends NativeFieldWrapperClass1 implem } @Native, Handle)>(symbol: 'SemanticsUpdateBuilder::build') external void _build(_NativeSemanticsUpdate outSemanticsUpdate); + + @override + String toString() => 'SemanticsUpdateBuilder'; } /// An opaque object representing a batch of semantics updates. @@ -1056,4 +1059,7 @@ base class _NativeSemanticsUpdate extends NativeFieldWrapperClass1 implements Se @override @Native)>(symbol: 'SemanticsUpdate::dispose') external void dispose(); + + @override + String toString() => 'SemanticsUpdate'; } diff --git a/lib/ui/text.dart b/lib/ui/text.dart index ff4665162577f..53f3e6a9533c3 100644 --- a/lib/ui/text.dart +++ b/lib/ui/text.dart @@ -3334,6 +3334,27 @@ base class _NativeParagraph extends NativeFieldWrapperClass1 implements Paragrap }()); return disposed ?? (throw StateError('$runtimeType.debugDisposed is only available when asserts are enabled.')); } + + @override + String toString() { + String? result; + assert(() { + if (_disposed && _needsLayout) { + result = 'Paragraph(DISPOSED while dirty)'; + } + if (_disposed && !_needsLayout) { + result = 'Paragraph(DISPOSED)'; + } + return true; + }()); + if (result != null) { + return result!; + } + if (_needsLayout) { + return 'Paragraph(dirty)'; + } + return 'Paragraph()'; + } } /// Builds a [Paragraph] containing text with the given styling information. @@ -3657,6 +3678,9 @@ base class _NativeParagraphBuilder extends NativeFieldWrapperClass1 implements P @Native, Handle)>(symbol: 'ParagraphBuilder::build') external void _build(_NativeParagraph outParagraph); + + @override + String toString() => 'ParagraphBuilder'; } /// Loads a font from a buffer and makes it available for rendering text. diff --git a/testing/dart/BUILD.gn b/testing/dart/BUILD.gn index 3cef3ae62838e..033204aa682fb 100644 --- a/testing/dart/BUILD.gn +++ b/testing/dart/BUILD.gn @@ -45,6 +45,7 @@ tests = [ "serial_gc_test.dart", "spawn_helper.dart", "spawn_test.dart", + "stringification_test.dart", "task_order_test.dart", "text_test.dart", "window_test.dart", diff --git a/testing/dart/stringification_test.dart b/testing/dart/stringification_test.dart new file mode 100644 index 0000000000000..4e6f25c5f0ab9 --- /dev/null +++ b/testing/dart/stringification_test.dart @@ -0,0 +1,43 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:typed_data'; +import 'dart:ui'; + +import 'package:litetest/litetest.dart'; + +final Uint8List imageData = Uint8List.fromList([ // Small WebP file + 0x52, 0x49, 0x46, 0x46, 0x12, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50, 0x38, 0x4c, // |RIFF....WEBPVP8L| + 0x06, 0x00, 0x00, 0x00, 0x2f, 0x41, 0x6c, 0x6f, 0x00, 0x6b, // |..../Alo.k| +]); + +void main() { + test('Stringification of native objects exposed in Dart', () async { + expect(SemanticsUpdateBuilder().toString(), 'SemanticsUpdateBuilder'); + expect(SemanticsUpdateBuilder().build().toString(), 'SemanticsUpdate'); + expect(ParagraphBuilder(ParagraphStyle()).toString(), 'ParagraphBuilder'); + expect(ParagraphBuilder(ParagraphStyle()).build().toString(), 'Paragraph(dirty)'); + expect((await instantiateImageCodec(imageData)).toString(), 'Codec()'); + expect(Path().toString(), 'Path'); + final PictureRecorder recorder = PictureRecorder(); + expect(recorder.toString(), 'PictureRecorder(recording: false)'); + final Canvas canvas = Canvas(recorder); + expect(recorder.toString(), 'PictureRecorder(recording: true)'); + expect(canvas.toString(), 'Canvas(recording: true)'); + final Picture picture = recorder.endRecording(); + expect(recorder.toString(), 'PictureRecorder(recording: false)'); + expect(canvas.toString(), 'Canvas(recording: false)'); + expect(picture.toString(), 'Picture'); + expect( + ImageDescriptor.raw( + await ImmutableBuffer.fromUint8List(Uint8List.fromList([0, 0, 0, 0])), + width: 1, + height: 1, + pixelFormat: PixelFormat.rgba8888, + ).toString(), 'ImageDescriptor(width: 1, height: 1, bytes per pixel: 4)', + ); + expect(SceneBuilder().toString(), 'SceneBuilder'); + expect(SceneBuilder().build().toString(), 'Scene'); + }); +}