Skip to content

CupertinoContextMenu child respects available screen width#175300

Merged
auto-submit[bot] merged 3 commits intoflutter:masterfrom
victorsanni:context-menu-fix
Sep 17, 2025
Merged

CupertinoContextMenu child respects available screen width#175300
auto-submit[bot] merged 3 commits intoflutter:masterfrom
victorsanni:context-menu-fix

Conversation

@victorsanni
Copy link
Contributor

@victorsanni victorsanni commented Sep 13, 2025

@github-actions github-actions bot added framework flutter/packages/flutter repository. See also f: labels. f: cupertino flutter/packages/flutter/cupertino repository labels Sep 13, 2025
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request fixes an issue where the CupertinoContextMenu child widget would not respect the available screen width, potentially causing layout overflows. The fix involves calculating the available width for the child, accounting for screen padding and the context menu's own width in landscape mode, and applying this as a maxWidth constraint during layout. New widget tests are added for both portrait and landscape orientations to verify the fix.

My review focuses on improving the new tests to be more specific and robust. I've suggested adding explicit assertions to check that the child's width is correctly constrained, which will better validate the fix and prevent future regressions.

Comment on lines +1457 to +1529
testWidgets('CupertinoContextMenu respects available screen width - Portrait', (
WidgetTester tester,
) async {
const Size portraitScreenSize = Size(300.0, 350.0);
await binding.setSurfaceSize(portraitScreenSize);
addTearDown(() => binding.setSurfaceSize(null));

final Widget child = getChild();
await tester.pumpWidget(
MediaQuery(
data: const MediaQueryData(size: portraitScreenSize),
child: CupertinoApp(
home: Center(
child: CupertinoContextMenu(
actions: <Widget>[
CupertinoContextMenuAction(child: const Text('Test'), onPressed: () {}),
],
child: child,
),
),
),
),
);

expect(find.byWidget(child), findsOneWidget);
final Rect childRect = tester.getRect(find.byWidget(child));

// Start a press on the child.
final TestGesture gesture = await tester.startGesture(childRect.center);
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
await gesture.up();
await tester.pumpAndSettle();

expect(tester.takeException(), null);
});

