Skip to content

feat: ALC-scoped AdaptiveTrigger window-size override - #23902

Draft
DerekRaven wants to merge 7 commits into
masterfrom
dev/de/23721-alc-scoped-adaptive-trigger-override
Draft

feat: ALC-scoped AdaptiveTrigger window-size override#23902
DerekRaven wants to merge 7 commits into
masterfrom
dev/de/23721-alc-scoped-adaptive-trigger-override

Conversation

@DerekRaven

Copy link
Copy Markdown
Contributor

GitHub Issue: closes #23721

PR Type:

✨ Feature

What changed? 🚀

AdaptiveTrigger.SetWindowSizeOverride(Size?) (added in #23445 for #23444) is a process-global static override: because a host and its hosted guest apps share the same Uno.UI instance from the default AssemblyLoadContext, simulating a form factor for a guest also re-evaluated every AdaptiveTrigger in the host's own UI, collapsing/expanding the host chrome based on the guest's simulated size.

This PR adds an ALC-scoped overload, SetWindowSizeOverride(Size?, AssemblyLoadContext), so a host can apply a simulated size to the triggers of a guest app loaded in a (collectible) ALC without affecting its own UI:

  • Owner resolution: each trigger walks its ancestry (VisualState → VisualStateGroup → owner element → ancestors) to the nearest element typed in a non-default ALC, lazily and cached per attach; nested contexts resolve to the innermost one. The cache is reset on every attach/detach so re-parenting re-resolves.
  • Precedence: scoped override → global override → window (XamlRoot) bounds. The existing global overload's behavior is unchanged.
  • No rooting of the guest ALC: storage is a weakly keyed ConditionalWeakTable, and trigger-side caching uses shared WeakReferences. The scope self-clears via the ALC's Unloading event (with an Application ALC-cleanup backstop), re-evaluating any still-live guest triggers so they don't stay frozen at the simulated size. Overrides for an ALC whose unload has already been initiated are ignored.
  • Fast path preserved: while no scoped override exists anywhere in the process, trigger evaluation pays a single volatile bool read — no ancestor walk, no table lookup.
  • Docs: doc/articles/features/AdaptiveTrigger.md documents both overloads, the precedence chain, and the attribution limits (guest content composed solely of shared framework types — e.g. popup/flyout subtrees re-parented under the host's popup root — falls back to the global override).

Validation: unit tests Given_AdaptiveTrigger 32/32 passed (guest-only application, scoped-over-global precedence, revert-on-clear chains, pre-attach and pre-parenting overrides, two concurrent scoped ALCs, nested-ALC innermost-wins, argument validation, foreign-scope isolation, no-rooting, self-clear on unload, unloading-ALC rejection); runtime tests Given_AlcContentHost 39/39 passed on Skia desktop (X11), including two new hosted-topology tests where guest triggers live inside the host's visual tree sharing its XamlRoot. An eight-lens review panel ran on the branch; all actionable findings are addressed in the last commit. Non-Skia heads are compile-verified by CI only (the new code uses System.Runtime.Loader and NativeDispatcher, both available on all heads).

PR Checklist ✅

🤖 Generated with Claude Code

DerekRaven and others added 7 commits July 28, 2026 15:49
A host that loads a guest app into a collectible AssemblyLoadContext can
now scope the simulated window size to that context's triggers only, via
a new SetWindowSizeOverride(Size?, AssemblyLoadContext) overload, without
affecting the host UI. Triggers resolve their owner ALC by walking to the
nearest ancestor typed in a non-default ALC (lazily, cached per attach).
Storage is weakly keyed and self-clears on unload, so a scoped override
never roots the collectible context. Precedence: scoped > global > window.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Guest elements are staged by loading a second copy of the view library
into a collectible AssemblyLoadContext, mirroring a hosted guest app's
page types. Covers guest-only application, scoped-over-global precedence,
revert-on-clear chains, pre-attach overrides, argument validation,
foreign-scope isolation, no-rooting of the target ALC, and scope
self-clearing on unload.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Runtime coverage in the real AlcContentHost shape, where guest triggers
live inside the host's visual tree and share its XamlRoot: a scoped
override activates only the guest's adaptive state (probe added to the
AlcApp main page), host-owned triggers stay untouched, and the scoped
value wins over — and falls back to — the global override.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Re-resolve the owner ALC on every attach so a subtree built before being
parented under a guest-typed ancestor doesn't keep a stale no-ALC cache.
Ignore new overrides for an ALC whose unload has already been initiated
(its Unloading event can never fire to self-clean), make the scoped
holder immutable for the lock-free read path, share one WeakReference
per ALC across triggers, and contain + re-evaluate from the Unloading
handler, which can run on the finalizer thread.

Covered by new unit tests: pre-parenting resolution, two concurrent
scoped ALCs, and unloading-ALC override rejection.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contain the trigger re-evaluation raised from ALC-unload paths (it can
run enqueued on the dispatcher or on the finalizer thread, where an
escaping subscriber exception had no handler), and make the application
ALC-cleanup backstop log its removals and re-evaluate live triggers like
the Unloading path already did. Consolidate the owner-cache reset into
ResetOwnerAlcCache, document the writers-only lock and the benign
fast-path latch race, and link the scoped unit tests to the issue with
GitHubWorkItem. New When_NestedScopedAlcs_InnermostWins test pins the
documented innermost-context resolution for nested guest ALCs.

Validated: Given_AdaptiveTrigger unit tests 32/32, Given_AlcContentHost
runtime tests 39/39 (Skia desktop).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions github-actions Bot added area/automation Categorizes an issue or PR as relevant to project automation kind/documentation labels Jul 29, 2026
@unodevops

Copy link
Copy Markdown
Contributor

🤖 Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-23902/wasm-skia-net9/index.html


for (var i = 0; i < 10 && weakAlc.IsAlive; i++)
{
GC.Collect();
Comment on lines +235 to +241
catch (Exception ex)
{
if (typeof(AdaptiveTrigger).Log().IsEnabled(LogLevel.Warning))
{
typeof(AdaptiveTrigger).Log().Warn($"Failure while handling the unload of ALC '{alc.Name}'", ex);
}
}
Comment on lines +254 to +260
catch (Exception ex)
{
if (typeof(AdaptiveTrigger).Log().IsEnabled(LogLevel.Warning))
{
typeof(AdaptiveTrigger).Log().Warn("Failed to re-evaluate adaptive triggers after a scoped window-size override was dropped", ex);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/automation Categorizes an issue or PR as relevant to project automation kind/documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AdaptiveTrigger window-size override should be scopable per AssemblyLoadContext

2 participants