You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(045): remove broad catch{} swallows in tear-off pipeline (#420) (#421)
* fix(045): remove broad catch{} swallows in tear-off pipeline (#420)
PR #418 (Spec 045 §2.6 VS-style immediate tab tear-off) landed with 14
broad catch{} blocks across the tear-off code paths. Most were
dead-defensive: the ReactorWindow public mutators (Close, Hide, Activate,
SetOpacity, SetNoActivate, SetIgnorePointerInput) are all no-op on
_disposed and internally narrow the teardown-COMException set, so an
outer catch swallowed nothing.
Changes per issue #420 inventory:
DockTabTearOff.cs (1 site)
• PositionFloating tracker-tick catch removed. Audited Stop-before-Close
ordering: every path that closes the floating window calls Stop()
first (detaches Tick handler, clears s_active), so AppWindow.Move
always targets a live AppWindow.
DockHostNativeComponent.cs (4 sites → 0)
• BeginImmediateTearOff: DockFloatingWindow.Open catch removed.
Open failure is a bug we want to surface; layout mutation is
deferred until after Open returns.
• FinalizeImmediateDrop: two floatingWindow.Close catches removed.
ReactorWindow.Close is idempotent; called once per branch.
• FinalizeImmediateDrop drop-outside: strip-drag-styles catch removed.
All four mutators are disposal-safe.
DockFloatingWindow.cs (9 sites → 0, plus 1 latent bug fix)
• BeginFloatingTearOff source-XamlRoot read: catch removed.
• BeginFloatingTearOff Open call: catch removed.
• Single-tab path: ownWindow.AppWindow.Hide() → ownWindow.Hide().
The bare AppWindow.Hide() bypassed ReactorWindow.Hide's _disposed
guard and teardown-COMException catch; the wrapper has both.
• Multi-tab path SetIgnorePointerInput(true) — latent bug fix.
The call requires WS_EX_LAYERED. A regular floating window opens
with Opacity=1.0 (not layered), so SetIgnorePointerInput(true)
was throwing InvalidOperationException every multi-tab tear-off,
silently swallowed — the source window was never actually marked
click-through despite the comment claiming it was. Now nudge
SetOpacity(0.9999) first to install WS_EX_LAYERED (alpha rounds
to 255 → visually identical).
• RestoreSourcePointerInput catch removed; also restores SetOpacity(1.0)
to undo the layering nudge.
• Two capturedDragged.Close catches (enqueued + sync fallback) removed.
• Fallback xr ??= capturedDragged.NativeWindow?.Content?.XamlRoot
catch removed (window was just opened synchronously).
• RestoreWindowFromDrag four-setter catch removed (all disposal-safe).
Three explicitly out-of-scope catches in DockTabTearOff.cs are preserved
per the issue:
• L191 TabView.CapturePointer (best-effort, can fail on stale pointer)
• L256 TabView.ReleasePointerCaptures (no captures → no-op)
• L502 active.CancelDrop in Tracker.ForceCancel (must not crash next drag)
Verified: dotnet test Reactor.Tests on ARM64 — 9038 passed, 0 failed.
The selftest fixtures (T07/T08 host tear-off, T11-T14 floating tear-off)
check Spec.Opacity >= 0.99 on restore; the 0.9999 layering nudge plus
the explicit SetOpacity(1.0) restore satisfy those assertions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(045): narrow XamlRoot-read catch to ERROR_INVALID_OPERATION_ID
T14 (NativeDockingTearOff_T14_FloatingTearOff_StaleTrackerForceCleaned)
regressed when the catch at BeginFloatingTearOff L547 was removed in the
previous commit. The catch wasn't dead defensiveness — it was guarding a
cross-render teardown race that T14 explicitly exercises:
1. First tear-off (single-tab path) → RemoveLocal sees willEmpty=true
→ holder[0]?.Close() → source `floating` window disposes
2. `windowHolder` array still holds a strong reference to the disposed
ReactorWindow across the test's `await Harness.Render()`
3. Second press fires on the same TabView (still alive — WinUI hasn't
synchronously torn down the visual tree)
4. BeginFloatingTearOff runs the closure from the floating window's
last render; `holder[0]` returns the disposed ReactorWindow
5. `ReactorWindow.NativeWindow` is a raw field accessor (no _disposed
guard) → `.Content` is a WinRT projection call into the disconnected
COM proxy → throws COMException with HResult 0x800710DD
(HRESULT_FROM_WIN32(ERROR_INVALID_OPERATION_ID=4317), surface text
"The operation identifier is not valid.")
Empirically verified the HResult ↔ message mapping with
`[System.ComponentModel.Win32Exception]::new(0x800710DD).Message`; my
prior commit comment incorrectly cited 0x80070C70 (NET.ACC database
error, unrelated).
Fix per the issue author's L547 prescription:
> Narrow to COMException and treat throw as identical to null (continue
> with the `xr` fallback at L657).
Added `HResults.ERROR_INVALID_OPERATION_ID = 0x800710DD` to the central
constants file and use it in a `when` filter — anything else here is a
real bug we want to surface. The `xr ??= capturedDragged.NativeWindow?.
Content?.XamlRoot` fallback below picks up the freshly-opened dragged
preview's XamlRoot when the source is dead.
Why not a deeper structural fix (null windowHolder[0] on Closed,
detach press hook on TabView unload)? Either would change T14's
expectation — the test explicitly requires the second tear-off to
SUCCEED on the orphaned TabView with a fresh preview window. Per the
test's stuck-state-recovery contract, the correct behavior is to
continue gracefully when the source is dead, not to refuse the drag.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0 commit comments