Skip to content

Fixed When focus is on entry and closing the app the "object disposed exception is thrown in windows platform" - #34354

Merged
kubaflo merged 13 commits into
dotnet:inflight/currentfrom
KarthikRajaKalaimani:fix-34272
Jul 16, 2026
Merged

Fixed When focus is on entry and closing the app the "object disposed exception is thrown in windows platform"#34354
kubaflo merged 13 commits into
dotnet:inflight/currentfrom
KarthikRajaKalaimani:fix-34272

Conversation

@KarthikRajaKalaimani

Copy link
Copy Markdown
Contributor

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!

Issue Details:

When focus is on entry and closing the app the "object disposed exception is thrown in windows platform"

Root Cause:

When closing a Windows MAUI app with style triggers on IsFocused:

  1. Window.Destroying() calls Handler?.DisconnectHandler() → sets Window's Handler = null, disposes the WinUI window content
  2. Then calls mauiContext?.DisposeWindowScope() → disposes the window-scoped IServiceProvider
  3. After this (async via WinUI's message pump), FocusManager.LostFocus fires on the focused Entry's TextBox
  4. ViewHandler.FocusManager_LostFocus → handler.UpdateIsFocused(false) → virtualView.IsFocused = false
  5. IsFocused property change fires the style trigger → setter applies FontAttributes.Bold → HandleFontChanged() → Handler.UpdateValue("Font") → MapFont →
    handler.GetRequiredService() → ObjectDisposedException on the already-disposed window scope

Description of Change:

In ElementHandlerExtensions.cs, the existing CanInvokeMappers() method — which already had an Android guard for disposed native views — was extended with a new cross-platform check: if the handler's MauiContext is a MauiContext instance and its IsWindowScopeDisposed flag is true, the method returns false. This means any mapper invocation that goes through this guard (including the IsFocused → style trigger → HandleFontChanged → MapFont → GetRequiredService() chain that was crashing) is silently skipped once the window scope is disposed, preventing any attempt to resolve services from the already-disposed IServiceScope.

Tested the behavior in the following platforms.

  • Android
  • Windows
  • iOS
  • Mac

Reference:

N/A

Issues Fixed:

Fixes #34272

Screenshots

Before After
555802171-27ff9777-aec5-46d1-b869-361b91f4cd4e
screen-capture.webm

@github-actions

github-actions Bot commented Mar 6, 2026

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 -- 34354

Or

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

@dotnet-policy-service dotnet-policy-service Bot added the community ✨ Community Contribution label Mar 6, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Hey there @@KarthikRajaKalaimani! Thank you so much for your PR! Someone from the team will get assigned to your PR shortly and we'll get it reviewed.

@dotnet-policy-service dotnet-policy-service Bot added the partner/syncfusion Issues / PR's with Syncfusion collaboration label Mar 6, 2026
@sheiksyedm
sheiksyedm marked this pull request as ready for review March 10, 2026 11:35
Copilot AI review requested due to automatic review settings March 10, 2026 11:35
@sheiksyedm sheiksyedm added area-core-lifecycle XPlat and Native UIApplicationDelegate/Activity/Window lifecycle events and removed area-controls-entry Entry labels Mar 10, 2026

Copilot AI left a comment

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.

Pull request overview

This PR addresses a Windows crash during app shutdown where property/command mappers (triggered by IsFocused style triggers) can run after the window-scoped IServiceScope has already been disposed, leading to ObjectDisposedException when resolving services like IFontManager.

Changes:

  • Add MauiContext.IsWindowScopeDisposed and set it during DisposeWindowScope().
  • Extend ElementHandlerExtensions.CanInvokeMappers() to return false once the window scope is disposed, preventing mapper execution during teardown.
  • Add a Windows device test asserting CanInvokeMappers() is blocked after DisposeWindowScope().

Reviewed changes

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

File Description
src/Core/src/MauiContext.cs Tracks window-scope disposal via a new internal flag.
src/Core/src/Handlers/ElementHandlerExtensions.cs Uses the new flag to prevent mapper invocations after teardown.
src/Controls/tests/DeviceTests/Elements/View/ViewTests.Windows.cs Adds a Windows regression test for the teardown guard behavior.

Comment thread src/Core/src/MauiContext.cs
Comment thread src/Controls/tests/DeviceTests/Elements/View/ViewTests.Windows.cs Outdated
@sheiksyedm

Copy link
Copy Markdown
Contributor

/azp run maui-pr-uitests

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@MauiBot MauiBot added s/agent-review-incomplete s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) labels Mar 20, 2026

