Skip to content

Inflight/current - #29755

Draft
rmarinho wants to merge 84 commits into
mainfrom
inflight/current
Draft

rmarinho wants to merge 84 commits into
mainfrom
inflight/current

Conversation

@rmarinho

Copy link
Copy Markdown
Member

No description provided.

@rmarinho

rmarinho commented Jun 2, 2025

Copy link
Copy Markdown
Member Author

/rebase

@github-actions
github-actions Bot force-pushed the inflight/current branch from f5a279d to c33253a Compare June 2, 2025 17:32
@PureWeen

PureWeen commented Jun 6, 2025

Copy link
Copy Markdown
Member

/rebase

@github-actions
github-actions Bot force-pushed the inflight/current branch from 98e46e1 to 4cc66a0 Compare June 6, 2025 03:24
@PureWeen

PureWeen commented Jun 6, 2025

Copy link
Copy Markdown
Member

/rebase

@github-actions
github-actions Bot force-pushed the inflight/current branch from 4cc66a0 to 8c2ba96 Compare June 6, 2025 15:10
@rmarinho

rmarinho commented Jun 7, 2025

Copy link
Copy Markdown
Member Author

/rebase

@github-actions
github-actions Bot force-pushed the inflight/current branch from 24dd756 to 0423cbb Compare June 7, 2025 13:18
@PureWeen

PureWeen commented Jun 7, 2025

Copy link
Copy Markdown
Member

/rebase

2 similar comments
@PureWeen

PureWeen commented Jun 9, 2025

Copy link
Copy Markdown
Member

/rebase

@PureWeen

Copy link
Copy Markdown
Member

/rebase

@PureWeen

Copy link
Copy Markdown
Member

/rebase

@PureWeen

Copy link
Copy Markdown
Member

/rebase

@PureWeen

Copy link
Copy Markdown
Member

/rebase

@PureWeen

Copy link
Copy Markdown
Member

/rebase

1 similar comment
@PureWeen

Copy link
Copy Markdown
Member

/rebase

@PureWeen

Copy link
Copy Markdown
Member

/rebase

@PureWeen
PureWeen force-pushed the inflight/current branch 2 times, most recently from 205d3a4 to ad707c9 Compare June 28, 2025 02:00
@PureWeen

Copy link
Copy Markdown
Member

/rebase

@PureWeen

Copy link
Copy Markdown
Member

/rebase

@PureWeen

PureWeen commented Jul 1, 2025

Copy link
Copy Markdown
Member

/rebase

Dhivya-SF4094 and others added 27 commits October 26, 2026 11:58
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Issue Detail
When height is a small non-integer value (e.g., 1.2), the shape renders
incorrectly as a thin stroked line instead of a filled rectangle.

### Root Cause

- Shape.StrokeThicknessProperty defaults to 1.0. The path and
measurement logic
in Shape.TransformPathForBounds, Shape.MeasureOverride, Rectangle.GetPath, Ellipse.GetPath,
and RoundRectangle.GetInnerPath / InnerPath always applies an inset
of StrokeThickness / 2 on each side, even when Stroke is null and no
stroke is actually rendered.
- For example, a Rectangle with HeightRequest="1.2" and no Stroke would
still apply the default stroke inset. This consumed 1.0of the
available 1.2 height as a phantom stroke, leaving only a 0.2-pixel-tall
fill area. As a result, the rectangle appeared as a thin line instead of
a filled rectangle.
- BoxView is unaffected because it does not
have Stroke or StrokeThickness properties.
- Shape geometry is also consumed by `Border`, which must preserve
stroke-aware geometry even when the same Shape would not apply an inset
when rendered standalone.

### Description of Change

- Standalone Shapes now apply the stroke inset only when their own
`Stroke` is present.
- Added an internal `IShapeWithStroke` contract so Border renderers can
explicitly request stroke-aware geometry.
- Updated Android, iOS, Mac Catalyst, Windows, and Tizen Border
rendering paths to use the appropriate geometry context.
- Kept the rendering decision with the consumer instead of storing
mutable Border state on shared Shape instances.
- Changed the default `Border.StrokeShape` to create a separate
`Rectangle` for each Border.
- Updated Rectangle, Ellipse, and RoundRectangle path and measurement
calculations.

### Validated the behaviour in the following platforms
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac

### Issues Fixed:
Fixes #31330 

### Screenshots

