Description
A non-Shell TabbedPage can leak its platform renderer/manager when BarBackground is set from a shared GradientBrush, such as an app-level XAML resource used through a Style.
The platform code subscribes to GradientBrush.InvalidateGradientBrushRequested when applying TabbedPage.BarBackground, but the subscription is not removed when the TabbedPage handler/renderer disconnects. If the brush is app-lifetime, the brush keeps the old platform subscriber alive.
Observed results:
- Android: shared brush retains
Microsoft.Maui.Controls.Handlers.TabbedPageManager, and the removed TabbedPage plus child page remain alive.
- iOS: shared brush retains
Microsoft.Maui.Controls.Handlers.Compatibility.TabbedRenderer.
- Control path: clearing
BarBackground before removal leaves zero brush subscribers and allows the captured subscriber to collect.
Steps to Reproduce
- Clone the repro branch:
git clone https://github.com/AdamEssenmacher/maui.git
cd maui
git checkout tabbed-page-leak-repro
- Build and run the repro project.
Android:
bash ./eng/common/dotnet.sh build src/Controls/samples/TabbedPageBarBackgroundLeakRepro/Maui.Controls.TabbedPageBarBackgroundLeakRepro.csproj -f net10.0-android -p:IncludeAndroidTargetFrameworks=true
adb install -r artifacts/bin/Maui.Controls.TabbedPageBarBackgroundLeakRepro/Debug/net10.0-android/com.microsoft.maui.repros.tabbedpagebarbackgroundleak-Signed.apk
adb shell monkey -p com.microsoft.maui.repros.tabbedpagebarbackgroundleak 1
iOS simulator:
bash ./eng/common/dotnet.sh build src/Controls/samples/TabbedPageBarBackgroundLeakRepro/Maui.Controls.TabbedPageBarBackgroundLeakRepro.csproj -f net10.0-ios -p:IncludeIosTargetFrameworks=true -p:RuntimeIdentifier=iossimulator-arm64
xcrun simctl install booted artifacts/bin/Maui.Controls.TabbedPageBarBackgroundLeakRepro/Debug/net10.0-ios/iossimulator-arm64/Maui.Controls.TabbedPageBarBackgroundLeakRepro.app
xcrun simctl launch booted com.microsoft.maui.repros.tabbedpagebarbackgroundleak
-
Wait for the repro to auto-run. It creates a TabbedPage whose app-level Style sets BarBackground from a shared LinearGradientBrush resource, replaces Window.Page, forces GC, then compares against a control path that clears BarBackground before removal.
-
Observe the result text in the app.
Expected affected result:
Leak reproduced: app resource style gradient retained the removed TabbedPage renderer/manager.
Android leak path shows:
Brush subscribers: 1
Subscriber targets: Microsoft.Maui.Controls.Handlers.TabbedPageManager
Captured subscriber target: alive
Previous child page: alive
TabbedPage: alive
iOS leak path shows:
Brush subscribers: 1
Subscriber targets: Microsoft.Maui.Controls.Handlers.Compatibility.TabbedRenderer
Captured subscriber target: alive
Control path should show:
Brush subscribers: 0
Captured subscriber target: collected
Link to public reproduction project repository
https://github.com/AdamEssenmacher/maui/tree/tabbed-page-leak-repro/src/Controls/samples/TabbedPageBarBackgroundLeakRepro
Version with bug
10.0.60
Is this a regression from previous behavior?
Not sure, did not test other versions
Last version that worked well
Unknown/Other
Affected platforms
iOS, Android, I was not able test on other platforms
Affected platform versions
No response
Did you find any workaround?
No response
Relevant log output
Description
A non-Shell
TabbedPagecan leak its platform renderer/manager whenBarBackgroundis set from a sharedGradientBrush, such as an app-level XAML resource used through aStyle.The platform code subscribes to
GradientBrush.InvalidateGradientBrushRequestedwhen applyingTabbedPage.BarBackground, but the subscription is not removed when theTabbedPagehandler/renderer disconnects. If the brush is app-lifetime, the brush keeps the old platform subscriber alive.Observed results:
Microsoft.Maui.Controls.Handlers.TabbedPageManager, and the removedTabbedPageplus child page remain alive.Microsoft.Maui.Controls.Handlers.Compatibility.TabbedRenderer.BarBackgroundbefore removal leaves zero brush subscribers and allows the captured subscriber to collect.Steps to Reproduce
git clone https://github.com/AdamEssenmacher/maui.git cd maui git checkout tabbed-page-leak-reproAndroid:
iOS simulator:
Wait for the repro to auto-run. It creates a
TabbedPagewhose app-levelStylesetsBarBackgroundfrom a sharedLinearGradientBrushresource, replacesWindow.Page, forces GC, then compares against a control path that clearsBarBackgroundbefore removal.Observe the result text in the app.
Expected affected result:
Android leak path shows:
iOS leak path shows:
Control path should show:
Link to public reproduction project repository
https://github.com/AdamEssenmacher/maui/tree/tabbed-page-leak-repro/src/Controls/samples/TabbedPageBarBackgroundLeakRepro
Version with bug
10.0.60
Is this a regression from previous behavior?
Not sure, did not test other versions
Last version that worked well
Unknown/Other
Affected platforms
iOS, Android, I was not able test on other platforms
Affected platform versions
No response
Did you find any workaround?
No response
Relevant log output