Let `OverflowBox` be shrink-wrappable (#129095)

Close https://github.com/flutter/flutter/issues/129094

I have demonstrated how this PR fixes the problem using tests in #129094. I will further add tests in this PR if the PR looks roughly acceptable :)
diff --git a/packages/flutter/lib/src/rendering/shifted_box.dart b/packages/flutter/lib/src/rendering/shifted_box.dart
index dc6de8a..21a5152 100644
--- a/packages/flutter/lib/src/rendering/shifted_box.dart
+++ b/packages/flutter/lib/src/rendering/shifted_box.dart
@@ -533,6 +533,20 @@
   }
 }
 
+/// How much space should be occupied by the [OverflowBox] if there is no
+/// overflow.
+enum OverflowBoxFit {
+  /// The widget will size itself to be as large as the parent allows.
+  max,
+
+  /// The widget will follow the child's size.
+  ///
+  /// More specifically, the render object will size itself to match the size of
+  /// its child within the constraints of its parent, or as small as the
+  /// parent allows if no child is set.
+  deferToChild,
+}
+
 /// A render object that imposes different constraints on its child than it gets
 /// from its parent, possibly allowing the child to overflow the parent.
 ///
@@ -571,12 +585,14 @@
     double? maxWidth,
     double? minHeight,
     double? maxHeight,
+    OverflowBoxFit fit = OverflowBoxFit.max,
     super.alignment,
     super.textDirection,
   }) : _minWidth = minWidth,
        _maxWidth = maxWidth,
        _minHeight = minHeight,
-       _maxHeight = maxHeight;
+       _maxHeight = maxHeight,
+       _fit = fit;
 
   /// The minimum width constraint to give the child. Set this to null (the
   /// default) to use the constraint from the parent instead.
@@ -626,6 +642,24 @@
     markNeedsLayout();
   }
 
+  /// The way to size the render object.
+  ///
+  /// This only affects scenario when the child does not indeed overflow.
+  /// If set to [OverflowBoxFit.deferToChild], the render object will size
+  /// itself to match the size of its child within the constraints of its
+  /// parent, or as small as the parent allows if no child is set.
+  /// If set to [OverflowBoxFit.max] (the default), the
+  /// render object will size itself to be as large as the parent allows.
+  OverflowBoxFit get fit => _fit;
+  OverflowBoxFit _fit;
+  set fit(OverflowBoxFit value) {
+    if (_fit == value) {
+      return;
+    }
+    _fit = value;
+    markNeedsLayoutForSizedByParentChange();
+  }
+
   BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
     return BoxConstraints(
       minWidth: _minWidth ?? constraints.minWidth,
@@ -636,19 +670,46 @@
   }
 
   @override
-  bool get sizedByParent => true;
+  bool get sizedByParent {
+    switch (fit) {
+      case OverflowBoxFit.max:
+        return true;
+      case OverflowBoxFit.deferToChild:
+        // If deferToChild, the size will be as small as its child when non-overflowing,
+        // thus it cannot be sizedByParent.
+        return false;
+    }
+  }
 
   @override
   @protected
   Size computeDryLayout(covariant BoxConstraints constraints) {
-    return constraints.biggest;
+    switch (fit) {
+      case OverflowBoxFit.max:
+        return constraints.biggest;
+      case OverflowBoxFit.deferToChild:
+        return child?.getDryLayout(constraints) ?? constraints.smallest;
+    }
   }
 
   @override
   void performLayout() {
     if (child != null) {
-      child?.layout(_getInnerConstraints(constraints), parentUsesSize: true);
+      child!.layout(_getInnerConstraints(constraints), parentUsesSize: true);
+      switch (fit) {
+        case OverflowBoxFit.max:
+          assert(sizedByParent);
+        case OverflowBoxFit.deferToChild:
+          size = constraints.constrain(child!.size);
+      }
       alignChild();
+    } else {
+      switch (fit) {
+        case OverflowBoxFit.max:
+          assert(sizedByParent);
+        case OverflowBoxFit.deferToChild:
+          size = constraints.smallest;
+      }
     }
   }
 
@@ -659,6 +720,7 @@
     properties.add(DoubleProperty('maxWidth', maxWidth, ifNull: 'use parent maxWidth constraint'));
     properties.add(DoubleProperty('minHeight', minHeight, ifNull: 'use parent minHeight constraint'));
     properties.add(DoubleProperty('maxHeight', maxHeight, ifNull: 'use parent maxHeight constraint'));
+    properties.add(EnumProperty<OverflowBoxFit>('fit', fit));
   }
 }
 
diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart
index 3e0a894..f565298 100644
--- a/packages/flutter/lib/src/widgets/basic.dart
+++ b/packages/flutter/lib/src/widgets/basic.dart
@@ -3051,6 +3051,7 @@
     this.maxWidth,
     this.minHeight,
     this.maxHeight,
+    this.fit = OverflowBoxFit.max,
     super.child,
   });
 
@@ -3090,6 +3091,16 @@
   /// default) to use the constraint from the parent instead.
   final double? maxHeight;
 
+  /// The way to size the render object.
+  ///
+  /// This only affects scenario when the child does not indeed overflow.
+  /// If set to [OverflowBoxFit.deferToChild], the render object will size itself to
+  /// match the size of its child within the constraints of its parent or be
+  /// as small as the parent allows if no child is set. If set to
+  /// [OverflowBoxFit.max] (the default), the render object will size itself
+  /// to be as large as the parent allows.
+  final OverflowBoxFit fit;
+
   @override
   RenderConstrainedOverflowBox createRenderObject(BuildContext context) {
     return RenderConstrainedOverflowBox(
@@ -3098,6 +3109,7 @@
       maxWidth: maxWidth,
       minHeight: minHeight,
       maxHeight: maxHeight,
+      fit: fit,
       textDirection: Directionality.maybeOf(context),
     );
   }
@@ -3110,6 +3122,7 @@
       ..maxWidth = maxWidth
       ..minHeight = minHeight
       ..maxHeight = maxHeight
+      ..fit = fit
       ..textDirection = Directionality.maybeOf(context);
   }
 
@@ -3121,6 +3134,7 @@
     properties.add(DoubleProperty('maxWidth', maxWidth, defaultValue: null));
     properties.add(DoubleProperty('minHeight', minHeight, defaultValue: null));
     properties.add(DoubleProperty('maxHeight', maxHeight, defaultValue: null));
+    properties.add(EnumProperty<OverflowBoxFit>('fit', fit));
   }
 }
 
diff --git a/packages/flutter/test/rendering/limited_box_test.dart b/packages/flutter/test/rendering/limited_box_test.dart
index 8d5cea2..cf6b200 100644
--- a/packages/flutter/test/rendering/limited_box_test.dart
+++ b/packages/flutter/test/rendering/limited_box_test.dart
@@ -42,6 +42,7 @@
         ' │ maxWidth: Infinity\n'
         ' │ minHeight: 0.0\n'
         ' │ maxHeight: Infinity\n'
+        ' │ fit: max\n'
         ' │\n'
         ' └─child: RenderLimitedBox#00000 relayoutBoundary=up1 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE\n'
         '   │ parentData: offset=Offset(350.0, 200.0) (can use size)\n'
@@ -128,6 +129,7 @@
         ' │ maxWidth: 500.0\n'
         ' │ minHeight: 0.0\n'
         ' │ maxHeight: Infinity\n'
+        ' │ fit: max\n'
         ' │\n'
         ' └─child: RenderLimitedBox#00000 relayoutBoundary=up1 NEEDS-PAINT\n'
         '     parentData: offset=Offset(395.0, 300.0) (can use size)\n'
@@ -164,6 +166,7 @@
         ' │ maxWidth: use parent maxWidth constraint\n'
         ' │ minHeight: use parent minHeight constraint\n'
         ' │ maxHeight: use parent maxHeight constraint\n'
+        ' │ fit: max\n'
         ' │\n'
         ' └─child: RenderLimitedBox#00000 relayoutBoundary=up1 NEEDS-PAINT\n'
         '     parentData: offset=Offset(395.0, 0.0) (can use size)\n'
diff --git a/packages/flutter/test/widgets/overflow_box_test.dart b/packages/flutter/test/widgets/overflow_box_test.dart
index bc97f50..1d01316 100644
--- a/packages/flutter/test/widgets/overflow_box_test.dart
+++ b/packages/flutter/test/widgets/overflow_box_test.dart
@@ -31,6 +31,64 @@
     expect(box.size, equals(const Size(100.0, 50.0)));
   });
 
+  // Adapted from https://github.com/flutter/flutter/issues/129094
+  group('when fit is OverflowBoxFit.deferToChild', () {
+    group('OverflowBox behavior with long and short content', () {
+      for (final bool contentSuperLong in <bool>[false, true]) {
+        testWidgetsWithLeakTracking('contentSuperLong=$contentSuperLong', (WidgetTester tester) async {
+          final GlobalKey<State<StatefulWidget>> key = GlobalKey();
+
+          final Column child = Column(
+            mainAxisSize: MainAxisSize.min,
+            children: <Widget>[
+              SizedBox(width: 100, height: contentSuperLong ? 10000 : 100),
+            ],
+          );
+
+          await tester.pumpWidget(Directionality(
+            textDirection: TextDirection.ltr,
+            child: Stack(
+              children: <Widget>[
+                Container(
+                  key: key,
+                  child: OverflowBox(
+                    maxHeight: 1000000,
+                    fit: OverflowBoxFit.deferToChild,
+                    child: child,
+                  ),
+                ),
+              ],
+            ),
+          ));
+
+          expect(tester.getBottomLeft(find.byKey(key)).dy, contentSuperLong ? 600 : 100);
+        });
+      }
+    });
+
+    testWidgetsWithLeakTracking('no child', (WidgetTester tester) async {
+      final GlobalKey<State<StatefulWidget>> key = GlobalKey();
+
+      await tester.pumpWidget(Directionality(
+        textDirection: TextDirection.ltr,
+        child: Stack(
+          children: <Widget>[
+            Container(
+              key: key,
+              child: const OverflowBox(
+                maxHeight: 1000000,
+                fit: OverflowBoxFit.deferToChild,
+                // no child
+              ),
+            ),
+          ],
+        ),
+      ));
+
+      expect(tester.getBottomLeft(find.byKey(key)).dy, 0);
+    });
+  });
+
   testWidgetsWithLeakTracking('OverflowBox implements debugFillProperties', (WidgetTester tester) async {
     final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
     const OverflowBox(
@@ -48,6 +106,7 @@
       'maxWidth: 2.0',
       'minHeight: 3.0',
       'maxHeight: 4.0',
+      'fit: max',
     ]);
   });