Skip to content

[net11.0][XSG] Trimmable EventTrigger#33611

Open
simonrozsival wants to merge 5 commits intonet11.0from
fix/33591-eventtrigger-aot
Open

[net11.0][XSG] Trimmable EventTrigger#33611
simonrozsival wants to merge 5 commits intonet11.0from
fix/33591-eventtrigger-aot

Conversation

@simonrozsival
Copy link
Copy Markdown
Member

@simonrozsival simonrozsival commented Jan 19, 2026

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

Description

This PR makes EventTrigger AOT-safe and trimming-safe by introducing factory methods that use compile-time event subscription instead of reflection.

Fixes #33591

Changes

New Factory Methods

Added two new EventTrigger.Create() factory methods that allow AOT-safe event subscription:

// For events using EventHandler
EventTrigger.Create<Button>("Clicked",
    static (b, h) => b.Clicked += h,
    static (b, h) => b.Clicked -= h);

// For events using EventHandler<TEventArgs>
EventTrigger.Create<Entry, TextChangedEventArgs>("TextChanged",
    static (e, h) => e.TextChanged += h,
    static (e, h) => e.TextChanged -= h);

Source Generator Integration

The XAML source generator now emits EventTrigger.Create<T>() calls instead of new EventTrigger() when the target type can be determined at compile time:

Before (reflection-based, not AOT-safe):

var eventTrigger = new EventTrigger();
eventTrigger.Event = "Clicked";

After (AOT-safe):

var eventTrigger = EventTrigger.Create<Button>("Clicked",
    static (button, handler) => button.Clicked += handler,
    static (button, handler) => button.Clicked -= handler);

Architecture

  • Introduced strategy pattern with IEventSubscriptionStrategy interface
  • ReflectionStrategy - Used by parameterless constructor (marked with [RequiresUnreferencedCode])
  • StaticStrategy<TBindable> and StaticStrategy<TBindable, TEventArgs> - Used by factory methods (fully trimming-safe)
  • Existing EventTrigger class remains sealed and backwards compatible

Backwards Compatibility

  • Existing code using new EventTrigger() continues to work (with trimming warnings)
  • The Event property is still set for all EventTrigger instances
  • Runtime XAML inflation falls back to reflection-based EventTrigger

Known Limitations

The AOT-safe code generation only works when the source generator can determine the target type at compile time. This works for the standard usage pattern:

<Button>
    <Button.Triggers>
        <EventTrigger Event="Clicked">
            <local:MyTriggerAction />
        </EventTrigger>
    </Button.Triggers>
</Button>

Edge cases that fall back to reflection-based EventTrigger:

  • EventTrigger defined inside Style.Triggers (the parent element is Style, not the actual target type)
  • EventTrigger defined in a ResourceDictionary and applied dynamically
  • Other unusual XAML structures where the parent element chain does not lead to the target BindableObject

These edge cases will continue to work at runtime but will not be AOT-safe. The source generator emits a warning (MAUIX2015) when it cannot determine the target type and falls back to reflection.

Testing

  • ✅ 122 SourceGen unit tests pass
  • ✅ 1,791 XAML unit tests pass
  • ✅ 5,427 Controls Core unit tests pass
  • ✅ 283 Essentials unit tests pass
  • Added snapshot test to verify generated code output
  • Added unit tests for new factory methods
  • Added test verifying EventTrigger fires when event occurs

Copilot AI review requested due to automatic review settings January 19, 2026 23:14
@simonrozsival simonrozsival marked this pull request as draft January 19, 2026 23:14
@simonrozsival simonrozsival changed the title Make EventTrigger AOT-safe and trimming-safe [net11.0][XSG] Make EventTrigger AOT-safe and trimming-safe Jan 19, 2026
@simonrozsival simonrozsival added perf/app-size Application Size / Trimming (sub: perf) xsg Xaml sourceGen labels Jan 19, 2026
@simonrozsival simonrozsival force-pushed the fix/33591-eventtrigger-aot branch from 7bf12d8 to 3b691d2 Compare January 19, 2026 23:23
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces AOT-safe and trimming-safe alternatives for EventTrigger by adding factory methods that use compile-time event subscription instead of reflection. The implementation uses a strategy pattern to support both the legacy reflection-based approach (for backward compatibility) and new AOT-safe static strategies.

