Investigate source-generated conditional element handler attributes
- Dominant language
- C#
- Stars
- 23.3k
- Forks
- 2k
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 290
Description
## Summary
PR #29952 moves built-in element handler resolution from the eager `AddControlsHandlers()` table to `[ElementHandler]` attributes on element types. That works well for simple one-element-to-one-handler mappings, but Android Material3 exposed a missing shape: some default handlers are conditional on runtime feature switches.
The PR currently handles this with small manual handler attributes, for example a `LabelHandlerAttribute` that can return `LabelHandler2` when `RuntimeFeature.IsMaterial3Enabled` is true, otherwise `LabelHandler`.
We should investigate whether this boilerplate should be generated.
## Why this matters
The old central registration table could express conditional defaults:
```csharp
if (RuntimeFeature.IsMaterial3Enabled)
handlers.AddHandler();
else
handlers.AddHandler();
```
With attribute-based resolution, a naive attribute list like this is attractive:
```csharp
[ElementHandler(typeof(LabelHandler))]
[ElementHandler(typeof(LabelHandler2), FeatureSwitch = "...", Priority = 100)]
```
However, hard-referencing all candidate handler types from attribute metadata may accidentally preserve disabled handlers and weaken the trimming benefit of the PR. We need a linker-friendly design where feature-switch branches remain visible as normal C# code that the linker can fold.
## Option 1: manual conditional handler attributes
This is the low-risk first iteration:
```csharp
[LabelHandler]
public partial class Label : View, ILabel
{
}
sealed class LabelHandlerAttribute : ElementHandlerAttribute
{
public LabelHandlerAttribute()
: base(typeof(LabelHandler))
{
}
public override Type GetHandlerType()
{
#if ANDROID
if (RuntimeFeature.IsMaterial3Enabled)
return typeof(LabelHandler2);
#endif
return base.GetHandlerType();
}
}
```
Pros:
- Simple.
- Reviewable.
- Runtime feature-switch logic is real C#.
- Keeps user DI overrides as the highest-priority resolution path.
- Avoids reintroducing a large central default handler table.
Cons:
- Boilerplate across every conditional handler.
- Easy for manually-authored attributes to drift in naming/style.
- Not ideal if more feature-conditional handlers are added.
## Option 2: source-generate the conditional handler attributes
Use repeatable declarative metadata and generate the small attribute class:
```csharp
[LabelHandler]
public partial class Label : View, ILabel
{
}
[ConditionalElementHandler(typeof(LabelHandler2), typeof(RuntimeFeature), nameof(RuntimeFeature.IsMaterial3Enabled), Platforms = ElementHandlerPlatforms.Android, Priority = 100)]
[ConditionalElementHandler(typeof(LabelHandler), Priority = 0)]
internal sealed partial class LabelHandlerAttribute
{
}
```
Generated output:
```csharp
internal sealed partial class LabelHandlerAttribute : ElementHandlerAttribute
{
public LabelHandlerAttribute()
: base(typeof(LabelHandler))
{
}
public override Type GetHandlerType()
{
#if ANDROID
if (global::Microsoft.Maui.RuntimeFeature.IsMaterial3Enabled)
return typeof(global::Microsoft.Maui.Handlers.LabelHandler2);
#endif
return typeof(global::Microsoft.Maui.Handlers.LabelHandler);
}
}
```
The generator should also be smart enough to add `: ElementHandlerAttribute` when the partial attribute declaration does not specify a base type.
Pros:
- Preserves the compact per-control attribute model.
- Keeps feature-switch logic in generated C# for linker analysis.
- Reduces boilerplate.
- Allows consistent priority/platform/feature semantics.
Cons:
- More implementation complexity.
- Needs source-generator tests.
- Needs careful validation that disabled feature handlers are not rooted in trimmed builds.
## HybridWebView follow-up
`HybridWebViewHandler` is currently still conditionally registered through DI because it uses dynamic `System.Text.Json` features and triggers trimming warnings if referenced unconditionally.
We should investigate whether the same conditional attribute model can support `HybridWebViewHandler` safely:
- `MauiHybridWebViewSupported=false` should not root `HybridWebViewHandler`.
- NativeAOT / `TrimMode=Full` should not warn.
- The generated branch should be linker-foldable through `RuntimeFeature.IsHybridWebViewSupported`.
## Acceptance criteria
- Decide whether manual conditional attributes are sufficient or source generation is worthwhile.
- If source-generated, define the metadata API and generated shape.
- Add tests for priority, platform guards, feature-switch branches, and fallback handler selection.
- Validate trimmed output for `UseMaterial3=false` and `MauiHybridWebViewSupported=false`.
Contributor guide
Research direction
Start by tracing PR #29952 and the existing AddControlsHandlers table, then inspect the manual LabelHandlerAttribute pattern and the current HybridWebViewHandler DI registration. Define whether ConditionalElementHandler metadata and generated partial attributes can preserve platform and feature-switch branches without rooting disabled handlers. Add source-generator tests for priority, platform guards, feature-switch branches, and fallback selection, then validate trimmed builds for the two disabled-feature configurations.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, csharp
- Domain
- mobile-dev, tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100