Skip to content

Migrate picker helpers to Microsoft.Windows.Storage.Pickers - #1394

Open
Alexandre Zollinger Chohfi (azchohfi) wants to merge 1 commit into
mainfrom
azchohfi-migrate-picker-helpers-to-windows-storag
Open

Alexandre Zollinger Chohfi (azchohfi) wants to merge 1 commit into
mainfrom
azchohfi-migrate-picker-helpers-to-windows-storag

Conversation

@azchohfi

Copy link
Copy Markdown
Collaborator

Summary

Moves Reactor's picker helpers from the UWP Windows.Storage.Pickers + InitializeWithWindow to Microsoft.Windows.Storage.Pickers, which ships in the Foundation 2.1.0 package that Microsoft.WindowsAppSDK.WinUI 2.2.1 already pulls in. Pickers are now constructed from the owner's AppWindow.Id, so there is no HWND/COM interop, and they work in elevated apps. The PR also fixes the owning-window bug and the hook-analyzer false positives from #1372's review.

  • Renamed as non-hooks: PickFileAsync, PickMultipleFilesAsync, PickFolderAsync, PickSaveFileAsync, plus the new PickMultipleFoldersAsync, on RenderContext with protected forwarders on Component.
    • They reserve no hook slot and must not run during render, so they are renamed rather than exempted from HookRulesAnalyzer. Calling them from a click lambda or a named handler no longer raises REACTOR_HOOKS_001/005, and the analyzer itself is unchanged. mur check wraps dotnet build, so it picks up the same behaviour.
  • Results: the native PickFileResult? / PickFolderResult? / IReadOnlyList<…>, all path-based. Cancellation returns null or an empty list.
  • Options:
    • Added Title, SuggestedFolder, SuggestedStartFolder, InitialFileTypeIndex, open-picker FileTypeChoices, and save-only DefaultFileExtension / ShowOverwritePrompt.
    • FileTypeChoices is now an ordered IReadOnlyList<FileTypeChoice>. The native map keeps insertion order, and that order sets the default entry. Reactor rejects null entries, blank or duplicate labels, and empty extension lists before the owner lookup.
    • Removed FolderPickerOptions.FileTypeFilter and SaveFilePickerOptions.SuggestedSaveFile, which have no native equivalent.
    • The old "*" fallback is dropped, because the native picker already treats an empty filter as All Files.
  • Owning window:
    • The owner used to be read from ReactorApp.ActiveHostInternal at call time. That field names the most recently rendered or constructed host, and becomes null after any host is disposed. So with windows A then B open, A's picker parented to B, and every picker threw once any window closed.
    • RenderContext now captures the owning window in BeginRender, where ReactorHost.Render scopes that field to itself. It clears the capture on detach and rejects a closed owner.

Linked issue / spec

Fixes #1374. Updates docs/specs/054-windowing-evolution.md §8.3 and its task file (new §7.3a).

