From 3d0607b543c3dad430c06858a02020b5c8b51c76 Mon Sep 17 00:00:00 2001 From: LongCatIsLooong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Fri, 16 Dec 2022 11:27:03 -0800 Subject: [PATCH] Defer `systemFontsDidChange` to the transientCallbacks phase (#117123) * Always defer systemFontsDidChange to transientCallbacks phase * unnecessary import --- .../flutter/lib/src/rendering/object.dart | 47 ++++++- .../flutter/lib/src/rendering/paragraph.dart | 32 +---- .../test/painting/system_fonts_test.dart | 131 +++++++++--------- 3 files changed, 110 insertions(+), 100 deletions(-) diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index 36cf10a5d0e1..10e39d844e7a 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -10,6 +10,7 @@ import 'package:flutter/animation.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/painting.dart'; +import 'package:flutter/scheduler.dart'; import 'package:flutter/semantics.dart'; import 'debug.dart'; @@ -4011,13 +4012,18 @@ mixin ContainerRenderObjectMixin verifyMarkedNeedsLayoutDuringTransientCallbacksPhase(WidgetTester tester, RenderObject renderObject) async { + assert(!renderObject.debugNeedsLayout); - const Map data = { - 'type': 'fontsChange', - }; - await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage( - 'flutter/system', - SystemChannels.system.codec.encodeMessage(data), - (ByteData? data) { }, - ); + const Map data = { + 'type': 'fontsChange', + }; + await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage( + 'flutter/system', + SystemChannels.system.codec.encodeMessage(data), + (ByteData? data) { }, + ); - final Completer animation = Completer(); - tester.binding.scheduleFrameCallback((Duration timeStamp) { - animation.complete(renderObject.debugNeedsLayout); - }); - expect(renderObject.debugNeedsLayout, isFalse); - await tester.pump(); - expect(await animation.future, isTrue); + final Completer animation = Completer(); + tester.binding.scheduleFrameCallback((Duration timeStamp) { + animation.complete(renderObject.debugNeedsLayout); }); - testWidgets('Safe to query RenderParagraph for text layout after system fonts changes', (WidgetTester tester) async { + // The fonts change does not mark the render object as needing layout + // immediately. + expect(renderObject.debugNeedsLayout, isFalse); + await tester.pump(); + expect(await animation.future, isTrue); +} + +void main() { + testWidgets('RenderParagraph relayout upon system fonts changes', (WidgetTester tester) async { await tester.pumpWidget( const MaterialApp( home: Text('text widget'), ), ); - const Map data = { - 'type': 'fontsChange', - }; - await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage( - 'flutter/system', - SystemChannels.system.codec.encodeMessage(data), - (ByteData? data) { }, - ); - final RenderParagraph paragraph = tester.renderObject(find.text('text widget')); - Object? exception; - try { - paragraph.getPositionForOffset(Offset.zero); - paragraph.hitTest(BoxHitTestResult(), position: Offset.zero); - } catch (e) { - exception = e; - } - expect(exception, isNull); + final RenderObject renderObject = tester.renderObject(find.text('text widget')); + await verifyMarkedNeedsLayoutDuringTransientCallbacksPhase(tester, renderObject); }); + testWidgets( + 'Safe to query a RelayoutWhenSystemFontsChangeMixin for text layout after system fonts changes', + (WidgetTester tester) async { + final _RenderCustomRelayoutWhenSystemFontsChange child = _RenderCustomRelayoutWhenSystemFontsChange(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WidgetToRenderBoxAdapter(renderBox: child), + ), + ); + const Map data = { + 'type': 'fontsChange', + }; + await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage( + 'flutter/system', + SystemChannels.system.codec.encodeMessage(data), + (ByteData? data) { }, + ); + expect(child.hasValidTextLayout, isTrue); + }, + ); + testWidgets('RenderEditable relayout upon system fonts changes', (WidgetTester tester) async { await tester.pumpWidget( const MaterialApp( home: SelectableText('text widget'), ), ); - const Map data = { - 'type': 'fontsChange', - }; - await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage( - 'flutter/system', - SystemChannels.system.codec.encodeMessage(data), - (ByteData? data) { }, - ); + final EditableTextState state = tester.state(find.byType(EditableText)); - expect(state.renderEditable.debugNeedsLayout, isTrue); + await verifyMarkedNeedsLayoutDuringTransientCallbacksPhase(tester, state.renderEditable); }); testWidgets('Banner repaint upon system fonts changes', (WidgetTester tester) async { @@ -210,11 +208,8 @@ void main() { SystemChannels.system.codec.encodeMessage(data), (ByteData? data) { }, ); - final RenderObject renderObject = tester.renderObject(find.byType(RangeSlider)); - - late bool sliderBoxNeedsLayout; - renderObject.visitChildren((RenderObject child) {sliderBoxNeedsLayout = child.debugNeedsLayout;}); - expect(sliderBoxNeedsLayout, isTrue); + final RenderObject renderObject = tester.renderObject(find.byWidgetPredicate((Widget widget) => widget.runtimeType.toString() == '_RangeSliderRenderObjectWidget')); + await verifyMarkedNeedsLayoutDuringTransientCallbacksPhase(tester, renderObject); }); testWidgets('Slider relayout upon system fonts changes', (WidgetTester tester) async { @@ -228,17 +223,9 @@ void main() { ), ), ); - const Map data = { - 'type': 'fontsChange', - }; - await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage( - 'flutter/system', - SystemChannels.system.codec.encodeMessage(data), - (ByteData? data) { }, - ); // _RenderSlider is the last render object in the tree. final RenderObject renderObject = tester.allRenderObjects.last; - expect(renderObject.debugNeedsLayout, isTrue); + await verifyMarkedNeedsLayoutDuringTransientCallbacksPhase(tester, renderObject); }); testWidgets('TimePicker relayout upon system fonts changes', (WidgetTester tester) async { @@ -289,3 +276,19 @@ void main() { expect(renderObject.debugNeedsPaint, isTrue); }); } + +class _RenderCustomRelayoutWhenSystemFontsChange extends RenderBox with RelayoutWhenSystemFontsChangeMixin { + bool hasValidTextLayout = false; + + @override + void systemFontsDidChange() { + super.systemFontsDidChange(); + hasValidTextLayout = false; + } + + @override + void performLayout() { + size = constraints.biggest; + hasValidTextLayout = true; + } +}