@kubaflo kubaflo left a comment

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.

It looks like this might affect not only Windows, but other platforms as well. Could you either add tests for all target platforms or scope the fix to Windows only?

@KarthikRajaKalaimani

Copy link
Copy Markdown
Contributor Author

It looks like this might affect not only Windows, but other platforms as well. Could you either add tests for all target platforms or scope the fix to Windows only?

I have scoped the fix to windows only.

@kubaflo kubaflo added the s/agent-suggestions-implemented Maintainer applies when PR author adopts agent's recommendation label Mar 23, 2026
@MauiBot MauiBot added s/agent-approved AI agent recommends approval - PR fix is correct and optimal s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates and removed s/agent-review-incomplete labels Mar 23, 2026
@sheiksyedm

Copy link
Copy Markdown
Contributor

/azp run maui-pr-uitests , maui-pr-devicetests

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 2 pipeline(s).

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Jul 5, 2026

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Expert Review — 1 findings

See inline comments for details.

public async Task CanInvokeMappers_DuringWindowScopeDispose_ReturnsFalse()
{
var entry = new Entry();
      {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔍 AI-Generated Review (multi-model)

[major] Regression Prevention and Test Coverage — This line starts only a local block, so the async test has no await (CS1998, with repo-wide TreatWarningsAsErrors=true) and the handler/teardown path runs on the test runner thread instead of the Windows UI thread where FocusManager.LostFocus and mapper callbacks occur. Mirror the first test by wrapping this block in await InvokeOnMainThreadAsync(() => { ... }); so the regression both compiles cleanly and validates the actual Windows handler lifecycle.

MauiBot

This comment was marked as outdated.

@MauiBot MauiBot removed the s/agent-review-in-progress AI review is currently running for this PR label Jul 5, 2026
@kubaflo

This comment has been minimized.

@github-actions

This comment has been minimized.

@kubaflo kubaflo left a comment

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.

Could you please check the failed tests?

@kubaflo kubaflo left a comment

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.

Could you please check the ai's suggestions?

@kubaflo

This comment has been minimized.

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Jul 13, 2026
@MauiBot MauiBot added s/agent-fix-win AI found a better alternative fix than the PR and removed s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates labels Jul 13, 2026
MauiBot

This comment was marked as outdated.

@MauiBot MauiBot removed the s/agent-review-in-progress AI review is currently running for this PR label Jul 13, 2026
@kubaflo

This comment has been minimized.

@github-actions

This comment has been minimized.

@kubaflo

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

Tests Failure Analysis

@KarthikRajaKalaimani — test-failure review results are available based on commit a9c0ade.
To request a fresh review after new comments, commits, or CI runs, comment /review tests.

Overall Insufficient data Failures 13 Baseline 0 on base Platform windows

Test Failure Review: Insufficient data - click to expand

Overall verdict: Insufficient data

All 12 inspectable failing checks returned 404 from the AzDO REST API (unauthenticated access was blocked), so no test-level failure details could be extracted. Additionally, 2 checks concluded as CANCELLED and 7 green device-test legs could not be positively verified. No baseline comparison was available.

Coverage: 162 checks · 149 passing · 13 failing · 0 pending · 12 inaccessible · 1 unmapped · 0 unexplained build legs · 0 unaccounted failing checks · 2 aborted failing checks · 0 canceled-build checks · 7 device-test unverified · 0 unattributed · 0 regressed-vs-base. Deterministic ceiling: Insufficient data — 12 failing check(s) could not be inspected (AzDO build/logs inaccessible): maui-pr-devicetests, maui-pr-devicetests (net10.0 Windows Helix Tests Run DeviceTests Windows), maui-pr-uitests, maui-pr-uitests (Android UITests Controls (API 30) CollectionView), maui-pr-uitests (Android UITests Controls (API 30) Entry), maui-pr-uitests (iOS UITests Mono CollectionView1 Controls (vlatest)), maui-pr-uitests (iOS UITests Mono Controls (vlatest) WebView), maui-pr-uitests (macOS UITests Controls Editor,Effects,Essentials,FlyoutPage,Focus,Fonts,Frame,Gestures,GraphicsView).

Failure Verdict On base? Evidence
maui-pr-devicetests Insufficient data unknown AzDO build 1478005 returned 404; no log data accessible without authentication
maui-pr-devicetests (net10.0 Windows Helix Tests Run DeviceTests Windows) Insufficient data unknown AzDO build 1478005 job inaccessible (404)
maui-pr-uitests Insufficient data unknown AzDO build 1478004 returned 404; no log data accessible without authentication
maui-pr-uitests (Android UITests Controls (API 30) CollectionView) Insufficient data unknown AzDO build 1478004 job inaccessible (404)
maui-pr-uitests (Android UITests Controls (API 30) Entry) Insufficient data unknown AzDO build 1478004 job inaccessible (404)
maui-pr-uitests (iOS UITests Mono CollectionView1 Controls (vlatest)) Insufficient data unknown AzDO build 1478004 job inaccessible (404)
maui-pr-uitests (iOS UITests Mono Controls (vlatest) WebView) Insufficient data unknown AzDO build 1478004 job inaccessible (404)
maui-pr-uitests (macOS UITests Controls Editor,Effects,Essentials,FlyoutPage,Focus,Fonts,Frame,Gestures,GraphicsView) Insufficient data unknown AzDO build 1478004 job inaccessible (404)
maui-pr-uitests (macOS UITests Controls ListView) Insufficient data unknown AzDO build 1478004 job inaccessible (404)
maui-pr-uitests (macOS UITests Controls WebView) Insufficient data unknown AzDO build 1478004 job inaccessible (404)
maui-pr-uitests (macOS UITests Controls CollectionView) Insufficient data unknown Concluded CANCELLED (aborted check); no extractable failure; aborted leg cannot be dismissed
maui-pr-uitests (macOS UITests Controls Shell) Insufficient data unknown Concluded CANCELLED (aborted check); no extractable failure; aborted leg cannot be dismissed
Build Analysis Insufficient data unknown Unmapped check — no AzDO build backing; links to Arcade Build Analysis documentation page

Recommended action

A human reviewer should inspect the AzDO build results directly (builds 1478005 and 1478004) to determine whether failures are pre-existing on main or introduced by this PR. The two CANCELLED macOS CollectionView/Shell jobs should also be investigated.

Evidence details
  • AzDO authenticated: false (no token available)
  • Both backing builds (1478004 and 1478005) returned HTTP 404 from the unauthenticated AzDO REST API — all test logs, timelines, and test result records were inaccessible
  • 2 aborted (CANCELLED) checks: maui-pr-uitests (macOS UITests Controls CollectionView) and maui-pr-uitests (macOS UITests Controls Shell) — these count toward the gate ceiling and cannot be dismissed
  • 7 green device-test legs could not be positively verified as passing (XHarness exits 0 on device-test failure, so green is trusted only after a complete, error-free read): net10.0 Android CoreCLR Build, net10.0 Android CoreCLR Run, net10.0 Android Mono Run, net10.0 MacCatalyst Mono Run, net10.0 Windows Build, net10.0 iOS Mono Run, net10.0 ios/catalyst/android Mono Build
  • 1 unmapped failing check: Build Analysis (links to Arcade documentation, not a build)
  • No baseline build from main was available for comparison
  • Changed files: src/Controls/tests/DeviceTests/Elements/View/ViewTests.Windows.cs, src/Core/src/Handlers/ElementHandlerExtensions.cs, src/Core/src/MauiContext.cs
  • PR scope: Windows platform, area-core-lifecycle

@kubaflo

This comment has been minimized.

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Jul 15, 2026

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Expert Review — 1 findings

See inline comments for details.


        var mauiContext = new MauiContext(innerScope.ServiceProvider);
        mauiContext.SetWindowScope(spyScope);
        var handler = CreateHandler<EntryHandler>(entry, mauiContext);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔍 AI-Generated Review (multi-model)

[major] Async and Threading Safety / Regression PreventionCanInvokeMappers_DuringWindowScopeDispose_ReturnsFalse is async Task but never awaits anything (no InvokeOnMainThreadAsync wrapper), so the whole body — including CreateHandler<EntryHandler>(entry, mauiContext) on this line, which constructs a real WinUI platform view/handler — runs synchronously on whatever thread the test runner invokes the test on, not the UI/dispatcher thread. Every other Windows device test in this file/sibling files (see CanInvokeMappers_AfterWindowScopeDisposed_ReturnsFalse above and EntryTests.Windows.cs) wraps handler creation in await InvokeOnMainThreadAsync(() => { ... }). Creating a XAML TextBox-backed EntryHandler off the UI thread can throw a wrong-thread/RPC_E_WRONG_THREAD-style exception on real Windows targets, or at minimum the test does not actually exercise the intended UI-thread reentrancy scenario (FocusManager.LostFocus firing synchronously from within Dispose() on the same UI thread) that this regression test is supposed to prove. Wrap this test body in await InvokeOnMainThreadAsync(() => { ... }) like the sibling test.

@MauiBot MauiBot added s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates and removed s/agent-fix-win AI found a better alternative fix than the PR labels Jul 15, 2026

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Review Summary

@KarthikRajaKalaimani — new AI review results are available based on this last commit: a9c0ade. To request a fresh review after new comments or commits, comment /review rerun.

Gate Inconclusive Confidence Low Platform Windows


🗂️ Review Sessions — click to expand
🚦 Gate — Test Before & After Fix

Gate Result: ⚠️ INCONCLUSIVE

Platform: WINDOWS · Base: main · Merge base: 0395a53b

Test Without Fix (expect FAIL) With Fix (expect PASS)
📱 ViewTests (CanInvokeMappers_AfterWindowScopeDisposed_ReturnsFalse, CanInvokeMappers_DuringWindowScopeDispose_ReturnsFalse) Category=View ⚠️ ENV ERROR ⚠️ ENV ERROR
🔴 Without fix — 📱 ViewTests (CanInvokeMappers_AfterWindowScopeDisposed_ReturnsFalse, CanInvokeMappers_DuringWindowScopeDispose_ReturnsFalse): ⚠️ ENV ERROR · 232s

No log file found

🟢 With fix — 📱 ViewTests (CanInvokeMappers_AfterWindowScopeDisposed_ReturnsFalse, CanInvokeMappers_DuringWindowScopeDispose_ReturnsFalse): ⚠️ ENV ERROR · 255s

No log file found

⚠️ Failure Details

  • ⚠️ ViewTests (CanInvokeMappers_AfterWindowScopeDisposed_ReturnsFalse, CanInvokeMappers_DuringWindowScopeDispose_ReturnsFalse) without fix: Cannot convert the "System.Object[]" value of type "System.Object[]" to type "System.Int32".
  • ⚠️ ViewTests (CanInvokeMappers_AfterWindowScopeDisposed_ReturnsFalse, CanInvokeMappers_DuringWindowScopeDispose_ReturnsFalse) with fix: Cannot convert the "System.Object[]" value of type "System.Object[]" to type "System.Int32".
📁 Fix files reverted (2 files)
  • src/Core/src/Handlers/ElementHandlerExtensions.cs
  • src/Core/src/MauiContext.cs

📱 UI Tests — ViewBaseTests

Detected UI test categories: ViewBaseTests

Deep UI tests — 115 passed, 0 failed across 1 category on platform-pool agent (replaces in-process counts above).

🧪 UI Test Execution Results (deep, platform pool)

Category Tests Snapshot diffs
ViewBaseTests 115/115 ✓
📎 Download drop-deep-uitests artifact (TRX + snapshot diffs)

📋 Pre-Flight — Context & Validation

Issue: #34272 - ObjectDisposed Exception Closing an App on Windows
PR: #34354 - Fixed When focus is on entry and closing the app the "object disposed exception is thrown in windows platform"
Platforms Affected: Windows
Files Changed: 2 implementation, 1 test

Key Findings

  • Issue #34272 is a Windows regression where closing an app with a focused Entry and IsFocused style triggers can fire a late focus-loss/property update after the window IServiceScope has been disposed, causing ObjectDisposedException while mapper code resolves services.
  • PR #34354 adds a Windows-only guard in CanInvokeMappers() using MauiContext.IsWindowScopeDisposed and moves the disposed flag before _windowScope.Dispose(), which addresses the known teardown timing gap.
  • The added Windows device test coverage targets both after-disposal and during-disposal behavior, but the current second test creates the handler off the UI thread and is async without await.
  • PR discussion contains prior MauiBot major findings about setting the disposed flag before disposing the scope; the current production code appears to have addressed those findings.

Code Review Summary

Verdict: NEEDS_CHANGES
Confidence: low
Errors: 1 | Warnings: 0 | Suggestions: 0

Key code review findings:

  • ✗ src/Controls/tests/DeviceTests/Elements/View/ViewTests.Windows.cs:54-71 - CanInvokeMappers_DuringWindowScopeDispose_ReturnsFalse is async with no await and creates an EntryHandler outside InvokeOnMainThreadAsync, so it can fail build/threading before validating the regression.

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #34354 Windows-only CanInvokeMappers guard checks MauiContext.IsWindowScopeDisposed; DisposeWindowScope sets the flag before disposing and resets it when a new window scope is assigned. ⚠️ INCONCLUSIVE (Gate) src/Core/src/Handlers/ElementHandlerExtensions.cs, src/Core/src/MauiContext.cs, src/Controls/tests/DeviceTests/Elements/View/ViewTests.Windows.cs Original PR; production approach appears sound, test still needs UI-thread fix.

🔬 Code Review — Deep Analysis

Code Review — PR #34354

Independent Assessment

What this changes: Adds a Windows mapper guard: after MauiContext.DisposeWindowScope() marks the window scope disposed, property/command mappers skip execution to avoid resolving services from a disposed scope. Adds Windows device tests for after/during disposal behavior.
Inferred motivation: Prevent late WinUI focus/style-trigger updates during app shutdown from throwing ObjectDisposedException.

Reconciliation with PR Narrative

Author claims: Fixes Windows shutdown crash when an focused Entry with IsFocused style triggers causes late mapper execution after window scope disposal.
Agreement/disagreement: The code matches the root-cause chain. The PR body calls the check “cross-platform”, but the actual guard is #if WINDOWS, which is consistent with the Windows-specific issue.

Prior Review Reconciliation

Prior ❌ Error Finding Source Status Evidence
IsWindowScopeDisposed was set after _windowScope.Dispose(), leaving a re-entry window during disposal MauiBot inline reviews on MauiContext.cs:67 ✅ Fixed Current MauiContext.cs:67-70 sets IsWindowScopeDisposed = true before _windowScope?.Dispose().
Test only verified behavior after disposal, not during disposal MauiBot inline reviews on ViewTests.Windows.cs ✅ Partially fixed Current second test observes CanInvokeMappers() inside SpyServiceScope.Dispose() at ViewTests.Windows.cs:64-78.
Brace/syntax errors in the added test file MauiBot inline reviews on ViewTests.Windows.cs ✅ Fixed Current file is structurally balanced; the second test remains inside ViewTests.
Second Windows test creates handler off UI thread / has no await MauiBot inline reviews on ViewTests.Windows.cs:54/71 ❌ Unresolved Current ViewTests.Windows.cs:54-71 is still async Task with no await and calls CreateHandler<EntryHandler> outside InvokeOnMainThreadAsync.

Blast Radius Assessment

  • Runs for all instances: Yes, all Windows mapper invocations go through CanInvokeMappers().
  • Startup impact: No; only affects mapper execution after window-scope disposal.
  • Static/shared state: No global static state; flag is per MauiContext.

CI Status

  • Required-check result: gh pr checks --required unavailable (gh auth login required). Public GitHub check-runs for head a9c0ade show failures/cancellations.
  • Classification: PR CI state undetermined/red; prior /review tests comment reports insufficient data due unauthenticated AzDO 404s.
  • Action taken: capped confidence low.

Findings

❌ Error — Added Windows test still runs handler creation off the UI thread and likely fails build

src/Controls/tests/DeviceTests/Elements/View/ViewTests.Windows.cs:54-71

CanInvokeMappers_DuringWindowScopeDispose_ReturnsFalse is declared async Task but never awaits anything. The repo has TreatWarningsAsErrors=true, so CS1998 can break the build. More importantly, line 71 calls CreateHandler<EntryHandler> without InvokeOnMainThreadAsync, unlike the first test at lines 22-44. On Windows, handler/platform view creation touches WinUI dispatcher-bound objects, so this test can fail for wrong-thread reasons instead of validating the teardown guard.

Wrap the second test body in await InvokeOnMainThreadAsync(() => { ... });.

Failure-Mode Probing

  • Late mapper during _windowScope.Dispose(): blocked because the flag is now set before disposal.
  • Mapper after window teardown: blocked on Windows via CanInvokeMappers().
  • Added regression test on Windows dispatcher: currently not safe; the second test does not marshal to UI thread.
  • Null/default scope: DisposeWindowScope() tolerates null via null-conditional dispose, but still marks disposed; production window contexts normally set scope through MakeWindowScope().

Verdict: NEEDS_CHANGES

Confidence: low
Summary: The production fix direction looks sound, but the added Windows regression test still has an unresolved build/threading issue previously flagged by reviewers. CI is also red/undetermined with unauthenticated AzDO access, so this PR should not merge until the test is fixed and CI is rechecked.


🛠️ Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 try-fix Windows FocusManager source suppression: remove stale focus mapping on Unloaded/Loaded and ignore stale focus callbacks. ❌ FAIL 3 files Built, but View XML had 37/39 passed; both CanInvokeMappers teardown tests failed. Too narrow because mapper calls can originate outside FocusManager.
2 try-fix MauiContext window-scope teardown CancellationToken checked by Windows CanInvokeMappers. ⚠️ Behavior PASS / command FAIL 2 files Build passed and View XML had 39/39 passed, but runner exited nonzero in result-summary parser. Not better than PR because it adds token allocation/disposal complexity for the same state signal.
3 try-fix Windows platform-window/IWindow.Handler lifecycle check in CanInvokeMappers. ❌ FAIL 2 files Built, but View XML had 0/39 passed. Too indirect; synthetic scope teardown and valid contexts may not have a platform-window signal.
PR PR #34354 Internal MauiContext.IsWindowScopeDisposed flag reset in SetWindowScope, set before DisposeWindowScope disposes the scope, checked by Windows CanInvokeMappers. ⚠️ INCONCLUSIVE (Gate pre-run could not build/run) 3 files Production approach remains simplest viable mapper-level teardown signal. Pre-flight code review still flags the second added Windows test as async/no-await and not wrapped in InvokeOnMainThreadAsync.

Cross-Pollination

Model Round New Ideas? Details
maui-expert-reviewer 2 No NO NEW IDEAS: viable production shape is mapper-level suppression keyed off window-scope teardown; source suppression is too narrow, window/platform lifecycle checks are too indirect, and token/catch/nulling variants are either the same signal with more complexity or riskier than the current MauiContext.IsWindowScopeDisposed flag.

Exhausted: Yes
Selected Fix: PR #34354 — The PR's boolean window-scope teardown flag is the simplest mapper-level guard that handles both during-dispose and after-dispose cases. Candidate 2 can pass the behavior assertions but is not demonstrably better; candidates 1 and 3 fail the regression tests.

Iteration Notes

  • Baseline script could not run because the worktree contained unrelated dirty files before this task. Candidate patches were applied and restored surgically against only the PR files.
  • The Windows device-test runner consistently built and launched the app, but its summary parser failed with Cannot convert the "System.Object[]" value of type "System.Object[]" to type "System.Int32"; generated xUnit XML was used to classify View category behavior.

📝 Recommended PR Title & Description

Assessment: ✏️ Recommend updating — the current description is technically detailed, but the title is not in the required platform/component form and the description should mention the added Windows regression tests plus the UI-thread test adjustment from the winning candidate.

Recommended title

[Windows] Handlers: Skip mapper updates after window scope disposal

Recommended description

### Issue Details

When focus is on an `Entry` and a Windows MAUI app is closed, an `ObjectDisposedException` can be thrown if style triggers react to `IsFocused` changes during teardown.

### Root Cause

When closing a Windows MAUI app with style triggers on `IsFocused`:

1. `Window.Destroying()` calls `Handler?.DisconnectHandler()`, which clears the Window handler and disposes the WinUI window content.
2. It then calls `mauiContext?.DisposeWindowScope()`, which disposes the window-scoped `IServiceProvider`.
3. After this, WinUI can asynchronously raise `FocusManager.LostFocus` for the focused `Entry`'s `TextBox`.
4. `ViewHandler.FocusManager_LostFocus` calls `handler.UpdateIsFocused(false)`, updating `virtualView.IsFocused`.
5. The `IsFocused` property change fires the style trigger, whose setter applies `FontAttributes.Bold`.
6. That flows through `HandleFontChanged()` -> `Handler.UpdateValue("Font")` -> `MapFont` -> `handler.GetRequiredService<IFontManager>()`, which can resolve services from the already-disposed window scope and throw `ObjectDisposedException`.

### Description of Change

`ElementHandlerExtensions.CanInvokeMappers()` now includes a Windows guard that returns `false` when the handler's `MauiContext` is a `MauiContext` instance whose window scope has started disposal.

`MauiContext` now tracks this lifecycle with an internal `IsWindowScopeDisposed` flag:

- `SetWindowScope()` resets the flag when a new window scope is assigned.
- `DisposeWindowScope()` sets the flag before disposing `_windowScope`, so mapper calls are blocked both during scope disposal and after disposal completes.

This prevents late mapper invocations, including the `IsFocused` -> style trigger -> font mapper path, from resolving services from a disposed window scope.

### Tests

Added Windows `ViewTests` coverage for:

- `CanInvokeMappers()` returning `false` after window scope disposal.
- `CanInvokeMappers()` returning `false` during `_windowScope.Dispose()`, using a spy `IServiceScope` to observe the timing window.

The during-disposal test should run its handler creation and teardown observation inside `InvokeOnMainThreadAsync`, matching Windows device-test threading requirements.

### Platforms Tested

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

### Issues Fixed

Fixes #34272

### Screenshots

| Before | After |
|--------|-------|
| <img width="831" height="150" alt="555802171-27ff9777-aec5-46d1-b869-361b91f4cd4e" src="https://github.com/user-attachments/assets/56131197-d131-4c5d-bb76-4b8666cb1e43" /> | <Video src="https://github.com/user-attachments/assets/a91cea91-39ea-4649-ae3d-9a86144e8f35" Width="300" Height="600"> |

🏁 Report — Final Recommendation

Comparative Report — PR #34354

Candidates Compared

Rank Candidate Regression/Test Result Assessment
1 pr-plus-reviewer Gate not rerun; based on raw PR gate result: inconclusive environment error Best candidate. Keeps the PR's simple direct window-scope teardown signal and applies expert reviewer feedback by moving the second Windows regression test onto the UI thread.
2 try-fix-2 Behavior PASS / command FAIL Functionally viable by generated XML (39/39 View tests passed), but it replaces the simple internal disposed flag with CancellationTokenSource allocation/disposal complexity for the same lifecycle signal.
3 pr Gate inconclusive environment error Production approach is sound, but the submitted test still has the expert-reviewed Windows threading flaw.
4 try-fix-1 FAIL Failed the PR regression tests (37/39 View tests passed). Suppressing only Windows focus-source callbacks is too narrow because mapper calls can originate outside FocusManager.
5 try-fix-3 FAIL Failed the View category (0/39 passed). Platform-window/IWindow.Handler lifecycle state is too indirect and misses valid/synthetic scope-teardown paths.

Failed regression-test candidates are ranked below candidates that passed behavior assertions or remained inconclusive due environment issues.

Key Comparison

The core issue is service-scope lifetime, not focus alone. A robust fix needs to block mapper execution once window-scope teardown starts, including callbacks that happen inside _windowScope.Dispose() and callbacks that happen after disposal. The PR's MauiContext.IsWindowScopeDisposed signal models that lifetime directly and is checked at the mapper gate, so it covers the failing path without depending on a particular event source.

try-fix-1 fails because it only guards the known FocusManager source. try-fix-3 fails because platform-window state is an indirect proxy and is not always available. try-fix-2 passes the behavior assertions but encodes the same lifecycle signal through a cancellation token, adding complexity without improving the production behavior.

The expert reviewer did not find a production-code flaw in the PR approach. The only actionable issue was in the second Windows regression test: handler creation and teardown observation must run inside InvokeOnMainThreadAsync. Applying that feedback produces pr-plus-reviewer, which keeps the best production approach and fixes the test-threading problem.

Winning Candidate

Winner: pr-plus-reviewer

Rationale: It is the smallest sound production fix and has better test quality than the raw PR. It also avoids the complexity of try-fix-2 and the demonstrated regression failures in try-fix-1 and try-fix-3.

Recommendation

Proceed with the PR fix after applying the reviewer test adjustment from pr-plus-reviewer. Do not replace the production fix with any STEP 5a try-fix candidate.


🧭 Next Steps — review latest findings

No alternative fix was selected for this run. Review the session findings and CI results before merging.

@MauiBot MauiBot removed the s/agent-review-in-progress AI review is currently running for this PR label Jul 15, 2026
kubaflo pushed a commit that referenced this pull request Jul 15, 2026
The gate detects a Windows device test as Category=<Name> and passes it to
Run-DeviceTests.ps1, which selected categories with a substring match. A bare
category name is a substring of many others (View is contained in BoxView,
CarouselView, CollectionView, ScrollView, WebView, TemplatedView, …), so a single
Category=View filter fanned out to 11 unrelated categories. Running them all
took ~6.5 min per gate run AND, when their result files were aggregated,
[int]($assembly.total ?? 0) hit a multi-value attribute and threw
"Cannot convert the System.Object[] value ... to type System.Int32" — surfaced
to the PR as a spurious Gate ENV ERROR with 'No log file found'
(e.g. #34354 ViewTests, Windows).

Select-WindowsDeviceTestCategories now prefers an EXACT (case-insensitive)
category match per filter token, falling back to substring only when no category
equals the token (keeps genuine partial filters working). Result-count parsing is
routed through a new ConvertTo-DeviceTestCount helper that is array/null tolerant,
so an unexpected result-file shape can never again throw an int-cast and mask
results as an env error.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 15d2af20-e4ab-4e88-9011-cfbd83513bc0
@kubaflo
kubaflo changed the base branch from main to inflight/current July 16, 2026 19:43
@kubaflo
kubaflo merged commit d94c3ad into dotnet:inflight/current Jul 16, 2026
155 of 168 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-core-lifecycle XPlat and Native UIApplicationDelegate/Activity/Window lifecycle events community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration platform/windows 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-suggestions-implemented Maintainer applies when PR author adopts agent's recommendation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ObjectDisposed Exception Closing an App on Windows

6 participants