testWidgets('CupertinoContextMenu respects available screen width - Landscape', (
WidgetTester tester,
) async {
const Size landscapeScreenSize = Size(350.0, 300.0);
await binding.setSurfaceSize(landscapeScreenSize);
addTearDown(() => binding.setSurfaceSize(null));

final Widget child = getChild(width: 500);
await tester.pumpWidget(
MediaQuery(
data: const MediaQueryData(size: landscapeScreenSize),
child: CupertinoApp(
home: Center(
child: CupertinoContextMenu(
actions: <Widget>[
CupertinoContextMenuAction(child: const Text('Test'), onPressed: () {}),
],
child: child,
),
),
),
),
);

expect(find.byWidget(child), findsOneWidget);
final Rect childRect = tester.getRect(find.byWidget(child));

// Start a press on the child.
final TestGesture gesture = await tester.startGesture(childRect.center);
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
await gesture.up();
await tester.pumpAndSettle();

expect(tester.takeException(), null);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

These new tests are great for ensuring CupertinoContextMenu respects screen width. However, they currently only check that no exceptions are thrown. To make them more robust and to specifically verify the fix, I suggest adding assertions to check that the child widget's size is correctly constrained in both portrait and landscape modes.

  testWidgets('CupertinoContextMenu respects available screen width - Portrait', (
    WidgetTester tester,
  ) async {
    const Size portraitScreenSize = Size(300.0, 350.0);
    await binding.setSurfaceSize(portraitScreenSize);
    addTearDown(() => binding.setSurfaceSize(null));

    final Widget child = getChild();
    await tester.pumpWidget(
      MediaQuery(
        data: const MediaQueryData(size: portraitScreenSize),
        child: CupertinoApp(
          home: Center(
            child: CupertinoContextMenu(
              actions: <Widget>[
                CupertinoContextMenuAction(child: const Text('Test'), onPressed: () {}),
              ],
              child: child,
            ),
          ),
        ),
      ),
    );

    expect(find.byWidget(child), findsOneWidget);
    final Rect childRect = tester.getRect(find.byWidget(child));

    // Start a press on the child.
    final TestGesture gesture = await tester.startGesture(childRect.center);
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 500));
    await gesture.up();
    await tester.pumpAndSettle();

    expect(tester.takeException(), null);

    // Verify the child width is constrained correctly.
    expect(findStatic(), findsOneWidget);
    final Size fittedBoxSize = tester.getSize(findFittedBox());
    // availableWidth = 300.0 (screen width) - 2 * 20.0 (padding) = 260.0
    expect(fittedBoxSize.width, 260.0);
  });

  testWidgets('CupertinoContextMenu respects available screen width - Landscape', (
    WidgetTester tester,
  ) async {
    const Size landscapeScreenSize = Size(350.0, 300.0);
    await binding.setSurfaceSize(landscapeScreenSize);
    addTearDown(() => binding.setSurfaceSize(null));

    final Widget child = getChild(width: 500);
    await tester.pumpWidget(
      MediaQuery(
        data: const MediaQueryData(size: landscapeScreenSize),
        child: CupertinoApp(
          home: Center(
            child: CupertinoContextMenu(
              actions: <Widget>[
                CupertinoContextMenuAction(child: const Text('Test'), onPressed: () {}),
              ],
              child: child,
            ),
          ),
        ),
      ),
    );

    expect(find.byWidget(child), findsOneWidget);
    final Rect childRect = tester.getRect(find.byWidget(child));

    // Start a press on the child.
    final TestGesture gesture = await tester.startGesture(childRect.center);
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 500));
    await gesture.up();
    await tester.pumpAndSettle();

    expect(tester.takeException(), null);

    // Verify the child width is constrained correctly.
    expect(findStatic(), findsOneWidget);
    final Size fittedBoxSize = tester.getSize(findFittedBox());
    // availableWidth = 350.0 (screen width) - 2 * 20.0 (padding) = 310.0
    // availableWidthForChild = 310.0 - 250.0 (menu width) = 60.0
    expect(fittedBoxSize.width, 60.0);
  });

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if this code from the bot would work, but you might be able to try getting the x an y coordinates of the four corners of the context menu and seeing if they are within the limits you expect and that might be more robust then just checking for exceptions.

Copy link
Contributor

@MitchellGoodwin MitchellGoodwin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approach looks good, I left two comments


final double availableHeightForChild =
screenBounds.height - _ContextMenuRouteStaticState._kPadding;
final double availableWidth = screenBounds.width - _ContextMenuRouteStaticState._kPadding * 2;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like elsewhere in the file, _kPadding is used without doubling it, so is multiplying it by two necessary here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the native context menu applies a vertical margin only to the bottom but not the top, but the horizontal margin is applied to the left and right sides.

The other uses of the horizontal _kPadding (that aren't doubled) refer to the padding between the context menu child and the menu sheet.

Comment on lines +1457 to +1529
testWidgets('CupertinoContextMenu respects available screen width - Portrait', (
WidgetTester tester,
) async {
const Size portraitScreenSize = Size(300.0, 350.0);
await binding.setSurfaceSize(portraitScreenSize);
addTearDown(() => binding.setSurfaceSize(null));

final Widget child = getChild();
await tester.pumpWidget(
MediaQuery(
data: const MediaQueryData(size: portraitScreenSize),
child: CupertinoApp(
home: Center(
child: CupertinoContextMenu(
actions: <Widget>[
CupertinoContextMenuAction(child: const Text('Test'), onPressed: () {}),
],
child: child,
),
),
),
),
);

expect(find.byWidget(child), findsOneWidget);
final Rect childRect = tester.getRect(find.byWidget(child));

// Start a press on the child.
final TestGesture gesture = await tester.startGesture(childRect.center);
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
await gesture.up();
await tester.pumpAndSettle();

expect(tester.takeException(), null);
});

testWidgets('CupertinoContextMenu respects available screen width - Landscape', (
WidgetTester tester,
) async {
const Size landscapeScreenSize = Size(350.0, 300.0);
await binding.setSurfaceSize(landscapeScreenSize);
addTearDown(() => binding.setSurfaceSize(null));

final Widget child = getChild(width: 500);
await tester.pumpWidget(
MediaQuery(
data: const MediaQueryData(size: landscapeScreenSize),
child: CupertinoApp(
home: Center(
child: CupertinoContextMenu(
actions: <Widget>[
CupertinoContextMenuAction(child: const Text('Test'), onPressed: () {}),
],
child: child,
),
),
),
),
);

expect(find.byWidget(child), findsOneWidget);
final Rect childRect = tester.getRect(find.byWidget(child));

// Start a press on the child.
final TestGesture gesture = await tester.startGesture(childRect.center);
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
await gesture.up();
await tester.pumpAndSettle();

expect(tester.takeException(), null);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if this code from the bot would work, but you might be able to try getting the x an y coordinates of the four corners of the context menu and seeing if they are within the limits you expect and that might be more robust then just checking for exceptions.

Copy link
Contributor

@MitchellGoodwin MitchellGoodwin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@victorsanni victorsanni added the autosubmit Merge PR when tree becomes green via auto submit App label Sep 17, 2025
@auto-submit auto-submit bot added this pull request to the merge queue Sep 17, 2025
Merged via the queue into flutter:master with commit 48d2277 Sep 17, 2025
79 checks passed
@flutter-dashboard flutter-dashboard bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Sep 17, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Sep 18, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Sep 18, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Sep 18, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Sep 18, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Sep 19, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Sep 19, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Sep 19, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Sep 19, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Sep 19, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Sep 20, 2025
auto-submit bot pushed a commit to flutter/packages that referenced this pull request Sep 20, 2025
Roll Flutter from 8f94cb0 to 9ff2767 (56 revisions)

flutter/flutter@8f94cb0...9ff2767

2025-09-20 [email protected] Roll Fuchsia Linux SDK from 0_jKqLGnkILvQ5C8a... to CcCe3HpQtBYhTZscb... (flutter/flutter#175698)
2025-09-20 [email protected] Roll Dart SDK from e6e9248aee4f to 9e943fe076c8 (1 revision) (flutter/flutter#175697)
2025-09-20 [email protected] Add `menuController` to `DropdownMenu` (flutter/flutter#175039)
2025-09-20 [email protected] Roll Skia from 1dae085e2f31 to a38a531dec1d (3 revisions) (flutter/flutter#175694)
2025-09-20 [email protected] [a11y] TimePicker clock is unnecessarily announced (flutter/flutter#175570)
2025-09-20 [email protected] Roll Dart SDK from 78e68d1a7dbf to e6e9248aee4f (4 revisions) (flutter/flutter#175690)
2025-09-19 [email protected] Roll Skia from b56003bf2c20 to 1dae085e2f31 (1 revision) (flutter/flutter#175674)
2025-09-19 [email protected] Update `CODEOWNERS` (for dev-tooling) (flutter/flutter#175201)
2025-09-19 [email protected] [a11y-app] Add label to TextFormField in AutoCompleteUseCase. (flutter/flutter#175576)
2025-09-19 [email protected] Fix RadioGroup single selection check. (flutter/flutter#175654)
2025-09-19 [email protected] Roll Packages from f2a65fd to 3d5c419 (2 revisions) (flutter/flutter#175668)
2025-09-19 [email protected] Roll Skia from c74d2bdbd93c to b56003bf2c20 (2 revisions) (flutter/flutter#175665)
2025-09-19 [email protected] Add `CupertinoLinearActivityIndicator` (flutter/flutter#170108)
2025-09-19 [email protected] [ Widget Preview ] Don't update filtered preview set when selecting non-source files (flutter/flutter#175596)
2025-09-19 [email protected] Roll Dart SDK from 2c79803c97db to 78e68d1a7dbf (3 revisions) (flutter/flutter#175646)
2025-09-19 [email protected] Delete unused web_unicode library (flutter/flutter#174896)
2025-09-19 [email protected] Roll Skia from 684f3a831216 to c74d2bdbd93c (2 revisions) (flutter/flutter#175644)
2025-09-19 [email protected] Roll Skia from 462bdece17bf to 684f3a831216 (3 revisions) (flutter/flutter#175641)
2025-09-19 [email protected] Roll Skia from a2c38aa9df80 to 462bdece17bf (11 revisions) (flutter/flutter#175629)
2025-09-18 [email protected] fix(tool): Use merge-base for content hash in detached HEAD (flutter/flutter#175554)
2025-09-18 [email protected] [web] Unskip Cupertino datepicker golden tests in Skwasm (flutter/flutter#174666)
2025-09-18 [email protected] Update rules to include extension rules (flutter/flutter#175618)
2025-09-18 [email protected] [engine] Cleanup Fuchsia FDIO library dependencies (flutter/flutter#174847)
2025-09-18 [email protected] Make sure that a CloseButton doesn't crash in 0x0 environment (flutter/flutter#172902)
2025-09-18 [email protected] [ Tool ] Serve DevTools from DDS, remove ResidentDevToolsHandler (flutter/flutter#174580)
2025-09-18 [email protected] [engine][fuchsia] Update to Fuchsia API level 28 and roll latest GN SDK (flutter/flutter#175425)
2025-09-18 [email protected] Added a 36 device for Firebase Lab Testing (flutter/flutter#175613)
2025-09-18 [email protected] Roll Dart SDK from 09a101793af4 to 2c79803c97db (2 revisions) (flutter/flutter#175608)
2025-09-18 [email protected] Engine Support for Dynamic View Resizing (flutter/flutter#173610)
2025-09-18 [email protected] Roll Packages from fdee698 to f2a65fd (3 revisions) (flutter/flutter#175594)
2025-09-18 [email protected] Connect the FlutterEngine to the FlutterSceneDelegate (flutter/flutter#174910)
2025-09-18 [email protected] Roll Dart SDK from de5dd0f1530f to 09a101793af4 (2 revisions) (flutter/flutter#175583)
2025-09-18 [email protected] Roll Skia from 7b9fe91446ee to a2c38aa9df80 (1 revision) (flutter/flutter#175579)
2025-09-18 [email protected] Roll Skia from ab1b10547461 to 7b9fe91446ee (4 revisions) (flutter/flutter#175569)
2025-09-18 [email protected] [a11y-app] Fix form field label and error message (flutter/flutter#174831)
2025-09-18 [email protected] Fix InputDecoration does not apply errorStyle to error (flutter/flutter#174787)
2025-09-18 [email protected] Migrate to `WidgetPropertyResolver` (flutter/flutter#175397)
2025-09-18 [email protected] Migrate to WidgetState (flutter/flutter#175396)
2025-09-18 [email protected] Roll Skia from 79ec8dfcd9d4 to ab1b10547461 (17 revisions) (flutter/flutter#175561)
2025-09-18 [email protected] Removes NOTICES from licenses input (flutter/flutter#174967)
2025-09-18 [email protected] Roll Dart SDK from 116f7fe72839 to de5dd0f1530f (2 revisions) (flutter/flutter#175557)
2025-09-17 [email protected] [reland][web] Refactor renderers to use the same frontend code #174588 (flutter/flutter#175392)
2025-09-17 [email protected] feat: Enable WidgetStateColor to be used in ChipThemeData.deleteIconColor (flutter/flutter#171646)
2025-09-17 [email protected] Filter out unexpected process logs on iOS with better regex matching. (flutter/flutter#175452)
2025-09-17 [email protected] CupertinoContextMenu child respects available screen width (flutter/flutter#175300)
2025-09-17 [email protected] Correct documentation in PredictiveBackFullscreenPageTransitionsBuilder (flutter/flutter#174362)
...
Jaineel-Mamtora pushed a commit to Jaineel-Mamtora/flutter_forked that referenced this pull request Sep 24, 2025
…75300)

Missed this in [Correct position for an open
CupertinoContextMenu](flutter#170943)

Fixes [CupertinoContextMenu wrong
position](flutter#175225)
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Nov 12, 2025
lucaantonelli pushed a commit to lucaantonelli/flutter that referenced this pull request Nov 21, 2025
…75300)

Missed this in [Correct position for an open
CupertinoContextMenu](flutter#170943)

Fixes [CupertinoContextMenu wrong
position](flutter#175225)
reidbaker pushed a commit to AbdeMohlbi/flutter that referenced this pull request Dec 10, 2025
…75300)

Missed this in [Correct position for an open
CupertinoContextMenu](flutter#170943)

Fixes [CupertinoContextMenu wrong
position](flutter#175225)
@victorsanni victorsanni deleted the context-menu-fix branch January 12, 2026 23:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

f: cupertino flutter/packages/flutter/cupertino repository framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CupertinoContextMenu wrong position

2 participants