Skip to content

Skia-mobile heads load the plain net9.0 build of every *.WinUI package: audit of OS-specific code lost per assembly #3171

Description

@kazo0

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:

  1. it resolved from the NuGet cache under lib/netX.0-{android,ios,maccatalyst,tvos}*project references are never touched;
  2. a plain lib/netX.0 sibling exists in the package;
  3. 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

  1. 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.
  2. Land the Web ephemeral-session fix (230d55726) on main.
  3. Serilog: unconditional file sink, runtime Platform enricher.
  4. 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.
  5. Add SkiaRenderer to the RuntimeTests head so the existing Android/iOS lanes test the default renderer.
  6. 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.
  7. 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 SkiaRendererUno.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.

Activity

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

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions