Skip to content

[Net10] OnSizeAllocated in Shell not triggered - fix#31056

Closed
kubaflo wants to merge 5 commits intodotnet:mainfrom
kubaflo:fix-31055
Closed

[Net10] OnSizeAllocated in Shell not triggered - fix#31056
kubaflo wants to merge 5 commits intodotnet:mainfrom
kubaflo:fix-31055

Conversation

@kubaflo
Copy link
Copy Markdown
Contributor

@kubaflo kubaflo commented Aug 6, 2025

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 from this PR and let us know in a comment if this change resolves your issue. Thank you!

Description of Change

This PR restores the expected behavior of the OnSizeAllocated method in Shell, which currently does not get called on either Android or iOS in .NET 10.

In previous .NET MAUI versions, OnSizeAllocated would fire on iOS when overridden in Shell, and still works for individual pages like MainPage. However, with .NET 10, the method is never called, which breaks layout-dependent initialization or resizing logic that developers may include in the Shell.

Issues Fixed

Fixes #31055
Fixes #31020

Copilot AI review requested due to automatic review settings August 6, 2025 23:41
@kubaflo kubaflo requested a review from a team as a code owner August 6, 2025 23:41
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR fixes the OnSizeAllocated method in AppShell not being triggered on Android and iOS in .NET 10. The issue was that the Shell's Frame property was not being properly updated during layout operations, preventing the OnSizeAllocated method from being called.

  • Adds proper Frame updates in shell renderers for both iOS and Android platforms
  • Overrides OnLayout method in Android's ShellFlyoutRenderer to trigger frame updates
  • Adds frame synchronization in iOS ShellRenderer's ViewDidLayoutSubviews method

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
PublicAPI.Unshipped.txt Documents new public OnLayout override in ShellFlyoutRenderer
ShellRenderer.cs (iOS) Adds Shell.Frame update in ViewDidLayoutSubviews to trigger size allocation
ShellFlyoutRenderer.cs (Android) Implements OnLayout override to update Shell.Frame and trigger size events

@dotnet-policy-service dotnet-policy-service bot added the community ✨ Community Contribution label Aug 6, 2025
@kubaflo kubaflo changed the title [Net10] in AppShell not triggered - fix [Net10] OnSizeAllocated in AppShell not triggered- fix Aug 6, 2025
@kubaflo kubaflo changed the title [Net10] OnSizeAllocated in AppShell not triggered- fix [Net10] OnSizeAllocated in Shell not triggered- fix Aug 6, 2025
@kubaflo kubaflo self-assigned this Aug 6, 2025
@kubaflo kubaflo added platform/android platform/ios area-controls-shell Shell Navigation, Routes, Tabs, Flyout labels Aug 6, 2025
@rmarinho rmarinho requested review from PureWeen and removed request for StephaneDelcroix August 8, 2025 12:00
@kubaflo kubaflo changed the title [Net10] OnSizeAllocated in Shell not triggered- fix [Net10] OnSizeAllocated in Shell not triggered - fix Aug 9, 2025
@PureWeen
Copy link
Copy Markdown
Member

PureWeen commented Aug 9, 2025

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 3 pipeline(s).


#if ANDROID || IOS || MACCATALYST
using ShellHandler = Microsoft.Maui.Controls.Handlers.Compatibility.ShellRenderer;
using Microsoft.Maui.Graphics;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fails con compile on Windows:

C:\a\_work\1\s\src\Controls\tests\DeviceTests\Elements\Shell\ShellTests.cs(1162,22): error CS0246: The type or namespace name 'Rect' could not be found (are you missing a using directive or an assembly reference?) [C:\a\_work\1\s\src\Controls\tests\DeviceTests\Controls.DeviceTests.csproj::TargetFramework=net10.0-windows10.0.20348.0]
C:\a\_work\1\s\src\Controls\tests\DeviceTests\Elements\Shell\ShellTests.cs(1162,22): error CS0246: The type or namespace name 'Rect' could not be found (are you missing a using directive or an assembly reference?) [C:\a\_work\1\s\src\Controls\tests\DeviceTests\Controls.DeviceTests.csproj::TargetFramework=net10.0-windows10.0.19041.0]
    2 Error(s)

@jsuarezruiz
Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 3 pipeline(s).

base.OnLayout(changed, left, top, right, bottom);

var destination = Context.ToCrossPlatformRectInReferenceFrame(left, top, right, bottom);
Shell.Frame = destination;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Layout passes occur frequently (device rotation, keyboard, animations, etc.) and always sets Shell.Frame even if unchanged.

Impact the performance? (mostly, animations etc).

Could we only update if if changed?

if (Shell.Frame != destination)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

sure

@PureWeen
Copy link
Copy Markdown
Member

/rebase

@jsuarezruiz
Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 3 pipeline(s).

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 16, 2026

🚀 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 -- 31056

Or

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

@MauiBot MauiBot added s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) s/agent-review-incomplete AI agent could not complete all phases (blocker, timeout, error) and removed s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-review-incomplete AI agent could not complete all phases (blocker, timeout, error) labels Mar 22, 2026
@dotnet dotnet deleted a comment from MauiBot Mar 25, 2026
@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented Mar 25, 2026

🤖 AI Summary

📊 Expand Full Reviewe9e6211 · Address review: fix Windows Rect ambiguity, add iOS frame guard
🔍 Pre-Flight — Context & Validation

Issue: #31055 - OnSizeAllocated in Shell not triggered on Android or iOS in .NET 10
Issue: #31020 - OnSizeAllocated event not fired for Android when rotating the device
PR: #31056 - [Net10] OnSizeAllocated in Shell not triggered - fix
Platforms Affected: Android, iOS
Files Changed: 3 implementation, 1 test

Key Findings

  • OnSizeAllocated is a virtual method on VisualElement that fires when the element's Frame changes
  • Shell uses compatibility renderers (ShellFlyoutRenderer on Android, ShellRenderer on iOS) that bypass the normal handler/layout pipeline
  • In .NET 10, neither renderer was updating Shell.Frame from native layout callbacks, so OnSizeAllocated was never invoked
  • For non-Shell pages (e.g., MainPage : ContentPage), OnSizeAllocated still works because the frame is updated elsewhere
  • Android fix: Override OnLayout in ShellFlyoutRenderer with equality guard if (Shell.Frame != destination) to avoid redundant updates
  • iOS fix: Add Shell.Frame = View.Bounds.ToRectangle() in ViewDidLayoutSubviews in ShellRenderer with equality guard
  • PublicAPI: Android OnLayout override correctly registered in PublicAPI.Unshipped.txt for net-android
  • Test: SettingFrameDoesTriggerInvalidatedMeasure — verifies that setting Shell.Frame triggers MeasureInvalidated
  • Gate ❌ FAILED: Test passes even without the fix — test sets Shell.Frame manually before handler is created, testing base VisualElement.Frame setter behavior (which already works), NOT the platform-specific native→MAUI layout propagation that this PR fixes
  • NRE risk (Android): Shell property = _shellContext.Shell; after Disconnect() sets _shellContext = null, any subsequent OnLayout system callback would throw NullReferenceException
  • NRE risk (iOS): Shell property = (Shell)Element; after renderer detach, Element can be null; ViewDidLayoutSubviews is a system callback that could fire during teardown
  • Prior agent review (7f51dc5): Previous run of agent found Gate PASSED on older commit; multiple try-fix attempts found similar approaches valid (route through SetElementSize or direct Shell.Frame assignment from layout callbacks)
  • Review feedback addressed: Windows Rect compile error fixed (using Microsoft.Maui.Graphics), redundant update guard added on Android

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #31056 Override OnLayout (Android) + Shell.Frame = View.Bounds.ToRectangle() in ViewDidLayoutSubviews (iOS), both with equality guard ❌ Gate FAILED (test doesn't validate fix) ShellFlyoutRenderer.cs, ShellRenderer.cs, PublicAPI.Unshipped.txt, ShellTests.cs NRE risk on both platforms after disconnect