Changes:

  • Added two EventTrigger.Create<T>() factory methods for AOT-safe event subscription (one for EventHandler and one for EventHandler<TEventArgs>)
  • Integrated source generator support to automatically emit AOT-safe code when EventTriggers are used in XAML
  • Introduced strategy pattern with IEventSubscriptionStrategy interface to support both reflection-based and static event subscription
  • Marked the parameterless constructor with [RequiresUnreferencedCode] and [RequiresDynamicCode] attributes
  • Updated all platform-specific PublicAPI.Unshipped.txt files

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/Controls/src/Core/Interactivity/EventTrigger.cs Core implementation: added factory methods, strategy pattern, and refactored event subscription logic
src/Controls/src/SourceGen/EventTriggerValueProvider.cs New source generator value provider for emitting AOT-safe EventTrigger.Create calls
src/Controls/src/SourceGen/Visitors/CreateValuesVisitor.cs Integration point for EventTriggerValueProvider in the visitor pattern
src/Controls/src/SourceGen/NodeSGExtensions.cs Registered EventTriggerValueProvider as a known markup value provider
src/Controls/tests/Xaml.UnitTests/Issues/Maui33591.xaml XAML test file with EventTriggers on Button, Entry, and Switch
src/Controls/tests/Xaml.UnitTests/Issues/Maui33591.xaml.cs Tests for XAML inflation, control creation, and EventTrigger property validation
src/Controls/tests/SourceGen.UnitTests/Maui33591SourceGenTests.cs Snapshot test verifying source generator produces correct AOT-safe code
src/Controls/tests/Core.UnitTests/Triggers/EventTriggerBaseTests.cs Unit tests for factory methods, event binding, and lifecycle management
src/Controls/src/Core/PublicAPI/*/PublicAPI.Unshipped.txt Public API surface additions for all platforms (7 files)

Comment on lines +63 to +66
var eventSymbols = targetType.GetAllEvents(eventName, context).ToList();
if (eventSymbols.Count > 0)
{
var eventSymbol = eventSymbols.First();
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The event lookup logic only checks if eventSymbols.Count is greater than 0 but always uses the first event found. If multiple events with the same name exist (e.g., through inheritance or explicit interface implementation), this could select the wrong event. Consider adding validation or documentation about this behavior, or use a more specific selection strategy.

Copilot uses AI. Check for mistakes.
Comment on lines +112 to +130
private static ITypeSymbol? FindTargetType(ElementNode eventTriggerNode, SourceGenContext context)
{
INode? current = eventTriggerNode;

while (current != null)
{
current = current.Parent;

// Skip ListNodes (they're the collection wrapper)
if (current is ListNode listNode)
current = listNode.Parent;

// Found an ElementNode - this should be our target
if (current is ElementNode parentElement && parentElement != eventTriggerNode)
return parentElement.XmlType.GetTypeSymbol(context);
}

return null;
}
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The FindTargetType method could return null if the EventTrigger is not properly nested in the XAML tree. When this happens, the fallback code at line 96-105 is used, but there's no logging or diagnostic information to help developers understand why the AOT-safe path wasn't taken. Consider adding a diagnostic message when targetType is null to aid in debugging XAML structure issues.

Copilot uses AI. Check for mistakes.
@simonrozsival simonrozsival changed the title [net11.0][XSG] Make EventTrigger AOT-safe and trimming-safe [net11.0][XSG] Trimmable EventTrigger Jan 20, 2026
@simonrozsival simonrozsival force-pushed the fix/33591-eventtrigger-aot branch from 77bc8e8 to ec764d4 Compare January 20, 2026 13:32
@simonrozsival simonrozsival marked this pull request as ready for review January 20, 2026 13:35
@rmarinho
Copy link
Copy Markdown
Member

/rebase

@simonrozsival
Copy link
Copy Markdown
Member Author

@copilot this PR needs a proper rebase with conflict resolution. open a PR which addresses this issue. make sure it is up to date with the base net11.0 branch.

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 12, 2026

@simonrozsival I've opened a new pull request, #34012, to work on those changes. Once the pull request is ready, I'll request review from you.

@simonrozsival simonrozsival force-pushed the fix/33591-eventtrigger-aot branch from f07cb44 to 6afe2be Compare February 13, 2026 08:48
simonrozsival added a commit that referenced this pull request Feb 18, 2026
…hanges

These changes belong in separate PRs:
- #33611 (CSS StyleSheet trimming)
- #33561 (EventTrigger trimming)
- #33160 (HybridWebView trimming)

Reverted files to their main branch state, keeping only the
[ElementHandler] attribute addition on HybridWebView.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 27, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 33611

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 33611"

@StephaneDelcroix StephaneDelcroix force-pushed the fix/33591-eventtrigger-aot branch 2 times, most recently from 0ed7c65 to bbea0c2 Compare February 27, 2026 20:24
@simonrozsival simonrozsival force-pushed the fix/33591-eventtrigger-aot branch from bbea0c2 to dfd63be Compare March 11, 2026 09:56
@simonrozsival
Copy link
Copy Markdown
Member Author

/azp run maui-pr-uitests,maui-pr-devicetests

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 2 pipeline(s).

@simonrozsival
Copy link
Copy Markdown
Member Author

@StephaneDelcroix @jfversluis could you please have a look at this PR and let me know what you think about it? could we merge this into net11.0 soon?

simonrozsival and others added 5 commits March 30, 2026 10:59
Squashed for clean rebase.
Duplicate was introduced during rebase conflict resolution.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When the source generator cannot determine the target type for an
EventTrigger (e.g., when used inside Style.Triggers or a
ResourceDictionary), it falls back to reflection-based EventTrigger.
This new warning (MAUIX2016) informs developers that the trigger
won't be AOT-safe and suggests ensuring the EventTrigger is nested
directly inside an element's Triggers collection.

The event lookup using .First() is consistent with the existing
ConnectEvent pattern in SetPropertyHelpers.cs and matches the
runtime GetRuntimeEvent behavior (most-derived event wins).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Document why .First() is correct for event lookup (most-derived wins,
  matching GetRuntimeEvent behavior and ConnectEvent in SetPropertyHelpers)
- Add Maui33591_ShadowedEvent test: DerivedButton shadows Button.Clicked
  with a new event, verifies EventTrigger subscribes to the derived one

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Leftover entries without the eventName parameter from an earlier
iteration of the API.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival
Copy link
Copy Markdown
Member Author

/azp run maui-pr-devicetests, maui-pr-uitests

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 2 pipeline(s).

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

Labels

perf/app-size Application Size / Trimming (sub: perf) xsg Xaml sourceGen

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants