Fixed the leading IconButton size (#110722)

diff --git a/dev/tools/gen_defaults/lib/icon_button_template.dart b/dev/tools/gen_defaults/lib/icon_button_template.dart
index 0e8a9b5..10a2009 100644
--- a/dev/tools/gen_defaults/lib/icon_button_template.dart
+++ b/dev/tools/gen_defaults/lib/icon_button_template.dart
@@ -108,7 +108,7 @@
     });
 
   @override
-  VisualDensity? get visualDensity => Theme.of(context).visualDensity;
+  VisualDensity? get visualDensity => VisualDensity.standard;
 
   @override
   MaterialTapTargetSize? get tapTargetSize => Theme.of(context).materialTapTargetSize;
diff --git a/examples/api/lib/material/icon_button/icon_button.3.dart b/examples/api/lib/material/icon_button/icon_button.3.dart
index 230d292..1e819a4 100644
--- a/examples/api/lib/material/icon_button/icon_button.3.dart
+++ b/examples/api/lib/material/icon_button/icon_button.3.dart
@@ -19,10 +19,6 @@
       theme: ThemeData(
         colorSchemeSeed: const Color(0xff6750a4),
         useMaterial3: true,
-        // Desktop and web platforms have a compact visual density by default.
-        // To see buttons with circular background on desktop/web, the "visualDensity"
-        // needs to be set to "VisualDensity.standard".
-        visualDensity: VisualDensity.standard,
       ),
       title: 'Icon Button Types',
       home: const Scaffold(
diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart
index b4e69b8..f04a3a3 100644
--- a/packages/flutter/lib/src/material/app_bar.dart
+++ b/packages/flutter/lib/src/material/app_bar.dart
@@ -1000,10 +1000,20 @@
       }
     }
     if (leading != null) {
-      leading = ConstrainedBox(
-        constraints: BoxConstraints.tightFor(width: widget.leadingWidth ?? _kLeadingWidth),
-        child: leading,
-      );
+      // Based on the Material Design 3 specs, the leading IconButton should have
+      // a size of 48x48, and a highlight size of 40x40. Users can also put other
+      // type of widgets on leading with the original config.
+      if (theme.useMaterial3) {
+        leading =  ConstrainedBox(
+          constraints: BoxConstraints.tightFor(width: widget.leadingWidth ?? _kLeadingWidth),
+          child: leading is IconButton ? Center(child: leading) : leading,
+        );
+      } else {
+        leading = ConstrainedBox(
+          constraints: BoxConstraints.tightFor(width: widget.leadingWidth ?? _kLeadingWidth),
+          child: leading,
+        );
+      }
     }
 
     Widget? title = widget.title;
@@ -1058,7 +1068,7 @@
     if (widget.actions != null && widget.actions!.isNotEmpty) {
       actions = Row(
         mainAxisSize: MainAxisSize.min,
-        crossAxisAlignment: CrossAxisAlignment.stretch,
+        crossAxisAlignment: theme.useMaterial3 ? CrossAxisAlignment.center : CrossAxisAlignment.stretch,
         children: widget.actions!,
       );
     } else if (hasEndDrawer) {
diff --git a/packages/flutter/lib/src/material/icon_button.dart b/packages/flutter/lib/src/material/icon_button.dart
index a1c8cbd..e196ec9 100644
--- a/packages/flutter/lib/src/material/icon_button.dart
+++ b/packages/flutter/lib/src/material/icon_button.dart
@@ -132,6 +132,11 @@
 /// precedence: widget property, [IconButtonTheme] property, [IconTheme] property and
 /// internal default property value.
 ///
+/// In Material Design 3, the [IconButton.visualDensity] defaults to [VisualDensity.standard]
+/// for all platforms; otherwise the button will have a rounded rectangle shape if
+/// the [IconButton.visualDensity] is set to [VisualDensity.compact]. Users can
+/// customize it by using [IconButtonTheme], [IconButton.style] or [IconButton.visualDensity].
+///
 /// {@tool dartpad}
 /// This sample shows creation of [IconButton] widgets for standard, filled,
 /// filled tonal and outlined types, as described in: https://m3.material.io/components/icon-buttons/overview
@@ -218,6 +223,9 @@
   ///
   /// {@macro flutter.material.themedata.visualDensity}
   ///
+  /// This property can be null. If null, it defaults to [VisualDensity.standard]
+  /// in Material Design 3 to make sure the button will be circular on all platforms.
+  ///
   /// See also:
   ///
   ///  * [ThemeData.visualDensity], which specifies the [visualDensity] for all
@@ -811,7 +819,7 @@
   /// * `mouseCursor`
   ///   * disabled - SystemMouseCursors.basic
   ///   * others - SystemMouseCursors.click
-  /// * `visualDensity` - theme.visualDensity
+  /// * `visualDensity` - VisualDensity.standard
   /// * `tapTargetSize` - theme.materialTapTargetSize
   /// * `animationDuration` - kThemeChangeDuration
   /// * `enableFeedback` - true
@@ -1053,7 +1061,7 @@
     });
 
   @override
-  VisualDensity? get visualDensity => Theme.of(context).visualDensity;
+  VisualDensity? get visualDensity => VisualDensity.standard;
 
   @override
   MaterialTapTargetSize? get tapTargetSize => Theme.of(context).materialTapTargetSize;
diff --git a/packages/flutter/lib/src/material/theme_data.dart b/packages/flutter/lib/src/material/theme_data.dart
index 87fc86f..6176c26 100644
--- a/packages/flutter/lib/src/material/theme_data.dart
+++ b/packages/flutter/lib/src/material/theme_data.dart
@@ -1267,6 +1267,11 @@
   ///
   /// A larger value translates to a spacing increase (less dense), and a
   /// smaller value translates to a spacing decrease (more dense).
+  ///
+  /// In Material Design 3, the [visualDensity] does not override the value of
+  /// [IconButton.visualDensity] which defaults to [VisualDensity.standard]
+  /// for all platforms. To override the default value of [IconButton.visualDensity],
+  /// use [ThemeData.iconButtonTheme] instead.
   /// {@endtemplate}
   final VisualDensity visualDensity;
 
diff --git a/packages/flutter/test/material/app_bar_test.dart b/packages/flutter/test/material/app_bar_test.dart
index 783758b..0ea45bd 100644
--- a/packages/flutter/test/material/app_bar_test.dart
+++ b/packages/flutter/test/material/app_bar_test.dart
@@ -693,10 +693,12 @@
     expect(iconColor(), color);
   });
 
-  testWidgets('leading button extends to edge and is square', (WidgetTester tester) async {
+  testWidgets('leading widget extends to edge and is square', (WidgetTester tester) async {
+    final ThemeData themeData = ThemeData(platform: TargetPlatform.android);
+    final bool material3 = themeData.useMaterial3;
     await tester.pumpWidget(
       MaterialApp(
-        theme: ThemeData(platform: TargetPlatform.android),
+        theme: themeData,
         home: Scaffold(
           appBar: AppBar(
             title: const Text('X'),
@@ -706,15 +708,52 @@
       ),
     );
 
-    final Finder hamburger = find.byTooltip('Open navigation menu');
-    expect(tester.getTopLeft(hamburger), Offset.zero);
-    expect(tester.getSize(hamburger), const Size(56.0, 56.0));
+    // Default IconButton has a size of (48x48) in M3, (56x56) in M2
+    final Finder hamburger = find.byType(IconButton);
+    expect(tester.getTopLeft(hamburger), material3 ? const Offset(4.0, 4.0) : Offset.zero);
+    expect(tester.getSize(hamburger), material3 ? const Size(48.0, 48.0) : const Size(56.0, 56.0));
+
+    await tester.pumpWidget(
+      MaterialApp(
+        theme: themeData,
+        home: Scaffold(
+          appBar: AppBar(
+            leading: Container(),
+            title: const Text('X'),
+          ),
+        ),
+      ),
+    );
+
+    // Default leading widget has a size of (56x56) for both M2 and M3
+    final Finder leadingBox = find.byType(Container);
+    expect(tester.getTopLeft(leadingBox), Offset.zero);
+    expect(tester.getSize(leadingBox), const Size(56.0, 56.0));
+
+    // The custom leading widget should still be 56x56 even if its size is smaller.
+    await tester.pumpWidget(
+      MaterialApp(
+        theme: themeData,
+        home: Scaffold(
+          appBar: AppBar(
+            leading: const SizedBox(height: 36, width: 36,),
+            title: const Text('X'),
+          ), // Doesn't really matter. Triggers a hamburger regardless.
+        ),
+      ),
+    );
+
+    final Finder leading = find.byType(SizedBox);
+    expect(tester.getTopLeft(leading), Offset.zero);
+    expect(tester.getSize(leading), const Size(56.0, 56.0));
   });
 
   testWidgets('test action is 4dp from edge and 48dp min', (WidgetTester tester) async {
+    final ThemeData theme = ThemeData(platform: TargetPlatform.android);
+    final bool material3 = theme.useMaterial3;
     await tester.pumpWidget(
       MaterialApp(
-        theme: ThemeData(platform: TargetPlatform.android),
+        theme: theme,
         home: Scaffold(
           appBar: AppBar(
             title: const Text('X'),
@@ -737,14 +776,14 @@
       ),
     );
 
-    final Finder addButton = find.byTooltip('Add');
+    final Finder addButton = find.widgetWithIcon(IconButton, Icons.add);
     expect(tester.getTopRight(addButton), const Offset(800.0, 0.0));
     // It's still the size it was plus the 2 * 8dp padding from IconButton.
     expect(tester.getSize(addButton), const Size(60.0 + 2 * 8.0, 56.0));
 
-    final Finder shareButton = find.byTooltip('Share');
+    final Finder shareButton = find.widgetWithIcon(IconButton, Icons.share);
     // The 20dp icon is expanded to fill the IconButton's touch target to 48dp.
-    expect(tester.getSize(shareButton), const Size(48.0, 56.0));
+    expect(tester.getSize(shareButton), material3 ? const Size(48.0, 48.0) : const Size(48.0, 56.0));
   });
 
   testWidgets('SliverAppBar default configuration', (WidgetTester tester) async {
diff --git a/packages/flutter/test/material/icon_button_test.dart b/packages/flutter/test/material/icon_button_test.dart
index 271db0f..5d65c37 100644
--- a/packages/flutter/test/material/icon_button_test.dart
+++ b/packages/flutter/test/material/icon_button_test.dart
@@ -275,11 +275,23 @@
 
   testWidgets('Small icons comply with VisualDensity requirements', (WidgetTester tester) async {
     final bool material3 = theme.useMaterial3;
+    final ThemeData themeDataM2 = ThemeData(
+      useMaterial3: material3,
+      visualDensity: const VisualDensity(horizontal: 1, vertical: -1),
+    );
+    final ThemeData themeDataM3 = ThemeData(
+      useMaterial3: material3,
+      iconButtonTheme: IconButtonThemeData(
+          style: IconButton.styleFrom(
+              visualDensity: const VisualDensity(horizontal: 1, vertical: -1)
+          )
+      ),
+    );
     await tester.pumpWidget(
       wrap(
         useMaterial3: material3,
         child: Theme(
-          data: ThemeData(visualDensity: const VisualDensity(horizontal: 1, vertical: -1), useMaterial3: material3),
+          data: material3 ? themeDataM3 : themeDataM2,
           child: IconButton(
             iconSize: 10.0,
             onPressed: mockOnPressedFunction.handler,
@@ -435,6 +447,7 @@
   });
 
   testWidgets('IconButton AppBar size', (WidgetTester tester) async {
+    final bool material3 = theme.useMaterial3;
     await tester.pumpWidget(
       MaterialApp(
         theme: theme,
@@ -454,7 +467,8 @@
 
     final RenderBox barBox = tester.renderObject(find.byType(AppBar));
     final RenderBox iconBox = tester.renderObject(find.byType(IconButton));
-    expect(iconBox.size.height, equals(barBox.size.height));
+    expect(iconBox.size.height, material3 ? 48 : equals(barBox.size.height));
+    expect(tester.getCenter(find.byType(IconButton)).dy, 28);
   });
 
   // This test is very similar to the '...explicit splashColor and highlightColor' test
@@ -1629,6 +1643,56 @@
     expect(find.byIcon(Icons.ac_unit), findsOneWidget);
   });
 
+  testWidgets('The visualDensity of M3 IconButton can be configured by IconButtonTheme, '
+      'but cannot be configured by ThemeData - M3' , (WidgetTester tester) async {
+    Future<void> buildTest({VisualDensity? iconButtonThemeVisualDensity, VisualDensity? themeVisualDensity}) async {
+      return tester.pumpWidget(
+        MaterialApp(
+          theme: ThemeData.from(colorScheme: colorScheme, useMaterial3: true).copyWith(
+              iconButtonTheme: IconButtonThemeData(
+                  style: IconButton.styleFrom(visualDensity: iconButtonThemeVisualDensity)
+              ),
+              visualDensity: themeVisualDensity
+          ),
+          home: Material(
+            child: Center(
+              child: IconButton(
+                onPressed: () {},
+                icon: const Icon(Icons.play_arrow),
+              ),
+            ),
+          ),
+        ),
+      );
+    }
+
+    await buildTest(iconButtonThemeVisualDensity: VisualDensity.standard);
+    final RenderBox box = tester.renderObject(find.byType(IconButton));
+    await tester.pumpAndSettle();
+    expect(box.size, equals(const Size(48, 48)));
+
+    await buildTest(iconButtonThemeVisualDensity: VisualDensity.compact);
+    await tester.pumpAndSettle();
+    expect(box.size, equals(const Size(40, 40)));
+
+    await buildTest(iconButtonThemeVisualDensity: const VisualDensity(horizontal: 3.0, vertical: 3.0));
+    await tester.pumpAndSettle();
+    expect(box.size, equals(const Size(64, 64)));
+
+    // ThemeData.visualDensity will be ignored because useMaterial3 is true
+    await buildTest(themeVisualDensity: VisualDensity.standard);
+    await tester.pumpAndSettle();
+    expect(box.size, equals(const Size(48, 48)));
+
+    await buildTest(themeVisualDensity: VisualDensity.compact);
+    await tester.pumpAndSettle();
+    expect(box.size, equals(const Size(48, 48)));
+
+    await buildTest(themeVisualDensity: const VisualDensity(horizontal: 3.0, vertical: 3.0));
+    await tester.pumpAndSettle();
+    expect(box.size, equals(const Size(48, 48)));
+  });
+
   group('IconTheme tests in Material 3', () {
     testWidgets('IconTheme overrides default values in M3', (WidgetTester tester) async {
       // Theme's IconTheme