🔧 Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 try-fix (claude-opus-4.6) Null-guarded Frame assignment (if (Element is Shell shell) iOS, if (_shellContext?.Shell is Shell shell) Android) + better test ✅ PASS (202 Shell tests) ShellRenderer.cs, ShellFlyoutRenderer.cs, ShellTests.cs Minimal delta over PR; fixes NRE risks
2 try-fix (claude-sonnet-4.6) Route through SetElementSize (iOS) + OnSizeChanged (Android) ✅ PASS (202 Shell tests) ShellRenderer.cs, ShellFlyoutRenderer.cs, ShellTests.cs Uses IVisualElementRenderer API; OnSizeChanged fires only on dimension change
3 try-fix (gpt-5.3-codex) Route through IView.Arrange(...) in layout callbacks ✅ PASS (202 Shell tests) ShellRenderer.cs, ShellFlyoutRenderer.cs, ShellTests.cs Uses MAUI IView layout contract
4 try-fix (gemini-3-pro-preview) ⚫ BLOCKED Model unavailable
5 try-fix (gpt-5.3-codex) IVisualElementController.PlatformSizeChanged() with bounds guard ❌ FAIL ShellRenderer.cs, ShellFlyoutRenderer.cs, ShellTests.cs Over-notification: MeasureInvalidated fires 2× instead of 1×
PR PR #31056 Override OnLayout (Android) + Shell.Frame in ViewDidLayoutSubviews (iOS), equality guard ❌ Gate FAILED (test doesn't validate fix) 4 files NRE risk on both platforms after disconnect/detach

Cross-Pollination

Model Round New Ideas? Details
claude-opus-4.6 2 NO NEW IDEAS All canonical size-propagation pathways covered
claude-sonnet-4.6 2 NO NEW IDEAS Space well-explored
gpt-5.3-codex 2 NEW IDEA → attempt-5 PlatformSizeChanged() with bounds guard — ran, ❌ FAIL
claude-opus-4.6 3 NO NEW IDEAS Confirmed exhausted
claude-sonnet-4.6 3 NO NEW IDEAS Confirmed exhausted
gpt-5.3-codex 3 NEW IDEA (covered) "Cache last size" — already implemented via equality guards in attempts 1-3

Exhausted: Yes
Selected Fix: Attempt 1 (claude-opus-4.6) — null-guarded Frame assignment + better test
Reason: Simplest possible improvement over the PR's own approach. The PR's core logic is correct (set Shell.Frame from native layout callbacks); it just needs (1) null guards on both platforms to prevent NRE during teardown, and (2) the test replaced with one that validates the actual regression path. Attempt 1 addresses both with minimal code change.


📋 Report — Final Recommendation

⚠️ Final Recommendation: REQUEST CHANGES

Phase Status

Phase Status Notes
Pre-Flight ✅ COMPLETE Issues #31055 (iOS+Android regression) and #31020 (Android)
Gate ❌ FAILED iOS — test SettingFrameDoesTriggerInvalidatedMeasure passes even without fix
Try-Fix ✅ COMPLETE 5 attempts (3 pass, 1 fail, 1 blocked); space exhausted
Report ✅ COMPLETE

Summary

The PR's core fix is correct and necessary: Shell compatibility renderers (ShellFlyoutRenderer on Android, ShellRenderer on iOS) never update Shell.Frame from native layout callbacks, so OnSizeAllocated is never invoked for Shell subclasses. Overriding OnLayout (Android) and updating Frame in ViewDidLayoutSubviews (iOS) is the right approach, confirmed by 3 independent try-fix attempts.

However, two issues must be addressed before merging:

  1. NRE risk on both platforms — The Shell property accessors ((Shell)Element on iOS, _shellContext.Shell on Android) are accessed without null checks in system callbacks (ViewDidLayoutSubviews, OnLayout) that can fire during teardown after the renderer is detached/disconnected.

  2. Test doesn't validate the regressionSettingFrameDoesTriggerInvalidatedMeasure sets Shell.Frame manually before the handler is created. This tests the base VisualElement.Frame setter (which has always worked), not the native→MAUI layout propagation path that the PR fixes. This is why Gate ❌ FAILED: the test passes regardless of whether the fix is present.

Root Cause

Shell uses compatibility renderers that bypass the normal handler/layout pipeline. Unlike ContentPage (where MauiView.LayoutVirtualView calls Shell.Arrange), Shell's renderers never fed native layout bounds back to Shell.Frame. The virtual OnSizeAllocated(width, height) method on VisualElement is only invoked when Frame changes, so it never fires for Shell subclasses.

Fix Quality

The approach is sound and well-targeted. Try-Fix attempt 1 (null-guarded Frame assignment) is the minimal improvement needed over the PR:

Required changes for the PR:

1. Android — Add null guard in ShellFlyoutRenderer.OnLayout:

protected override void OnLayout(bool changed, int left, int top, int right, int bottom)
{
    base.OnLayout(changed, left, top, right, bottom);

    if (_shellContext?.Shell is not Shell shell)
        return;

    var destination = Context.ToCrossPlatformRectInReferenceFrame(left, top, right, bottom);
    if (shell.Frame != destination)
        shell.Frame = destination;
}

2. iOS — Add null guard in ShellRenderer.ViewDidLayoutSubviews:

public override void ViewDidLayoutSubviews()
{
    base.ViewDidLayoutSubviews();
    if (_currentShellItemRenderer != null)
        _currentShellItemRenderer.ViewController.View.Frame = View.Bounds;

    if (Element is Shell shell)
    {
        var destination = View.Bounds.ToRectangle();
        if (shell.Frame != destination)
            shell.Frame = destination;
    }
}

3. Replace the test — Replace SettingFrameDoesTriggerInvalidatedMeasure with a test that validates the actual regression path:

