Summary
On a Skia-rendered Android/iOS/Catalyst head (the Uno.Sdk 6.0 template default), Uno.Sdk substitutes the plain net9.0 build of every Uno.Extensions.*.WinUI assembly for its net9.0-android / net9.0-ios build. Any OS-specific behavior we put behind #if __ANDROID__ / #if __IOS__ / XAMARIN (or TFM-conditioned csproj items) is silently absent in that configuration. Nothing throws; behavior degrades.
The MSAL case (#3139, spec specs/010-msal-skia-mobile-runtime-dispatch) was the first instance found and is fixed on main (#3161, #3163). This issue tracks the audit of the remaining 18 assemblies.
Mechanism
RuntimeAssetsSelectorTask.HandleSkiaMobileForNonRuntimeEnabledPackages (uno repo, src/SourceGenerators/Uno.UI.Tasks/RuntimeAssetsSelector/RuntimeAssetsSelectorTask.cs) replaces an assembly when all of these hold:
- it resolved from the NuGet cache under
lib/netX.0-{android,ios,maccatalyst,tvos}* — project references are never touched;
- a plain
lib/netX.0 sibling exists in the package;
- the assembly's metadata references
Uno.UI (Mono.Cecil check). Assemblies that only reach the WinRT layer (Uno.dll) are skipped.
Every Uno.Extensions.*.WinUI package meets all three (verified with System.Reflection.Metadata over the built Release/net9.0-android DLLs and the packed nupkg layout). There is no opt-out. The swap is correct on Uno's side — on a Skia head the loaded Uno.UI is the Skia flavor; the defect is ours: OS concerns gated by TFM symbols instead of runtime checks.
What stays platform-real at runtime and is therefore usable from the plain build: the WinRT layer (ApplicationData, WebAuthenticationBroker, …), Microsoft.Identity.Client, the BCL (OperatingSystem.Is*(), HttpClientHandler's native wrapping), and any package that does not reference Uno.UI.
Matrix
| Assembly (package) |
Android/iOS build has, plain build lacks |
What a Skia mobile app gets |
Severity |
Adjustment |
Uno.Extensions.Storage.UI (Storage.WinUI) |
KeyStoreKeyValueStorage / KeyChainKeyValueStorage (whole-file #if), their registration and the SetDefaultInstance pick (ServiceCollectionExtensions.cs:119-167). Type dump of the plain build: only ApplicationData + SessionStorage stores. |
Default IKeyValueStorage = ApplicationDataKeyValueStorage (plain-text LocalSettings). TokenCache resolves the default, so OIDC / Web / Custom auth tokens are stored unencrypted. By-name resolution of the secure stores fails. MSAL unaffected (SetupStorageCore already runtime-dispatches to native MSAL persistence). |
High |
Stop referencing Uno.UI: the assembly only uses Windows.Storage.* (Uno.dll) + Uno.Foundation. Reference Uno.WinRT instead (DisableImplicitUnoPackages, PackageReference Uno.WinRT on non-Windows TFMs), replace the single Uno.UI.Toolkit.StorageFileHelper.ExistsInPackage call, drop the Core.WinUI project reference (Settings / ApplicationDataExtensions). Then the swap does not apply and the android/ios builds run as-is. Guard with a test asserting no Uno.UI assembly reference. |
Uno.Extensions.Logging.Serilog |
AndroidLog / NSLog sinks (TFM-conditioned Serilog.Sinks.Xamarin) and the whole AddFileLogging body (#if __ANDROID__ || __IOS__ || NETSTANDARD, HostBuilderExtensions.cs:98-125). |
Console + Debug sinks (stdout does reach logcat / device log). File logging silently not configured. |
Medium |
Serilog.Sinks.File is cross-platform: make AddFileLogging unconditional, set the Platform enricher from OperatingSystem. |
Uno.Extensions.Authentication.UI (Authentication.WinUI) |
WebAuthenticationProvider.cs:82-84: PrefersEphemeralWebBrowserSession applied under #if __IOS__. |
Sign-in works (WinRT broker is native); ephemeral-session setting ignored on Skia iOS. |
Medium |
Already fixed by runtime dispatch in 230d55726 (dev/sb/auth-providers-onmain); land on main. |
Uno.Extensions.Http.UI (Http.WinUI) |
Native handler registration (NSUrlSessionHandler / AndroidMessageHandler), native cookie branches in CookieManager. |
Equivalent transport: with the default UseNativeHttpHandler=true the BCL HttpClientHandler wraps the native handler; cookies go through the Handler is HttpClientHandler branch. Lost: handler-type-specific tuning (TrustedCerts). |
Low |
None for Skia. Incidental: CookieManager.cs:55 tests __Android__ (wrong case) — the native cookie branch is dead on native Android too. |
Uno.Extensions.Logging (Logging.WinUI) |
iOS/Catalyst: OSLogLoggerProvider (TFM-conditioned package). Android build already Console + Debug. |
Skia iOS: Console + Debug instead of unified logging. Skia Android: identical. |
Low |
Optional. |
Uno.Extensions.Reactive.UI (Reactive.WinUI) |
CollectionFacet.cs:240 #if XAMARIN raises multi-item collection changes as one event. |
Item-per-item path — the code Desktop and WebAssembly already run. Perf-only. |
Low |
None. |
Uno.Extensions.Navigation.UI (Navigation.WinUI) |
Android-only Task.Yield() after EnsureLoaded (FrameworkElementExtensions.cs:197); iOS-only ShowPickerAsync targeting Uno's native Picker (NavigatorExtensions.cs:32). |
Both are native-renderer concerns; dropping them under Skia is correct. |
None |
None. Canonical "renderer gate, not OS gate" example. |
Uno.Extensions.Navigation.Toolkit (Navigation.Toolkit.WinUI) |
ModalFlyout.xaml:99 mobile: template variant (iOS fixed size). |
Default FrameTemplate, as on Desktop/WASM/Windows. |
None |
None. |
Uno.Extensions.Authentication.MSAL.WinUI |
Was a 2-byte no-op stub in the plain build. |
Functional provider, native MSAL persistence, runtime-selected redirect. Only maccatalyst remains a stub (native Catalyst — separate gap). |
Fixed |
#3161, #3163. |
Uno.Extensions.Authentication.Oidc.WinUI |
Only #if WINDOWS gates. |
Identical; inherits the Storage row for token storage. |
None |
Fixed by Storage. |
| Core.UI · Hosting.UI · Localization.UI · Toolkit.UI · Navigation.UI.Markup · Reactive.UI.Markup |
Gates are WINUI / WINDOWS / HAS_UNO_WINUI only; no TFM-conditioned items; no platform-suffixed files. |
Identical. |
None |
None. |
Uno.Extensions.Maui.WinUI (+ Markup, Runtime.Skia) |
Platform-specific by nature. |
Out of scope — MAUI embedding is a native-renderer feature. |
N/A |
None. |
Mac Catalyst under Skia follows the same substitution (-maccatalyst is in the task's list); the Storage and Serilog rows apply there too.
Why CI has not caught any of this
Condition 1: only NuGet-cache assemblies are substituted. Every head in this repo (Uno.Extensions.RuntimeTests, testing/TestHarness, samples/Playground) consumes the libraries by ProjectReference, so the Android/iOS runtime-test lanes exercise the android/ios builds that no packaged Skia app loads. In addition, none of those heads has SkiaRenderer in UnoFeatures, so they run the native renderer — not the template default. Commit 230d55726 records the same limitation.
Enabling SkiaRenderer on a ProjectReference head would produce a false green (it still loads the android build). Catching this class needs a head with SkiaRenderer and a PackageReference to the packed nupkgs.
Proposed order of work
- Storage — reference
Uno.WinRT instead of Uno.WinUI (see matrix); red/fix/green with a metadata test asserting no Uno.UI reference; spec + doc/Learn/Storage.
- Land the Web ephemeral-session fix (
230d55726) on main.
- Serilog: unconditional file sink, runtime
Platform enricher.
- Packaged Skia-mobile probe lane — build-only: pack to
artifacts/, a tiny net9.0-android/net9.0-ios head with UnoFeatures: SkiaRenderer and PackageReferences to the local feed, AfterBuild target asserting which Uno.Extensions.*.dll paths land in @(ReferenceCopyLocalPaths) (or grep the binlog for Replacing uno.extensions.). No emulator needed for the selection decision.
- Add
SkiaRenderer to the RuntimeTests head so the existing Android/iOS lanes test the default renderer.
- Repo rule in
AGENTS.md + specs/lessons.md: inside a *.WinUI project, __ANDROID__ / __IOS__ / __MACCATALYST__ / XAMARIN may guard native-renderer concerns only; OS concerns go through OperatingSystem.Is*() at runtime or into an assembly that does not reference Uno.UI.
- Housekeeping:
__Android__ typo in CookieManager.cs:55; decide whether the OSLog provider and the Reactive XAMARIN branch are worth keeping.
How each claim was verified
- Swap criteria read from the Uno repo source (
RuntimeAssetsSelectorTask.cs:447-556), not from memory.
- All 19
Release/net9.0-android DLLs under src/ reference Uno.UI (System.Reflection.Metadata); packed layout confirmed on uno.extensions.storage.winui 7.4.0-dev.15.
- Per-project tally of every
#if / #elif symbol, every TFM-conditioned csproj item, search for platform-suffixed files (none outside RuntimeTests). Symbols defined in src/Directory.Build.props:88-121.
- Storage type dump per TFM; Storage's Uno type references:
Uno (Windows.Storage.*), Uno.Foundation, Uno.UI (generator output only: GlobalStaticResources, XamlParseContext, FeatureConfiguration), Uno.UI.Toolkit (one StorageFileHelper.ExistsInPackage call).
- Skia default renderer: Uno docs ("default rendering engine … as of Uno.Sdk 6.0 … across all targets except WinAppSDK"); enabled via
UnoFeatures SkiaRenderer → Uno.WinUI.Runtime.Skia.* props set UnoUIRuntimeIdentifier=Skia.
UseNativeHttpHandler default true on Android/iOS (Microsoft Learn).
Related: #3139, #3161, #3163, specs/010-msal-skia-mobile-runtime-dispatch/spec.md, specs/012-msal-auth-fixes/progress.md.
Summary
On a Skia-rendered Android/iOS/Catalyst head (the Uno.Sdk 6.0 template default),
Uno.Sdksubstitutes the plainnet9.0build of everyUno.Extensions.*.WinUIassembly for itsnet9.0-android/net9.0-iosbuild. Any OS-specific behavior we put behind#if __ANDROID__/#if __IOS__/XAMARIN(or TFM-conditioned csproj items) is silently absent in that configuration. Nothing throws; behavior degrades.The MSAL case (#3139, spec
specs/010-msal-skia-mobile-runtime-dispatch) was the first instance found and is fixed onmain(#3161, #3163). This issue tracks the audit of the remaining 18 assemblies.Mechanism
RuntimeAssetsSelectorTask.HandleSkiaMobileForNonRuntimeEnabledPackages(uno repo,src/SourceGenerators/Uno.UI.Tasks/RuntimeAssetsSelector/RuntimeAssetsSelectorTask.cs) replaces an assembly when all of these hold:lib/netX.0-{android,ios,maccatalyst,tvos}*— project references are never touched;lib/netX.0sibling exists in the package;Uno.UI(Mono.Cecil check). Assemblies that only reach the WinRT layer (Uno.dll) are skipped.Every
Uno.Extensions.*.WinUIpackage meets all three (verified withSystem.Reflection.Metadataover the builtRelease/net9.0-androidDLLs and the packed nupkg layout). There is no opt-out. The swap is correct on Uno's side — on a Skia head the loadedUno.UIis the Skia flavor; the defect is ours: OS concerns gated by TFM symbols instead of runtime checks.What stays platform-real at runtime and is therefore usable from the plain build: the WinRT layer (
ApplicationData,WebAuthenticationBroker, …),Microsoft.Identity.Client, the BCL (OperatingSystem.Is*(),HttpClientHandler's native wrapping), and any package that does not referenceUno.UI.Matrix
Uno.Extensions.Storage.UI(Storage.WinUI)KeyStoreKeyValueStorage/KeyChainKeyValueStorage(whole-file#if), their registration and theSetDefaultInstancepick (ServiceCollectionExtensions.cs:119-167). Type dump of the plain build: onlyApplicationData+SessionStoragestores.IKeyValueStorage=ApplicationDataKeyValueStorage(plain-textLocalSettings).TokenCacheresolves the default, so OIDC / Web / Custom auth tokens are stored unencrypted. By-name resolution of the secure stores fails. MSAL unaffected (SetupStorageCorealready runtime-dispatches to native MSAL persistence).Uno.UI: the assembly only usesWindows.Storage.*(Uno.dll) +Uno.Foundation. ReferenceUno.WinRTinstead (DisableImplicitUnoPackages,PackageReference Uno.WinRTon non-Windows TFMs), replace the singleUno.UI.Toolkit.StorageFileHelper.ExistsInPackagecall, drop theCore.WinUIproject reference (Settings/ApplicationDataExtensions). Then the swap does not apply and the android/ios builds run as-is. Guard with a test asserting noUno.UIassembly reference.Uno.Extensions.Logging.SerilogAndroidLog/NSLogsinks (TFM-conditionedSerilog.Sinks.Xamarin) and the wholeAddFileLoggingbody (#if __ANDROID__ || __IOS__ || NETSTANDARD,HostBuilderExtensions.cs:98-125).Serilog.Sinks.Fileis cross-platform: makeAddFileLoggingunconditional, set thePlatformenricher fromOperatingSystem.Uno.Extensions.Authentication.UI(Authentication.WinUI)WebAuthenticationProvider.cs:82-84:PrefersEphemeralWebBrowserSessionapplied under#if __IOS__.230d55726(dev/sb/auth-providers-onmain); land onmain.Uno.Extensions.Http.UI(Http.WinUI)NSUrlSessionHandler/AndroidMessageHandler), native cookie branches inCookieManager.UseNativeHttpHandler=truethe BCLHttpClientHandlerwraps the native handler; cookies go through theHandler is HttpClientHandlerbranch. Lost: handler-type-specific tuning (TrustedCerts).CookieManager.cs:55tests__Android__(wrong case) — the native cookie branch is dead on native Android too.Uno.Extensions.Logging(Logging.WinUI)OSLogLoggerProvider(TFM-conditioned package). Android build already Console + Debug.Uno.Extensions.Reactive.UI(Reactive.WinUI)CollectionFacet.cs:240#if XAMARINraises multi-item collection changes as one event.Uno.Extensions.Navigation.UI(Navigation.WinUI)Task.Yield()afterEnsureLoaded(FrameworkElementExtensions.cs:197); iOS-onlyShowPickerAsynctargeting Uno's nativePicker(NavigatorExtensions.cs:32).Uno.Extensions.Navigation.Toolkit(Navigation.Toolkit.WinUI)ModalFlyout.xaml:99mobile:template variant (iOS fixed size).FrameTemplate, as on Desktop/WASM/Windows.Uno.Extensions.Authentication.MSAL.WinUImaccatalystremains a stub (native Catalyst — separate gap).Uno.Extensions.Authentication.Oidc.WinUI#if WINDOWSgates.WINUI/WINDOWS/HAS_UNO_WINUIonly; no TFM-conditioned items; no platform-suffixed files.Uno.Extensions.Maui.WinUI(+ Markup, Runtime.Skia)Mac Catalyst under Skia follows the same substitution (
-maccatalystis in the task's list); the Storage and Serilog rows apply there too.Why CI has not caught any of this
Condition 1: only NuGet-cache assemblies are substituted. Every head in this repo (
Uno.Extensions.RuntimeTests,testing/TestHarness,samples/Playground) consumes the libraries byProjectReference, so the Android/iOS runtime-test lanes exercise the android/ios builds that no packaged Skia app loads. In addition, none of those heads hasSkiaRendererinUnoFeatures, so they run the native renderer — not the template default. Commit230d55726records the same limitation.Enabling
SkiaRendereron aProjectReferencehead would produce a false green (it still loads the android build). Catching this class needs a head withSkiaRendererand aPackageReferenceto the packed nupkgs.Proposed order of work
Uno.WinRTinstead ofUno.WinUI(see matrix); red/fix/green with a metadata test asserting noUno.UIreference; spec +doc/Learn/Storage.230d55726) onmain.Platformenricher.artifacts/, a tinynet9.0-android/net9.0-ioshead withUnoFeatures: SkiaRendererandPackageReferences to the local feed,AfterBuildtarget asserting whichUno.Extensions.*.dllpaths land in@(ReferenceCopyLocalPaths)(or grep the binlog forReplacing uno.extensions.). No emulator needed for the selection decision.SkiaRendererto the RuntimeTests head so the existing Android/iOS lanes test the default renderer.AGENTS.md+specs/lessons.md: inside a*.WinUIproject,__ANDROID__/__IOS__/__MACCATALYST__/XAMARINmay guard native-renderer concerns only; OS concerns go throughOperatingSystem.Is*()at runtime or into an assembly that does not referenceUno.UI.__Android__typo inCookieManager.cs:55; decide whether the OSLog provider and the ReactiveXAMARINbranch are worth keeping.How each claim was verified
RuntimeAssetsSelectorTask.cs:447-556), not from memory.Release/net9.0-androidDLLs undersrc/referenceUno.UI(System.Reflection.Metadata); packed layout confirmed onuno.extensions.storage.winui 7.4.0-dev.15.#if/#elifsymbol, every TFM-conditioned csproj item, search for platform-suffixed files (none outside RuntimeTests). Symbols defined insrc/Directory.Build.props:88-121.Uno(Windows.Storage.*),Uno.Foundation,Uno.UI(generator output only:GlobalStaticResources,XamlParseContext,FeatureConfiguration),Uno.UI.Toolkit(oneStorageFileHelper.ExistsInPackagecall).UnoFeaturesSkiaRenderer→Uno.WinUI.Runtime.Skia.*props setUnoUIRuntimeIdentifier=Skia.UseNativeHttpHandlerdefault true on Android/iOS (Microsoft Learn).Related: #3139, #3161, #3163,
specs/010-msal-skia-mobile-runtime-dispatch/spec.md,specs/012-msal-auth-fixes/progress.md.