Test plan

  • Unit tests in tests/Reactor.Tests/PickerTests.cs (renamed from PickerHookTests.cs): null options, missing-owner message for all five operations, and choice validation through the public API.
  • Analyzer regression tests in HookRulesAnalyzerTests: Pick*Async called from lambdas, a conditional lambda and named handlers, through both Component and RenderContext, report nothing. A Use*-named positive control in the same call shapes reports HOOKS_001 (lambda) and HOOKS_005 (named handler), so the clean result isn't vacuous.
  • Selftest Pickers_Suite (renamed from PickerHooks_Suite): routing, cancellation, collection pass-through, off-UI-thread rejection, error propagation, and read-back of every option on real Microsoft.Windows.Storage.Pickers objects, including non-alphabetical FileTypeChoices order on both the open and save pickers.
  • New selftest Pickers_OwningWindow: with A then B open, A's five helpers route to A before and after B closes, and B's helpers are rejected after B closes.
    • Guarded by oracle checks that the two owners differ and that ActiveHostInternal names B.
    • Mutation-checked: with GetPickerOwner reverted to the call-time lookup, all 10 routing checks fail.
  • Updated Phase7WindowingFixtures picker checks.
  • dotnet test tests/Reactor.Tests -c Release -p:Platform=x64 → passed: 14,971 total, 0 failed, 64 skipped. This includes the doc, API-index and search-index gates.
  • dotnet run --project tests/Reactor.AppTests.Host -- --self-test (full corpus) → passed: 8,864 checks, 0 failures.
  • dotnet restore Reactor.slnx + dotnet build Reactor.slnx --no-restore -c Release → no compile errors. The only failures were environmental:
    • NU1301 restore errors on six projects whose packages weren't reachable offline (VS extension projects, Reactor.AppTests, Reactor.PackagedTests, Reactor.SelfTests).
    • The NU1900 offline-audit cascade from the repo notes; I set NuGetAudit=false locally to get past it.
    • Reactor.Tests itself was built separately in Release (warnings as errors) with -p:Platform=x64.
  • dotnet test tests/Reactor.SelfTests and the packaged tier were not run locally because their packages couldn't be restored offline. CI covers both.
  • Docs: compiled only the windows and hooks topics, reverted unrelated screenshot churn, and deleted the four stale reference/hooks/Use*PickerAsync.md pages (the pipeline doesn't prune). mur --regen-api regenerated both byte-identical reactor.api.txt copies, and both windowing skills are updated.

Risk / breaking changes

  • Breaking (pre-1.0, accepted in Migrate picker helpers to Microsoft.Windows.Storage.Pickers #1374):
    • The Use*PickerAsync methods are renamed and return PickFileResult/PickFolderResult instead of StorageFile/StorageFolder.
    • The option enums move to Microsoft.Windows.Storage.Pickers. Both namespaces define PickerLocationId/PickerViewMode, so import only the new one.
    • SaveFilePickerOptions.FileTypeChoices changes from a dictionary to IReadOnlyList<FileTypeChoice>.
    • FolderPickerOptions.FileTypeFilter and SuggestedSaveFile are removed.
  • Hot path: BeginRender adds one volatile static read per component render.
  • Known limitation (pre-existing, not addressed): content rendered through ReactorHostControl doesn't scope ActiveHostInternal, so it can capture an unrelated window. Every window hook (UseWindow, UseDpi, …) shares this today. Documented in the windows guide and spec 054 §8.3; I suggest a follow-up issue.

Rebuild the picker helpers on Microsoft.Windows.Storage.Pickers (WindowId
constructor, no InitializeWithWindow) and return path-based PickFileResult /
PickFolderResult. Rename them as non-hooks (PickFileAsync,
PickMultipleFilesAsync, PickFolderAsync, PickSaveFileAsync) and add
PickMultipleFoldersAsync, so calling them from handlers no longer raises
REACTOR_HOOKS_001/005.

Options gain Title, SuggestedFolder, SuggestedStartFolder,
InitialFileTypeIndex, open-picker FileTypeChoices, DefaultFileExtension and
ShowOverwritePrompt. FileTypeChoices becomes an ordered
IReadOnlyList<FileTypeChoice>, validated before the owner lookup.
FolderPickerOptions.FileTypeFilter and SuggestedSaveFile are removed (no
native equivalent).

RenderContext now captures its owning window during render instead of
reading ReactorApp.ActiveHostInternal at call time, which parented A's
picker to window B and threw after any window closed.

Fixes #1374

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 890c9be9-295c-487f-a96e-93eaa64994b0
@github-actions

Copy link
Copy Markdown
Contributor

📦 Build metrics

Artifact sizes for 497f2ff vs the base branch (05ecd1c).

Packages (compressed .nupkg)

Artifact base PR Δ
Microsoft.UI.Reactor.nupkg 1.86 MB 1.86 MB +2.59 KB (+0.14%) ⚠️
Microsoft.UI.Reactor.Advanced.nupkg 493.3 KB 493.3 KB +3 B (0.00%) ≈
Microsoft.UI.Reactor.Devtools.nupkg 287.9 KB 287.9 KB -3 B (0.00%) ≈

Assemblies in Microsoft.UI.Reactor

Artifact base PR Δ
Reactor.Analyzers.dll 410.5 KB 410.5 KB +0 B (0.00%) ≈
Reactor.dll 2.62 MB 2.62 MB +2.50 KB (+0.09%) ⚠️
Reactor.Localization.Generator.dll 16.0 KB 16.0 KB +0 B (0.00%) ≈
Reactor.Wrappers.Abstractions.dll 10.5 KB 10.5 KB +0 B (0.00%) ≈
Reactor.Wrappers.Generator.dll 99.5 KB 99.5 KB +0 B (0.00%) ≈

Assemblies in Microsoft.UI.Reactor.Advanced

Artifact base PR Δ
Reactor.Advanced.dll 1.00 MB 1.00 MB +0 B (0.00%) ≈

Assemblies in Microsoft.UI.Reactor.Devtools

Artifact base PR Δ
Microsoft.UI.Reactor.Devtools.dll 787.0 KB 787.0 KB +0 B (0.00%) ≈

✅ smaller / ⚠️ larger / ≈ within noise. Sizes come from a Release dotnet pack on the CI runner: packages are the compressed .nupkg download size, assemblies the uncompressed DLL inside it.
workflow run.

public object? Options;
public string? Operation;
public int Calls;
public IReadOnlyList<PickFileResult> Files = new List<PickFileResult>();
public string? Operation;
public int Calls;
public IReadOnlyList<PickFileResult> Files = new List<PickFileResult>();
public IReadOnlyList<PickFolderResult> Folders = new List<PickFolderResult>();
stub.Error = error;
Exception? actual = null;
try { await component.Operations[i](); }
catch (Exception ex) { actual = ex; }
var calls = stub.Calls;
Exception? error = null;
try { await a.Operations[i](); }
catch (Exception ex) { error = ex; }
@github-actions

Copy link
Copy Markdown
Contributor

🧪 Merged coverage

Coverage for 497f2ff vs the base branch (05ecd1c) — unit + selftest merged.

Metric base PR Δ
Line 87.09% 87.09% 0.00 pp ≈
Branch 77.10% (973/1262) 77.10% (973/1262) 0.00 pp ≈

No coverage change beyond the noise floor. ✅

✅ higher / ⚠️ lower / ≈ within noise. Δ is in percentage points; coverage is unit + selftest merged (Debug x64) on the CI runner. Cobertura reports attached to the workflow run as artifacts.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate picker helpers to Microsoft.Windows.Storage.Pickers

1 participant