[Fact]
public async Task ShellFrameIsNonZeroAfterAddingToWindow()
{
    SetupBuilder();
    var page = new ContentPage();
    var shell = await CreateShellAsync(shell =>
    {
        shell.CurrentItem = new ShellContent { Content = page };
    });
    await CreateHandlerAndAddToWindow<IWindowHandler>(shell, async _ =>
    {
        await OnFrameSetToNotEmpty(shell);
        Assert.True(shell.Frame.Width > 0);
        Assert.True(shell.Frame.Height > 0);
    });
}

Selected Fix: Attempt 1 over PR's Fix

PR's fix Selected fix (attempt-1)
Core approach ✅ Same (Frame from layout callbacks) ✅ Same
Android null guard ❌ Missing — NRE if OnLayout fires after Disconnect() _shellContext?.Shell is not Shell shell
iOS null guard ❌ Missing — NRE if ViewDidLayoutSubviews fires after detach Element is Shell shell
Test validity ❌ Tests Frame setter (always worked) — Gate FAILED ✅ Tests native layout propagation path
Test result ❌ Gate FAILED ✅ 202/202 Shell tests passed

@MauiBot MauiBot added s/agent-approved AI agent recommends approval - PR fix is correct and optimal and removed s/agent-changes-requested AI agent recommends changes - found a better alternative or issues labels Mar 25, 2026
@kubaflo
Copy link
Copy Markdown
Contributor Author

kubaflo commented Mar 26, 2026

🚦 Gate — Test Verification

📊 Expand Full Gate7f51dc5 · Update ShellFlyoutRenderer.cs

Gate Result: ⚠️ SKIPPED

Platform: iOS

# Type Test Name Filter
1 DeviceTest ShellTests (SettingFrameDoesTriggerInvalidatedMeasure) Category=Shell
Step Expected Actual Result
Without fix FAIL FAIL (APP_LAUNCH_FAILURE) ⚠️
With fix PASS FAIL (APP_LAUNCH_FAILURE) ⚠️

Environment blocker: Both runs crashed with XHarness exit code: 83 (APP_LAUNCH_FAILURE) — SIGABRT in load_aot_module during Mono AOT loading. Test app never launched; SettingFrameDoesTriggerInvalidatedMeasure never executed. This is an infrastructure issue unrelated to the PR fix.

Note: Prior agent review (MauiBot comment) confirmed Gate ✅ PASSED on iOS with tests FAIL without fix and PASS with fix.


@kubaflo
Copy link
Copy Markdown
Contributor Author

kubaflo commented Apr 1, 2026

Code Review — PR #31056

Independent Assessment

What this changes: Ensures Shell.Frame is updated from native layout callbacks on Android (ShellFlyoutRenderer.OnLayout) and iOS (ShellRenderer.ViewDidLayoutSubviews). Without this, the VisualElement.Frame setter is never called for Shell, so OnSizeAllocated never fires.

Inferred motivation: Shell uses compatibility renderers that bypass the normal handler layout pipeline. Unlike regular pages where the layout infrastructure sets Frame, Shell's renderers never fed layout bounds back to the cross-platform Shell.Frame property, breaking OnSizeAllocated for Shell subclasses.

Reconciliation with PR Narrative

Author claims: Restores OnSizeAllocated in Shell on Android and iOS — a regression in .NET 10 (iOS) and long-standing gap (Android).

Agreement: ✅ Fully consistent. The approach correctly mirrors how ViewHandlerExtensions.Android.cs:LayoutVirtualView uses ToCrossPlatformRectInReferenceFrame for standard handlers. The iOS equivalent uses View.Bounds.ToRectangle() — a well-established iOS pattern. The fix is minimal and targeted.

Findings

⚠️ Medium — Potential NullReferenceException after disconnect (Android)

ShellFlyoutRenderer.cs:413–415OnLayout is a virtual override (system callback), not an event handler. After Disconnect() sets _shellContext = null (line 432), any subsequent OnLayout call would crash at Shell.Frame because Shell is _shellContext.Shell.

While the Android view system typically stops calling OnLayout on removed views, there's a race window during teardown. Recommend:

protected override void OnLayout(bool changed, int left, int top, int right, int bottom)
{
    base.OnLayout(changed, left, top, right, bottom);

    if (_shellContext?.Shell is not Shell shell)
        return;

    var destination = Context.ToCrossPlatformRectInReferenceFrame(left, top, right, bottom);
    if (shell.Frame != destination)
        shell.Frame = destination;
}

⚠️ Medium — Same NRE risk on iOS

ShellRenderer.cs:144Shell returns (Shell)Element. After the renderer is detached, Element can be null. ViewDidLayoutSubviews is also a system callback that could fire during teardown. Recommend guarding:

if (Element is Shell shell)
    shell.Frame = View.Bounds.ToRectangle();

💡 Low — iOS lacks the equality guard that Android has

The reviewer specifically requested if (Shell.Frame != destination) on Android (which was added). The iOS side doesn't have this guard. While VisualElement.Frame's setter already short-circuits on equality (if (_frame == value) return;), adding the guard on iOS would be consistent and avoids the ToRectangle() allocation on every layout pass:

if (Element is Shell shell)
{
    var frame = View.Bounds.ToRectangle();
    if (shell.Frame != frame)
        shell.Frame = frame;
}

💡 Low — Test doesn't exercise the actual fix path

ShellTests.cs:1144–1167SettingFrameDoesTriggerInvalidatedMeasure sets shell.Frame manually before the handler is created, then asserts MeasureInvalidated fires once. This validates the base VisualElement.Frame setter behavior (which already works), not the platform-specific fix added in this PR.

A test that validates the actual regression would verify that Shell.Frame becomes non-zero (or that OnSizeAllocated fires) after the Shell is displayed with a handler — exercising the OnLayout/ViewDidLayoutSubviews path. That said, this test does have value as a baseline behavior assertion, and the fix is simple enough that platform-level validation via device tests is reasonable.

Devil's Advocate

Could the OnLayout override in ShellFlyoutRenderer cause performance issues during animations or rapid layout? The ToCrossPlatformRectInReferenceFrame call involves coordinate conversion. However, the equality guard prevents downstream work (no property changed notifications, no OnSizeAllocated callbacks) when the frame hasn't actually changed. Android's OnLayout during animation typically receives the same bounds, so the guard effectively short-circuits. The Frame setter itself also has an equality check. The performance risk is negligible.

Could this cause layout loops? The Frame setter triggers MeasureInvalidated, which could re-trigger layout. However, this is the same pattern used by all other handlers via LayoutVirtualView, and the equality guard prevents infinite recursion (same frame → no-op → no invalidation → no re-layout).

Verdict: NEEDS_CHANGES

Confidence: High

Summary: The fix is architecturally correct and addresses a real regression. The approach matches existing handler patterns (ToCrossPlatformRectInReferenceFrame on Android, Bounds.ToRectangle() on iOS). However, both platform implementations should guard against null Shell/Element after teardown, since OnLayout and ViewDidLayoutSubviews are system callbacks that can fire during view lifecycle transitions. The null guards are a one-line fix each.

@kubaflo
Copy link
Copy Markdown
Contributor Author

kubaflo commented Apr 1, 2026

🟡 .NET MAUI Review - Changes Suggested

Expand Full Review - 7f51dc5 - [Net10] OnSizeAllocated in Shell not triggered - fix

Independent Assessment

What this changes: Ensures Shell.Frame is updated from native layout callbacks on Android (ShellFlyoutRenderer.OnLayout) and iOS (ShellRenderer.ViewDidLayoutSubviews). Without this, the VisualElement.Frame setter is never called for Shell, so OnSizeAllocated never fires.

Inferred motivation: Shell uses compatibility renderers that bypass the normal handler layout pipeline. Unlike regular pages where the layout infrastructure sets Frame, Shell's renderers never fed layout bounds back to the cross-platform Shell.Frame property, breaking OnSizeAllocated for Shell subclasses.

Reconciliation with PR Narrative

Author claims: Restores OnSizeAllocated in Shell on Android and iOS — a regression in .NET 10 (iOS) and long-standing gap (Android).

Agreement: ✅ Fully consistent. The approach correctly mirrors how ViewHandlerExtensions.Android.cs:LayoutVirtualView uses ToCrossPlatformRectInReferenceFrame for standard handlers. The iOS equivalent uses View.Bounds.ToRectangle() — a well-established iOS pattern. The fix is minimal and targeted.

Findings

⚠️ Medium — Potential NullReferenceException after disconnect (Android)

ShellFlyoutRenderer.cs:413–415OnLayout is a virtual override (system callback), not an event handler. After Disconnect() sets _shellContext = null (line 432), any subsequent OnLayout call would crash at Shell.Frame because Shell is _shellContext.Shell.

While the Android view system typically stops calling OnLayout on removed views, there's a race window during teardown. Recommend:

protected override void OnLayout(bool changed, int left, int top, int right, int bottom)
{
    base.OnLayout(changed, left, top, right, bottom);

    if (_shellContext?.Shell is not Shell shell)
        return;

    var destination = Context.ToCrossPlatformRectInReferenceFrame(left, top, right, bottom);
    if (shell.Frame != destination)
        shell.Frame = destination;
}

⚠️ Medium — Same NRE risk on iOS

ShellRenderer.cs:144Shell returns (Shell)Element. After the renderer is detached, Element can be null. ViewDidLayoutSubviews is also a system callback that could fire during teardown. Recommend guarding:

if (Element is Shell shell)
    shell.Frame = View.Bounds.ToRectangle();

💡 Low — iOS lacks the equality guard that Android has

The reviewer specifically requested if (Shell.Frame != destination) on Android (which was added). The iOS side doesn't have this guard. While VisualElement.Frame's setter already short-circuits on equality (if (_frame == value) return;), adding the guard on iOS would be consistent and avoids the ToRectangle() allocation on every layout pass:

if (Element is Shell shell)
{
    var frame = View.Bounds.ToRectangle();
    if (shell.Frame != frame)
        shell.Frame = frame;
}

💡 Low — Test doesn't exercise the actual fix path

ShellTests.cs:1144–1167SettingFrameDoesTriggerInvalidatedMeasure sets shell.Frame manually before the handler is created, then asserts MeasureInvalidated fires once. This validates the base VisualElement.Frame setter behavior (which already works), not the platform-specific fix added in this PR.

A test that validates the actual regression would verify that Shell.Frame becomes non-zero (or that OnSizeAllocated fires) after the Shell is displayed with a handler — exercising the OnLayout/ViewDidLayoutSubviews path. That said, this test does have value as a baseline behavior assertion, and the fix is simple enough that platform-level validation via device tests is reasonable.

Devil's Advocate

Could the OnLayout override in ShellFlyoutRenderer cause performance issues during animations or rapid layout? The ToCrossPlatformRectInReferenceFrame call involves coordinate conversion. However, the equality guard prevents downstream work (no property changed notifications, no OnSizeAllocated callbacks) when the frame hasn't actually changed. Android's OnLayout during animation typically receives the same bounds, so the guard effectively short-circuits. The Frame setter itself also has an equality check. The performance risk is negligible.

Could this cause layout loops? The Frame setter triggers MeasureInvalidated, which could re-trigger layout. However, this is the same pattern used by all other handlers via LayoutVirtualView, and the equality guard prevents infinite recursion (same frame → no-op → no invalidation → no re-layout).

Verdict: NEEDS_CHANGES

Confidence: High

Summary: The fix is architecturally correct and addresses a real regression. The approach matches existing handler patterns (ToCrossPlatformRectInReferenceFrame on Android, Bounds.ToRectangle() on iOS). However, both platform implementations should guard against null Shell/Element after teardown, since OnLayout and ViewDidLayoutSubviews are system callbacks that can fire during view lifecycle transitions. The null guards are a one-line fix each.

kubaflo and others added 5 commits April 2, 2026 12:11
Introduces a unit test to verify that setting the Shell's Frame property triggers the MeasureInvalidated event exactly once. This ensures correct layout invalidation behavior when the frame changes.
- Use fully qualified Microsoft.Maui.Graphics.Rect in ShellTests to fix
  Windows compilation error (CS0246)
- Add 'if (Shell.Frame != destination)' guard in iOS ShellRenderer to
  avoid unnecessary frame updates during layout passes
- Remove unused 'using Microsoft.Maui.Graphics' import
- Restore original whitespace formatting in using directives

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented Apr 2, 2026

🚦 Gate - Test Before and After Fix

📊 Expand Full Gatee9e6211 · Address review: fix Windows Rect ambiguity, add iOS frame guard

Gate Result: ❌ FAILED

Platform: IOS · Base: main · Merge base: 720a9d4a

Test Without Fix (expect FAIL) With Fix (expect PASS)
📱 ShellTests (SettingFrameDoesTriggerInvalidatedMeasure) Category=Shell ❌ PASS — 455s ✅ PASS — 214s
🔴 Without fix — 📱 ShellTests (SettingFrameDoesTriggerInvalidatedMeasure): PASS ❌ · 455s

(truncated to last 15,000 chars)