| Before  | After |
|---------|--------|
|  <img
src="https://github.com/user-attachments/assets/ed5ef7d2-7324-4855-af8c-69201ea2152f">
|  <img
src="https://github.com/user-attachments/assets/8ef27d00-0af8-495b-a616-d441d0f0ed22"> 
|
…ot square (#39366)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Description of Change

On Android, a Shell back button whose `BackButtonBehavior.IconOverride`
is a `FontImageSource` is drawn stretched. A `‹` glyph comes out
flattened and a `●` glyph becomes an oval.

**Root cause**

Shell sets a `FlyoutIconDrawerDrawable` as the toolbar's navigation
icon, and that drawable draws the custom icon inside itself. Its `Draw`
set the icon's bounds to its own full bounds, so the icon was stretched
to fill them regardless of its shape. A font glyph image is as wide as
the measured text and as tall as the font's ascent plus descent
(`FontModelResourceDecoder`), so it rarely has the same shape as those
bounds.

**Fix**

`FlyoutIconDrawerDrawable.Draw` now scales the icon to fit inside the
bounds with its own aspect ratio and centers it. Each side is at least 1
pixel, so an icon with an extreme aspect ratio, such as a 1000x1 image,
is still drawn. An icon with the same aspect ratio as the bounds gets
the same bounds as before. No public API changes.

The same drawable is used for a custom `Shell.FlyoutIcon`, so a
non-square flyout icon keeps its shape as well. I did not test that case
separately.

**Tests**

`ShellTests.BackButtonIconOverrideKeepsItsAspectRatio`
(Controls.DeviceTests, Android) pushes a page whose back button icon is
a `FontImageSource`, draws the back button to a bitmap, and compares the
aspect ratio of what was drawn with the aspect ratio of the icon drawn
at its own size.

`ShellTests.BackButtonIconOverrideWithExtremeAspectRatioIsDrawn` uses a
1000x1 image as `IconOverride` and checks that the back button still
draws something.

| | Without fix | With fix |
|---|---|---|
| `BackButtonIconOverrideKeepsItsAspectRatio` | Fails: expected 0.59,
actual 2.14 | Passes |
| `BackButtonIconOverrideWithExtremeAspectRatioIsDrawn` | Fails without
the 1-pixel minimum | Passes |
| Controls.DeviceTests `Category=Shell` | 71 passed, 2 failed (first
test only) | 73 passed, 1 failed (both tests) |

Run on `main` (f16eb79) on a Lenovo TB323FU tablet, Android 16 (API
36). The remaining failure is
`FlyoutHeaderMinimumHeight(CollapseOnScroll)` (`Expected: 56. Actual:
64`), which fails the same way without this change on that tablet.

I also checked a square image (`groceries.png`, 512x512) as
`IconOverride` in the Sandbox app: the toolbar is pixel-identical with
and without the fix.

In CI, `IconOverrideInShellBackButtonBehaviorShouldWork`, which uses
`coffee.png` (48x56) as `IconOverride`, passed on both Android API 30
legs, so its snapshot is unchanged.

**Before**

https://github.com/user-attachments/assets/5f32e9e9-2c8c-4364-b844-7c97f34d70c9

**After**

https://github.com/user-attachments/assets/7ff47e5d-053e-4573-84e4-efa3a3647342

### Issues Fixed

Fixes #35310

---------

Co-authored-by: Kirk <12691869+KirkPatrickJunsay@users.noreply.github.com>
### Description of Change

Correct `PlatformImage.GetPngDimension` to read the big-endian width and
height fields from the PNG IHDR chunk at bytes 16-23 instead of reading
the 8-byte PNG signature.

The existing stream test now uses a validated 2x3 PNG and asserts the
parsed dimensions for both seekable and non-seekable streams.

Validation:
- `dotnet test src/Graphics/tests/Graphics.Tests/Graphics.Tests.csproj
--no-restore` (372 passed)
- `dotnet format whitespace --folder . --include
src/Graphics/src/Graphics/PlatformImage.cs
src/Graphics/tests/Graphics.Tests/PlatformImageTests.cs
--verify-no-changes`
- `git diff --check`

### Issues Fixed

Fixes #39362
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Description of Change

<!-- Enter description of the fix in this section -->

Navigating away from a BlazorWebView during initialization can leave
queued native callbacks running after its handler has disconnected.

- Ignore late scheme requests when the web view manager is unavailable,
including when request interception disconnects the handler.
- Cancel late navigation callbacks before accessing the detached virtual
view.
- Add iOS/Mac Catalyst device regression tests for both callback paths
and disconnection during interception.

Reproduced the reported null-reference crash in Sandbox before applying
the fix. Afterward, Sandbox completed 20 navigation cycles without
either lifecycle exception.


### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes #34407

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->
…#39429)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->


### Issue Details
On Windows, CollectionView grid layouts show too much blank space
between items.

### Root Cause
WinUI applies a default minimum size to each native GridViewItem. This
makes item containers larger than their content and creates unwanted
empty space.

### Description of Change
Set MinHeight to 0 for vertical grids and MinWidth to 0 for horizontal
grids. This lets items use their natural size while preserving the
configured item spacing.

Validated the behavior in the following platforms
 
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
 
### Issues Fixed
  
Fixes #23708 

### Output  ScreenShot

|Before|After|
|--|--|
| <img width="800" height="415" alt="image"
src="https://github.com/user-attachments/assets/8460ec51-2e71-4da5-af90-71e6aeef3516"
/> | <img width="800" height="420" alt="image"
src="https://github.com/user-attachments/assets/23458819-ce1a-4584-8739-2e0cad500895"
/> |
…op (#39497)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue details

On WPF, awaiting `BlazorWebView.DisposeAsync()` can hang when a Razor
component or scoped service awaits JavaScript interop during its
asynchronous disposal.

For example, a scoped service implementing `IAsyncDisposable` can await
`IJSRuntime.InvokeVoidAsync` during cleanup. The same problem affects
components that perform asynchronous JS cleanup, such as disposing an
`IJSObjectReference` module.

The disposal-time JS invocation is silently dropped rather than
completed or faulted. Consequently, the component or service cannot
finish disposal, and the owning `BlazorWebView.DisposeAsync()` remains
incomplete. Catching `JSDisconnectedException` does not help because no
such exception is raised by this path.

The reported real-world scenario involves closing a WPF dialog
containing a DevExpress Blazor component whose scoped service releases
JS modules during disposal.

### Root cause

WPF's `BlazorWebView.DisposeAsync()` calls
`_webviewManager.MarkAsDisposing()` before disposing the page through
the WebView manager.

That flag causes `WebView2WebViewManager.SendMessage()` to return
without sending messages. However, page disposal still needs to dispose
the Razor renderer and scoped services, and their cleanup code can await
JS interop.

This ordering blocks the JS invocation that disposal itself is waiting
to complete:

1. The control marks the manager as disposing.
2. Page disposal invokes component or scoped-service cleanup.
3. Cleanup awaits a JS interop call.
4. `SendMessage()` drops the outgoing invocation because the manager is
already marked as disposing.
5. Cleanup cannot finish, so control disposal cannot finish either.

Additionally, the existing `ConfigureAwait(false)` can move native
WebView cleanup off the WPF UI thread when manager disposal completes
asynchronously.

### Fix description

- Keep JS messaging available while awaiting WebView manager disposal,
allowing component and scoped-service JS cleanup to complete.
- Call `MarkAsDisposing()` after manager/page disposal completes and
before disposing the native WebView.
- Use `ConfigureAwait(true)` to preserve the WPF UI context for native
WebView disposal.
- Update the shared manager's documentation to describe the required
disposal order.

The existing late-message guards in `SendMessage()` remain in place. The
control's `_isDisposed` guard is also retained.

The functional change is limited to the WPF BlazorWebView
implementation. No MAUI handler behavior is changed.

### Why no automated regression test is included

No existing automated tests targeting the WPF BlazorWebView
implementation were found in this repository. The existing BlazorWebView
unit and device test projects target the MAUI implementation; the WPF
application is a sample rather than an automated test suite.

A representative regression test would require a WPF host running on an
STA/UI dispatcher with a real WebView2 instance and JS interop. This PR
does not introduce a new WPF test project or test infrastructure. Adding
a MAUI test would not cover the affected WPF disposal path.

Instead, the fix was verified using a separate executable WPF
reproduction harness. That harness and the MAUI Sandbox comparison
changes are not included in this PR.

### Verification

Verified locally on Windows 11 with a real WebView2 runtime, using both
the reported package version (`10.0.110`) and source-built WPF
libraries.

The WPF reproduction initializes Blazor, instantiates the scoped
service, and successfully completes a JS preflight call before disposing
the control on the WPF UI thread. A bounded 10-second observation
distinguishes completed disposal from the reported hang.

| Scenario | Before fix | After fix |
| --- | --- | --- |
| Scoped service awaits JS during disposal | Service entered disposal,
but its JS call and control disposal remained incomplete after 10
seconds | JS call, service disposal, and control disposal completed |
| Razor component awaits JS during disposal | Component entered
disposal, but its JS call and control disposal remained incomplete after
10 seconds | JS call, component disposal, and control disposal completed
|
| Scoped service disposes without calling JS | Disposal completed |
Disposal completed |

All three scenarios completed successfully in three repeated runs each
against the patched source-built WPF library. Subsequent runs of the
local verification script also passed.

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes #39471

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…en item spacing changes (#37032)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!
 
### Issue Details

On iOS/MacCatalyst, increasing CollectionView.ItemSpacingat runtime
silently shifts ContentOffset, hiding the first item until the user
manually scrolls up.

### Root Cause 

- File:
src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs
- Any spacing/span change routes:
CollectionViewHandler2.OnItemsLayoutPropertyChanged → UpdateLayout() →
ItemsViewController2.UpdateLayout(newLayout) → EnsureLayoutInitialized()
- EnsureLayoutInitialized() always builds and swaps in a brand-new
UICollectionViewCompositionalLayout instance via
CollectionView.SetCollectionViewLayout(ItemsViewLayout, false).
- Because CollectionView items use self-sizing/estimated dimensions
(NSCollectionLayoutDimension.CreateEstimated(30f)), UIKit's self-sizing
reconciliation — triggered because the fresh layout instance has no
prior size cache — synchronously shifts ContentOffset as a side effect
of that single call.
- Confirmed via logging: ContentOffset.Y moved 0 → 22.5 → 42.5 → 142.5
as spacing increased 20 → 50 → 100 → 150, with all drift happening
inside the SetCollectionViewLayout call (no further drift afterward).

### Description of Change
 
- Captures the offset before the layout swap, restores it if UIKit
shifted it.
- ResetScrollTracking() prevents a spurious Scrolled delta from being
computed against the transient drifted value — this exact pattern
(ResetScrollTracking() + ContentOffset assignment) already exists
elsewhere in the same file (UpdateItemsSource()), so the fix matches
established codebase convention.
- Verified: scrolled-away-from-top positions are correctly preserved
(not force-reset to zero) — no regression.

 
### Issues Fixed
 
Fixes 
#36422 
#34785
 
**Tested the behavior in the following platforms.**
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
 
| Before  | After  |
|---------|--------|
| <Video
src="https://github.com/user-attachments/assets/3b6117a5-216c-4cb6-bab5-3fa02e00715b"
Width="300" Height="600" /> | <Video
src="https://github.com/user-attachments/assets/4bb43152-6f8c-492e-b4ab-e2958081c720"
Width="300" Height="600" /> |
Replaced deprecated status bar orientation notifications with UIDevice
orientation notifications for iOS.

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Description of Change

Switches using the deprecated 
UIApplication.DidChangeStatusBarOrientationNotification 
and 
UIApplication.SharedApplication.StatusBarOrientation.IsLandscape()
to use
UIDevice.OrientationDidChangeNotification
and
UIDevice.CurrentDevice.Orientation.IsLandscape();
instead

### Issues Fixed

Fixes #39182
#39182
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!


### Root Cause :

- When an iOS splash screen uses an unresized raster image, the
generated storyboard could reference the image with its `Resources/`
directory prefix.
- The image is bundled by filename, so the storyboard’s reference did
not match the bundled image name and the splash image could appear
blank.

### Description of Change
- **Storyboard image reference update:**
- The image reference in the storyboard is now set to just the file name
instead of the full path, ensuring the storyboard references the bundled
image correctly. (`GenerateSplashStoryboard.cs`)

- **Unit test addition:**
- A new unit test, `UnresizedRasterSplashReferencesBundledImageName`,
has been added to verify that unresized raster splash images are
correctly referenced by their bundled file name in the storyboard.
(`GenerateSplashStoryboardTests.cs`)
<!-- Enter description of the fix in this section -->

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes #28751 
### Tested the behavior in the following platforms

- [ ] Windows
- [ ] Android
- [x] iOS
- [ ] Mac

| Before Issue Fix | After Issue Fix |
|----------|----------|
| <video
src="https://github.com/user-attachments/assets/80653f8b-4d02-45d8-b98c-98a2da2b1976">
| <video
src="https://github.com/user-attachments/assets/c322df15-fdcc-46ed-8a8a-63336ac15c9f">
|
<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->
…ources (#39470)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

Recheck source compatibility on every `TypedBinding.ApplyCore`
invocation instead of caching it alongside the target property's default
value.

During Windows CollectionView recycling, a template can temporarily lose
its ancestor. `RelativeBindingSource` intentionally applies a null
source until the ancestor becomes available again. The previously cached
successful type check allowed the null source through to SourceGen
getters such as `source => (source.UseCommand, true)`, causing a
first-chance `NullReferenceException`. The reverse transition could also
leave a binding using a cached unsuccessful check after a compatible
source became available.

This change prevents getter invocation for null or incompatible sources,
restores binding values when a compatible source returns, and
deduplicates consecutive source-type mismatch diagnostics. It does not
retain the source strongly or change the public API. The fix is in
shared binding code; Windows is the runtime-validated platform, not an
exclusive scope claim.

Regression tests count getter invocations, so they detect the exception
even when the binding engine catches it. Coverage includes
valid/null/incompatible/valid transitions, fallback values, initial
incompatible-source recovery, and OneWay, OneTime, and TwoWay modes.

- Reproduced with the public sample from #38667 using
Microsoft.Maui.Controls 10.0.110, SourceGen, and Windows ARM64.
- Added an instrumented runtime harness in a local scratch copy of the
sample: incremental animated scrolling, large scrolling jumps,
dynamic-resource replacement, ItemsSource reset, and ancestor
detach/reattach.
- Before: 6,094 matching first-chance exceptions in generated command
getters. These were caught by the binding engine; the standalone process
did not terminate.
- After: zero matching exceptions in two complete five-scenario runs.
Confirmed the running app loaded the source-built Windows Controls
assembly, and a real item-command click still updated the UI.
- Regression cases failed against the unchanged implementation and pass
with the fix. The final five regression cases pass locally.
- Broader TypedBinding tests: 115 passed, 2 skipped, and one
`FailToConvert` failure also reproduced on the unchanged baseline.
- Runtime harness/logs are local investigation artifacts, not additions
to the repository. Android and iOS runtime verification has not been
performed.

Fixes #38667

---------
…bBar overflow menu (#39412)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Description of Change

On Windows, clicking a Shell tab with more than one ShellContent in the
overflow menu of the top tab bar (the ... button) crashed the app with
E_BOUNDS (invalid vector subscript).

The crash is inside WinUI NavigationView. From what I see in the WinUI
code, in Top mode it cant find the container of a data bound item that
sits in the overflow menu, so the clicked item never gets IsSelected,
only IsChildSelected, and when it expands right after the click WinUI
reads past the end of the selected index path. MAUI always fills the tab
bar through MenuItemsSource, so every tab with sub tabs runs into it. A
plain WinUI NavigationView with MenuItemsSource and a similar item
template crashes the same way.

Fixing up the selection state after the click only helps for the first
click, other overflow bugs in WinUI show up after that (for example
picking a sub tab from the overflow menu and closing the menu later
crashes again). So now ShellItemHandler sets SelectsOnInvoked to false
on overflow menu containers that have sub tabs, and clears it when the
container leaves the overflow menu. MapMenuItems also updates the items
that are already in the overflow menu, because a tab can get or lose sub
tabs while it sits there. A click on such a tab just expands it, picking
one of its sub tabs navigates like before and WinUI moves the tab into
the bar. Tabs in the bar work like before.

There are four device tests. The first one opens a tab with one
ShellContent from the overflow menu, then clicks the tab with sub tabs,
picks Sub B and checks the navigation. The second one adds and removes a
sub tab of a tab that already sits in the open overflow menu and checks
the item follows. The third one moves a tab with sub tabs from the
overflow menu back into the bar and checks it gets selected there like
before. The fourth one applies the NavigationView template again and
checks the new overflow menu behaves the same. Without the fix the click
makes WinUI kill the whole test app, so the tests check SelectsOnInvoked
before clicking to fail in a normal way.

### Issues Fixed

Fixes #37985

---------

Co-authored-by: Empiree <Empiree@users.noreply.github.com>
Co-authored-by: Jakub Florkowski <kubaflo123@gmail.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…anges (#38441)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Issue Details
- On Mac Catalyst and iOS, when a CollectionView is initially populated
with a larger number of items, it expands and reports a larger rendered
height through the SizeChanged event. If the ItemsSource is subsequently
updated with fewer items, the CollectionView retains the previously
measured height instead of recalculating and shrinking to match the new
content size.

### Root Cause
- InvalidateMeasureIfContentSizeChanged() ran in
ViewWillLayoutSubviews(), where CV2's compositional layout could still
expose the previous content size because estimated, self-sizing cells
had not completed layout. Once UIKit produced the updated size later in
the layout pass, MAUI received no subsequent measure invalidation and
retained the stale CollectionView height.


### Description of Change
- Moved the CV2 content-size check to ViewDidLayoutSubviews() so MAUI
invalidates and remeasures the CollectionView after UIKit has updated
the compositional layout's content size. Added the ViewDidLayoutSubviews
override to ItemsViewController2 and updated the public API files for
iOS and Mac Catalyst.


### Issues Fixed
Fixes #38276

### Validated the behaviour in the following platforms
- [ ] Windows
- [ ] Android
- [x] iOS
- [x] Mac

### Output
| Before | After |
|----------|----------|
| <video
src="https://github.com/user-attachments/assets/bd45e97e-0915-4296-9fb0-cf5845cabfaf">
| <video
src="https://github.com/user-attachments/assets/503bd075-9031-4029-b2aa-b84c39be8fb9">
|

---------

Co-authored-by: Jakub Florkowski <kubaflo123@gmail.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…sposed renderer (#39458)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details

On iOS 26, a native back navigation followed by dismissal of a modal
NavigationPage can leave a queued cleanup continuation running after its
NavigationRenderer has been disposed and its native
UINavigationController released.

The continuation throws ObjectDisposedException when it reads
ViewControllers. Because its task is not observed, the exception
subsequently surfaces through TaskScheduler.UnobservedTaskException.
Applications monitoring unobserved task exceptions can record this as an
application error even though navigation has already completed.

### Root Cause

NavigationRenderer.RemoveViewControllers attaches a continuation to the
page's appeared/disappeared task. When that task completes with false,
the iOS 26 cleanup branch checks ViewControllers.Contains(controller)
before disposing the popped controller.

The continuation previously checked only the navigation task's result.
It did not check whether the renderer had been disposed while the
continuation was queued. Reading ViewControllers after release of the
native renderer throws ObjectDisposedException.

Simply returning when the renderer is disposed is insufficient: its
disposal loop only cleans up controllers still in ViewControllers. A
controller already removed by native pop still needs disposal to detach
its page and toolbar subscriptions. Additionally, clearing that
controller's Child during cleanup calls UpdateIconColor, which must not
update a disposed navigation renderer.

### Description of Change

<!-- Enter description of the fix in this section -->

- Avoid reading ViewControllers when the navigation renderer is
disposed, while still disposing the captured popped controller so its
cleanup is not skipped.
- Preserve the existing iOS 26 navigation-stack membership check for
active renderers, including its interrupted-transition behavior.
- Guard ParentingViewController.UpdateIconColor against updating a
disposed navigation renderer, allowing controller disconnection to
finish safely.
- Add parameterized device regression coverage for both native
PopViewController and PopToRootViewController after modal dismissal.
- The tests delay the real continuation, wait for the native renderer
handle to be released, and then execute and await the continuation. They
retain the popped page and controller, change IconColor, title and
toolbar properties after dismissal, and verify the old controller is
disposed.

There are no public API changes or new dependencies.

The baseline Sandbox exception matches the reported framework call
chain: NSObject.get_SuperHandle ->
UINavigationController.get_ViewControllers ->
NavigationRenderer.RemoveViewControllers, and surfaces through
TaskScheduler.UnobservedTaskException.

This is a controlled reproduction of the reported race. The ordinary
scenario did not reproduce the production timing naturally. The
diagnostic does not synthesize exceptions or explicitly dispose the
renderer.

### Validation

- The original controlled Sandbox diagnostic produced five matching
exceptions before the fix and none afterward.
- The extended retained-page tests failed with the initial early-return
fix for both native pop and pop-to-root: changing IconColor reached
UpdateIconColor -> UpdateBarTextColor after renderer disposal. Both pass
with the cleanup-safe implementation.
- Merged current main without rebasing, preserving its new back-button
tests alongside the disposal regression tests. The post-merge iOS 26.5
simulator run passed **26 tests, 0 failures**; the build completed with
**0 warnings and 0 errors**.

Exact post-merge validation commands (from the repository root):

```bash
export PATH="$PWD/.dotnet:$PATH"
export DOTNET_ROOT="$PWD/.dotnet"

dotnet format Microsoft.Maui.sln --no-restore --exclude Templates/src --exclude-diagnostics CA1822 --include src/Controls/src/Core/Compatibility/Handlers/NavigationPage/iOS/NavigationRenderer.cs src/Controls/tests/DeviceTests/Elements/NavigationPage/NavigationPageTests.iOS.cs --verbosity quiet
# Passed.

dotnet tool restore
# Passed; restored the updated XHarness tool required after merging main.

pwsh .github/skills/run-device-tests/scripts/Run-DeviceTests.ps1 -Project Controls -Platform ios -Configuration Debug -DeviceUdid CD32F3F6-DF24-4CA5-8D8C-4FC360DB5BAD -SkipXcodeVersionCheck -TestFilter Category=NavigationPage -Timeout 00:05:00
# Passed: 26 tests, 0 failures. Used a separate local -OutputDirectory for captured evidence.
```

Validation used an iPhone 17 Pro simulator running iOS 26.5.
SkipXcodeVersionCheck was needed because installed Xcode differs from
the repository iOS SDK's expected version. Physical devices, Mac
Catalyst, pre-iOS 26, and interrupted interactive swipe-back were not
separately validated. Hosted CI results for the updated head are
pending.

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes #39391

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->
…plete Requests (#39469)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!


<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Root cause

The issue occurs because HybridWebViewHandler.MapEvaluateJavaScriptAsync
is an async void command mapper that awaits an internal JavaScript
evaluation task. On iOS, WKWebView can report a native evaluation error,
such as WKErrorDomain code 5, which faults the task.

Previously, the mapper did not catch the exception, allowing it to
escape through the synchronization context as an unhandled exception
that could terminate the application. The original request returned to
the caller was never completed, preventing callers from catching the
failure when awaiting EvaluateJavaScriptAsync. InvokeJavaScriptAsync was
also affected because it uses the same evaluation command internally.

Additionally, when the platform view was unavailable, the mapper
returned without completing the request because the subsequent
cancellation check was unreachable, potentially leaving the caller
waiting indefinitely.

### Solution description

The fix involves updating the shared evaluation mapper to:

- Catch evaluation exceptions and forward them to the caller through
TrySetException.
- Complete the request as canceled when the platform view is unavailable
or evaluation is canceled.
- Preserve existing script preparation and result normalization,
including returning null for null results rather than an empty string.
- Although the reported failure occurs on iOS, the fix belongs in the
shared mapper because that is where the exception escaped and the
caller's request remained incomplete. No task-manager interface changes
are required.

Validated the behavior in the following platforms
 
- [x] iOS
- [ ] Mac
- [ ] Android
- [ ] Windows
 
### Issues Fixed
  
Fixes #39078

### Output  ScreenShot

|Platform|Before|After|
|--|--|--|
|iOS|<video
src="https://github.com/user-attachments/assets/afa4e0fd-2e45-4753-92a1-9572ae188271">|<video
src="https://github.com/user-attachments/assets/6d945e62-b5a6-4ca4-8327-61aab1013f84">|
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Issue Details
On Android, when Shell uses FlyoutBehavior.Locked with a right-to-left
(RTL) flow direction, the content does not fit into the remaining space
beside the flyout. The flyout appears on the right, but the content is
positioned beneath it instead of occupying the available space on the
left.
This issue occurs only with the Shell renderer and does not occur with
the .NET 11 Shell handler.

### Root Cause
The renderer positions the flyout using GravityFlags.Start, which
resolves to the left in LTR and the right in RTL.
However, in locked mode, SetPadding reserves space for the flyout on the
physical left edge, regardless of the layout direction. As a result, in
RTL mode, the flyout and the reserved content space are positioned on
opposite sides.

### Description of change
Updated ShellFlyoutRenderer.cs:

- Replaced SetPadding with SetPaddingRelative in locked mode so the
reserved space follows the layout direction and aligns with the flyout's
position.
- Replaced PaddingRight with PaddingEnd to preserve padding on the
correct opposite edge.
- Updated the padding resets for Flyout and Disabled modes to clear the
start-relative padding and prevent leftover spacing when switching away
from locked RTL mode.

**Reference:**
https://github.com/dotnet/maui/blob/net11.0/src/Controls/src/Core/Handlers/Shell/ShellHandler.Android.cs#L325

### Why tests were not added
This scenario is already covered by
Issue32419.VerifyShellMenuItemsAlignedInRTL, so no additional test is
needed.

### Issues Fixed
Fixes #39461 

### Screenshots
| Before Issue Fix | After Issue Fix |
|----------|----------|
| <img width="540" height="868"
src="https://github.com/user-attachments/assets/9c5ed988-c5cb-4ac0-a0fe-ea6e9f7b6471">
| <img width="540" height="868"
src="https://github.com/user-attachments/assets/e2984d03-af5b-42c3-abad-e2466297d450">
|
…39484)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!
<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->


### Issue Details
- On iOS and Mac Catalyst, a Button with a gradient background and a
non-zero CornerRadius displays a rounded border, but the gradient
background extends beyond the rounded corners.
- Solid-color backgrounds are not affected.

### Root Cause
- PR #33346 introduced a new button-specific background update path to
correctly clear backgrounds during VisualState changes.
- During this change, the button's stroke information (including
CornerRadius) was no longer passed through the background rendering
flow.
- As a result, gradient background layers no longer received the
CornerRadius value, causing the gradient to render with square corners
while the native button itself remained rounded.

### Description of Change

**Button background rendering improvements:**

* Updated the `UpdateBackground` extension method in
`ButtonExtensions.cs` to accept an optional `IButtonStroke` parameter,
allowing the background rendering logic to respect the button's
`CornerRadius`. The method now passes this parameter to the underlying
view background update logic.
* Modified the `MapBackground` method in `ButtonHandler.iOS.cs` to pass
the button instance as the stroke parameter when updating the
background, ensuring the platform view can access the `CornerRadius`
property.

**Test coverage:**

* Added a new test page (`Issue39450.cs`) to verify that various button
backgrounds—linear gradient, radial gradient, and visual state-driven
backgrounds—are clipped to the rounded corners as expected.
* Introduced a corresponding UI test (`Issue39450.cs` in
`TestCases.Shared.Tests`) that toggles button visual states and checks
via screenshots and label assertions that the background remains within
the rounded borders.

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes #39450

### Tested the behavior in the following platforms
 
- [ ] Windows
- [ ] Android
- [x] iOS
- [x] Mac

### Output
 
| Before | After |
|----------|----------|
| <img
src="https://github.com/user-attachments/assets/95766dff-c233-487a-879f-73825e94c1a0">
| <img
src="https://github.com/user-attachments/assets/56960788-249b-4e58-8efa-6db61bc00df7">
|






<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->


### Issue Details

On Windows, a regular WebView renders blank when used alongside a
HybridWebView. This regression was originally reported in
#34558 and reported again in #39297 after the
previous fix, #35092, was reverted by #35814
because of HybridWebView device-test failures in CI.

The reported two-window scenario was reproduced in Sandbox using local
HTML to avoid network dependencies. In both initialization orders, the
first control loaded successfully while the second failed WebView2
initialization with COMException `0x8007139F`.

### Root Cause

WinUI initializes its default WebView2 environment with `Language` set
to the first entry in
`Windows.Globalization.ApplicationLanguages.Languages`. HybridWebView
creates an explicit environment without applying the same language
default.

WebView2 controls sharing a user data folder require compatible
browser-process environment options. The different language defaults
make these environments incompatible, preventing the second control from
initializing.

### Fix Details

Apply WinUI's application-language default when the HybridWebView
environment language is unspecified.

- Preserve explicitly configured environment languages.
- Preserve custom user data folders, profiles, and other
environment/controller settings.
- Retain a single explicit environment/controller initialization path.
- Leave regular WebView initialization and non-Windows implementations
unchanged.

Unlike the reverted fix, this does not switch between default and
explicit initialization depending on whether settings are customized. It
resolves the underlying language mismatch for both default and
customized initialization.

#### Validation

Added focused Windows device tests covering:

- Default coexistence in both initialization orders.
- Coexistence with a custom HybridWebView profile.
- Coexistence with explicitly supplied environment options whose
language is unspecified.
- Preservation of an explicitly configured language.

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes #34558
Fixes #39297

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->
… to Null (#39513)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Issue Details:
CollectionView with an ItemsSource initially set to a list, the
EmptyViewTemplate is not displayed when the ItemsSource is subsequently
set to Null.
 
### Root Cause:
The Windows handler clears the previous data source, then returns
immediately if the new ItemsSource is null. This bypasses
UpdateEmptyViewVisibility(), leaving the empty-view visibility stale.
 
### Description of Changes:
Updated ItemsViewHandler.Windows.cs to refresh empty-view visibility
before returning for a null source.

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes #28334 

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->

 **Tested the behavior in the following platforms.**
- [ ] Android
- [x] Windows
- [ ] iOS
- [ ] Mac

| Before  | After  |
|---------|--------|
| **Windows**<br> <video
src="https://github.com/user-attachments/assets/fcde9fcf-588b-432f-b24f-5266a2925c3a"
width="600" height="300"> | **Windows**<br> <video
src="https://github.com/user-attachments/assets/f87e3159-5fb1-4585-85b5-03b1c650f914"
width="600" height="300"> |
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Description of Change

Review the 30 unique observed unsuppressed PoliCheck fingerprints,
accounting for all 48 raw occurrences and coalesced matches. These are
contextual scanner matches, not 30 independently confirmed product
defects.

- Change the editable Maps sample label to **Country/region name** and
clarify four related `Locale`/`Placemark` documentation descriptions.
- Replace the adapted, unattributed accessibility quotation with
unquoted owned guidance: retain font scaling for people with low vision
and prioritize accessibility over visual polish. Do not treat quotation
marks or helpful intent as a term-guidance exemption.
- Clarify two comments and expand two local gate-result variable names
throughout `Write-MarkdownReport`, preserving reporting and retry logic.
Together, these source changes address seven observed fingerprints.
- Add 23 exact observed primary fingerprints, each with its own
contextual justification, to the existing active
`eng/automation/guardian/source.gdnsuppress` file. Retain contributor
attribution, font identifiers, public/native API references, and
technical usages. Remove the proposed rule 211960 suppression rather
than suppressing editable accessibility prose.
- Preserve all 15 preexisting suppression entries, their metadata,
public API baselines, XAML bindings, and scan-policy/pipeline
configuration. No alternate signatures or broad exclusions are added.

No other open PR addressing this issue was found when preparing this
approach.

### Validation

- **10 existing focused Pester tests passed** (`Write-MarkdownReport*`
in `Verify-TestsFail.Tests.ps1`; 101 unrelated tests not selected). This
evidence remains applicable to the Markdown/JSON-only follow-up:
verifier and test-file bytes are unchanged, so Pester was not rerun.
- Follow-up checks confirm that only the proposed accessibility
suppression was removed; the other 23 new records, all 15 prior records,
and metadata remain unchanged. The owned accessibility paraphrase and
updated JSON/counts pass the new scope and whitespace checks.
- Original and edited PowerShell parse successfully. Significant tokens
are identical after normalizing the two local renames; all 14 renamed
references remain in the same function, without name collisions.
- Checked JSON with duplicate-key rejection, exact added-fingerprint
membership, individual justifications, preservation of prior entries and
metadata, unchanged APIs/bindings/executable C# and pipeline text,
XAML/XML parseability, and `git diff --check`.

No fresh PoliCheck/Guardian scan or full MAUI/native build was run, and
no CI run was manually queued. This bounded main-branch cleanup does not
assert zero raw matches or release/global compliance.

### Issue Addressed

Addresses #39528.

---------
…t Snapshots for Issue (#39526)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!


<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Root cause

The issue occur because of the Android ImageButton rendering its border
as part of the view’s background, behind its image. Image scaling can
draw the image into the area reserved for the border. With AspectFill,
the image may extend beyond the content area, while with Center, a large
image can also overlap the border. The image then obscures the
background stroke, making some or all sides appear missing. This affects
URI and file image sources.

### Solution description

The fix involves enabling CropToPadding on the Android
ShapeableImageView created by ImageButtonHandler. This setting ensures
that image drawing is clipped to the view’s padded content area instead
of extending into the area reserved for the border. As a result, the
image no longer obscures the background stroke, and the border remains
visible on all sides. The setting is applied in the shared handler
setup, addressing the issue for both AspectFill and Center image
rendering without changing the existing image scaling modes.

**Snapshot Updates:**

I have replaced the existing snapshots with updated versions to reflect
the expected visual changes introduced by enabling CropToPadding. This
clips image drawing to the padded content area, preventing the image
from overlapping the border and ensuring the border remains visible.

- VerifyImageButtonAspect_AspectFillWithImageSourceFromFontImage
-
VerifyMaterial3ImageButtonAspect_AspectFillWithImageSourceFromFontImage

	
**Additional Test Snapshots and Restriction Removal**

I have added the required snapshots and removed the test restrictions
for the following test cases related to issue #29956.

**ImageButton test cases:**

- VerifyImageButtonAspect_AspectFillWithImageSourceFromUri
- VerifyImageButtonAspect_AspectFillWithImageSourceFromFile
- VerifyImageButtonAspect_CenterWithImageSourceFromUri

**Material 3 ImageButton test cases:**

- VerifyMaterial3ImageButtonAspect_AspectFillWithImageSourceFromUri
- VerifyMaterial3ImageButtonAspect_AspectFillWithImageSourceFromFile
- VerifyMaterial3ImageButtonAspect_CenterWithImageSourceFromUri

Validated the behavior in the following platforms
 
- [x] Android
- [ ] Mac
- [ ] iOS
- [ ] Windows
 
### Issues Fixed
  
Fixes #29956

### Output  ScreenShot

|Platform|Before|After|
|--|--|--|
|Android|<video
src="https://github.com/user-attachments/assets/d8c08b97-8de2-4af0-8773-46a1e376bbbc">|<video
src="https://github.com/user-attachments/assets/fb3c6814-acd2-4688-8d12-e691245a44e0">|

---------

Co-authored-by: Jakub Florkowski <42434498+kubaflo@users.noreply.github.com>
…39525)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Description of Change

<!-- Enter description of the fix in this section -->

The Windows CollectionView handler creates a managed
`GroupHeaderStyleSelector` whenever the item template is updated,
including during layout changes. Post-GC heap snapshots show these
selector instances accumulating after CollectionViews are removed or
their pages are popped.

The selector ignores the group and nesting level and always returns a
`GroupStyle` using the same header template. Replace this managed WinRT
callback with a native `GroupStyle` configured directly on the control,
and remove the unused selector class. Replace the existing style on
subsequent updates so styles do not accumulate.

Add Windows regression coverage for grouped and ungrouped collections,
repeated template updates, list/grid layout changes, group header/footer
rendering, and switching grouping off and on.

Fixed #32403 — addresses the GroupHeaderStyleSelector accumulation
reported in the comment

Co-authored-by: Jakub Florkowski <kubaflo123@gmail.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…bounds (#39561)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!


<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Description of Change

Fix popup alignment reported after upgrading Microsoft.Maui.Controls
from 10.0.110 to 10.0.120.

The margin-overflow logic in LayoutExtensions clamps negative alignment
space even when the margins themselves fit within the available bounds.
When content plus margins exceeds those bounds, this suppresses the
offset needed for centering and shifts the popup toward the trailing
edge.

This change:
- Restricts horizontal and vertical alignment clamping to cases where
the margins consume all available space.
- Preserves the existing large-margin correction and FlexLayout
exception.
- Adds regression coverage for Center, End, and explicitly sized Fill
alignment, plus margin-overflow and FlexLayout preservation cases.

Regression PR #37080

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes #39557

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->
#39486)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Issue Details
- When IsRefreshEnabled="false" is set on a RefreshView, the
pull-to-refresh gesture remains active on iOS and macOS, allowing the
content to be pulled down even though the refresh indicator is not
displayed and the Refreshing event is not triggered.

### Root Cause
- MauiRefreshView sets UIScrollView.AlwaysBounceVertical to true when
attaching UIRefreshControl, so the user can pull the content to trigger
refresh. However, when IsRefreshEnabled is set to false, MAUI removes
the UIRefreshControl but does not restore the scroll view's original
AlwaysBounceVertical value. As a result, the UIScrollView remains
vertically pullable even though the refresh control has been removed.


### Description of Change
- MauiRefreshView now stores the original value of AlwaysBounceVertical
before enabling refresh, and restores it only if it hasn't been changed
elsewhere when disabling refresh. This prevents unintended side effects
when toggling refresh or replacing scroll views.


### Issues Fixed
Fixes #39080

### Validated the behaviour in the following platforms
- [ ] Windows
- [ ] Android
- [x] iOS
- [x] Mac

### Output
| Platform | Before | After |
|----------|----------|----------|
| iOS | <video
src="https://github.com/user-attachments/assets/56070801-4357-4e94-9a1d-c8b3ed0d3125">
| <video
src="https://github.com/user-attachments/assets/f2dbe2cb-2c58-4b5c-8c5f-c4d4bd6f409b">
|
| Mac | <video
src="https://github.com/user-attachments/assets/6a4f6b2a-9a8c-4886-96ae-6514f1b2c844">
| <video
src="https://github.com/user-attachments/assets/292ee1c6-1b79-46a5-bd0a-f13a3848d1dc">
|
)

### Issue

- While running `Controls.DeviceTests` on Android, the build fails with
the `xUnit2031` analyzer error.

### Description

- Updates the Android `SwipeView` device test in
`SwipeViewTests.Android.cs` to use the `Assert.Single` overload that
accepts a predicate.
- This resolves the `xUnit2031` analyzer error caused by using a `Where`
clause to filter a collection before calling `Assert.Single`.
- **Reference:** https://xunit.net/xunit.analyzers/rules/xUnit2031

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes #39376

| Before | After |
|----------|----------|
| <img
src="https://github.com/user-attachments/assets/fb0e0455-9b80-433f-bb78-80cbe499d600">
| <img
src="https://github.com/user-attachments/assets/595fe45a-aca4-4dd7-b65b-1ce5c8642d1b">
|
<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->
On Android a ContentView or Border kept the full height of its content
after we set the content to IsVisible=false. In our app that left a
hidden card at 576 dp and the page two thirds empty. On Windows the same
layout collapses fine.

MeasureContent measures the content no matter if it's visible. WinUI
returns 0 for a collapsed element, but Android measures a GONE view at
its full size. The layout managers already skip collapsed children, so I
did the same in both MeasureContent overloads and added a unit test for
it.

Right now we hide the wrapper instead of the content.

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Description of Change

<!-- Enter description of the fix in this section -->

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes #

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->
#39595)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Description of Change

<!-- Enter description of the fix in this section -->

Backports [#38911](#38911), squash
commit `37045aab7a4700a5de982ad4647e14a15b27f2c9`, from `net11.0` to
**`inflight/current`**, as explicitly requested.

Uses the owning window's geometry for the iOS 26 Stepper landscape
compensation, the effective view scale for safe-area pixel comparisons,
and the owning window-scene display scale for bitmap rendering. Detached
views retain the main-screen fallback. Includes the original regression
tests; no public API changes.

The cherry-pick applied without conflicts or target-specific
adaptations, preserving the original author. Source and backport stable
patch IDs both equal `9a3c3feabcc820e6c510853169982b9797b2efb9`. No
equivalent open or recently merged backport was found before publishing.

**Validation performed on this backport:**

- `git diff --cached --check` and `git diff --check` — passed before
committing.
- `dotnet format Microsoft.Maui.sln --no-restore --exclude Templates/src
--exclude-diagnostics CA1822 --include $(git diff --cached --name-only)`
— exited 0 and made no changes, but reported missing references for
Core, Controls.Core and Core.DeviceTests on iOS/MacCatalyst. This is not
a successful semantic-formatting or compilation claim.
- `dotnet format whitespace --folder --include $(git diff --cached
--name-only) --verify-no-changes` — passed.
- `./build.sh -restore -build -configuration Release -projects
"$PWD/Microsoft.Maui.BuildTasks.slnf"
/bl:/Users/kubaflo/.copilot/session-state/2cf9c397-ac62-41d3-b41c-3d5bd2ba5242/files/BuildBuildTasks.binlog
-warnAsError false` — attempted, then stopped during pinned SDK/workload
bootstrapping; no completed build result. The separate solution-based
whitespace verification was also stopped during workspace loading; the
folder-based check above completed successfully.

Runtime/device/UI tests were **not run** for this backport. No XCTest or
host UI automation was used. Source-PR validation results are not
presented as new backport results. Target-branch CI remains required.

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Backport of [#38911](#38911).

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Description of Change

<!-- Enter description of the fix in this section -->

Backports [#38915](#38915), "Add
iPhone Duo support to Controls.Foldable", from squash commit
`a561eaec4aacbe962416c64491aa5ffbc6bde9ae` to **`inflight/current`**, as
explicitly requested.

Preserves the source implementation: view/window-scoped reserved-region
geometry, hinge monitoring and lifecycle cleanup, scene activation
support, shared layout refresh, state triggers, sample updates, and
Foldable unit tests. Original author and source coauthors are preserved.

The only cherry-pick conflicts were in `.github/scripts/RunTests.ps1`
and `.github/scripts/shared/Detect-TestsInDiff.ps1`. Resolution retains
this branch's Compatibility test entries and adds the source Foldable
mappings. Formatting is scoped to changed C# files; the formatter's
unrelated Android null-conditional event rewrite was not retained.

**SDK limitation:** the source's native Duo path is enabled only for iOS
target platform version 27.1+ (or an explicit `IosDuoBindingsAvailable`
override with compatible bindings). This target branch currently
defaults to iOS 26.0, so its default build retains fallback behavior
rather than enabling Duo APIs. This PR does not upgrade the branch-wide
SDK/workload or remove capability/runtime guards. Publishing actual
Duo-enabled artifacts requires a compatible iOS 27.1 SDK build
configuration.

**Validation run in this backport worktree** using .NET SDK
`11.0.100-preview.6.26359.118`, targeting this branch's `net10.0` TFMs,
and Xcode 26.6:

| Command | Result |
| --- | --- |
| `dotnet test
src/Controls/Foldable/test/Controls.Foldable.UnitTests.csproj --nologo
--verbosity minimal` | Passed: 39, failed: 0, skipped: 0; rerun after
formatting and normal restore. |
| `dotnet build src/Controls/Foldable/src/Controls.Foldable.csproj -f
net10.0-ios26.0 --nologo --verbosity minimal` | Passed: 0 warnings, 0
errors. |
| `dotnet build src/Controls/Foldable/src/Controls.Foldable.csproj -f
net10.0-maccatalyst26.0 --no-restore --nologo --verbosity minimal` |
Passed: 0 warnings, 0 errors. |
| `dotnet build src/Controls/Foldable/src/Controls.Foldable.csproj -f
net10.0-android36.0 --no-restore --nologo --verbosity minimal` | Passed:
3 Gradle Java 8 obsolescence warnings, 0 errors. |
| `dotnet build src/Controls/Foldable/src/Controls.Foldable.csproj -f
net10.0-ios27.1 -p:TargetFrameworks=net10.0-ios27.1 --nologo --verbosity
minimal` | Blocked: NETSDK1140, local SDK supports iOS 26.0/26.5, not
27.1. |
| `git diff --cached --check` | Passed. |
| `git diff --check` | Passed. |

Formatting command (passed; `changed_files` contains staged changed
`.cs` paths):

```bash
changed_files=()
while IFS= read -r file; do changed_files+=("$file"); done < <(git diff --cached --name-only -- '*.cs')
dotnet format Microsoft.Maui.sln --no-restore --exclude Templates/src --exclude-diagnostics CA1822 --include "${changed_files[@]}" --verbosity quiet
```

PowerShell syntax check (passed for all three source script files):

```powershell
$files=@(".github/scripts/RunTests.ps1", ".github/scripts/shared/Detect-TestsInDiff.ps1", ".github/scripts/shared/Detect-TestsInDiff.Tests.ps1")
foreach ($file in $files) {
    $tokens=$null; $errors=$null
    [System.Management.Automation.Language.Parser]::ParseFile((Join-Path $PWD $file), [ref]$tokens, [ref]$errors) > $null
    if ($errors.Count) { $errors | Write-Error; exit 1 }
    Write-Output "Parsed $file"
}
```

Build/test/format subprocesses ran with `GH_TOKEN`, `GITHUB_TOKEN`, and
`COPILOT_GITHUB_TOKEN` removed from their environments. A `--no-restore`
test attempt after the explicit 27.1 restore probe encountered changed
assets; normal restore and the full 39-test rerun above succeeded.

**Not run:** native iOS 27.1/Duo compilation or simulator/device runtime
validation, sample app build/runtime validation, Windows validation, or
pipeline Pester tests. No XCTest or host UI automation was used. Source
PR validation is not claimed as a result of this backport.

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Backport of [#38915](#38915); no
separate issue.

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->

Co-authored-by: Gerald Versluis <gerald.versluis@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 741e14bf-fe5c-4150-857c-40b354de3403
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 29755

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 29755"

@kubaflo

This comment has been minimized.

@azure-pipelines

This comment has been minimized.

This branch had an error being deployed

1 failed and 1 inactive (outdated) deployments
skill-evaluation-pat-pool — 4606786a Deployed Oct 26, 2026 by kubaflo via Harness hermeticity gate #30629
copilot-pat-pool — 5f4a5154 Deployed Oct 23, 2026 by kubaflo via conclusion #2487
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-infrastructure CI, Maestro / Coherency, upstream dependencies/versions do-not-merge Don't merge this PR platform/android platform/ios platform/macos macOS / Mac Catalyst platform/windows s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-win AI found a better alternative fix than the PR s/agent-review-in-progress AI review is currently running for this PR s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.