Fixed When focus is on entry and closing the app the "object disposed exception is thrown in windows platform" - #34354
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34354Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34354" |
|
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. |
There was a problem hiding this comment.
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.IsWindowScopeDisposedand set it duringDisposeWindowScope(). - Extend
ElementHandlerExtensions.CanInvokeMappers()to returnfalseonce the window scope is disposed, preventing mapper execution during teardown. - Add a Windows device test asserting
CanInvokeMappers()is blocked afterDisposeWindowScope().
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. |
|
/azp run maui-pr-uitests |
|
Azure Pipelines successfully started running 1 pipeline(s). |
kubaflo
left a comment
There was a problem hiding this comment.
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. |
|
/azp run maui-pr-uitests , maui-pr-devicetests |
|
Azure Pipelines successfully started running 2 pipeline(s). |
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 1 findings
See inline comments for details.
| public async Task CanInvokeMappers_DuringWindowScopeDispose_ReturnsFalse() | ||
| { | ||
| var entry = new Entry(); | ||
| { |
There was a problem hiding this comment.
🔍 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.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
kubaflo
left a comment
There was a problem hiding this comment.
Could you please check the failed tests?
kubaflo
left a comment
There was a problem hiding this comment.
Could you please check the ai's suggestions?
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Tests Failure Analysis
Test Failure Review: Insufficient data - click to expandOverall 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).
Recommended actionA human reviewer should inspect the AzDO build results directly (builds 1478005 and 1478004) to determine whether failures are pre-existing on Evidence details
|
This comment has been minimized.
This comment has been minimized.
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 1 findings
See inline comments for details.
|
|
||
| var mauiContext = new MauiContext(innerScope.ServiceProvider); | ||
| mauiContext.SetWindowScope(spyScope); | ||
| var handler = CreateHandler<EntryHandler>(entry, mauiContext); |
There was a problem hiding this comment.
🔍 AI-Generated Review (multi-model)
[major] Async and Threading Safety / Regression Prevention — CanInvokeMappers_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
left a comment
There was a problem hiding this comment.
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.
🗂️ 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 |
🔴 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.cssrc/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. | 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 --requiredunavailable (gh auth loginrequired). Public GitHub check-runs for heada9c0adeshow failures/cancellations. - Classification: PR CI state undetermined/red; prior
/review testscomment 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 throughMakeWindowScope().
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. | 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. | 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.
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
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:
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.
Reference:
N/A
Issues Fixed:
Fixes #34272
Screenshots
screen-capture.webm