Run + 1208
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x0000000186a709e8 CoreFoundation`CFRunLoopRunSpecific + 572
�[40m�[37mdbug�[39m�[22m�[49m: frame #7: 0x0000000188040c78 Foundation`-[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 212
�[40m�[37mdbug�[39m�[22m�[49m: frame #8: 0x00000001880b43a4 Foundation`-[NSRunLoop(NSRunLoop) runUntilDate:] + 100
�[40m�[37mdbug�[39m�[22m�[49m: frame #9: 0x0000000104afef28 mlaunch`xamarin_dyn_objc_msgSend + 160
�[40m�[37mdbug�[39m�[22m�[49m: frame #10: 0x000000010b04e584
�[40m�[37mdbug�[39m�[22m�[49m: frame #11: 0x000000010b3b1c48
�[40m�[37mdbug�[39m�[22m�[49m: frame #12: 0x000000010b047f6c
�[40m�[37mdbug�[39m�[22m�[49m: frame #13: 0x000000010afe10b4
�[40m�[37mdbug�[39m�[22m�[49m: frame #14: 0x000000010a7fcd54
�[40m�[37mdbug�[39m�[22m�[49m: frame #15: 0x0000000106754c04 libcoreclr.dylib`CallDescrWorkerInternal + 132
�[40m�[37mdbug�[39m�[22m�[49m: frame #16: 0x00000001065d2d30 libcoreclr.dylib`MethodDescCallSite::CallTargetWorker(unsigned long long const*, unsigned long long*, int) + 836
�[40m�[37mdbug�[39m�[22m�[49m: frame #17: 0x00000001064d9350 libcoreclr.dylib`RunMain(MethodDesc*, short, int*, PtrArray**) + 648
�[40m�[37mdbug�[39m�[22m�[49m: frame #18: 0x00000001064d9688 libcoreclr.dylib`Assembly::ExecuteMainMethod(PtrArray**, int) + 264
�[40m�[37mdbug�[39m�[22m�[49m: frame #19: 0x000000010650129c libcoreclr.dylib`CorHost2::ExecuteAssembly(unsigned int, char16_t const*, int, char16_t const**, unsigned int*) + 640
�[40m�[37mdbug�[39m�[22m�[49m: frame #20: 0x00000001064c7650 libcoreclr.dylib`coreclr_execute_assembly + 232
�[40m�[37mdbug�[39m�[22m�[49m: frame #21: 0x0000000104afa140 mlaunch`mono_jit_exec + 204
�[40m�[37mdbug�[39m�[22m�[49m: frame #22: 0x0000000104afdecc mlaunch`xamarin_main + 884
�[40m�[37mdbug�[39m�[22m�[49m: frame #23: 0x0000000104aff1f4 mlaunch`main + 64
�[40m�[37mdbug�[39m�[22m�[49m: frame #24: 0x00000001865e6b98 dyld`start + 6076
�[40m�[37mdbug�[39m�[22m�[49m: thread #2
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x0000000186945c34 libsystem_kernel.dylib`mach_msg2_trap + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x00000001869583a0 libsystem_kernel.dylib`mach_msg2_internal + 76
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000018694e764 libsystem_kernel.dylib`mach_msg_overwrite + 484
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x0000000186945fa8 libsystem_kernel.dylib`mach_msg + 24
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x00000001064c52f4 libcoreclr.dylib`MachMessage::Receive(unsigned int) + 80
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x00000001064c461c libcoreclr.dylib`SEHExceptionThread(void*) + 164
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x0000000186987bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #3, name = '.NET SynchManager'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000018694bd04 libsystem_kernel.dylib`kevent + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x00000001064b9304 libcoreclr.dylib`CorUnix::CPalSynchronizationManager::ReadBytesFromProcessPipe(int, unsigned char*, int) + 484
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x00000001064b89f0 libcoreclr.dylib`CorUnix::CPalSynchronizationManager::WorkerThread(void*) + 164
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x00000001064c20fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x0000000186987bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #4, name = '.NET EventPipe'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000018694e498 libsystem_kernel.dylib`poll + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x00000001067b4e90 libcoreclr.dylib`ds_ipc_poll(_DiagnosticsIpcPollHandle*, unsigned long, unsigned int, void (*)(char const*, unsigned int)) + 172
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x0000000106862bb0 libcoreclr.dylib`ds_ipc_stream_factory_get_next_available_stream(void (*)(char const*, unsigned int)) + 756
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x0000000106860a68 libcoreclr.dylib`server_thread(void*) + 372
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x00000001064c20fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x0000000186987bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #5, name = '.NET DebugPipe'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x0000000186946678 libsystem_kernel.dylib`__open + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x00000001869516a4 libsystem_kernel.dylib`open + 64
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x00000001067b5a84 libcoreclr.dylib`TwoWayPipe::WaitForConnection() + 40
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x00000001067b0578 libcoreclr.dylib`DbgTransportSession::TransportWorker() + 232
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x00000001067af5c8 libcoreclr.dylib`DbgTransportSession::TransportWorkerStatic(void*) + 40
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x00000001064c20fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x0000000186987bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #6, name = '.NET Debugger'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x00000001869493cc libsystem_kernel.dylib`__psynch_cvwait + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x000000018698809c libsystem_pthread.dylib`_pthread_cond_wait + 984
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x00000001064b6f6c libcoreclr.dylib`CorUnix::CPalSynchronizationManager::ThreadNativeWait(CorUnix::_ThreadNativeWaitData*, unsigned int, CorUnix::ThreadWakeupReason*, unsigned int*) + 320
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x00000001064b6bec libcoreclr.dylib`CorUnix::CPalSynchronizationManager::BlockThread(CorUnix::CPalThread*, unsigned int, bool, bool, CorUnix::ThreadWakeupReason*, unsigned int*) + 380
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x00000001064bb0cc libcoreclr.dylib`CorUnix::InternalWaitForMultipleObjectsEx(CorUnix::CPalThread*, unsigned int, void* const*, int, unsigned int, int, int) + 1600
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x00000001067adda8 libcoreclr.dylib`DebuggerRCThread::MainLoop() + 228
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x00000001067adc70 libcoreclr.dylib`DebuggerRCThread::ThreadProc() + 256
�[40m�[37mdbug�[39m�[22m�[49m: frame #7: 0x00000001067ada24 libcoreclr.dylib`DebuggerRCThread::ThreadProcStatic(void*) + 56
�[40m�[37mdbug�[39m�[22m�[49m: frame #8: 0x00000001064c20fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #9: 0x0000000186987bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #7
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x00000001869493cc libsystem_kernel.dylib`__psynch_cvwait + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x000000018698809c libsystem_pthread.dylib`_pthread_cond_wait + 984
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x00000001064b6f6c libcoreclr.dylib`CorUnix::CPalSynchronizationManager::ThreadNativeWait(CorUnix::_ThreadNativeWaitData*, unsigned int, CorUnix::ThreadWakeupReason*, unsigned int*) + 320
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x00000001064b6bec libcoreclr.dylib`CorUnix::CPalSynchronizationManager::BlockThread(CorUnix::CPalThread*, unsigned int, bool, bool, CorUnix::ThreadWakeupReason*, unsigned int*) + 380
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x00000001064bb0cc libcoreclr.dylib`CorUnix::InternalWaitForMultipleObjectsEx(CorUnix::CPalThread*, unsigned int, void* const*, int, unsigned int, int, int) + 1600
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x0000000106608078 libcoreclr.dylib`FinalizerThread::WaitForFinalizerEvent(CLREvent*) + 240
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x00000001066081d8 libcoreclr.dylib`FinalizerThread::FinalizerThreadWorker(void*) + 264
�[40m�[37mdbug�[39m�[22m�[49m: frame #7: 0x00000001065a5fa8 libcoreclr.dylib`ManagedThreadBase_DispatchOuter(ManagedThreadCallState*) + 248
�[40m�[37mdbug�[39m�[22m�[49m: frame #8: 0x00000001065a648c libcoreclr.dylib`ManagedThreadBase::FinalizerBase(void (*)(void*)) + 36
�[40m�[37mdbug�[39m�[22m�[49m: frame #9: 0x0000000106608350 libcoreclr.dylib`FinalizerThread::FinalizerThreadStart(void*) + 88
�[40m�[37mdbug�[39m�[22m�[49m: frame #10: 0x00000001064c20fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #11: 0x0000000186987bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #8, name = '.NET SigHandler'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x00000001869467dc libsystem_kernel.dylib`read + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x0000000104bb8e98 libSystem.Native.dylib`SignalHandlerLoop + 96
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x0000000186987bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #9
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000018694bd04 libsystem_kernel.dylib`kevent + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x0000000104bb74a4 libSystem.Native.dylib`SystemNative_WaitForSocketEvents + 80
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000010b15cb34
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x000000010b15c694
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x000000010b15c544
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x000000010b041388
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x000000010b0411e0
�[40m�[37mdbug�[39m�[22m�[49m: frame #7: 0x000000010b041108
�[40m�[37mdbug�[39m�[22m�[49m: frame #8: 0x0000000106754c04 libcoreclr.dylib`CallDescrWorkerInternal + 132
�[40m�[37mdbug�[39m�[22m�[49m: frame #9: 0x00000001065d2988 libcoreclr.dylib`DispatchCallSimple(unsigned long*, unsigned int, unsigned long long, unsigned int) + 268
�[40m�[37mdbug�[39m�[22m�[49m: frame #10: 0x00000001065e4c6c libcoreclr.dylib`ThreadNative::KickOffThread_Worker(void*) + 148
�[40m�[37mdbug�[39m�[22m�[49m: frame #11: 0x00000001065a5fa8 libcoreclr.dylib`ManagedThreadBase_DispatchOuter(ManagedThreadCallState*) + 248
�[40m�[37mdbug�[39m�[22m�[49m: frame #12: 0x00000001065a645c libcoreclr.dylib`ManagedThreadBase::KickOff(void (*)(void*), void*) + 32
�[40m�[37mdbug�[39m�[22m�[49m: frame #13: 0x00000001065e4d44 libcoreclr.dylib`ThreadNative::KickOffThread(void*) + 172
�[40m�[37mdbug�[39m�[22m�[49m: frame #14: 0x00000001064c20fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #15: 0x0000000186987bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #10
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x0000000186945c34 libsystem_kernel.dylib`mach_msg2_trap + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x00000001869583a0 libsystem_kernel.dylib`mach_msg2_internal + 76
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000018694e764 libsystem_kernel.dylib`mach_msg_overwrite + 484
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x0000000186945fa8 libsystem_kernel.dylib`mach_msg + 24
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x0000000186a72c0c CoreFoundation`__CFRunLoopServiceMachPort + 160
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x0000000186a71528 CoreFoundation`__CFRunLoopRun + 1208
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x0000000186a709e8 CoreFoundation`CFRunLoopRunSpecific + 572
�[40m�[37mdbug�[39m�[22m�[49m: frame #7: 0x0000000188040c78 Foundation`-[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 212
�[40m�[37mdbug�[39m�[22m�[49m: frame #8: 0x0000000104afef28 mlaunch`xamarin_dyn_objc_msgSend + 160
�[40m�[37mdbug�[39m�[22m�[49m: frame #9: 0x000000010b3a7754
�[40m�[37mdbug�[39m�[22m�[49m: frame #10: 0x000000010b3a7618
�[40m�[37mdbug�[39m�[22m�[49m: frame #11: 0x000000010b3a744c
�[40m�[37mdbug�[39m�[22m�[49m: frame #12: 0x000000010b3a43d0
�[40m�[37mdbug�[39m�[22m�[49m: frame #13: 0x000000010b041330
�[40m�[37mdbug�[39m�[22m�[49m: frame #14: 0x000000010b0411e0
�[40m�[37mdbug�[39m�[22m�[49m: frame #15: 0x000000010b041108
�[40m�[37mdbug�[39m�[22m�[49m: frame #16: 0x0000000106754c04 libcoreclr.dylib`CallDescrWorkerInternal + 132
�[40m�[37mdbug�[39m�[22m�[49m: frame #17: 0x00000001065d2988 libcoreclr.dylib`DispatchCallSimple(unsigned long*, unsigned int, unsigned long long, unsigned int) + 268
�[40m�[37mdbug�[39m�[22m�[49m: frame #18: 0x00000001065e4c6c libcoreclr.dylib`ThreadNative::KickOffThread_Worker(void*) + 148
�[40m�[37mdbug�[39m�[22m�[49m: frame #19: 0x00000001065a5fa8 libcoreclr.dylib`ManagedThreadBase_DispatchOuter(ManagedThreadCallState*) + 248
�[40m�[37mdbug�[39m�[22m�[49m: frame #20: 0x00000001065a645c libcoreclr.dylib`ManagedThreadBase::KickOff(void (*)(void*), void*) + 32
�[40m�[37mdbug�[39m�[22m�[49m: frame #21: 0x00000001065e4d44 libcoreclr.dylib`ThreadNative::KickOffThread(void*) + 172
�[40m�[37mdbug�[39m�[22m�[49m: frame #22: 0x00000001064c20fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #23: 0x0000000186987bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #11
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x00000001869478b0 libsystem_kernel.dylib`__workq_kernreturn + 8
�[40m�[37mdbug�[39m�[22m�[49m: thread #12, name = 'com.apple.CFSocket.private'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x0000000186950c2c libsystem_kernel.dylib`__select + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x0000000186a98a80 CoreFoundation`__CFSocketManager + 704
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x0000000186987bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #13
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x00000001869478b0 libsystem_kernel.dylib`__workq_kernreturn + 8
�[40m�[37mdbug�[39m�[22m�[49m: (lldb) detach
�[40m�[37mdbug�[39m�[22m�[49m: Process 9317 detached
�[40m�[37mdbug�[39m�[22m�[49m: (lldb) quit
�[40m�[37mdbug�[39m�[22m�[49m: 9317 Execution timed out after 60 seconds and the process was killed.
�[40m�[37mdbug�[39m�[22m�[49m: Process mlaunch exited with 137
�[40m�[37mdbug�[39m�[22m�[49m: Failed to list crash reports from device.
�[40m�[37mdbug�[39m�[22m�[49m: Test run started but crashed and no test results were reported
�[40m�[37mdbug�[39m�[22m�[49m: No crash reports, waiting 30 seconds for the crash report service...
�[41m�[30mfail�[39m�[22m�[49m: Application test run crashed
      Failed to launch the application, please try again. If the problem persists, try rebooting MacOS
�[40m�[32minfo�[39m�[22m�[49m: Uninstalling the application 'com.microsoft.maui.controls.devicetests' from 'iPhone 11 Pro'
�[40m�[37mdbug�[39m�[22m�[49m: 
�[40m�[37mdbug�[39m�[22m�[49m: Running /Applications/Xcode_26.1.1.app/Contents/Developer/usr/bin/simctl
�[40m�[37mdbug�[39m�[22m�[49m: An error was encountered processing the command (domain=com.apple.CoreSimulator.SimError, code=405):
�[40m�[37mdbug�[39m�[22m�[49m: Unable to lookup in current state: Shutdown
�[40m�[37mdbug�[39m�[22m�[49m: Process simctl exited with 149
�[41m�[30mfail�[39m�[22m�[49m: Failed to uninstall the app bundle! Check logs for more details!
XHarness exit code: 83 (APP_LAUNCH_FAILURE)
  Passed: 0
  Failed: 0
  Tests completed with exit code: 83

🟢 With fix — 📱 ShellTests (SettingFrameDoesTriggerInvalidatedMeasure): PASS ✅ · 214s
  Determining projects to restore...
  All projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723433
  Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Release/net10.0-ios26.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723433
  Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Release/net10.0-ios26.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723433
  Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core/Release/net10.0-ios26.0/Microsoft.Maui.dll
  TestUtils.DeviceTests -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/TestUtils.DeviceTests/Release/net10.0-ios/Microsoft.Maui.TestUtils.DeviceTests.dll
  Controls.BindingSourceGen -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.BindingSourceGen/Release/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723433
  Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Release/net10.0-ios26.0/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723433
  Controls.Xaml -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Xaml/Release/net10.0-ios26.0/Microsoft.Maui.Controls.Xaml.dll
  TestUtils.DeviceTests.Runners -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/TestUtils.DeviceTests.Runners/Release/net10.0-ios/Microsoft.Maui.TestUtils.DeviceTests.Runners.dll
  Core.DeviceTests.Shared -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core.DeviceTests.Shared/Release/net10.0-ios/Microsoft.Maui.DeviceTests.Shared.dll
  TestUtils.DeviceTests.Runners.SourceGen -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/TestUtils.DeviceTests.Runners.SourceGen/Release/netstandard2.0/Microsoft.Maui.TestUtils.DeviceTests.Runners.SourceGen.dll
  Detected signing identity:
    Code Signing Key: "" (-)
    Provisioning Profile: "" () - no entitlements
    Bundle Id: com.microsoft.maui.controls.devicetests
    App Id: com.microsoft.maui.controls.devicetests
  Controls.DeviceTests -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.DeviceTests/Release/net10.0-ios/iossimulator-arm64/Microsoft.Maui.Controls.DeviceTests.dll
  Optimizing assemblies for size may change the behavior of the app. Be sure to test after publishing. See: https://aka.ms/dotnet-illink
  Optimizing assemblies for size. This process might take a while.
  IL stripping assemblies

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:02:29.16
[11.0.0-prerelease.26107.1+bfbac237157e59cdbd19334325b2af80bd6e9828] XHarness command issued: apple test --app artifacts/bin/Controls.DeviceTests/Release/net10.0-ios/iossimulator-arm64/Microsoft.Maui.Controls.DeviceTests.app --target ios-simulator-64_18.6 --device 755498B8-2E06-4CB4-BEE6-C43F6BDABC49 -o artifacts/log --timeout 01:00:00 -v --set-env=TestFilter=Category=Shell
�[40m�[32minfo�[39m�[22m�[49m: Preparing run for ios-simulator-64_18.6 targeting 755498B8-2E06-4CB4-BEE6-C43F6BDABC49
�[40m�[32minfo�[39m�[22m�[49m: Looking for available ios-simulator-64_18.6 simulators..
�[40m�[37mdbug�[39m�[22m�[49m: Looking for available ios-simulator-64_18.6 simulators. Storing logs into list-ios-simulator-64_18.6-20260402_033249.log
�[40m�[32minfo�[39m�[22m�[49m: Found simulator device 'iPhone 11 Pro'
�[40m�[32minfo�[39m�[22m�[49m: Getting app bundle information from '/Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.DeviceTests/Release/net10.0-ios/iossimulator-arm64/Microsoft.Maui.Controls.DeviceTests.app'..
�[40m�[37mdbug�[39m�[22m�[49m: 
�[40m�[37mdbug�[39m�[22m�[49m: Running /usr/libexec/PlistBuddy
�[40m�[37mdbug�[39m�[22m�[49m: Process PlistBuddy exited with 0
�[40m�[37mdbug�[39m�[22m�[49m: 
�[40m�[37mdbug�[39m�[22m�[49m: Running /usr/libexec/PlistBuddy
�[40m�[37mdbug�[39m�[22m�[49m: Process PlistBuddy exited with 0
�[40m�[37mdbug�[39m�[22m�[49m: 
�[40m�[37mdbug�[39m�[22m�[49m: Running /usr/libexec/PlistBuddy
�[40m�[37mdbug�[39m�[22m�[49m: Process PlistBuddy exited with 0
�[40m�[37mdbug�[39m�[22m�[49m: 
�[40m�[37mdbug�[39m�[22m�[49m: Running /usr/libexec/PlistBuddy
�[40m�[37mdbug�[39m�[22m�[49m: Process PlistBuddy exited with 0
�[40m�[32minfo�[39m�[22m�[49m: Uninstalling any previous instance of 'com.microsoft.maui.controls.devicetests' from 'iPhone 11 Pro'
�[40m�[37mdbug�[39m�[22m�[49m: 
�[40m�[37mdbug�[39m�[22m�[49m: Running /Applications/Xcode_26.1.1.app/Contents/Developer/usr/bin/simctl
�[40m�[37mdbug�[39m�[22m�[49m: Process simctl exited with 0
�[40m�[32minfo�[39m�[22m�[49m: Application 'com.microsoft.maui.controls.devicetests' was uninstalled successfully
�[40m�[32minfo�[39m�[22m�[49m: Installing application 'Microsoft.Maui.Controls.DeviceTests' on 'iPhone 11 Pro'
�[40m�[37mdbug�[39m�[22m�[49m: Installing '/Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.DeviceTests/Release/net10.0-ios/iossimulator-arm64/Microsoft.Maui.Controls.DeviceTests.app' to 'iPhone 11 Pro' (142.06 MB)
�[40m�[37mdbug�[39m�[22m�[49m: 
�[40m�[37mdbug�[39m�[22m�[49m: Running /Users/cloudtest/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26107.1/tools/net10.0/any/../../../runtimes/any/native/mlaunch/bin/mlaunch
�[40m�[37mdbug�[39m�[22m�[49m: Using Xcode 26.1.1 found in /Applications/Xcode_26.1.1.app
�[40m�[37mdbug�[39m�[22m�[49m: xcrun simctl list --json --json-output /tmp/tmpfLItR7.tmp
�[40m�[37mdbug�[39m�[22m�[49m: Xamarin.Hosting: No need to boot (already booted): iPhone 11 Pro
�[40m�[37mdbug�[39m�[22m�[49m: Xamarin.Hosting: Installing on iPhone 11 Pro (755498B8-2E06-4CB4-BEE6-C43F6BDABC49) by executing 'xcrun simctl install 755498B8-2E06-4CB4-BEE6-C43F6BDABC49 /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.DeviceTests/Release/net10.0-ios/iossimulator-arm64/Microsoft.Maui.Controls.DeviceTests.app'
�[40m�[37mdbug�[39m�[22m�[49m: Xamarin.Hosting: The bundle id com.microsoft.maui.controls.devicetests was successfully installed.
�[40m�[37mdbug�[39m�[22m�[49m: Process mlaunch exited with 0
�[40m�[32minfo�[39m�[22m�[49m: Application 'Microsoft.Maui.Controls.DeviceTests' was installed successfully on 'iPhone 11 Pro'
�[40m�[32minfo�[39m�[22m�[49m: Starting test run for com.microsoft.maui.controls.devicetests..
�[40m�[37mdbug�[39m�[22m�[49m: *** Executing 'Microsoft.Maui.Controls.DeviceTests' on ios-simulator-64_18.6 'iPhone 11 Pro' ***
�[40m�[37mdbug�[39m�[22m�[49m: Test log server listening on: 0.0.0.0:56170
�[40m�[37mdbug�[39m�[22m�[49m: System log for the 'iPhone 11 Pro' simulator is: /Users/cloudtest/Library/Logs/CoreSimulator/755498B8-2E06-4CB4-BEE6-C43F6BDABC49/system.log
�[40m�[37mdbug�[39m�[22m�[49m: Simulator 'iPhone 11 Pro' is already booted
�[40m�[37mdbug�[39m�[22m�[49m: Scanning log stream for Microsoft.Maui.Controls.DeviceTests into '/Users/cloudtest/vss/_work/1/s/artifacts/log/Microsoft.Maui.Controls.DeviceTests.log'..
�[40m�[37mdbug�[39m�[22m�[49m: 
�[40m�[37mdbug�[39m�[22m�[49m: Running /Applications/Xcode_26.1.1.app/Contents/Developer/usr/bin/simctl
�[40m�[37mdbug�[39m�[22m�[49m: Launching the app
�[40m�[37mdbug�[39m�[22m�[49m: 
�[40m�[37mdbug�[39m�[22m�[49m: Running /Users/cloudtest/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26107.1/tools/net10.0/any/../../../runtimes/any/native/mlaunch/bin/mlaunch
�[40m�[37mdbug�[39m�[22m�[49m: Connection from 127.0.0.1:56179 saving logs to /Users/cloudtest/vss/_work/1/s/artifacts/log/test-ios-simulator-64_18.6-20260402_033253.log
�[40m�[37mdbug�[39m�[22m�[49m: Tests have finished executing
�[40m�[37mdbug�[39m�[22m�[49m: Process mlaunch exited with 0
�[40m�[37mdbug�[39m�[22m�[49m: Test run completed
�[40m�[37mdbug�[39m�[22m�[49m: Process simctl exited with 137
�[40m�[37mdbug�[39m�[22m�[49m: 
�[40m�[37mdbug�[39m�[22m�[49m: Running /bin/bash
�[40m�[37mdbug�[39m�[22m�[49m: cp: /Users/cloudtest/Library/Developer/CoreSimulator/Devices/755498B8-2E06-4CB4-BEE6-C43F6BDABC49/data/Containers/Data/Application/9406A8E7-0C74-4833-AA93-FA0929E9E779/Documents/test-results.xml: No such file or directory
�[40m�[37mdbug�[39m�[22m�[49m: Process bash exited with 1
�[40m�[37mdbug�[39m�[22m�[49m: Test run succeeded
�[40m�[37mdbug�[39m�[22m�[49m: No crash reports, waiting 0 seconds for the crash report service...
�[40m�[32minfo�[39m�[22m�[49m: Application finished the test run successfully
�[40m�[32minfo�[39m�[22m�[49m: Tests run: 202 Passed: 202 Inconclusive: 0 Failed: 0 Ignored: 0
�[40m�[32minfo�[39m�[22m�[49m: Uninstalling the application 'com.microsoft.maui.controls.devicetests' from 'iPhone 11 Pro'
�[40m�[37mdbug�[39m�[22m�[49m: 
�[40m�[37mdbug�[39m�[22m�[49m: Running /Applications/Xcode_26.1.1.app/Contents/Developer/usr/bin/simctl
�[40m�[37mdbug�[39m�[22m�[49m: Process simctl exited with 0
�[40m�[32minfo�[39m�[22m�[49m: Application 'com.microsoft.maui.controls.devicetests' was uninstalled successfully
XHarness exit code: 0
  Passed: 404
  Failed: 0
  Tests completed successfully

⚠️ Issues found
  • ShellTests (SettingFrameDoesTriggerInvalidatedMeasure) PASSED without fix (should fail) — tests don't catch the bug
📁 Fix files reverted (7 files)
  • eng/pipelines/ci-copilot.yml
  • src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellFlyoutRenderer.cs
  • src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellRenderer.cs
  • src/Controls/src/Core/FlyoutPage/FlyoutPage.cs
  • src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt
  • src/Core/src/Handlers/FlyoutView/FlyoutViewHandler.Android.cs
  • src/Core/src/Platform/Android/Navigation/NavigationViewFragment.cs

New files (not reverted):

  • github-merge-flow-release-11.jsonc

@MauiBot MauiBot added s/agent-changes-requested AI agent recommends changes - found a better alternative or issues and removed s/agent-approved AI agent recommends approval - PR fix is correct and optimal labels Apr 2, 2026
@kubaflo
Copy link
Copy Markdown
Contributor Author

kubaflo commented Apr 2, 2026

No longer needed

@kubaflo kubaflo closed this Apr 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-controls-shell Shell Navigation, Routes, Tabs, Flyout community ✨ Community Contribution platform/android platform/ios s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates 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.

OnSizeAllocated in Shell not triggered on Android or iOS in .NET 10 OnSizeAllocated event not fired for Android when rotating the device

5 participants